From 04a6d12b35c560edc0c84b1494cc04e05597642b Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 11:47:02 +0200 Subject: [PATCH 01/38] Add support for updating and simple annotations --- .../src/main/antlr/org/exist/xquery/parser/XQuery.g | 11 ++++++++++- .../main/antlr/org/exist/xquery/parser/XQueryTree.g | 2 +- .../test/java/org/exist/xquery/XQueryUpdate3Test.java | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 30ec8b2ae3f..328edbf6e2b 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -263,7 +263,8 @@ prolog throws XPathException ( "declare" "context" "item" ) => contextItemDeclUp { inSetters = false; } | - ( "declare" MOD ) + // bare keyword updating is valid because of rule CompatibilityAnnotation in the XQUF standard + ( "declare" (MOD | "updating") ) => annotateDecl { inSetters = false; } ) SEMICOLON! @@ -456,6 +457,12 @@ annotation : MOD! name=eqName! (LPAREN! literal (COMMA! literal)* RPAREN!)? { #annotation= #(#[ANNOT_DECL, name], #annotation); } + + | "updating"! + { + name = "updating"; + #annotation= #(#[ANNOT_DECL, name], #annotation); + } ; eqName returns [String name] @@ -2224,6 +2231,8 @@ reservedKeywords returns [String name] "map" { name = "map"; } | "array" { name = "array"; } + | + "updating" { name = "updating"; } ; /** diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index d1bea32bfdc..d501a7730e8 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -146,7 +146,7 @@ options { String ns = qname.getNamespaceURI(); if (ns.equals(Namespaces.XPATH_FUNCTIONS_NS)) { String ln = qname.getLocalPart(); - return ("private".equals(ln) || "public".equals(ln)); + return ("private".equals(ln) || "public".equals(ln) || "updating".equals(ln) || "simple".equals(ln)); } else { return !(ns.equals(Namespaces.XML_NS) || ns.equals(Namespaces.SCHEMA_NS) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java new file mode 100644 index 00000000000..d3c36ce9e1c --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -0,0 +1,2 @@ +package org.exist.xquery;public class XQueryUpdate3Test { +} From 081e857165d9542135a54a395189525ed7284b3d Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 11:48:13 +0200 Subject: [PATCH 02/38] Add tests for new annotations --- .../org/exist/xquery/XQueryUpdate3Test.java | 105 +++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index d3c36ce9e1c..0c109921a36 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -1,2 +1,105 @@ -package org.exist.xquery;public class XQueryUpdate3Test { +package org.exist.xquery; + +import antlr.RecognitionException; +import antlr.TokenStreamException; +import antlr.collections.AST; +import org.exist.EXistException; +import org.exist.security.PermissionDeniedException; +import org.exist.storage.BrokerPool; +import org.exist.storage.DBBroker; +import org.exist.test.ExistEmbeddedServer; +import org.exist.xquery.parser.XQueryAST; +import org.exist.xquery.parser.XQueryLexer; +import org.exist.xquery.parser.XQueryParser; +import org.exist.xquery.parser.XQueryTreeParser; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.StringReader; + +import static org.junit.Assert.*; + +public class XQueryUpdate3Test +{ + + @ClassRule + public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true); + + @Test + public void updatingCompatibilityAnnotation() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "xquery version \"3.0\"\n;" + + "module namespace t=\"http://exist-db.org/xquery/test/examples\";\n" + + "declare updating function" + + " t:upsert($e as element(), \n" + + " $an as xs:QName, \n" + + " $av as xs:anyAtomicType) \n" + + " {\n" + + " let $ea := $e/attribute()[fn:node-name(.) = $an]\n" + + " return\n" + + " $ea\n" + + " };"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + } + } + + @Test + public void simpleAnnotation() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "xquery version \"3.0\"\n;" + + "module namespace t=\"http://exist-db.org/xquery/test/examples\";\n" + + "declare %simple function" + + " t:upsert($e as element(), \n" + + " $an as xs:QName, \n" + + " $av as xs:anyAtomicType) \n" + + " {\n" + + " let $ea := $e/attribute()[fn:node-name(.) = $an]\n" + + " return\n" + + " $ea\n" + + " };"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + } + } } From d6520cc0be68434205fe3b9df5548ef8d7060a49 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 13:03:02 +0200 Subject: [PATCH 03/38] Add detection of error XUST0032 --- .../org/exist/xquery/parser/XQueryTree.g | 18 +++++++++++++ .../java/org/exist/xquery/ErrorCodes.java | 3 +++ .../org/exist/xquery/XQueryUpdate3Test.java | 27 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index d501a7730e8..c7bbe8ec705 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -497,6 +497,24 @@ throws PermissionDeniedException, EXistException, XPathException { List annots = new ArrayList(); } (annotations [annots] )? + { + for(int i = 0; i < annots.size(); i++) + { + List la = (List) annots.get(i); + if(la.size() > 0) + { + for(int a = 0; a < la.size(); a++) + { + if(la.get(a).toString().equals("simple") || la.get(a).toString().equals("updating")) + { + throw new XPathException(qname, ErrorCodes.XUST0032, + "It is a static error if an %updating or %simple annotation is used on a VarDecl."); + } + } + } + } + + } ( #( "as" diff --git a/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java b/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java index 38faf8e7497..9d69cd06821 100644 --- a/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java +++ b/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java @@ -228,6 +228,9 @@ public class ErrorCodes { "module has incorrect type"); public static final ErrorCode FOQM0006 = new W3CErrorCode("FOQM0006", "No suitable XQuery processor available."); + /* XQuery 3.0 Update Facility https://www.w3.org/TR/xquery-update-30/#id-new-error-codes */ + public static final ErrorCode XUST0032 = new W3CErrorCode("XUST0032", "It is a static error if an %updating or %simple annotation is used on a VarDecl."); + /* eXist specific XQuery and XPath errors * * Codes have the format [EX][XQ|XP][DY|SE|ST][nnnn] diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index 0c109921a36..f7c733dea7b 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -102,4 +102,31 @@ public void simpleAnnotation() throws EXistException, RecognitionException, XPat } } } + + @Test + public void simpleAnnotationIsInvalidForVariableDeclaration() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = "declare %simple variable $ab := 1;"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.prolog(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.prolog(ast, expr); + } + catch(XPathException ex) { + assertEquals(ErrorCodes.XUST0032, ex.getErrorCode()); + } + } } From b8d3c371b9606b28f6e8b07aace35f94846e66a1 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 18:09:21 +0200 Subject: [PATCH 04/38] Add annotation support for testing of an updating function --- .../antlr/org/exist/xquery/parser/XQuery.g | 15 ++++---- .../org/exist/xquery/parser/XQueryTree.g | 36 ++++++++++++++++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 328edbf6e2b..7cd9b08ff95 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -583,7 +583,7 @@ itemType throws XPathException : ( "item" LPAREN ) => "item"^ LPAREN! RPAREN! | - ( "function" LPAREN ) => functionTest + ( ("function" LPAREN) | ( MOD ) ) => functionTest | ( "map" LPAREN ) => mapType | @@ -618,20 +618,23 @@ atomicType throws XPathException functionTest throws XPathException : - ( "function" LPAREN STAR RPAREN) => anyFunctionTest - | - typedFunctionTest + annotations + ( + ( "function" LPAREN STAR RPAREN ) => anyFunctionTest + | + typedFunctionTest + ) ; anyFunctionTest throws XPathException : - "function"! LPAREN! s:STAR RPAREN! + annotations "function"! LPAREN! s:STAR RPAREN! { #anyFunctionTest = #(#[FUNCTION_TEST, "anyFunction"], #s); } ; typedFunctionTest throws XPathException : - "function"! LPAREN! (sequenceType (COMMA! sequenceType)*)? RPAREN! "as" sequenceType + annotations "function"! LPAREN! (sequenceType (COMMA! sequenceType)*)? RPAREN! "as" sequenceType { #typedFunctionTest = #(#[FUNCTION_TEST, "anyFunction"], #typedFunctionTest); } ; diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index c7bbe8ec705..5b25b6c5142 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -156,8 +156,36 @@ options { } } + private static Annotation[] processAnnotations(List annots) { + Annotation[] anns = new Annotation[annots.size()]; + + //iterate the declare Annotations + for(int i = 0; i < anns.length; i++) { + List la = (List)annots.get(i); + + //extract the Value for the Annotation + LiteralValue[] aValue; + if(la.size() > 1) { + + PathExpr aPath = (PathExpr)la.get(1); + + aValue = new LiteralValue[aPath.getSubExpressionCount()]; + for(int j = 0; j < aValue.length; j++) { + aValue[j] = (LiteralValue)aPath.getExpression(j); + } + } else { + aValue = new LiteralValue[0]; + } + + Annotation a = new Annotation((QName)la.get(0), aValue, null); + anns[i] = a; + } + + return anns; + } + private static void processAnnotations(List annots, FunctionSignature signature) { - Annotation[] anns = new Annotation[annots.size()]; + Annotation[] anns = new Annotation[annots.size()]; //iterate the declare Annotations for(int i = 0; i < anns.length; i++) { @@ -1066,6 +1094,12 @@ throws XPathException } ) | + { List annots = new ArrayList(); } + (annotations [annots])? + { + Annotation[] anns = processAnnotations(annots); + type.setAnnotations(anns); + } #( FUNCTION_TEST { type.setPrimaryType(Type.FUNCTION_REFERENCE); } ( From e7f7ceaceeff29b9bf496dc2d8da22b5100c49d9 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 18:09:44 +0200 Subject: [PATCH 05/38] Change SequenceType to support annotations --- .../main/java/org/exist/xquery/value/SequenceType.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/exist-core/src/main/java/org/exist/xquery/value/SequenceType.java b/exist-core/src/main/java/org/exist/xquery/value/SequenceType.java index 35e6165fa4f..675d7593bec 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/SequenceType.java +++ b/exist-core/src/main/java/org/exist/xquery/value/SequenceType.java @@ -22,6 +22,7 @@ package org.exist.xquery.value; import org.exist.dom.QName; +import org.exist.xquery.Annotation; import org.exist.xquery.Cardinality; import org.exist.xquery.XPathException; import org.w3c.dom.Document; @@ -40,6 +41,8 @@ public class SequenceType { private Cardinality cardinality = Cardinality.EXACTLY_ONE; private QName nodeName = null; + private Annotation[] annotations; + public SequenceType() { } @@ -229,6 +232,13 @@ public void checkCardinality(Sequence seq) throws XPathException { } } + public void setAnnotations(final Annotation[] annotations) { + this.annotations = annotations; + } + public Annotation[] getAnnotations() { + return annotations; + } + @Override public String toString() { if (cardinality == Cardinality.EMPTY_SEQUENCE) { From 1829fff7abe7e7ce07704c74cba8d693cac1eb3b Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 18:11:58 +0200 Subject: [PATCH 06/38] Add test for annotation in testing of an updating function --- .../org/exist/xquery/XQueryUpdate3Test.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index f7c733dea7b..89b9935f8ba 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -12,6 +12,8 @@ import org.exist.xquery.parser.XQueryLexer; import org.exist.xquery.parser.XQueryParser; import org.exist.xquery.parser.XQueryTreeParser; +import org.exist.xquery.value.Sequence; +import org.exist.xquery.value.SequenceType; import org.junit.ClassRule; import org.junit.Test; @@ -129,4 +131,33 @@ public void simpleAnnotationIsInvalidForVariableDeclaration() throws EXistExcept assertEquals(ErrorCodes.XUST0032, ex.getErrorCode()); } } + + @Test + public void testingForUpdatingFunction() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = "%simple function ( * )"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.sequenceType(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + SequenceType type = new SequenceType(); + treeParser.sequenceType(ast, type); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + } + } } From f9570acf4e393676a93973562677c6adafb73ab2 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Fri, 8 Jul 2022 11:43:16 +0200 Subject: [PATCH 07/38] Add Revalidation Mode to XQueryContext --- .../java/org/exist/xquery/XQueryContext.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java index a961b297d8d..aaa75d3dc95 100644 --- a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java +++ b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java @@ -109,6 +109,7 @@ * * @author Wolfgang Meier */ + public class XQueryContext implements BinaryValueManager, Context { private static final Logger LOG = LogManager.getLogger(XQueryContext.class); @@ -420,6 +421,8 @@ public class XQueryContext implements BinaryValueManager, Context { private final Map staticDecimalFormats = new HashMap<>(); private static final QName UNNAMED_DECIMAL_FORMAT = new QName("__UNNAMED__", Function.BUILTIN_FUNCTION_NS); + private RevalidationMode revalidationMode = RevalidationMode.LAX; + public XQueryContext() { profiler = new Profiler(null); staticDecimalFormats.put(UNNAMED_DECIMAL_FORMAT, DecimalFormat.UNNAMED); @@ -3260,6 +3263,22 @@ public void registerBinaryValueInstance(final BinaryValue binaryValue) { binaryValueInstances.push(binaryValue); } + public enum RevalidationMode { + STRICT, + LAX, + SKIP + } + + /** + * Revalidation mode controls the process by which type information + * is recovered for an updated document + */ + public void setRevalidationMode(final RevalidationMode rev) { + this.revalidationMode = rev; + } + + public RevalidationMode getRevalidationMode() { return this.revalidationMode; } + /** * Cleanup Task which is responsible for relasing the streams * of any {@link BinaryValue} which have been used during From f09dbee07d11ca3dded16fd35321b8ffacddcb78 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Fri, 8 Jul 2022 11:45:38 +0200 Subject: [PATCH 08/38] Add support revalidation declaration --- .../antlr/org/exist/xquery/parser/XQuery.g | 18 ++++++++++++++++ .../org/exist/xquery/parser/XQueryTree.g | 21 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 7cd9b08ff95..e4197a4fcc9 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -266,6 +266,11 @@ prolog throws XPathException // bare keyword updating is valid because of rule CompatibilityAnnotation in the XQUF standard ( "declare" (MOD | "updating") ) => annotateDecl { inSetters = false; } + | + ( "declare" "revalidation" ) + => revalidationDecl { + inSetters = false; + } ) SEMICOLON! )* @@ -465,6 +470,11 @@ annotation } ; +revalidationDecl throws XPathException +: + "declare"! "revalidation"^ ("strict" | "lax" | "skip") + ; + eqName returns [String name] { name= null; } : @@ -2236,6 +2246,14 @@ reservedKeywords returns [String name] "array" { name = "array"; } | "updating" { name = "updating"; } + | + "revalidation" { name = "revalidation"; } + | + "strict" { name = "strict"; } + | + "lax" { name = "lax"; } + | + "skip" { name = "skip"; } ; /** diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index 5b25b6c5142..67807692783 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -658,6 +658,27 @@ throws PermissionDeniedException, EXistException, XPathException functionDecl [path] | importDecl [path] + | + #( + "revalidation" + ( + "strict" + { + staticContext.setRevalidationMode(XQueryContext.RevalidationMode.STRICT); + } + | + "lax" + { + staticContext.setRevalidationMode(XQueryContext.RevalidationMode.LAX); + } + | + "skip" + { + staticContext.setRevalidationMode(XQueryContext.RevalidationMode.SKIP); + } + ) + + ) )* ; From 128ae68508210d12175585567849252df48cbbcc Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Fri, 8 Jul 2022 11:46:17 +0200 Subject: [PATCH 09/38] Add detection of error XUST0003 --- exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g | 5 ++++- exist-core/src/main/java/org/exist/xquery/ErrorCodes.java | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index e4197a4fcc9..c67a8bc4555 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -238,7 +238,7 @@ moduleDecl throws XPathException // === Prolog === prolog throws XPathException -{ boolean inSetters = true; } +{ boolean inSetters = true; boolean redeclaration = false; } : ( ( @@ -270,6 +270,9 @@ prolog throws XPathException ( "declare" "revalidation" ) => revalidationDecl { inSetters = false; + if(redeclaration) + throw new XPathException((XQueryAST) returnAST, ErrorCodes.XUST0003, "It is a static error if a Prolog contains more than one revalidation declaration."); + redeclaration = true; } ) SEMICOLON! diff --git a/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java b/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java index 9d69cd06821..7d349fafc36 100644 --- a/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java +++ b/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java @@ -230,6 +230,7 @@ public class ErrorCodes { /* XQuery 3.0 Update Facility https://www.w3.org/TR/xquery-update-30/#id-new-error-codes */ public static final ErrorCode XUST0032 = new W3CErrorCode("XUST0032", "It is a static error if an %updating or %simple annotation is used on a VarDecl."); + public static final ErrorCode XUST0003 = new W3CErrorCode("XUST0003", "It is a static error if a Prolog contains more than one revalidation declaration."); /* eXist specific XQuery and XPath errors * From 134d2b05d9bd854d83f6bc1c9e5713bbe50e43f1 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Fri, 8 Jul 2022 11:46:29 +0200 Subject: [PATCH 10/38] Add test for revalidation declaration --- .../org/exist/xquery/XQueryUpdate3Test.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index 89b9935f8ba..c03ac253cb2 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -160,4 +160,34 @@ public void testingForUpdatingFunction() throws EXistException, RecognitionExcep } } } + + @Test + public void revalidationDeclaration() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = "declare revalidation strict;"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.prolog(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.prolog(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + } + } } From 54cc5b6dbc7c732f983d953e216d2443b2f1269f Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:50:15 +0200 Subject: [PATCH 11/38] Add parsin support for Transform With expression --- .../main/antlr/org/exist/xquery/parser/XQuery.g | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index c67a8bc4555..a15e9db6251 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -1030,7 +1030,7 @@ castableExpr throws XPathException castExpr throws XPathException : - arrowExpr ( "cast"^ "as"! singleType )? + transformWithExpr ( "cast"^ "as"! singleType )? ; comparisonExpr throws XPathException @@ -1304,6 +1304,17 @@ arrowExpr throws XPathException unaryExpr ( ARROW_OP^ arrowFunctionSpecifier argumentList )* ; + +// This is not perfectly adherent to the standard grammar +// at https://www.w3.org/TR/xquery-31/#prod-xquery31-ArrowExpr +// but the standard XQuery 3.1 grammar conflicts with the XQuery Update Facility 3.0 grammar +// https://www.w3.org/TR/xquery-update-30/#prod-xquery30-TransformWithExpr +// However, the end behavior should be identical +transformWithExpr throws XPathException +: + arrowExpr ( "transform"^ "with"! LCURLY! ( expr )? RCURLY! )? + ; + arrowFunctionSpecifier throws XPathException { String name= null; } : @@ -2257,6 +2268,8 @@ reservedKeywords returns [String name] "lax" { name = "lax"; } | "skip" { name = "skip"; } + | + "transform" { name = "transform"; } ; /** From 842a8ad10fd1461d61f4a9599abd0a8ecb842291 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:50:40 +0200 Subject: [PATCH 12/38] Add parsing support for Copy Modify expression --- .../src/main/antlr/org/exist/xquery/parser/XQuery.g | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index a15e9db6251..67e247ca0a7 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -722,6 +722,7 @@ exprSingle throws XPathException | ( "switch" LPAREN ) => switchExpr | ( "typeswitch" LPAREN ) => typeswitchExpr | ( "update" ( "replace" | "value" | "insert" | "delete" | "rename" )) => updateExpr + | ( "copy" DOLLAR) => copyModifyExpr | orExpr ; @@ -765,6 +766,14 @@ renameExpr throws XPathException "rename" exprSingle "as"! exprSingle ; +copyModifyExpr throws XPathException +: + "copy"^ letVarBinding ( COMMA! letVarBinding )* + "modify"! exprSingle + "return"! exprSingle + ; + + // === try/catch === tryCatchExpr throws XPathException : From 309043b06caded0e6b671574c96525c8201c0c2e Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:52:05 +0200 Subject: [PATCH 13/38] Add CopyModifyExpression class to implement Transform With and Copy Modify expressions --- .../exist/xquery/CopyModifyExpression.java | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/CopyModifyExpression.java diff --git a/exist-core/src/main/java/org/exist/xquery/CopyModifyExpression.java b/exist-core/src/main/java/org/exist/xquery/CopyModifyExpression.java new file mode 100644 index 00000000000..f99d5ba0390 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/CopyModifyExpression.java @@ -0,0 +1,134 @@ +package org.exist.xquery; + +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.Item; +import org.exist.xquery.value.Sequence; + +import java.util.ArrayList; +import java.util.List; + +public class CopyModifyExpression extends PathExpr { + + public static class CopySource { + protected String varName; + protected Expression inputSequence; + + public String getVariable() { + return this.varName; + } + public Expression getInputSequence() { + return this.inputSequence; + } + + public void setVariable(String varName) { + this.varName = varName; + } + + public void setInputSequence(Expression inputSequence) { + this.inputSequence = inputSequence; + } + + public CopySource(String name, Expression value) { + this.varName = name; + this.inputSequence = value; + } + + public CopySource() { + } + } + + + protected List sources; + protected Expression modifyExpr; + protected Expression returnExpr; + + public enum Category { + UPDATING, + SIMPLE + } + + // see https://www.w3.org/TR/xquery-update-30/#id-copy-modify for details + public Category getCategory() { + return Category.SIMPLE; + } + + public CopyModifyExpression(XQueryContext context) { + super(context); + this.sources = new ArrayList(); + } + + public void addCopySource(String varName, Expression value) { + this.sources.add(new CopySource(varName, value)); + } + + public void setModifyExpr(Expression expr) { + this.modifyExpr = expr; + } + + public Expression getModifyExpr() { + return this.modifyExpr; + } + + public void setReturnExpr(Expression expr) { + this.returnExpr = expr; + } + + public Expression getReturnExpr() { + return this.returnExpr; + } + + @Override + public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { + } + + @Override + public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException { + return Sequence.EMPTY_SEQUENCE; + } + + @Override + public Cardinality getCardinality() { + return Cardinality.ONE_OR_MORE; + } + + @Override + public void dump(ExpressionDumper dumper) { + dumper.display("copy").nl(); + dumper.startIndent(); + for(int i = 0; i < sources.size(); i++) + { + dumper.display("$").display(sources.get(i).varName); + dumper.display(" := "); + sources.get(i).inputSequence.dump(dumper); + } + dumper.endIndent(); + dumper.display("modify").nl(); + modifyExpr.dump(dumper); + dumper.nl().display("return "); + dumper.startIndent(); + returnExpr.dump(dumper); + dumper.endIndent(); + } + + @Override + public String toString() { + final StringBuilder result = new StringBuilder(); + result.append("copy "); + for(int i = 0; i < sources.size(); i++) + { + result.append("$").append(sources.get(i).varName); + result.append(sources.get(i).inputSequence.toString()); + if (sources.size() > 1 && i < sources.size() - 1) + result.append(", "); + else + result.append(" "); + } + result.append(" "); + result.append("modify "); + result.append(modifyExpr.toString()); + result.append(" "); + result.append("return "); + result.append(returnExpr.toString()); + return result.toString(); + } +} From ad1609390a428ad1eb075f7656d22ff8dffa0c6a Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:52:39 +0200 Subject: [PATCH 14/38] Add intermediate AST handling for Copy Modify and Transform With expressions --- .../org/exist/xquery/parser/XQueryTree.g | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index 67807692783..787d8782dc9 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -2138,6 +2138,10 @@ throws PermissionDeniedException, EXistException, XPathException step=numericExpr [path] | step=updateExpr [path] + | + step=transformWithExpr [path] + | + step=copyModifyExpr [path] ; /** @@ -3486,6 +3490,92 @@ throws PermissionDeniedException, EXistException, XPathException ) ; + +transformWithExpr [PathExpr path] +returns [Expression step] +throws PermissionDeniedException, EXistException, XPathException +{ + step= null; + CopyModifyExpression cpme = new CopyModifyExpression(context); + QName virtualVariable = null; +}: + #( + tr:"transform" + { + PathExpr transformExpr = new PathExpr(context); + } + t:expr [transformExpr] + { + try { + virtualVariable = QName.parse(staticContext, "virtualCopyModifyName", null); + } + catch (final IllegalQNameException e) { + // this should never happen, since it is a virtual QName + } + + final VariableDeclaration decl = new VariableDeclaration(context, virtualVariable, transformExpr); + decl.setASTNode(t); + + cpme.addCopySource("virtualCopyModifyName", decl); + } + { + PathExpr withExpr = new PathExpr(context); + } + ( + expr [withExpr] + )? + { + // see https://www.w3.org/TR/xquery-update-30/#id-transform-with for explanation + // in short TransformWith is a shorthand notation for a common Copy Modify Expression + + PathExpr refExpr = new PathExpr(context); + refExpr.add(new VariableReference(context, virtualVariable)); + cpme.setModifyExpr(new OpSimpleMap(context, refExpr, withExpr)); + cpme.setReturnExpr(refExpr); + + cpme.setASTNode(tr); + path.add(cpme); + step = cpme; + } + ) + ; + +copyModifyExpr [PathExpr path] +returns [Expression step] +throws PermissionDeniedException, EXistException, XPathException +{ + step= null; + CopyModifyExpression cpme = new CopyModifyExpression(context); + PathExpr modify = new PathExpr(context); + PathExpr retExpr = new PathExpr(context); +}: + #( + cp:"copy" + ( + #( + copyVarName:VARIABLE_BINDING + { + PathExpr inputSequence= new PathExpr(context); + } + step=expr [inputSequence] + { + cpme.addCopySource(copyVarName.getText(), inputSequence); + } + ) + )+ + step=expr [modify] + step=expr [retExpr] + { + cpme.setModifyExpr(modify); + cpme.setReturnExpr(retExpr); + + cpme.setASTNode(cp); + path.add(cpme); + step = cpme; + } + ) + ; + typeCastExpr [PathExpr path] returns [Expression step] throws PermissionDeniedException, EXistException, XPathException From 018018ae691e4894c0da418499f8b9aca198d097 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:52:54 +0200 Subject: [PATCH 15/38] Add tests for Copy Modify and Transform With expressions --- .../org/exist/xquery/XQueryUpdate3Test.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index c03ac253cb2..b9bb943353d 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -190,4 +190,66 @@ public void revalidationDeclaration() throws EXistException, RecognitionExceptio } } } + + @Test + public void transformWith() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = "$e transform with { $e + 1 }\n"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + Expression ret = treeParser.expr(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(ret instanceof CopyModifyExpression); + } + } + + @Test + public void copyModifyExprTest() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = "copy $je := $e\n" + + " modify $je\n" + + " return $je"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.expr(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + } + } } From 3740ff936f13b04561717a97fae0eebdbd644e5a Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 21:27:23 +0200 Subject: [PATCH 16/38] Add Category enum to Expression in order to distinguish between updating and simple expressions --- .../java/org/exist/xquery/CopyModifyExpression.java | 6 +----- .../java/org/exist/xquery/DynamicFunctionCall.java | 13 ++++++++++++- .../src/main/java/org/exist/xquery/Expression.java | 12 ++++++++++++ .../java/org/exist/xquery/update/Modification.java | 3 +++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/exist-core/src/main/java/org/exist/xquery/CopyModifyExpression.java b/exist-core/src/main/java/org/exist/xquery/CopyModifyExpression.java index f99d5ba0390..47973ba141d 100644 --- a/exist-core/src/main/java/org/exist/xquery/CopyModifyExpression.java +++ b/exist-core/src/main/java/org/exist/xquery/CopyModifyExpression.java @@ -42,13 +42,9 @@ public CopySource() { protected Expression modifyExpr; protected Expression returnExpr; - public enum Category { - UPDATING, - SIMPLE - } - // see https://www.w3.org/TR/xquery-update-30/#id-copy-modify for details public Category getCategory() { + // placeholder implementation return Category.SIMPLE; } diff --git a/exist-core/src/main/java/org/exist/xquery/DynamicFunctionCall.java b/exist-core/src/main/java/org/exist/xquery/DynamicFunctionCall.java index 25b1aa73502..35d0c604612 100644 --- a/exist-core/src/main/java/org/exist/xquery/DynamicFunctionCall.java +++ b/exist-core/src/main/java/org/exist/xquery/DynamicFunctionCall.java @@ -34,9 +34,10 @@ public class DynamicFunctionCall extends AbstractExpression { private final Expression functionExpr; private final List arguments; private final boolean isPartial; - private AnalyzeContextInfo cachedContextInfo; + private Category category = Category.SIMPLE; + public DynamicFunctionCall(final XQueryContext context, final Expression fun, final List args, final boolean partial) { super(context); setLocation(fun.getLine(), fun.getColumn()); @@ -155,4 +156,14 @@ public String toString() { return builder.toString(); } + + + @Override + public Category getCategory() { + return this.category; + } + + public void setCategory(Category category) { + this.category = category; + } } diff --git a/exist-core/src/main/java/org/exist/xquery/Expression.java b/exist-core/src/main/java/org/exist/xquery/Expression.java index b32205a370d..b4dec95c638 100644 --- a/exist-core/src/main/java/org/exist/xquery/Expression.java +++ b/exist-core/src/main/java/org/exist/xquery/Expression.java @@ -36,6 +36,16 @@ */ public interface Expression { + /** + * Updating expressions are a new category of expression introduced by XQuery Update Facility 3.0 + * An updating expression is an expression that can return a non-empty pending update list + * Simple expressions are all expressions that are not updating expressions + */ + enum Category { + UPDATING, + SIMPLE + } + // Flags to be passed to analyze: /** * Indicates that the query engine will call the expression once for every @@ -255,4 +265,6 @@ public interface Expression { public boolean allowMixedNodesInReturn(); public Expression getParent(); + + public default Category getCategory() { return Category.SIMPLE; }; } \ No newline at end of file diff --git a/exist-core/src/main/java/org/exist/xquery/update/Modification.java b/exist-core/src/main/java/org/exist/xquery/update/Modification.java index 16b767f5dc5..5876a90c3ee 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/Modification.java +++ b/exist-core/src/main/java/org/exist/xquery/update/Modification.java @@ -351,4 +351,7 @@ protected Txn getTransaction() { return node.getParentNode(); } } + + @Override + public Category getCategory() { return Category.UPDATING; } } From c93d36e338c5cdcf9d13df2dfc702b3dfbfdc438 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 21:29:15 +0200 Subject: [PATCH 17/38] Add parsing support for Dynamic updating function call --- .../src/main/antlr/org/exist/xquery/parser/XQuery.g | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 67e247ca0a7..a6bcb265924 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -723,6 +723,7 @@ exprSingle throws XPathException | ( "typeswitch" LPAREN ) => typeswitchExpr | ( "update" ( "replace" | "value" | "insert" | "delete" | "rename" )) => updateExpr | ( "copy" DOLLAR) => copyModifyExpr + | ( "invoke" "updating" ) => dynamicUpdFunCall | orExpr ; @@ -1308,6 +1309,11 @@ postfixExpr throws XPathException )* ; +dynamicUpdFunCall throws XPathException +: + "invoke"! "updating"^ primaryExpr ( argumentList )* + ; + arrowExpr throws XPathException : unaryExpr ( ARROW_OP^ arrowFunctionSpecifier argumentList )* @@ -2279,6 +2285,8 @@ reservedKeywords returns [String name] "skip" { name = "skip"; } | "transform" { name = "transform"; } + | + "invoke" { name = "invoke"; } ; /** From ac632e6664f0c1e14419c58dc9c2e3271f79274e Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 21:30:43 +0200 Subject: [PATCH 18/38] Add intermediate AST handling for dynamic updating function call --- .../org/exist/xquery/parser/XQueryTree.g | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index 787d8782dc9..8fa881788b4 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -2142,6 +2142,8 @@ throws PermissionDeniedException, EXistException, XPathException step=transformWithExpr [path] | step=copyModifyExpr [path] + | + step=dynamicUpdFunCall [path] ; /** @@ -2940,6 +2942,37 @@ throws PermissionDeniedException, EXistException, XPathException ) ; +dynamicUpdFunCall [PathExpr path] +returns [Expression step] +throws PermissionDeniedException, EXistException, XPathException +{ + step = null; + PathExpr primary = new PathExpr(context); +} +: + #( + "updating" + { + List params = new ArrayList(5); + boolean isPartial = false; + } + step=primaryExpr [primary] + ( + ( + { PathExpr pathExpr = new PathExpr(context); } + expr [pathExpr] { params.add(pathExpr); } + ) + )* + { + DynamicFunctionCall dynCall = new DynamicFunctionCall(context, step, params, isPartial); + dynCall.setCategory(Expression.Category.UPDATING); + step = dynCall; + path.add(step); + } + ) +; + + functionCall [PathExpr path] returns [Expression step] throws PermissionDeniedException, EXistException, XPathException From 58ebc44d654930a776709436885ad0c28dc295ee Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 21:31:27 +0200 Subject: [PATCH 19/38] Add test for dynamic updating function call --- .../org/exist/xquery/XQueryUpdate3Test.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index b9bb943353d..e6453a5e5bd 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -252,4 +252,39 @@ public void copyModifyExprTest() throws EXistException, RecognitionException, XP } } } + + @Test + public void dynamicUpdatingFunctionCall() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = "let $f := fn:put#2\n" + + "return invoke updating $f(,\"newnode.xml\")"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(((DebuggableExpression) ((LetExpr)expr.getFirst()).returnExpr).getFirst() instanceof DynamicFunctionCall); + DynamicFunctionCall dfc = (DynamicFunctionCall) ((DebuggableExpression) ((LetExpr)expr.getFirst()).returnExpr).getFirst(); + assertEquals(Expression.Category.UPDATING, dfc.getCategory()); + } + } } From 21313a8d57166c1cb64be9cd2ab30807e59f7756 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 12 Jul 2022 18:17:51 +0200 Subject: [PATCH 20/38] Add parsing support for insert expression --- .../antlr/org/exist/xquery/parser/XQuery.g | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index a6bcb265924..1f7bae04df8 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -180,6 +180,7 @@ imaginaryTokenDefinitions PRAGMA GTEQ SEQUENCE + INSERT_TARGET ; // === XPointer === @@ -722,6 +723,7 @@ exprSingle throws XPathException | ( "switch" LPAREN ) => switchExpr | ( "typeswitch" LPAREN ) => typeswitchExpr | ( "update" ( "replace" | "value" | "insert" | "delete" | "rename" )) => updateExpr + | ( "insert" ) => xqufInsertExpr | ( "copy" DOLLAR) => copyModifyExpr | ( "invoke" "updating" ) => dynamicUpdFunCall | orExpr @@ -757,6 +759,27 @@ insertExpr throws XPathException ( "into" | "preceding" | "following" ) exprSingle ; +xqufInsertExpr throws XPathException +: + "insert"^ ( "node"! | "nodes"! ) exprSingle + insertExprTargetChoice exprSingle + ; + +insertExprTargetChoice throws XPathException +{ String target = null; } +: + ( + ( ( "as"! ( "first"! { target = "first"; } | "last"! { target = "last"; } ) )? "into"! { + if (target == null) + target = "into"; + } ) + | "after"! { target = "after"; } + | "before"! { target = "before"; } + ) + { #insertExprTargetChoice= #(#[INSERT_TARGET, target]); } + +; + deleteExpr throws XPathException : "delete" exprSingle @@ -2287,6 +2310,16 @@ reservedKeywords returns [String name] "transform" { name = "transform"; } | "invoke" { name = "invoke"; } + | + "nodes" { name = "nodes"; } + | + "first" { name = "first"; } + | + "last" { name = "last"; } + | + "after" { name = "after"; } + | + "before" { name = "before"; } ; /** From e814d1cda20ce81f68dd02d28ee5a605c5c74925 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 12 Jul 2022 18:18:27 +0200 Subject: [PATCH 21/38] Add intermediate AST support for insert expression --- .../org/exist/xquery/parser/XQueryTree.g | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index 8fa881788b4..e383b759efb 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -2139,6 +2139,8 @@ throws PermissionDeniedException, EXistException, XPathException | step=updateExpr [path] | + step=xqufInsertExpr [path] + | step=transformWithExpr [path] | step=copyModifyExpr [path] @@ -3758,6 +3760,51 @@ throws XPathException, PermissionDeniedException, EXistException ) ; +xqufInsertExpr [PathExpr path] +returns [Expression step] +throws XPathException, PermissionDeniedException, EXistException +{ +}: + #( + insertAST:"insert" + { + PathExpr source = new PathExpr(context); + PathExpr target = new PathExpr(context); + InsertExpr.Choice choice = null; + } + step=expr [source] + #( + it:INSERT_TARGET + { + switch (it.getText()) { + case "first": + choice = InsertExpr.Choice.FIRST; + break; + case "last": + choice = InsertExpr.Choice.LAST; + break; + case "into": + choice = InsertExpr.Choice.INTO; + break; + case "before": + choice = InsertExpr.Choice.BEFORE; + break; + case "after": + choice = InsertExpr.Choice.AFTER; + break; + } + } + ) + step=expr [target] + { + InsertExpr insertExpr = new InsertExpr(context, source, target, choice); + insertExpr.setASTNode(insertAST); + path.add(insertExpr); + step = insertExpr; + } + ) + ; + mapConstr [PathExpr path] returns [Expression step] throws XPathException, PermissionDeniedException, EXistException From dc3138b81bed9e99e9d43e386b49fdaf8858a7f1 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 12 Jul 2022 18:18:51 +0200 Subject: [PATCH 22/38] Add InsertExpr class to support XQUF insert expression --- .../java/org/exist/xquery/InsertExpr.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/InsertExpr.java diff --git a/exist-core/src/main/java/org/exist/xquery/InsertExpr.java b/exist-core/src/main/java/org/exist/xquery/InsertExpr.java new file mode 100644 index 00000000000..912406da67c --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/InsertExpr.java @@ -0,0 +1,77 @@ +package org.exist.xquery; + +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.Item; +import org.exist.xquery.value.Sequence; +import org.exist.xquery.value.Type; + +public class InsertExpr extends AbstractExpression +{ + public enum Choice { + FIRST, + LAST, + INTO, + AFTER, + BEFORE + } + + protected final Expression source; + protected final Expression target; + protected final Choice choice; + + public InsertExpr(XQueryContext context, Expression source, Expression target, Choice choice) { + super(context); + this.source = source; + this.target = target; + this.choice = choice; + } + + @Override + public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { + } + + @Override + public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException { + return Sequence.EMPTY_SEQUENCE; + } + + @Override + public int returnsType() + { + // placeholder implementation + return Type.EMPTY; + } + + public Category getCategory() { + // placeholder implementation + return Category.UPDATING; + } + + @Override + public Cardinality getCardinality() { + return Cardinality.ONE_OR_MORE; + } + + @Override + public void dump(ExpressionDumper dumper) { + dumper.display("insert").nl(); + dumper.startIndent(); + source.dump(dumper); + dumper.endIndent(); + dumper.display(choice).nl(); + dumper.startIndent(); + target.dump(dumper); + } + + @Override + public String toString() { + final StringBuilder result = new StringBuilder(); + result.append("insert "); + result.append(source.toString()); + result.append(" "); + result.append(choice.toString()); + result.append(" "); + result.append(target.toString()); + return result.toString(); + } +} From b18b13e1ae5eed3d02d4e66dd0c7b689dfb41b7f Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 12 Jul 2022 18:19:11 +0200 Subject: [PATCH 23/38] Add test for insert expression --- .../org/exist/xquery/XQueryUpdate3Test.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index e6453a5e5bd..04d9e00258f 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -287,4 +287,39 @@ public void dynamicUpdatingFunctionCall() throws EXistException, RecognitionExce assertEquals(Expression.Category.UPDATING, dfc.getCategory()); } } + + @Test + public void insertExpr() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "insert node 2005\n" + + " after book/publisher"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.expr(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(expr.getFirst() instanceof InsertExpr); + assertEquals(Expression.Category.UPDATING, expr.getFirst().getCategory()); + } + } } From 36d2ba34fd4153554960a36680ecdd3c41077032 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:18:58 +0200 Subject: [PATCH 24/38] Add base class ModifyingExpression to support insert, delete, replace and rename expressions --- .../org/exist/xquery/ModifyingExpression.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/ModifyingExpression.java diff --git a/exist-core/src/main/java/org/exist/xquery/ModifyingExpression.java b/exist-core/src/main/java/org/exist/xquery/ModifyingExpression.java new file mode 100644 index 00000000000..53c390a15b7 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/ModifyingExpression.java @@ -0,0 +1,27 @@ +package org.exist.xquery; + +import org.exist.xquery.value.Type; + +public abstract class ModifyingExpression extends AbstractExpression { + + protected final Expression targetExpr; + + public ModifyingExpression(XQueryContext context, Expression target) { + super(context); + this.targetExpr = target; + } + + @Override + public int returnsType() { + // placeholder implementation + return Type.EMPTY; + } + + public Category getCategory() { + return Category.UPDATING; + } + + public Expression getTargetExpr() { + return targetExpr; + } +} From 6fa890e1af9cdee11f4f64fe383549a8620f558d Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:33:36 +0200 Subject: [PATCH 25/38] Update class InsertExpr --- .../java/org/exist/xquery/InsertExpr.java | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/exist-core/src/main/java/org/exist/xquery/InsertExpr.java b/exist-core/src/main/java/org/exist/xquery/InsertExpr.java index 912406da67c..9b41a7379a1 100644 --- a/exist-core/src/main/java/org/exist/xquery/InsertExpr.java +++ b/exist-core/src/main/java/org/exist/xquery/InsertExpr.java @@ -3,10 +3,8 @@ import org.exist.xquery.util.ExpressionDumper; import org.exist.xquery.value.Item; import org.exist.xquery.value.Sequence; -import org.exist.xquery.value.Type; -public class InsertExpr extends AbstractExpression -{ +public class InsertExpr extends ModifyingExpression { public enum Choice { FIRST, LAST, @@ -15,14 +13,12 @@ public enum Choice { BEFORE } - protected final Expression source; - protected final Expression target; + protected final Expression sourceExpr; protected final Choice choice; public InsertExpr(XQueryContext context, Expression source, Expression target, Choice choice) { - super(context); - this.source = source; - this.target = target; + super(context, target); + this.sourceExpr = source; this.choice = choice; } @@ -35,18 +31,6 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc return Sequence.EMPTY_SEQUENCE; } - @Override - public int returnsType() - { - // placeholder implementation - return Type.EMPTY; - } - - public Category getCategory() { - // placeholder implementation - return Category.UPDATING; - } - @Override public Cardinality getCardinality() { return Cardinality.ONE_OR_MORE; @@ -56,22 +40,31 @@ public Cardinality getCardinality() { public void dump(ExpressionDumper dumper) { dumper.display("insert").nl(); dumper.startIndent(); - source.dump(dumper); + sourceExpr.dump(dumper); dumper.endIndent(); dumper.display(choice).nl(); dumper.startIndent(); - target.dump(dumper); + targetExpr.dump(dumper); + dumper.endIndent(); } @Override public String toString() { final StringBuilder result = new StringBuilder(); result.append("insert "); - result.append(source.toString()); + result.append(sourceExpr.toString()); result.append(" "); result.append(choice.toString()); result.append(" "); - result.append(target.toString()); + result.append(targetExpr.toString()); return result.toString(); } + + public Choice getChoice() { + return choice; + } + + public Expression getSourceExpr() { + return sourceExpr; + } } From c1a66e9d59464da5c304d30c8d00cffbd7d5f2c9 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:34:09 +0200 Subject: [PATCH 26/38] Add parsing support for delete expression --- .../antlr/org/exist/xquery/parser/XQuery.g | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 1f7bae04df8..1fd42a19c59 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -724,6 +724,7 @@ exprSingle throws XPathException | ( "typeswitch" LPAREN ) => typeswitchExpr | ( "update" ( "replace" | "value" | "insert" | "delete" | "rename" )) => updateExpr | ( "insert" ) => xqufInsertExpr + | ( "delete" ) => xqufDeleteExpr | ( "copy" DOLLAR) => copyModifyExpr | ( "invoke" "updating" ) => dynamicUpdFunCall | orExpr @@ -759,6 +760,16 @@ insertExpr throws XPathException ( "into" | "preceding" | "following" ) exprSingle ; +deleteExpr throws XPathException +: + "delete" exprSingle + ; + +renameExpr throws XPathException +: + "rename" exprSingle "as"! exprSingle + ; + xqufInsertExpr throws XPathException : "insert"^ ( "node"! | "nodes"! ) exprSingle @@ -780,14 +791,9 @@ insertExprTargetChoice throws XPathException ; -deleteExpr throws XPathException -: - "delete" exprSingle - ; - -renameExpr throws XPathException +xqufDeleteExpr throws XPathException : - "rename" exprSingle "as"! exprSingle + "delete"^ ( "node"! | "nodes"! ) exprSingle ; copyModifyExpr throws XPathException From 447269bce2cf7db6bf19e8592283b6283e9c98d8 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:34:37 +0200 Subject: [PATCH 27/38] Add intermediate AST support for delete expression --- .../org/exist/xquery/parser/XQueryTree.g | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index e383b759efb..852f7b823d2 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -2141,6 +2141,8 @@ throws PermissionDeniedException, EXistException, XPathException | step=xqufInsertExpr [path] | + step=xqufDeleteExpr [path] + | step=transformWithExpr [path] | step=copyModifyExpr [path] @@ -3805,6 +3807,26 @@ throws XPathException, PermissionDeniedException, EXistException ) ; +xqufDeleteExpr [PathExpr path] +returns [Expression step] +throws XPathException, PermissionDeniedException, EXistException +{ +}: + #( + deleteAST:"delete" + { + PathExpr target = new PathExpr(context); + } + step=expr [target] + { + DeleteExpr deleteExpr = new DeleteExpr(context, target); + deleteExpr.setASTNode(deleteAST); + path.add(deleteExpr); + step = deleteExpr; + } + ) + ; + mapConstr [PathExpr path] returns [Expression step] throws XPathException, PermissionDeniedException, EXistException From f730cb98cc01c2d46cd29a3a8995bd661360f253 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:35:12 +0200 Subject: [PATCH 28/38] Add DeleteExpr class to support XQUF delete expression --- .../java/org/exist/xquery/DeleteExpr.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/DeleteExpr.java diff --git a/exist-core/src/main/java/org/exist/xquery/DeleteExpr.java b/exist-core/src/main/java/org/exist/xquery/DeleteExpr.java new file mode 100644 index 00000000000..5a10f5e103e --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/DeleteExpr.java @@ -0,0 +1,41 @@ +package org.exist.xquery; + +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.Item; +import org.exist.xquery.value.Sequence; + +public class DeleteExpr extends ModifyingExpression { + + public DeleteExpr(XQueryContext context, Expression target) + { + super(context, target); + } + + @Override + public void analyze(AnalyzeContextInfo contextInfo) throws XPathException + { + + } + + @Override + public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException + { + return Sequence.EMPTY_SEQUENCE; + } + + public void dump(ExpressionDumper dumper) { + dumper.display("delete").nl(); + dumper.startIndent(); + targetExpr.dump(dumper); + dumper.endIndent(); + } + + @Override + public String toString() { + final StringBuilder result = new StringBuilder(); + result.append("delete "); + result.append(" "); + result.append(targetExpr.toString()); + return result.toString(); + } +} From 57b4cf18fd5b64141f443d43f64764f425417110 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:35:40 +0200 Subject: [PATCH 29/38] Add test for delete expression --- .../org/exist/xquery/XQueryUpdate3Test.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index 04d9e00258f..fc64f97b523 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -322,4 +322,38 @@ public void insertExpr() throws EXistException, RecognitionException, XPathExcep assertEquals(Expression.Category.UPDATING, expr.getFirst().getCategory()); } } + + @Test + public void deleteExpr() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "delete node fn:doc(\"bib.xml\")/books/book[1]/author[last()]"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.expr(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(expr.getFirst() instanceof DeleteExpr); + assertEquals(Expression.Category.UPDATING, expr.getFirst().getCategory()); + } + } } From 0f69177abc2b5f3accd22e5b73d2f110eb745518 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:28:25 +0200 Subject: [PATCH 30/38] Add parsing support for replace expression --- .../src/main/antlr/org/exist/xquery/parser/XQuery.g | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 1fd42a19c59..3236fa3ff72 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -723,8 +723,9 @@ exprSingle throws XPathException | ( "switch" LPAREN ) => switchExpr | ( "typeswitch" LPAREN ) => typeswitchExpr | ( "update" ( "replace" | "value" | "insert" | "delete" | "rename" )) => updateExpr - | ( "insert" ) => xqufInsertExpr - | ( "delete" ) => xqufDeleteExpr + | ( "insert" ( "node" | "nodes" ) ) => xqufInsertExpr + | ( "delete" ( "node" | "nodes" ) ) => xqufDeleteExpr + | ( "replace" ( "value" | "node" ) ) => xqufReplaceExpr | ( "copy" DOLLAR) => copyModifyExpr | ( "invoke" "updating" ) => dynamicUpdFunCall | orExpr @@ -796,6 +797,11 @@ xqufDeleteExpr throws XPathException "delete"^ ( "node"! | "nodes"! ) exprSingle ; +xqufReplaceExpr throws XPathException +: + "replace"^ ("value" "of"!)? "node"! exprSingle "with"! exprSingle + ; + copyModifyExpr throws XPathException : "copy"^ letVarBinding ( COMMA! letVarBinding )* From df1f086da0f89607ca1ab48041b034d8b1db17f1 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:29:10 +0200 Subject: [PATCH 31/38] Add handling of intermediate AST for replace expression --- .../org/exist/xquery/parser/XQueryTree.g | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index 852f7b823d2..e604aab1a70 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -2142,6 +2142,8 @@ throws PermissionDeniedException, EXistException, XPathException step=xqufInsertExpr [path] | step=xqufDeleteExpr [path] + | + step=xqufReplaceExpr [path] | step=transformWithExpr [path] | @@ -3827,6 +3829,35 @@ throws XPathException, PermissionDeniedException, EXistException ) ; +xqufReplaceExpr [PathExpr path] +returns [Expression step] +throws XPathException, PermissionDeniedException, EXistException +{ +}: + #( + replaceAST:"replace" + { + PathExpr target = new PathExpr(context); + PathExpr with = new PathExpr(context); + ReplaceExpr.ReplacementType replacementType = ReplaceExpr.ReplacementType.NODE; + } + ( + "value" + { + replacementType = ReplaceExpr.ReplacementType.VALUE; + } + )? + step=expr [target] + step=expr [with] + { + ReplaceExpr replaceExpr = new ReplaceExpr(context, target, with, replacementType); + replaceExpr.setASTNode(replaceAST); + path.add(replaceExpr); + step = replaceExpr; + } + ) + ; + mapConstr [PathExpr path] returns [Expression step] throws XPathException, PermissionDeniedException, EXistException From db081c47b45ecb78887c9abead71fef8b750d646 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:29:34 +0200 Subject: [PATCH 32/38] Add ReplaceExpr class to implement replace expression --- .../java/org/exist/xquery/ReplaceExpr.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/ReplaceExpr.java diff --git a/exist-core/src/main/java/org/exist/xquery/ReplaceExpr.java b/exist-core/src/main/java/org/exist/xquery/ReplaceExpr.java new file mode 100644 index 00000000000..d8239ee1bb0 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/ReplaceExpr.java @@ -0,0 +1,67 @@ +package org.exist.xquery; + +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.Item; +import org.exist.xquery.value.Sequence; + +public class ReplaceExpr extends ModifyingExpression{ + public enum ReplacementType { + NODE, + VALUE + } + + protected final Expression withExpr; + protected final ReplaceExpr.ReplacementType replacementType; + + public ReplaceExpr(XQueryContext context, Expression target, Expression with, ReplaceExpr.ReplacementType replacementType) { + super(context, target); + this.withExpr = with; + this.replacementType = replacementType; + } + + @Override + public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { + } + + @Override + public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException { + return Sequence.EMPTY_SEQUENCE; + } + + @Override + public Cardinality getCardinality() { + return Cardinality.ONE_OR_MORE; + } + + @Override + public void dump(ExpressionDumper dumper) { + dumper.display("replace").nl(); + dumper.startIndent(); + targetExpr.dump(dumper); + dumper.endIndent(); + dumper.display(replacementType).nl(); + dumper.startIndent(); + withExpr.dump(dumper); + dumper.endIndent(); + } + + @Override + public String toString() { + final StringBuilder result = new StringBuilder(); + result.append("replace "); + result.append(targetExpr.toString()); + result.append(" "); + result.append(replacementType.toString()); + result.append(" "); + result.append(withExpr.toString()); + return result.toString(); + } + + public ReplaceExpr.ReplacementType getReplacementType() { + return replacementType; + } + + public Expression getWithExpr() { + return withExpr; + } +} From 977777a43c93ebf71a6c380b1a7b1b08d6b430c1 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:30:00 +0200 Subject: [PATCH 33/38] Add tests for replace expression --- .../org/exist/xquery/XQueryUpdate3Test.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index fc64f97b523..9758315b40f 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -356,4 +356,78 @@ public void deleteExpr() throws EXistException, RecognitionException, XPathExcep assertEquals(Expression.Category.UPDATING, expr.getFirst().getCategory()); } } + + @Test + public void replaceNodeExpr() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "replace node fn:doc(\"bib.xml\")/books/book[1]/publisher\n" + + "with fn:doc(\"bib.xml\")/books/book[2]/publisher"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.expr(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(expr.getFirst() instanceof ReplaceExpr); + assertEquals(ReplaceExpr.ReplacementType.NODE,((ReplaceExpr) expr.getFirst()).getReplacementType()); + assertEquals("doc(\"bib.xml\")/child::{}books/child::{}book[1]/child::{}publisher",((ReplaceExpr) expr.getFirst()).getTargetExpr().toString()); + assertEquals("doc(\"bib.xml\")/child::{}books/child::{}book[2]/child::{}publisher",((ReplaceExpr) expr.getFirst()).getWithExpr().toString()); + } + } + + @Test + public void replaceValueExpr() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "replace value of node fn:doc(\"bib.xml\")/books/book[1]/price\n" + + "with fn:doc(\"bib.xml\")/books/book[1]/price * 1.1"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.expr(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(expr.getFirst() instanceof ReplaceExpr); + assertEquals(ReplaceExpr.ReplacementType.VALUE,((ReplaceExpr) expr.getFirst()).getReplacementType()); + assertEquals("doc(\"bib.xml\")/child::{}books/child::{}book[1]/child::{}price",((ReplaceExpr) expr.getFirst()).getTargetExpr().toString()); + assertEquals("doc(\"bib.xml\")/child::{}books/child::{}book[1]/child::{}price * 1.1",((ReplaceExpr) expr.getFirst()).getWithExpr().toString()); + } + } } From 9246ea8f7a1440f9d8885eb86ce27565431996c5 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:46:12 +0200 Subject: [PATCH 34/38] Add parsing support for rename expression --- .../src/main/antlr/org/exist/xquery/parser/XQuery.g | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 3236fa3ff72..fef482dc750 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -726,7 +726,8 @@ exprSingle throws XPathException | ( "insert" ( "node" | "nodes" ) ) => xqufInsertExpr | ( "delete" ( "node" | "nodes" ) ) => xqufDeleteExpr | ( "replace" ( "value" | "node" ) ) => xqufReplaceExpr - | ( "copy" DOLLAR) => copyModifyExpr + | ( "rename" "node" ) => xqufRenameExpr + | ( "copy" DOLLAR ) => copyModifyExpr | ( "invoke" "updating" ) => dynamicUpdFunCall | orExpr ; @@ -802,6 +803,11 @@ xqufReplaceExpr throws XPathException "replace"^ ("value" "of"!)? "node"! exprSingle "with"! exprSingle ; +xqufRenameExpr throws XPathException +: + "rename"^ "node"! exprSingle "as"! exprSingle + ; + copyModifyExpr throws XPathException : "copy"^ letVarBinding ( COMMA! letVarBinding )* From 61cf0e302a326ae1fbc8addf11b8a7db8a4e0e79 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:46:41 +0200 Subject: [PATCH 35/38] Add handling of intermediate AST for rename expression --- .../org/exist/xquery/parser/XQueryTree.g | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index e604aab1a70..d45d1eda559 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -2144,6 +2144,8 @@ throws PermissionDeniedException, EXistException, XPathException step=xqufDeleteExpr [path] | step=xqufReplaceExpr [path] + | + step=xqufRenameExpr [path] | step=transformWithExpr [path] | @@ -3858,6 +3860,28 @@ throws XPathException, PermissionDeniedException, EXistException ) ; +xqufRenameExpr [PathExpr path] +returns [Expression step] +throws XPathException, PermissionDeniedException, EXistException +{ +}: + #( + renameAST:"rename" + { + PathExpr target = new PathExpr(context); + PathExpr newName = new PathExpr(context); + } + step=expr [target] + step=expr [newName] + { + RenameExpr renameExpr = new RenameExpr(context, target, newName); + renameExpr.setASTNode(renameAST); + path.add(renameExpr); + step = renameExpr; + } + ) + ; + mapConstr [PathExpr path] returns [Expression step] throws XPathException, PermissionDeniedException, EXistException From cf3dd0c8cf5e1bbaf11502fb9aa6f81ccfa0ef98 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:47:02 +0200 Subject: [PATCH 36/38] Add class RenameExpr to support rename expression --- .../java/org/exist/xquery/RenameExpr.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/RenameExpr.java diff --git a/exist-core/src/main/java/org/exist/xquery/RenameExpr.java b/exist-core/src/main/java/org/exist/xquery/RenameExpr.java new file mode 100644 index 00000000000..9255287a8a7 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/RenameExpr.java @@ -0,0 +1,53 @@ +package org.exist.xquery; + +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.Item; +import org.exist.xquery.value.Sequence; + +public class RenameExpr extends ModifyingExpression { + protected final Expression newNameExpr; + + public RenameExpr(XQueryContext context, Expression target, Expression newName) { + super(context, target); + this.newNameExpr = newName; + } + + @Override + public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { + } + + @Override + public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException { + return Sequence.EMPTY_SEQUENCE; + } + + @Override + public Cardinality getCardinality() { + return Cardinality.ONE_OR_MORE; + } + + @Override + public void dump(ExpressionDumper dumper) { + dumper.display("replace").nl(); + dumper.startIndent(); + targetExpr.dump(dumper); + dumper.endIndent(); + dumper.startIndent(); + newNameExpr.dump(dumper); + dumper.endIndent(); + } + + @Override + public String toString() { + final StringBuilder result = new StringBuilder(); + result.append("replace "); + result.append(targetExpr.toString()); + result.append(" "); + result.append(newNameExpr.toString()); + return result.toString(); + } + + public Expression getNewNameExpr() { + return newNameExpr; + } +} From 98d754b55f16cf1176594f541f1b6937351c5128 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:47:13 +0200 Subject: [PATCH 37/38] Add test for rename expression --- .../org/exist/xquery/XQueryUpdate3Test.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index 9758315b40f..8803b2048d7 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -430,4 +430,40 @@ public void replaceValueExpr() throws EXistException, RecognitionException, XPat assertEquals("doc(\"bib.xml\")/child::{}books/child::{}book[1]/child::{}price * 1.1",((ReplaceExpr) expr.getFirst()).getWithExpr().toString()); } } + + @Test + public void renameExpr() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "rename node fn:doc(\"bib.xml\")/books/book[1]/author[1]\n" + + "as \"principal-author\""; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.expr(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(expr.getFirst() instanceof RenameExpr); + assertEquals("doc(\"bib.xml\")/child::{}books/child::{}book[1]/child::{}author[1]",((RenameExpr) expr.getFirst()).getTargetExpr().toString()); + assertEquals("\"principal-author\"",((RenameExpr) expr.getFirst()).getNewNameExpr().toString()); + } + } } From ac450e12875d66b7df5521c8cf1ae2f967bcba90 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 20 Jul 2022 17:27:39 +0200 Subject: [PATCH 38/38] Add new tests found in reference --- .../org/exist/xquery/XQueryUpdate3Test.java | 157 +++++++++++++++++- 1 file changed, 155 insertions(+), 2 deletions(-) diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java index 8803b2048d7..9eba0c49ba2 100644 --- a/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/XQueryUpdate3Test.java @@ -226,7 +226,7 @@ public void transformWith() throws EXistException, RecognitionException, XPathEx public void copyModifyExprTest() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException { String query = "copy $je := $e\n" + - " modify $je\n" + + " modify delete node $je/salary\n" + " return $je"; BrokerPool pool = BrokerPool.getInstance(); @@ -245,11 +245,50 @@ public void copyModifyExprTest() throws EXistException, RecognitionException, XP XQueryTreeParser treeParser = new XQueryTreeParser(context); PathExpr expr = new PathExpr(context); - treeParser.expr(ast, expr); + Expression ret = treeParser.expr(ast, expr); if (treeParser.foundErrors()) { fail(treeParser.getErrorMessage()); return; } + + assertTrue(ret instanceof CopyModifyExpression); + assertEquals("$je",((CopyModifyExpression) ret).getReturnExpr().toString()); + } + } + + @Test + public void copyModifyExprTestComplexModify() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = "copy $newx := $oldx\n" + + " modify (rename node $newx as \"newx\", \n" + + " replace value of node $newx with $newx * 2)\n" + + " return ($oldx, $newx)"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + Expression ret = treeParser.expr(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(ret instanceof CopyModifyExpression); + assertEquals("( replace $newx \"newx\", replace $newx VALUE $newx * 2 )",((CopyModifyExpression) ret).getModifyExpr().toString()); + assertEquals("( $oldx, $newx )",((CopyModifyExpression) ret).getReturnExpr().toString()); } } @@ -320,6 +359,48 @@ public void insertExpr() throws EXistException, RecognitionException, XPathExcep assertTrue(expr.getFirst() instanceof InsertExpr); assertEquals(Expression.Category.UPDATING, expr.getFirst().getCategory()); + assertEquals(InsertExpr.Choice.AFTER, ((InsertExpr) expr.getFirst()).getChoice()); + } + } + + @Test + public void insertExprAsLast() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "insert node $new-police-report\n" + + " as last into fn:doc(\"insurance.xml\")/policies\n" + + " /policy[id = $pid]\n" + + " /driver[license = $license]\n" + + " /accident[date = $accdate]\n" + + " /police-reports"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.expr(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(expr.getFirst() instanceof InsertExpr); + assertEquals(Expression.Category.UPDATING, expr.getFirst().getCategory()); + assertEquals("$new-police-report", ((InsertExpr) expr.getFirst()).getSourceExpr().toString()); + assertEquals(InsertExpr.Choice.LAST, ((InsertExpr) expr.getFirst()).getChoice()); } } @@ -329,6 +410,42 @@ public void deleteExpr() throws EXistException, RecognitionException, XPathExcep String query = "delete node fn:doc(\"bib.xml\")/books/book[1]/author[last()]"; + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.expr(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(expr.getFirst() instanceof DeleteExpr); + assertEquals(Expression.Category.UPDATING, expr.getFirst().getCategory()); + assertEquals("doc(\"bib.xml\")/child::{}books/child::{}book[1]/child::{}author[last()]", ((DeleteExpr) expr.getFirst()).getTargetExpr().toString()); + } + } + + @Test + public void deleteExprComplex() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "delete nodes /email/message\n" + + " [date > xs:dayTimeDuration(\"P365D\")]"; + BrokerPool pool = BrokerPool.getInstance(); try(final DBBroker broker = pool.getBroker()) { // parse the query into the internal syntax tree @@ -466,4 +583,40 @@ public void renameExpr() throws EXistException, RecognitionException, XPathExcep assertEquals("\"principal-author\"",((RenameExpr) expr.getFirst()).getNewNameExpr().toString()); } } + + @Test + public void renameExprWithExpr() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException + { + String query = + "rename node fn:doc(\"bib.xml\")/books/book[1]/author[1]\n" + + "as $newname"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.expr(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.expr(ast, expr); + + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue(expr.getFirst() instanceof RenameExpr); + assertEquals("doc(\"bib.xml\")/child::{}books/child::{}book[1]/child::{}author[1]",((RenameExpr) expr.getFirst()).getTargetExpr().toString()); + assertEquals("$newname",((RenameExpr) expr.getFirst()).getNewNameExpr().toString()); + } + } }