version 1.1, 2004/09/09 16:10:22
|
version 1.2, 2004/11/21 12:13:07
|
|
|
|
//%2003//////////////////////////////////////////////////////////////////////// |
|
// |
|
// Copyright (c) 2000, 2001, 2002 BMC Software, Hewlett-Packard Development |
|
// Company, L. P., IBM Corp., The Open Group, Tivoli Systems. |
|
// Copyright (c) 2003 BMC Software; Hewlett-Packard Development Company, L. P.; |
|
// IBM Corp.; EMC Corporation, The Open Group. |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy |
|
// of this software and associated documentation files (the "Software"), to |
|
// deal in the Software without restriction, including without limitation the |
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
|
// sell copies of the Software, and to permit persons to whom the Software is |
|
// furnished to do so, subject to the following conditions: |
|
// |
|
// THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN |
|
// ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED |
|
// "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT |
|
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
|
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
// |
|
//============================================================================== |
|
// |
|
// Authors: David Rosckes (rosckes@us.ibm.com) |
|
// Bert Rivero (hurivero@us.ibm.com) |
|
// Chuck Carmack (carmack@us.ibm.com) |
|
// Brian Lucier (lucier@us.ibm.com) |
|
// |
|
// Modified By: |
|
// |
|
//%///////////////////////////////////////////////////////////////////////////// |
|
|
|
#include "CQLSelectStatement.h" |
|
|
|
#include "CQLSelectStatementRep.h" |
|
|
|
#include <iostream> |
|
|
|
#include <Pegasus/Common/CIMValue.h> |
|
#include <Pegasus/Common/CIMInstance.h> |
|
#include <Pegasus/Common/CIMProperty.h> |
|
#include <Pegasus/Query/QueryCommon/QueryException.h> |
|
#include <Pegasus/Query/QueryCommon/QueryIdentifier.h> |
|
#include <Pegasus/Query/QueryCommon/QueryChainedIdentifier.h> |
|
#include <Pegasus/Common/InternalException.h> |
|
#include <Pegasus/Common/CIMStatusCode.h> |
|
#include <Pegasus/Common/AutoPtr.h> |
|
#include "CQLValue.h" |
|
#include "CQLIdentifier.h" |
|
#include "CQLChainedIdentifier.h" |
|
#include "Cql2Dnf.h" |
|
|
|
// ATTN: TODOs - |
|
// optimize |
|
// documentation |
|
// trace? but this could be used by provider |
|
|
|
PEGASUS_NAMESPACE_BEGIN |
|
|
|
struct PropertyNode |
|
{ |
|
CIMName name; // property name |
|
CIMName scope; // class the property is on |
|
Boolean wildcard; // true if this property is a wildcard |
|
AutoPtr<PropertyNode> sibling; |
|
AutoPtr<PropertyNode> firstChild; |
|
|
|
PropertyNode() {/*PEGASUS_STD(cout) << "new " << this << PEGASUS_STD(endl);*/} |
|
~PropertyNode() {/*PEGASUS_STD(cout) << "delete " << this << PEGASUS_STD(endl);*/} |
|
}; |
|
|
|
|
|
CQLSelectStatementRep::CQLSelectStatementRep() |
|
:SelectStatementRep(), |
|
_hasWhereClause(false), |
|
_contextApplied(false) |
|
{ |
|
|
|
} |
|
|
|
CQLSelectStatementRep::CQLSelectStatementRep(String& inQlang, |
|
String& inQuery, |
|
QueryContext& inCtx) |
|
:SelectStatementRep(inQlang, inQuery, inCtx), |
|
_hasWhereClause(false), |
|
_contextApplied(false) |
|
{ |
|
|
|
} |
|
|
|
CQLSelectStatementRep::CQLSelectStatementRep(String& inQlang, |
|
String& inQuery) |
|
|
|
:SelectStatementRep(inQlang, inQuery), |
|
_hasWhereClause(false), |
|
_contextApplied(false) |
|
{ |
|
|
|
} |
|
|
|
CQLSelectStatementRep::CQLSelectStatementRep(const CQLSelectStatementRep& rep) |
|
:SelectStatementRep(rep), |
|
_selectIdentifiers(rep._selectIdentifiers), |
|
_hasWhereClause(rep._hasWhereClause), |
|
_predicate(rep._predicate), |
|
_contextApplied(rep._contextApplied) |
|
{ |
|
} |
|
|
|
CQLSelectStatementRep::~CQLSelectStatementRep() |
|
{ |
|
} |
|
|
|
CQLSelectStatementRep& CQLSelectStatementRep::operator=(const CQLSelectStatementRep& rhs) |
|
{ |
|
if (this == &rhs) |
|
return *this; |
|
|
|
SelectStatementRep::operator=(rhs); |
|
|
|
_selectIdentifiers = rhs._selectIdentifiers; |
|
_predicate = rhs._predicate; |
|
_contextApplied = rhs._contextApplied; |
|
_hasWhereClause = rhs._hasWhereClause; |
|
|
|
return *this; |
|
} |
|
|
|
void CQLSelectStatementRep::setQueryContext(QueryContext& inCtx) |
|
{ |
|
SelectStatementRep::setQueryContext(inCtx); |
|
} |
|
|
|
Boolean CQLSelectStatementRep::evaluate(const CIMInstance& inCI) |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLRuntimeException(parms); |
|
} |
|
if (!hasWhereClause()) |
|
{ |
|
return true; |
|
} |
|
else |
|
{ |
|
if (!_contextApplied) |
|
applyContext(); |
|
|
|
try |
|
{ |
|
return _predicate.evaluate(inCI, *_ctx); |
|
} |
|
catch (CQLNullContagionException& ) |
|
{ |
|
// The null contagion rule. |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
void CQLSelectStatementRep::applyProjection(CIMInstance& inCI) throw(Exception) |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLRuntimeException(parms); |
|
} |
|
|
|
if (!_contextApplied) |
|
applyContext(); |
|
|
|
// |
|
// Build a tree to represent the projected properties from the select list |
|
// of chained identifiers. This is needed because embedded instances below |
|
// the FROM class form a tree structure when projected. |
|
// |
|
// The design of the tree is to gather all the required properties for |
|
// an instance at a node as child nodes. The root node |
|
// of the tree represents the instance passed in to this function. Below the |
|
// root there can be nodes that are required embedded instance properties. |
|
// The child nodes of these embedded instance nodes represent the required |
|
// properties on the embedded instance (which may themselves be embedded instances). |
|
// |
|
// Each node has a name, which is in 2 parts -- the property name and the |
|
// scope (ie. the class the property is on). This allows the scoping |
|
// operator to be handled correctly, so that the parent instance can be |
|
// checked to see if it is the right class to provide the property. |
|
// Note that the scoping is a base class; ie. the parent instance of a node |
|
// may be a subclass of the scope. |
|
// |
|
|
|
// Set up the root node of the tree. This represents the instance |
|
// passed in. |
|
AutoPtr<PropertyNode> rootNode(new PropertyNode); |
|
Array<QueryIdentifier> fromList = _ctx->getFromList(); |
|
rootNode->name = fromList[0].getName(); // not doing joins |
|
rootNode->scope = fromList[0].getName(); // not used on root, just to fill in the var |
|
rootNode->wildcard = false; |
|
|
|
// Build the tree below the root. |
|
for (Uint32 i = 0; i < _selectIdentifiers.size(); i++) |
|
{ |
|
// Get the chain elements |
|
Array<CQLIdentifier> ids = _selectIdentifiers[i].getSubIdentifiers(); |
|
|
|
PEGASUS_ASSERT(ids.size() > 1); |
|
|
|
PropertyNode * curNode = rootNode.get(); |
|
PropertyNode * curChild = curNode->firstChild.get(); |
|
|
|
// Loop through the identifiers in the chain. |
|
// NOTE: this starts at the position *after* the FROM class |
|
// So, the loop index is always one position after the current node, |
|
// ie. it will become a child node of the current node. |
|
for (Uint32 j = 1; j < ids.size(); j++) |
|
{ |
|
// If the child is wildcarded, then every property exposed by the |
|
// class of the instance at the current node is required. |
|
// Mark the current node as wildcarded. |
|
if (ids[j].isWildcard()) |
|
{ |
|
curNode->wildcard = true; |
|
break; |
|
} |
|
|
|
// Determine if this identifier is already a child node of |
|
// the current node. |
|
Boolean found = false; |
|
while (curChild != NULL && !found) |
|
{ |
|
// The scoping class is either the scope of the identifier |
|
// or the FROM class if the identifier is not scoped. |
|
String scope = fromList[0].getName().getString(); |
|
if (ids[j].isScoped()) |
|
{ |
|
scope = ids[j].getScope(); |
|
} |
|
|
|
if (curChild->name == ids[j].getName() && |
|
String::equalNoCase(curChild->scope.getString(), scope)) |
|
{ |
|
// Name and scope match. The identifier already has a child node. |
|
found = true; |
|
} |
|
else |
|
{ |
|
curChild = curChild->sibling.get(); |
|
} |
|
} |
|
|
|
if (!found) |
|
{ |
|
// The identifier doesn't already have a child node. |
|
// Create a node and add it as a child to the current node. |
|
curChild = new PropertyNode; |
|
curChild->sibling = curNode->firstChild; |
|
curChild->name = ids[j].getName(); |
|
curChild->wildcard = false; |
|
curNode->firstChild.reset(curChild); // safer than using the = operator |
|
} |
|
|
|
// Set the scope for the child node |
|
if (ids[j].isScoped()) |
|
{ |
|
// Child node has a scoping class |
|
PEGASUS_ASSERT(ids[j].getScope().size() > 0); |
|
curChild->scope = CIMName(ids[j].getScope()); |
|
} |
|
else |
|
{ |
|
// Not scoped. The scope is the FROM class. |
|
PEGASUS_ASSERT(j == 1); |
|
PEGASUS_ASSERT(fromList[0].getName().getString().size() > 0); |
|
curChild->scope = fromList[0].getName(); |
|
} |
|
|
|
curNode = curChild; |
|
curChild = curNode->firstChild.get(); |
|
} |
|
} |
|
|
|
// |
|
// Do the projection. |
|
// |
|
|
|
Array<CIMName> requiredProps; |
|
Boolean allPropsRequired = rootNode->wildcard; |
|
|
|
// Loop through the children of the root node. |
|
// The root node represents the FROM class, |
|
// and the child nodes are the required properties on the FROM class. |
|
PropertyNode* childNode = rootNode->firstChild.get(); |
|
while (childNode != NULL) |
|
{ |
|
// Determine if the instance passed in meets the class scoping |
|
// rules for the current child node. |
|
Boolean filterable = isFilterable(inCI, childNode); |
|
|
|
// If the instance is filterable, and the child node has children, |
|
// or is wildcarded, then the child is assumed to be an embedded instance, |
|
// and we need to recurse to apply the projection on the embedded instance. |
|
// (the check for embedded instance is done in the recursive call) |
|
if (filterable && |
|
(childNode->firstChild.get() != NULL || childNode->wildcard)) |
|
{ |
|
// We need to project on an embedded instance property. The steps are to |
|
// remove the embedded instance property from the instance passed in, |
|
// project on that embedded instance property, and then add the projected |
|
// embedded instance property back to the instance passed in. |
|
Uint32 index = inCI.findProperty(childNode->name); |
|
if (index != PEG_NOT_FOUND) |
|
{ |
|
// The embedded instance has the required embedded instance property. |
|
CIMProperty childProp = inCI.getProperty(index); |
|
inCI.removeProperty(index); |
|
applyProjection(childNode, childProp); |
|
inCI.addProperty(childProp); |
|
} |
|
} |
|
|
|
// If the node is not wildcarded, and the instance passed in |
|
// is filterable, then add the current child to the list |
|
// of required properties on the instance passed in. |
|
if (!allPropsRequired && filterable) |
|
{ |
|
requiredProps.append(childNode->name); |
|
} |
|
|
|
childNode = childNode->sibling.get(); |
|
} |
|
|
|
// Remove the properties that are not in the projection. |
|
// This also checks for missing required properties. |
|
removeUnneededProperties(inCI, |
|
allPropsRequired, |
|
fromList[0].getName(), |
|
requiredProps); |
|
} |
|
|
|
void CQLSelectStatementRep::applyProjection(PropertyNode* node, |
|
CIMProperty& nodeProp) |
|
{ |
|
PEGASUS_ASSERT(node->firstChild.get() != NULL); |
|
|
|
// |
|
// The property passed in must be an embedded instance. It is not |
|
// allowed to project properties on embedded classes. |
|
// |
|
// Get the embedded instance from the property. |
|
// |
|
|
|
CIMValue nodeVal = nodeProp.getValue(); |
|
// ATTN - UNCOMMENT when emb objs are supported |
|
/* |
|
if (nodeVal.getType() != CIMTYPE_OBJECT) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.PROP_NOT_EMB", |
|
"The property $0 must contain an embedded object.", |
|
nodeProp.getName().getString()); |
|
throw CQLRuntimeException(parms); |
|
} |
|
*/ |
|
|
|
if (nodeVal.isArray() && |
|
(node->firstChild.get() != NULL || node->wildcard)) |
|
{ |
|
// NOTE - since we are blocking projection of array elements, we can |
|
// assume that if we get here we were told to project a whole array (ie. no index used), as |
|
// an embedded object with properties or wildcard. |
|
// Examples not allowed: SELECT fromClass.someArrayProp.scope::notAllowedProp FROM fromClass |
|
// SELECT fromClass.someArrayProp.* FROM fromClass |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.PROJ_WHOLE_ARRAY", |
|
"CQL requires that array indexing is used on embedded object property $0.", |
|
nodeProp.getName().getString()); |
|
throw CQLRuntimeException(parms); |
|
} |
|
|
|
CIMObject nodeObj; |
|
// ATTN - UNCOMMENT when emb objs are supported |
|
// nodeVal.get(nodeObj); |
|
if (!nodeObj.isInstance()) |
|
{ |
|
// Not allowed to project on a Class |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.PROJ_CLASS", |
|
"CQL does not allow properties to be projected on class $0.", |
|
nodeProp.getName().getString()); |
|
throw CQLRuntimeException(parms); |
|
} |
|
|
|
CIMInstance nodeInst(nodeObj); |
|
|
|
// |
|
// Do the projection. |
|
// |
|
|
|
Array<CIMName> requiredProps; |
|
Boolean allPropsRequired = node->wildcard; |
|
|
|
// Loop through the children of the node. |
|
// The node represents an embedded instance, |
|
// and the child nodes are the required properties on the embedded instance. |
|
PropertyNode * curChild = node->firstChild.get(); |
|
while (curChild != NULL) |
|
{ |
|
// Determine if the embedded instance meets the class scoping |
|
// rules for the current child node |
|
Boolean filterable = isFilterable(nodeInst, curChild); |
|
|
|
// If the embedded instance is filterable, and the child node has children, |
|
// or is wildcarded, then the child is assumed to be an embedded instance, |
|
// and we need to recurse to apply the projection on the embedded instance. |
|
// (the check for embedded instance is done in the recursive call) |
|
if (filterable && |
|
(curChild->firstChild.get() != NULL || curChild->wildcard)) |
|
{ |
|
// We need to project on an embedded instance property. The steps are to |
|
// remove the embedded instance property from the current instance, |
|
// project on that embedded instance property, and then add the projected |
|
// embedded instance property back to the current instance. |
|
Uint32 index = nodeInst.findProperty(curChild->name); |
|
if (index != PEG_NOT_FOUND) |
|
{ |
|
// The embedded instance has the required embedded instance property. |
|
CIMProperty childProp = nodeInst.getProperty(index); |
|
nodeInst.removeProperty(index); |
|
applyProjection(curChild, childProp); |
|
nodeInst.addProperty(childProp); |
|
} |
|
} |
|
|
|
// If the node is not wildcarded, and the embedded instance |
|
// is filterable, then add the current child to the list |
|
// of required properties on the embedded instance. |
|
if (!allPropsRequired && filterable) |
|
{ |
|
// The instance is filterable, add the property to the required list. |
|
requiredProps.append(node->name); |
|
} |
|
|
|
curChild = curChild->sibling.get(); |
|
} |
|
|
|
// Remove the properties that are not in the projection. |
|
// This also checks for missing required properties. |
|
removeUnneededProperties(nodeInst, |
|
allPropsRequired, |
|
nodeInst.getClassName(), |
|
requiredProps); |
|
|
|
// Put the projected instance back into the property. |
|
// ATTN - UNCOMMENT when emb objs are supported |
|
//CIMValue newNodeVal(nodeInst); |
|
//nodeProp.setValue(newNodeVal); |
|
} |
|
|
|
Boolean CQLSelectStatementRep::isFilterable(const CIMInstance& inst, |
|
PropertyNode* node) |
|
{ |
|
// |
|
// Determine if an instance is filterable for a scoped property (ie. its |
|
// type is the scoping class or a subclass of the scoping class where the |
|
// property exists) |
|
// |
|
// Note that an instance that is unfilterable is not considered |
|
// an error. In CQL, an instance that is not of the required scope |
|
// would cause a NULL to be placed in the result set column for the |
|
// property. However since we are not implementing result set in stage1, |
|
// just skip the property. This can lead to an instance having |
|
// NO required properties even though it is derived from the FROM class. |
|
// This can easily happen if the scoping operator is used. |
|
// |
|
|
|
Boolean filterable = false; |
|
if (inst.getClassName() == node->scope) |
|
{ |
|
// The instance's class is the same as the required scope |
|
filterable = true; |
|
} |
|
else |
|
{ |
|
try |
|
{ |
|
if (_ctx->isSubClass(node->scope, inst.getClassName())) |
|
{ |
|
// The instance's class is a subclass of the required scope. |
|
filterable = true; |
|
} |
|
} |
|
catch (CIMException& ce) |
|
{ |
|
if (ce.getCode() == CIM_ERR_INVALID_CLASS || |
|
ce.getCode() == CIM_ERR_NOT_FOUND) |
|
{ |
|
// The scoping class was not found in the schema. |
|
// Just swallow this error because according to the |
|
// spec we should be putting NULL in the result column, |
|
// which means skipping the property on the instance. |
|
; |
|
} |
|
} |
|
} |
|
|
|
return filterable; |
|
} |
|
|
|
void CQLSelectStatementRep::removeUnneededProperties(CIMInstance& inst, |
|
Boolean& allPropsRequired, |
|
const CIMName& allPropsClass, |
|
Array<CIMName>& requiredProps) |
|
{ |
|
// Implementation note: |
|
// Scoping operator before a wildcard is not allowed: |
|
// Example: |
|
// SELECT fromclass.embobj1.scope1::* FROM fromclass |
|
// |
|
// However, the following are allowed: |
|
// SELECT fromclass.embobj1.* FROM fromclass |
|
// (this means that all the properties on the class of instance embobj1 |
|
// are required) |
|
// |
|
// SELECT fromclass.* FROM fromclass |
|
// (this means that all the properties on class fromclass are required |
|
// to be on the instance being projected, not including any |
|
// properties on a subclass of fromclass) |
|
|
|
// If all properties are required (ie. wildcarded), then rebuild the |
|
// required property list from all the properties on the classname passed in |
|
// This is either the FROM class or the class of an embedded instance. |
|
if (allPropsRequired) |
|
{ |
|
requiredProps.clear(); |
|
CIMClass cls = _ctx->getClass(allPropsClass); |
|
Array<CIMName> clsProps; |
|
for (Uint32 i = 0; i < cls.getPropertyCount(); i++) |
|
{ |
|
requiredProps.append(cls.getProperty(i).getName()); |
|
} |
|
} |
|
|
|
// Find out what properties are on the instance. |
|
Array<CIMName> supportedProps; |
|
for (Uint32 i = 0; i < inst.getPropertyCount(); i++) |
|
{ |
|
supportedProps.append(inst.getProperty(i).getName()); |
|
} |
|
|
|
// Check that all required properties are on the instance. |
|
for (Uint32 i = 0; i < requiredProps.size(); i++) |
|
{ |
|
if (!containsProperty(requiredProps[i], supportedProps)) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.PROJ_MISSING_PROP", |
|
"The property $0 is missing on the instance of class $1.", |
|
requiredProps[i].getString(), inst.getClassName().getString()); |
|
throw CQLRuntimeException(parms); |
|
} |
|
} |
|
|
|
// Remove the properties on the instance that are not required. |
|
for (Uint32 i = 0; i < supportedProps.size(); i++) |
|
{ |
|
if (!containsProperty(supportedProps[i], requiredProps)) |
|
{ |
|
Uint32 index = inst.findProperty(supportedProps[i]); |
|
PEGASUS_ASSERT(index != PEG_NOT_FOUND); |
|
inst.removeProperty(index); |
|
} |
|
} |
|
} |
|
|
|
// |
|
// Validates that all the chained identifiers in the statement meet |
|
// the rules in the CQL spec vs.the class definitions in the repository |
|
// |
|
void CQLSelectStatementRep::validate() throw(Exception) |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLValidationException(parms); |
|
} |
|
|
|
if (!_contextApplied) |
|
applyContext(); |
|
|
|
for (Uint32 i = 0; i < _selectIdentifiers.size(); i++) |
|
{ |
|
validateProperty(_selectIdentifiers[i]); |
|
} |
|
|
|
Array<QueryChainedIdentifier> _whereIdentifiers = _ctx->getWhereList(); |
|
for (Uint32 i = 0; i < _whereIdentifiers.size(); i++) |
|
{ |
|
validateProperty(_whereIdentifiers[i]); |
|
} |
|
} |
|
|
|
// |
|
// Validates that the chained identifier meets all the rules in the CQL |
|
// spec vs.the class definitions in the repository |
|
// |
|
void CQLSelectStatementRep::validateProperty(QueryChainedIdentifier& chainId) |
|
{ |
|
// Note: applyContext has been called beforehand |
|
|
|
Array<QueryIdentifier> ids = chainId.getSubIdentifiers(); |
|
|
|
Uint32 startingPos = 0; |
|
CIMName curContext; |
|
for (Uint32 pos = startingPos; pos < ids.size(); pos++) |
|
{ |
|
// Determine the current class context |
|
if (ids[pos].isScoped()) |
|
{ |
|
// The chain element is scoped. Use the scoping |
|
// class as the current context. Note: this depends |
|
// on applyContext resolving the class aliases before we get here. |
|
curContext = CIMName(ids[pos].getScope()); |
|
} |
|
else |
|
{ |
|
// The chain element is not scoped. Assume that we are |
|
// before a position that is required to be scoped. |
|
// (applyContext validates that the chained identifier |
|
// has scoped identifiers in the required positions). |
|
// The current context is the name at the first position, |
|
// which must be a classname (ie. right side of ISA where |
|
// the only element in the chain is a classname, or |
|
// cases where the FROM class is the first, and maybe only, |
|
// element of the chain). |
|
PEGASUS_ASSERT((pos < startingPos + 2) || ids[pos].isWildcard()); |
|
curContext = ids[0].getName(); |
|
} |
|
|
|
// Get the class definition of the current class context |
|
CIMClass classDef; |
|
try |
|
{ |
|
classDef = _ctx->getClass(curContext); |
|
} |
|
catch (CIMException& ce) |
|
{ |
|
if (ce.getCode() == CIM_ERR_NOT_FOUND || |
|
ce.getCode() == CIM_ERR_INVALID_CLASS) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.VAL_CLASS_NOT_EXIST", |
|
"The class $0 does not exist.", |
|
curContext.getString()); |
|
throw CQLValidationException(parms); |
|
} |
|
} |
|
|
|
// Now do the checks for properties existing on the current class context |
|
// and the class relationship rules in section 5.4.1. |
|
// Only do these checks if the chain id has a property. |
|
if (pos > startingPos) |
|
{ |
|
if (ids[pos].isWildcard()) |
|
{ |
|
// The wildcard is at the end (verified by applyContext), so |
|
// no checking is required at this position. |
|
continue; |
|
} |
|
|
|
// Determine if the property name at the current position |
|
// exists on the current class context. |
|
Uint32 propertyIndex = classDef.findProperty(ids[pos].getName()); |
|
if (propertyIndex == PEG_NOT_FOUND) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.VAL_PROP_NOT_ON_CLASS", |
|
"The property $0 does not exist on class $1.", |
|
ids[pos].getName().getString(), classDef.getClassName().getString()); |
|
throw CQLValidationException(parms); |
|
} |
|
|
|
// Checking class relationship rules in section 5.4.1. |
|
// For validateProperties, this only applies to the first |
|
// property in the chain. This is because once we get into |
|
// embedded properties we don't know what the class will be |
|
// until we have an instance. |
|
if ((pos == (startingPos+1)) && !curContext.equal(ids[0].getName())) |
|
{ |
|
// Its the first property, and the class context is not the FROM class. |
|
// Check the class relationship between the scoping class and the FROM class. |
|
if (_ctx->getClassRelation(ids[0].getName(), curContext) == QueryContext::NOTRELATED) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.VAL_SCOPE_VIOLATION", |
|
"The class $0 is not a superclass, subclass, or the same class as $1.", |
|
curContext.getString(), ids[0].getName().getString()); |
|
throw CQLValidationException(parms); |
|
} |
|
} |
|
|
|
// If the current position implies an embedded object, then |
|
// verify that the property is an embedded object |
|
if ((pos > startingPos) && (pos < (ids.size() - 1))) |
|
{ |
|
CIMProperty embObj = classDef.getProperty(propertyIndex); |
|
CIMName qual("EmbeddedObject"); |
|
if (embObj.findQualifier(qual) == PEG_NOT_FOUND) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.PROP_NOT_EMB", |
|
"The property $0 must be an embedded object.", |
|
embObj.getName().getString()); |
|
throw CQLValidationException(parms); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
CIMName CQLSelectStatementRep::lookupFromClass(const String& lookup) |
|
{ |
|
QueryIdentifier id = _ctx->findClass(lookup); |
|
|
|
return id.getName(); |
|
} |
|
|
|
Array<CIMObjectPath> CQLSelectStatementRep::getClassPathList() |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLRuntimeException(parms); |
|
} |
|
|
|
Array<QueryIdentifier> ids = _ctx->getFromList(); |
|
PEGASUS_ASSERT(ids.size() == 1); // no joins yet |
|
|
|
// No wbem-uri support yet. |
|
CIMObjectPath path(String::EMPTY, _ctx->getNamespace(), ids[0].getName()); |
|
|
|
Array<CIMObjectPath> paths; |
|
paths.append(path); |
|
|
|
return paths; |
|
} |
|
|
|
CIMPropertyList CQLSelectStatementRep::getPropertyList(const CIMObjectPath& inClassName) |
|
{ |
|
return getPropertyListInternal(inClassName, true, true); |
|
} |
|
|
|
CIMPropertyList CQLSelectStatementRep::getSelectPropertyList(const CIMObjectPath& inClassName) |
|
{ |
|
return getPropertyListInternal(inClassName, true, false); |
|
} |
|
|
|
CIMPropertyList CQLSelectStatementRep::getWherePropertyList(const CIMObjectPath& inClassName) |
|
{ |
|
return getPropertyListInternal(inClassName, false, true); |
|
} |
|
|
|
CIMPropertyList CQLSelectStatementRep::getPropertyListInternal(const CIMObjectPath& inClassName, |
|
Boolean includeSelect, |
|
Boolean includeWhere) |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLRuntimeException(parms); |
|
} |
|
|
|
if (!_contextApplied) |
|
applyContext(); |
|
|
|
// Get the classname. Note: since wbem-uri is not supported yet, |
|
// only use the classname part of the path. |
|
CIMName className = inClassName.getClassName(); |
|
if (className.isNull()) |
|
{ |
|
// If the caller passed in an empty className, then the |
|
// FROM class is to be used. |
|
className = _ctx->getFromList()[0].getName(); |
|
} |
|
|
|
Boolean isWildcard; |
|
Array<CIMName> reqProps; |
|
Array<CIMName> matchedScopes; |
|
Array<CIMName> unmatchedScopes; |
|
|
|
// Add required properties from the select list. |
|
if (includeSelect) |
|
{ |
|
for (Uint32 i = 0; i < _selectIdentifiers.size(); i++) |
|
{ |
|
isWildcard = addRequiredProperty(reqProps, |
|
className, |
|
_selectIdentifiers[i], |
|
matchedScopes, |
|
unmatchedScopes); |
|
|
|
if (isWildcard) |
|
{ |
|
// If any wildcard is found then all properties are required. |
|
// Return null property list to indicate all properties required. |
|
return CIMPropertyList(); |
|
} |
|
} |
|
} |
|
|
|
// Add required properties from the WHERE clause. |
|
if (includeWhere) |
|
{ |
|
Array<QueryChainedIdentifier> _whereIdentifiers = _ctx->getWhereList(); |
|
for (Uint32 i = 0; i < _whereIdentifiers.size(); i++) |
|
{ |
|
isWildcard = addRequiredProperty(reqProps, |
|
className, |
|
_whereIdentifiers[i], |
|
matchedScopes, |
|
unmatchedScopes); |
|
|
|
// Wildcards are not allowed in the WHERE clause |
|
PEGASUS_ASSERT(!isWildcard); |
|
} |
|
} |
|
|
|
// Check if every property on the class is required. |
|
CIMClass theClass = _ctx->getClass(className); |
|
Uint32 propCnt = theClass.getPropertyCount(); |
|
Boolean allProps = true; |
|
for (Uint32 i = 0; i < propCnt; i++) |
|
{ |
|
if (!containsProperty(theClass.getProperty(i).getName(), reqProps)) |
|
{ |
|
allProps = false; |
|
break; |
|
} |
|
} |
|
|
|
if (allProps) |
|
{ |
|
// Return null property list to indicate all properties are required. |
|
return CIMPropertyList(); |
|
} |
|
else |
|
{ |
|
// Return the required property list. Note that it is possible to return |
|
// an empty list in the case of no required properties for the classname |
|
// passed in. This can happen when the scoping operator is used. |
|
return CIMPropertyList(reqProps); |
|
} |
|
} |
|
|
|
Boolean CQLSelectStatementRep::addRequiredProperty(Array<CIMName>& reqProps, |
|
CIMName& className, |
|
QueryChainedIdentifier& chainId, |
|
Array<CIMName>& matchedScopes, |
|
Array<CIMName>& unmatchedScopes) |
|
{ |
|
// |
|
// Implementation notes: |
|
// This function does not look for required properties on embedded objects. |
|
// This function assumes that applyContext has been called. |
|
// |
|
|
|
Array<QueryIdentifier> ids = chainId.getSubIdentifiers(); |
|
|
|
// After applyContext has been called, a single element |
|
// chained identifier refers to either an instance of the |
|
// FROM class, or is the classname on the right side of ISA. |
|
if (ids.size() == 1) |
|
{ |
|
// This identifier is not a property name |
|
return false; |
|
} |
|
|
|
if (ids[1].isSymbolicConstant()) |
|
{ |
|
// Non-embedded symbolic constants are not properties |
|
// Note that an embedded symbolic constant like this: |
|
// fromclass.embobj.scope::someprop#'ok' |
|
// implies that embobj is a required property, because |
|
// embobj could be null, and that affects how the |
|
// identifier is evaluated. |
|
return false; |
|
} |
|
|
|
// Since applyContext has been called, the first chain element |
|
// will be the FROM class, so go to the 2nd chain element. |
|
if (ids[1].isScoped()) |
|
{ |
|
// The 2nd chain element is a scoped property. |
|
// Eg. fromclass.someclass::someprop |
|
|
|
// Determine the class that the property is being scoped to. |
|
// This could be the FROM class, or some other class not in the FROM list |
|
CIMName scopingClass = CIMName(ids[1].getScope()); |
|
|
|
// Check if the scoping class is the same as the class passed in. |
|
if (scopingClass == className) |
|
{ |
|
// The scoping class is the same as the class passed, |
|
// add the property if not already added |
|
if (!containsProperty(ids[1].getName(), reqProps)) |
|
{ |
|
reqProps.append(ids[1].getName()); |
|
} |
|
} |
|
else |
|
{ |
|
// The scoping class is not the same as the class passed. |
|
// Check if we already know that the scoping class is a subclass |
|
// of the class passed in |
|
if (containsProperty(scopingClass, unmatchedScopes)) |
|
{ |
|
// Scoping class is a subclass. |
|
return false; |
|
} |
|
|
|
// Check if we already know that the scoping class is a superclass |
|
// of the class passed in |
|
Boolean isSuper = false; |
|
if (containsProperty(scopingClass, matchedScopes)) |
|
{ |
|
// Scoping class is a superclass. |
|
isSuper = true; |
|
} |
|
|
|
// Check if the scoping class is a superclass of the class passed in |
|
if (isSuper || _ctx->isSubClass(scopingClass, className)) |
|
{ |
|
// Scoping class is a superclass of the class passed in. |
|
if (!isSuper) |
|
{ |
|
// Save this information |
|
matchedScopes.append(scopingClass); |
|
} |
|
|
|
// Add to the required property list if not already there. |
|
if (!containsProperty(ids[1].getName(), reqProps)) |
|
{ |
|
reqProps.append(ids[1].getName()); |
|
} |
|
} |
|
else |
|
{ |
|
// Scoping class is not superclass of class passed in. |
|
// Save this information. |
|
unmatchedScopes.append(scopingClass); |
|
} |
|
} // end else scoping class not == class passed in |
|
} // end if first id is scoped |
|
else |
|
{ |
|
// The 2nd chain element is an unscoped property |
|
// Check if it is wildcarded |
|
if (ids[1].isWildcard()) |
|
{ |
|
// Wildcard. |
|
// If the class passed in is the FROM class, then |
|
// all properties are required on the class passed in. |
|
CIMName fromClassName = _ctx->getFromList()[0].getName(); |
|
if (fromClassName == className) |
|
{ |
|
return true; |
|
} |
|
|
|
// Add all the properties on the FROM class to |
|
// the required property list. |
|
CIMClass fromClass = _ctx->getClass(fromClassName); |
|
for (Uint32 n = 0; n < fromClass.getPropertyCount(); n++) |
|
{ |
|
// Add to the required property list if not already there. |
|
if (!containsProperty(fromClass.getProperty(n).getName(), reqProps)) |
|
{ |
|
reqProps.append(fromClass.getProperty(n).getName()); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// Implementation note: |
|
// Since this API assumes that the class passed in |
|
// is the FROM class or a subclass of the FROM class, |
|
// AND validateProperties can be called to check if |
|
// unscoped properties are on the FROM class, |
|
// we can just add the required property because |
|
// it is assumed to be on the FROM class. |
|
|
|
// Add to the required property list if not already there. |
|
if (!containsProperty(ids[1].getName(), reqProps)) |
|
{ |
|
reqProps.append(ids[1].getName()); |
|
} |
|
} |
|
|
|
// Indicate the required property is not a wildcard |
|
return false; |
|
} |
|
|
|
Array<CQLChainedIdentifier> CQLSelectStatementRep::getSelectChainedIdentifiers() |
|
{ |
|
if (!_contextApplied) |
|
applyContext(); |
|
|
|
return _selectIdentifiers; |
|
} |
|
|
|
Array<CQLChainedIdentifier> CQLSelectStatementRep::getWhereChainedIdentifiers() |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLRuntimeException(parms); |
|
} |
|
|
|
if (!_contextApplied) |
|
applyContext(); |
|
|
|
Array<QueryChainedIdentifier> qChainIds = _ctx->getWhereList(); |
|
Array<CQLChainedIdentifier> cqlChainIds; |
|
for (Uint32 i = 0; i < qChainIds.size(); i++) |
|
{ |
|
Array<QueryIdentifier> qSubs = qChainIds[i].getSubIdentifiers(); |
|
CQLChainedIdentifier cqlChainId; |
|
for (Uint32 j = 0; j < qSubs.size(); j++) |
|
{ |
|
cqlChainId.append(qSubs[j]); |
|
} |
|
|
|
cqlChainIds.append(cqlChainId); |
|
} |
|
|
|
return cqlChainIds; |
|
} |
|
|
|
Boolean CQLSelectStatementRep::containsProperty(const CIMName& name, |
|
const Array<CIMName>& props) |
|
{ |
|
for (Uint32 i = 0; i < props.size(); i++) |
|
{ |
|
if (props[i] == name) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void CQLSelectStatementRep::appendClassPath(const CQLIdentifier& inIdentifier) |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLValidationException(parms); |
|
} |
|
_ctx->insertClassPath(inIdentifier); |
|
} |
|
|
|
void CQLSelectStatementRep::setPredicate(const CQLPredicate& inPredicate) |
|
{ |
|
_predicate = inPredicate; |
|
} |
|
|
|
CQLPredicate CQLSelectStatementRep::getPredicate() const |
|
{ |
|
return _predicate; |
|
} |
|
|
|
void CQLSelectStatementRep::insertClassPathAlias(const CQLIdentifier& inIdentifier, |
|
String inAlias) |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLValidationException(parms); |
|
} |
|
_ctx->insertClassPath(inIdentifier,inAlias); |
|
} |
|
|
|
void CQLSelectStatementRep::appendSelectIdentifier(const CQLChainedIdentifier& x) |
|
{ |
|
_selectIdentifiers.append(x); |
|
} |
|
|
|
void CQLSelectStatementRep::applyContext() |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLRuntimeException(parms); |
|
} |
|
|
|
for (Uint32 i = 0; i < _selectIdentifiers.size(); i++) |
|
{ |
|
_selectIdentifiers[i].applyContext(*_ctx); |
|
checkWellFormedIdentifier(_selectIdentifiers[i], true); |
|
} |
|
|
|
if (hasWhereClause()) |
|
{ |
|
_predicate.applyContext(*_ctx); |
|
|
|
// Note: must be after call to predicate's applyContext |
|
Array<QueryChainedIdentifier> _whereIdentifiers = _ctx->getWhereList(); |
|
for (Uint32 i = 0; i < _whereIdentifiers.size(); i++) |
|
{ |
|
checkWellFormedIdentifier(_whereIdentifiers[i], false); |
|
} |
|
} |
|
|
|
_contextApplied = true; |
|
} |
|
|
|
void CQLSelectStatementRep::checkWellFormedIdentifier(const QueryChainedIdentifier& chainId, |
|
Boolean isSelectListId) |
|
{ |
|
// This function assumes that applyContext has been called. |
|
Array<QueryIdentifier> ids = chainId.getSubIdentifiers(); |
|
|
|
if (ids.size() == 0) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.EMPTY_CHAIN", |
|
"An empty chained identifier was found."); |
|
throw CQLSyntaxErrorException(parms); |
|
} |
|
|
|
if (ids.size() == 1 && isSelectListId) |
|
{ |
|
// Single element chain ids are not allow in the select list. |
|
// The select list can only have properties. |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.SINGLE_CHID_IN_SELECT", |
|
"A property on the FROM class must be selected."); |
|
throw CQLSyntaxErrorException(parms); |
|
} |
|
|
|
if (ids[0].isScoped() |
|
|| ids[0].isWildcard() |
|
|| ids[0].isSymbolicConstant() |
|
|| ids[0].isArray()) |
|
{ |
|
// The first identifier must be a classname (it could be the FROM class, or |
|
// some other class for the right side of ISA) |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.FIRST_ID_ILLEGAL", |
|
"The chained identifier $0 is illegal.", |
|
chainId.toString()); |
|
throw CQLSyntaxErrorException(parms); |
|
} |
|
|
|
Uint32 startingPos = 1; |
|
for (Uint32 pos = startingPos; pos < ids.size(); pos++) |
|
{ |
|
if (ids[pos].isArray() && isSelectListId) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.ARRAY_IN_SELECT", |
|
"The identifier $0 of $1 in the SELECT list cannot use an array index.", |
|
ids[pos].toString(), chainId.toString()); |
|
throw CQLSyntaxErrorException(parms); |
|
} |
|
|
|
if (ids[pos].isSymbolicConstant() && isSelectListId) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.SYMCONST_IN_SELECT", |
|
"The identifier $0 of $1 in the SELECT list cannot use a symbolic constant.", |
|
ids[pos].toString(), chainId.toString()); |
|
throw CQLSyntaxErrorException(parms); |
|
} |
|
|
|
if (ids[pos].isSymbolicConstant() && pos != (ids.size() -1)) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.SYMCONST_NOT_LAST", |
|
"The symbolic constant identifier $0 of $1 must be the last element.", |
|
ids[pos].toString(), chainId.toString()); |
|
throw CQLSyntaxErrorException(parms); |
|
} |
|
|
|
if (ids[pos].isWildcard()) |
|
{ |
|
if ( !isSelectListId) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.WILD_IN_WHERE", |
|
"The identifier $0 of $1 in the WHERE list cannot use a wildcard.", |
|
ids[pos].toString(), chainId.toString()); |
|
throw CQLSyntaxErrorException(parms); |
|
} |
|
|
|
if ( pos != ids.size() - 1) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.WILD_NOT_END", |
|
"The wildcard identifier $0 of $1 must be the last element.", |
|
ids[pos].toString(), chainId.toString()); |
|
throw CQLSyntaxErrorException(parms); |
|
} |
|
} |
|
|
|
if (pos > startingPos && !ids[pos].isWildcard()) |
|
{ |
|
if (!ids[pos].isScoped()) |
|
{ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.EMB_PROP_NOT_SCOPED", |
|
"The identifier $0 of $1 must use the scope operator.", |
|
ids[pos].toString(), chainId.toString()); |
|
throw CQLSyntaxErrorException(parms); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CQLSelectStatementRep::normalizeToDOC() |
|
{ |
|
if (!_contextApplied) |
|
applyContext(); |
|
|
|
if(_hasWhereClause){ |
|
Cql2Dnf DNFer(_predicate); |
|
_predicate = DNFer.getDnfPredicate(); |
|
} |
|
} |
|
|
|
String CQLSelectStatementRep::toString() |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLRuntimeException(parms); |
|
} |
|
|
|
String s("SELECT "); |
|
for(Uint32 i = 0; i < _selectIdentifiers.size(); i++){ |
|
if((i > 0) && (i < _selectIdentifiers.size())){ |
|
s.append(","); |
|
} |
|
s.append(_selectIdentifiers[i].toString()); |
|
} |
|
|
|
s.append(" "); |
|
s.append(_ctx->getFromString()); |
|
|
|
if(_hasWhereClause){ |
|
s.append(" WHERE "); |
|
s.append(_predicate.toString()); |
|
} |
|
return s; |
|
} |
|
|
|
void CQLSelectStatementRep::setHasWhereClause() |
|
{ |
|
_hasWhereClause = true; |
|
} |
|
|
|
Boolean CQLSelectStatementRep::hasWhereClause() |
|
{ |
|
return _hasWhereClause; |
|
} |
|
|
|
void CQLSelectStatementRep::clear() |
|
{ |
|
if(_ctx == NULL){ |
|
MessageLoaderParms parms("CQL.CQLSelectStatementRep.QUERY_CONTEXT_IS_NULL", |
|
"Trying to process a query with a NULL Query Context."); |
|
throw CQLRuntimeException(parms); |
|
} |
|
|
|
_ctx->clear(); |
|
_hasWhereClause = false; |
|
_contextApplied = false; |
|
_predicate = CQLPredicate(); |
|
_selectIdentifiers.clear(); |
|
} |
|
|
|
PEGASUS_NAMESPACE_END |