diff --git a/.github/workflows/run-qt3.yml b/.github/workflows/run-qt3.yml index bd86df6b34..46ae8f4f1a 100644 --- a/.github/workflows/run-qt3.yml +++ b/.github/workflows/run-qt3.yml @@ -24,10 +24,14 @@ jobs: with: script: | const workflowId = 'run-qt3.yml'; - const branch = context.eventName === 'pull_request' + const isPullRequest = context.eventName === 'pull_request'; + const defaultBranch = context.payload.repository.default_branch; + const branch = isPullRequest ? context.payload.pull_request.base.ref : context.ref.replace('refs/heads/', ''); - const event = 'push'; + const event = !isPullRequest || branch === defaultBranch + ? 'push' + : 'pull_request'; const runs = await github.paginate(github.rest.actions.listWorkflowRuns, { owner: context.repo.owner, @@ -35,7 +39,6 @@ jobs: workflow_id: workflowId, branch, event, - status: 'completed', per_page: 100 }); @@ -57,6 +60,10 @@ jobs: ); } + runs.sort( + (a, b) => new Date(b.created_at) - new Date(a.created_at) + ); + let baselineRun = null; for (const run of runs) { if (run.conclusion !== 'success') { @@ -74,8 +81,8 @@ jobs: const baselineRunId = baselineRun ? String(baselineRun.id) : ''; core.info( baselineRunId - ? `Using baseline run ${baselineRunId} from branch ${branch}.` - : `No successful baseline run with unexpired QT3 artifacts found for branch ${branch}.` + ? `Using baseline run ${baselineRunId} from ${event} on branch ${branch}.` + : `No successful ${event} baseline run with unexpired QT3 artifacts found for branch ${branch}.` ); core.setOutput('baseline_run_id', baselineRunId); @@ -104,7 +111,7 @@ jobs: qt3-jsoniq: needs: [resolve-baseline, build] - uses: RumbleDB/rumble-test-suite/.github/workflows/qt3-suite.yml@master + uses: RumbleDB/rumble-test-suite/.github/workflows/qt3-suite.yml@jimmy/xml-schema with: tested_parser: jsoniq rumble_artifact_name: rumble-build @@ -112,7 +119,7 @@ jobs: qt3-xquery: needs: [resolve-baseline, build] - uses: RumbleDB/rumble-test-suite/.github/workflows/qt3-suite.yml@master + uses: RumbleDB/rumble-test-suite/.github/workflows/qt3-suite.yml@jimmy/xml-schema with: tested_parser: xquery rumble_artifact_name: rumble-build diff --git a/src/main/java/org/rumbledb/compiler/CloneVisitor.java b/src/main/java/org/rumbledb/compiler/CloneVisitor.java index 44419db5b0..2074581b7b 100644 --- a/src/main/java/org/rumbledb/compiler/CloneVisitor.java +++ b/src/main/java/org/rumbledb/compiler/CloneVisitor.java @@ -46,6 +46,7 @@ import org.rumbledb.expressions.module.LibraryModule; import org.rumbledb.expressions.module.MainModule; import org.rumbledb.expressions.module.Prolog; +import org.rumbledb.expressions.module.SchemaImport; import org.rumbledb.expressions.module.TypeDeclaration; import org.rumbledb.expressions.module.VariableDeclaration; import org.rumbledb.expressions.postfix.ArrayLookupExpression; @@ -90,6 +91,7 @@ import org.rumbledb.expressions.typing.InstanceOfExpression; import org.rumbledb.expressions.typing.IsStaticallyExpression; import org.rumbledb.expressions.typing.TreatExpression; +import org.rumbledb.expressions.typing.ValidateExpression; import org.rumbledb.expressions.typing.ValidateTypeExpression; import org.rumbledb.expressions.xml.AttributeNodeContentExpression; import org.rumbledb.expressions.xml.AttributeNodeExpression; @@ -135,6 +137,9 @@ public Node visitLibraryModule(LibraryModule module, Node argument) { @Override public Node visitProlog(Prolog expression, Node argument) { + List schemaImports = expression.getSchemaImports().stream() + .map(schemaImport -> (SchemaImport) visit(schemaImport, argument)) + .collect(Collectors.toList()); List libraryModules = expression.getImportedModules().stream() .map(libraryModule -> (LibraryModule) visit(libraryModule, argument)) .collect(Collectors.toList()); @@ -142,11 +147,23 @@ public Node visitProlog(Prolog expression, Node argument) { .map(expr -> visit(expr, argument)) .collect(Collectors.toList()); expression.setDeclarations(declarations); + expression.getSchemaImports().clear(); + expression.getSchemaImports().addAll(schemaImports); expression.getImportedModules().clear(); expression.getImportedModules().addAll(libraryModules); return expression; } + @Override + public Node visitSchemaImport(SchemaImport schemaImport, Node argument) { + return new SchemaImport( + schemaImport.getTargetNamespace(), + schemaImport.getBindingKind(), + schemaImport.getPrefix(), + schemaImport.getLocationHints(), + schemaImport.getMetadata()); + } + @Override public Node visitProgram(Program program, Node argument) { StatementsAndOptionalExpr statementsAndOptionalExpr = @@ -1143,6 +1160,19 @@ public Node visitValidateTypeExpression(ValidateTypeExpression expression, Node return result; } + @Override + public Node visitValidateExpression(ValidateExpression expression, Node argument) { + Expression result = new ValidateExpression( + (Expression) visit(expression.getMainExpression(), argument), + expression.getValidationMode(), + expression.getTypeName(), + expression.getMetadata()); + result.setStaticContext(expression.getStaticContext()); + result.setStaticSequenceType(expression.getStaticSequenceType()); + result.setSequential(expression.isSequential()); + return result; + } + // region scripting @Override public Node visitApplyStatement(ApplyStatement statement, Node argument) { diff --git a/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java b/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java index fa841ab189..a0bfb1107d 100644 --- a/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java +++ b/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java @@ -82,6 +82,7 @@ import org.rumbledb.expressions.scripting.statement.StatementsAndExpr; import org.rumbledb.expressions.scripting.statement.StatementsAndOptionalExpr; import org.rumbledb.expressions.typing.TreatExpression; +import org.rumbledb.expressions.typing.ValidateExpression; import org.rumbledb.expressions.typing.ValidateTypeExpression; import org.rumbledb.expressions.update.CopyDeclaration; import org.rumbledb.expressions.update.TransformExpression; @@ -691,6 +692,13 @@ public StaticContext visitValidateTypeExpression(ValidateTypeExpression expressi } } + @Override + public StaticContext visitValidateExpression(ValidateExpression expression, StaticContext argument) { + visitDescendants(expression, argument); + expression.setHighestExecutionMode(ExecutionMode.LOCAL); + return argument; + } + @Override public StaticContext visitRangeExpr(RangeExpression rangeExpression, StaticContext argument) { visitDescendants(rangeExpression, argument); diff --git a/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java b/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java index b2d7ce782f..19a89ded11 100644 --- a/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java +++ b/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java @@ -105,6 +105,7 @@ import org.rumbledb.expressions.typing.InstanceOfExpression; import org.rumbledb.expressions.typing.IsStaticallyExpression; import org.rumbledb.expressions.typing.TreatExpression; +import org.rumbledb.expressions.typing.ValidateExpression; import org.rumbledb.expressions.typing.ValidateTypeExpression; import org.rumbledb.expressions.update.AppendExpression; import org.rumbledb.expressions.update.CopyDeclaration; @@ -2543,6 +2544,13 @@ public StaticContext visitValidateTypeExpression(ValidateTypeExpression expressi return argument; } + @Override + public StaticContext visitValidateExpression(ValidateExpression expression, StaticContext argument) { + visitDescendants(expression, expression.getStaticContext()); + expression.setStaticSequenceType(expression.getMainExpression().getStaticSequenceType()); + return argument; + } + // endregion // // // begin scripting diff --git a/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java b/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java index 3bb6a6541c..9094a9061c 100644 --- a/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java +++ b/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java @@ -37,6 +37,7 @@ import org.rumbledb.errorcodes.ErrorCode; import org.rumbledb.exceptions.ExceptionMetadata; import org.rumbledb.exceptions.OurBadException; +import org.rumbledb.exceptions.UnsupportedFeatureException; import org.rumbledb.expressions.AbstractNodeVisitor; import org.rumbledb.expressions.CommaExpression; import org.rumbledb.expressions.ExecutionMode; @@ -119,6 +120,7 @@ import org.rumbledb.expressions.typing.CastableExpression; import org.rumbledb.expressions.typing.InstanceOfExpression; import org.rumbledb.expressions.typing.TreatExpression; +import org.rumbledb.expressions.typing.ValidateExpression; import org.rumbledb.expressions.typing.ValidateTypeExpression; import org.rumbledb.expressions.update.AppendExpression; import org.rumbledb.expressions.update.CopyDeclaration; @@ -1480,6 +1482,12 @@ public ItemRuntimePlan visitValidateTypeExpression(ValidateTypeExpression expres return resultIterator; } + @Override + public ItemRuntimePlan visitValidateExpression(ValidateExpression expression, ItemRuntimePlan argument) { + throw new UnsupportedFeatureException( + "XML Schema validate expressions are not executable yet.", expression.getMetadata()); + } + @Override public ItemRuntimePlan visitTreatExpression(TreatExpression expression, ItemRuntimePlan argument) { ItemRuntimePlan childExpression = this.visit(expression.getMainExpression(), argument); diff --git a/src/main/java/org/rumbledb/compiler/StaticContextVisitor.java b/src/main/java/org/rumbledb/compiler/StaticContextVisitor.java index 1b5f179034..bbb7e14453 100644 --- a/src/main/java/org/rumbledb/compiler/StaticContextVisitor.java +++ b/src/main/java/org/rumbledb/compiler/StaticContextVisitor.java @@ -79,6 +79,7 @@ import org.rumbledb.expressions.typing.CastableExpression; import org.rumbledb.expressions.typing.InstanceOfExpression; import org.rumbledb.expressions.typing.TreatExpression; +import org.rumbledb.expressions.typing.ValidateExpression; import org.rumbledb.expressions.typing.ValidateTypeExpression; import org.rumbledb.expressions.update.CopyDeclaration; import org.rumbledb.expressions.update.TransformExpression; @@ -474,6 +475,12 @@ public StaticContext visitValidateTypeExpression(ValidateTypeExpression expressi return argument; } + @Override + public StaticContext visitValidateExpression(ValidateExpression expression, StaticContext argument) { + visitDescendants(expression, argument); + return argument; + } + @Override public StaticContext visitCastExpression(CastExpression expression, StaticContext argument) { visitDescendants(expression, argument); diff --git a/src/main/java/org/rumbledb/compiler/TranslationVisitor.java b/src/main/java/org/rumbledb/compiler/TranslationVisitor.java index e1d0acb900..9c52e3a813 100644 --- a/src/main/java/org/rumbledb/compiler/TranslationVisitor.java +++ b/src/main/java/org/rumbledb/compiler/TranslationVisitor.java @@ -141,6 +141,7 @@ import org.rumbledb.expressions.typing.InstanceOfExpression; import org.rumbledb.expressions.typing.IsStaticallyExpression; import org.rumbledb.expressions.typing.TreatExpression; +import org.rumbledb.expressions.typing.ValidateExpression; import org.rumbledb.expressions.typing.ValidateTypeExpression; import org.rumbledb.expressions.update.AppendExpression; import org.rumbledb.expressions.update.CopyDeclaration; @@ -1565,9 +1566,19 @@ public Node visitValueExpr(JsoniqParser.ValueExprContext ctx) { @Override public Node visitValidateExpr(JsoniqParser.ValidateExprContext ctx) { - Expression mainExpr = (Expression) this.visitExpr(ctx.expr()); - SequenceType sequenceType = this.processSequenceType(ctx.sequenceType()); - return new ValidateTypeExpression(mainExpr, true, sequenceType, createMetadataFromContext(ctx)); + Expression mainExpression = (Expression) this.visitExpr(ctx.expr()); + if (ctx.sequenceType() != null) { + // TODO: This is a compatibility hack to support JSONiq validate expression, which has a different semantics + // than XQuery validate expression + SequenceType sequenceType = this.processSequenceType(ctx.sequenceType()); + return new ValidateTypeExpression(mainExpression, true, sequenceType, createMetadataFromContext(ctx)); + } + + ValidateExpression.ValidationMode validationMode = ValidateExpression.ValidationMode.STRICT; + if (ctx.validationMode() != null && ctx.validationMode().KW_LAX() != null) { + validationMode = ValidateExpression.ValidationMode.LAX; + } + return new ValidateExpression(mainExpression, validationMode, null, createMetadataFromContext(ctx)); } // endregion diff --git a/src/main/java/org/rumbledb/compiler/XQueryTranslationVisitor.java b/src/main/java/org/rumbledb/compiler/XQueryTranslationVisitor.java index b6f0f7c70a..58d48f63ea 100644 --- a/src/main/java/org/rumbledb/compiler/XQueryTranslationVisitor.java +++ b/src/main/java/org/rumbledb/compiler/XQueryTranslationVisitor.java @@ -92,6 +92,8 @@ import org.rumbledb.expressions.module.MainModule; import org.rumbledb.expressions.module.OptionDeclaration; import org.rumbledb.expressions.module.Prolog; +import org.rumbledb.expressions.module.SchemaImport; +import org.rumbledb.expressions.module.SchemaImport.BindingKind; import org.rumbledb.expressions.module.TypeDeclaration; import org.rumbledb.expressions.module.VariableDeclaration; import org.rumbledb.expressions.postfix.DynamicFunctionCallExpression; @@ -137,7 +139,8 @@ import org.rumbledb.expressions.typing.InstanceOfExpression; import org.rumbledb.expressions.typing.IsStaticallyExpression; import org.rumbledb.expressions.typing.TreatExpression; -import org.rumbledb.expressions.typing.ValidateTypeExpression; +import org.rumbledb.expressions.typing.ValidateExpression; +import org.rumbledb.expressions.typing.ValidateExpression.ValidationMode; import org.rumbledb.expressions.xml.AttributeNodeContentExpression; import org.rumbledb.expressions.xml.AttributeNodeExpression; import org.rumbledb.expressions.xml.CommentNodeConstructorExpression; @@ -355,8 +358,10 @@ public Node visitLibraryModule(XQueryParser.LibraryModuleContext ctx) { @Override public Node visitProlog(XQueryParser.PrologContext ctx) { List libraryModules = new ArrayList<>(); + List schemaImports = new ArrayList<>(); List optionDeclarations = new ArrayList<>(); Set namespaces = new HashSet<>(); + Set schemaNamespaces = new HashSet<>(); PrologPhase1Flags phase1 = new PrologPhase1Flags(); for (int ci = 0; ci < ctx.getChildCount(); ci++) { ParseTree child = ctx.getChild(ci); @@ -372,8 +377,18 @@ public Node visitProlog(XQueryParser.PrologContext ctx) { processNamespaceDecl(namespaceDeclContext); } else if (child instanceof SetterContext setterContext) { processPrologPhase1Setter(setterContext, phase1); - } else if (child instanceof XQueryParser.SchemaImportContext) { - // Not supported yet; previously skipped as well. + } else if (child instanceof XQueryParser.SchemaImportContext schemaImportContext) { + SchemaImport schemaImport = translateSchemaImport(schemaImportContext); + if (!schemaNamespaces.add(schemaImport.getTargetNamespace())) { + throw new SemanticException( + "The schema namespace " + + schemaImport.getTargetNamespace() + + " is imported more than once.", + ErrorCode.DuplicateSchemaImportErrorCode, + createMetadataFromContext(schemaImportContext)); + } + bindSchemaImportNamespace(schemaImport); + schemaImports.add(schemaImport); } else if (child instanceof XQueryParser.ModuleImportContext namespace) { LibraryModule libraryModule = this.processModuleImport(namespace); libraryModules.add(libraryModule); @@ -445,12 +460,57 @@ public Node visitProlog(XQueryParser.PrologContext ctx) { for (LibraryModule libraryModule : libraryModules) { prolog.addImportedModule(libraryModule); } + for (SchemaImport schemaImport : schemaImports) { + prolog.addSchemaImport(schemaImport); + } for (OptionDeclaration optionDeclaration : optionDeclarations) { prolog.addDeclaration(optionDeclaration); } return prolog; } + private SchemaImport translateSchemaImport(XQueryParser.SchemaImportContext ctx) { + String targetNamespace = URILiteralUtils.normalizeAsAnyURI(processURILiteral(ctx.nsURI)); + BindingKind bindingKind = BindingKind.NONE; + String prefix = null; + if (ctx.schemaPrefix() != null) { + if (ctx.schemaPrefix().ncName() != null) { + bindingKind = BindingKind.PREFIX; + prefix = ctx.schemaPrefix().ncName().getText(); + } else { + bindingKind = BindingKind.DEFAULT_ELEMENT_NAMESPACE; + } + } + List locationHints = ctx.locations.stream() + .map(this::processURILiteral) + .map(URILiteralUtils::normalizeAsAnyURI) + .collect(Collectors.toList()); + return new SchemaImport(targetNamespace, bindingKind, prefix, locationHints, createMetadataFromContext(ctx)); + } + + private void bindSchemaImportNamespace(SchemaImport schemaImport) { + if (schemaImport.getBindingKind() == BindingKind.NONE) { + return; + } + String namespace = schemaImport.getTargetNamespace(); + if (schemaImport.getBindingKind() == BindingKind.DEFAULT_ELEMENT_NAMESPACE) { + bindNamespace("", namespace, schemaImport.getMetadata()); + return; + } + String prefix = schemaImport.getPrefix(); + if (namespace.isEmpty()) { + throw new SemanticException( + "A schema import cannot bind a prefix to a zero-length target namespace.", + ErrorCode.SchemaImportWithoutTargetNamespaceErrorCode, + schemaImport.getMetadata()); + } + if (prefix.equals("xml") || prefix.equals("xmlns")) { + throw new PredefinedPrefixInNamespaceDeclarationException( + "Schema import prefix " + prefix + " is reserved.", schemaImport.getMetadata()); + } + bindNamespace(prefix, namespace, schemaImport.getMetadata()); + } + @Override public Node visitOptionDecl(XQueryParser.OptionDeclContext ctx) { Name name = parseEqName(ctx.name, false, false, false, false); @@ -1485,10 +1545,16 @@ public Node visitValueExpr(XQueryParser.ValueExprContext ctx) { @Override public Node visitValidateExpr(XQueryParser.ValidateExprContext ctx) { - Expression mainExpr = (Expression) this.visitExpr(ctx.expr()); - SequenceType sequenceType = this.processSequenceType(ctx.sequenceType()); - return new ValidateTypeExpression(mainExpr, true, sequenceType, createMetadataFromContext(ctx)); - // TODO: this is not implemented in XQuery. Throw an unsupported feature exception. + Expression mainExpression = (Expression) this.visitExpr(ctx.expr()); + ValidationMode validationMode = ValidationMode.STRICT; + Name typeName = null; + if (ctx.validationMode() != null && ctx.validationMode().KW_LAX() != null) { + validationMode = ValidationMode.LAX; + } else if (ctx.KW_TYPE() != null) { + validationMode = ValidationMode.TYPE; + typeName = parseEqName(ctx.typeName().eqName(), false, true, false, false); + } + return new ValidateExpression(mainExpression, validationMode, typeName, createMetadataFromContext(ctx)); } // endregion diff --git a/src/main/java/org/rumbledb/errorcodes/ErrorCode.java b/src/main/java/org/rumbledb/errorcodes/ErrorCode.java index ca6cd91d7c..6065cada4c 100644 --- a/src/main/java/org/rumbledb/errorcodes/ErrorCode.java +++ b/src/main/java/org/rumbledb/errorcodes/ErrorCode.java @@ -187,6 +187,8 @@ public String toString() { public static final ErrorCode NamespaceDoesNotMatchModule = registerBuiltIn("XQST0048"); public static final ErrorCode VariableAlreadyExists = registerBuiltIn("XQST0049"); public static final ErrorCode UnknownCastTypeErrorCode = registerBuiltIn("XQST0052"); + public static final ErrorCode SchemaImportWithoutTargetNamespaceErrorCode = registerBuiltIn("XQST0057"); + public static final ErrorCode DuplicateSchemaImportErrorCode = registerBuiltIn("XQST0058"); public static final ErrorCode MoreThanOneCopyNamespacesDeclarationErrorCode = registerBuiltIn("XQST0055"); public static final ErrorCode ModuleNotFoundErrorCode = registerBuiltIn("XQST0059"); public static final ErrorCode MoreThanOneBoundarySpaceDeclarationErrorCode = registerBuiltIn("XQST0068"); diff --git a/src/main/java/org/rumbledb/expressions/AbstractNodeVisitor.java b/src/main/java/org/rumbledb/expressions/AbstractNodeVisitor.java index 384b0dfb4d..a9d1b19ac7 100644 --- a/src/main/java/org/rumbledb/expressions/AbstractNodeVisitor.java +++ b/src/main/java/org/rumbledb/expressions/AbstractNodeVisitor.java @@ -50,6 +50,7 @@ import org.rumbledb.expressions.module.MainModule; import org.rumbledb.expressions.module.OptionDeclaration; import org.rumbledb.expressions.module.Prolog; +import org.rumbledb.expressions.module.SchemaImport; import org.rumbledb.expressions.module.TypeDeclaration; import org.rumbledb.expressions.module.VariableDeclaration; import org.rumbledb.expressions.postfix.*; @@ -91,6 +92,7 @@ import org.rumbledb.expressions.typing.InstanceOfExpression; import org.rumbledb.expressions.typing.IsStaticallyExpression; import org.rumbledb.expressions.typing.TreatExpression; +import org.rumbledb.expressions.typing.ValidateExpression; import org.rumbledb.expressions.typing.ValidateTypeExpression; import org.rumbledb.expressions.update.AppendExpression; import org.rumbledb.expressions.update.CreateCollectionExpression; @@ -159,6 +161,10 @@ public T visitProlog(Prolog expression, T argument) { return defaultAction(expression, argument); } + public T visitSchemaImport(SchemaImport schemaImport, T argument) { + return defaultAction(schemaImport, argument); + } + // endregion // region flwor @@ -514,6 +520,10 @@ public T visitValidateTypeExpression(ValidateTypeExpression expression, T argume return defaultAction(expression, argument); } + public T visitValidateExpression(ValidateExpression expression, T argument) { + return defaultAction(expression, argument); + } + public T visitProgram(Program expression, T argument) { return defaultAction(expression, argument); } diff --git a/src/main/java/org/rumbledb/expressions/module/Prolog.java b/src/main/java/org/rumbledb/expressions/module/Prolog.java index f98be50156..43c76e61f0 100644 --- a/src/main/java/org/rumbledb/expressions/module/Prolog.java +++ b/src/main/java/org/rumbledb/expressions/module/Prolog.java @@ -40,6 +40,7 @@ public class Prolog extends Node { private List declarations; private final List importedModules; + private final List schemaImports; public Prolog( List variableDeclarations, @@ -51,12 +52,17 @@ public Prolog( this.declarations.addAll(functionDeclarations); this.declarations.addAll(typeDeclarations); this.importedModules = new ArrayList<>(); + this.schemaImports = new ArrayList<>(); } public void addImportedModule(LibraryModule importedModule) { this.importedModules.add(importedModule); } + public void addSchemaImport(SchemaImport schemaImport) { + this.schemaImports.add(schemaImport); + } + public List getFunctionDeclarations() { return this.declarations.stream() .mapMulti((x, downstream) -> { @@ -110,6 +116,7 @@ public void clearDeclarations() { @Override public List getChildren() { List result = new ArrayList<>(); + result.addAll(this.schemaImports); result.addAll(this.importedModules); result.addAll(this.declarations); return result; @@ -117,9 +124,14 @@ public List getChildren() { @Override public void serializeToJSONiq(StringBuilder sb, int indent) { - for (int i = 0; i < this.declarations.size(); i++) { - this.declarations.get(i).serializeToJSONiq(sb, indent); - this.importedModules.get(i).serializeToJSONiq(sb, indent); + for (SchemaImport schemaImport : this.schemaImports) { + schemaImport.serializeToJSONiq(sb, indent); + } + for (LibraryModule importedModule : this.importedModules) { + importedModule.serializeToJSONiq(sb, indent); + } + for (Node declaration : this.declarations) { + declaration.serializeToJSONiq(sb, indent); } } diff --git a/src/main/java/org/rumbledb/expressions/module/SchemaImport.java b/src/main/java/org/rumbledb/expressions/module/SchemaImport.java new file mode 100644 index 0000000000..077ed8e743 --- /dev/null +++ b/src/main/java/org/rumbledb/expressions/module/SchemaImport.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rumbledb.expressions.module; + +import java.util.List; + +import lombok.Getter; + +import org.rumbledb.exceptions.ExceptionMetadata; +import org.rumbledb.exceptions.OurBadException; +import org.rumbledb.expressions.AbstractNodeVisitor; +import org.rumbledb.expressions.Node; + +/** An XQuery schema import declaration whose location hints have not yet been resolved. */ +@Getter +public class SchemaImport extends Node { + + public enum BindingKind { + NONE, + PREFIX, + DEFAULT_ELEMENT_NAMESPACE + } + + private final String targetNamespace; + private final BindingKind bindingKind; + private final String prefix; + private final List locationHints; + + public SchemaImport( + String targetNamespace, + BindingKind bindingKind, + String prefix, + List locationHints, + ExceptionMetadata metadata) { + super(metadata); + if (targetNamespace == null || bindingKind == null || locationHints == null) { + throw new OurBadException("A schema import cannot contain null components."); + } + if ((bindingKind == BindingKind.PREFIX) != (prefix != null)) { + throw new OurBadException( + "A prefixed schema import must have a prefix, and other schema imports must not have one."); + } + this.targetNamespace = targetNamespace; + this.bindingKind = bindingKind; + this.prefix = prefix; + this.locationHints = List.copyOf(locationHints); + } + + @Override + public T accept(AbstractNodeVisitor visitor, T argument) { + return visitor.visitSchemaImport(this, argument); + } + + @Override + public List getChildren() { + return List.of(); + } + + @Override + public void serializeToJSONiq(StringBuilder sb, int indent) { + indentIt(sb, indent); + sb.append("import schema "); + if (this.bindingKind == BindingKind.PREFIX) { + sb.append("namespace ").append(this.prefix).append(" = "); + } else if (this.bindingKind == BindingKind.DEFAULT_ELEMENT_NAMESPACE) { + sb.append("default element namespace "); + } + appendURILiteral(sb, this.targetNamespace); + if (!this.locationHints.isEmpty()) { + sb.append(" at "); + for (int i = 0; i < this.locationHints.size(); i++) { + if (i > 0) { + sb.append(", "); + } + appendURILiteral(sb, this.locationHints.get(i)); + } + } + sb.append(";\n"); + } + + private static void appendURILiteral(StringBuilder sb, String value) { + sb.append('"').append(value.replace("\"", "\"\"")).append('"'); + } +} diff --git a/src/main/java/org/rumbledb/expressions/typing/ValidateExpression.java b/src/main/java/org/rumbledb/expressions/typing/ValidateExpression.java new file mode 100644 index 0000000000..354f35f68e --- /dev/null +++ b/src/main/java/org/rumbledb/expressions/typing/ValidateExpression.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rumbledb.expressions.typing; + +import java.util.List; + +import lombok.Getter; + +import org.rumbledb.context.Name; +import org.rumbledb.exceptions.ExceptionMetadata; +import org.rumbledb.exceptions.OurBadException; +import org.rumbledb.expressions.AbstractNodeVisitor; +import org.rumbledb.expressions.Expression; +import org.rumbledb.expressions.Node; + +/** An XQuery validate expression, distinct from JSONiq sequence validation. */ +@Getter +public class ValidateExpression extends Expression { + + public enum ValidationMode { + STRICT, + LAX, + TYPE + } + + private final Expression mainExpression; + private final ValidationMode validationMode; + private final Name typeName; + + public ValidateExpression( + Expression mainExpression, ValidationMode validationMode, Name typeName, ExceptionMetadata metadata) { + super(metadata); + if (mainExpression == null || validationMode == null) { + throw new OurBadException("A validate expression must have an operand and a validation mode."); + } + if ((validationMode == ValidationMode.TYPE) != (typeName != null)) { + throw new OurBadException("A validate type expression must have exactly one type name."); + } + this.mainExpression = mainExpression; + this.validationMode = validationMode; + this.typeName = typeName; + } + + @Override + public T accept(AbstractNodeVisitor visitor, T argument) { + return visitor.visitValidateExpression(this, argument); + } + + @Override + public List getChildren() { + return List.of(this.mainExpression); + } + + @Override + public void serializeToJSONiq(StringBuilder sb, int indent) { + indentIt(sb, indent); + sb.append("validate "); + switch (this.validationMode) { + case STRICT -> sb.append("strict "); + case LAX -> sb.append("lax "); + case TYPE -> sb.append("type ").append(this.typeName).append(" "); + } + sb.append("{\n"); + this.mainExpression.serializeToJSONiq(sb, indent + 1); + sb.append('\n'); + indentIt(sb, indent); + sb.append('}'); + } +} diff --git a/src/main/java/org/rumbledb/parser/xquery/XQueryParser.g4 b/src/main/java/org/rumbledb/parser/xquery/XQueryParser.g4 index 6723393943..a5c7909a3c 100644 --- a/src/main/java/org/rumbledb/parser/xquery/XQueryParser.g4 +++ b/src/main/java/org/rumbledb/parser/xquery/XQueryParser.g4 @@ -469,16 +469,9 @@ nodeComp | (LANGLE LANGLE) | (RANGLE RANGLE) ; -/* - * replaced with the enclosedExpression production to match the JSONiq grammar - * TODO: this is out of spec. However, it is currently kept to match the JSONiq grammar - * TODO: replace with the proper rule, throw excep. - * validateExpr: KW_VALIDATE (validationMode | (KW_TYPE typeName))? LBRACE expr? RBRACE ; - */ - - + validateExpr - : KW_VALIDATE (validationMode | (KW_TYPE sequenceType))? LBRACE expr? RBRACE + : KW_VALIDATE (validationMode | (KW_TYPE typeName))? LBRACE expr RBRACE ; validationMode