/* * Copyright (C) 2012, Antonin Houska */ #include "postgres.h" #include "utils/builtins.h" #include "xpath.h" #include "xmlnode_util.h" static void evaluateXPathOperand(XPathExprState exprState, XPathExprOperand operand, unsigned short recursionLevel, XPathExprOperandValue result); static void evaluateXPathFunction(XPathExprState exprState, XPathExpression funcExpr, unsigned short recursionLevel, XPathExprOperandValue result); static void prepareLiteral(XPathExprState exprState, XPathExprOperand operand); static void evaluateBinaryOperator(XPathExprState exprState, XPathExprOperandValue valueLeft, XPathExprOperandValue valueRight, XPathExprOperator operator, XPathExprOperandValue result); static bool considerSubScan(XPathElement xpEl, XMLNodeHdr node, XMLScan xscan, bool subScanJustDone); static void addNodeToIgnoreList(XMLNodeHdr node, XMLScan scan); static void substituteAttributes(XPathExprState exprState, XMLCompNodeHdr element); static void substitutePaths(XPathExpression expression, XPathExprState exprState, XMLCompNodeHdr element, xmldoc document, XPathHeader xpHdr); static void substituteFunctions(XPathExpression expression, XPathExprState exprState, XMLScan xscan); static void compareNumValues(XPathExprState exprState, XPathExprOperandValue valueLeft, XPathExprOperandValue valueRight, XPathExprOperator operator, XPathExprOperandValue result); static bool xmlNumberToDouble(XPathExprState exprState, XPathExprOperandValue numOperand, double *simple); static void compareNumbers(double numLeft, double numRight, XPathExprOperator operator, XPathExprOperandValue result); static bool compareNodeSets(XPathExprState exprState, XPathNodeSet ns1, XPathNodeSet ns2, XPathExprOperator operator); static bool compareElements(XMLCompNodeHdr elLeft, XMLCompNodeHdr elRight); static bool compareValueToNode(XPathExprState exprState, XPathExprOperandValue value, XMLNodeHdr node, XPathExprOperator operator); static void compareNumToStr(double num, char *numStr, XPathExprOperator operator, XPathExprOperandValue result); static bool compareValueToNodeSet(XPathExprState exprState, XPathExprOperandValue value, XPathNodeSet ns, XPathExprOperator operator); static bool isOnIgnoreList(XMLNodeHdr node, XMLScan scan); static void getUnion(XPathExprState exprState, XPathNodeSet setLeft, XPathNodeSet setRight, XPathNodeSet setResult); static void copyNodeSet(XPathExprState exprState, XPathNodeSet nodeSet, XMLNodeHdr *output, unsigned int *position); static int nodePtrComparator(const void *arg1, const void *arg2); static void flipOperandValue(XPathExprOperandValue value, XPathExprState exprState); /* * 'xscan' - the scan to be initialized 'xpath' - location path to be used * for this scan * * 'xpHdr' - header of the 'path set' the 'xpath' is contained in. * We may need it if one or more predicates in 'xpath' contain other paths (subpaths) as operands. * * 'scanRoot' - node where the scan starts. This node won't be tested itself, the scan starts * one level lower. Typically, this is document node. */ void initXMLScan(XMLScan xscan, XMLScan parent, XPath xpath, XPathHeader xpHdr, XMLCompNodeHdr scanRoot, xmldoc document, bool checkUniqueness) { XMLScanOneLevel firstLevel; xscan->done = false; xscan->xpath = xpath; xscan->xpathHeader = xpHdr; xscan->xpathRoot = 0; if (xscan->xpath->depth > 0) { firstLevel = (XMLScanOneLevel) palloc(xscan->xpath->depth * sizeof(XMLScanOneLevelData)); firstLevel->parent = scanRoot; firstLevel->nodeRefPtr = XNODE_FIRST_REF(scanRoot); firstLevel->siblingsLeft = scanRoot->children; firstLevel->contextPosition = 0; firstLevel->contextSizeKnown = false; firstLevel->up = (parent == NULL) ? NULL : XMLSCAN_CURRENT_LEVEL(parent); xscan->state = firstLevel; } else { xscan->state = NULL; } xscan->depth = 0; xscan->skip = false; xscan->document = document; xscan->parent = parent; if (checkUniqueness) { /* * Only top-level scan can have the container, to share it with * sub-scans. */ if (xscan->parent == NULL) { xscan->ignoreList = (XMLNodeContainer) palloc(sizeof(XMLNodeContainerData)); xmlnodeContainerInit(xscan->ignoreList); } else { xscan->ignoreList = xscan->parent->ignoreList; } } else { xscan->ignoreList = NULL; } xscan->subScan = NULL; } void finalizeXMLScan(XMLScan xscan) { if (xscan->state != NULL) { pfree(xscan->state); xscan->state = NULL; } if (xscan->ignoreList != NULL && xscan->parent == NULL) { xmlnodeContainerFree(xscan->ignoreList); pfree(xscan->ignoreList); } } /* * Find the next matching node. The XML scan itself. * * 'xscan' - scan status. It remembers where the last scan ended. If used for consequent call, the function * will continue right after that position. * If caller changes the document between calls to this function, he is responsible for adjusting the * scan status and - if some exist - state of any sub-scan. In addition, 'xscan->document' of the scan and * sub-scans has to point to the new (modified) document. * * * Returns a pointer to the matching node. This points to inside 'xscan->document' and therefore * must not be pfree'd. */ XMLNodeHdr getNextXMLNode(XMLScan xscan) { if (xscan->state == NULL) { elog(ERROR, "XML scan state is not well initialized"); } while (true) { XMLScanOneLevel scanLevel = XMLSCAN_CURRENT_LEVEL(xscan); while (scanLevel->siblingsLeft > 0) { XPathElement xpEl; XMLCompNodeHdr eh = scanLevel->parent; XMLNodeHdr currentNode = NULL; XMLNodeKind cKind; /* * Indicates later in the loop whether sub-scan has finished in * the current iteration. */ bool subScanDone = false; if (scanLevel->nodeRefPtr == NULL) { if (scanLevel->siblingsLeft != 1) { elog(ERROR, "undefined state of xml scan"); } /* Done for the current level. */ scanLevel->siblingsLeft--; break; } xpEl = XPATH_CURRENT_LEVEL(xscan); if (xscan->subScan != NULL) { XMLNodeHdr subNode = getNextXMLNode(xscan->subScan); /* * isOnIgnoreList() is not used here on return because the * check has been performed when returning from the * getNextXMLNode() above. * * addToIgnoreList() is not used bellow for the same reason. */ if (subNode != NULL) { return subNode; } else { finalizeXMLScan(xscan->subScan); pfree(xscan->subScan); xscan->subScan = NULL; xscan->skip = true; subScanDone = true; } } currentNode = (XMLNodeHdr) ((char *) eh - readXMLNodeOffset(&scanLevel->nodeRefPtr, XNODE_GET_REF_BWIDTH(eh), false)); /* CDATA should be treated as text node. */ cKind = currentNode->kind == XMLNODE_CDATA ? XMLNODE_TEXT : currentNode->kind; if (xscan->skip) { /* * The current node is already processed, but the possible * descendant axe hasn't been considered yet. * * considerSubScan() is used twice in the loop It would be * possible to call it just once, at the beginning. But thus * the descendants would be returned before the element * itself. */ if (considerSubScan(xpEl, currentNode, xscan, subScanDone)) { continue; } xscan->skip = false; /* * The current node is processed and no subscan is needed, so * just move ahead. */ scanLevel->nodeRefPtr = XNODE_NEXT_REF(scanLevel->nodeRefPtr, eh); scanLevel->siblingsLeft--; continue; } /* * Evaluate the node according to its type */ if (cKind == XMLNODE_ELEMENT && !(XPATH_LAST_LEVEL(xscan) && xscan->xpath->targNdKind != XMLNODE_ELEMENT)) { XMLCompNodeHdr currentElement = (XMLCompNodeHdr) currentNode; char *childFirst = XNODE_FIRST_REF(currentElement); char *name = XNODE_ELEMENT_NAME(currentElement); char *nameTest = xpEl->name; if (XPATH_LAST_LEVEL(xscan) && xscan->xpath->targNdKind == XMLNODE_NODE && !isOnIgnoreList(currentNode, xscan)) { xscan->skip = true; addNodeToIgnoreList(currentNode, xscan); return currentNode; } if (strcmp(name, nameTest) == 0) { bool passed = true; scanLevel->contextPosition++; if (xpEl->hasPredicate) { XPathExprOperandValueData result; char *exprUnaligned; XPathExpression exprOrig; XPathExprState exprState; exprUnaligned = (char *) xpEl + sizeof(XPathElementData) + strlen(nameTest); exprOrig = (XPathExpression) TYPEALIGN(XPATH_ALIGNOF_EXPR, exprUnaligned); exprState = prepareXPathExpression(exprOrig, currentElement, xscan->document, xscan->xpathHeader, xscan); evaluateXPathExpression(exprState, exprState->expr, 0, &result); if (result.isNull) { passed = false; } else { if (result.type == XPATH_VAL_NUMBER) { XPathExprOperandValueData resSigned; /* * The cast is used to get rid of the * 'negative' flag. */ castXPathExprOperandToNum(exprState, &result, &resSigned, false); if (resSigned.v.num <= 0.0f || resSigned.v.num > XMLNODE_MAX_CHILDREN) { passed = false; } else { int2 posInt2 = DatumGetInt16(DirectFunctionCall1Coll(dtoi2, InvalidOid, Float8GetDatum(resSigned.v.num))); passed = (scanLevel->contextPosition == posInt2); } } else { XPathExprOperandValueData resultCast; castXPathExprOperandToBool(exprState, &result, &resultCast); passed = resultCast.v.boolean; } } freeExpressionState(exprState); } if (!passed) { XMLCompNodeHdr eh = scanLevel->parent; scanLevel->nodeRefPtr = XNODE_NEXT_REF(scanLevel->nodeRefPtr, eh); scanLevel->siblingsLeft--; continue; } if (!XPATH_LAST_LEVEL(xscan) && XNODE_HAS_CHILDREN(currentElement)) { /* * Avoid descent to lower levels if all nodes at the * next level are attributes and attribute is not * target of the xpath */ if ((currentElement->common.flags & XNODE_EMPTY) && xscan->xpath->targNdKind != XMLNODE_ATTRIBUTE) { XMLCompNodeHdr eh = scanLevel->parent; scanLevel->nodeRefPtr = XNODE_NEXT_REF(scanLevel->nodeRefPtr, eh); scanLevel->siblingsLeft--; continue; } else { XMLScanOneLevel nextLevel; /* * Initialize the scan state for the next (deeper) * level */ xscan->depth++; nextLevel = XMLSCAN_CURRENT_LEVEL(xscan); nextLevel->parent = currentElement; nextLevel->nodeRefPtr = childFirst; nextLevel->siblingsLeft = currentElement->children; nextLevel->contextPosition = 0; nextLevel->contextSizeKnown = false; nextLevel->up = scanLevel; break; } } else if (XPATH_LAST_LEVEL(xscan) && !isOnIgnoreList(currentNode, xscan)) { /* * We're at the end of the xpath. */ xscan->skip = true; /* Return the matching node. */ addNodeToIgnoreList(currentNode, xscan); return currentNode; } } } else if (XPATH_LAST_LEVEL(xscan) && !isOnIgnoreList(currentNode, xscan)) { /* * If the 'targNdKind' is XMLNODE_ELEMENT, it means that the * kind did match with path step above, but the node name did * not. In such a case we're not interested in the current * node anymore. */ if (cKind == xscan->xpath->targNdKind && xscan->xpath->targNdKind != XMLNODE_ELEMENT) { if (cKind == XMLNODE_TEXT || cKind == XMLNODE_COMMENT) { xscan->skip = true; addNodeToIgnoreList(currentNode, xscan); return currentNode; } else if (cKind == XMLNODE_PI) { char *piTarget = (char *) (currentNode + 1); char *piTargTest = xpEl->name; if (xscan->xpath->piTestValue) { if (strcmp(piTarget, piTargTest) == 0) { xscan->skip = true; addNodeToIgnoreList(currentNode, xscan); return currentNode; } } else { xscan->skip = true; addNodeToIgnoreList(currentNode, xscan); return currentNode; } } else if (cKind == XMLNODE_ATTRIBUTE) { if (xscan->xpath->allAttributes) { xscan->skip = true; addNodeToIgnoreList(currentNode, xscan); return currentNode; } else { char *attrName = (char *) currentNode + sizeof(XMLNodeHdrData); char *attrNameTest = xpEl->name; if (strcmp(attrNameTest, attrName) == 0) { xscan->skip = true; addNodeToIgnoreList(currentNode, xscan); return currentNode; } } } } else if (xscan->xpath->targNdKind == XMLNODE_NODE && currentNode->kind != XMLNODE_ATTRIBUTE) { xscan->skip = true; addNodeToIgnoreList(currentNode, xscan); return currentNode; } } /* * No match found. The current path element however might be * interested in descendants. */ if (considerSubScan(xpEl, currentNode, xscan, subScanDone)) { continue; } /* * Whether descendants found or not, go to the next element on the * current level */ scanLevel->nodeRefPtr = XNODE_NEXT_REF(scanLevel->nodeRefPtr, scanLevel->parent); scanLevel->siblingsLeft--; } /* * If descent to next level has just been prepared (see the 'break' * statement after initialization of next level), we're not yet at the * end and the following condition can't be met. Otherwise we're done * with the current level. */ if (scanLevel->siblingsLeft == 0) { if (xscan->depth == 0) { xscan->done = true; return NULL; } else { xscan->skip = true; xscan->depth--; continue; } } } /* keep the compiler silent */ return NULL; } /* * Returns expression state, containing a (mutable) copy of an expression as well as * values of operands that had to be substituted. * * The function does process all sub-expressions (implicit / explicit) and * (sub)paths. When it tries to substitute node-sets for sub-paths, it has to * process predicate expressions of those sub-paths (which can again contain * sub-paths, etc.) */ XPathExprState prepareXPathExpression(XPathExpression exprOrig, XMLCompNodeHdr ctxElem, xmldoc document, XPathHeader xpHdr, XMLScan xscan) { XPathExpression expr; XPathExprState state = createXPathVarCache(XPATH_VAR_CACHE_DEF_SIZE); expr = (XPathExpression) palloc(exprOrig->common.size); memcpy(expr, exprOrig, exprOrig->common.size); state->expr = expr; /* Replace attribute names with the values found in the current node. */ if (ctxElem != NULL) { substituteAttributes(state, ctxElem); } if (expr->npaths > 0) { /* Replace paths with matching node-sets. */ substitutePaths(expr, state, ctxElem, document, xpHdr); } if (expr->nfuncs) { /* * Functions having no arguments can also be replaced by value before * evaluation starts. */ substituteFunctions(expr, state, xscan); } return state; } /* * Size is estimated for the initial allocation so that reallocation may not be needed. */ void allocXPathExpressionVarCache(XPathExprState state, XPathExprVar varKind, unsigned int increment, bool init) { unsigned short unitSize; unsigned int maxOrig, sizeOrig, size; void *chunkNew, *chunkOrig = NULL; if (increment == 0) { elog(ERROR, "increment of variable cache size must be greater than zero"); } if (init) { state->count[varKind] = 0; state->countMax[varKind] = 0; } switch (varKind) { case XPATH_VAR_STRING: if (init) { state->strings = NULL; } chunkOrig = state->strings; unitSize = sizeof(char *); break; case XPATH_VAR_NODE_SINGLE: if (init) { state->nodes = NULL; } chunkOrig = state->nodes; unitSize = sizeof(XMLNodeHdr); break; case XPATH_VAR_NODE_ARRAY: if (init) { state->nodeSets = NULL; } chunkOrig = state->nodeSets; unitSize = sizeof(XMLNodeHdr *); break; default: elog(ERROR, "unrecognized variable type %u", varKind); break; } maxOrig = state->countMax[varKind]; state->countMax[varKind] += increment; size = state->countMax[varKind] * unitSize; sizeOrig = maxOrig * unitSize; if (chunkOrig == NULL) { chunkNew = palloc(size); memset(chunkNew, 0, size); } else { chunkNew = repalloc(chunkOrig, size); memset((char *) chunkNew + sizeOrig, 0, size - sizeOrig); } switch (varKind) { case XPATH_VAR_STRING: state->strings = (char **) chunkNew; break; case XPATH_VAR_NODE_SINGLE: state->nodes = (XMLNodeHdr *) chunkNew; break; case XPATH_VAR_NODE_ARRAY: state->nodeSets = (XMLNodeHdr **) chunkNew; break; default: elog(ERROR, "unrecognized variable type %u", varKind); break; } } XPathExprState createXPathVarCache(unsigned int size) { XPathExprState state = (XPathExprState) palloc(sizeof(XPathExprStateData)); allocXPathExpressionVarCache(state, XPATH_VAR_STRING, size, true); allocXPathExpressionVarCache(state, XPATH_VAR_NODE_SINGLE, size, true); allocXPathExpressionVarCache(state, XPATH_VAR_NODE_ARRAY, size, true); state->expr = NULL; return state; } /* * 'element' is the 'owning element' of the predicate that we're just * evaluating. In general - a context node. */ void evaluateXPathExpression(XPathExprState exprState, XPathExpression expr, unsigned short recursionLevel, XPathExprOperandValue result) { unsigned short i; char *currentPtr = (char *) expr; XPathExprOperand currentOpnd; XPathExprOperator operator = NULL; currentPtr += sizeof(XPathExpressionData); Assert(PointerIsAligned(currentPtr, XPATH_ALIGNOF_OFFSET)); if (recursionLevel == 0) { unsigned short varSize = XPATH_EXPR_VAR_MAX * sizeof(XPathOffset); currentPtr += varSize; } for (i = 0; i < expr->members; i++) { XPathExprOperandValueData resTmp, currentVal; if (i > 0) { operator = XPATH_EXPR_OPERATOR(currentPtr); currentPtr += sizeof(XPathExprOperatorStorageData); } currentPtr = (char *) TYPEALIGN(XPATH_ALIGNOF_OPERAND, currentPtr); currentOpnd = (XPathExprOperand) currentPtr; currentPtr += currentOpnd->common.size; prepareLiteral(exprState, currentOpnd); evaluateXPathOperand(exprState, currentOpnd, recursionLevel, ¤tVal); if (i == 0) { memcpy(result, ¤tVal, sizeof(XPathExprOperandValueData)); } if (expr->members == 1) { break; } if (i == 0) continue; evaluateBinaryOperator(exprState, result, ¤tVal, operator, &resTmp); memcpy(result, &resTmp, sizeof(XPathExprOperandValueData)); /* * Short evaluation. * * If the operator is OR, then all operators on the same level must be * OR too. The same applies to AND. These operators are unique in * terms of precedence. */ if (operator->id == XPATH_EXPR_OPERATOR_OR) { Assert(result->type == XPATH_VAL_BOOLEAN); if (!result->isNull && result->v.boolean) { break; } } else if (operator->id == XPATH_EXPR_OPERATOR_AND) { Assert(result->type == XPATH_VAL_BOOLEAN); if (result->isNull || (!result->isNull && !result->v.boolean)) { break;; } } } /* * Both evaluateXPathOperand() and evaluateBinaryOperator() must ensure * that the actual value (result->v.num) is negative by now if the * 'result->negative' was originally 'true'. */ Assert(!result->negative); result->negative = expr->negative; if (result->negative) { flipOperandValue(result, exprState); } } void freeExpressionState(XPathExprState state) { if (state->strings) { pfree(state->strings); } if (state->nodes) { pfree(state->nodes); } if (state->nodeSets) { unsigned short i; for (i = 0; i < state->countMax[XPATH_VAR_NODE_ARRAY]; i++) { XMLNodeHdr *ns = state->nodeSets[i]; if (ns == NULL) { break; } pfree(ns); } pfree(state->nodeSets); } pfree(state); } static void evaluateXPathOperand(XPathExprState exprState, XPathExprOperand operand, unsigned short recursionLevel, XPathExprOperandValue result) { XPathExpression expr = (XPathExpression) operand; if (operand->common.type == XPATH_OPERAND_EXPR_SUB) evaluateXPathExpression(exprState, expr, recursionLevel + 1, result); else if (operand->common.type == XPATH_OPERAND_FUNC) evaluateXPathFunction(exprState, expr, recursionLevel + 1, result); else { if (operand->value.negative) { castXPathExprOperandToNum(exprState, &operand->value, result, false); } else { memcpy(result, &operand->value, sizeof(XPathExprOperandValueData)); } } } /* * Evaluate XPath function having non-empty argument list. * * If a function has no arguments, its value only depends on context * and as such has already been evaluated by substituteFunctions(). */ static void evaluateXPathFunction(XPathExprState exprState, XPathExpression funcExpr, unsigned short recursionLevel, XPathExprOperandValue result) { char *c; unsigned short i; XPathExprOperandValue args, argCursor; XPathFunctionId funcId; XPathFunction function; XpathFuncImpl funcImpl; XPathValueType *argTypes; argCursor = args = (XPathExprOperandValue) palloc(funcExpr->members * sizeof(XPathExprOperandValueData)); c = (char *) funcExpr + sizeof(XPathExpressionData); funcId = funcExpr->funcId; function = &xpathFunctions[funcId]; argTypes = function->argTypes; /* * Prepare each argument: evaluate it if it's expression or function or * just copy if it's a single operand. * * 'args' will contain the arguments to pass.. * */ for (i = 0; i < funcExpr->members; i++) { XPathExprOperand opnd; XPathExprOperandValueData argValue; XPathValueType targetType; c = (char *) TYPEALIGN(XPATH_ALIGNOF_OPERAND, c); opnd = (XPathExprOperand) c; if (opnd->common.type == XPATH_OPERAND_EXPR_TOP || opnd->common.type == XPATH_OPERAND_EXPR_SUB || opnd->common.type == XPATH_OPERAND_FUNC) { XPathExpression subExpr = (XPathExpression) opnd; if (opnd->common.type == XPATH_OPERAND_FUNC) { /* In this case, 'subExpr' means 'argument list'. */ evaluateXPathFunction(exprState, subExpr, recursionLevel, &argValue); } else { evaluateXPathExpression(exprState, subExpr, recursionLevel, &argValue); } } else { prepareLiteral(exprState, opnd); evaluateXPathOperand(exprState, opnd, recursionLevel, &argValue); } /* * Cast the argument now so that implementation function can assume * having received the desired type(s). */ if (i < function->nargs) { targetType = argTypes[i]; } else if (function->variadic) { targetType = argTypes[function->nargs - 1]; } else { /* Parser should not allow this if working as supposed to. */ targetType = XPATH_VAL_OBJECT; /* Keep the compiler silent. */ elog(ERROR, "too many arguments passed to a function"); } switch (targetType) { case XPATH_VAL_BOOLEAN: castXPathExprOperandToBool(exprState, &argValue, argCursor); break; case XPATH_VAL_NUMBER: castXPathExprOperandToNum(exprState, &argValue, argCursor, false); break; case XPATH_VAL_STRING: castXPathExprOperandToStr(exprState, &argValue, argCursor); break; case XPATH_VAL_NODESET: if (argValue.type == XPATH_VAL_NODESET) { memcpy(argCursor, &argValue, sizeof(XPathExprOperandValueData)); } else elog(ERROR, "%s type cannot be cast to nodeset. check argument %u of %s() function", xpathValueTypes[argValue.type], i, function->name); break; case XPATH_VAL_OBJECT: /* Function accepts anything, so just copy it. */ memcpy(argCursor, &argValue, sizeof(XPathExprOperandValueData)); break; default: elog(ERROR, "unrecognized type to cast to: %u", targetType); break; } c += opnd->common.size; argCursor++; } funcImpl = function->impl.args; result->negative = false; /* The implementation function may set this to 'true'. */ result->isNull = false; /* Call the function. */ funcImpl(exprState, funcExpr->members, args, result); pfree(args); result->type = function->resType; if (result->negative) { elog(ERROR, "function implementation is not expected to set 'negative' flag"); } result->negative = funcExpr->negative; if (result->negative) { flipOperandValue(result, exprState); } } /* * Find out whether the literal is in buffer that the appropriate function received from FMGR * or is it a copy. */ static void prepareLiteral(XPathExprState exprState, XPathExprOperand operand) { if (operand->common.type == XPATH_OPERAND_LITERAL && operand->value.type == XPATH_VAL_STRING) { operand->value.v.stringId = getXPathOperandId(exprState, XPATH_STRING_LITERAL(&operand->value), XPATH_VAR_STRING); } } static void evaluateBinaryOperator(XPathExprState exprState, XPathExprOperandValue valueLeft, XPathExprOperandValue valueRight, XPathExprOperator operator, XPathExprOperandValue result) { /* If numeric value is the output, the value will reflect the sign itself */ result->negative = false; if (operator->id == XPATH_EXPR_OPERATOR_OR || operator->id == XPATH_EXPR_OPERATOR_AND) { bool left, right; XPathExprOperandValueData valueTmp; result->isNull = false; if (valueLeft->type == XPATH_VAL_NODESET && valueRight->type == XPATH_VAL_NODESET && valueLeft->v.nodeSet.isDocument && valueRight->v.nodeSet.isDocument) { elog(ERROR, "invalid xpath expression"); } /* * Subexpression is currently the only type of operand of boolean type */ castXPathExprOperandToBool(exprState, valueLeft, &valueTmp); left = valueTmp.v.boolean; castXPathExprOperandToBool(exprState, valueRight, &valueTmp); right = valueTmp.v.boolean; result->type = XPATH_VAL_BOOLEAN; result->v.boolean = (operator->id == XPATH_EXPR_OPERATOR_OR) ? left || right : left && right; } else if (operator->id == XPATH_EXPR_OPERATOR_EQ || operator->id == XPATH_EXPR_OPERATOR_NEQ) { bool equal; XPathValueType typeLeft = valueLeft->type; XPathValueType typeRight = valueRight->type; XPathExprOperandValueData valueTmp; result->isNull = false; result->type = XPATH_VAL_BOOLEAN; if (typeLeft == XPATH_VAL_BOOLEAN || typeRight == XPATH_VAL_BOOLEAN) { bool boolLeft, boolRight; /* * As we don't know which one is boolean and which one is * something else, cast both values to boolean */ castXPathExprOperandToBool(exprState, valueLeft, &valueTmp); boolLeft = valueTmp.v.boolean; castXPathExprOperandToBool(exprState, valueRight, &valueTmp); boolRight = valueTmp.v.boolean; ; equal = boolLeft == boolRight; result->v.boolean = (operator->id == XPATH_EXPR_OPERATOR_EQ) ? equal : !equal; return; } else if (typeLeft == XPATH_VAL_NUMBER || typeRight == XPATH_VAL_NUMBER) { compareNumValues(exprState, valueLeft, valueRight, operator, result); return; } else { /* * compare 2 strings, 2 node-sets or a combination of both. */ if (typeLeft == XPATH_VAL_NODESET && typeRight == XPATH_VAL_NODESET) { XPathNodeSet nsLeft = &valueLeft->v.nodeSet; XPathNodeSet nsRight = &valueRight->v.nodeSet; /* * Document is not a typical node-set, let's exclude it first. */ if (nsLeft->isDocument && nsRight->isDocument) { result->v.boolean = operator->id == XPATH_EXPR_OPERATOR_EQ; return; } else if (nsLeft->isDocument || nsRight->isDocument) { XPathNodeSet setDoc, setNonDoc; if (nsLeft->isDocument) { setDoc = nsLeft; setNonDoc = nsRight; } else { setDoc = nsRight; setNonDoc = nsLeft; } if (setNonDoc->count > 0) { result->v.boolean = (operator->id == XPATH_EXPR_OPERATOR_EQ); } else { result->v.boolean = false; } return; } if (nsLeft->count == 0 || nsRight->count == 0) { result->v.boolean = false; return; } result->v.boolean = compareNodeSets(exprState, &valueLeft->v.nodeSet, &valueRight->v.nodeSet, operator); return; } else if (typeLeft == XPATH_VAL_NODESET || typeRight == XPATH_VAL_NODESET) { /* * One operand is a simple (string) value, the other is a node * set */ XPathExprOperandValue setValue, nonSetValue; XPathNodeSet nodeSet; if (typeLeft == XPATH_VAL_NODESET) { setValue = valueLeft; nonSetValue = valueRight; } else { setValue = valueRight; nonSetValue = valueLeft; } if (setValue->v.nodeSet.isDocument) { result->v.boolean = (operator->id == XPATH_EXPR_OPERATOR_NEQ); return; } nodeSet = &setValue->v.nodeSet; /* * The previous conditions should have caught 'number to * node-set' and 'boolean to node-set' comparisons. */ Assert(nonSetValue->type == XPATH_VAL_STRING); if (nonSetValue->isNull || nodeSet->count == 0) { /* * If either operand is null, comparison makes no sense. * (No match even if XPATH_EXPR_OPERATOR_EQ is the * operator). */ result->v.boolean = false; return; } result->v.boolean = compareValueToNodeSet(exprState, nonSetValue, nodeSet, operator); return; } /* * Compare 2 'simple strings', i.e. none is contained in a * node-set. */ if (!valueLeft->isNull && !valueRight->isNull) { char *strLeft, *strRight; strLeft = getXPathOperandValue(exprState, valueLeft->v.stringId, XPATH_VAR_STRING); strRight = getXPathOperandValue(exprState, valueRight->v.stringId, XPATH_VAR_STRING); equal = strcmp(strLeft, strRight) == 0; result->v.boolean = (operator->id == XPATH_EXPR_OPERATOR_EQ) ? equal : !equal; return; } else { /* * One or both sides are null. It makes no sense to say * whether the operands are equal or non-equal. */ result->v.boolean = false; return; } } } else if (operator->id == XPATH_EXPR_OPERATOR_LT || operator->id == XPATH_EXPR_OPERATOR_GT || operator->id == XPATH_EXPR_OPERATOR_LTE || operator->id == XPATH_EXPR_OPERATOR_GTE) { compareNumValues(exprState, valueLeft, valueRight, operator, result); } else if (operator->id == XPATH_EXPR_OPERATOR_PLUS || operator->id == XPATH_EXPR_OPERATOR_MINUS || operator->id == XPATH_EXPR_OPERATOR_MULTIPLY || operator->id == XPATH_EXPR_OPERATOR_DIV || operator->id == XPATH_EXPR_OPERATOR_MOD) { XPathExprOperandValueData numLeft, numRight; result->isNull = false; result->type = XPATH_VAL_NUMBER; castXPathExprOperandToNum(exprState, valueLeft, &numLeft, false); castXPathExprOperandToNum(exprState, valueRight, &numRight, false); if (numLeft.isNull || numRight.isNull) { result->isNull = true; return; } if (numLeft.negative) { numLeft.v.num *= -1.0f; } if (numRight.negative) { numRight.v.num *= -1.0f; } switch (operator->id) { case XPATH_EXPR_OPERATOR_PLUS: result->v.num = numLeft.v.num + numRight.v.num; break; case XPATH_EXPR_OPERATOR_MINUS: result->v.num = numLeft.v.num - numRight.v.num; break; case XPATH_EXPR_OPERATOR_MULTIPLY: result->v.num = numLeft.v.num * numRight.v.num; break; case XPATH_EXPR_OPERATOR_DIV: case XPATH_EXPR_OPERATOR_MOD: result->v.num = DatumGetFloat8(DirectFunctionCall2Coll(float8div, InvalidOid, Float8GetDatum(numLeft.v.num), Float8GetDatum(numRight.v.num))); if (operator->id == XPATH_EXPR_OPERATOR_MOD) { float8 truncated = DatumGetFloat8(DirectFunctionCall1Coll(dtrunc, InvalidOid, Float8GetDatum(result->v.num))); result->v.num = numLeft.v.num - truncated * numRight.v.num; } break; default: elog(ERROR, "unrecognized operator %u", operator->id); break; } } else if (operator->id == XPATH_EXPR_OPERATOR_UNION) { XPathNodeSet nodeSetLeft, nodeSetRight; if (valueLeft->type != XPATH_VAL_NODESET || valueRight->type != XPATH_VAL_NODESET) { /* Parser shouldn't allow this, but let's check anyway. */ elog(ERROR, "union operator expects nodeset on both sides"); } nodeSetLeft = &valueLeft->v.nodeSet; nodeSetRight = &valueRight->v.nodeSet; getUnion(exprState, nodeSetLeft, nodeSetRight, &result->v.nodeSet); result->isNull = (result->v.nodeSet.count == 0); result->type = XPATH_VAL_NODESET; } else { elog(ERROR, "unknown operator to evaluate: %u", operator->id); result->v.boolean = false; } } void initScanForTextNodes(XMLScan xscan, XMLCompNodeHdr root) { XPath xp = (XPath) palloc(sizeof(XPathData) + sizeof(XPathElementData)); XPathElement xpEl = (XPathElement) ((char *) xp + sizeof(XPathData)); xpEl->descendant = true; xpEl->hasPredicate = false; xp->depth = 1; xp->targNdKind = XMLNODE_TEXT; xp->allAttributes = false; xp->elements[0] = sizeof(XPathData); initXMLScan(xscan, NULL, xp, NULL, root, xscan->document, false); } void finalizeScanForTextNodes(XMLScan xscan) { pfree(xscan->xpath); finalizeXMLScan(xscan); } /* * Sub-scans are used to search for descendants recursively. * It takes the current node as the root, so it in fact scans children of the current node. * If any of those children has children, new sub-scan is initiated, etc. */ static bool considerSubScan(XPathElement xpEl, XMLNodeHdr node, XMLScan xscan, bool subScanJustDone) { if (xpEl->descendant && node->kind == XMLNODE_ELEMENT && !subScanJustDone) { XMLCompNodeHdr el = (XMLCompNodeHdr) node; if (el->children > 0) { xscan->subScan = (XMLScan) palloc(sizeof(XMLScanData)); initXMLScan(xscan->subScan, xscan, xscan->xpath, xscan->xpathHeader, el, xscan->document, xscan->ignoreList != NULL); xscan->subScan->xpathRoot = xscan->xpathRoot + xscan->depth; return true; } } return false; } static void addNodeToIgnoreList(XMLNodeHdr node, XMLScan scan) { if (scan->ignoreList != NULL) { xmlnodePushSingleNode(scan->ignoreList, XNODE_OFFSET(node, scan->document)); } } /* * Replace attribute names in the expression with actual values (i.e. with * values that the current element contains). * * 'exprState' contains the expath expression. * 'exprState' also receives array of attribute values referenced by the expression variables. * By return time the referencing variables (expression operands) have obtained * the appropriate subscripts for this array. */ static void substituteAttributes(XPathExprState exprState, XMLCompNodeHdr element) { unsigned short attrNr = 0; unsigned short attrCount = 0; XMLNodeHdr *attributes = NULL; unsigned int attrId = 0; unsigned int attrsArrayId = 0; XMLNodeIteratorData iterator; XMLNodeHdr child; initXMLNodeIterator(&iterator, element, true); while ((child = getNextXMLNodeChild(&iterator)) != NULL) { if (child->kind == XMLNODE_ATTRIBUTE) { char *attrName = XNODE_CONTENT(child); unsigned short i; unsigned short matches = 0; XPathOffset *varOffPtr = (XPathOffset *) ((char *) exprState->expr + sizeof(XPathExpressionData)); /* * Check all variables and find those referencing 'child' * attribute. */ for (i = 0; i < exprState->expr->variables; i++) { XPathExprOperand opnd = (XPathExprOperand) ((char *) exprState->expr + *varOffPtr); if (opnd->substituted) { varOffPtr++; continue; } if (opnd->common.type == XPATH_OPERAND_ATTRIBUTE) { XPathNodeSet nodeSet = &opnd->value.v.nodeSet; unsigned int nodeNr = exprState->count[XPATH_VAR_NODE_SINGLE] + attrNr; char *opndValue = XPATH_STRING_LITERAL(&opnd->value); if (*opndValue == XNODE_CHAR_ASTERISK) { if (attributes == NULL) { unsigned short j = 0; unsigned int size = element->children * sizeof(XMLNodeHdr); XMLNodeIteratorData iterAttr; XMLNodeHdr attrNode; initXMLNodeIterator(&iterAttr, element, true); attributes = (XMLNodeHdr *) palloc(size); MemSet(attributes, 0, size); while ((attrNode = getNextXMLNodeChild(&iterAttr)) != NULL) { if (attrNode->kind != XMLNODE_ATTRIBUTE) { break; } attributes[j++] = attrNode; attrCount++; } Assert(j > 0); if (attrCount == 1) { attrId = getXPathOperandId(exprState, attributes[0], XPATH_VAR_NODE_SINGLE); /* * In this case only the single attribute has * been added to the cache, so the * 'attributes' array is no longer needed. */ pfree(attributes); } else { attrsArrayId = getXPathOperandId(exprState, attributes, XPATH_VAR_NODE_ARRAY); } } if (attrCount == 1) { nodeSet->nodes.nodeId = attrId; } else { nodeSet->nodes.arrayId = attrsArrayId; } nodeSet->count = attrCount; nodeSet->isDocument = false; opnd->value.isNull = false; opnd->substituted = true; opnd->value.type = XPATH_VAL_NODESET; } else if (strcmp(attrName, opndValue) == 0) { /* * Save node pointer into the variable cache and * assign its id to the operand. * * getXPathOperandId() can't be used here because any * attribute may be substituted for multiple * variables. It wouldn't bee too efficient to assign * a separate id (and storage) to such attribute * multiple times (i.e. once for each variable that * references it). */ matches++; Assert(nodeNr <= exprState->countMax[XPATH_VAR_NODE_SINGLE]); if (nodeNr == exprState->countMax[XPATH_VAR_NODE_SINGLE]) { allocXPathExpressionVarCache(exprState, XPATH_VAR_NODE_SINGLE, XPATH_VAR_CACHE_DEF_SIZE, false); } /* * The attribute pointer is only added to cache once. * If it occurs in the expression multiple times, all * occurrences share the same instance in the cache. */ if (matches == 1) { exprState->nodes[nodeNr] = child; } nodeSet->nodes.nodeId = nodeNr; nodeSet->count = 1; nodeSet->isDocument = false; opnd->value.isNull = false; opnd->substituted = true; opnd->value.type = XPATH_VAL_NODESET; } } varOffPtr++; } /* * If at least one variable references the current attribute, the * potential next attribute needs a new unique number. */ if (matches > 0) { attrNr++; } } else { /* * Attributes are first of the children. Thus if the current node * is not an attribute, we're done. */ break; } } exprState->count[XPATH_VAR_NODE_SINGLE] += attrNr; } /* * All sub-paths in 'expression' are replaced with the matching node-sets. * * expression - XPath expression containing the paths we're evaluating * (replacing with node-sets). * * element - context node (XML element that we'll test using 'expression' when the substitution is complete) */ static void substitutePaths(XPathExpression expression, XPathExprState exprState, XMLCompNodeHdr element, xmldoc document, XPathHeader xpHdr) { unsigned short i, processed; XPathOffset *varOffPtr = (XPathOffset *) ((char *) expression + sizeof(XPathExpressionData)); processed = 0; for (i = 0; i < expression->variables; i++) { XPathExprOperand opnd = (XPathExprOperand) ((char *) expression + *varOffPtr); if (opnd->common.type == XPATH_OPERAND_PATH) { XMLScanData xscanSub; XPath subPath = XPATH_HDR_GET_PATH(xpHdr, opnd->value.v.path); XMLNodeHdr matching; unsigned short count = 0; unsigned short arrSize = 0; if (!subPath->relative && subPath->depth == 0) { opnd->value.isNull = false; opnd->value.v.nodeSet.isDocument = true; opnd->value.v.nodeSet.nodes.nodeId = getXPathOperandId(exprState, XNODE_ROOT(document), XPATH_VAR_NODE_SINGLE); opnd->value.v.nodeSet.count = 1; } else if (!subPath->relative && subPath->depth == 1 && subPath->descendants == 0 && subPath->targNdKind == XMLNODE_ATTRIBUTE) { /* * Paths like '/@attr' or '/@*' never point to a valid node. * Further evaluation makes no sense in such cases. */ opnd->value.isNull = true; opnd->value.v.nodeSet.count = 0; opnd->value.v.nodeSet.isDocument = false; } else { XMLCompNodeHdr parent = (subPath->relative) ? element : (XMLCompNodeHdr) XNODE_ROOT(document); initXMLScan(&xscanSub, NULL, subPath, xpHdr, parent, document, subPath->descendants > 0); while ((matching = getNextXMLNode(&xscanSub)) != NULL) { XMLNodeHdr *array = NULL; if (count == 0) { opnd->value.v.nodeSet.nodes.nodeId = getXPathOperandId(exprState, matching, XPATH_VAR_NODE_SINGLE); } else if (count == 1) { /* * Adding 2nd node, so the array size must be at least * 2. */ arrSize = (element->children > count) ? element->children : 2; array = (XMLNodeHdr *) palloc(sizeof(XMLNodeHdr) * arrSize); array[0] = getXPathOperandValue(exprState, opnd->value.v.nodeSet.nodes.nodeId, XPATH_VAR_NODE_SINGLE); array[1] = matching; opnd->value.v.nodeSet.nodes.arrayId = getXPathOperandId(exprState, array, XPATH_VAR_NODE_ARRAY); } else { unsigned short arrayId = opnd->value.v.nodeSet.nodes.arrayId; XMLNodeHdr *array = getXPathOperandValue(exprState, arrayId, XPATH_VAR_NODE_ARRAY); if (count >= arrSize) { /* * Estimate like this might be o.k. whether the * node has few or many children */ arrSize = count + (count >> 1) + 1; array = (XMLNodeHdr *) repalloc(array, sizeof(XMLNodeHdr) * arrSize); exprState->nodeSets[arrayId] = array; } array[count] = matching; } count++; } opnd->value.isNull = (count == 0); opnd->value.v.nodeSet.count = count; opnd->value.v.nodeSet.isDocument = false; finalizeXMLScan(&xscanSub); } opnd->substituted = true; opnd->value.type = XPATH_VAL_NODESET; processed++; if (processed == expression->npaths) { break; } } varOffPtr++; } } /* * Substitute functions having no arguments. Functions that do have arguments * are considered a special type of sub-expression. */ static void substituteFunctions(XPathExpression expression, XPathExprState exprState, XMLScan xscan) { unsigned short i, processed; XPathOffset *varOffPtr = (XPathOffset *) ((char *) expression + sizeof(XPathExpressionData)); processed = 0; for (i = 0; i < expression->variables; i++) { XPathExprOperand opnd = (XPathExprOperand) ((char *) expression + *varOffPtr); if (opnd->common.type == XPATH_OPERAND_FUNC_NOARG) { XPathFunctionId funcId = opnd->value.v.funcId; XPathFunction func; XpathFuncImplNoArgs funcImpl; if (funcId >= XPATH_FUNCTIONS) { elog(ERROR, "unrecognized function id: %u", funcId); } func = &xpathFunctions[funcId]; Assert(func->nargs == 0); funcImpl = func->impl.noargs; /* Assume not null result. The implementation may change it. */ opnd->value.isNull = false; /* Call the function. */ funcImpl(xscan, exprState, &opnd->value); opnd->value.type = func->resType; opnd->substituted = true; processed++; if (processed == expression->nfuncs) { break; } } varOffPtr++; } } /* * Both operands are cast to number. * If either cast fails, the operator evaluates to true for != operator and to false in other cases. */ static void compareNumValues(XPathExprState exprState, XPathExprOperandValue valueLeft, XPathExprOperandValue valueRight, XPathExprOperator operator, XPathExprOperandValue result) { double numLeft, numRight; result->type = XPATH_VAL_BOOLEAN; result->isNull = false; if (valueLeft->isNull || valueRight->isNull) { result->v.boolean = (operator->id == XPATH_EXPR_OPERATOR_NEQ); return; } if (!xmlNumberToDouble(exprState, valueLeft, &numLeft)) { result->v.boolean = (operator->id == XPATH_EXPR_OPERATOR_NEQ); return; } if (!xmlNumberToDouble(exprState, valueRight, &numRight)) { result->v.boolean = (operator->id == XPATH_EXPR_OPERATOR_NEQ); return; } compareNumbers(numLeft, numRight, operator, result); } static bool xmlNumberToDouble(XPathExprState exprState, XPathExprOperandValue numOperand, double *simple) { if (numOperand->type == XPATH_VAL_NUMBER) { *simple = numOperand->v.num; if (numOperand->negative) { *simple *= -1.0f; } } else { XPathExprOperandValueData opValueNum; castXPathExprOperandToNum(exprState, numOperand, &opValueNum, false); if (opValueNum.isNull) { return false; } *simple = opValueNum.v.num; } return true; } static void compareNumbers(double numLeft, double numRight, XPathExprOperator operator, XPathExprOperandValue result) { result->type = XPATH_VAL_BOOLEAN; result->isNull = false; switch (operator->id) { case XPATH_EXPR_OPERATOR_EQ: result->v.boolean = (numLeft == numRight); return; case XPATH_EXPR_OPERATOR_NEQ: result->v.boolean = (numLeft != numRight); return; case XPATH_EXPR_OPERATOR_LT: result->v.boolean = (numLeft < numRight); break; case XPATH_EXPR_OPERATOR_LTE: result->v.boolean = (numLeft <= numRight); break; case XPATH_EXPR_OPERATOR_GT: result->v.boolean = (numLeft > numRight); break; case XPATH_EXPR_OPERATOR_GTE: result->v.boolean = (numLeft >= numRight); break; default: elog(ERROR, "unexpected operator %u", operator->id); break; } } /* * Returns true if the node sets are equal according to * http://www.w3.org/TR/1999/REC-xpath-19991116/#booleans */ static bool compareNodeSets(XPathExprState exprState, XPathNodeSet ns1, XPathNodeSet ns2, XPathExprOperator operator) { XMLNodeHdr node1, node2; XMLNodeHdr *arrayOuter, *arrayInner; XPathNodeSet nsOuter = ns1; XPathNodeSet nsInner = ns2; unsigned short i; if (ns1->count > 1) { arrayOuter = (XMLNodeHdr *) getXPathOperandValue(exprState, ns1->nodes.arrayId, XPATH_VAR_NODE_ARRAY); } else { node1 = (XMLNodeHdr) getXPathOperandValue(exprState, ns1->nodes.nodeId, XPATH_VAR_NODE_SINGLE); arrayOuter = &node1; } if (ns2->count > 1) { arrayInner = (XMLNodeHdr *) getXPathOperandValue(exprState, ns2->nodes.arrayId, XPATH_VAR_NODE_ARRAY); } else { node2 = (XMLNodeHdr) getXPathOperandValue(exprState, ns2->nodes.nodeId, XPATH_VAR_NODE_SINGLE); arrayInner = &node2; } for (i = 0; i < nsOuter->count; i++) { XMLNodeHdr nodeOuter = arrayOuter[i]; unsigned short j; bool match = false; for (j = 0; j < nsInner->count; j++) { XMLNodeHdr nodeInner = arrayInner[j]; /* * In order to reduce the number of combinations there's a rule * that for 'element - non-element' comparisons the 'non-element * node' will always be on the same side. The outer loop has been * chosen. */ if (nodeOuter->kind == XMLNODE_ELEMENT && nodeInner->kind != XMLNODE_ELEMENT) { XMLNodeHdr nodeTmp; nodeTmp = nodeOuter; nodeOuter = nodeInner; nodeInner = nodeTmp; } if (nodeOuter->kind == XMLNODE_ELEMENT) { /* Both nodes are elements */ bool matchResult = false; match = compareElements((XMLCompNodeHdr) nodeOuter, (XMLCompNodeHdr) nodeInner); matchResult = (operator->id == XPATH_EXPR_OPERATOR_EQ) ? match : !match; if (matchResult) { return true; } } else { /* * The outer is non-element, the inner is any node kind. */ char *nodeStr = getNonElementNodeStr(nodeOuter); XPathExprOperandValueData valueOuter; if (nodeStr == NULL) { return false; } valueOuter.type = XPATH_VAL_STRING; valueOuter.isNull = false; valueOuter.castToNumber = false; valueOuter.v.stringId = getXPathOperandId(exprState, nodeStr, XPATH_VAR_STRING); if (compareValueToNode(exprState, &valueOuter, nodeInner, operator)) { return true; } } } } return false; } static bool compareElements(XMLCompNodeHdr elLeft, XMLCompNodeHdr elRight) { XMLScanData scanLeft, scanRight; /* maximum text position that we have for both elements */ unsigned int charsToCompare; XMLNodeHdr nodeLeft, nodeRight; char *textLeft, *textRight; unsigned int lengthLeft, lengthRight; bool done = false; bool match = false; initScanForTextNodes(&scanLeft, elLeft); initScanForTextNodes(&scanRight, elRight); nodeLeft = getNextXMLNode(&scanLeft); nodeRight = getNextXMLNode(&scanRight); done = false; if (nodeLeft == NULL && nodeRight == NULL) { match = true; done = true; } else if (nodeLeft == NULL || nodeRight == NULL) { match = false; done = true; } if (done) { finalizeScanForTextNodes(&scanLeft); finalizeScanForTextNodes(&scanRight); return match; } /* * Both left and right nodes are not null at this moment. */ textLeft = XNODE_CONTENT(nodeLeft); lengthLeft = strlen(textLeft); textRight = XNODE_CONTENT(nodeRight); lengthRight = strlen(textRight); charsToCompare = (lengthLeft < lengthRight) ? lengthLeft : lengthRight; while (!done) { if (strncmp(textLeft, textRight, charsToCompare) != 0) { break; } if (lengthLeft < lengthRight) { nodeLeft = getNextXMLNode(&scanLeft); if (nodeLeft == NULL) { break; } textLeft = XNODE_CONTENT(nodeLeft); textRight += charsToCompare; } else if (lengthLeft > lengthRight) { nodeRight = getNextXMLNode(&scanRight); if (nodeRight == NULL) { break; } textLeft += charsToCompare; textRight = XNODE_CONTENT(nodeRight); } else { /* shift the 'cursor' on both left and right side */ nodeLeft = getNextXMLNode(&scanLeft); nodeRight = getNextXMLNode(&scanRight); if (nodeLeft == NULL && nodeRight == NULL) { match = true; break; } else if (nodeLeft == NULL || nodeRight == NULL) { break; } textLeft = XNODE_CONTENT(nodeLeft); textRight = XNODE_CONTENT(nodeRight); } lengthLeft = strlen(textLeft); lengthRight = strlen(textRight); charsToCompare = (lengthLeft < lengthRight) ? lengthLeft : lengthRight; } finalizeScanForTextNodes(&scanLeft); finalizeScanForTextNodes(&scanRight); return match; } /* * The operator direction (whether '<' or '>') assumes 'value' is on the left * side in the expression and node on the right. If it's the other way round, * the caller must switch the direction. */ static bool compareValueToNode(XPathExprState exprState, XPathExprOperandValue value, XMLNodeHdr node, XPathExprOperator operator) { bool match = false; if (!(value->type == XPATH_VAL_STRING || value->type == XPATH_VAL_NUMBER)) { elog(ERROR, "unable to compare operand value type %u to a node", value->type); } if (node->kind == XMLNODE_ELEMENT) { XMLNodeHdr textNode; unsigned int strLen; unsigned int nodeCntLen = 0; if (value->type == XPATH_VAL_STRING) { char *cStr = (char *) getXPathOperandValue(exprState, value->v.stringId, XPATH_VAR_STRING); XMLScanData textScan; strLen = strlen(cStr); initScanForTextNodes(&textScan, (XMLCompNodeHdr) node); while ((textNode = getNextXMLNode(&textScan)) != NULL) { char *cntPart = XNODE_CONTENT(textNode); unsigned int cntPartLen = strlen(cntPart); nodeCntLen += cntPartLen; if (nodeCntLen > strLen) { break; } if (strncmp(cStr, cntPart, cntPartLen) == 0) { /* * The text node content matches the corresponding part of * 'str', so prepare to compare the next text node. */ match = true; cStr += cntPartLen; } else { match = false; break; } } finalizeScanForTextNodes(&textScan); } else { /* * Compare number to element node. Concatenation of the text * elements can't be avoided now. */ XPathExprOperandValueData result; char *nodeStr; result.type = XPATH_VAL_BOOLEAN; result.v.boolean = false; nodeStr = getElementNodeStr((XMLCompNodeHdr) node); if (strlen(nodeStr) > 0) { compareNumToStr(value->v.num, nodeStr, operator, &result); } pfree(nodeStr); return result.v.boolean; } if (value->type == XPATH_VAL_STRING) { if (match) { if (nodeCntLen != strLen) { match = false; } } else { /* * This condition represents both strings being empty (But * node set non-empty: if the node set is empty, this function * shouldn't be called at all). */ if (nodeCntLen == 0 && strLen == 0) { match = true; } } } } else { char *nodeStr = getNonElementNodeStr(node); XPathExprOperandValueData result; result.type = XPATH_VAL_BOOLEAN; result.v.boolean = false; if (nodeStr == NULL) { return false; } if (value->type == XPATH_VAL_STRING) { match = strcmp((char *) getXPathOperandValue(exprState, value->v.stringId, XPATH_VAR_STRING), nodeStr) == 0; } else if (value->type == XPATH_VAL_NUMBER) { compareNumToStr(value->v.num, nodeStr, operator, &result); return result.v.boolean; } else { elog(ERROR, "unrecognized type of value to be compared to a node: %u", value->type); } } /* * For numbers this check is performed by compareNumToStr() and result * returned immediately. Extra operators '<', '>', '<=' and '>=' exist for * numbers, whereas only '=' and '!=' can be applied to strings in this * function. */ Assert(value->type == XPATH_VAL_STRING); return (operator->id == XPATH_EXPR_OPERATOR_EQ) ? match : !match; } static void compareNumToStr(double num, char *numStr, XPathExprOperator operator, XPathExprOperandValue result) { char *end; double numValue = strtod(numStr, &end); result->type = XPATH_VAL_BOOLEAN; result->v.boolean = false; if (end > numStr) { /* only whitespaces are accepted after the number */ while (*end != '\0') { if (!XNODE_WHITESPACE(end)) { break; } end++; } if (*end == '\0') { compareNumbers(num, numValue, operator, result); } } else { /* 'numStr' does not represent valid number. */ result->v.boolean = (operator->id == XPATH_EXPR_OPERATOR_NEQ); } } /* * The operator direction (whether '<' or '>') assumes 'value' is on the left * side in the expression and node-set on the right. If it's the other way * round, the caller must switch the direction. */ static bool compareValueToNodeSet(XPathExprState exprState, XPathExprOperandValue value, XPathNodeSet ns, XPathExprOperator operator) { XMLNodeHdr *array; unsigned short i; if (ns->count > 1) { array = (XMLNodeHdr *) getXPathOperandValue(exprState, ns->nodes.arrayId, XPATH_VAR_NODE_ARRAY); } else { XMLNodeHdr node = (XMLNodeHdr) getXPathOperandValue(exprState, ns->nodes.nodeId, XPATH_VAR_NODE_SINGLE); array = &node; } if (!(operator->id == XPATH_EXPR_OPERATOR_EQ || operator->id == XPATH_EXPR_OPERATOR_NEQ || operator->id == XPATH_EXPR_OPERATOR_LT || operator->id == XPATH_EXPR_OPERATOR_GT || operator->id == XPATH_EXPR_OPERATOR_LTE || operator->id == XPATH_EXPR_OPERATOR_GTE)) { elog(ERROR, "unexpected operator: %u", operator->id); } for (i = 0; i < ns->count; i++) { XMLNodeHdr node = array[i]; if (compareValueToNode(exprState, value, node, operator)) { return true; } } return false; } static bool isOnIgnoreList(XMLNodeHdr node, XMLScan scan) { XMLNodeOffset nodeOff = XNODE_OFFSET(node, scan->document); if (scan->ignoreList != NULL) { XMLNodeContainer cont = scan->ignoreList; unsigned short i; for (i = 0; i < cont->position; i++) { XNodeListItem *item = cont->content + i; if (!item->valid) { continue; } if (item->kind == XNODE_LIST_ITEM_SINGLE_OFF) { if (item->value.singleOff == nodeOff) { return true; } } else { if (item->value.range.lower <= nodeOff && item->value.range.upper >= nodeOff) { return true; } } } return false; } else { return false; } } /* * Generate node set containing nodes from both 'setLeft' and 'setRight', omitting duplicate nodes. * * The simplest approach is to sort the nodes by position in the document, then iterate through the list * and ignore repeated values. * * Given the binary format (children at lower positions than parents) this may produce different order from * what user may expect. For example * xml.path('/root/a|/root/text()|/root/a/text()', 'bc') * gives * bbc * * Until it's clear if this is in contradiction with XPath standard, let's leave it this way. */ static void getUnion(XPathExprState exprState, XPathNodeSet setLeft, XPathNodeSet setRight, XPathNodeSet setResult) { unsigned int countMax = setLeft->count + setRight->count; unsigned int resSize = countMax * sizeof(XMLNodeHdr *); XMLNodeHdr *nodesAll = (XMLNodeHdr *) palloc(resSize); XMLNodeHdr *result = (XMLNodeHdr *) palloc(resSize); unsigned int pos = 0; unsigned int i, j; copyNodeSet(exprState, setLeft, nodesAll, &pos); copyNodeSet(exprState, setRight, nodesAll, &pos); Assert(pos == countMax); qsort(nodesAll, countMax, sizeof(XMLNodeHdr), nodePtrComparator); /* Copy nodes to the result array, but skip the repeating values. */ MemSet(result, 0, resSize); j = 0; for (i = 0; i < countMax; i++) { if (i == 0 || (i > 0 && result[j - 1] != nodesAll[i])) { result[j++] = nodesAll[i]; } } setResult->count = j; setResult->isDocument = false; /* * Don't add anything to variable cache if the union is empty. The caller * will handle such a case by setting the operand's 'isNull' to true. */ if (j == 1) { setResult->nodes.nodeId = getXPathOperandId(exprState, result[0], XPATH_VAR_NODE_SINGLE); pfree(result); } else if (j > 1) { setResult->nodes.arrayId = getXPathOperandId(exprState, result, XPATH_VAR_NODE_ARRAY); } pfree(nodesAll); } /* * Copy node(s) from 'nodeSet' to 'output', starting at '*position'. * Sufficient space must be allocated in 'output'. * '*position' gets increased by the number of nodes actually added. */ static void copyNodeSet(XPathExprState exprState, XPathNodeSet nodeSet, XMLNodeHdr *output, unsigned int *position) { XMLNodeHdr *start = output + *position; if (nodeSet->count == 0) { return; } if (nodeSet->count == 1) { *start = (XMLNodeHdr) getXPathOperandValue(exprState, nodeSet->nodes.nodeId, XPATH_VAR_NODE_SINGLE); (*position)++; } else { XMLNodeHdr *nodes = (XMLNodeHdr *) getXPathOperandValue(exprState, nodeSet->nodes.arrayId, XPATH_VAR_NODE_ARRAY); memcpy(start, nodes, nodeSet->count * sizeof(XMLNodeHdr *)); *position += nodeSet->count; } } static int nodePtrComparator(const void *arg1, const void *arg2) { XMLNodeHdr *node1 = (XMLNodeHdr *) arg1; XMLNodeHdr *node2 = (XMLNodeHdr *) arg2; if (*node1 < *node2) { return -1; } else if (*node1 > *node2) { return 1; } else { return 0; } } /* * If value->negative is 'true', set it to 'false' and ensure the sign is * pushed to the actual value. In some cases the value must first be cast * to number. */ static void flipOperandValue(XPathExprOperandValue value, XPathExprState exprState) { if (value->type != XPATH_VAL_NUMBER) { XPathExprOperandValueData valTmp; castXPathExprOperandToNum(exprState, value, &valTmp, false); memcpy(value, &valTmp, sizeof(XPathExprOperandValueData)); /* The cast above must have processed the 'negative' flag. */ Assert(!value->negative); } else { value->v.num *= -1.0f; value->negative = false; } }