From f6b3623e2a8724fb676fd1d355b9192e544c2875 Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Sat, 10 Jun 2023 23:00:10 +0100 Subject: [PATCH 01/39] [refactor] Move existing non W3C specification compliant XQuery Update implementation from org.exist.xquery.update to org.exist.xquery.update.legacy --- exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g | 1 + exist-core/src/main/java/org/exist/xquery/XQueryContext.java | 2 +- .../java/org/exist/xquery/functions/xmldb/XMLDBDefragment.java | 2 +- .../main/java/org/exist/xquery/update/{ => legacy}/Delete.java | 3 +-- .../main/java/org/exist/xquery/update/{ => legacy}/Insert.java | 2 +- .../org/exist/xquery/update/{ => legacy}/Modification.java | 2 +- .../main/java/org/exist/xquery/update/{ => legacy}/Rename.java | 2 +- .../java/org/exist/xquery/update/{ => legacy}/Replace.java | 2 +- .../main/java/org/exist/xquery/update/{ => legacy}/Update.java | 3 +-- .../exist/xquery/update/{ => legacy}/AbstractTestUpdate.java | 2 +- .../exist/xquery/update/{ => legacy}/IndexIntegrationTest.java | 2 +- .../org/exist/xquery/update/{ => legacy}/UpdateInsertTest.java | 2 +- .../exist/xquery/update/{ => legacy}/UpdateReplaceTest.java | 2 +- .../org/exist/xquery/update/{ => legacy}/UpdateValueTest.java | 2 +- 14 files changed, 14 insertions(+), 15 deletions(-) rename exist-core/src/main/java/org/exist/xquery/update/{ => legacy}/Delete.java (99%) rename exist-core/src/main/java/org/exist/xquery/update/{ => legacy}/Insert.java (99%) rename exist-core/src/main/java/org/exist/xquery/update/{ => legacy}/Modification.java (99%) rename exist-core/src/main/java/org/exist/xquery/update/{ => legacy}/Rename.java (99%) rename exist-core/src/main/java/org/exist/xquery/update/{ => legacy}/Replace.java (99%) rename exist-core/src/main/java/org/exist/xquery/update/{ => legacy}/Update.java (99%) rename exist-core/src/test/java/org/exist/xquery/update/{ => legacy}/AbstractTestUpdate.java (99%) rename exist-core/src/test/java/org/exist/xquery/update/{ => legacy}/IndexIntegrationTest.java (99%) rename exist-core/src/test/java/org/exist/xquery/update/{ => legacy}/UpdateInsertTest.java (99%) rename exist-core/src/test/java/org/exist/xquery/update/{ => legacy}/UpdateReplaceTest.java (99%) rename exist-core/src/test/java/org/exist/xquery/update/{ => legacy}/UpdateValueTest.java (98%) 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 e965f13c295..abd0f9e5b49 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 @@ -53,6 +53,7 @@ header { import org.exist.xquery.value.*; import org.exist.xquery.functions.fn.*; import org.exist.xquery.update.*; + import org.exist.xquery.update.legacy.*; import org.exist.storage.ElementValue; import org.exist.xquery.functions.map.MapExpr; import org.exist.xquery.functions.array.ArrayConstructor; 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 6953d748945..fd828783bca 100644 --- a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java +++ b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java @@ -91,7 +91,7 @@ import org.exist.xmldb.XmldbURI; import org.exist.xquery.parser.*; import org.exist.xquery.pragmas.*; -import org.exist.xquery.update.Modification; +import org.exist.xquery.update.legacy.Modification; import org.exist.xquery.util.SerializerUtils; import org.exist.xquery.value.*; import org.w3c.dom.Node; diff --git a/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBDefragment.java b/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBDefragment.java index 0408777f828..7180b31b6ab 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBDefragment.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/xmldb/XMLDBDefragment.java @@ -33,7 +33,7 @@ import org.exist.xquery.FunctionSignature; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; -import org.exist.xquery.update.Modification; +import org.exist.xquery.update.legacy.Modification; import org.exist.xquery.value.FunctionParameterSequenceType; import org.exist.xquery.value.IntegerValue; import org.exist.xquery.value.Sequence; diff --git a/exist-core/src/main/java/org/exist/xquery/update/Delete.java b/exist-core/src/main/java/org/exist/xquery/update/legacy/Delete.java similarity index 99% rename from exist-core/src/main/java/org/exist/xquery/update/Delete.java rename to exist-core/src/main/java/org/exist/xquery/update/legacy/Delete.java index b6d19596785..923ee13a0b6 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/Delete.java +++ b/exist-core/src/main/java/org/exist/xquery/update/legacy/Delete.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.exist.EXistException; import org.exist.collections.triggers.TriggerException; @@ -46,7 +46,6 @@ import org.exist.xquery.value.StringValue; import org.exist.xquery.value.Type; import org.exist.xquery.value.ValueSequence; -import org.w3c.dom.Attr; import org.w3c.dom.Node; /** diff --git a/exist-core/src/main/java/org/exist/xquery/update/Insert.java b/exist-core/src/main/java/org/exist/xquery/update/legacy/Insert.java similarity index 99% rename from exist-core/src/main/java/org/exist/xquery/update/Insert.java rename to exist-core/src/main/java/org/exist/xquery/update/legacy/Insert.java index 9d5f4e04e61..d96da1e2fbc 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/Insert.java +++ b/exist-core/src/main/java/org/exist/xquery/update/legacy/Insert.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.exist.EXistException; import org.exist.Namespaces; diff --git a/exist-core/src/main/java/org/exist/xquery/update/Modification.java b/exist-core/src/main/java/org/exist/xquery/update/legacy/Modification.java similarity index 99% rename from exist-core/src/main/java/org/exist/xquery/update/Modification.java rename to exist-core/src/main/java/org/exist/xquery/update/legacy/Modification.java index b4d201e8b67..3055901cb12 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/Modification.java +++ b/exist-core/src/main/java/org/exist/xquery/update/legacy/Modification.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import java.util.Iterator; diff --git a/exist-core/src/main/java/org/exist/xquery/update/Rename.java b/exist-core/src/main/java/org/exist/xquery/update/legacy/Rename.java similarity index 99% rename from exist-core/src/main/java/org/exist/xquery/update/Rename.java rename to exist-core/src/main/java/org/exist/xquery/update/legacy/Rename.java index d5c7a77744a..e3cabf37457 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/Rename.java +++ b/exist-core/src/main/java/org/exist/xquery/update/legacy/Rename.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.exist.EXistException; import org.exist.collections.triggers.TriggerException; diff --git a/exist-core/src/main/java/org/exist/xquery/update/Replace.java b/exist-core/src/main/java/org/exist/xquery/update/legacy/Replace.java similarity index 99% rename from exist-core/src/main/java/org/exist/xquery/update/Replace.java rename to exist-core/src/main/java/org/exist/xquery/update/legacy/Replace.java index 755b400af96..6d4ab0f715b 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/Replace.java +++ b/exist-core/src/main/java/org/exist/xquery/update/legacy/Replace.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.exist.EXistException; import org.exist.collections.triggers.TriggerException; diff --git a/exist-core/src/main/java/org/exist/xquery/update/Update.java b/exist-core/src/main/java/org/exist/xquery/update/legacy/Update.java similarity index 99% rename from exist-core/src/main/java/org/exist/xquery/update/Update.java rename to exist-core/src/main/java/org/exist/xquery/update/legacy/Update.java index cc44a0a5b24..ba4638be3bd 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/Update.java +++ b/exist-core/src/main/java/org/exist/xquery/update/legacy/Update.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,7 +32,6 @@ import org.exist.dom.persistent.StoredNode; import org.exist.dom.persistent.TextImpl; import org.exist.security.Permission; -import org.exist.security.PermissionDeniedException; import org.exist.storage.NotificationService; import org.exist.storage.UpdateListener; import org.exist.storage.txn.Txn; diff --git a/exist-core/src/test/java/org/exist/xquery/update/AbstractTestUpdate.java b/exist-core/src/test/java/org/exist/xquery/update/legacy/AbstractTestUpdate.java similarity index 99% rename from exist-core/src/test/java/org/exist/xquery/update/AbstractTestUpdate.java rename to exist-core/src/test/java/org/exist/xquery/update/legacy/AbstractTestUpdate.java index a7886583684..674b8942212 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/AbstractTestUpdate.java +++ b/exist-core/src/test/java/org/exist/xquery/update/legacy/AbstractTestUpdate.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.exist.TestUtils; import org.exist.test.ExistXmldbEmbeddedServer; diff --git a/exist-core/src/test/java/org/exist/xquery/update/IndexIntegrationTest.java b/exist-core/src/test/java/org/exist/xquery/update/legacy/IndexIntegrationTest.java similarity index 99% rename from exist-core/src/test/java/org/exist/xquery/update/IndexIntegrationTest.java rename to exist-core/src/test/java/org/exist/xquery/update/legacy/IndexIntegrationTest.java index fc1d0f1f716..a887b939887 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/IndexIntegrationTest.java +++ b/exist-core/src/test/java/org/exist/xquery/update/legacy/IndexIntegrationTest.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.easymock.IArgumentMatcher; import org.easymock.IMocksControl; diff --git a/exist-core/src/test/java/org/exist/xquery/update/UpdateInsertTest.java b/exist-core/src/test/java/org/exist/xquery/update/legacy/UpdateInsertTest.java similarity index 99% rename from exist-core/src/test/java/org/exist/xquery/update/UpdateInsertTest.java rename to exist-core/src/test/java/org/exist/xquery/update/legacy/UpdateInsertTest.java index ac097ded063..80fe46387c8 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/UpdateInsertTest.java +++ b/exist-core/src/test/java/org/exist/xquery/update/legacy/UpdateInsertTest.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.junit.Test; import org.xmldb.api.base.XMLDBException; diff --git a/exist-core/src/test/java/org/exist/xquery/update/UpdateReplaceTest.java b/exist-core/src/test/java/org/exist/xquery/update/legacy/UpdateReplaceTest.java similarity index 99% rename from exist-core/src/test/java/org/exist/xquery/update/UpdateReplaceTest.java rename to exist-core/src/test/java/org/exist/xquery/update/legacy/UpdateReplaceTest.java index 2f390a68afa..30980e446a4 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/UpdateReplaceTest.java +++ b/exist-core/src/test/java/org/exist/xquery/update/legacy/UpdateReplaceTest.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.junit.Test; import org.w3c.dom.Document; diff --git a/exist-core/src/test/java/org/exist/xquery/update/UpdateValueTest.java b/exist-core/src/test/java/org/exist/xquery/update/legacy/UpdateValueTest.java similarity index 98% rename from exist-core/src/test/java/org/exist/xquery/update/UpdateValueTest.java rename to exist-core/src/test/java/org/exist/xquery/update/legacy/UpdateValueTest.java index e2894380d70..26f575c05ba 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/UpdateValueTest.java +++ b/exist-core/src/test/java/org/exist/xquery/update/legacy/UpdateValueTest.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.xquery.update; +package org.exist.xquery.update.legacy; import org.junit.Test; import org.xmldb.api.base.XMLDBException; From 3b5b54fd7b000e1eb5eff69ee8c5828276f8623a Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 11:47:02 +0200 Subject: [PATCH 02/39] [feature] Add support for XQUF 3.0 updating and simple annotations --- .../antlr/org/exist/xquery/parser/XQuery.g | 11 +- .../org/exist/xquery/parser/XQueryTree.g | 2 +- .../xquery/update/XQueryUpdate3Test.java | 126 ++++++++++++++++++ 3 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 exist-core/src/test/java/org/exist/xquery/update/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 f5f3cb0209a..78708cf1c63 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! @@ -454,6 +455,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] @@ -2228,6 +2235,8 @@ reservedKeywords returns [String name] "empty-sequence" { name = "empty-sequence"; } | "schema-element" { name = "schema-element"; } + | + "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 abd0f9e5b49..d5fa20e754b 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 @@ -147,7 +147,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/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java new file mode 100644 index 00000000000..bddefc0c06a --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -0,0 +1,126 @@ +/* + * 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 + */ +package org.exist.xquery.update; + +import antlr.RecognitionException; +import antlr.TokenStreamException; +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.PathExpr; +import org.exist.xquery.XPathException; +import org.exist.xquery.XQueryContext; +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.*; + +/** + * @author Adam Retter + * @author Gabriele Tomassetti + */ +public class XQueryUpdate3Test { + + @ClassRule + public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true); + + @Test + public void updatingCompatibilityAnnotation() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException { + final 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" + + " };"; + + final BrokerPool pool = BrokerPool.getInstance(); + try (final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + final XQueryContext context = new XQueryContext(broker.getBrokerPool()); + final XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + final XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + } + + final XQueryAST ast = (XQueryAST) xparser.getAST(); + final XQueryTreeParser treeParser = new XQueryTreeParser(context); + final PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + } + } + } + + @Test + public void simpleAnnotation() throws EXistException, RecognitionException, XPathException, TokenStreamException, PermissionDeniedException { + final 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" + + " };"; + + final BrokerPool pool = BrokerPool.getInstance(); + try (final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + final XQueryContext context = new XQueryContext(broker.getBrokerPool()); + final XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + final XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + final XQueryAST ast = (XQueryAST) xparser.getAST(); + final XQueryTreeParser treeParser = new XQueryTreeParser(context); + final PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + } + } + } +} From c02bb0275389f044072e9f20c23e8ee4d933bf61 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 13:03:02 +0200 Subject: [PATCH 03/39] [refactor] Add detection of error XUST0032 --- .../org/exist/xquery/parser/XQueryTree.g | 18 ++++++++++++ .../java/org/exist/xquery/ErrorCodes.java | 3 ++ .../xquery/update/XQueryUpdate3Test.java | 28 +++++++++++++++++++ 3 files changed, 49 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 d5fa20e754b..f7fc71589d0 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 @@ -500,6 +500,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 d916c2d6434..07a93ed91c5 100644 --- a/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java +++ b/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java @@ -239,6 +239,9 @@ public class ErrorCodes { public static final ErrorCode XTSE0165 = new W3CErrorCode("XTSE0165","It is a static error if the processor is not able to retrieve the resource identified by the URI reference [ in the href attribute of xsl:include or xsl:import] , or if the resource that is retrieved does not contain a stylesheet module conforming to this specification."); + /* 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/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index bddefc0c06a..2c80edebfeb 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -28,6 +28,7 @@ import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.test.ExistEmbeddedServer; +import org.exist.xquery.ErrorCodes; import org.exist.xquery.PathExpr; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; @@ -123,4 +124,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 5814ad62d995835d9e87efd780676e088b60be3c Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 18:09:21 +0200 Subject: [PATCH 04/39] [feature] Add annotation support for testing of an XQUF updating function --- .../main/antlr/org/exist/xquery/parser/XQuery.g | 15 +++++++++------ .../antlr/org/exist/xquery/parser/XQueryTree.g | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 8 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 78708cf1c63..05150c85dde 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 @@ -581,7 +581,7 @@ itemType throws XPathException : ( "item" LPAREN ) => "item"^ LPAREN! RPAREN! | - ( "function" LPAREN ) => functionTest + ( ("function" LPAREN) | ( MOD ) ) => functionTest | ( "map" LPAREN ) => mapType | @@ -616,20 +616,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 f7fc71589d0..d0cbbf43345 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 @@ -157,7 +157,7 @@ options { } } - private static void processAnnotations(List annots, FunctionSignature signature) { + private static Annotation[] processAnnotations(List annots, FunctionSignature signature) { Annotation[] anns = new Annotation[annots.size()]; //iterate the declare Annotations @@ -183,7 +183,11 @@ options { } //set the Annotations on the Function Signature - signature.setAnnotations(anns); + if (signature != null) { + signature.setAnnotations(anns); + } + + return anns; } private static void processParams(List varList, UserDefinedFunction func, FunctionSignature signature) @@ -1081,6 +1085,12 @@ throws XPathException } ) | + { List annots = new ArrayList(); } + (annotations [annots])? + { + Annotation[] anns = processAnnotations(annots, null); + type.setAnnotations(anns); + } #( FUNCTION_TEST { type.setPrimaryType(Type.FUNCTION_REFERENCE); } ( From 669f90e796ee9ea7c5b40fab506e93104ba3bff2 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 18:09:44 +0200 Subject: [PATCH 05/39] [refactor] 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 0ad7175f5bd..bac8c2964c2 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.Expression; import org.exist.xquery.XPathException; @@ -41,6 +42,8 @@ public class SequenceType { private Cardinality cardinality = Cardinality.EXACTLY_ONE; private QName nodeName = null; + private Annotation[] annotations; + public SequenceType() { } @@ -230,6 +233,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 6fb663ac085bd5289c8e37e7e952931b70e7f398 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Thu, 7 Jul 2022 18:11:58 +0200 Subject: [PATCH 06/39] [test] Add test for annotation in testing of an updating function --- .../xquery/update/XQueryUpdate3Test.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index 2c80edebfeb..6600bdbaadf 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -36,6 +36,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; @@ -151,4 +153,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 98fa9f61769c61da8577916bd48e75cfa2bbb8d5 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Fri, 8 Jul 2022 11:43:16 +0200 Subject: [PATCH 07/39] [feature] 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 fd828783bca..0daabb14edb 100644 --- a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java +++ b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java @@ -111,6 +111,7 @@ * * @author Wolfgang Meier */ + public class XQueryContext implements BinaryValueManager, Context { private static final Logger LOG = LogManager.getLogger(XQueryContext.class); @@ -412,6 +413,8 @@ public class XQueryContext implements BinaryValueManager, Context { private final Map staticDecimalFormats = hashMap(Tuple(UNNAMED_DECIMAL_FORMAT, DecimalFormat.UNNAMED)); + private RevalidationMode revalidationMode = RevalidationMode.LAX; + // Only used for testing, e.g. {@link org.exist.test.runner.XQueryTestRunner}. private Optional testRepository = Optional.empty(); @@ -3283,6 +3286,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 bab26ef9b402e1b41ab11291db030e75624df86e Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Fri, 8 Jul 2022 11:45:38 +0200 Subject: [PATCH 08/39] [feature] Add support for revalidation declaration --- .../antlr/org/exist/xquery/parser/XQuery.g | 18 +++++++++++++++++ .../org/exist/xquery/parser/XQueryTree.g | 20 +++++++++++++++++++ 2 files changed, 38 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 05150c85dde..441a5a76337 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! )* @@ -463,6 +468,11 @@ annotation } ; +revalidationDecl throws XPathException +: + "declare"! "revalidation"^ ("strict" | "lax" | "skip") + ; + eqName returns [String name] { name= null; } : @@ -2240,6 +2250,14 @@ reservedKeywords returns [String name] "schema-element" { name = "schema-element"; } | "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 d0cbbf43345..a5a6251a6dd 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 @@ -644,6 +644,26 @@ 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 fd36ddca5a4039eb28bf9f9c4d3ca00069370f54 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Fri, 8 Jul 2022 11:46:17 +0200 Subject: [PATCH 09/39] [refactor] 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 441a5a76337..023dcffbb36 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 07a93ed91c5..47c7c9c1c30 100644 --- a/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java +++ b/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java @@ -241,6 +241,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 2d55fff45e25118b9e01d3b08f75ea6a89f96772 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Fri, 8 Jul 2022 11:46:29 +0200 Subject: [PATCH 10/39] [feature] Add test for revalidation declaration --- .../xquery/update/XQueryUpdate3Test.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index 6600bdbaadf..9fba55dc78e 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -182,4 +182,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 c61a4be09ce92d991bd1f53637113ecf24fde47e Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:50:15 +0200 Subject: [PATCH 11/39] [feature] Add parsing 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 023dcffbb36..e8a4d4e2de8 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 @@ -1028,7 +1028,7 @@ castableExpr throws XPathException castExpr throws XPathException : - arrowExpr ( "cast"^ "as"! singleType )? + transformWithExpr ( "cast"^ "as"! singleType )? ; comparisonExpr throws XPathException @@ -1302,6 +1302,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; } : @@ -2261,6 +2272,8 @@ reservedKeywords returns [String name] "lax" { name = "lax"; } | "skip" { name = "skip"; } + | + "transform" { name = "transform"; } ; /** From 1f944c813ca3c20421d06cb20fac5b46028fdde2 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:50:40 +0200 Subject: [PATCH 12/39] [feature] 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 e8a4d4e2de8..bb440ead515 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 @@ -720,6 +720,7 @@ exprSingle throws XPathException | ( "switch" LPAREN ) => switchExpr | ( "typeswitch" LPAREN ) => typeswitchExpr | ( "update" ( "replace" | "value" | "insert" | "delete" | "rename" )) => updateExpr + | ( "copy" DOLLAR) => copyModifyExpr | orExpr ; @@ -763,6 +764,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 9b5b14cf703ff88069ad1aef377f6b9843407200 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:52:05 +0200 Subject: [PATCH 13/39] [feature] Add CopyModifyExpression class to implement Transform With and Copy Modify expressions --- .../xquery/update/CopyModifyExpression.java | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/update/CopyModifyExpression.java diff --git a/exist-core/src/main/java/org/exist/xquery/update/CopyModifyExpression.java b/exist-core/src/main/java/org/exist/xquery/update/CopyModifyExpression.java new file mode 100644 index 00000000000..84c9d26ba00 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/update/CopyModifyExpression.java @@ -0,0 +1,160 @@ +/* + * 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 + */ +package org.exist.xquery.update; + +import 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; + +/** + * @author Adam Retter + * @author Gabriele Tomassetti + */ +public class CopyModifyExpression extends PathExpr { + + public static class CopySource { + private String varName; + private Expression inputSequence; + + public String getVariable() { + return this.varName; + } + + public Expression getInputSequence() { + return this.inputSequence; + } + + public void setVariable(final String varName) { + this.varName = varName; + } + + public void setInputSequence(final Expression inputSequence) { + this.inputSequence = inputSequence; + } + + public CopySource(final String name, final Expression value) { + this.varName = name; + this.inputSequence = value; + } + + public CopySource() { + } + } + + + private List sources; + private Expression modifyExpr; + private 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(final XQueryContext context) { + super(context); + this.sources = new ArrayList<>(); + } + + public void addCopySource(final String varName, final Expression value) { + this.sources.add(new CopySource(varName, value)); + } + + public void setModifyExpr(final Expression expr) { + this.modifyExpr = expr; + } + + public Expression getModifyExpr() { + return this.modifyExpr; + } + + public void setReturnExpr(final Expression expr) { + this.returnExpr = expr; + } + + public Expression getReturnExpr() { + return this.returnExpr; + } + + @Override + public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException { + } + + @Override + public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException { + return Sequence.EMPTY_SEQUENCE; + } + + @Override + public Cardinality getCardinality() { + return Cardinality.ONE_OR_MORE; + } + + @Override + public void dump(final 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 872ec1d84f27b10aa5a7b3610007b12f65e225a3 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:52:39 +0200 Subject: [PATCH 14/39] [feature] 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 a5a6251a6dd..cedddaf1bb6 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 @@ -2195,6 +2195,10 @@ throws PermissionDeniedException, EXistException, XPathException step=numericExpr [path] | step=updateExpr [path] + | + step=transformWithExpr [path] + | + step=copyModifyExpr [path] ; /** @@ -3602,6 +3606,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 9adcfb976105a80b9dc7662501b98b47129f2c90 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 12:52:54 +0200 Subject: [PATCH 15/39] [test] Add tests for Copy Modify and Transform With expressions --- .../xquery/update/XQueryUpdate3Test.java | 67 +++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index 9fba55dc78e..7f9932f794d 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -28,10 +28,7 @@ import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.test.ExistEmbeddedServer; -import org.exist.xquery.ErrorCodes; -import org.exist.xquery.PathExpr; -import org.exist.xquery.XPathException; -import org.exist.xquery.XQueryContext; +import org.exist.xquery.*; import org.exist.xquery.parser.XQueryAST; import org.exist.xquery.parser.XQueryLexer; import org.exist.xquery.parser.XQueryParser; @@ -212,4 +209,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 47fc3e7091686c16a39b8c5965179784995ceb90 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 21:27:23 +0200 Subject: [PATCH 16/39] [refactor] Add Category enum to Expression in order to distinguish between updating and simple expressions --- .../java/org/exist/xquery/DynamicFunctionCall.java | 13 ++++++++++++- .../src/main/java/org/exist/xquery/Expression.java | 14 +++++++++++++- .../exist/xquery/update/CopyModifyExpression.java | 6 +----- .../exist/xquery/update/legacy/Modification.java | 3 +++ 4 files changed, 29 insertions(+), 7 deletions(-) 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 8478417cf5e..94ff7009fdb 100644 --- a/exist-core/src/main/java/org/exist/xquery/DynamicFunctionCall.java +++ b/exist-core/src/main/java/org/exist/xquery/DynamicFunctionCall.java @@ -35,9 +35,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()); @@ -163,4 +164,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 78404d39309..bfa091053b7 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 @@ -264,4 +274,6 @@ public interface Expression { * @return true if the next expression should be evaluated, false otherwise. */ boolean evalNextExpressionOnEmptyContextSequence(); -} \ No newline at end of file + + public default Category getCategory() { return Category.SIMPLE; }; +} diff --git a/exist-core/src/main/java/org/exist/xquery/update/CopyModifyExpression.java b/exist-core/src/main/java/org/exist/xquery/update/CopyModifyExpression.java index 84c9d26ba00..05f088d12b2 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/CopyModifyExpression.java +++ b/exist-core/src/main/java/org/exist/xquery/update/CopyModifyExpression.java @@ -69,13 +69,9 @@ public CopySource() { private Expression modifyExpr; private 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/update/legacy/Modification.java b/exist-core/src/main/java/org/exist/xquery/update/legacy/Modification.java index 3055901cb12..f13a147c7e0 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/legacy/Modification.java +++ b/exist-core/src/main/java/org/exist/xquery/update/legacy/Modification.java @@ -351,4 +351,7 @@ protected Txn getTransaction() { return node.getParentNode(); } } + + @Override + public Category getCategory() { return Category.UPDATING; } } From dd9631bd88ec180277dbce39a7f42cceeb122b78 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 21:29:15 +0200 Subject: [PATCH 17/39] [feature] 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 bb440ead515..0e9c19a05e5 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 @@ -721,6 +721,7 @@ exprSingle throws XPathException | ( "typeswitch" LPAREN ) => typeswitchExpr | ( "update" ( "replace" | "value" | "insert" | "delete" | "rename" )) => updateExpr | ( "copy" DOLLAR) => copyModifyExpr + | ( "invoke" "updating" ) => dynamicUpdFunCall | orExpr ; @@ -1306,6 +1307,11 @@ postfixExpr throws XPathException )* ; +dynamicUpdFunCall throws XPathException +: + "invoke"! "updating"^ primaryExpr ( argumentList )* + ; + arrowExpr throws XPathException : unaryExpr ( ARROW_OP^ arrowFunctionSpecifier argumentList )* @@ -2283,6 +2289,8 @@ reservedKeywords returns [String name] "skip" { name = "skip"; } | "transform" { name = "transform"; } + | + "invoke" { name = "invoke"; } ; /** From 4848644732e96a8b7101d0d51a6738f665c09eb8 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 21:30:43 +0200 Subject: [PATCH 18/39] [feature] 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 cedddaf1bb6..9a4f88b1c6e 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 @@ -2199,6 +2199,8 @@ throws PermissionDeniedException, EXistException, XPathException step=transformWithExpr [path] | step=copyModifyExpr [path] + | + step=dynamicUpdFunCall [path] ; /** @@ -3019,6 +3021,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 d53f922a64136310f54f8399c06c8d25bac36b55 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 11 Jul 2022 21:31:27 +0200 Subject: [PATCH 19/39] [test] Add test for dynamic updating function call --- .../xquery/update/XQueryUpdate3Test.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index 7f9932f794d..de593bb5412 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -271,4 +271,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()).getReturnExpression()).getFirst() instanceof DynamicFunctionCall); + DynamicFunctionCall dfc = (DynamicFunctionCall) ((DebuggableExpression) ((LetExpr)expr.getFirst()).getReturnExpression()).getFirst(); + assertEquals(Expression.Category.UPDATING, dfc.getCategory()); + } + } } From 5031dcc8a984cf39cbbeb8b83cb5fe805f6d12e4 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 12 Jul 2022 18:17:51 +0200 Subject: [PATCH 20/39] [feature] 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 0e9c19a05e5..25332d61f05 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 === @@ -720,6 +721,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 @@ -755,6 +757,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 @@ -2291,6 +2314,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 d37154c9fdbd5843391c535f6b384821045aeb03 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 12 Jul 2022 18:18:27 +0200 Subject: [PATCH 21/39] [feature] 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 9a4f88b1c6e..2cf16a1b01f 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 @@ -2196,6 +2196,8 @@ throws PermissionDeniedException, EXistException, XPathException | step=updateExpr [path] | + step=xqufInsertExpr [path] + | step=transformWithExpr [path] | step=copyModifyExpr [path] @@ -3883,6 +3885,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 e2895fafc8f1bfd61a6e251cb6706ea4bd3c9667 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 12 Jul 2022 18:18:51 +0200 Subject: [PATCH 22/39] [feature] Add InsertExpr class to support XQUF insert expression --- .../org/exist/xquery/update/InsertExpr.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/update/InsertExpr.java diff --git a/exist-core/src/main/java/org/exist/xquery/update/InsertExpr.java b/exist-core/src/main/java/org/exist/xquery/update/InsertExpr.java new file mode 100644 index 00000000000..7be721fa389 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/update/InsertExpr.java @@ -0,0 +1,101 @@ +/* + * 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 + */ +package org.exist.xquery.update; + +import 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; + +/** + * @author Adam Retter + * @author Gabriele Tomassetti + */ +public class InsertExpr extends AbstractExpression { + public enum Choice { + FIRST, + LAST, + INTO, + AFTER, + BEFORE + } + + private final Expression source; + private final Expression target; + private final Choice choice; + + public InsertExpr(final XQueryContext context, final Expression source, final Expression target, final Choice choice) { + super(context); + this.source = source; + this.target = target; + this.choice = choice; + } + + @Override + public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException { + } + + @Override + public Sequence eval(final Sequence contextSequence, final 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(final 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 b1d14de3fcbf2f6ca2bbdb0f0c2e7a4e8cfccf3c Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 12 Jul 2022 18:19:11 +0200 Subject: [PATCH 23/39] [test] Add test for insert expression --- .../xquery/update/XQueryUpdate3Test.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index de593bb5412..a1d53371d27 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -306,4 +306,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 ba1e1c75e249cf19ec8bb40a51bbaff28168b104 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:18:58 +0200 Subject: [PATCH 24/39] [feature] Add base class ModifyingExpression to support insert, delete, replace and rename expressions --- .../xquery/update/ModifyingExpression.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/update/ModifyingExpression.java diff --git a/exist-core/src/main/java/org/exist/xquery/update/ModifyingExpression.java b/exist-core/src/main/java/org/exist/xquery/update/ModifyingExpression.java new file mode 100644 index 00000000000..9bb7f7ed49d --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/update/ModifyingExpression.java @@ -0,0 +1,55 @@ +/* + * 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 + */ +package org.exist.xquery.update; + +import org.exist.xquery.AbstractExpression; +import org.exist.xquery.Expression; +import org.exist.xquery.XQueryContext; +import org.exist.xquery.value.Type; + +/** + * @author Adam Retter + * @author Gabriele Tomassetti + */ +public abstract class ModifyingExpression extends AbstractExpression { + + protected final Expression targetExpr; + + public ModifyingExpression(final XQueryContext context, final 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 7e9057a55533927f2d4727e0df82c4d67a6b6be8 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:33:36 +0200 Subject: [PATCH 25/39] [refactor] Update class InsertExpr --- .../org/exist/xquery/update/InsertExpr.java | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/exist-core/src/main/java/org/exist/xquery/update/InsertExpr.java b/exist-core/src/main/java/org/exist/xquery/update/InsertExpr.java index 7be721fa389..3ed1d4ccf36 100644 --- a/exist-core/src/main/java/org/exist/xquery/update/InsertExpr.java +++ b/exist-core/src/main/java/org/exist/xquery/update/InsertExpr.java @@ -25,13 +25,12 @@ import org.exist.xquery.util.ExpressionDumper; import org.exist.xquery.value.Item; import org.exist.xquery.value.Sequence; -import org.exist.xquery.value.Type; /** * @author Adam Retter * @author Gabriele Tomassetti */ -public class InsertExpr extends AbstractExpression { +public class InsertExpr extends ModifyingExpression { public enum Choice { FIRST, LAST, @@ -40,14 +39,12 @@ public enum Choice { BEFORE } - private final Expression source; - private final Expression target; + private final Expression sourceExpr; private final Choice choice; public InsertExpr(final XQueryContext context, final Expression source, final Expression target, final Choice choice) { - super(context); - this.source = source; - this.target = target; + super(context, target); + this.sourceExpr = source; this.choice = choice; } @@ -60,17 +57,6 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr 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; @@ -80,22 +66,31 @@ public Cardinality getCardinality() { public void dump(final 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 ec9b38b9e8807b2b6c14b2f01327fbd6c3baafe6 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:34:09 +0200 Subject: [PATCH 26/39] [feature] 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 25332d61f05..07d9984b909 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 | ( "typeswitch" LPAREN ) => typeswitchExpr | ( "update" ( "replace" | "value" | "insert" | "delete" | "rename" )) => updateExpr | ( "insert" ) => xqufInsertExpr + | ( "delete" ) => xqufDeleteExpr | ( "copy" DOLLAR) => copyModifyExpr | ( "invoke" "updating" ) => dynamicUpdFunCall | orExpr @@ -757,6 +758,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 @@ -778,14 +789,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 b7ee82c05fb61bb495139ad0e188cb27c366e22f Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:34:37 +0200 Subject: [PATCH 27/39] [feature] 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 2cf16a1b01f..0f97567335d 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 @@ -2198,6 +2198,8 @@ throws PermissionDeniedException, EXistException, XPathException | step=xqufInsertExpr [path] | + step=xqufDeleteExpr [path] + | step=transformWithExpr [path] | step=copyModifyExpr [path] @@ -3930,6 +3932,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 e7f3495880c2bfe2b79488aed0f1d24653ab491e Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:35:12 +0200 Subject: [PATCH 28/39] [feature] Add DeleteExpr class to support XQUF delete expression --- .../org/exist/xquery/update/DeleteExpr.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/update/DeleteExpr.java diff --git a/exist-core/src/main/java/org/exist/xquery/update/DeleteExpr.java b/exist-core/src/main/java/org/exist/xquery/update/DeleteExpr.java new file mode 100644 index 00000000000..a7b948625cd --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/update/DeleteExpr.java @@ -0,0 +1,67 @@ +/* + * 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 + */ +package org.exist.xquery.update; + +import org.exist.xquery.AnalyzeContextInfo; +import org.exist.xquery.Expression; +import org.exist.xquery.XPathException; +import org.exist.xquery.XQueryContext; +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.Item; +import org.exist.xquery.value.Sequence; + +/** + * @author Adam Retter + * @author Gabriele Tomassetti + */ +public class DeleteExpr extends ModifyingExpression { + + public DeleteExpr(final XQueryContext context, final Expression target) { + super(context, target); + } + + @Override + public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException { + + } + + @Override + public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException { + return Sequence.EMPTY_SEQUENCE; + } + + public void dump(final 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 e94b0d9c3dd1e58e84119303cd9d76301aabeda0 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 09:35:40 +0200 Subject: [PATCH 29/39] [test] Add test for delete expression --- .../xquery/update/XQueryUpdate3Test.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index a1d53371d27..56499569475 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -341,4 +341,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 a2b81539376e25c0b91cda9cfae34964f893b852 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:28:25 +0200 Subject: [PATCH 30/39] [feature] 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 07d9984b909..e8efbd30666 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 @@ -721,8 +721,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 @@ -794,6 +795,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 71d0e090aa3431f4b086a6ebc416f5f8c7a9bd91 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:29:10 +0200 Subject: [PATCH 31/39] [feature] 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 0f97567335d..0f5ecc3d64e 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 @@ -2200,6 +2200,8 @@ throws PermissionDeniedException, EXistException, XPathException | step=xqufDeleteExpr [path] | + step=xqufReplaceExpr [path] + | step=transformWithExpr [path] | step=copyModifyExpr [path] @@ -3952,6 +3954,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 3514f7f0e471b8529340ac44ef61d396b08527c8 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:29:34 +0200 Subject: [PATCH 32/39] [feature] Add ReplaceExpr class to implement replace expression --- .../org/exist/xquery/update/ReplaceExpr.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/update/ReplaceExpr.java diff --git a/exist-core/src/main/java/org/exist/xquery/update/ReplaceExpr.java b/exist-core/src/main/java/org/exist/xquery/update/ReplaceExpr.java new file mode 100644 index 00000000000..621e039cf1a --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/update/ReplaceExpr.java @@ -0,0 +1,97 @@ +/* + * 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 + */ +package org.exist.xquery.update; + +import org.exist.xquery.AnalyzeContextInfo; +import org.exist.xquery.Cardinality; +import org.exist.xquery.Expression; +import org.exist.xquery.XPathException; +import org.exist.xquery.XQueryContext; +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.Item; +import org.exist.xquery.value.Sequence; + +/** + * @author Adam Retter + * @author Gabriele Tomassetti + */ +public class ReplaceExpr extends ModifyingExpression { + public enum ReplacementType { + NODE, + VALUE + } + + private final Expression withExpr; + private final ReplaceExpr.ReplacementType replacementType; + + public ReplaceExpr(final XQueryContext context, final Expression target, final Expression with, final ReplaceExpr.ReplacementType replacementType) { + super(context, target); + this.withExpr = with; + this.replacementType = replacementType; + } + + @Override + public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException { + } + + @Override + public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException { + return Sequence.EMPTY_SEQUENCE; + } + + @Override + public Cardinality getCardinality() { + return Cardinality.ONE_OR_MORE; + } + + @Override + public void dump(final 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 1d80f17d300f8ecd7c707334001d8803c9fad01d Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:30:00 +0200 Subject: [PATCH 33/39] [test] Add tests for replace expression --- .../xquery/update/XQueryUpdate3Test.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index 56499569475..69f02e86478 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -375,4 +375,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 7481ee112890ea425bc323d1a205e95cb3f5bdbc Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:46:12 +0200 Subject: [PATCH 34/39] [feature] 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 e8efbd30666..6ed1d0349a1 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,7 +724,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 ; @@ -800,6 +801,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 391afe91a4f74af3c3e6b3a66a9ee89d3109b648 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:46:41 +0200 Subject: [PATCH 35/39] [feature] 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 0f5ecc3d64e..22e78774c35 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 @@ -2202,6 +2202,8 @@ throws PermissionDeniedException, EXistException, XPathException | step=xqufReplaceExpr [path] | + step=xqufRenameExpr [path] + | step=transformWithExpr [path] | step=copyModifyExpr [path] @@ -3983,6 +3985,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 d426c2c52b7ceeb8074f527cbb7a6cca8f0e500c Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:47:02 +0200 Subject: [PATCH 36/39] [feature] Add class RenameExpr to support rename expression --- .../org/exist/xquery/update/RenameExpr.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 exist-core/src/main/java/org/exist/xquery/update/RenameExpr.java diff --git a/exist-core/src/main/java/org/exist/xquery/update/RenameExpr.java b/exist-core/src/main/java/org/exist/xquery/update/RenameExpr.java new file mode 100644 index 00000000000..6d372d57b39 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/update/RenameExpr.java @@ -0,0 +1,83 @@ +/* + * 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 + */ +package org.exist.xquery.update; + +import org.exist.xquery.AnalyzeContextInfo; +import org.exist.xquery.Cardinality; +import org.exist.xquery.Expression; +import org.exist.xquery.XPathException; +import org.exist.xquery.XQueryContext; +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.Item; +import org.exist.xquery.value.Sequence; + +/** + * @author Adam Retter + * @author Gabriele Tomassetti + */ +public class RenameExpr extends ModifyingExpression { + private final Expression newNameExpr; + + public RenameExpr(final XQueryContext context, final Expression target, final Expression newName) { + super(context, target); + this.newNameExpr = newName; + } + + @Override + public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException { + } + + @Override + public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException { + return Sequence.EMPTY_SEQUENCE; + } + + @Override + public Cardinality getCardinality() { + return Cardinality.ONE_OR_MORE; + } + + @Override + public void dump(final 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 ac7e942b6114d91f9c0451cacc6c391fc266a0ce Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 13 Jul 2022 10:47:13 +0200 Subject: [PATCH 37/39] [test] Add test for rename expression --- .../xquery/update/XQueryUpdate3Test.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index 69f02e86478..0f377478bcb 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -449,4 +449,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 a0ec78aecc25589ec457c1e4f8e6286745f54feb Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 20 Jul 2022 17:27:39 +0200 Subject: [PATCH 38/39] [test] Add new tests found in reference --- .../xquery/update/XQueryUpdate3Test.java | 157 +++++++++++++++++- 1 file changed, 155 insertions(+), 2 deletions(-) diff --git a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java index 0f377478bcb..ac44c8c89ca 100644 --- a/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java +++ b/exist-core/src/test/java/org/exist/xquery/update/XQueryUpdate3Test.java @@ -245,7 +245,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(); @@ -264,11 +264,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()); } } @@ -339,6 +378,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()); } } @@ -348,6 +429,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 @@ -485,4 +602,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()); + } + } } From 4d774ba1946346e83278adb6d66d1249961f82c8 Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Sat, 10 Jun 2023 22:56:51 +0100 Subject: [PATCH 39/39] [bugfix] Add missing reserved keyword 'copy' --- exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g | 2 ++ 1 file changed, 2 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 6ed1d0349a1..188804f1e33 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 @@ -2342,6 +2342,8 @@ reservedKeywords returns [String name] "after" { name = "after"; } | "before" { name = "before"; } + | + "copy" { name = "copy"; } ; /**