From 0e459fbbb628f99b3a53660e616f878a9641b55b Mon Sep 17 00:00:00 2001 From: Juri Leino Date: Sun, 6 Dec 2020 00:50:44 +0100 Subject: [PATCH 1/3] [refactor] ContextItemExpression uses staticReturnType Turns out, a property for the static return type is already defined by the Step super class. ContextItemExpression uses this now and its own returnType property is removed. --- .../main/java/org/exist/xquery/ContextItemExpression.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/exist-core/src/main/java/org/exist/xquery/ContextItemExpression.java b/exist-core/src/main/java/org/exist/xquery/ContextItemExpression.java index 23d18223b70..a67e7efb8ae 100644 --- a/exist-core/src/main/java/org/exist/xquery/ContextItemExpression.java +++ b/exist-core/src/main/java/org/exist/xquery/ContextItemExpression.java @@ -29,8 +29,6 @@ public class ContextItemExpression extends LocationStep { - private int returnType = Type.ITEM; - public ContextItemExpression(final XQueryContext context) { // TODO: create class AnyItemTest (one private implementation found in saxon) super(context, Constants.SELF_AXIS, new AnyNodeTest()); @@ -40,7 +38,7 @@ public ContextItemExpression(final XQueryContext context) { public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { contextInfo.addFlag(DOT_TEST); // set return type to static type to allow for index use in optimization step - returnType = contextInfo.getStaticType(); + staticReturnType = contextInfo.getStaticType(); super.analyze(contextInfo); } @@ -69,7 +67,8 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr @Override public int returnsType() { - return returnType; + // "." will have the same type as the parent expression + return staticReturnType; } @Override From c4d4585565b188fe987f26d122a314177fde323c Mon Sep 17 00:00:00 2001 From: Juri Leino Date: Sun, 6 Dec 2020 01:13:58 +0100 Subject: [PATCH 2/3] [fix] simple predicates evaluating to empty sequences Fixes #2445 By definition the following Xpaths must evaluate to an empty sequence - `[self::b]` - `/@b/c` - `/text()/b` Instead of throwing an Exception the staticReturnType of the current step and the contextInfo returnType are set to Type.EMPTY. --- .../java/org/exist/xquery/LocationStep.java | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/exist-core/src/main/java/org/exist/xquery/LocationStep.java b/exist-core/src/main/java/org/exist/xquery/LocationStep.java index e902a369b53..624795add20 100644 --- a/exist-core/src/main/java/org/exist/xquery/LocationStep.java +++ b/exist-core/src/main/java/org/exist/xquery/LocationStep.java @@ -263,40 +263,61 @@ public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException this.axis = Constants.DESCENDANT_AXIS; } + final Expression contextStep; + final NodeTest stepTest; + final NodeTest contextStepTest; + // static analysis for empty-sequence switch (axis) { case Constants.SELF_AXIS: - if (getTest().getType() != Type.NODE) { - final Expression contextStep = contextInfo.getContextStep(); - if (contextStep instanceof LocationStep cStep) { - - // WM: the following checks will only work on simple filters like //a[self::b], so we - // have to make sure they are not applied to more complex expression types - if (parent.getSubExpressionCount() == 1 && !Type.subTypeOf(getTest().getType(), cStep.getTest().getType())) { - throw new XPathException(this, - ErrorCodes.XPST0005, "Got nothing from self::" + getTest() + ", because parent node kind " + Type.getTypeName(cStep.getTest().getType())); - } + if (getTest().getType() == Type.NODE) { + break; + } - if (parent.getSubExpressionCount() == 1 && !(cStep.getTest().isWildcardTest() || getTest().isWildcardTest()) && !cStep.getTest().equals(getTest())) { - throw new XPathException(this, - ErrorCodes.XPST0005, "Self::" + getTest() + " called on set of nodes which do not contain any nodes of this name."); - } - } + // WM: the following checks will only work on simple filters like //a[self::b], so we + // have to make sure they are not applied to more complex expression types + if (parent.getSubExpressionCount() > 1) { + break; + } + + contextStep = contextInfo.getContextStep(); + if (!(contextStep instanceof LocationStep cStep)) { + break; + } + + stepTest = getTest(); + contextStepTest = cStep.getTest(); + + if (!Type.subTypeOf(stepTest.getType(), contextStepTest.getType())) { + // return empty sequence + contextInfo.setStaticType(Type.EMPTY_SEQUENCE); + staticReturnType = Type.EMPTY_SEQUENCE; + break; + } + + if (!stepTest.isWildcardTest() && + !contextStepTest.isWildcardTest() && + !contextStepTest.equals(stepTest)) { + // return empty sequence + contextInfo.setStaticType(Type.EMPTY_SEQUENCE); + staticReturnType = Type.EMPTY_SEQUENCE; } break; -// case Constants.DESCENDANT_AXIS: +// case Constants.DESCENDANT_AXIS: case Constants.DESCENDANT_SELF_AXIS: - final Expression contextStep = contextInfo.getContextStep(); - if (contextStep instanceof LocationStep cStep) { - - if (( - cStep.getTest().getType() == Type.ATTRIBUTE || - cStep.getTest().getType() == Type.TEXT - ) - && cStep.getTest() != getTest()) { - throw new XPathException(this, - ErrorCodes.XPST0005, "Descendant-or-self::" + getTest() + " from an attribute gets nothing."); - } + contextStep = contextInfo.getContextStep(); + if (!(contextStep instanceof LocationStep cStep)) { + break; + } + + stepTest = getTest(); + contextStepTest = cStep.getTest(); + + if ((contextStepTest.getType() == Type.ATTRIBUTE || contextStepTest.getType() == Type.TEXT) && + contextStepTest != stepTest) { + // return empty sequence + contextInfo.setStaticType(Type.EMPTY_SEQUENCE); + staticReturnType = Type.EMPTY_SEQUENCE; } break; // case Constants.PARENT_AXIS: From d7d13a296388815845a4f47f04d9f0a692b71d2a Mon Sep 17 00:00:00 2001 From: Juri Leino Date: Mon, 17 Mar 2025 19:17:58 +0100 Subject: [PATCH 3/3] [test] add tests to ensure no static typing refs #2445 These tests aim to ensure that certain XPath expressions no longer raise error XPST0005 as exist-db does not implement the static typing feature and this feature is even dropped entirely with the next version of XQuery. --- .../src/test/xquery/no-static-typing.xqm | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 exist-core/src/test/xquery/no-static-typing.xqm diff --git a/exist-core/src/test/xquery/no-static-typing.xqm b/exist-core/src/test/xquery/no-static-typing.xqm new file mode 100644 index 00000000000..200cfc9fda1 --- /dev/null +++ b/exist-core/src/test/xquery/no-static-typing.xqm @@ -0,0 +1,66 @@ +(: + : eXist-db Open Source Native XML Database + : Copyright (C) 2001 The eXist-db Authors + : + : info@exist-db.org + : http://www.exist-db.org + : + : This library is free software; you can redistribute it and/or + : modify it under the terms of the GNU Lesser General Public + : License as published by the Free Software Foundation; either + : version 2.1 of the License, or (at your option) any later version. + : + : This library is distributed in the hope that it will be useful, + : but WITHOUT ANY WARRANTY; without even the implied warranty of + : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + : Lesser General Public License for more details. + : + : You should have received a copy of the GNU Lesser General Public + : License along with this library; if not, write to the Free Software + : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + :) +xquery version "3.1"; + +(: + : Tests created for issue https://github.com/exist-db/exist/issues/2445 + :) +module namespace tnst="http://exist-db.org/xquery/test/no-static-typing"; + +import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql"; + +declare + %test:assertEmpty +function tnst:self-other-node-name() { + [self::b] +}; + +declare + %test:assertEmpty +function tnst:element-after-attribute() { + /@b/c +}; + +declare + %test:assertEmpty +function tnst:element-after-text() { + /text()/b +}; + +declare + %test:assertEmpty +function tnst:descendant-or-self-other-node-name() { + [descendant-or-self::b] +}; + +declare + %test:assertEmpty +function tnst:element-after-descendant-or-self-attribute() { + /descendant-or-self::attribute(b)/c +}; + +declare + %test:assertEmpty +function tnst:element-after-descendant-or-self-text() { + /descendant-or-self::text()/b +}; +