diff --git a/.github/workflows/conformance-report.yml b/.github/workflows/conformance-report.yml index 81e2c62e65..a7bf265e8f 100644 --- a/.github/workflows/conformance-report.yml +++ b/.github/workflows/conformance-report.yml @@ -2,6 +2,7 @@ name: Conformance Test Report Generation on: [push, pull_request] env: + CONFORMANCE_REPORT_NAME: conformance_test_results.ion PATH_TO_TEST_RUNNER: test/partiql-tests-runner CONFORMANCE_REPORT_RELATIVE_PATH: build/conformance-test-report COMPARISON_REPORT_NAME: comparison_report.md @@ -35,7 +36,7 @@ jobs: - name: Upload `conformance-test-report` folder uses: actions/upload-artifact@v3 with: - path: ${{ env.PATH_TO_TEST_RUNNER }}/build/conformance-test-report + path: ${{ env.PATH_TO_TEST_RUNNER }}/${{ env.CONFORMANCE_REPORT_RELATIVE_PATH }} # Cache the conformance report for `conformance-report-comparison` job (pull_request event only) - name: Cache conformance report and build if: github.event_name == 'pull_request' @@ -96,12 +97,32 @@ jobs: run: | mkdir -p $GITHUB_WORKSPACE/artifact cp -r $GITHUB_WORKSPACE/${{ github.event.pull_request.base.sha }}/$PATH_TO_TEST_RUNNER/$CONFORMANCE_REPORT_RELATIVE_PATH $GITHUB_WORKSPACE/artifact/$CONFORMANCE_REPORT_RELATIVE_PATH - # Run conformance report comparison. Generates `comparison_report.md` - - name: Run conformance report comparison for artifact. Generates `comparison_report.md` + # Run conformance report comparison (cross-engine). Generates `comparison_report.md` + - name: Run cross-engine conformance report comparison for artifact. Generates `comparison_report.md` continue-on-error: true run: | - ARGS="$GITHUB_WORKSPACE/artifact $CONFORMANCE_REPORT_RELATIVE_PATH ${{ github.event.pull_request.base.sha }} $GITHUB_SHA $COMPARISON_REPORT_NAME" - gradle :test:partiql-tests-runner:run --args="$ARGS" + gradle :test:partiql-tests-runner:run \ + -t "CROSS-ENGINE Comparison Report" \ + -bf $GITHUB_WORKSPACE/artifact/$CONFORMANCE_REPORT_RELATIVE_PATH/legacy/$CONFORMANCE_REPORT_NAME \ + -bl LEGACY \ + -bt ${{ github.event.pull_request.base.sha }} \ + -tf $CONFORMANCE_REPORT_RELATIVE_PATH/eval/CONFORMANCE_REPORT_NAME \ + -tl EVAL \ + -tt $GITHUB_SHA \ + -o $COMPARISON_REPORT_NAME + # Run conformance report comparison (cross-commit). Generates `comparison_report.md` + - name: Run cross-commit conformance report comparison for artifact. Generates `comparison_report.md` + continue-on-error: true + run: | + gradle :test:partiql-tests-runner:run \ + -t "CROSS-COMMIT Comparison Report" \ + -bf $GITHUB_WORKSPACE/artifact/$CONFORMANCE_REPORT_RELATIVE_PATH/eval/$CONFORMANCE_REPORT_NAME \ + -bl EVAL \ + -bt ${{ github.event.pull_request.base.sha }} \ + -tf $CONFORMANCE_REPORT_RELATIVE_PATH/eval/CONFORMANCE_REPORT_NAME \ + -tl EVAL \ + -tt $GITHUB_SHA \ + -o $COMPARISON_REPORT_NAME # Print conformance report to GitHub actions workflow summary page - name: Print markdown in run continue-on-error: true @@ -111,12 +132,34 @@ jobs: uses: actions/upload-artifact@v3 with: path: ${{ env.PATH_TO_TEST_RUNNER }}/comparison_report.md - # Rebuild the test report with a size limit for comment - - name: Run conformance report comparison for comment. Generates `comparison_report_limited.md` + # Rebuild the test report (cross-engine) with a size limit for comment + - name: Run cross-engine conformance report comparison for comment. Generates `comparison_report_limited.md` + continue-on-error: true + run: | + gradle :test:partiql-tests-runner:run \ + -t "CROSS-ENGINE Conformance Report" \ + -bf $GITHUB_WORKSPACE/artifact/$CONFORMANCE_REPORT_RELATIVE_PATH/legacy/$CONFORMANCE_REPORT_NAME \ + -bl LEGACY \ + -bt ${{ github.event.pull_request.base.sha }} \ + -tf $CONFORMANCE_REPORT_RELATIVE_PATH/eval/$CONFORMANCE_REPORT_NAME \ + -tl EVAL \ + -tt $GITHUB_SHA \ + -o $COMPARISON_REPORT_NAME_WITH_LIMIT \ + -l $COMMENT_SIZE_LIMIT + # Rebuild the test report (cross-commit) with a size limit for comment + - name: Run cross-commit conformance report comparison for comment. Generates `comparison_report_limited.md` continue-on-error: true run: | - ARGS="$GITHUB_WORKSPACE/artifact $CONFORMANCE_REPORT_RELATIVE_PATH ${{ github.event.pull_request.base.sha }} $GITHUB_SHA $COMPARISON_REPORT_NAME_WITH_LIMIT $COMMENT_SIZE_LIMIT" - gradle :test:partiql-tests-runner:run --args="$ARGS" + gradle :test:partiql-tests-runner:run \ + -t "CROSS-COMMIT Conformance Report" \ + -bf $GITHUB_WORKSPACE/artifact/$CONFORMANCE_REPORT_RELATIVE_PATH/eval/$CONFORMANCE_REPORT_NAME \ + -bl EVAL \ + -bt ${{ github.event.pull_request.base.sha }} \ + -tf $CONFORMANCE_REPORT_RELATIVE_PATH/eval/$CONFORMANCE_REPORT_NAME \ + -tl EVAL \ + -tt $GITHUB_SHA \ + -o $COMPARISON_REPORT_NAME_WITH_LIMIT \ + -l $COMMENT_SIZE_LIMIT # Find comment w/ conformance comparison if previous comment published - name: Find Comment uses: peter-evans/find-comment@v2 diff --git a/buildSrc/src/main/kotlin/partiql.versions.kt b/buildSrc/src/main/kotlin/partiql.versions.kt index 75eab9e8ec..d3353f14f7 100644 --- a/buildSrc/src/main/kotlin/partiql.versions.kt +++ b/buildSrc/src/main/kotlin/partiql.versions.kt @@ -46,6 +46,7 @@ object Versions { const val picoCli = "4.7.0" const val kasechange = "1.3.0" const val pig = "0.6.2" + const val shadow = "8.1.1" const val kotlinxCoroutines = "1.6.0" const val kotlinxCoroutinesJdk8 = "1.6.0" const val ktlint = "0.42.1" // we're on an old version of ktlint. TODO upgrade https://github.com/partiql/partiql-lang-kotlin/issues/1418 @@ -123,5 +124,6 @@ object Plugins { const val dokka = "org.jetbrains.dokka" const val jmh = "me.champeau.jmh" const val library = "org.gradle.java-library" + const val shadow = "com.github.johnrengelman.shadow" const val testFixtures = "org.gradle.java-test-fixtures" } \ No newline at end of file diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 749d49222f..bef0075ccd 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -23,7 +23,7 @@ application { } dependencies { - implementation(project(":partiql-lang")) + implementation("org.partiql:partiql-lang-kotlin:0.14.8") implementation(Deps.kotlinxCoroutines) implementation(Deps.kotlinxCoroutinesJdk8) implementation(Deps.awsSdkS3) diff --git a/partiql-coverage/build.gradle.kts b/partiql-coverage/build.gradle.kts index f99d60e1f0..0ff628e6ab 100644 --- a/partiql-coverage/build.gradle.kts +++ b/partiql-coverage/build.gradle.kts @@ -17,36 +17,41 @@ plugins { id(Plugins.conventions) id(Plugins.dokka) id(Plugins.library) - id(Plugins.publish) + // TODO: Once code coverage is supported with the new evaluator, we can publish a new version. + // id(Plugins.publish) } dependencies { - api(project(":partiql-lang")) + // TODO: Once code coverage is published again, we can re-add the HEAD of the PartiQL Library. + api("org.partiql:partiql-lang-kotlin:0.14.8") implementation(Deps.junitApi) implementation(Deps.junitParams) implementation(Deps.junitPlatformLauncher) implementation(Deps.jgenhtml) } -tasks.shadowJar { - configurations = listOf(project.configurations.shadow.get()) -} - -// Workaround for https://github.com/johnrengelman/shadow/issues/651 -components.withType(AdhocComponentWithVariants::class.java).forEach { c -> - c.withVariantsFromConfiguration(project.configurations.shadowRuntimeElements.get()) { - skip() - } -} - // Need to add this as we have both Java and Kotlin sources. Dokka already handles multi-language projects. If // Javadoc is enabled, we end up overwriting index.html (causing compilation errors). tasks.withType() { enabled = false } -publish { - artifactId = "partiql-coverage" - name = "PartiQL Code Coverage" - description = "Code Coverage APIs for testing PartiQL source." -} +// START OF COMMENTED OUT CODE +// TODO: This has all be commented out due to the *temporary* removal of the publish API + +// tasks.shadowJar { +// configurations = listOf(project.configurations.shadow.get()) +// } + +// Workaround for https://github.com/johnrengelman/shadow/issues/651 +// components.withType(AdhocComponentWithVariants::class.java).forEach { c -> +// c.withVariantsFromConfiguration(project.configurations.shadowRuntimeElements.get()) { +// skip() +// } +// } +// publish { +// artifactId = "partiql-coverage" +// name = "PartiQL Code Coverage" +// description = "Code Coverage APIs for testing PartiQL source." +// } +// END OF COMMENTED OUT CODE diff --git a/partiql-eval/build.gradle.kts b/partiql-eval/build.gradle.kts index 0c367cf320..94bf4b51bc 100644 --- a/partiql-eval/build.gradle.kts +++ b/partiql-eval/build.gradle.kts @@ -32,7 +32,6 @@ dependencies { testImplementation(project(":plugins:partiql-local")) testImplementation(project(":plugins:partiql-memory")) testImplementation(testFixtures(project(":partiql-planner"))) - testImplementation(testFixtures(project(":partiql-lang"))) testImplementation(Deps.junit4) testImplementation(Deps.junit4Params) testImplementation(Deps.junitVintage) // Enables JUnit4 diff --git a/partiql-lang/api/partiql-lang.api b/partiql-lang/api/partiql-lang.api deleted file mode 100644 index 04b7cf365b..0000000000 --- a/partiql-lang/api/partiql-lang.api +++ /dev/null @@ -1,2652 +0,0 @@ -public abstract interface annotation class org/partiql/annotations/ExperimentalPartiQLCompilerPipeline : java/lang/annotation/Annotation { -} - -public abstract interface annotation class org/partiql/annotations/ExperimentalWindowFunctions : java/lang/annotation/Annotation { -} - -public abstract interface class org/partiql/lang/CompilerPipeline { - public static final field Companion Lorg/partiql/lang/CompilerPipeline$Companion; - public static fun builder ()Lorg/partiql/lang/CompilerPipeline$Builder; - public abstract fun compile (Ljava/lang/String;)Lorg/partiql/lang/eval/Expression; - public abstract fun compile (Lorg/partiql/lang/domains/PartiqlAst$Statement;)Lorg/partiql/lang/eval/Expression; - public abstract fun getCompileOptions ()Lorg/partiql/lang/eval/CompileOptions; - public abstract fun getCustomDataTypes ()Ljava/util/List; - public abstract fun getFunctions ()Ljava/util/Map; - public abstract fun getGlobalTypeBindings ()Lorg/partiql/lang/eval/Bindings; - public abstract fun getProcedures ()Ljava/util/Map; - public static fun standard ()Lorg/partiql/lang/CompilerPipeline; -} - -public final class org/partiql/lang/CompilerPipeline$Builder { - public fun ()V - public final fun addFunction (Lorg/partiql/lang/eval/ExprFunction;)Lorg/partiql/lang/CompilerPipeline$Builder; - public final fun addPreprocessingStep (Lkotlin/jvm/functions/Function2;)Lorg/partiql/lang/CompilerPipeline$Builder; - public final fun addProcedure (Lorg/partiql/lang/eval/builtins/storedprocedure/StoredProcedure;)Lorg/partiql/lang/CompilerPipeline$Builder; - public final fun build ()Lorg/partiql/lang/CompilerPipeline; - public final fun compileOptions (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/CompilerPipeline$Builder; - public final fun compileOptions (Lorg/partiql/lang/eval/CompileOptions;)Lorg/partiql/lang/CompilerPipeline$Builder; - public final fun customDataTypes (Ljava/util/List;)Lorg/partiql/lang/CompilerPipeline$Builder; - public final fun globalTypeBindings (Lorg/partiql/lang/eval/Bindings;)Lorg/partiql/lang/CompilerPipeline$Builder; - public final fun sqlParser (Lorg/partiql/lang/syntax/Parser;)Lorg/partiql/lang/CompilerPipeline$Builder; - public final fun withCoverageStatistics (Z)Lorg/partiql/lang/CompilerPipeline$Builder; -} - -public final class org/partiql/lang/CompilerPipeline$Companion { - public final fun build (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/CompilerPipeline; - public final fun builder ()Lorg/partiql/lang/CompilerPipeline$Builder; - public final fun standard ()Lorg/partiql/lang/CompilerPipeline; -} - -public class org/partiql/lang/SqlException : java/lang/RuntimeException { - public fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;)V - public synthetic fun (Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun generateMessage ()Ljava/lang/String; - public final fun generateMessageNoLocation ()Ljava/lang/String; - public final fun getErrorCode ()Lorg/partiql/errors/ErrorCode; - public final fun getErrorContext ()Lorg/partiql/errors/PropertyValueMap; - public fun getInternal ()Z - public fun getMessage ()Ljava/lang/String; - public fun setMessage (Ljava/lang/String;)V - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/StepContext { - public fun (Lorg/partiql/lang/eval/CompileOptions;Ljava/util/Map;Ljava/util/Map;)V - public final fun component1 ()Lorg/partiql/lang/eval/CompileOptions; - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Ljava/util/Map; - public final fun copy (Lorg/partiql/lang/eval/CompileOptions;Ljava/util/Map;Ljava/util/Map;)Lorg/partiql/lang/StepContext; - public static synthetic fun copy$default (Lorg/partiql/lang/StepContext;Lorg/partiql/lang/eval/CompileOptions;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lorg/partiql/lang/StepContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getCompileOptions ()Lorg/partiql/lang/eval/CompileOptions; - public final fun getFunctions ()Ljava/util/Map; - public final fun getProcedures ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/SemanticException : org/partiql/lang/SqlException { - public fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Lorg/partiql/errors/Problem;Ljava/lang/Throwable;)V - public synthetic fun (Lorg/partiql/errors/Problem;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public abstract class org/partiql/lang/ast/passes/SemanticProblemDetails : org/partiql/errors/ProblemDetails { - public synthetic fun (Lorg/partiql/errors/ProblemSeverity;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getMessage ()Ljava/lang/String; - public final fun getMessageFormatter ()Lkotlin/jvm/functions/Function0; - public fun getSeverity ()Lorg/partiql/errors/ProblemSeverity; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$CoercionError : org/partiql/lang/ast/passes/SemanticProblemDetails { - public fun (Lorg/partiql/types/StaticType;)V - public final fun component1 ()Lorg/partiql/types/StaticType; - public final fun copy (Lorg/partiql/types/StaticType;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$CoercionError; - public static synthetic fun copy$default (Lorg/partiql/lang/ast/passes/SemanticProblemDetails$CoercionError;Lorg/partiql/types/StaticType;ILjava/lang/Object;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$CoercionError; - public fun equals (Ljava/lang/Object;)Z - public final fun getActualType ()Lorg/partiql/types/StaticType; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$DuplicateAliasesInSelectListItem : org/partiql/lang/ast/passes/SemanticProblemDetails { - public static final field INSTANCE Lorg/partiql/lang/ast/passes/SemanticProblemDetails$DuplicateAliasesInSelectListItem; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$ExpressionAlwaysReturnsMissing : org/partiql/lang/ast/passes/SemanticProblemDetails { - public static final field INSTANCE Lorg/partiql/lang/ast/passes/SemanticProblemDetails$ExpressionAlwaysReturnsMissing; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$ExpressionAlwaysReturnsNullOrMissing : org/partiql/lang/ast/passes/SemanticProblemDetails { - public static final field INSTANCE Lorg/partiql/lang/ast/passes/SemanticProblemDetails$ExpressionAlwaysReturnsNullOrMissing; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$IncompatibleDataTypeForExpr : org/partiql/lang/ast/passes/SemanticProblemDetails { - public fun (Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;)V - public final fun component1 ()Lorg/partiql/types/StaticType; - public final fun component2 ()Lorg/partiql/types/StaticType; - public final fun copy (Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$IncompatibleDataTypeForExpr; - public static synthetic fun copy$default (Lorg/partiql/lang/ast/passes/SemanticProblemDetails$IncompatibleDataTypeForExpr;Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;ILjava/lang/Object;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$IncompatibleDataTypeForExpr; - public fun equals (Ljava/lang/Object;)Z - public final fun getActualType ()Lorg/partiql/types/StaticType; - public final fun getExpectedType ()Lorg/partiql/types/StaticType; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$IncompatibleDatatypesForOp : org/partiql/lang/ast/passes/SemanticProblemDetails { - public fun (Ljava/util/List;Ljava/lang/String;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/util/List;Ljava/lang/String;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$IncompatibleDatatypesForOp; - public static synthetic fun copy$default (Lorg/partiql/lang/ast/passes/SemanticProblemDetails$IncompatibleDatatypesForOp;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$IncompatibleDatatypesForOp; - public fun equals (Ljava/lang/Object;)Z - public final fun getActualArgumentTypes ()Ljava/util/List; - public final fun getNAryOp ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$IncorrectNumberOfArgumentsToFunctionCall : org/partiql/lang/ast/passes/SemanticProblemDetails { - public fun (Ljava/lang/String;Lkotlin/ranges/IntRange;I)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lkotlin/ranges/IntRange; - public final fun component3 ()I - public final fun copy (Ljava/lang/String;Lkotlin/ranges/IntRange;I)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$IncorrectNumberOfArgumentsToFunctionCall; - public static synthetic fun copy$default (Lorg/partiql/lang/ast/passes/SemanticProblemDetails$IncorrectNumberOfArgumentsToFunctionCall;Ljava/lang/String;Lkotlin/ranges/IntRange;IILjava/lang/Object;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$IncorrectNumberOfArgumentsToFunctionCall; - public fun equals (Ljava/lang/Object;)Z - public final fun getActualArity ()I - public final fun getExpectedArity ()Lkotlin/ranges/IntRange; - public final fun getFunctionName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$InvalidArgumentTypeForFunction : org/partiql/lang/ast/passes/SemanticProblemDetails { - public fun (Ljava/lang/String;Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lorg/partiql/types/StaticType; - public final fun component3 ()Lorg/partiql/types/StaticType; - public final fun copy (Ljava/lang/String;Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$InvalidArgumentTypeForFunction; - public static synthetic fun copy$default (Lorg/partiql/lang/ast/passes/SemanticProblemDetails$InvalidArgumentTypeForFunction;Ljava/lang/String;Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;ILjava/lang/Object;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$InvalidArgumentTypeForFunction; - public fun equals (Ljava/lang/Object;)Z - public final fun getActualType ()Lorg/partiql/types/StaticType; - public final fun getExpectedType ()Lorg/partiql/types/StaticType; - public final fun getFunctionName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$MissingAlias : org/partiql/lang/ast/passes/SemanticProblemDetails { - public static final field INSTANCE Lorg/partiql/lang/ast/passes/SemanticProblemDetails$MissingAlias; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$NoSuchFunction : org/partiql/lang/ast/passes/SemanticProblemDetails { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$NoSuchFunction; - public static synthetic fun copy$default (Lorg/partiql/lang/ast/passes/SemanticProblemDetails$NoSuchFunction;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$NoSuchFunction; - public fun equals (Ljava/lang/Object;)Z - public final fun getFunctionName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/SemanticProblemDetails$NullOrMissingFunctionArgument : org/partiql/lang/ast/passes/SemanticProblemDetails { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$NullOrMissingFunctionArgument; - public static synthetic fun copy$default (Lorg/partiql/lang/ast/passes/SemanticProblemDetails$NullOrMissingFunctionArgument;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/ast/passes/SemanticProblemDetails$NullOrMissingFunctionArgument; - public fun equals (Ljava/lang/Object;)Z - public final fun getFunctionName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/StatementRedactorKt { - public static final field INPUT_AST_STATEMENT_MISMATCH Ljava/lang/String; - public static final field INVALID_NUM_ARGS Ljava/lang/String; - public static final fun redact (Ljava/lang/String;Ljava/util/Set;Ljava/util/Map;)Ljava/lang/String; - public static final fun redact (Ljava/lang/String;Lorg/partiql/lang/domains/PartiqlAst$Statement;Ljava/util/Set;Ljava/util/Map;)Ljava/lang/String; - public static synthetic fun redact$default (Ljava/lang/String;Ljava/util/Set;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/String; - public static synthetic fun redact$default (Ljava/lang/String;Lorg/partiql/lang/domains/PartiqlAst$Statement;Ljava/util/Set;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/String; - public static final fun skipRedaction (Lorg/partiql/lang/domains/PartiqlAst$Expr;Ljava/util/Set;)Z -} - -public final class org/partiql/lang/ast/passes/inference/StaticTypeInferencer { - public fun (Lorg/partiql/lang/eval/Bindings;Ljava/util/List;Ljava/util/Map;)V - public final fun inferStaticType (Lorg/partiql/lang/domains/PartiqlAst$Statement;)Lorg/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult; -} - -public abstract class org/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult { - public abstract fun getProblems ()Ljava/util/List; -} - -public final class org/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult$Failure : org/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult { - public fun (Lorg/partiql/types/StaticType;Lorg/partiql/lang/domains/PartiqlAst$Statement;Ljava/util/List;)V - public final fun component3 ()Ljava/util/List; - public final fun copy (Lorg/partiql/types/StaticType;Lorg/partiql/lang/domains/PartiqlAst$Statement;Ljava/util/List;)Lorg/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult$Failure; - public static synthetic fun copy$default (Lorg/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult$Failure;Lorg/partiql/types/StaticType;Lorg/partiql/lang/domains/PartiqlAst$Statement;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult$Failure; - public fun equals (Ljava/lang/Object;)Z - public fun getProblems ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult$Success : org/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult { - public fun (Lorg/partiql/types/StaticType;Ljava/util/List;)V - public final fun component1 ()Lorg/partiql/types/StaticType; - public final fun component2 ()Ljava/util/List; - public final fun copy (Lorg/partiql/types/StaticType;Ljava/util/List;)Lorg/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult$Success; - public static synthetic fun copy$default (Lorg/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult$Success;Lorg/partiql/types/StaticType;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult$Success; - public fun equals (Ljava/lang/Object;)Z - public fun getProblems ()Ljava/util/List; - public final fun getStaticType ()Lorg/partiql/types/StaticType; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/ast/passes/inference/StaticTypeInferencerKt { - public static final fun staticTypeOrError (Lorg/partiql/lang/ast/passes/inference/StaticTypeInferencer$InferenceResult;)Lorg/partiql/types/StaticType; -} - -public abstract interface class org/partiql/lang/compiler/PartiQLCompiler { - public abstract fun compile (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;)Lorg/partiql/lang/eval/PartiQLStatement; - public abstract fun compile (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails;)Lorg/partiql/lang/eval/PartiQLStatement; -} - -public abstract interface class org/partiql/lang/compiler/PartiQLCompilerAsync { - public abstract fun compile (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun compile (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class org/partiql/lang/compiler/PartiQLCompilerAsyncBuilder { - public static final field Companion Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder$Companion; - public final fun build ()Lorg/partiql/lang/compiler/PartiQLCompilerAsync; - public final fun customFunctions (Ljava/util/List;)Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder; - public final fun customOperatorFactories (Ljava/util/List;)Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder; - public final fun customProcedures (Ljava/util/List;)Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder; - public final fun customTypes (Ljava/util/List;)Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder; - public final fun options (Lorg/partiql/lang/planner/EvaluatorOptions;)Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder; - public static final fun standard ()Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder; -} - -public final class org/partiql/lang/compiler/PartiQLCompilerAsyncBuilder$Companion { - public final fun standard ()Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder; -} - -public final class org/partiql/lang/compiler/PartiQLCompilerBuilder { - public static final field Companion Lorg/partiql/lang/compiler/PartiQLCompilerBuilder$Companion; - public final fun build ()Lorg/partiql/lang/compiler/PartiQLCompiler; - public final fun customFunctions (Ljava/util/List;)Lorg/partiql/lang/compiler/PartiQLCompilerBuilder; - public final fun customOperatorFactories (Ljava/util/List;)Lorg/partiql/lang/compiler/PartiQLCompilerBuilder; - public final fun customProcedures (Ljava/util/List;)Lorg/partiql/lang/compiler/PartiQLCompilerBuilder; - public final fun customTypes (Ljava/util/List;)Lorg/partiql/lang/compiler/PartiQLCompilerBuilder; - public final fun options (Lorg/partiql/lang/planner/EvaluatorOptions;)Lorg/partiql/lang/compiler/PartiQLCompilerBuilder; - public static final fun standard ()Lorg/partiql/lang/compiler/PartiQLCompilerBuilder; -} - -public final class org/partiql/lang/compiler/PartiQLCompilerBuilder$Companion { - public final fun standard ()Lorg/partiql/lang/compiler/PartiQLCompilerBuilder; -} - -public final class org/partiql/lang/compiler/PartiQLCompilerPipeline$Builder { - public final fun getCompiler ()Lorg/partiql/lang/compiler/PartiQLCompilerBuilder; - public final fun getParser ()Lorg/partiql/lang/syntax/PartiQLParserBuilder; - public final fun getPlanner ()Lorg/partiql/lang/planner/PartiQLPlannerBuilder; - public final fun setCompiler (Lorg/partiql/lang/compiler/PartiQLCompilerBuilder;)V - public final fun setParser (Lorg/partiql/lang/syntax/PartiQLParserBuilder;)V - public final fun setPlanner (Lorg/partiql/lang/planner/PartiQLPlannerBuilder;)V -} - -public final class org/partiql/lang/compiler/PartiQLCompilerPipeline$Companion { - public final fun build (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/compiler/PartiQLCompilerPipeline; - public final fun standard ()Lorg/partiql/lang/compiler/PartiQLCompilerPipeline; -} - -public final class org/partiql/lang/compiler/PartiQLCompilerPipelineAsync { - public static final field Companion Lorg/partiql/lang/compiler/PartiQLCompilerPipelineAsync$Companion; - public fun (Lorg/partiql/lang/syntax/Parser;Lorg/partiql/lang/planner/PartiQLPlanner;Lorg/partiql/lang/compiler/PartiQLCompilerAsync;)V - public final fun compile (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun compile (Lorg/partiql/lang/domains/PartiqlAst$Statement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun compile (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun compile (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun compile$default (Lorg/partiql/lang/compiler/PartiQLCompilerPipelineAsync;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public static final fun standard ()Lorg/partiql/lang/compiler/PartiQLCompilerPipelineAsync; -} - -public final class org/partiql/lang/compiler/PartiQLCompilerPipelineAsync$Builder { - public final fun getCompiler ()Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder; - public final fun getParser ()Lorg/partiql/lang/syntax/PartiQLParserBuilder; - public final fun getPlanner ()Lorg/partiql/lang/planner/PartiQLPlannerBuilder; - public final fun setCompiler (Lorg/partiql/lang/compiler/PartiQLCompilerAsyncBuilder;)V - public final fun setParser (Lorg/partiql/lang/syntax/PartiQLParserBuilder;)V - public final fun setPlanner (Lorg/partiql/lang/planner/PartiQLPlannerBuilder;)V -} - -public final class org/partiql/lang/compiler/PartiQLCompilerPipelineAsync$Companion { - public final fun build (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/compiler/PartiQLCompilerPipelineAsync; - public final fun standard ()Lorg/partiql/lang/compiler/PartiQLCompilerPipelineAsync; -} - -public final class org/partiql/lang/domains/UtilKt { - public static final fun addSourceLocation (Lorg/partiql/errors/PropertyValueMap;Ljava/util/Map;)Lorg/partiql/errors/PropertyValueMap; - public static final fun extractSourceLocation (Lorg/partiql/lang/domains/PartiqlAst$PartiqlAstNode;)Ljava/util/Map; - public static final fun getStaticType (Ljava/util/Map;)Lorg/partiql/lang/ast/StaticTypeMeta; - public static final fun id (Lorg/partiql/lang/domains/PartiqlAst$Builder;Ljava/lang/String;)Lorg/partiql/lang/domains/PartiqlAst$Expr$Id; - public static final fun id (Lorg/partiql/lang/domains/PartiqlLogical$Builder;Ljava/lang/String;)Lorg/partiql/lang/domains/PartiqlLogical$Expr$Id; - public static final fun metaContainerOf ([Lorg/partiql/lang/ast/Meta;)Ljava/util/Map; - public static final fun pathExpr (Lorg/partiql/lang/domains/PartiqlLogical$Builder;Lorg/partiql/lang/domains/PartiqlLogical$Expr;)Lorg/partiql/lang/domains/PartiqlLogical$PathStep$PathExpr; - public static final fun toBindingCase (Lorg/partiql/lang/domains/PartiqlAst$CaseSensitivity;)Lorg/partiql/lang/eval/BindingCase; - public static final fun toBindingCase (Lorg/partiql/lang/domains/PartiqlLogical$CaseSensitivity;)Lorg/partiql/lang/eval/BindingCase; - public static final fun toBindingCase (Lorg/partiql/lang/domains/PartiqlPhysical$CaseSensitivity;)Lorg/partiql/lang/eval/BindingCase; -} - -public final class org/partiql/lang/errors/PartiQLException : java/lang/RuntimeException { - public fun (Ljava/lang/String;)V - public fun getMessage ()Ljava/lang/String; -} - -public abstract interface class org/partiql/lang/eval/Addressed { - public abstract fun getAddress ()Lorg/partiql/lang/eval/ExprValue; -} - -public abstract class org/partiql/lang/eval/Arguments { -} - -public abstract class org/partiql/lang/eval/BaseExprValue : org/partiql/lang/eval/ExprValue { - public fun ()V - public final fun asFacet (Ljava/lang/Class;)Ljava/lang/Object; - public fun getBindings ()Lorg/partiql/lang/eval/Bindings; - public fun getGraphValue ()Lorg/partiql/lang/graph/Graph; - public fun getOrdinalBindings ()Lorg/partiql/lang/eval/OrdinalBindings; - public fun getScalar ()Lorg/partiql/lang/eval/Scalar; - public fun iterator ()Ljava/util/Iterator; - public fun provideFacet (Ljava/lang/Class;)Ljava/lang/Object; - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/BindingCase : java/lang/Enum { - public static final field Companion Lorg/partiql/lang/eval/BindingCase$Companion; - public static final field INSENSITIVE Lorg/partiql/lang/eval/BindingCase; - public static final field SENSITIVE Lorg/partiql/lang/eval/BindingCase; - public final fun toSymbol (Lcom/amazon/ion/IonSystem;)Lcom/amazon/ion/IonSymbol; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/BindingCase; - public static fun values ()[Lorg/partiql/lang/eval/BindingCase; -} - -public final class org/partiql/lang/eval/BindingCase$Companion { - public final fun fromIonValue (Lcom/amazon/ion/IonValue;)Lorg/partiql/lang/eval/BindingCase; -} - -public final class org/partiql/lang/eval/BindingName { - public fun (Ljava/lang/String;Lorg/partiql/lang/eval/BindingCase;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lorg/partiql/lang/eval/BindingCase; - public final fun copy (Ljava/lang/String;Lorg/partiql/lang/eval/BindingCase;)Lorg/partiql/lang/eval/BindingName; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/BindingName;Ljava/lang/String;Lorg/partiql/lang/eval/BindingCase;ILjava/lang/Object;)Lorg/partiql/lang/eval/BindingName; - public fun equals (Ljava/lang/Object;)Z - public final fun getBindingCase ()Lorg/partiql/lang/eval/BindingCase; - public final fun getLoweredName ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public final fun isEquivalentTo (Ljava/lang/String;)Z - public fun toString ()Ljava/lang/String; -} - -public abstract interface class org/partiql/lang/eval/Bindings { - public static final field Companion Lorg/partiql/lang/eval/Bindings$Companion; - public abstract fun get (Lorg/partiql/lang/eval/BindingName;)Ljava/lang/Object; - public static fun lazyBindingsBuilder ()Lorg/partiql/lang/eval/Bindings$LazyBindingBuilder; - public static fun ofIonStruct (Lcom/amazon/ion/IonStruct;)Lorg/partiql/lang/eval/Bindings; - public static fun ofMap (Ljava/util/Map;)Lorg/partiql/lang/eval/Bindings; -} - -public final class org/partiql/lang/eval/Bindings$Companion { - public final fun buildLazyBindings (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/eval/Bindings; - public final fun empty ()Lorg/partiql/lang/eval/Bindings; - public final fun lazyBindingsBuilder ()Lorg/partiql/lang/eval/Bindings$LazyBindingBuilder; - public final fun ofIonStruct (Lcom/amazon/ion/IonStruct;)Lorg/partiql/lang/eval/Bindings; - public final fun ofMap (Ljava/util/Map;)Lorg/partiql/lang/eval/Bindings; - public final fun over (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/eval/Bindings; -} - -public final class org/partiql/lang/eval/Bindings$LazyBindingBuilder { - public fun ()V - public final fun addBinding (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lorg/partiql/lang/eval/Bindings$LazyBindingBuilder; - public final fun build ()Lorg/partiql/lang/eval/Bindings; -} - -public final class org/partiql/lang/eval/BindingsExtensionsKt { - public static final fun delegate (Lorg/partiql/lang/eval/Bindings;Lorg/partiql/lang/eval/Bindings;)Lorg/partiql/lang/eval/Bindings; -} - -public final class org/partiql/lang/eval/BindingsKt { - public static final fun toBindingCase (Lorg/partiql/lang/domains/PartiqlAst$CaseSensitivity;)Lorg/partiql/lang/eval/BindingCase; -} - -public final class org/partiql/lang/eval/CompileOptions { - public static final field Companion Lorg/partiql/lang/eval/CompileOptions$Companion; - public static final fun builder ()Lorg/partiql/lang/eval/CompileOptions$Builder; - public static final fun builder (Lorg/partiql/lang/eval/CompileOptions;)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun component1 ()Lorg/partiql/lang/eval/UndefinedVariableBehavior; - public final fun component2 ()Lorg/partiql/lang/eval/ProjectionIterationBehavior; - public final fun component3 ()Lorg/partiql/lang/eval/VisitorTransformMode; - public final fun component4 ()Lorg/partiql/lang/eval/ThunkOptions; - public final fun component5 ()Lorg/partiql/lang/eval/TypingMode; - public final fun component6 ()Lorg/partiql/lang/eval/TypedOpBehavior; - public final fun component7 ()Ljava/time/ZoneOffset; - public final fun component8 ()Z - public final fun copy (Lorg/partiql/lang/eval/UndefinedVariableBehavior;Lorg/partiql/lang/eval/ProjectionIterationBehavior;Lorg/partiql/lang/eval/VisitorTransformMode;Lorg/partiql/lang/eval/ThunkOptions;Lorg/partiql/lang/eval/TypingMode;Lorg/partiql/lang/eval/TypedOpBehavior;Ljava/time/ZoneOffset;Z)Lorg/partiql/lang/eval/CompileOptions; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/CompileOptions;Lorg/partiql/lang/eval/UndefinedVariableBehavior;Lorg/partiql/lang/eval/ProjectionIterationBehavior;Lorg/partiql/lang/eval/VisitorTransformMode;Lorg/partiql/lang/eval/ThunkOptions;Lorg/partiql/lang/eval/TypingMode;Lorg/partiql/lang/eval/TypedOpBehavior;Ljava/time/ZoneOffset;ZILjava/lang/Object;)Lorg/partiql/lang/eval/CompileOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getDefaultTimezoneOffset ()Ljava/time/ZoneOffset; - public final fun getInterruptible ()Z - public final fun getProjectionIteration ()Lorg/partiql/lang/eval/ProjectionIterationBehavior; - public final fun getThunkOptions ()Lorg/partiql/lang/eval/ThunkOptions; - public final fun getTypedOpBehavior ()Lorg/partiql/lang/eval/TypedOpBehavior; - public final fun getTypingMode ()Lorg/partiql/lang/eval/TypingMode; - public final fun getUndefinedVariable ()Lorg/partiql/lang/eval/UndefinedVariableBehavior; - public final fun getVisitorTransformMode ()Lorg/partiql/lang/eval/VisitorTransformMode; - public fun hashCode ()I - public static final fun standard ()Lorg/partiql/lang/eval/CompileOptions; - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/CompileOptions$Builder { - public fun ()V - public fun (Lorg/partiql/lang/eval/CompileOptions;)V - public synthetic fun (Lorg/partiql/lang/eval/CompileOptions;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun build ()Lorg/partiql/lang/eval/CompileOptions; - public final fun defaultTimezoneOffset (Ljava/time/ZoneOffset;)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun isInterruptible (Z)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun projectionIteration (Lorg/partiql/lang/eval/ProjectionIterationBehavior;)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun thunkOptions (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun thunkOptions (Lorg/partiql/lang/eval/ThunkOptions;)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun typedOpBehavior (Lorg/partiql/lang/eval/TypedOpBehavior;)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun typingMode (Lorg/partiql/lang/eval/TypingMode;)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun undefinedVariable (Lorg/partiql/lang/eval/UndefinedVariableBehavior;)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun visitorTransformMode (Lorg/partiql/lang/eval/VisitorTransformMode;)Lorg/partiql/lang/eval/CompileOptions$Builder; -} - -public final class org/partiql/lang/eval/CompileOptions$Companion { - public final fun build (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/eval/CompileOptions; - public final fun build (Lorg/partiql/lang/eval/CompileOptions;Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/eval/CompileOptions; - public final fun builder ()Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun builder (Lorg/partiql/lang/eval/CompileOptions;)Lorg/partiql/lang/eval/CompileOptions$Builder; - public final fun standard ()Lorg/partiql/lang/eval/CompileOptions; -} - -public final class org/partiql/lang/eval/CoverageData { - public fun ()V - public fun (Lorg/partiql/lang/eval/CoverageData$ExecutionCount;Lorg/partiql/lang/eval/CoverageData$ExecutionCount;)V - public synthetic fun (Lorg/partiql/lang/eval/CoverageData$ExecutionCount;Lorg/partiql/lang/eval/CoverageData$ExecutionCount;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lorg/partiql/lang/eval/CoverageData$ExecutionCount; - public final fun component2 ()Lorg/partiql/lang/eval/CoverageData$ExecutionCount; - public final fun copy (Lorg/partiql/lang/eval/CoverageData$ExecutionCount;Lorg/partiql/lang/eval/CoverageData$ExecutionCount;)Lorg/partiql/lang/eval/CoverageData; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/CoverageData;Lorg/partiql/lang/eval/CoverageData$ExecutionCount;Lorg/partiql/lang/eval/CoverageData$ExecutionCount;ILjava/lang/Object;)Lorg/partiql/lang/eval/CoverageData; - public fun equals (Ljava/lang/Object;)Z - public final fun getBranchConditionCount ()Lorg/partiql/lang/eval/CoverageData$ExecutionCount; - public final fun getBranchCount ()Lorg/partiql/lang/eval/CoverageData$ExecutionCount; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/CoverageData$ExecutionCount : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { - public fun (Ljava/util/Map;)V - public fun clear ()V - public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun compute (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Long; - public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; - public fun computeIfAbsent (Ljava/lang/String;Ljava/util/function/Function;)Ljava/lang/Long; - public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun computeIfPresent (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Long; - public final fun containsKey (Ljava/lang/Object;)Z - public fun containsKey (Ljava/lang/String;)Z - public fun containsValue (J)Z - public final fun containsValue (Ljava/lang/Object;)Z - public final fun entrySet ()Ljava/util/Set; - public final fun get (Ljava/lang/Object;)Ljava/lang/Long; - public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; - public fun get (Ljava/lang/String;)Ljava/lang/Long; - public fun getEntries ()Ljava/util/Set; - public fun getKeys ()Ljava/util/Set; - public fun getSize ()I - public fun getValues ()Ljava/util/Collection; - public fun isEmpty ()Z - public final fun keySet ()Ljava/util/Set; - public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun merge (Ljava/lang/String;Ljava/lang/Long;Ljava/util/function/BiFunction;)Ljava/lang/Long; - public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public fun put (Ljava/lang/String;J)Ljava/lang/Long; - public fun putAll (Ljava/util/Map;)V - public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public fun putIfAbsent (Ljava/lang/String;Ljava/lang/Long;)Ljava/lang/Long; - public fun remove (Ljava/lang/Object;)Ljava/lang/Long; - public synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; - public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z - public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z - public fun replace (Ljava/lang/String;Ljava/lang/Long;)Ljava/lang/Long; - public fun replace (Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;)Z - public fun replaceAll (Ljava/util/function/BiFunction;)V - public final fun size ()I - public final fun values ()Ljava/util/Collection; -} - -public final class org/partiql/lang/eval/CoverageStructure { - public fun ()V - public fun (Ljava/util/Map;Ljava/util/Map;)V - public synthetic fun (Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/Map; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/util/Map;Ljava/util/Map;)Lorg/partiql/lang/eval/CoverageStructure; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/CoverageStructure;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lorg/partiql/lang/eval/CoverageStructure; - public fun equals (Ljava/lang/Object;)Z - public final fun getBranchConditions ()Ljava/util/Map; - public final fun getBranches ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/CoverageStructure$Branch { - public fun (Ljava/lang/String;Lorg/partiql/lang/eval/CoverageStructure$Branch$Type;Lorg/partiql/lang/eval/CoverageStructure$Branch$Outcome;J)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lorg/partiql/lang/eval/CoverageStructure$Branch$Type; - public final fun component3 ()Lorg/partiql/lang/eval/CoverageStructure$Branch$Outcome; - public final fun component4 ()J - public final fun copy (Ljava/lang/String;Lorg/partiql/lang/eval/CoverageStructure$Branch$Type;Lorg/partiql/lang/eval/CoverageStructure$Branch$Outcome;J)Lorg/partiql/lang/eval/CoverageStructure$Branch; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/CoverageStructure$Branch;Ljava/lang/String;Lorg/partiql/lang/eval/CoverageStructure$Branch$Type;Lorg/partiql/lang/eval/CoverageStructure$Branch$Outcome;JILjava/lang/Object;)Lorg/partiql/lang/eval/CoverageStructure$Branch; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()Ljava/lang/String; - public final fun getLine ()J - public final fun getOutcome ()Lorg/partiql/lang/eval/CoverageStructure$Branch$Outcome; - public final fun getType ()Lorg/partiql/lang/eval/CoverageStructure$Branch$Type; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/CoverageStructure$Branch$Outcome : java/lang/Enum { - public static final field FALSE Lorg/partiql/lang/eval/CoverageStructure$Branch$Outcome; - public static final field TRUE Lorg/partiql/lang/eval/CoverageStructure$Branch$Outcome; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/CoverageStructure$Branch$Outcome; - public static fun values ()[Lorg/partiql/lang/eval/CoverageStructure$Branch$Outcome; -} - -public final class org/partiql/lang/eval/CoverageStructure$Branch$Type : java/lang/Enum { - public static final field CASE_WHEN Lorg/partiql/lang/eval/CoverageStructure$Branch$Type; - public static final field HAVING Lorg/partiql/lang/eval/CoverageStructure$Branch$Type; - public static final field WHERE Lorg/partiql/lang/eval/CoverageStructure$Branch$Type; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/CoverageStructure$Branch$Type; - public static fun values ()[Lorg/partiql/lang/eval/CoverageStructure$Branch$Type; -} - -public final class org/partiql/lang/eval/CoverageStructure$BranchCondition { - public fun (Ljava/lang/String;Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type;Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome;J)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public final fun component3 ()Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome; - public final fun component4 ()J - public final fun copy (Ljava/lang/String;Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type;Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome;J)Lorg/partiql/lang/eval/CoverageStructure$BranchCondition; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/CoverageStructure$BranchCondition;Ljava/lang/String;Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type;Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome;JILjava/lang/Object;)Lorg/partiql/lang/eval/CoverageStructure$BranchCondition; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()Ljava/lang/String; - public final fun getLine ()J - public final fun getOutcome ()Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome; - public final fun getType ()Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome : java/lang/Enum { - public static final field FALSE Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome; - public static final field MISSING Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome; - public static final field NULL Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome; - public static final field TRUE Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome; - public static fun values ()[Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Outcome; -} - -public final class org/partiql/lang/eval/CoverageStructure$BranchCondition$Type : java/lang/Enum { - public static final field AND Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field BETWEEN Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field EQ Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field GT Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field GTE Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field IN Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field IS Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field LIKE Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field LT Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field LTE Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field NEQ Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field NOT Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static final field OR Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; - public static fun values ()[Lorg/partiql/lang/eval/CoverageStructure$BranchCondition$Type; -} - -public class org/partiql/lang/eval/EvaluationException : org/partiql/lang/SqlException { - public fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;Z)V - public synthetic fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Ljava/lang/Throwable;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Z)V - public synthetic fun (Ljava/lang/Throwable;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getInternal ()Z -} - -public final class org/partiql/lang/eval/EvaluationSession { - public static final field Companion Lorg/partiql/lang/eval/EvaluationSession$Companion; - public synthetic fun (Lorg/partiql/lang/eval/Bindings;Ljava/util/List;Ljava/util/Map;Lcom/amazon/ion/Timestamp;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public static final fun builder ()Lorg/partiql/lang/eval/EvaluationSession$Builder; - public final fun getContext ()Ljava/util/Map; - public final fun getGlobals ()Lorg/partiql/lang/eval/Bindings; - public final fun getNow ()Lcom/amazon/ion/Timestamp; - public final fun getParameters ()Ljava/util/List; - public static final fun standard ()Lorg/partiql/lang/eval/EvaluationSession; -} - -public final class org/partiql/lang/eval/EvaluationSession$Builder { - public fun ()V - public final fun build ()Lorg/partiql/lang/eval/EvaluationSession; - public final fun globals (Lorg/partiql/lang/eval/Bindings;)Lorg/partiql/lang/eval/EvaluationSession$Builder; - public final fun now (Lcom/amazon/ion/Timestamp;)Lorg/partiql/lang/eval/EvaluationSession$Builder; - public final fun parameters (Ljava/util/List;)Lorg/partiql/lang/eval/EvaluationSession$Builder; - public final fun user (Ljava/lang/String;)Lorg/partiql/lang/eval/EvaluationSession$Builder; - public final fun withContextVariable (Ljava/lang/String;Ljava/lang/Object;)Lorg/partiql/lang/eval/EvaluationSession$Builder; -} - -public final class org/partiql/lang/eval/EvaluationSession$Companion { - public final fun build (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/eval/EvaluationSession; - public final fun builder ()Lorg/partiql/lang/eval/EvaluationSession$Builder; - public final fun standard ()Lorg/partiql/lang/eval/EvaluationSession; -} - -public final class org/partiql/lang/eval/EvaluationSession$Constants { - public static final field CURRENT_USER_KEY Ljava/lang/String; - public static final field INSTANCE Lorg/partiql/lang/eval/EvaluationSession$Constants; -} - -public final class org/partiql/lang/eval/ExceptionsKt { - public static final fun errorContextFrom (Ljava/util/Map;)Lorg/partiql/errors/PropertyValueMap; - public static final fun errorContextFrom (Lorg/partiql/lang/ast/SourceLocationMeta;)Lorg/partiql/errors/PropertyValueMap; - public static final fun fillErrorContext (Lorg/partiql/errors/PropertyValueMap;Lorg/partiql/lang/ast/SourceLocationMeta;)V -} - -public abstract interface class org/partiql/lang/eval/ExprFunction { - public abstract fun callWithOptional (Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/ExprValue; - public abstract fun callWithRequired (Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;)Lorg/partiql/lang/eval/ExprValue; - public abstract fun callWithVariadic (Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;Ljava/util/List;)Lorg/partiql/lang/eval/ExprValue; - public abstract fun getSignature ()Lorg/partiql/lang/types/FunctionSignature; -} - -public final class org/partiql/lang/eval/ExprFunction$DefaultImpls { - public static fun callWithOptional (Lorg/partiql/lang/eval/ExprFunction;Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/ExprValue; - public static fun callWithRequired (Lorg/partiql/lang/eval/ExprFunction;Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;)Lorg/partiql/lang/eval/ExprValue; - public static fun callWithVariadic (Lorg/partiql/lang/eval/ExprFunction;Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;Ljava/util/List;)Lorg/partiql/lang/eval/ExprValue; -} - -public final class org/partiql/lang/eval/ExprFunctionKt { - public static final fun call (Lorg/partiql/lang/eval/ExprFunction;Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;)Lorg/partiql/lang/eval/ExprValue; - public static final fun call (Lorg/partiql/lang/eval/ExprFunction;Lorg/partiql/lang/eval/EvaluationSession;Lorg/partiql/lang/eval/Arguments;)Lorg/partiql/lang/eval/ExprValue; -} - -public abstract interface class org/partiql/lang/eval/ExprValue : com/amazon/ion/facet/Faceted, java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { - public static final field Companion Lorg/partiql/lang/eval/ExprValue$Companion; - public abstract fun getBindings ()Lorg/partiql/lang/eval/Bindings; - public static fun getEmptyBag ()Lorg/partiql/lang/eval/ExprValue; - public static fun getEmptyList ()Lorg/partiql/lang/eval/ExprValue; - public static fun getEmptySexp ()Lorg/partiql/lang/eval/ExprValue; - public static fun getEmptyStruct ()Lorg/partiql/lang/eval/ExprValue; - public abstract fun getGraphValue ()Lorg/partiql/lang/graph/Graph; - public static fun getMissingValue ()Lorg/partiql/lang/eval/ExprValue; - public static fun getNullValue ()Lorg/partiql/lang/eval/ExprValue; - public abstract fun getOrdinalBindings ()Lorg/partiql/lang/eval/OrdinalBindings; - public abstract fun getScalar ()Lorg/partiql/lang/eval/Scalar; - public abstract fun getType ()Lorg/partiql/lang/eval/ExprValueType; - public abstract fun iterator ()Ljava/util/Iterator; - public static fun newBag (Ljava/lang/Iterable;)Lorg/partiql/lang/eval/ExprValue; - public static fun newBag (Lkotlin/sequences/Sequence;)Lorg/partiql/lang/eval/ExprValue; - public static fun newBlob ([B)Lorg/partiql/lang/eval/ExprValue; - public static fun newBoolean (Z)Lorg/partiql/lang/eval/ExprValue; - public static fun newClob ([B)Lorg/partiql/lang/eval/ExprValue; - public static fun newDate (III)Lorg/partiql/lang/eval/ExprValue; - public static fun newDate (Ljava/lang/String;)Lorg/partiql/lang/eval/ExprValue; - public static fun newDate (Ljava/time/LocalDate;)Lorg/partiql/lang/eval/ExprValue; - public static fun newDecimal (I)Lorg/partiql/lang/eval/ExprValue; - public static fun newDecimal (J)Lorg/partiql/lang/eval/ExprValue; - public static fun newDecimal (Ljava/math/BigDecimal;)Lorg/partiql/lang/eval/ExprValue; - public static fun newFloat (D)Lorg/partiql/lang/eval/ExprValue; - public static fun newFromIonReader (Lcom/amazon/ion/IonSystem;Lcom/amazon/ion/IonReader;)Lorg/partiql/lang/eval/ExprValue; - public static fun newGraph (Lorg/partiql/lang/graph/Graph;)Lorg/partiql/lang/eval/ExprValue; - public static fun newInt (I)Lorg/partiql/lang/eval/ExprValue; - public static fun newInt (J)Lorg/partiql/lang/eval/ExprValue; - public static fun newList (Ljava/lang/Iterable;)Lorg/partiql/lang/eval/ExprValue; - public static fun newList (Lkotlin/sequences/Sequence;)Lorg/partiql/lang/eval/ExprValue; - public static fun newNull (Lcom/amazon/ion/IonType;)Lorg/partiql/lang/eval/ExprValue; - public static fun newSexp (Ljava/lang/Iterable;)Lorg/partiql/lang/eval/ExprValue; - public static fun newSexp (Lkotlin/sequences/Sequence;)Lorg/partiql/lang/eval/ExprValue; - public static fun newString (Ljava/lang/String;)Lorg/partiql/lang/eval/ExprValue; - public static fun newStruct (Ljava/lang/Iterable;Lorg/partiql/lang/eval/StructOrdering;)Lorg/partiql/lang/eval/ExprValue; - public static fun newStruct (Lkotlin/sequences/Sequence;Lorg/partiql/lang/eval/StructOrdering;)Lorg/partiql/lang/eval/ExprValue; - public static fun newSymbol (Ljava/lang/String;)Lorg/partiql/lang/eval/ExprValue; - public static fun newTime (Lorg/partiql/lang/eval/time/Time;)Lorg/partiql/lang/eval/ExprValue; - public static fun newTimestamp (Lcom/amazon/ion/Timestamp;)Lorg/partiql/lang/eval/ExprValue; - public static fun of (Lcom/amazon/ion/IonValue;)Lorg/partiql/lang/eval/ExprValue; -} - -public final class org/partiql/lang/eval/ExprValue$Companion { - public final fun getEmptyBag ()Lorg/partiql/lang/eval/ExprValue; - public final fun getEmptyList ()Lorg/partiql/lang/eval/ExprValue; - public final fun getEmptySexp ()Lorg/partiql/lang/eval/ExprValue; - public final fun getEmptyStruct ()Lorg/partiql/lang/eval/ExprValue; - public final fun getMissingValue ()Lorg/partiql/lang/eval/ExprValue; - public final fun getNullValue ()Lorg/partiql/lang/eval/ExprValue; - public final fun newBag (Ljava/lang/Iterable;)Lorg/partiql/lang/eval/ExprValue; - public final fun newBag (Lkotlin/sequences/Sequence;)Lorg/partiql/lang/eval/ExprValue; - public final fun newBlob ([B)Lorg/partiql/lang/eval/ExprValue; - public final fun newBoolean (Z)Lorg/partiql/lang/eval/ExprValue; - public final fun newClob ([B)Lorg/partiql/lang/eval/ExprValue; - public final fun newDate (III)Lorg/partiql/lang/eval/ExprValue; - public final fun newDate (Ljava/lang/String;)Lorg/partiql/lang/eval/ExprValue; - public final fun newDate (Ljava/time/LocalDate;)Lorg/partiql/lang/eval/ExprValue; - public final fun newDecimal (I)Lorg/partiql/lang/eval/ExprValue; - public final fun newDecimal (J)Lorg/partiql/lang/eval/ExprValue; - public final fun newDecimal (Ljava/math/BigDecimal;)Lorg/partiql/lang/eval/ExprValue; - public final fun newFloat (D)Lorg/partiql/lang/eval/ExprValue; - public final fun newFromIonReader (Lcom/amazon/ion/IonSystem;Lcom/amazon/ion/IonReader;)Lorg/partiql/lang/eval/ExprValue; - public final fun newGraph (Lorg/partiql/lang/graph/Graph;)Lorg/partiql/lang/eval/ExprValue; - public final fun newInt (I)Lorg/partiql/lang/eval/ExprValue; - public final fun newInt (J)Lorg/partiql/lang/eval/ExprValue; - public final fun newList (Ljava/lang/Iterable;)Lorg/partiql/lang/eval/ExprValue; - public final fun newList (Lkotlin/sequences/Sequence;)Lorg/partiql/lang/eval/ExprValue; - public final fun newNull (Lcom/amazon/ion/IonType;)Lorg/partiql/lang/eval/ExprValue; - public final fun newSexp (Ljava/lang/Iterable;)Lorg/partiql/lang/eval/ExprValue; - public final fun newSexp (Lkotlin/sequences/Sequence;)Lorg/partiql/lang/eval/ExprValue; - public final fun newString (Ljava/lang/String;)Lorg/partiql/lang/eval/ExprValue; - public final fun newStruct (Ljava/lang/Iterable;Lorg/partiql/lang/eval/StructOrdering;)Lorg/partiql/lang/eval/ExprValue; - public final fun newStruct (Lkotlin/sequences/Sequence;Lorg/partiql/lang/eval/StructOrdering;)Lorg/partiql/lang/eval/ExprValue; - public final fun newSymbol (Ljava/lang/String;)Lorg/partiql/lang/eval/ExprValue; - public final fun newTime (Lorg/partiql/lang/eval/time/Time;)Lorg/partiql/lang/eval/ExprValue; - public final fun newTimestamp (Lcom/amazon/ion/Timestamp;)Lorg/partiql/lang/eval/ExprValue; - public final fun of (Lcom/amazon/ion/IonValue;)Lorg/partiql/lang/eval/ExprValue; -} - -public abstract interface class org/partiql/lang/eval/ExprValueBagOp { - public static final field Companion Lorg/partiql/lang/eval/ExprValueBagOp$Companion; - public abstract fun eval (Lkotlin/sequences/Sequence;Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; - public abstract fun eval (Lorg/partiql/lang/eval/ExprValue;Lorg/partiql/lang/eval/ExprValue;)Lkotlin/sequences/Sequence; -} - -public final class org/partiql/lang/eval/ExprValueBagOp$Companion { - public final fun create (Lorg/partiql/pig/runtime/DomainNode;Ljava/util/Map;)Lorg/partiql/lang/eval/ExprValueBagOp; -} - -public final class org/partiql/lang/eval/ExprValueBagOp$DefaultImpls { - public static fun eval (Lorg/partiql/lang/eval/ExprValueBagOp;Lorg/partiql/lang/eval/ExprValue;Lorg/partiql/lang/eval/ExprValue;)Lkotlin/sequences/Sequence; -} - -public final class org/partiql/lang/eval/ExprValueExtensionsKt { - public static final field BAG_ANNOTATION Ljava/lang/String; - public static final field DATE_ANNOTATION Ljava/lang/String; - public static final field GRAPH_ANNOTATION Ljava/lang/String; - public static final field MISSING_ANNOTATION Ljava/lang/String; - public static final field TIME_ANNOTATION Ljava/lang/String; - public static final fun asNamed (Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/Named; - public static final fun bigDecimalValue (Lorg/partiql/lang/eval/ExprValue;)Ljava/math/BigDecimal; - public static final fun booleanValue (Lorg/partiql/lang/eval/ExprValue;)Z - public static final fun bytesValue (Lorg/partiql/lang/eval/ExprValue;)[B - public static final fun cast (Lorg/partiql/lang/eval/ExprValue;Lorg/partiql/types/SingleType;Lorg/partiql/lang/eval/TypedOpBehavior;Lorg/partiql/lang/ast/SourceLocationMeta;Ljava/time/ZoneOffset;)Lorg/partiql/lang/eval/ExprValue; - public static final fun compareTo (Lorg/partiql/lang/eval/ExprValue;Lorg/partiql/lang/eval/ExprValue;)I - public static final fun dateValue (Lorg/partiql/lang/eval/ExprValue;)Ljava/time/LocalDate; - public static final fun distinct (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; - public static final fun exprEquals (Lorg/partiql/lang/eval/ExprValue;Lorg/partiql/lang/eval/ExprValue;)Z - public static final fun getAddress (Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/ExprValue; - public static final fun getDEFAULT_COMPARATOR ()Lorg/partiql/lang/eval/NaturalExprValueComparators; - public static final fun getName (Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/ExprValue; - public static final fun getOrderedNames (Lorg/partiql/lang/eval/ExprValue;)Ljava/util/List; - public static final fun intValue (Lorg/partiql/lang/eval/ExprValue;)I - public static final fun longValue (Lorg/partiql/lang/eval/ExprValue;)J - public static final fun multiplicities (Lkotlin/sequences/Sequence;)Ljava/util/TreeMap; - public static final fun namedValue (Lorg/partiql/lang/eval/ExprValue;Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/ExprValue; - public static final fun numberValue (Lorg/partiql/lang/eval/ExprValue;)Ljava/lang/Number; - public static final fun orderedNamesValue (Lorg/partiql/lang/eval/ExprValue;Ljava/util/List;)Lorg/partiql/lang/eval/ExprValue; - public static final fun rangeOver (Lorg/partiql/lang/eval/ExprValue;)Ljava/lang/Iterable; - public static final fun stringValue (Lorg/partiql/lang/eval/ExprValue;)Ljava/lang/String; - public static final fun stringify (Lorg/partiql/lang/eval/ExprValue;)Ljava/lang/String; - public static final fun timeValue (Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/time/Time; - public static final fun timestampValue (Lorg/partiql/lang/eval/ExprValue;)Lcom/amazon/ion/Timestamp; - public static final fun toIonValue (Lorg/partiql/lang/eval/ExprValue;Lcom/amazon/ion/IonSystem;)Lcom/amazon/ion/IonValue; - public static final fun unnamedValue (Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/ExprValue; -} - -public final class org/partiql/lang/eval/ExprValueType : java/lang/Enum { - public static final field BAG Lorg/partiql/lang/eval/ExprValueType; - public static final field BLOB Lorg/partiql/lang/eval/ExprValueType; - public static final field BOOL Lorg/partiql/lang/eval/ExprValueType; - public static final field CLOB Lorg/partiql/lang/eval/ExprValueType; - public static final field Companion Lorg/partiql/lang/eval/ExprValueType$Companion; - public static final field DATE Lorg/partiql/lang/eval/ExprValueType; - public static final field DECIMAL Lorg/partiql/lang/eval/ExprValueType; - public static final field FLOAT Lorg/partiql/lang/eval/ExprValueType; - public static final field GRAPH Lorg/partiql/lang/eval/ExprValueType; - public static final field INT Lorg/partiql/lang/eval/ExprValueType; - public static final field LIST Lorg/partiql/lang/eval/ExprValueType; - public static final field MISSING Lorg/partiql/lang/eval/ExprValueType; - public static final field NULL Lorg/partiql/lang/eval/ExprValueType; - public static final field SEXP Lorg/partiql/lang/eval/ExprValueType; - public static final field STRING Lorg/partiql/lang/eval/ExprValueType; - public static final field STRUCT Lorg/partiql/lang/eval/ExprValueType; - public static final field SYMBOL Lorg/partiql/lang/eval/ExprValueType; - public static final field TIME Lorg/partiql/lang/eval/ExprValueType; - public static final field TIMESTAMP Lorg/partiql/lang/eval/ExprValueType; - public final fun isDirectlyComparableTo (Lorg/partiql/lang/eval/ExprValueType;)Z - public final fun isLob ()Z - public final fun isNumber ()Z - public final fun isRangedFrom ()Z - public final fun isScalar ()Z - public final fun isSequence ()Z - public final fun isText ()Z - public final fun isUnknown ()Z - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/ExprValueType; - public static fun values ()[Lorg/partiql/lang/eval/ExprValueType; -} - -public final class org/partiql/lang/eval/ExprValueType$Companion { - public final fun fromIonType (Lcom/amazon/ion/IonType;)Lorg/partiql/lang/eval/ExprValueType; -} - -public abstract interface class org/partiql/lang/eval/Expression { - public abstract fun eval (Lorg/partiql/lang/eval/EvaluationSession;)Lorg/partiql/lang/eval/ExprValue; - public abstract fun evaluate (Lorg/partiql/lang/eval/EvaluationSession;)Lorg/partiql/lang/eval/PartiQLResult; - public abstract fun getCoverageStructure ()Lorg/partiql/lang/eval/CoverageStructure; -} - -public final class org/partiql/lang/eval/FunctionNotFoundException : java/lang/Exception { - public fun (Ljava/lang/String;)V -} - -public final class org/partiql/lang/eval/MapBindings : org/partiql/lang/eval/Bindings { - public fun (Ljava/util/Map;)V - public fun get (Lorg/partiql/lang/eval/BindingName;)Ljava/lang/Object; - public final fun getOriginalCaseMap ()Ljava/util/Map; -} - -public abstract interface class org/partiql/lang/eval/Named { - public abstract fun getName ()Lorg/partiql/lang/eval/ExprValue; -} - -public final class org/partiql/lang/eval/NaturalExprValueComparators : java/lang/Enum, java/util/Comparator { - public static final field Companion Lorg/partiql/lang/eval/NaturalExprValueComparators$Companion; - public static final field NULLS_FIRST_ASC Lorg/partiql/lang/eval/NaturalExprValueComparators; - public static final field NULLS_FIRST_DESC Lorg/partiql/lang/eval/NaturalExprValueComparators; - public static final field NULLS_LAST_ASC Lorg/partiql/lang/eval/NaturalExprValueComparators; - public static final field NULLS_LAST_DESC Lorg/partiql/lang/eval/NaturalExprValueComparators; - public synthetic fun compare (Ljava/lang/Object;Ljava/lang/Object;)I - public fun compare (Lorg/partiql/lang/eval/ExprValue;Lorg/partiql/lang/eval/ExprValue;)I - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/NaturalExprValueComparators; - public static fun values ()[Lorg/partiql/lang/eval/NaturalExprValueComparators; -} - -public final class org/partiql/lang/eval/NaturalExprValueComparators$Companion { -} - -public abstract interface class org/partiql/lang/eval/OrderedBindNames { - public abstract fun getOrderedNames ()Ljava/util/List; -} - -public abstract interface class org/partiql/lang/eval/OrdinalBindings { - public static final field Companion Lorg/partiql/lang/eval/OrdinalBindings$Companion; - public abstract fun get (I)Lorg/partiql/lang/eval/ExprValue; - public static fun ofList (Ljava/util/List;)Lorg/partiql/lang/eval/OrdinalBindings; -} - -public final class org/partiql/lang/eval/OrdinalBindings$Companion { - public final fun getEMPTY ()Lorg/partiql/lang/eval/OrdinalBindings; - public final fun ofList (Ljava/util/List;)Lorg/partiql/lang/eval/OrdinalBindings; -} - -public abstract class org/partiql/lang/eval/PartiQLResult { - public abstract fun getCoverageData ()Lorg/partiql/lang/eval/CoverageData; - public abstract fun getCoverageStructure ()Lorg/partiql/lang/eval/CoverageStructure; -} - -public final class org/partiql/lang/eval/PartiQLResult$Delete : org/partiql/lang/eval/PartiQLResult { - public fun (Ljava/lang/String;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getCoverageData ()Lorg/partiql/lang/eval/CoverageData; - public fun getCoverageStructure ()Lorg/partiql/lang/eval/CoverageStructure; - public final fun getRows ()Ljava/lang/Iterable; - public final fun getTarget ()Ljava/lang/String; -} - -public abstract class org/partiql/lang/eval/PartiQLResult$Explain : org/partiql/lang/eval/PartiQLResult { -} - -public final class org/partiql/lang/eval/PartiQLResult$Explain$Domain : org/partiql/lang/eval/PartiQLResult$Explain { - public fun (Lorg/partiql/pig/runtime/DomainNode;Ljava/lang/String;)V - public final fun component1 ()Lorg/partiql/pig/runtime/DomainNode; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Lorg/partiql/pig/runtime/DomainNode;Ljava/lang/String;)Lorg/partiql/lang/eval/PartiQLResult$Explain$Domain; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/PartiQLResult$Explain$Domain;Lorg/partiql/pig/runtime/DomainNode;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/eval/PartiQLResult$Explain$Domain; - public fun equals (Ljava/lang/Object;)Z - public fun getCoverageData ()Lorg/partiql/lang/eval/CoverageData; - public fun getCoverageStructure ()Lorg/partiql/lang/eval/CoverageStructure; - public final fun getFormat ()Ljava/lang/String; - public final fun getValue ()Lorg/partiql/pig/runtime/DomainNode; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/PartiQLResult$Insert : org/partiql/lang/eval/PartiQLResult { - public fun (Ljava/lang/String;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getCoverageData ()Lorg/partiql/lang/eval/CoverageData; - public fun getCoverageStructure ()Lorg/partiql/lang/eval/CoverageStructure; - public final fun getRows ()Ljava/lang/Iterable; - public final fun getTarget ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/PartiQLResult$Replace : org/partiql/lang/eval/PartiQLResult { - public fun (Ljava/lang/String;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getCoverageData ()Lorg/partiql/lang/eval/CoverageData; - public fun getCoverageStructure ()Lorg/partiql/lang/eval/CoverageStructure; - public final fun getRows ()Ljava/lang/Iterable; - public final fun getTarget ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/PartiQLResult$Value : org/partiql/lang/eval/PartiQLResult { - public fun (Lorg/partiql/lang/eval/ExprValue;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V - public synthetic fun (Lorg/partiql/lang/eval/ExprValue;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getCoverageData ()Lorg/partiql/lang/eval/CoverageData; - public fun getCoverageStructure ()Lorg/partiql/lang/eval/CoverageStructure; - public final fun getValue ()Lorg/partiql/lang/eval/ExprValue; -} - -public abstract interface class org/partiql/lang/eval/PartiQLStatement { - public abstract fun eval (Lorg/partiql/lang/eval/EvaluationSession;)Lorg/partiql/lang/eval/PartiQLResult; -} - -public abstract interface class org/partiql/lang/eval/PartiQLStatementAsync { - public abstract fun eval (Lorg/partiql/lang/eval/EvaluationSession;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class org/partiql/lang/eval/PartiqlAstExtensionsKt { - public static final fun extractColumnAlias (Lorg/partiql/lang/domains/PartiqlAst$Expr$Path;I)Ljava/lang/String; - public static final fun extractColumnAlias (Lorg/partiql/lang/domains/PartiqlAst$Expr;I)Ljava/lang/String; - public static final fun getStartingSourceLocationMeta (Lorg/partiql/lang/domains/PartiqlAst$Expr;)Lorg/partiql/lang/ast/SourceLocationMeta; -} - -public final class org/partiql/lang/eval/ProjectionIterationBehavior : java/lang/Enum { - public static final field FILTER_MISSING Lorg/partiql/lang/eval/ProjectionIterationBehavior; - public static final field UNFILTERED Lorg/partiql/lang/eval/ProjectionIterationBehavior; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/ProjectionIterationBehavior; - public static fun values ()[Lorg/partiql/lang/eval/ProjectionIterationBehavior; -} - -public final class org/partiql/lang/eval/RequiredArgs : org/partiql/lang/eval/Arguments { - public fun (Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lorg/partiql/lang/eval/RequiredArgs; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/RequiredArgs;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/eval/RequiredArgs; - public fun equals (Ljava/lang/Object;)Z - public final fun getRequired ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/RequiredWithOptional : org/partiql/lang/eval/Arguments { - public fun (Ljava/util/List;Lorg/partiql/lang/eval/ExprValue;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Lorg/partiql/lang/eval/ExprValue; - public final fun copy (Ljava/util/List;Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/RequiredWithOptional; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/RequiredWithOptional;Ljava/util/List;Lorg/partiql/lang/eval/ExprValue;ILjava/lang/Object;)Lorg/partiql/lang/eval/RequiredWithOptional; - public fun equals (Ljava/lang/Object;)Z - public final fun getOpt ()Lorg/partiql/lang/eval/ExprValue; - public final fun getRequired ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/RequiredWithVariadic : org/partiql/lang/eval/Arguments { - public fun (Ljava/util/List;Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/List; - public final fun copy (Ljava/util/List;Ljava/util/List;)Lorg/partiql/lang/eval/RequiredWithVariadic; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/RequiredWithVariadic;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/eval/RequiredWithVariadic; - public fun equals (Ljava/lang/Object;)Z - public final fun getRequired ()Ljava/util/List; - public final fun getVariadic ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class org/partiql/lang/eval/Scalar { - public static final field Companion Lorg/partiql/lang/eval/Scalar$Companion; - public static final field EMPTY Lorg/partiql/lang/eval/Scalar; - public abstract fun booleanValue ()Ljava/lang/Boolean; - public abstract fun bytesValue ()[B - public abstract fun dateValue ()Ljava/time/LocalDate; - public abstract fun numberValue ()Ljava/lang/Number; - public abstract fun stringValue ()Ljava/lang/String; - public abstract fun timeValue ()Lorg/partiql/lang/eval/time/Time; - public abstract fun timestampValue ()Lcom/amazon/ion/Timestamp; -} - -public final class org/partiql/lang/eval/Scalar$Companion { -} - -public final class org/partiql/lang/eval/Scalar$DefaultImpls { - public static fun booleanValue (Lorg/partiql/lang/eval/Scalar;)Ljava/lang/Boolean; - public static fun bytesValue (Lorg/partiql/lang/eval/Scalar;)[B - public static fun dateValue (Lorg/partiql/lang/eval/Scalar;)Ljava/time/LocalDate; - public static fun numberValue (Lorg/partiql/lang/eval/Scalar;)Ljava/lang/Number; - public static fun stringValue (Lorg/partiql/lang/eval/Scalar;)Ljava/lang/String; - public static fun timeValue (Lorg/partiql/lang/eval/Scalar;)Lorg/partiql/lang/eval/time/Time; - public static fun timestampValue (Lorg/partiql/lang/eval/Scalar;)Lcom/amazon/ion/Timestamp; -} - -public final class org/partiql/lang/eval/StandardNamesKt { - public static final fun syntheticColumnName (I)Ljava/lang/String; -} - -public final class org/partiql/lang/eval/StructOrdering : java/lang/Enum { - public static final field ORDERED Lorg/partiql/lang/eval/StructOrdering; - public static final field UNORDERED Lorg/partiql/lang/eval/StructOrdering; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/StructOrdering; - public static fun values ()[Lorg/partiql/lang/eval/StructOrdering; -} - -public final class org/partiql/lang/eval/ThunkOptions { - public static final field Companion Lorg/partiql/lang/eval/ThunkOptions$Companion; - public static final fun builder ()Lorg/partiql/lang/eval/ThunkOptions$Builder; - public final fun component1 ()Lkotlin/jvm/functions/Function2; - public final fun component2 ()Lkotlin/jvm/functions/Function2; - public final fun component3 ()Lorg/partiql/lang/eval/ThunkReturnTypeAssertions; - public final fun copy (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lorg/partiql/lang/eval/ThunkReturnTypeAssertions;)Lorg/partiql/lang/eval/ThunkOptions; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/ThunkOptions;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lorg/partiql/lang/eval/ThunkReturnTypeAssertions;ILjava/lang/Object;)Lorg/partiql/lang/eval/ThunkOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getHandleExceptionForLegacyMode ()Lkotlin/jvm/functions/Function2; - public final fun getHandleExceptionForPermissiveMode ()Lkotlin/jvm/functions/Function2; - public final fun getThunkReturnTypeAssertions ()Lorg/partiql/lang/eval/ThunkReturnTypeAssertions; - public fun hashCode ()I - public static final fun standard ()Lorg/partiql/lang/eval/ThunkOptions; - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/ThunkOptions$Builder { - public fun ()V - public final fun build ()Lorg/partiql/lang/eval/ThunkOptions; - public final fun evaluationTimeTypeChecks (Lorg/partiql/lang/eval/ThunkReturnTypeAssertions;)Lorg/partiql/lang/eval/ThunkOptions$Builder; - public final fun handleExceptionForLegacyMode (Lkotlin/jvm/functions/Function2;)Lorg/partiql/lang/eval/ThunkOptions$Builder; - public final fun handleExceptionForPermissiveMode (Lkotlin/jvm/functions/Function2;)Lorg/partiql/lang/eval/ThunkOptions$Builder; -} - -public final class org/partiql/lang/eval/ThunkOptions$Companion { - public final fun build (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/eval/ThunkOptions; - public final fun builder ()Lorg/partiql/lang/eval/ThunkOptions$Builder; - public final fun standard ()Lorg/partiql/lang/eval/ThunkOptions; -} - -public final class org/partiql/lang/eval/ThunkReturnTypeAssertions : java/lang/Enum { - public static final field DISABLED Lorg/partiql/lang/eval/ThunkReturnTypeAssertions; - public static final field ENABLED Lorg/partiql/lang/eval/ThunkReturnTypeAssertions; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/ThunkReturnTypeAssertions; - public static fun values ()[Lorg/partiql/lang/eval/ThunkReturnTypeAssertions; -} - -public final class org/partiql/lang/eval/TypedOpBehavior : java/lang/Enum { - public static final field HONOR_PARAMETERS Lorg/partiql/lang/eval/TypedOpBehavior; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/TypedOpBehavior; - public static fun values ()[Lorg/partiql/lang/eval/TypedOpBehavior; -} - -public final class org/partiql/lang/eval/TypingMode : java/lang/Enum { - public static final field LEGACY Lorg/partiql/lang/eval/TypingMode; - public static final field PERMISSIVE Lorg/partiql/lang/eval/TypingMode; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/TypingMode; - public static fun values ()[Lorg/partiql/lang/eval/TypingMode; -} - -public final class org/partiql/lang/eval/UndefinedVariableBehavior : java/lang/Enum { - public static final field ERROR Lorg/partiql/lang/eval/UndefinedVariableBehavior; - public static final field MISSING Lorg/partiql/lang/eval/UndefinedVariableBehavior; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/UndefinedVariableBehavior; - public static fun values ()[Lorg/partiql/lang/eval/UndefinedVariableBehavior; -} - -public abstract class org/partiql/lang/eval/VisitorTransformMode : java/lang/Enum { - public static final field DEFAULT Lorg/partiql/lang/eval/VisitorTransformMode; - public static final field NONE Lorg/partiql/lang/eval/VisitorTransformMode; - public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/VisitorTransformMode; - public static fun values ()[Lorg/partiql/lang/eval/VisitorTransformMode; -} - -public final class org/partiql/lang/eval/binding/Alias { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/partiql/lang/eval/binding/Alias; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/binding/Alias;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/eval/binding/Alias; - public fun equals (Ljava/lang/Object;)Z - public final fun getAsName ()Ljava/lang/String; - public final fun getAtName ()Ljava/lang/String; - public final fun getByName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class org/partiql/lang/eval/binding/LocalsBinder { - public fun ()V - public final fun bindLocals (Ljava/util/List;)Lorg/partiql/lang/eval/Bindings; - public abstract fun binderForName (Lorg/partiql/lang/eval/BindingName;)Lkotlin/jvm/functions/Function1; -} - -public final class org/partiql/lang/eval/binding/LocalsBinderKt { - public static final fun localsBinder (Ljava/util/List;Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/binding/LocalsBinder; -} - -public final class org/partiql/lang/eval/builtins/DynamicLookupExprFunction : org/partiql/lang/eval/ExprFunction { - public fun ()V - public fun callWithOptional (Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/lang/eval/ExprValue; - public fun callWithRequired (Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;)Lorg/partiql/lang/eval/ExprValue; - public fun callWithVariadic (Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;Ljava/util/List;)Lorg/partiql/lang/eval/ExprValue; - public fun getSignature ()Lorg/partiql/lang/types/FunctionSignature; -} - -public final class org/partiql/lang/eval/builtins/DynamicLookupExprFunctionKt { - public static final field DYNAMIC_LOOKUP_FUNCTION_NAME Ljava/lang/String; -} - -public abstract interface class org/partiql/lang/eval/builtins/storedprocedure/StoredProcedure { - public abstract fun call (Lorg/partiql/lang/eval/EvaluationSession;Ljava/util/List;)Lorg/partiql/lang/eval/ExprValue; - public abstract fun getSignature ()Lorg/partiql/lang/eval/builtins/storedprocedure/StoredProcedureSignature; -} - -public final class org/partiql/lang/eval/builtins/storedprocedure/StoredProcedureSignature { - public fun (Ljava/lang/String;I)V - public fun (Ljava/lang/String;Lkotlin/ranges/IntRange;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lkotlin/ranges/IntRange; - public final fun copy (Ljava/lang/String;Lkotlin/ranges/IntRange;)Lorg/partiql/lang/eval/builtins/storedprocedure/StoredProcedureSignature; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/builtins/storedprocedure/StoredProcedureSignature;Ljava/lang/String;Lkotlin/ranges/IntRange;ILjava/lang/Object;)Lorg/partiql/lang/eval/builtins/storedprocedure/StoredProcedureSignature; - public fun equals (Ljava/lang/Object;)Z - public final fun getArity ()Lkotlin/ranges/IntRange; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/builtins/timestamp/TimestampTemporalAccessor : java/time/temporal/TemporalAccessor { - public fun (Lcom/amazon/ion/Timestamp;)V - public fun getLong (Ljava/time/temporal/TemporalField;)J - public final fun getTs ()Lcom/amazon/ion/Timestamp; - public fun isSupported (Ljava/time/temporal/TemporalField;)Z -} - -public final class org/partiql/lang/eval/internal/ext/ExprValueExtKt { - public static final fun distinct (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; - public static final fun rangeOver (Lorg/partiql/lang/eval/ExprValue;)Ljava/lang/Iterable; -} - -public final class org/partiql/lang/eval/io/DelimitedValues { - public static final field INSTANCE Lorg/partiql/lang/eval/io/DelimitedValues; - public static final fun exprValue (Ljava/io/Reader;Lorg/apache/commons/csv/CSVFormat;Lorg/partiql/lang/eval/io/DelimitedValues$ConversionMode;)Lorg/partiql/lang/eval/ExprValue; - public static final fun writeTo (Lcom/amazon/ion/IonSystem;Ljava/io/Writer;Lorg/partiql/lang/eval/ExprValue;CLjava/lang/String;Z)V -} - -public final class org/partiql/lang/eval/io/DelimitedValues$ConversionMode : java/lang/Enum { - public static final field AUTO Lorg/partiql/lang/eval/io/DelimitedValues$ConversionMode; - public static final field NONE Lorg/partiql/lang/eval/io/DelimitedValues$ConversionMode; - public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public abstract fun convert (Ljava/lang/String;)Lorg/partiql/lang/eval/ExprValue; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/io/DelimitedValues$ConversionMode; - public static fun values ()[Lorg/partiql/lang/eval/io/DelimitedValues$ConversionMode; -} - -public final class org/partiql/lang/eval/physical/EvaluatorState { - public fun (Lorg/partiql/lang/eval/EvaluationSession;[Lorg/partiql/lang/eval/ExprValue;)V - public final fun getSession ()Lorg/partiql/lang/eval/EvaluationSession; -} - -public final class org/partiql/lang/eval/physical/VariableBinding { - public fun (Lkotlin/jvm/functions/Function2;Lorg/partiql/lang/eval/physical/operators/ValueExpression;)V - public final fun getExpr ()Lorg/partiql/lang/eval/physical/operators/ValueExpression; - public final fun getSetFunc ()Lkotlin/jvm/functions/Function2; -} - -public final class org/partiql/lang/eval/physical/VariableBindingAsync { - public fun (Lkotlin/jvm/functions/Function2;Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;)V - public final fun getExpr ()Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync; - public final fun getSetFunc ()Lkotlin/jvm/functions/Function2; -} - -public abstract class org/partiql/lang/eval/physical/operators/AggregateOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/eval/physical/operators/RelationExpression;Lorg/partiql/lang/domains/PartiqlPhysical$GroupingStrategy;Ljava/util/List;Ljava/util/List;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/AggregateOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync;Lorg/partiql/lang/domains/PartiqlPhysical$GroupingStrategy;Ljava/util/List;Ljava/util/List;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public final class org/partiql/lang/eval/physical/operators/CompiledAggregateFunction { - public fun (Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lorg/partiql/lang/eval/physical/operators/ValueExpression;Lorg/partiql/lang/domains/PartiqlPhysical$SetQuantifier;)V - public final fun getName ()Ljava/lang/String; - public final fun getQuantifier ()Lorg/partiql/lang/domains/PartiqlPhysical$SetQuantifier; - public final fun getSetAggregateVal ()Lkotlin/jvm/functions/Function2; - public final fun getValue ()Lorg/partiql/lang/eval/physical/operators/ValueExpression; -} - -public final class org/partiql/lang/eval/physical/operators/CompiledAggregateFunctionAsync { - public fun (Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;Lorg/partiql/lang/domains/PartiqlPhysical$SetQuantifier;)V - public final fun getName ()Ljava/lang/String; - public final fun getQuantifier ()Lorg/partiql/lang/domains/PartiqlPhysical$SetQuantifier; - public final fun getSetAggregateVal ()Lkotlin/jvm/functions/Function2; - public final fun getValue ()Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync; -} - -public final class org/partiql/lang/eval/physical/operators/CompiledGroupKey { - public fun (Lkotlin/jvm/functions/Function2;Lorg/partiql/lang/eval/physical/operators/ValueExpression;Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl;)V - public final fun getSetGroupKeyVal ()Lkotlin/jvm/functions/Function2; - public final fun getValue ()Lorg/partiql/lang/eval/physical/operators/ValueExpression; - public final fun getVariable ()Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl; -} - -public final class org/partiql/lang/eval/physical/operators/CompiledGroupKeyAsync { - public fun (Lkotlin/jvm/functions/Function2;Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl;)V - public final fun getSetGroupKeyVal ()Lkotlin/jvm/functions/Function2; - public final fun getValue ()Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync; - public final fun getVariable ()Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl; -} - -public final class org/partiql/lang/eval/physical/operators/CompiledSortKey { - public fun (Lorg/partiql/lang/eval/NaturalExprValueComparators;Lorg/partiql/lang/eval/physical/operators/ValueExpression;)V - public final fun getComparator ()Lorg/partiql/lang/eval/NaturalExprValueComparators; - public final fun getValue ()Lorg/partiql/lang/eval/physical/operators/ValueExpression; -} - -public final class org/partiql/lang/eval/physical/operators/CompiledSortKeyAsync { - public fun (Lorg/partiql/lang/eval/NaturalExprValueComparators;Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;)V - public final fun getComparator ()Lorg/partiql/lang/eval/NaturalExprValueComparators; - public final fun getValue ()Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync; -} - -public final class org/partiql/lang/eval/physical/operators/CompiledWindowFunction { - public fun (Lorg/partiql/lang/eval/physical/window/WindowFunction;Ljava/util/List;Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl;)V - public final fun getFunc ()Lorg/partiql/lang/eval/physical/window/WindowFunction; - public final fun getParameters ()Ljava/util/List; - public final fun getWindowVarDecl ()Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl; -} - -public final class org/partiql/lang/eval/physical/operators/CompiledWindowFunctionAsync { - public fun (Lorg/partiql/lang/eval/physical/window/NavigationWindowFunctionAsync;Ljava/util/List;Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl;)V - public final fun getFunc ()Lorg/partiql/lang/eval/physical/window/NavigationWindowFunctionAsync; - public final fun getParameters ()Ljava/util/List; - public final fun getWindowVarDecl ()Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl; -} - -public abstract class org/partiql/lang/eval/physical/operators/FilterRelationalOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/ValueExpression;Lorg/partiql/lang/eval/physical/operators/RelationExpression;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/FilterRelationalOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/JoinRelationalOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/domains/PartiqlPhysical$JoinType;Lorg/partiql/lang/eval/physical/operators/RelationExpression;Lorg/partiql/lang/eval/physical/operators/RelationExpression;Lorg/partiql/lang/eval/physical/operators/ValueExpression;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/JoinRelationalOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/domains/PartiqlPhysical$JoinType;Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync;Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync;Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/LetRelationalOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/RelationExpression;Ljava/util/List;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/LetRelationalOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync;Ljava/util/List;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/LimitRelationalOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/ValueExpression;Lorg/partiql/lang/eval/physical/operators/RelationExpression;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/LimitRelationalOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/OffsetRelationalOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/ValueExpression;Lorg/partiql/lang/eval/physical/operators/RelationExpression;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/OffsetRelationalOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/ProjectRelationalOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lkotlin/jvm/functions/Function2;Ljava/util/List;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/ProjectRelationalOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lkotlin/jvm/functions/Function2;Ljava/util/List;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract interface class org/partiql/lang/eval/physical/operators/RelationExpression { - public abstract fun evaluate (Lorg/partiql/lang/eval/physical/EvaluatorState;)Lorg/partiql/lang/eval/relation/RelationIterator; -} - -public abstract interface class org/partiql/lang/eval/physical/operators/RelationExpressionAsync { - public abstract fun evaluate (Lorg/partiql/lang/eval/physical/EvaluatorState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public abstract fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public final class org/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey { - public fun (Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind;Ljava/lang/String;)V - public final fun component1 ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind;Ljava/lang/String;)Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey;Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getOperator ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/physical/operators/RelationalOperatorKind : java/lang/Enum { - public static final field AGGREGATE Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field FILTER Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field JOIN Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field LET Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field LIMIT Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field OFFSET Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field PROJECT Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field SCAN Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field SORT Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field UNPIVOT Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static final field WINDOW Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; - public static fun values ()[Lorg/partiql/lang/eval/physical/operators/RelationalOperatorKind; -} - -public abstract class org/partiql/lang/eval/physical/operators/ScanRelationalOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/ValueExpression;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/ScanRelationalOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/domains/PartiqlPhysical$Impl;Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/SortOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Ljava/util/List;Lorg/partiql/lang/eval/physical/operators/RelationExpression;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/SortOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Ljava/util/List;Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/UnpivotOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/eval/physical/operators/ValueExpression;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/eval/physical/operators/ValueExpressionAsync;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract interface class org/partiql/lang/eval/physical/operators/ValueExpression { - public abstract fun getSourceLocation ()Lorg/partiql/lang/ast/SourceLocationMeta; - public abstract fun invoke (Lorg/partiql/lang/eval/physical/EvaluatorState;)Lorg/partiql/lang/eval/ExprValue; -} - -public abstract interface class org/partiql/lang/eval/physical/operators/ValueExpressionAsync { - public abstract fun getSourceLocation ()Lorg/partiql/lang/ast/SourceLocationMeta; - public abstract fun invoke (Lorg/partiql/lang/eval/physical/EvaluatorState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract class org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactory : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/eval/physical/operators/RelationExpression;Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lorg/partiql/lang/eval/physical/operators/RelationExpression; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryAsync : org/partiql/lang/eval/physical/operators/RelationalOperatorFactory { - public fun (Ljava/lang/String;)V - public abstract fun create (Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync;Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lorg/partiql/lang/eval/physical/operators/RelationExpressionAsync; - public final fun getKey ()Lorg/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey; -} - -public abstract class org/partiql/lang/eval/physical/window/NavigationWindowFunction : org/partiql/lang/eval/physical/window/WindowFunction { - public field currentPartition Ljava/util/List; - public fun ()V - public final fun getCurrentPartition ()Ljava/util/List; - public abstract fun processRow (Lorg/partiql/lang/eval/physical/EvaluatorState;Ljava/util/List;I)Lorg/partiql/lang/eval/ExprValue; - public fun processRow (Lorg/partiql/lang/eval/physical/EvaluatorState;Ljava/util/List;Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl;)V - public fun reset (Ljava/util/List;)V - public final fun setCurrentPartition (Ljava/util/List;)V -} - -public abstract class org/partiql/lang/eval/physical/window/NavigationWindowFunctionAsync : org/partiql/lang/eval/physical/window/WindowFunctionAsync { - public field currentPartition Ljava/util/List; - public fun ()V - public final fun getCurrentPartition ()Ljava/util/List; - public abstract fun processRow (Lorg/partiql/lang/eval/physical/EvaluatorState;Ljava/util/List;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun processRow (Lorg/partiql/lang/eval/physical/EvaluatorState;Ljava/util/List;Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun reset (Ljava/util/List;)V - public final fun setCurrentPartition (Ljava/util/List;)V -} - -public abstract interface class org/partiql/lang/eval/physical/window/WindowFunction { - public abstract fun getName ()Ljava/lang/String; - public abstract fun processRow (Lorg/partiql/lang/eval/physical/EvaluatorState;Ljava/util/List;Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl;)V - public abstract fun reset (Ljava/util/List;)V -} - -public abstract interface class org/partiql/lang/eval/physical/window/WindowFunctionAsync { - public abstract fun getName ()Ljava/lang/String; - public abstract fun processRow (Lorg/partiql/lang/eval/physical/EvaluatorState;Ljava/util/List;Lorg/partiql/lang/domains/PartiqlPhysical$VarDecl;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun reset (Ljava/util/List;)V -} - -public abstract interface annotation class org/partiql/lang/eval/relation/RelationDsl : java/lang/annotation/Annotation { -} - -public abstract interface class org/partiql/lang/eval/relation/RelationIterator { - public abstract fun getRelType ()Lorg/partiql/lang/eval/relation/RelationType; - public abstract fun nextRow ()Z -} - -public final class org/partiql/lang/eval/relation/RelationKt { - public static final fun relation (Lorg/partiql/lang/eval/relation/RelationType;Lkotlin/jvm/functions/Function2;)Lorg/partiql/lang/eval/relation/RelationIterator; -} - -public abstract interface class org/partiql/lang/eval/relation/RelationScope { - public abstract fun yield (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun yieldAll (Lorg/partiql/lang/eval/relation/RelationIterator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class org/partiql/lang/eval/relation/RelationType : java/lang/Enum { - public static final field BAG Lorg/partiql/lang/eval/relation/RelationType; - public static final field LIST Lorg/partiql/lang/eval/relation/RelationType; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/relation/RelationType; - public static fun values ()[Lorg/partiql/lang/eval/relation/RelationType; -} - -public final class org/partiql/lang/eval/time/Time { - public static final field Companion Lorg/partiql/lang/eval/time/Time$Companion; - public synthetic fun (Ljava/time/LocalTime;ILjava/time/ZoneOffset;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/time/LocalTime; - public final fun component2 ()I - public final fun component3 ()Ljava/time/ZoneOffset; - public final fun copy (Ljava/time/LocalTime;ILjava/time/ZoneOffset;)Lorg/partiql/lang/eval/time/Time; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/time/Time;Ljava/time/LocalTime;ILjava/time/ZoneOffset;ILjava/lang/Object;)Lorg/partiql/lang/eval/time/Time; - public fun equals (Ljava/lang/Object;)Z - public final fun getLocalTime ()Ljava/time/LocalTime; - public final fun getOffsetTime ()Ljava/time/OffsetTime; - public final fun getPrecision ()I - public final fun getSecondsWithFractionalPart ()Ljava/math/BigDecimal; - public final fun getTimezoneHour ()Ljava/lang/Integer; - public final fun getTimezoneMinute ()Ljava/lang/Integer; - public final fun getZoneOffset ()Ljava/time/ZoneOffset; - public fun hashCode ()I - public final fun isDirectlyComparableTo (Lorg/partiql/lang/eval/time/Time;)Z - public final fun naturalOrderCompareTo (Lorg/partiql/lang/eval/time/Time;)I - public static final fun of (IIIII)Lorg/partiql/lang/eval/time/Time; - public static final fun of (IIIIILjava/lang/Integer;)Lorg/partiql/lang/eval/time/Time; - public static final fun of (Ljava/time/LocalTime;I)Lorg/partiql/lang/eval/time/Time; - public static final fun of (Ljava/time/LocalTime;ILjava/time/ZoneOffset;)Lorg/partiql/lang/eval/time/Time; - public final fun toIonValue (Lcom/amazon/ion/IonSystem;)Lcom/amazon/ion/IonStruct; - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/eval/time/Time$Companion { - public final fun of (IIIII)Lorg/partiql/lang/eval/time/Time; - public final fun of (IIIIILjava/lang/Integer;)Lorg/partiql/lang/eval/time/Time; - public final fun of (Ljava/time/LocalTime;I)Lorg/partiql/lang/eval/time/Time; - public final fun of (Ljava/time/LocalTime;ILjava/time/ZoneOffset;)Lorg/partiql/lang/eval/time/Time; - public static synthetic fun of$default (Lorg/partiql/lang/eval/time/Time$Companion;IIIIILjava/lang/Integer;ILjava/lang/Object;)Lorg/partiql/lang/eval/time/Time; - public static synthetic fun of$default (Lorg/partiql/lang/eval/time/Time$Companion;Ljava/time/LocalTime;ILjava/time/ZoneOffset;ILjava/lang/Object;)Lorg/partiql/lang/eval/time/Time; -} - -public final class org/partiql/lang/eval/visitors/AggregateSupportVisitorTransform : org/partiql/lang/eval/visitors/VisitorTransformBase { - public fun ()V - public fun transformExprCallAgg (Lorg/partiql/lang/domains/PartiqlAst$Expr$CallAgg;)Lorg/partiql/lang/domains/PartiqlAst$Expr; - public fun transformExprSelect (Lorg/partiql/lang/domains/PartiqlAst$Expr$Select;)Lorg/partiql/lang/domains/PartiqlAst$Expr; - public fun transformExprSelect_metas (Lorg/partiql/lang/domains/PartiqlAst$Expr$Select;)Ljava/util/Map; - public fun transformProjectionProjectValue_value (Lorg/partiql/lang/domains/PartiqlAst$Projection$ProjectValue;)Lorg/partiql/lang/domains/PartiqlAst$Expr; -} - -public final class org/partiql/lang/eval/visitors/FromSourceAliasVisitorTransform : org/partiql/lang/eval/visitors/VisitorTransformBase { - public fun ()V - public fun transformExprSelect_from (Lorg/partiql/lang/domains/PartiqlAst$Expr$Select;)Lorg/partiql/lang/domains/PartiqlAst$FromSource; - public fun transformStatementDml_from (Lorg/partiql/lang/domains/PartiqlAst$Statement$Dml;)Lorg/partiql/lang/domains/PartiqlAst$FromSource; -} - -public final class org/partiql/lang/eval/visitors/GroupByItemAliasVisitorTransform : org/partiql/lang/eval/visitors/VisitorTransformBase { - public fun ()V - public fun (I)V - public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getNestLevel ()I - public final fun setNestLevel (I)V - public fun transformExprSelect (Lorg/partiql/lang/domains/PartiqlAst$Expr$Select;)Lorg/partiql/lang/domains/PartiqlAst$Expr; - public fun transformGroupBy (Lorg/partiql/lang/domains/PartiqlAst$GroupBy;)Lorg/partiql/lang/domains/PartiqlAst$GroupBy; -} - -public final class org/partiql/lang/eval/visitors/GroupByPathExpressionVisitorTransform : org/partiql/lang/eval/visitors/SubstitutionVisitorTransform { - public static final field Companion Lorg/partiql/lang/eval/visitors/GroupByPathExpressionVisitorTransform$Companion; - public fun ()V - public fun (Ljava/util/Map;)V - public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun transformExprCallAgg (Lorg/partiql/lang/domains/PartiqlAst$Expr$CallAgg;)Lorg/partiql/lang/domains/PartiqlAst$Expr; - public fun transformExprSelect (Lorg/partiql/lang/domains/PartiqlAst$Expr$Select;)Lorg/partiql/lang/domains/PartiqlAst$Expr; -} - -public final class org/partiql/lang/eval/visitors/GroupByPathExpressionVisitorTransform$Companion { - public final fun canBeSubstituted (Lorg/partiql/lang/domains/PartiqlAst$GroupKey;)Z - public final fun collectAliases (Lorg/partiql/lang/domains/PartiqlAst$FromSource;)Ljava/util/List; -} - -public final class org/partiql/lang/eval/visitors/PartiqlAstSanityValidator : org/partiql/lang/domains/PartiqlAst$Visitor { - public fun ()V - public final fun validate (Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/eval/CompileOptions;)V - public static synthetic fun validate$default (Lorg/partiql/lang/eval/visitors/PartiqlAstSanityValidator;Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/eval/CompileOptions;ILjava/lang/Object;)V -} - -public final class org/partiql/lang/eval/visitors/PipelinedVisitorTransform : org/partiql/lang/domains/PartiqlAst$VisitorTransform { - public fun ([Lorg/partiql/lang/domains/PartiqlAst$VisitorTransform;)V - public final fun appendVisitorTransforms ([Lorg/partiql/lang/domains/PartiqlAst$VisitorTransform;)Lorg/partiql/lang/eval/visitors/PipelinedVisitorTransform; - public fun transformStatement (Lorg/partiql/lang/domains/PartiqlAst$Statement;)Lorg/partiql/lang/domains/PartiqlAst$Statement; -} - -public final class org/partiql/lang/eval/visitors/SelectListItemAliasVisitorTransform : org/partiql/lang/eval/visitors/VisitorTransformBase { - public fun ()V - public fun transformProjectionProjectList (Lorg/partiql/lang/domains/PartiqlAst$Projection$ProjectList;)Lorg/partiql/lang/domains/PartiqlAst$Projection; -} - -public final class org/partiql/lang/eval/visitors/SelectStarVisitorTransform : org/partiql/lang/eval/visitors/VisitorTransformBase { - public fun ()V - public fun transformExprSelect (Lorg/partiql/lang/domains/PartiqlAst$Expr$Select;)Lorg/partiql/lang/domains/PartiqlAst$Expr; -} - -public final class org/partiql/lang/eval/visitors/StaticTypeVisitorTransform : org/partiql/lang/eval/visitors/VisitorTransformBase { - public fun (Lcom/amazon/ion/IonSystem;Lorg/partiql/lang/eval/Bindings;Ljava/util/Set;)V - public synthetic fun (Lcom/amazon/ion/IonSystem;Lorg/partiql/lang/eval/Bindings;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun transformStatement (Lorg/partiql/lang/domains/PartiqlAst$Statement;)Lorg/partiql/lang/domains/PartiqlAst$Statement; -} - -public final class org/partiql/lang/eval/visitors/StaticTypeVisitorTransformConstraints : java/lang/Enum { - public static final field PREVENT_GLOBALS_EXCEPT_IN_FROM Lorg/partiql/lang/eval/visitors/StaticTypeVisitorTransformConstraints; - public static final field PREVENT_GLOBALS_IN_NESTED_QUERIES Lorg/partiql/lang/eval/visitors/StaticTypeVisitorTransformConstraints; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/eval/visitors/StaticTypeVisitorTransformConstraints; - public static fun values ()[Lorg/partiql/lang/eval/visitors/StaticTypeVisitorTransformConstraints; -} - -public final class org/partiql/lang/eval/visitors/SubqueryCoercionVisitorTransform : org/partiql/lang/eval/visitors/VisitorTransformBase { - public fun ()V - public fun transformExpr (Lorg/partiql/lang/domains/PartiqlAst$Expr;)Lorg/partiql/lang/domains/PartiqlAst$Expr; -} - -public final class org/partiql/lang/eval/visitors/SubstitutionPair { - public fun (Lorg/partiql/lang/domains/PartiqlAst$Expr;Lorg/partiql/lang/domains/PartiqlAst$Expr;)V - public final fun component1 ()Lorg/partiql/lang/domains/PartiqlAst$Expr; - public final fun component2 ()Lorg/partiql/lang/domains/PartiqlAst$Expr; - public final fun copy (Lorg/partiql/lang/domains/PartiqlAst$Expr;Lorg/partiql/lang/domains/PartiqlAst$Expr;)Lorg/partiql/lang/eval/visitors/SubstitutionPair; - public static synthetic fun copy$default (Lorg/partiql/lang/eval/visitors/SubstitutionPair;Lorg/partiql/lang/domains/PartiqlAst$Expr;Lorg/partiql/lang/domains/PartiqlAst$Expr;ILjava/lang/Object;)Lorg/partiql/lang/eval/visitors/SubstitutionPair; - public fun equals (Ljava/lang/Object;)Z - public final fun getReplacement ()Lorg/partiql/lang/domains/PartiqlAst$Expr; - public final fun getTarget ()Lorg/partiql/lang/domains/PartiqlAst$Expr; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class org/partiql/lang/eval/visitors/SubstitutionVisitorTransform : org/partiql/lang/eval/visitors/VisitorTransformBase { - public fun (Ljava/util/Map;)V - protected final fun getSubstitutions ()Ljava/util/Map; - public fun transformExpr (Lorg/partiql/lang/domains/PartiqlAst$Expr;)Lorg/partiql/lang/domains/PartiqlAst$Expr; -} - -public abstract class org/partiql/lang/eval/visitors/VisitorTransformBase : org/partiql/lang/domains/PartiqlAst$VisitorTransform { - public fun ()V - public final fun transformDataManipulationEvaluationOrder (Lorg/partiql/lang/domains/PartiqlAst$Statement$Dml;)Lorg/partiql/lang/domains/PartiqlAst$Statement; - public fun transformExpr (Lorg/partiql/lang/domains/PartiqlAst$Expr;)Lorg/partiql/lang/domains/PartiqlAst$Expr; - public final fun transformExprSelectEvaluationOrder (Lorg/partiql/lang/domains/PartiqlAst$Expr$Select;)Lorg/partiql/lang/domains/PartiqlAst$Expr; -} - -public final class org/partiql/lang/eval/visitors/VisitorTransformsKt { - public static final fun basicVisitorTransforms ()Lorg/partiql/lang/eval/visitors/PipelinedVisitorTransform; -} - -public final class org/partiql/lang/graph/DirSpec : java/lang/Enum { - public static final field DirLUR Lorg/partiql/lang/graph/DirSpec; - public static final field DirLU_ Lorg/partiql/lang/graph/DirSpec; - public static final field DirL_R Lorg/partiql/lang/graph/DirSpec; - public static final field DirL__ Lorg/partiql/lang/graph/DirSpec; - public static final field Dir_UR Lorg/partiql/lang/graph/DirSpec; - public static final field Dir_U_ Lorg/partiql/lang/graph/DirSpec; - public static final field Dir__R Lorg/partiql/lang/graph/DirSpec; - public final fun getWantLeft ()Z - public final fun getWantRight ()Z - public final fun getWantUndir ()Z - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/graph/DirSpec; - public static fun values ()[Lorg/partiql/lang/graph/DirSpec; -} - -public final class org/partiql/lang/graph/EdgeSpec : org/partiql/lang/graph/ElemSpec { - public fun (Ljava/lang/String;Lorg/partiql/lang/graph/LabelSpec;Lorg/partiql/lang/graph/DirSpec;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lorg/partiql/lang/graph/LabelSpec; - public final fun component3 ()Lorg/partiql/lang/graph/DirSpec; - public final fun copy (Ljava/lang/String;Lorg/partiql/lang/graph/LabelSpec;Lorg/partiql/lang/graph/DirSpec;)Lorg/partiql/lang/graph/EdgeSpec; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/EdgeSpec;Ljava/lang/String;Lorg/partiql/lang/graph/LabelSpec;Lorg/partiql/lang/graph/DirSpec;ILjava/lang/Object;)Lorg/partiql/lang/graph/EdgeSpec; - public fun equals (Ljava/lang/Object;)Z - public fun getBinder ()Ljava/lang/String; - public final fun getDir ()Lorg/partiql/lang/graph/DirSpec; - public final fun getLabel ()Lorg/partiql/lang/graph/LabelSpec; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class org/partiql/lang/graph/ElemSpec { - public abstract fun getBinder ()Ljava/lang/String; -} - -public abstract class org/partiql/lang/graph/ExternalGraphException : java/lang/RuntimeException { - public fun (Ljava/lang/String;)V - public fun getMessage ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/ExternalGraphReader { - public static final field INSTANCE Lorg/partiql/lang/graph/ExternalGraphReader; - public final fun read (Lcom/amazon/ion/IonValue;)Lorg/partiql/lang/graph/SimpleGraph; - public final fun read (Ljava/lang/String;)Lorg/partiql/lang/graph/SimpleGraph; - public final fun validate (Lcom/amazon/ion/IonValue;)V - public final fun validate (Ljava/io/File;)V - public final fun validate (Ljava/lang/String;)V -} - -public final class org/partiql/lang/graph/GpmlTranslator { - public static final field INSTANCE Lorg/partiql/lang/graph/GpmlTranslator; - public final fun normalizeElemList (Ljava/util/List;)Ljava/util/List; - public final fun translateDirection (Lorg/partiql/lang/domains/PartiqlAst$GraphMatchDirection;)Lorg/partiql/lang/graph/DirSpec; - public final fun translateEdgePat (Lorg/partiql/lang/domains/PartiqlAst$GraphMatchPatternPart$Edge;)Lorg/partiql/lang/graph/EdgeSpec; - public final fun translateGpmlPattern (Lorg/partiql/lang/domains/PartiqlAst$GpmlPattern;)Lorg/partiql/lang/graph/MatchSpec; - public final fun translateLabels (Lorg/partiql/lang/domains/PartiqlAst$GraphLabelSpec;)Lorg/partiql/lang/graph/LabelSpec; - public final fun translateNodePat (Lorg/partiql/lang/domains/PartiqlAst$GraphMatchPatternPart$Node;)Lorg/partiql/lang/graph/NodeSpec; - public final fun translatePartPat (Lorg/partiql/lang/domains/PartiqlAst$GraphMatchPatternPart;)Ljava/util/List; - public final fun translatePathPat (Lorg/partiql/lang/domains/PartiqlAst$GraphMatchPattern;)Ljava/util/List; -} - -public abstract interface class org/partiql/lang/graph/Graph { - public abstract fun scanDirectedBlunt (Lkotlin/Triple;)Ljava/util/List; - public abstract fun scanDirectedFlipped (Lkotlin/Triple;)Ljava/util/List; - public abstract fun scanDirectedStraight (Lkotlin/Triple;)Ljava/util/List; - public abstract fun scanNodes (Lorg/partiql/lang/graph/LabelSpec;)Ljava/util/List; - public abstract fun scanUndir (Lkotlin/Triple;)Ljava/util/List; -} - -public abstract interface class org/partiql/lang/graph/Graph$Edge : org/partiql/lang/graph/Graph$Elem { -} - -public abstract interface class org/partiql/lang/graph/Graph$EdgeDirected : org/partiql/lang/graph/Graph$Edge { -} - -public abstract interface class org/partiql/lang/graph/Graph$EdgeUndir : org/partiql/lang/graph/Graph$Edge { -} - -public abstract interface class org/partiql/lang/graph/Graph$Elem { - public abstract fun getLabels ()Ljava/util/Set; - public abstract fun getPayload ()Lorg/partiql/lang/eval/ExprValue; -} - -public abstract interface class org/partiql/lang/graph/Graph$Node : org/partiql/lang/graph/Graph$Elem { -} - -public final class org/partiql/lang/graph/GraphEngine { - public static final field INSTANCE Lorg/partiql/lang/graph/GraphEngine; - public final fun evaluate (Lorg/partiql/lang/graph/Graph;Lorg/partiql/lang/graph/MatchSpec;)Lorg/partiql/lang/graph/MatchResult; - public final fun getMatchingSteps (Lorg/partiql/lang/graph/Graph;Lorg/partiql/lang/graph/StepSpec;)Ljava/util/List; - public final fun joinAdjacentStrides (Lorg/partiql/lang/graph/StrideResult;Lorg/partiql/lang/graph/StrideResult;)Lorg/partiql/lang/graph/StrideResult; - public final fun joinStridesOnBinders (Ljava/util/List;)Lorg/partiql/lang/graph/MatchResult; - public final fun stridesJoinable (Lorg/partiql/lang/graph/StrideSpec;Lorg/partiql/lang/graph/StrideSpec;)Lkotlin/jvm/functions/Function2; -} - -public final class org/partiql/lang/graph/GraphIonException : org/partiql/lang/graph/ExternalGraphException { - public fun (Ljava/lang/String;)V -} - -public final class org/partiql/lang/graph/GraphReadException : org/partiql/lang/graph/ExternalGraphException { - public fun (Ljava/lang/String;)V -} - -public final class org/partiql/lang/graph/GraphValidationException : org/partiql/lang/graph/ExternalGraphException { - public fun (Ljava/lang/String;)V -} - -public abstract class org/partiql/lang/graph/LabelSpec { -} - -public final class org/partiql/lang/graph/LabelSpec$Name : org/partiql/lang/graph/LabelSpec { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lorg/partiql/lang/graph/LabelSpec$Name; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/LabelSpec$Name;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/graph/LabelSpec$Name; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/LabelSpec$Wildcard : org/partiql/lang/graph/LabelSpec { - public static final field INSTANCE Lorg/partiql/lang/graph/LabelSpec$Wildcard; -} - -public final class org/partiql/lang/graph/MatchResult { - public fun (Ljava/util/List;Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/List; - public final fun copy (Ljava/util/List;Ljava/util/List;)Lorg/partiql/lang/graph/MatchResult; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/MatchResult;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/graph/MatchResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getResult ()Ljava/util/List; - public final fun getSpecs ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/MatchSpec { - public fun (Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lorg/partiql/lang/graph/MatchSpec; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/MatchSpec;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/graph/MatchSpec; - public fun equals (Ljava/lang/Object;)Z - public final fun getStrides ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/NodeSpec : org/partiql/lang/graph/ElemSpec { - public fun (Ljava/lang/String;Lorg/partiql/lang/graph/LabelSpec;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lorg/partiql/lang/graph/LabelSpec; - public final fun copy (Ljava/lang/String;Lorg/partiql/lang/graph/LabelSpec;)Lorg/partiql/lang/graph/NodeSpec; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/NodeSpec;Ljava/lang/String;Lorg/partiql/lang/graph/LabelSpec;ILjava/lang/Object;)Lorg/partiql/lang/graph/NodeSpec; - public fun equals (Ljava/lang/Object;)Z - public fun getBinder ()Ljava/lang/String; - public final fun getLabel ()Lorg/partiql/lang/graph/LabelSpec; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/SimpleGraph : org/partiql/lang/graph/Graph { - public static final field Companion Lorg/partiql/lang/graph/SimpleGraph$Companion; - public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;)V - public final fun getDirected ()Ljava/util/List; - public final fun getNodes ()Ljava/util/List; - public final fun getUndir ()Ljava/util/List; - public fun scanDirectedBlunt (Lkotlin/Triple;)Ljava/util/List; - public fun scanDirectedFlipped (Lkotlin/Triple;)Ljava/util/List; - public fun scanDirectedStraight (Lkotlin/Triple;)Ljava/util/List; - public fun scanNodes (Lorg/partiql/lang/graph/LabelSpec;)Ljava/util/List; - public fun scanUndir (Lkotlin/Triple;)Ljava/util/List; -} - -public final class org/partiql/lang/graph/SimpleGraph$Companion { - public final fun getEmpty ()Lorg/partiql/lang/graph/Graph; -} - -public abstract class org/partiql/lang/graph/SimpleGraph$Edge : org/partiql/lang/graph/Graph$Edge { - public fun ()V -} - -public final class org/partiql/lang/graph/SimpleGraph$EdgeDirected : org/partiql/lang/graph/SimpleGraph$Edge, org/partiql/lang/graph/Graph$EdgeDirected { - public fun (Ljava/util/Set;Lorg/partiql/lang/eval/ExprValue;)V - public fun getLabels ()Ljava/util/Set; - public fun getPayload ()Lorg/partiql/lang/eval/ExprValue; -} - -public final class org/partiql/lang/graph/SimpleGraph$EdgeUndir : org/partiql/lang/graph/SimpleGraph$Edge, org/partiql/lang/graph/Graph$EdgeUndir { - public fun (Ljava/util/Set;Lorg/partiql/lang/eval/ExprValue;)V - public fun getLabels ()Ljava/util/Set; - public fun getPayload ()Lorg/partiql/lang/eval/ExprValue; -} - -public final class org/partiql/lang/graph/SimpleGraph$Node : org/partiql/lang/graph/Graph$Node { - public fun (Ljava/util/Set;Lorg/partiql/lang/eval/ExprValue;)V - public fun getLabels ()Ljava/util/Set; - public fun getPayload ()Lorg/partiql/lang/eval/ExprValue; -} - -public final class org/partiql/lang/graph/StepSpec { - public fun (Lorg/partiql/lang/graph/DirSpec;Lkotlin/Triple;)V - public final fun component1 ()Lorg/partiql/lang/graph/DirSpec; - public final fun component2 ()Lkotlin/Triple; - public final fun copy (Lorg/partiql/lang/graph/DirSpec;Lkotlin/Triple;)Lorg/partiql/lang/graph/StepSpec; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/StepSpec;Lorg/partiql/lang/graph/DirSpec;Lkotlin/Triple;ILjava/lang/Object;)Lorg/partiql/lang/graph/StepSpec; - public fun equals (Ljava/lang/Object;)Z - public final fun getDirSpec ()Lorg/partiql/lang/graph/DirSpec; - public final fun getTripleSpec ()Lkotlin/Triple; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/Stride { - public fun (Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lorg/partiql/lang/graph/Stride; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/Stride;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/graph/Stride; - public fun equals (Ljava/lang/Object;)Z - public final fun getElems ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/StrideJoin : org/partiql/lang/graph/StrideTree { - public fun (Lorg/partiql/lang/graph/StrideTree;Lorg/partiql/lang/graph/StrideTree;)V - public final fun component1 ()Lorg/partiql/lang/graph/StrideTree; - public final fun component2 ()Lorg/partiql/lang/graph/StrideTree; - public final fun copy (Lorg/partiql/lang/graph/StrideTree;Lorg/partiql/lang/graph/StrideTree;)Lorg/partiql/lang/graph/StrideJoin; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/StrideJoin;Lorg/partiql/lang/graph/StrideTree;Lorg/partiql/lang/graph/StrideTree;ILjava/lang/Object;)Lorg/partiql/lang/graph/StrideJoin; - public fun equals (Ljava/lang/Object;)Z - public final fun getLeft ()Lorg/partiql/lang/graph/StrideTree; - public final fun getRight ()Lorg/partiql/lang/graph/StrideTree; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/StrideLeaf : org/partiql/lang/graph/StrideTree { - public fun (Lorg/partiql/lang/graph/StrideSpec;)V - public final fun component1 ()Lorg/partiql/lang/graph/StrideSpec; - public final fun copy (Lorg/partiql/lang/graph/StrideSpec;)Lorg/partiql/lang/graph/StrideLeaf; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/StrideLeaf;Lorg/partiql/lang/graph/StrideSpec;ILjava/lang/Object;)Lorg/partiql/lang/graph/StrideLeaf; - public fun equals (Ljava/lang/Object;)Z - public final fun getStride ()Lorg/partiql/lang/graph/StrideSpec; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/StrideResult { - public fun (Lorg/partiql/lang/graph/StrideSpec;Ljava/util/Set;)V - public final fun component1 ()Lorg/partiql/lang/graph/StrideSpec; - public final fun component2 ()Ljava/util/Set; - public final fun copy (Lorg/partiql/lang/graph/StrideSpec;Ljava/util/Set;)Lorg/partiql/lang/graph/StrideResult; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/StrideResult;Lorg/partiql/lang/graph/StrideSpec;Ljava/util/Set;ILjava/lang/Object;)Lorg/partiql/lang/graph/StrideResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getResult ()Ljava/util/Set; - public final fun getSpec ()Lorg/partiql/lang/graph/StrideSpec; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/graph/StrideSpec { - public fun (Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lorg/partiql/lang/graph/StrideSpec; - public static synthetic fun copy$default (Lorg/partiql/lang/graph/StrideSpec;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/graph/StrideSpec; - public fun equals (Ljava/lang/Object;)Z - public final fun getElems ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class org/partiql/lang/graph/StrideTree { -} - -public final class org/partiql/lang/planner/DmlAction : java/lang/Enum { - public static final field Companion Lorg/partiql/lang/planner/DmlAction$Companion; - public static final field DELETE Lorg/partiql/lang/planner/DmlAction; - public static final field INSERT Lorg/partiql/lang/planner/DmlAction; - public static final field REPLACE Lorg/partiql/lang/planner/DmlAction; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/planner/DmlAction; - public static fun values ()[Lorg/partiql/lang/planner/DmlAction; -} - -public final class org/partiql/lang/planner/DmlAction$Companion { - public final fun safeValueOf (Ljava/lang/String;)Lorg/partiql/lang/planner/DmlAction; -} - -public final class org/partiql/lang/planner/ErrorsKt { - public static final fun handleUnimplementedFeature (Lorg/partiql/errors/ProblemHandler;Lorg/partiql/pig/runtime/DomainNode;Ljava/lang/String;)V -} - -public final class org/partiql/lang/planner/EvaluatorOptions { - public static final field Companion Lorg/partiql/lang/planner/EvaluatorOptions$Companion; - public static final fun builder ()Lorg/partiql/lang/planner/EvaluatorOptions$Builder; - public static final fun builder (Lorg/partiql/lang/planner/EvaluatorOptions;)Lorg/partiql/lang/planner/EvaluatorOptions$Builder; - public final fun component1 ()Lorg/partiql/lang/eval/ProjectionIterationBehavior; - public final fun component2 ()Lorg/partiql/lang/eval/ThunkOptions; - public final fun component3 ()Lorg/partiql/lang/eval/TypingMode; - public final fun component4 ()Lorg/partiql/lang/eval/TypedOpBehavior; - public final fun component5 ()Ljava/time/ZoneOffset; - public final fun copy (Lorg/partiql/lang/eval/ProjectionIterationBehavior;Lorg/partiql/lang/eval/ThunkOptions;Lorg/partiql/lang/eval/TypingMode;Lorg/partiql/lang/eval/TypedOpBehavior;Ljava/time/ZoneOffset;)Lorg/partiql/lang/planner/EvaluatorOptions; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/EvaluatorOptions;Lorg/partiql/lang/eval/ProjectionIterationBehavior;Lorg/partiql/lang/eval/ThunkOptions;Lorg/partiql/lang/eval/TypingMode;Lorg/partiql/lang/eval/TypedOpBehavior;Ljava/time/ZoneOffset;ILjava/lang/Object;)Lorg/partiql/lang/planner/EvaluatorOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getDefaultTimezoneOffset ()Ljava/time/ZoneOffset; - public final fun getProjectionIteration ()Lorg/partiql/lang/eval/ProjectionIterationBehavior; - public final fun getThunkOptions ()Lorg/partiql/lang/eval/ThunkOptions; - public final fun getTypedOpBehavior ()Lorg/partiql/lang/eval/TypedOpBehavior; - public final fun getTypingMode ()Lorg/partiql/lang/eval/TypingMode; - public fun hashCode ()I - public static final fun standard ()Lorg/partiql/lang/planner/EvaluatorOptions; - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/EvaluatorOptions$Builder { - public fun ()V - public fun (Lorg/partiql/lang/planner/EvaluatorOptions;)V - public synthetic fun (Lorg/partiql/lang/planner/EvaluatorOptions;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun build ()Lorg/partiql/lang/planner/EvaluatorOptions; - public final fun defaultTimezoneOffset (Ljava/time/ZoneOffset;)Lorg/partiql/lang/planner/EvaluatorOptions$Builder; - public final fun projectionIteration (Lorg/partiql/lang/eval/ProjectionIterationBehavior;)Lorg/partiql/lang/planner/EvaluatorOptions$Builder; - public final fun thunkOptions (Lorg/partiql/lang/eval/ThunkOptions;)Lorg/partiql/lang/planner/EvaluatorOptions$Builder; - public final fun typedOpBehavior (Lorg/partiql/lang/eval/TypedOpBehavior;)Lorg/partiql/lang/planner/EvaluatorOptions$Builder; - public final fun typingMode (Lorg/partiql/lang/eval/TypingMode;)Lorg/partiql/lang/planner/EvaluatorOptions$Builder; -} - -public final class org/partiql/lang/planner/EvaluatorOptions$Companion { - public final fun build (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/planner/EvaluatorOptions; - public final fun build (Lorg/partiql/lang/planner/EvaluatorOptions;Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/planner/EvaluatorOptions; - public final fun builder ()Lorg/partiql/lang/planner/EvaluatorOptions$Builder; - public final fun builder (Lorg/partiql/lang/planner/EvaluatorOptions;)Lorg/partiql/lang/planner/EvaluatorOptions$Builder; - public final fun standard ()Lorg/partiql/lang/planner/EvaluatorOptions; -} - -public abstract class org/partiql/lang/planner/GlobalResolutionResult { -} - -public final class org/partiql/lang/planner/GlobalResolutionResult$GlobalVariable : org/partiql/lang/planner/GlobalResolutionResult { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lorg/partiql/lang/planner/GlobalResolutionResult$GlobalVariable; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/GlobalResolutionResult$GlobalVariable;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/planner/GlobalResolutionResult$GlobalVariable; - public fun equals (Ljava/lang/Object;)Z - public final fun getUniqueId ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/GlobalResolutionResult$Undefined : org/partiql/lang/planner/GlobalResolutionResult { - public static final field INSTANCE Lorg/partiql/lang/planner/GlobalResolutionResult$Undefined; -} - -public abstract interface class org/partiql/lang/planner/GlobalVariableResolver { - public static final field Companion Lorg/partiql/lang/planner/GlobalVariableResolver$Companion; - public abstract fun resolveGlobal (Lorg/partiql/lang/eval/BindingName;)Lorg/partiql/lang/planner/GlobalResolutionResult; -} - -public final class org/partiql/lang/planner/GlobalVariableResolver$Companion { - public final fun getEMPTY ()Lorg/partiql/lang/planner/GlobalVariableResolver; -} - -public abstract interface class org/partiql/lang/planner/PartiQLPhysicalPass : org/partiql/lang/planner/PartiQLPlannerPass { -} - -public abstract interface class org/partiql/lang/planner/PartiQLPlanner { - public static final field Companion Lorg/partiql/lang/planner/PartiQLPlanner$Companion; - public static final field PLAN_VERSION Ljava/lang/String; - public abstract fun plan (Lorg/partiql/lang/domains/PartiqlAst$Statement;)Lorg/partiql/lang/planner/PartiQLPlanner$Result; -} - -public final class org/partiql/lang/planner/PartiQLPlanner$Companion { - public static final field PLAN_VERSION Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PartiQLPlanner$Options { - public fun ()V - public fun (ZLorg/partiql/lang/eval/TypedOpBehavior;)V - public synthetic fun (ZLorg/partiql/lang/eval/TypedOpBehavior;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getAllowedUndefinedVariables ()Z - public final fun getTypedOpBehavior ()Lorg/partiql/lang/eval/TypedOpBehavior; -} - -public final class org/partiql/lang/planner/PartiQLPlanner$PlanningDetails { - public fun ()V - public fun (Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/domains/PartiqlLogical$Plan;Lorg/partiql/lang/domains/PartiqlLogicalResolved$Plan;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;)V - public synthetic fun (Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/domains/PartiqlLogical$Plan;Lorg/partiql/lang/domains/PartiqlLogicalResolved$Plan;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lorg/partiql/lang/domains/PartiqlAst$Statement; - public final fun component2 ()Lorg/partiql/lang/domains/PartiqlAst$Statement; - public final fun component3 ()Lorg/partiql/lang/domains/PartiqlLogical$Plan; - public final fun component4 ()Lorg/partiql/lang/domains/PartiqlLogicalResolved$Plan; - public final fun component5 ()Lorg/partiql/lang/domains/PartiqlPhysical$Plan; - public final fun component6 ()Lorg/partiql/lang/domains/PartiqlPhysical$Plan; - public final fun copy (Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/domains/PartiqlLogical$Plan;Lorg/partiql/lang/domains/PartiqlLogicalResolved$Plan;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;)Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails;Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/domains/PartiqlAst$Statement;Lorg/partiql/lang/domains/PartiqlLogical$Plan;Lorg/partiql/lang/domains/PartiqlLogicalResolved$Plan;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;ILjava/lang/Object;)Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails; - public fun equals (Ljava/lang/Object;)Z - public final fun getAst ()Lorg/partiql/lang/domains/PartiqlAst$Statement; - public final fun getAstNormalized ()Lorg/partiql/lang/domains/PartiqlAst$Statement; - public final fun getLogical ()Lorg/partiql/lang/domains/PartiqlLogical$Plan; - public final fun getLogicalResolved ()Lorg/partiql/lang/domains/PartiqlLogicalResolved$Plan; - public final fun getPhysical ()Lorg/partiql/lang/domains/PartiqlPhysical$Plan; - public final fun getPhysicalTransformed ()Lorg/partiql/lang/domains/PartiqlPhysical$Plan; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class org/partiql/lang/planner/PartiQLPlanner$Result { -} - -public final class org/partiql/lang/planner/PartiQLPlanner$Result$Error : org/partiql/lang/planner/PartiQLPlanner$Result { - public fun (Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lorg/partiql/lang/planner/PartiQLPlanner$Result$Error; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PartiQLPlanner$Result$Error;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/planner/PartiQLPlanner$Result$Error; - public fun equals (Ljava/lang/Object;)Z - public final fun getProblems ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PartiQLPlanner$Result$Success : org/partiql/lang/planner/PartiQLPlanner$Result { - public fun (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Ljava/util/List;)V - public fun (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Ljava/util/List;Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails;)V - public synthetic fun (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Ljava/util/List;Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lorg/partiql/lang/domains/PartiqlPhysical$Plan; - public final fun component2 ()Ljava/util/List; - public final fun component3 ()Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails; - public final fun copy (Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Ljava/util/List;Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails;)Lorg/partiql/lang/planner/PartiQLPlanner$Result$Success; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PartiQLPlanner$Result$Success;Lorg/partiql/lang/domains/PartiqlPhysical$Plan;Ljava/util/List;Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails;ILjava/lang/Object;)Lorg/partiql/lang/planner/PartiQLPlanner$Result$Success; - public fun equals (Ljava/lang/Object;)Z - public final fun getDetails ()Lorg/partiql/lang/planner/PartiQLPlanner$PlanningDetails; - public final fun getPlan ()Lorg/partiql/lang/domains/PartiqlPhysical$Plan; - public final fun getWarnings ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PartiQLPlannerBuilder { - public static final field Companion Lorg/partiql/lang/planner/PartiQLPlannerBuilder$Companion; - public final fun build ()Lorg/partiql/lang/planner/PartiQLPlanner; - public final fun callback (Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/planner/PartiQLPlannerBuilder; - public final fun globalVariableResolver (Lorg/partiql/lang/planner/GlobalVariableResolver;)Lorg/partiql/lang/planner/PartiQLPlannerBuilder; - public final fun options (Lorg/partiql/lang/planner/PartiQLPlanner$Options;)Lorg/partiql/lang/planner/PartiQLPlannerBuilder; - public final fun physicalPlannerPasses (Ljava/util/List;)Lorg/partiql/lang/planner/PartiQLPlannerBuilder; - public static final fun standard ()Lorg/partiql/lang/planner/PartiQLPlannerBuilder; -} - -public final class org/partiql/lang/planner/PartiQLPlannerBuilder$Companion { - public final fun standard ()Lorg/partiql/lang/planner/PartiQLPlannerBuilder; -} - -public abstract interface class org/partiql/lang/planner/PartiQLPlannerPass { - public abstract fun apply (Lorg/partiql/pig/runtime/DomainNode;Lorg/partiql/errors/ProblemHandler;)Lorg/partiql/pig/runtime/DomainNode; -} - -public final class org/partiql/lang/planner/PlannerEvent { - public fun (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/time/Duration;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/Object; - public final fun component3 ()Ljava/lang/Object; - public final fun component4 ()Ljava/time/Duration; - public final fun copy (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/time/Duration;)Lorg/partiql/lang/planner/PlannerEvent; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PlannerEvent;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/time/Duration;ILjava/lang/Object;)Lorg/partiql/lang/planner/PlannerEvent; - public fun equals (Ljava/lang/Object;)Z - public final fun getDuration ()Ljava/time/Duration; - public final fun getEventName ()Ljava/lang/String; - public final fun getInput ()Ljava/lang/Object; - public final fun getOutput ()Ljava/lang/Object; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class org/partiql/lang/planner/PlanningProblemDetails : org/partiql/errors/ProblemDetails { - public synthetic fun (Lorg/partiql/errors/ProblemSeverity;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getMessage ()Ljava/lang/String; - public final fun getMessageFormatter ()Lkotlin/jvm/functions/Function0; - public fun getSeverity ()Lorg/partiql/errors/ProblemSeverity; - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$CompileError : org/partiql/lang/planner/PlanningProblemDetails { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lorg/partiql/lang/planner/PlanningProblemDetails$CompileError; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PlanningProblemDetails$CompileError;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/planner/PlanningProblemDetails$CompileError; - public fun equals (Ljava/lang/Object;)Z - public final fun getErrorMessage ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$InsertValueDisallowed : org/partiql/lang/planner/PlanningProblemDetails { - public static final field INSTANCE Lorg/partiql/lang/planner/PlanningProblemDetails$InsertValueDisallowed; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$InsertValuesDisallowed : org/partiql/lang/planner/PlanningProblemDetails { - public static final field INSTANCE Lorg/partiql/lang/planner/PlanningProblemDetails$InsertValuesDisallowed; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$InvalidDmlTarget : org/partiql/lang/planner/PlanningProblemDetails { - public static final field INSTANCE Lorg/partiql/lang/planner/PlanningProblemDetails$InvalidDmlTarget; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$ParseError : org/partiql/lang/planner/PlanningProblemDetails { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lorg/partiql/lang/planner/PlanningProblemDetails$ParseError; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PlanningProblemDetails$ParseError;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/planner/PlanningProblemDetails$ParseError; - public fun equals (Ljava/lang/Object;)Z - public final fun getParseErrorMessage ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$UndefinedDmlTarget : org/partiql/lang/planner/PlanningProblemDetails { - public fun (Ljava/lang/String;Z)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Z - public final fun copy (Ljava/lang/String;Z)Lorg/partiql/lang/planner/PlanningProblemDetails$UndefinedDmlTarget; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PlanningProblemDetails$UndefinedDmlTarget;Ljava/lang/String;ZILjava/lang/Object;)Lorg/partiql/lang/planner/PlanningProblemDetails$UndefinedDmlTarget; - public fun equals (Ljava/lang/Object;)Z - public final fun getCaseSensitive ()Z - public final fun getVariableName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$UndefinedVariable : org/partiql/lang/planner/PlanningProblemDetails { - public fun (Ljava/lang/String;Z)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Z - public final fun copy (Ljava/lang/String;Z)Lorg/partiql/lang/planner/PlanningProblemDetails$UndefinedVariable; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PlanningProblemDetails$UndefinedVariable;Ljava/lang/String;ZILjava/lang/Object;)Lorg/partiql/lang/planner/PlanningProblemDetails$UndefinedVariable; - public fun equals (Ljava/lang/Object;)Z - public final fun getCaseSensitive ()Z - public final fun getVariableName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$UnimplementedFeature : org/partiql/lang/planner/PlanningProblemDetails { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lorg/partiql/lang/planner/PlanningProblemDetails$UnimplementedFeature; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PlanningProblemDetails$UnimplementedFeature;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/planner/PlanningProblemDetails$UnimplementedFeature; - public fun equals (Ljava/lang/Object;)Z - public final fun getFeatureName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$UnresolvedExcludeExprRoot : org/partiql/lang/planner/PlanningProblemDetails { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lorg/partiql/lang/planner/PlanningProblemDetails$UnresolvedExcludeExprRoot; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PlanningProblemDetails$UnresolvedExcludeExprRoot;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/planner/PlanningProblemDetails$UnresolvedExcludeExprRoot; - public fun equals (Ljava/lang/Object;)Z - public final fun getRoot ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/PlanningProblemDetails$VariablePreviouslyDefined : org/partiql/lang/planner/PlanningProblemDetails { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lorg/partiql/lang/planner/PlanningProblemDetails$VariablePreviouslyDefined; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/PlanningProblemDetails$VariablePreviouslyDefined;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/planner/PlanningProblemDetails$VariablePreviouslyDefined; - public fun equals (Ljava/lang/Object;)Z - public final fun getVariableName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class org/partiql/lang/planner/QueryPlan { - public abstract fun eval (Lorg/partiql/lang/eval/EvaluationSession;)Lorg/partiql/lang/planner/QueryResult; -} - -public abstract class org/partiql/lang/planner/QueryResult { -} - -public final class org/partiql/lang/planner/QueryResult$DmlCommand : org/partiql/lang/planner/QueryResult { - public fun (Lorg/partiql/lang/planner/DmlAction;Ljava/lang/String;Ljava/lang/Iterable;)V - public final fun component1 ()Lorg/partiql/lang/planner/DmlAction; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/Iterable; - public final fun copy (Lorg/partiql/lang/planner/DmlAction;Ljava/lang/String;Ljava/lang/Iterable;)Lorg/partiql/lang/planner/QueryResult$DmlCommand; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/QueryResult$DmlCommand;Lorg/partiql/lang/planner/DmlAction;Ljava/lang/String;Ljava/lang/Iterable;ILjava/lang/Object;)Lorg/partiql/lang/planner/QueryResult$DmlCommand; - public fun equals (Ljava/lang/Object;)Z - public final fun getAction ()Lorg/partiql/lang/planner/DmlAction; - public final fun getRows ()Ljava/lang/Iterable; - public final fun getTargetUniqueId ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/QueryResult$Value : org/partiql/lang/planner/QueryResult { - public fun (Lorg/partiql/lang/eval/ExprValue;)V - public final fun getValue ()Lorg/partiql/lang/eval/ExprValue; -} - -public abstract interface class org/partiql/lang/planner/StaticTypeResolver { - public abstract fun getVariableStaticType (Ljava/lang/String;)Lorg/partiql/types/StaticType; -} - -public final class org/partiql/lang/planner/transforms/AstNormalizeKt { - public static final fun normalize (Lorg/partiql/lang/domains/PartiqlAst$Statement;)Lorg/partiql/lang/domains/PartiqlAst$Statement; -} - -public final class org/partiql/lang/planner/transforms/UtilKt { - public static final field PLAN_VERSION_NUMBER Ljava/lang/String; - public static final fun isLitTrue (Lorg/partiql/lang/domains/PartiqlPhysical$Expr;)Z -} - -public final class org/partiql/lang/planner/transforms/optimizations/FieldEqualityPredicate { - public fun (Ljava/lang/String;Lorg/partiql/lang/domains/PartiqlPhysical$Expr;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lorg/partiql/lang/domains/PartiqlPhysical$Expr; - public final fun copy (Ljava/lang/String;Lorg/partiql/lang/domains/PartiqlPhysical$Expr;)Lorg/partiql/lang/planner/transforms/optimizations/FieldEqualityPredicate; - public static synthetic fun copy$default (Lorg/partiql/lang/planner/transforms/optimizations/FieldEqualityPredicate;Ljava/lang/String;Lorg/partiql/lang/domains/PartiqlPhysical$Expr;ILjava/lang/Object;)Lorg/partiql/lang/planner/transforms/optimizations/FieldEqualityPredicate; - public fun equals (Ljava/lang/Object;)Z - public final fun getEquivalentValue ()Lorg/partiql/lang/domains/PartiqlPhysical$Expr; - public final fun getKeyFieldName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/planner/transforms/optimizations/FilterScanToKeyLookupKt { - public static final fun createFilterScanToKeyLookupPass (Ljava/lang/String;Lorg/partiql/lang/planner/StaticTypeResolver;Lkotlin/jvm/functions/Function2;)Lorg/partiql/lang/planner/PartiQLPhysicalPass; -} - -public final class org/partiql/lang/planner/transforms/optimizations/RemoveUselessAndsKt { - public static final fun createRemoveUselessAndsPass ()Lorg/partiql/lang/planner/PartiQLPhysicalPass; -} - -public final class org/partiql/lang/planner/transforms/optimizations/RemoveUselessFiltersKt { - public static final fun createRemoveUselessFiltersPass ()Lorg/partiql/lang/planner/PartiQLPhysicalPass; -} - -public final class org/partiql/lang/planner/validators/PartiqlLogicalResolvedValidator : org/partiql/lang/domains/PartiqlLogicalResolved$Visitor { - public fun ()V - public fun visitPlan (Lorg/partiql/lang/domains/PartiqlLogicalResolved$Plan;)V -} - -public final class org/partiql/lang/planner/validators/PartiqlLogicalValidator : org/partiql/lang/domains/PartiqlLogical$Visitor { - public fun (Lorg/partiql/lang/eval/TypedOpBehavior;)V -} - -public final class org/partiql/lang/prettyprint/ASTPrettyPrinter { - public fun ()V - public final fun prettyPrintAST (Ljava/lang/String;)Ljava/lang/String; - public final fun prettyPrintAST (Lorg/partiql/lang/domains/PartiqlAst$Statement;)Ljava/lang/String; -} - -public final class org/partiql/lang/prettyprint/QueryPrettyPrinter { - public fun ()V - public final fun astToPrettyQuery (Lorg/partiql/lang/domains/PartiqlAst$Statement;)Ljava/lang/String; - public final fun prettyPrintQuery (Ljava/lang/String;)Ljava/lang/String; -} - -public final class org/partiql/lang/prettyprint/RecursionTree { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun convertToString ()Ljava/lang/String; -} - -public class org/partiql/lang/syntax/LexerException : org/partiql/lang/syntax/SyntaxException { - public fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public abstract interface class org/partiql/lang/syntax/Parser { - public abstract fun parseAstStatement (Ljava/lang/String;)Lorg/partiql/lang/domains/PartiqlAst$Statement; -} - -public class org/partiql/lang/syntax/ParserException : org/partiql/lang/syntax/SyntaxException { - public fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class org/partiql/lang/syntax/PartiQLParserBuilder { - public static final field Companion Lorg/partiql/lang/syntax/PartiQLParserBuilder$Companion; - public fun ()V - public final fun build ()Lorg/partiql/lang/syntax/Parser; - public final fun customTypes (Ljava/util/List;)Lorg/partiql/lang/syntax/PartiQLParserBuilder; - public static final fun experimental ()Lorg/partiql/lang/syntax/PartiQLParserBuilder; - public static final fun standard ()Lorg/partiql/lang/syntax/PartiQLParserBuilder; -} - -public final class org/partiql/lang/syntax/PartiQLParserBuilder$Companion { - public final fun experimental ()Lorg/partiql/lang/syntax/PartiQLParserBuilder; - public final fun standard ()Lorg/partiql/lang/syntax/PartiQLParserBuilder; -} - -public class org/partiql/lang/syntax/SyntaxException : org/partiql/lang/SqlException { - public fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Lorg/partiql/errors/ErrorCode;Lorg/partiql/errors/PropertyValueMap;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class org/partiql/lang/types/CustomType { - public fun (Ljava/lang/String;Lorg/partiql/lang/types/TypedOpParameter;Ljava/util/List;)V - public synthetic fun (Ljava/lang/String;Lorg/partiql/lang/types/TypedOpParameter;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lorg/partiql/lang/types/TypedOpParameter; - public final fun component3 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Lorg/partiql/lang/types/TypedOpParameter;Ljava/util/List;)Lorg/partiql/lang/types/CustomType; - public static synthetic fun copy$default (Lorg/partiql/lang/types/CustomType;Ljava/lang/String;Lorg/partiql/lang/types/TypedOpParameter;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/lang/types/CustomType; - public fun equals (Ljava/lang/Object;)Z - public final fun getAliases ()Ljava/util/List; - public final fun getName ()Ljava/lang/String; - public final fun getTypedOpParameter ()Lorg/partiql/lang/types/TypedOpParameter; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/types/FunctionSignature { - public fun (Ljava/lang/String;Ljava/util/List;Lorg/partiql/lang/types/VarargFormalParameter;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/UnknownArguments;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;Lorg/partiql/lang/types/VarargFormalParameter;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/UnknownArguments;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Ljava/lang/String;Ljava/util/List;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/UnknownArguments;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/UnknownArguments;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Ljava/lang/String;Ljava/util/List;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/VarargFormalParameter;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/UnknownArguments;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/VarargFormalParameter;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/UnknownArguments;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Ljava/lang/String;Ljava/util/List;Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/UnknownArguments;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;Lorg/partiql/lang/types/UnknownArguments;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getArity ()Lkotlin/ranges/IntRange; - public final fun getName ()Ljava/lang/String; - public final fun getRequiredParameters ()Ljava/util/List; - public final fun getReturnType ()Lorg/partiql/types/StaticType; - public final fun getUnknownArguments ()Lorg/partiql/lang/types/UnknownArguments; -} - -public final class org/partiql/lang/types/StaticTypeUtils { - public static final field INSTANCE Lorg/partiql/lang/types/StaticTypeUtils; - public static final fun areStaticTypesComparable (Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;)Z - public static final fun getRuntimeType (Lorg/partiql/types/SingleType;)Lorg/partiql/lang/eval/ExprValueType; - public static final fun getTypeDomain (Lorg/partiql/types/StaticType;)Ljava/util/Set; - public static final fun isInstance (Lorg/partiql/lang/eval/ExprValue;Lorg/partiql/types/StaticType;)Z - public static final fun isSubTypeOf (Lorg/partiql/types/StaticType;Lorg/partiql/types/StaticType;)Z - public static final fun staticTypeFromExprValue (Lorg/partiql/lang/eval/ExprValue;)Lorg/partiql/types/StaticType; - public static final fun staticTypeFromExprValueType (Lorg/partiql/lang/eval/ExprValueType;)Lorg/partiql/types/StaticType; - public static final fun stringLengthConstraintMatches (Lorg/partiql/types/StringType$StringLengthConstraint;Lorg/partiql/lang/eval/ExprValue;)Z -} - -public final class org/partiql/lang/types/TypedOpParameter { - public fun (Lorg/partiql/types/StaticType;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lorg/partiql/types/StaticType;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lorg/partiql/types/StaticType; - public final fun component2 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Lorg/partiql/types/StaticType;Lkotlin/jvm/functions/Function1;)Lorg/partiql/lang/types/TypedOpParameter; - public static synthetic fun copy$default (Lorg/partiql/lang/types/TypedOpParameter;Lorg/partiql/types/StaticType;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/lang/types/TypedOpParameter; - public fun equals (Ljava/lang/Object;)Z - public final fun getStaticType ()Lorg/partiql/types/StaticType; - public final fun getValidationThunk ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/lang/types/UnknownArguments : java/lang/Enum { - public static final field PASS_THRU Lorg/partiql/lang/types/UnknownArguments; - public static final field PROPAGATE Lorg/partiql/lang/types/UnknownArguments; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/lang/types/UnknownArguments; - public static fun values ()[Lorg/partiql/lang/types/UnknownArguments; -} - -public final class org/partiql/lang/types/VarargFormalParameter { - public fun (Lorg/partiql/types/StaticType;I)V - public fun (Lorg/partiql/types/StaticType;Lkotlin/ranges/IntRange;)V - public final fun component1 ()Lorg/partiql/types/StaticType; - public final fun component2 ()Lkotlin/ranges/IntRange; - public final fun copy (Lorg/partiql/types/StaticType;Lkotlin/ranges/IntRange;)Lorg/partiql/lang/types/VarargFormalParameter; - public static synthetic fun copy$default (Lorg/partiql/lang/types/VarargFormalParameter;Lorg/partiql/types/StaticType;Lkotlin/ranges/IntRange;ILjava/lang/Object;)Lorg/partiql/lang/types/VarargFormalParameter; - public fun equals (Ljava/lang/Object;)Z - public final fun getArityRange ()Lkotlin/ranges/IntRange; - public final fun getType ()Lorg/partiql/types/StaticType; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class org/partiql/lang/util/BindingHelper { - public static final field Companion Lorg/partiql/lang/util/BindingHelper$Companion; - public static final fun bindingNameEquals (Ljava/lang/String;Ljava/lang/String;Lorg/partiql/lang/eval/BindingCase;)Z - public static final fun throwAmbiguousBindingEvaluationException (Ljava/lang/String;Ljava/util/List;)Ljava/lang/Void; -} - -public final class org/partiql/lang/util/BindingHelper$Companion { - public final fun bindingNameEquals (Ljava/lang/String;Ljava/lang/String;Lorg/partiql/lang/eval/BindingCase;)Z - public final fun throwAmbiguousBindingEvaluationException (Ljava/lang/String;Ljava/util/List;)Ljava/lang/Void; -} - -public final class org/partiql/lang/util/BindingHelpersKt { - public static final fun caseInsensitiveEquivalent (Ljava/lang/String;Ljava/lang/String;)Z - public static final fun isBindingNameEquivalent (Ljava/lang/String;Ljava/lang/String;Lorg/partiql/lang/eval/BindingCase;)Z -} - -public final class org/partiql/lang/util/CollectionExtensionsKt { - public static final fun cartesianProduct (Ljava/util/List;)Ljava/util/List; - public static final fun foldLeftProduct (Ljava/util/List;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Iterable; - public static final fun forAll (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Z - public static final fun getHead (Ljava/util/List;)Ljava/lang/Object; - public static final fun getTail (Ljava/util/List;)Ljava/util/List; - public static final fun isAnyMissing (Ljava/lang/Iterable;)Z - public static final fun isAnyNull (Ljava/lang/Iterable;)Z - public static final fun isAnyUnknown (Ljava/lang/Iterable;)Z - public static final fun product (Ljava/util/List;)Ljava/lang/Iterable; -} - -public final class org/partiql/lang/util/ConfigurableExprValueFormatter : org/partiql/lang/util/ExprValueFormatter { - public static final field Companion Lorg/partiql/lang/util/ConfigurableExprValueFormatter$Companion; - public fun (Lorg/partiql/lang/util/ConfigurableExprValueFormatter$Configuration;)V - public fun format (Lorg/partiql/lang/eval/ExprValue;)Ljava/lang/String; - public fun formatTo (Lorg/partiql/lang/eval/ExprValue;Ljava/lang/Appendable;)V - public static final fun getPretty ()Lorg/partiql/lang/util/ConfigurableExprValueFormatter; - public static final fun getStandard ()Lorg/partiql/lang/util/ConfigurableExprValueFormatter; -} - -public final class org/partiql/lang/util/ConfigurableExprValueFormatter$Companion { - public final fun getPretty ()Lorg/partiql/lang/util/ConfigurableExprValueFormatter; - public final fun getStandard ()Lorg/partiql/lang/util/ConfigurableExprValueFormatter; -} - -public final class org/partiql/lang/util/ConfigurableExprValueFormatter$Configuration { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lorg/partiql/lang/util/ConfigurableExprValueFormatter$Configuration; - public static synthetic fun copy$default (Lorg/partiql/lang/util/ConfigurableExprValueFormatter$Configuration;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/partiql/lang/util/ConfigurableExprValueFormatter$Configuration; - public fun equals (Ljava/lang/Object;)Z - public final fun getContainerValueSeparator ()Ljava/lang/String; - public final fun getIndentation ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class org/partiql/lang/util/ExprValueFormatter { - public abstract fun format (Lorg/partiql/lang/eval/ExprValue;)Ljava/lang/String; - public abstract fun formatTo (Lorg/partiql/lang/eval/ExprValue;Ljava/lang/Appendable;)V -} - -public final class org/partiql/lang/util/ExprValueFormatter$DefaultImpls { - public static fun format (Lorg/partiql/lang/util/ExprValueFormatter;Lorg/partiql/lang/eval/ExprValue;)Ljava/lang/String; -} - -public final class org/partiql/lang/util/FacetExtensionsKt { - public static final fun downcast (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; -} - -public final class org/partiql/lang/util/IonValueExtensionsKt { - public static final fun asIonInt (Lcom/amazon/ion/IonValue;)Lcom/amazon/ion/IonInt; - public static final fun asIonSexp (Lcom/amazon/ion/IonValue;)Lcom/amazon/ion/IonSexp; - public static final fun asIonStruct (Lcom/amazon/ion/IonValue;)Lcom/amazon/ion/IonStruct; - public static final fun asIonSymbol (Lcom/amazon/ion/IonValue;)Lcom/amazon/ion/IonSymbol; - public static final fun asSequence (Lcom/amazon/ion/IonValue;)Lkotlin/sequences/Sequence; - public static final fun bigDecimalValue (Lcom/amazon/ion/IonValue;)Ljava/math/BigDecimal; - public static final fun bigDecimalValueOrNull (Lcom/amazon/ion/IonValue;)Ljava/math/BigDecimal; - public static final fun booleanValue (Lcom/amazon/ion/IonValue;)Z - public static final fun booleanValueOrNull (Lcom/amazon/ion/IonValue;)Ljava/lang/Boolean; - public static final fun bytesValue (Lcom/amazon/ion/IonValue;)[B - public static final fun bytesValueOrNull (Lcom/amazon/ion/IonValue;)[B - public static final fun doubleValue (Lcom/amazon/ion/IonValue;)D - public static final fun doubleValueOrNull (Lcom/amazon/ion/IonValue;)Ljava/lang/Double; - public static final fun field (Lcom/amazon/ion/IonStruct;Ljava/lang/String;)Lcom/amazon/ion/IonValue; - public static final fun filterMetaNodes (Lcom/amazon/ion/IonSexp;)Lcom/amazon/ion/IonValue; - public static final fun get (Lcom/amazon/ion/IonValue;I)Lcom/amazon/ion/IonValue; - public static final fun get (Lcom/amazon/ion/IonValue;Ljava/lang/String;)Lcom/amazon/ion/IonValue; - public static final fun getArgs (Lcom/amazon/ion/IonSexp;)Ljava/util/List; - public static final fun getArity (Lcom/amazon/ion/IonSexp;)I - public static final fun getLastIndex (Lcom/amazon/ion/IonValue;)I - public static final fun getOrdinal (Lcom/amazon/ion/IonValue;)I - public static final fun getSize (Lcom/amazon/ion/IonValue;)I - public static final fun getTagText (Lcom/amazon/ion/IonSexp;)Ljava/lang/String; - public static final fun isAstLiteral (Lcom/amazon/ion/IonValue;)Z - public static final fun isBag (Lcom/amazon/ion/IonValue;)Z - public static final fun isDate (Lcom/amazon/ion/IonValue;)Z - public static final fun isMissing (Lcom/amazon/ion/IonValue;)Z - public static final fun isNonNullText (Lcom/amazon/ion/IonValue;)Z - public static final fun isNumeric (Lcom/amazon/ion/IonValue;)Z - public static final fun isText (Lcom/amazon/ion/IonValue;)Z - public static final fun isTime (Lcom/amazon/ion/IonValue;)Z - public static final fun isUnsignedInteger (Lcom/amazon/ion/IonValue;)Z - public static final fun iterator (Lcom/amazon/ion/IonValue;)Ljava/util/Iterator; - public static final fun javaValue (Lcom/amazon/ion/IonInt;)Ljava/lang/Number; - public static final fun longValue (Lcom/amazon/ion/IonValue;)J - public static final fun longValueOrNull (Lcom/amazon/ion/IonValue;)Ljava/lang/Long; - public static final fun numberValue (Lcom/amazon/ion/IonValue;)Ljava/lang/Number; - public static final fun numberValueOrNull (Lcom/amazon/ion/IonValue;)Ljava/lang/Number; - public static final fun seal (Lcom/amazon/ion/IonValue;)Lcom/amazon/ion/IonValue; - public static final fun singleArgWithTag (Lcom/amazon/ion/IonSexp;Ljava/lang/String;)Lcom/amazon/ion/IonValue; - public static final fun singleArgWithTagOrNull (Lcom/amazon/ion/IonSexp;Ljava/lang/String;)Lcom/amazon/ion/IonValue; - public static final fun stringValue (Lcom/amazon/ion/IonValue;)Ljava/lang/String; - public static final fun stringValueOrNull (Lcom/amazon/ion/IonValue;)Ljava/lang/String; - public static final fun timestampValue (Lcom/amazon/ion/IonValue;)Lcom/amazon/ion/Timestamp; - public static final fun timestampValueOrNull (Lcom/amazon/ion/IonValue;)Lcom/amazon/ion/Timestamp; - public static final fun toListOfIonSexp (Ljava/lang/Iterable;)Ljava/util/List; -} - -public final class org/partiql/lang/util/IonWriterContext { - public fun (Lcom/amazon/ion/IonWriter;)V - public final fun bool (Ljava/lang/String;Z)V - public final fun bool (Z)V - public final fun getWriter ()Lcom/amazon/ion/IonWriter; - public final fun int (J)V - public final fun int (Ljava/lang/String;J)V - public final fun list (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public final fun list (Lkotlin/jvm/functions/Function1;)V - public final fun nullBlob ()V - public final fun nullBlob (Ljava/lang/String;)V - public final fun nullBool ()V - public final fun nullBool (Ljava/lang/String;)V - public final fun nullClob ()V - public final fun nullClob (Ljava/lang/String;)V - public final fun nullDecimal ()V - public final fun nullDecimal (Ljava/lang/String;)V - public final fun nullFloat ()V - public final fun nullFloat (Ljava/lang/String;)V - public final fun nullInt ()V - public final fun nullInt (Ljava/lang/String;)V - public final fun nullList ()V - public final fun nullList (Ljava/lang/String;)V - public final fun nullSexp ()V - public final fun nullSexp (Ljava/lang/String;)V - public final fun nullString ()V - public final fun nullString (Ljava/lang/String;)V - public final fun nullStruct ()V - public final fun nullStruct (Ljava/lang/String;)V - public final fun nullSymbol ()V - public final fun nullSymbol (Ljava/lang/String;)V - public final fun setNextFieldName (Ljava/lang/String;)V - public final fun sexp (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public final fun sexp (Lkotlin/jvm/functions/Function1;)V - public final fun string (Ljava/lang/String;)V - public final fun string (Ljava/lang/String;Ljava/lang/String;)V - public final fun struct (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public final fun struct (Lkotlin/jvm/functions/Function1;)V - public final fun symbol (Ljava/lang/String;)V - public final fun symbol (Ljava/lang/String;Ljava/lang/String;)V - public final fun untypedNull ()V - public final fun untypedNull (Ljava/lang/String;)V - public final fun value (Lcom/amazon/ion/IonValue;)V - public final fun value (Ljava/lang/String;Lcom/amazon/ion/IonValue;)V -} - -public final class org/partiql/lang/util/LongExtensionsKt { - public static final fun toIntExact (J)I -} - -public final class org/partiql/lang/util/NumberExtensionsKt { - public static final fun bigDecimalOf (Ljava/lang/Number;Ljava/math/MathContext;)Ljava/math/BigDecimal; - public static final fun bigDecimalOf (Ljava/lang/String;Ljava/math/MathContext;)Ljava/math/BigDecimal; - public static synthetic fun bigDecimalOf$default (Ljava/lang/Number;Ljava/math/MathContext;ILjava/lang/Object;)Ljava/math/BigDecimal; - public static synthetic fun bigDecimalOf$default (Ljava/lang/String;Ljava/math/MathContext;ILjava/lang/Object;)Ljava/math/BigDecimal; - public static final fun coerce (Ljava/lang/Number;Ljava/lang/Class;)Ljava/lang/Number; - public static final fun coerceNumbers (Ljava/lang/Number;Ljava/lang/Number;)Lkotlin/Pair; - public static final fun compareTo (Ljava/lang/Number;Ljava/lang/Number;)I - public static final fun div (Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number; - public static final fun exp (Ljava/math/BigDecimal;Ljava/math/MathContext;)Ljava/math/BigDecimal; - public static synthetic fun exp$default (Ljava/math/BigDecimal;Ljava/math/MathContext;ILjava/lang/Object;)Ljava/math/BigDecimal; - public static final fun ionValue (Ljava/lang/Number;Lcom/amazon/ion/IonSystem;)Lcom/amazon/ion/IonValue; - public static final fun isNaN (Ljava/lang/Number;)Z - public static final fun isNegInf (Ljava/lang/Number;)Z - public static final fun isPosInf (Ljava/lang/Number;)Z - public static final fun ln (Ljava/math/BigDecimal;Ljava/math/MathContext;)Ljava/math/BigDecimal; - public static synthetic fun ln$default (Ljava/math/BigDecimal;Ljava/math/MathContext;ILjava/lang/Object;)Ljava/math/BigDecimal; - public static final fun minus (Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number; - public static final fun plus (Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number; - public static final fun power (Ljava/math/BigDecimal;Ljava/math/BigDecimal;Ljava/math/MathContext;)Ljava/math/BigDecimal; - public static synthetic fun power$default (Ljava/math/BigDecimal;Ljava/math/BigDecimal;Ljava/math/MathContext;ILjava/lang/Object;)Ljava/math/BigDecimal; - public static final fun rem (Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number; - public static final fun squareRoot (Ljava/math/BigDecimal;Ljava/math/MathContext;)Ljava/math/BigDecimal; - public static synthetic fun squareRoot$default (Ljava/math/BigDecimal;Ljava/math/MathContext;ILjava/lang/Object;)Ljava/math/BigDecimal; - public static final fun times (Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number; - public static final fun unaryMinus (Lcom/amazon/ion/Decimal;)Lcom/amazon/ion/Decimal; - public static final fun unaryMinus (Ljava/lang/Number;)Ljava/lang/Number; -} - -public final class org/partiql/lang/util/PropertyMapHelpersKt { - public static final fun propertyValueMapOf (II[Lkotlin/Pair;)Lorg/partiql/errors/PropertyValueMap; - public static final fun propertyValueMapOf ([Lkotlin/Pair;)Lorg/partiql/errors/PropertyValueMap; - public static final fun to (Lorg/partiql/errors/Property;Ljava/lang/Object;)Lkotlin/Pair; -} - -public final class org/partiql/lang/util/StringExtensionsKt { - public static final fun codePointSequence (Ljava/lang/String;)Lkotlin/sequences/Sequence; -} - -public final class org/partiql/lang/util/WhenAsExpressionHelper { - public static final field Companion Lorg/partiql/lang/util/WhenAsExpressionHelper$Companion; - public final fun toUnit ()V -} - -public final class org/partiql/lang/util/WhenAsExpressionHelper$Companion { - public final fun getInstance ()Lorg/partiql/lang/util/WhenAsExpressionHelper; -} - -public final class org/partiql/lang/util/WhenAsExpressionKt { - public static final fun case (Lkotlin/jvm/functions/Function0;)Lorg/partiql/lang/util/WhenAsExpressionHelper; -} - -public final class org/partiql/lang/util/impl/ResourceAuthority : com/amazon/ionschema/Authority { - public static final field Companion Lorg/partiql/lang/util/impl/ResourceAuthority$Companion; - public fun (Ljava/lang/String;Ljava/lang/ClassLoader;Lcom/amazon/ion/IonSystem;)V - public final fun getIon ()Lcom/amazon/ion/IonSystem; - public fun iteratorFor (Lcom/amazon/ionschema/IonSchemaSystem;Ljava/lang/String;)Lcom/amazon/ionschema/util/CloseableIterator; -} - -public final class org/partiql/lang/util/impl/ResourceAuthority$Companion { - public final fun getResourceAuthority (Lcom/amazon/ion/IonSystem;)Lorg/partiql/lang/util/impl/ResourceAuthority; -} - diff --git a/partiql-lang/build.gradle.kts b/partiql-lang/build.gradle.kts deleted file mode 100644 index 517bab4748..0000000000 --- a/partiql-lang/build.gradle.kts +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file 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. - */ - -plugins { - id(Plugins.conventions) - id(Plugins.jmh) version Versions.jmhGradlePlugin - id(Plugins.library) - id(Plugins.publish) - id(Plugins.testFixtures) -} - -// Disabled for partiql-lang project. -kotlin { - explicitApi = null -} - -dependencies { - api(project(":partiql-ast")) - api(project(":partiql-parser", configuration = "shadow")) - api(project(":partiql-plan")) - api(project(":partiql-planner")) - api(project(":partiql-spi")) - api(project(":partiql-types")) - // - api(Deps.ionElement) - api(Deps.ionJava) - api(Deps.ionSchema) - implementation(Deps.csv) - implementation(Deps.kotlinReflect) - implementation(Deps.kotlinxCoroutines) - - testImplementation(testFixtures(project(":partiql-planner"))) - testImplementation(project(":plugins:partiql-memory")) - testImplementation(Deps.assertj) - testImplementation(Deps.junit4) - testImplementation(Deps.junit4Params) - testImplementation(Deps.junitVintage) // Enables JUnit4 - testImplementation(Deps.mockk) - testImplementation(Deps.kotlinxCoroutinesTest) - - testFixturesImplementation(project(":lib:isl")) - testFixturesImplementation(Deps.kotlinTest) - testFixturesImplementation(Deps.kotlinTestJunit) - testFixturesImplementation(Deps.assertj) - testFixturesImplementation(Deps.junit4) - testFixturesImplementation(Deps.junit4Params) - testFixturesImplementation(Deps.junitApi) - testFixturesImplementation(Deps.junitParams) - testFixturesImplementation(Deps.junitVintage) // Enables JUnit4 - testFixturesImplementation(Deps.mockk) -} - -tasks.shadowJar { - configurations = listOf(project.configurations.shadow.get()) -} - -// Workaround for https://github.com/johnrengelman/shadow/issues/651 -components.withType(AdhocComponentWithVariants::class.java).forEach { c -> - c.withVariantsFromConfiguration(project.configurations.shadowRuntimeElements.get()) { - skip() - } -} - -apiValidation { - /** - * Classes (fully qualified) that are excluded from public API dumps even if they - * contain public API. - */ - ignoredClasses.addAll( - listOf( - "org.partiql.lang.compiler.PartiQLCompilerPipeline" // deprecated - ) - ) -} - -publish { - artifactId = "partiql-lang-kotlin" - name = "PartiQL Lang Kotlin" - description = "An implementation of PartiQL for the JVM written in Kotlin." -} - -jmh { - resultFormat = properties["resultFormat"] as String? ?: "json" - resultsFile = project.file(properties["resultsFile"] as String? ?: "$buildDir/reports/jmh/results.json") - includes = listOfNotNull(properties["include"] as String?) - properties["warmupIterations"]?.let { it -> warmupIterations = Integer.parseInt(it as String) } - properties["iterations"]?.let { it -> iterations = Integer.parseInt(it as String) } - properties["fork"]?.let { it -> fork = Integer.parseInt(it as String) } -} - -tasks.processResources { - // include .g4 in partiql-lang-kotlin JAR for backwards compatibility - from("$rootDir/partiql-parser/src/main/antlr") { - include("**/*.g4") - } - // include partiql.ion in partiql-lang-kotlin JAR for backwards compatibility - from("$rootDir/partiql-ast/src/main/pig") { - include("partiql.ion") - } -} - -tasks.processTestResources { - dependsOn(":partiql-planner:generateResourcePath") - from("${project(":partiql-planner").buildDir}/resources/testFixtures") -} diff --git a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/CompilerInterruptionBenchmark.kt b/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/CompilerInterruptionBenchmark.kt deleted file mode 100644 index 15a4e6c0bb..0000000000 --- a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/CompilerInterruptionBenchmark.kt +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.jmh.benchmarks - -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.BenchmarkMode -import org.openjdk.jmh.annotations.Fork -import org.openjdk.jmh.annotations.Measurement -import org.openjdk.jmh.annotations.Mode -import org.openjdk.jmh.annotations.OutputTimeUnit -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import org.openjdk.jmh.annotations.Warmup -import org.openjdk.jmh.infra.Blackhole -import org.partiql.jmh.utils.FORK_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_TIME_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_TIME_VALUE_RECOMMENDED -import org.partiql.lang.CompilerPipeline -import org.partiql.lang.eval.CompileOptions -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.PartiQLResult -import org.partiql.lang.syntax.PartiQLParserBuilder -import java.util.concurrent.TimeUnit - -/** - * These are the sample benchmarks to demonstrate how JMH benchmarks in PartiQL should be set up. - * Refer this [JMH tutorial](http://tutorials.jenkov.com/java-performance/jmh.html) for more information on [Benchmark]s, - * [BenchmarkMode]s, etc. - */ -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -open class CompilerInterruptionBenchmark { - - companion object { - private const val FORK_VALUE: Int = FORK_VALUE_RECOMMENDED - private const val MEASUREMENT_ITERATION_VALUE: Int = MEASUREMENT_ITERATION_VALUE_RECOMMENDED - private const val MEASUREMENT_TIME_VALUE: Int = MEASUREMENT_TIME_VALUE_RECOMMENDED - private const val WARMUP_ITERATION_VALUE: Int = WARMUP_ITERATION_VALUE_RECOMMENDED - private const val WARMUP_TIME_VALUE: Int = WARMUP_TIME_VALUE_RECOMMENDED - } - - @State(Scope.Thread) - open class MyState { - val parser = PartiQLParserBuilder.standard().build() - val session = EvaluationSession.standard() - val pipeline = CompilerPipeline.standard() - val pipelineWithoutInterruption = CompilerPipeline.build { - compileOptions(CompileOptions.standard().copy(interruptible = false)) - } - - val crossJoins = """ - SELECT - * - FROM - ([1, 2, 3, 4]) as x1, - ([1, 2, 3, 4]) as x2, - ([1, 2, 3, 4]) as x3, - ([1, 2, 3, 4]) as x4, - ([1, 2, 3, 4]) as x5, - ([1, 2, 3, 4]) as x6, - ([1, 2, 3, 4]) as x7, - ([1, 2, 3, 4]) as x8 - """.trimIndent() - val crossJoinsAst = parser.parseAstStatement(crossJoins) - - val crossJoinsWithAggFunction = """ - SELECT - COUNT(*) - FROM - ([1, 2, 3, 4]) as x1, - ([1, 2, 3, 4]) as x2, - ([1, 2, 3, 4]) as x3, - ([1, 2, 3, 4]) as x4, - ([1, 2, 3, 4]) as x5, - ([1, 2, 3, 4]) as x6, - ([1, 2, 3, 4]) as x7, - ([1, 2, 3, 4]) as x8, - ([1, 2, 3, 4]) as x9, - ([1, 2, 3, 4]) as x10, - ([1, 2, 3, 4]) as x11 - """.trimIndent() - val crossJoinsAggAst = parser.parseAstStatement(crossJoinsWithAggFunction) - - val crossJoinsWithAggFunctionAndGroupBy = """ - SELECT - COUNT(*) - FROM - ([1, 2, 3, 4]) as x1, - ([1, 2, 3, 4]) as x2, - ([1, 2, 3, 4]) as x3, - ([1, 2, 3, 4]) as x4, - ([1, 2, 3, 4]) as x5, - ([1, 2, 3, 4]) as x6, - ([1, 2, 3, 4]) as x7, - ([1, 2, 3, 4]) as x8, - ([1, 2, 3, 4]) as x9, - ([1, 2, 3, 4]) as x10, - ([1, 2, 3, 4]) as x11 - GROUP BY x1._1 - """.trimIndent() - val crossJoinsAggGroupAst = parser.parseAstStatement(crossJoinsWithAggFunctionAndGroupBy) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun compileCrossJoinWithInterruptible(state: MyState, blackhole: Blackhole) { - val exprValue = state.pipeline.compile(state.crossJoins) - blackhole.consume(exprValue) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun compileCrossJoinWithoutInterruptible(state: MyState, blackhole: Blackhole) { - val exprValue = state.pipelineWithoutInterruption.compile(state.crossJoins) - blackhole.consume(exprValue) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun compileCrossJoinAggFuncWithInterruptible(state: MyState, blackhole: Blackhole) { - val exprValue = state.pipeline.compile(state.crossJoinsWithAggFunction) - blackhole.consume(exprValue) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun compileCrossJoinAggFuncWithoutInterruptible(state: MyState, blackhole: Blackhole) { - val exprValue = state.pipelineWithoutInterruption.compile(state.crossJoinsWithAggFunction) - blackhole.consume(exprValue) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun compileCrossJoinAggFuncGroupingWithInterruptible(state: MyState, blackhole: Blackhole) { - val exprValue = state.pipeline.compile(state.crossJoinsWithAggFunctionAndGroupBy) - blackhole.consume(exprValue) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun compileCrossJoinAggFuncGroupingWithoutInterruptible(state: MyState, blackhole: Blackhole) { - val exprValue = state.pipelineWithoutInterruption.compile(state.crossJoinsWithAggFunctionAndGroupBy) - blackhole.consume(exprValue) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun evalCrossJoinWithInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipeline.compile(state.crossJoinsAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - blackhole.consume(value) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun evalCrossJoinWithoutInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipelineWithoutInterruption.compile(state.crossJoinsAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - blackhole.consume(value) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun evalCrossJoinAggWithInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipeline.compile(state.crossJoinsAggAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - blackhole.consume(value) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun evalCrossJoinAggWithoutInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipelineWithoutInterruption.compile(state.crossJoinsAggAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - blackhole.consume(value) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun evalCrossJoinAggGroupWithInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipeline.compile(state.crossJoinsAggGroupAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - blackhole.consume(value) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun evalCrossJoinAggGroupWithoutInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipelineWithoutInterruption.compile(state.crossJoinsAggGroupAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - blackhole.consume(value) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun iterCrossJoinWithInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipeline.compile(state.crossJoinsAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - value.forEach { blackhole.consume(it) } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun iterCrossJoinWithoutInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipelineWithoutInterruption.compile(state.crossJoinsAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - value.forEach { blackhole.consume(it) } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun iterCrossJoinAggWithInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipeline.compile(state.crossJoinsAggAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - value.forEach { blackhole.consume(it) } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun iterCrossJoinAggWithoutInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipelineWithoutInterruption.compile(state.crossJoinsAggAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - value.forEach { blackhole.consume(it) } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun iterCrossJoinAggGroupWithInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipeline.compile(state.crossJoinsAggGroupAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - value.forEach { blackhole.consume(it) } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun iterCrossJoinAggGroupWithoutInterruptible(state: MyState, blackhole: Blackhole) { - val result = state.pipelineWithoutInterruption.compile(state.crossJoinsAggGroupAst).evaluate(state.session) as PartiQLResult.Value - val value = result.value - value.forEach { blackhole.consume(it) } - } -} diff --git a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/MultipleLikeBenchmark.kt b/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/MultipleLikeBenchmark.kt deleted file mode 100644 index f58e62db72..0000000000 --- a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/MultipleLikeBenchmark.kt +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.jmh.benchmarks - -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.BenchmarkMode -import org.openjdk.jmh.annotations.Fork -import org.openjdk.jmh.annotations.Measurement -import org.openjdk.jmh.annotations.Mode -import org.openjdk.jmh.annotations.OutputTimeUnit -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import org.openjdk.jmh.annotations.Warmup -import org.openjdk.jmh.infra.Blackhole -import org.partiql.jmh.utils.FORK_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_TIME_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_TIME_VALUE_RECOMMENDED -import org.partiql.lang.CompilerPipeline -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.syntax.PartiQLParserBuilder -import java.util.concurrent.TimeUnit - -/** - * JMH micro-benchmark for parse/compile/eval of multiple `LIKE` expressions. - */ -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -open class MultipleLikeBenchmark { - - companion object { - private const val FORK_VALUE: Int = FORK_VALUE_RECOMMENDED - private const val MEASUREMENT_ITERATION_VALUE: Int = MEASUREMENT_ITERATION_VALUE_RECOMMENDED - private const val MEASUREMENT_TIME_VALUE: Int = MEASUREMENT_TIME_VALUE_RECOMMENDED - private const val WARMUP_ITERATION_VALUE: Int = WARMUP_ITERATION_VALUE_RECOMMENDED - private const val WARMUP_TIME_VALUE: Int = WARMUP_TIME_VALUE_RECOMMENDED - } - - @State(Scope.Thread) - open class MyState { - val parser = PartiQLParserBuilder().build() - val pipeline = CompilerPipeline.standard() - - val name1 = listOf( - "Bob", - "Madden", - "Brycen", - "Bryanna", - "Zayne", - "Jocelynn", - "Breanna", - "Margaret", - "Jasmine", - "Kenyon", - "Aryanna", - "Zackery", - "Jorden", - "Malia", - "Raven", - "Neveah", - "Finley", - "Austin", - "Jaxson", - "Tobias", - "Dominique", - "Devan", - "Colby", - "Tanner", - "Mckenna", - "Kristina", - "Cristal", - "River", - "Taliyah", - "Abagail", - "Spencer", - "Gage", - "Ronnie", - "Amari", - "Jabari", - "Alanna", - "Anderson", - "Saniya", - "Baylee", - "Elisa", - "Savannah", - "Jakobe", - "Sandra", - "Simone", - "Frank", - "Braedon", - "Clark", - "Francisco", - "Roman", - "Matias", - "Messi", - "Elisha", - "Alexander", - "Kadence", - "Karsyn", - "Adonis", - "Ishaan", - "Trevon", - "Ryan", - "Jaelynn", - "Marilyn", - "Emma", - "Avah", - "Jordan", - "Riley", - "Amelie", - "Denisse", - "Darion", - "Lydia", - "Marley", - "Brogan", - "Trace", - "Maeve", - "Elijah", - "Kareem", - "Erick", - "Hope", - "Elisabeth", - "Antwan", - "Francesca", - "Layla", - "Jase", - "Angel", - "Addyson", - "Mckinley", - "Julianna", - "Winston", - "Royce", - "Paola", - "Issac", - "Zachary", - "Niko", - "Shania", - "Colin", - "Jesse", - "Pedro", - "Cheyenne", - "Ashley", - "Karli", - "Bianca", - "Mario" - ) - val name2 = listOf( - "Smith", - "Oconnell", - "Whitehead", - "Carrillo", - "Parrish", - "Monroe", - "Summers", - "Hurst", - "Durham", - "Hardin", - "Hunt", - "Mitchell", - "Pennington", - "Woodward", - "Franklin", - "Martinez", - "Shepard", - "Khan", - "Mcfarland", - "Frey", - "Mckenzie", - "Blair", - "Mercer", - "Callahan", - "Cameron", - "Gilmore", - "Bowers", - "Donovan", - "Meyers", - "Horne", - "Rice", - "Castillo", - "Cain", - "Dickson", - "Valenzuela", - "Silva", - "Prince", - "Vance", - "Berry", - "Coffey", - "Young", - "Walker", - "Burch", - "Ross", - "Mejia", - "Zuniga", - "Haney", - "Jordan", - "Love", - "Larsen", - "Bowman", - "Werner", - "Greer", - "Krause", - "Bishop", - "Day", - "Luna", - "Patrick", - "Adkins", - "Benson", - "Mcconnell", - "Sanchez", - "Villa", - "Wu", - "Duke", - "Fisher", - "Hess", - "Lawrence", - "Perry", - "Hardy", - "Wyatt", - "Mcknight", - "Thomas", - "Trevino", - "Flowers", - "Cisneros", - "Coleman", - "Sanders", - "Good", - "Newton", - "Carpenter", - "Garza", - "Barber", - "Swanson", - "Owen", - "Anderson", - "Bright", - "Beck", - "Lawson", - "Jones", - "Davila", - "Porter", - "Dougherty", - "Stevenson", - "Malone", - "Garrison", - "Bates", - "Wheeler", - "Petty", - "Rojas", - "Townsend", - ) - - // cartesian product of name1 x name2 (e.g., listOf("Bob Smith", ... "Mario Townsend")) - val combined = name1.flatMap { n1 -> name2.map { n2 -> n1 + " " + n2 } } - var nextId = 1 - val random = kotlin.random.Random(42) - private val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') + listOf(' ') - val employeeData = combined.map { name -> - val prefix = - (1..random.nextInt(5, 100)).map { kotlin.random.Random.nextInt(0, charPool.size) }.map(charPool::get) - .joinToString("") - val suffix = - (1..random.nextInt(5, 100)).map { kotlin.random.Random.nextInt(0, charPool.size) }.map(charPool::get) - .joinToString("") - val id = nextId++ - "{ 'id': $id, 'name': '$prefix $name $suffix' }" - } - - val data = """ - { - 'hr': { - 'employees': <<""" + employeeData.joinToString(",") + """>> - } - } - """.trimIndent() - - val bindings = pipeline.compile(parser.parseAstStatement(data)).eval(EvaluationSession.standard()).bindings - val session = EvaluationSession.build { globals(bindings) } - - val employeeData10 = employeeData + employeeData + employeeData + employeeData + employeeData + - employeeData + employeeData + employeeData + employeeData + employeeData - val data10 = """ - { - 'hr': { - 'employees': <<""" + employeeData10.joinToString(",") + """>> - } - } - """.trimIndent() - - val bindings10 = pipeline.compile(parser.parseAstStatement(data10)).eval(EvaluationSession.standard()).bindings - val session10 = EvaluationSession.build { globals(bindings10) } - - val query15 = """ - SELECT * - FROM hr.employees as emp - WHERE lower(emp.name) LIKE '%bob smith%' - OR lower(emp.name) LIKE '%gage swanson%' - OR lower(emp.name) LIKE '%riley perry%' - OR lower(emp.name) LIKE '%sandra woodward%' - OR lower(emp.name) LIKE '%abagail oconnell%' - OR lower(emp.name) LIKE '%amari duke%' - OR lower(emp.name) LIKE '%elisha wyatt%' - OR lower(emp.name) LIKE '%aryanna hess%' - OR lower(emp.name) LIKE '%bryanna jones%' - OR lower(emp.name) LIKE '%trace gilmore%' - OR lower(emp.name) LIKE '%antwan stevenson%' - OR lower(emp.name) LIKE '%julianna callahan%' - OR lower(emp.name) LIKE '%jaelynn trevino%' - OR lower(emp.name) LIKE '%kadence bates%' - OR lower(emp.name) LIKE '%jakobe townsend%' - """ - val astStatement15 = parser.parseAstStatement(query15) - val expression15 = pipeline.compile(astStatement15) - - val query30 = query15 + """ - OR lower(emp.name) LIKE '%austin pennington%' - OR lower(emp.name) LIKE '%colby woodward%' - OR lower(emp.name) LIKE '%brycen blair%' - OR lower(emp.name) LIKE '%cristal mercer%' - OR lower(emp.name) LIKE '%river gilmore%' - OR lower(emp.name) LIKE '%saniya bowers%' - OR lower(emp.name) LIKE '%braedon ross%' - OR lower(emp.name) LIKE '%clark mejia%' - OR lower(emp.name) LIKE '%ryan day%' - OR lower(emp.name) LIKE '%marilyn luna%' - OR lower(emp.name) LIKE '%avah sanchez%' - OR lower(emp.name) LIKE '%amelie wu%' - OR lower(emp.name) LIKE '%paola duke%' - OR lower(emp.name) LIKE '%jesse trevino%' - OR lower(emp.name) LIKE '%bianca cisneros%' - """ - val astStatement30 = parser.parseAstStatement(query30) - val expression30 = pipeline.compile(astStatement30) - } - - /** - * Benchmarks parsing a query containing 15 `OR`ed `LIKE` expressions - */ - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLParser15(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.query15) - blackhole.consume(expr) - } - - /** - * Benchmarks compiling a query containing 15 `OR`ed `LIKE` expressions - */ - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLCompiler15(state: MyState, blackhole: Blackhole) { - val exprValue = state.pipeline.compile(state.astStatement15) - blackhole.consume(exprValue) - } - - /** - * Benchmarks evaluating a query containing 15 `OR`ed `LIKE` expressions - * against 10,201 rows of strings each of which are ~20 to ~220 codepoints long - */ - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLEvaluator15(state: MyState, blackhole: Blackhole) { - val exprValue = state.expression15.eval(state.session) - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - /** - * Benchmarks parsing a query containing 30 `OR`ed `LIKE` expressions - */ - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLParser30(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.query30) - blackhole.consume(expr) - } - - /** - * Benchmarks compiling a query containing 30 `OR`ed `LIKE` expressions - */ - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLCompiler30(state: MyState, blackhole: Blackhole) { - val exprValue = state.pipeline.compile(state.astStatement30) - blackhole.consume(exprValue) - } - - /** - * Benchmarks evaluating a query containing 30 `OR`ed `LIKE` expressions - * against 10,201 rows of strings each of which are ~20 to ~220 codepoints long - */ - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLEvaluator30(state: MyState, blackhole: Blackhole) { - val exprValue = state.expression30.eval(state.session) - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - /** - * Benchmarks evaluating a query containing 15 `OR`ed `LIKE` expressions - * against 102,010 rows of strings each of which are ~20 to ~220 codepoints long - */ - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLEvaluator30WithData10(state: MyState, blackhole: Blackhole) { - val exprValue = state.expression30.eval(state.session10) - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } -} diff --git a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/ParserBenchmark.kt b/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/ParserBenchmark.kt deleted file mode 100644 index f46a28599f..0000000000 --- a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/ParserBenchmark.kt +++ /dev/null @@ -1,1342 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.jmh.benchmarks - -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.BenchmarkMode -import org.openjdk.jmh.annotations.Fork -import org.openjdk.jmh.annotations.Measurement -import org.openjdk.jmh.annotations.Mode -import org.openjdk.jmh.annotations.OutputTimeUnit -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import org.openjdk.jmh.annotations.Warmup -import org.openjdk.jmh.infra.Blackhole -import org.partiql.jmh.utils.FORK_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_TIME_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_TIME_VALUE_RECOMMENDED -import org.partiql.lang.syntax.ParserException -import org.partiql.lang.syntax.PartiQLParserBuilder -import java.util.concurrent.TimeUnit - -// TODO: If https://github.com/benchmark-action/github-action-benchmark/issues/141 gets fixed, we can move to using -// parameterized tests. This file intentionally uses the same prefix `parse` and `parseFail` for each benchmark. It -// expects that the parameter `name` will be used in the future, so it adds `Name` to prefix each argument. This will -// potentially make it easier to transition to the continuous benchmarking framework if parameterized benchmarks -// are supported. - -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -internal open class ParserBenchmark { - - companion object { - private const val FORK_VALUE: Int = FORK_VALUE_RECOMMENDED - private const val MEASUREMENT_ITERATION_VALUE: Int = MEASUREMENT_ITERATION_VALUE_RECOMMENDED - private const val MEASUREMENT_TIME_VALUE: Int = MEASUREMENT_TIME_VALUE_RECOMMENDED - private const val WARMUP_ITERATION_VALUE: Int = WARMUP_ITERATION_VALUE_RECOMMENDED - private const val WARMUP_TIME_VALUE: Int = WARMUP_TIME_VALUE_RECOMMENDED - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQuerySimple(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::querySimple.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameNestedParen(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::nestedParen.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSomeJoins(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::someJoins.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSeveralJoins(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::severalJoins.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSomeSelect(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::someSelect.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSeveralSelect(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::severalSelect.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSomeProjections(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::someProjections.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSeveralProjections(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::severalProjections.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQueryFunc(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::queryFunc.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQueryFuncInProjection(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::queryFuncInProjection.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQueryList(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::queryList.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQuery15OrsAndLikes(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::query15OrsAndLikes.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQuery30Plus(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::query30Plus.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQueryNestedSelect(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::queryNestedSelect.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameGraphPattern(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::graphPattern.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameGraphPreFilters(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::graphPreFilters.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameManyJoins(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::manyJoins.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameTimeZone(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::timeZone.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameCaseWhenThen(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::caseWhenThen.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSimpleInsert(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::simpleInsert.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameExceptUnionIntersectSixty(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::exceptUnionIntersectSixty.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameExec20Expressions(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::exec20Expressions.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameFromLet(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::fromLet.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameGroupLimit(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::groupLimit.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNamePivot(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::pivot.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameLongFromSourceOrderBy(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::longFromSourceOrderBy.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameNestedAggregates(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::nestedAggregates.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameComplexQuery(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::complexQuery.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameComplexQuery01(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::complexQuery01.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameComplexQuery02(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::complexQuery02.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameVeryLongQuery(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::veryLongQuery.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameVeryLongQuery01(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.queries[state::veryLongQuery01.name]!!) - blackhole.consume(expr) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQuerySimple(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::querySimple.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameNestedParen(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::nestedParen.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSomeJoins(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::someJoins.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSeveralJoins(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::severalJoins.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSomeSelect(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::someSelect.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSeveralSelect(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::severalSelect.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSomeProjections(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::someProjections.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSeveralProjections(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::severalProjections.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQueryFunc(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::queryFunc.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQueryFuncInProjection(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::queryFuncInProjection.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQueryList(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::queryList.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQuery15OrsAndLikes(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::query15OrsAndLikes.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQuery30Plus(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::query30Plus.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQueryNestedSelect(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::queryNestedSelect.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameGraphPattern(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::graphPattern.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameGraphPreFilters(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::graphPreFilters.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameManyJoins(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::manyJoins.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameTimeZone(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::timeZone.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameCaseWhenThen(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::caseWhenThen.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSimpleInsert(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::simpleInsert.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameExceptUnionIntersectSixty(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::exceptUnionIntersectSixty.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameExec20Expressions(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::exec20Expressions.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameFromLet(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::fromLet.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameGroupLimit(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::groupLimit.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNamePivot(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::pivot.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameLongFromSourceOrderBy(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::longFromSourceOrderBy.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameNestedAggregates(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::nestedAggregates.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameComplexQuery(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::complexQuery.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameComplexQuery01(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::complexQuery01.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameComplexQuery02(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::complexQuery02.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameVeryLongQuery(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::veryLongQuery.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameVeryLongQuery01(state: MyState, blackhole: Blackhole) { - try { - val expr = state.parser.parseAstStatement(state.queriesFail[state::veryLongQuery01.name]!!) - blackhole.consume(expr) - throw RuntimeException() - } catch (ex: ParserException) { - blackhole.consume(ex) - } - } - - @State(Scope.Thread) - open class MyState { - - val parser = PartiQLParserBuilder().build() - - val query15OrsAndLikes = """ - SELECT * - FROM hr.employees as emp - WHERE lower(emp.name) LIKE '%bob smith%' - OR lower(emp.name) LIKE '%gage swanson%' - OR lower(emp.name) LIKE '%riley perry%' - OR lower(emp.name) LIKE '%sandra woodward%' - OR lower(emp.name) LIKE '%abagail oconnell%' - OR lower(emp.name) LIKE '%amari duke%' - OR lower(emp.name) LIKE '%elisha wyatt%' - OR lower(emp.name) LIKE '%aryanna hess%' - OR lower(emp.name) LIKE '%bryanna jones%' - OR lower(emp.name) LIKE '%trace gilmore%' - OR lower(emp.name) LIKE '%antwan stevenson%' - OR lower(emp.name) LIKE '%julianna callahan%' - OR lower(emp.name) LIKE '%jaelynn trevino%' - OR lower(emp.name) LIKE '%kadence bates%' - OR lower(emp.name) LIKE '%jakobe townsend%' - """ - - val query30Plus = """ - 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 - """ - - val querySimple = """ - SELECT a FROM t - """ - - val queryNestedSelect = """ - SELECT - ( - SELECT a AS p - FROM ( - SELECT VALUE b - FROM some_table - WHERE 3 = 4 - ) AS some_wrapped_table - WHERE id = 3 - ) AS projectionQuery - FROM ( - SELECT everything - FROM ( - SELECT * - FROM someSourceTable AS t - LET 5 + t.b AS x - WHERE x = 2 - GROUP BY t.a AS k - GROUP AS g - ORDER BY t.d - ) AS someTable - ) - LET (SELECT a FROM smallTable) AS letVariable - WHERE letVariable > 4 - GROUP BY t.a AS groupKey - """ - - val queryList = """ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] - """ - - val queryFunc = """ - f(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29) - """ - - val queryFuncInProjection = """ - SELECT - f(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29) - FROM t - """ - - val someJoins = """ - SELECT a - FROM a, b, c - """ - - val severalJoins = """ - SELECT a - FROM a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p - """ - - val someProjections = """ - SELECT a, b, c - FROM t - """ - - val severalProjections = """ - SELECT a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p - FROM t - """ - - val someSelect = """ - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) - """ - - val severalSelect = """ - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) - """ - - val nestedParen = """ - ((((((((((((((((((((((((((((((0)))))))))))))))))))))))))))))) - """ - - val graphPreFilters = """ - SELECT u as banCandidate - FROM g - MATCH (p:Post Where p.isFlagged = true) <-[:createdPost]- (u:Usr WHERE u.isBanned = false AND u.karma < 20) -[:createdComment]->(c:Comment WHERE c.isFlagged = true) - WHERE p.title LIKE '%considered harmful%' - """.trimIndent() - - val graphPattern = """ - SELECT the_a.name AS src, the_b.name AS dest - FROM my_graph MATCH (the_a:a) -[the_y:y]-> (the_b:b) - WHERE the_y.score > 10 - """.trimIndent() - - val manyJoins = """ - SELECT x FROM a INNER CROSS JOIN b CROSS JOIN c LEFT JOIN d ON e RIGHT OUTER CROSS JOIN f OUTER JOIN g ON h - """ - - val timeZone = "TIME WITH TIME ZONE '23:59:59.123456789+18:00'" - - val caseWhenThen = "CASE WHEN name = 'zoe' THEN 1 WHEN name > 'kumo' THEN 2 ELSE 0 END" - - val simpleInsert = """ - INSERT INTO foo VALUE 1 AT bar RETURNING MODIFIED OLD bar, MODIFIED NEW bar, ALL NEW * - """ - - val exceptUnionIntersectSixty = """ - a EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - """ - - val exec20Expressions = """ - EXEC - a - b, - a, - b, - c, - d, - 123, - "aaaaa", - 'aaaaa', - @ident, - 1 + 1, - 2 + 2, - a, - a, - a, - a, - a, - a, - a, - a - """ - - val fromLet = - "SELECT C.region, MAX(nameLength) AS maxLen FROM C LET char_length(C.name) AS nameLength GROUP BY C.region" - - val groupLimit = - "SELECT g FROM `[{foo: 1, bar: 10}, {foo: 1, bar: 11}]` AS f GROUP BY f.foo GROUP AS g LIMIT 1" - - val pivot = """ - PIVOT foo.a AT foo.b - FROM <<{'a': 1, 'b':'I'}, {'a': 2, 'b':'II'}, {'a': 3, 'b':'III'}>> AS foo - LIMIT 1 OFFSET 1 - """.trimIndent() - - val longFromSourceOrderBy = """ - SELECT * - FROM [{'a': {'a': 5}}, {'a': {'a': 'b'}}, {'a': {'a': true}}, {'a': {'a': []}}, {'a': {'a': {}}}, {'a': {'a': <<>>}}, {'a': {'a': `{{}}`}}, {'a': {'a': null}}] - ORDER BY a DESC - """.trimIndent() - - val nestedAggregates = """ - SELECT - i2 AS outerKey, - g2 AS outerGroupAs, - MIN(innerQuery.innerSum) AS outerMin, - ( - SELECT VALUE SUM(i2) - FROM << 0, 1 >> - ) AS projListSubQuery - FROM ( - SELECT - i, - g, - SUM(col1) AS innerSum - FROM simple_1_col_1_group_2 AS innerFromSource - GROUP BY col1 AS i GROUP AS g - ) AS innerQuery - GROUP BY innerQuery.i AS i2, innerQuery.g AS g2 - """.trimIndent() - - val complexQuery = """ - 1 + ( - SELECT a, b, c - FROM [ - { 'a': 1} - ] AS t - LET x AS y - WHERE y > 2 AND y > 3 AND y > 4 - GROUP BY t.a, t.b AS b, t.c AS c - GROUP AS d - ORDER BY x - LIMIT 1 + 22222222222222222 - OFFSET x + y + z + a + b + c - ) + ( - CAST( - '45678920irufji332r94832fhedjcd2wqbxucri3' - AS INT - ) - ) + [ - 1, 2, 3, 4, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 - ] - ((((((((((2)))))))))) + ( - SELECT VALUE { 'a': a } FROM t WHERE t.a > 3 - ) - """.trimIndent() - - val complexQuery01 = """ - SELECT - DATE_FORMAT(co.order_date, '%Y-%m') AS order_month, - DATE_FORMAT(co.order_date, '%Y-%m-%d') AS order_day, - COUNT(DISTINCT co.order_id) AS num_orders, - COUNT(ol.book_id) AS num_books, - SUM(ol.price) AS total_price - FROM cust_order co - INNER JOIN order_line ol ON co.order_id = ol.order_id - GROUP BY - DATE_FORMAT(co.order_date, '%Y-%m'), - DATE_FORMAT(co.order_date, '%Y-%m-%d') - ORDER BY co.order_date ASC; - """.trimIndent() - - val complexQuery02 = """ - SELECT - c.calendar_date, - c.calendar_year, - c.calendar_month, - c.calendar_dayName, - COUNT(DISTINCT sub.order_id) AS num_orders, - COUNT(sub.book_id) AS num_books, - SUM(sub.price) AS total_price, - SUM(COUNT(sub.book_id)) AS running_total_num_books, - LAG(COUNT(sub.book_id), 7) AS prev_books - FROM calendar_days c - LEFT JOIN ( - SELECT - co.order_date, - co.order_id, - ol.book_id, - ol.price - FROM cust_order co - INNER JOIN order_line ol ON co.order_id = ol.order_id - ) sub ON c.calendar_date = sub.order_date - GROUP BY c.calendar_date, c.calendar_year, c.calendar_month, c.calendar_dayname - ORDER BY c.calendar_date ASC; - """.trimIndent() - - val veryLongQuery = """ - SELECT - e.employee_id AS "Employee#", e.first_name || '' || e.last_name AS "Name", e.email AS "Email", - e.phone_number AS "Phone", TO_CHAR(e.hire_date, 'MM/DD/YYYY') AS "Hire Date", - TO_CHAR(e.salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS=''.,''NLS_CURRENCY=''${'$'}''') AS "Salary", - e.commission_pct AS "Comission%", - 'works as' || j.job_title || 'in' || d.department_name || ' department (manager: ' - || dm.first_name || '' || dm.last_name || ')andimmediatesupervisor:' || m.first_name || '' || m.last_name AS "CurrentJob", - TO_CHAR(j.min_salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS=''.,''NLS_CURRENCY=''${'$'}''') || '-' || - TO_CHAR(j.max_salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS=''.,''NLS_CURRENCY=''${'$'}''') AS "CurrentSalary", - l.street_address || ',' || l.postal_code || ',' || l.city || ',' || l.state_province || ',' - || c.country_name || '(' || r.region_name || ')' AS "Location", - jh.job_id AS "HistoryJobID", - 'worked from' || TO_CHAR(jh.start_date, 'MM/DD/YYYY') || 'to' || TO_CHAR(jh.end_date, 'MM/DD/YYYY') || - 'as' || jj.job_title || 'in' || dd.department_name || 'department' AS "HistoryJobTitle" - FROM employees e - JOIN jobs j - ON e.job_id = j.job_id - LEFT JOIN employees m - ON e.manager_id = m.employee_id - LEFT JOIN departments d - ON d.department_id = e.department_id - LEFT JOIN employees dm - ON d.manager_id = dm.employee_id - LEFT JOIN locations l - ON d.location_id = l.location_id - LEFT JOIN countries c - ON l.country_id = c.country_id - LEFT JOIN regions r - ON c.region_id = r.region_id - LEFT JOIN job_history jh - ON e.employee_id = jh.employee_id - LEFT JOIN jobs jj - ON jj.job_id = jh.job_id - LEFT JOIN departments dd - ON dd.department_id = jh.department_id - - ORDER BY e.employee_id; - """.trimIndent() - - val veryLongQuery01 = """ - SELECT - id as feedId, - (IF(groupId > 0, groupId, IF(friendId > 0, friendId, userId))) as wallOwnerId, - (IF(groupId > 0 or friendId > 0, userId, NULL)) as guestWriterId, - (IF(groupId > 0 or friendId > 0, userId, NULL)) as guestWriterType, - case - when type = 2 then 1 - when type = 1 then IF(media_count = 1, 2, 4) - when type = 5 then IF(media_count = 1, IF(albumName = 'Audio Feeds', 5, 6), 7) - when type = 6 then IF(media_count = 1, IF(albumName = 'Video Feeds', 8, 9), 10) - end as contentType, - albumId, - albumName, - addTime, - IF(validity > 0,IF((validity - updateTime) / 86400000 > 1,(validity - updateTime) / 86400000, 1),0) as validity, - updateTime, - status, - location, - latitude as locationLat, - longitude as locationLon, - sharedFeedId as parentFeedId, - case - when privacy = 2 or privacy = 10 then 15 - when privacy = 3 then 25 - else 1 - end as privacy, - pagefeedcategoryid, - case - when lastSharedFeedId = 2 then 10 - when lastSharedFeedId = 3 then 15 - when lastSharedFeedId = 4 then 25 - when lastSharedFeedId = 5 then 20 - when lastSharedFeedId = 6 then 99 - else 1 - end as wallOwnerType, - (ISNULL(latitude) or latitude = 9999.0 or ISNULL(longitude) or longitude = 9999.0) as latlongexists, - (SELECT concat('[',GROUP_CONCAT(moodId),']') FROM feedactivities WHERE newsFeedId = newsfeed.id) as feelings, - (SELECT concat('[',GROUP_CONCAT(userId),']') FROM feedtags WHERE newsFeedId = newsfeed.id) as withTag, - (SELECT concat('{',GROUP_CONCAT(pos,':', friendId),'}') FROM statustags WHERE newsFeedId = newsfeed.id) as textTag, - albumType, - defaultCategoryType, - linkType,linkTitle,linkURL,linkDesc,linkImageURL,linkDomain, -- Link Content - title,description,shortDescription,newsUrl,externalUrlOption, -- Additional Content - url, height, width, thumnail_url, thumnail_height, thumbnail_width, duration, artist -- Media - FROM - (newsfeed LEFT JOIN - ( - SELECT - case - when (mediaalbums.media_type = 1 and album_name = 'AudioFeeds') - or (mediaalbums.media_type = 2 and album_name = 'VideoFeeds') - then -1 * mediaalbums.user_id else mediaalbums.id - end as albumId, - album_name as albumName, - newsFeedId, - (NULL) as height, - (NULL) as width, - media_thumbnail_url as thumnail_url, - max(thumb_image_height) as thumnail_height, - max(thumb_image_width) as thumbnail_width, - max(media_duration) as duration, - case - when mediaalbums.media_type = 1 and album_name = 'AudioFeeds' - then 4 - when mediaalbums.media_type = 2 and album_name = 'VideoFeeds' - then 5 else 8 - end as albumType, - count(mediacontents.id) as media_count, - media_artist as artist - FROM - (mediaalbums INNER JOIN mediacontents ON mediaalbums.id = mediacontents.album_id) - INNER JOIN newsfeedmediacontents - ON newsfeedmediacontents.contentId = mediacontents.id group by newsfeedid - UNION - SELECT - -1 * userId as albumId, - newsFeedId,imageUrl as url, - max(imageHeight) as height, - max(imageWidth) as width, - (NULL) as thumnail_url, - (NULL) as thumnail_height, - (NULL) as thumbnail_width, - (NULL) as duration, - case - when albumId = 'default' then 1 - when albumId = 'profileimages' then 2 - when albumId = 'coverimages' then 3 - end as albumType, - count(imageid) as media_count, - (NULL) as artist - FROM userimages - INNER JOIN newsfeedimages on userimages.id = newsfeedimages.imageId - group by newsfeedid - ) album - ON newsfeed.id = album.newsfeedId - ) - LEFT JOIN - ( - select newsPortalFeedId as feedid, - title,description,shortDescription,newsUrl,externalUrlOption, newsPortalCategoryId as pagefeedcategoryid, - (15) as defaultCategoryType from newsportalFeedInfo - UNION - select businessPageFeedId as feedid,title,description,shortDescription,newsUrl,externalUrlOption, - businessPageCategoryId as pagefeedcategoryid,(25) as defaultCategoryType from businessPageFeedInfo - UNION - select newsfeedId as feedid,(NULL) as title,description,(NULL) as shortDescription,(NULL) as newsUrl, - (NULL) as externalUrlOption, categoryMappingId as pagefeedcategoryid, - (20) as defaultCategoryType from mediaPageFeedInfo - ) page - ON newsfeed.id = page.feedId WHERE privacy != 10 - """.trimIndent() - - val queries = mapOf( - ::querySimple.name to querySimple, - ::nestedParen.name to nestedParen, - ::someJoins.name to someJoins, - ::severalJoins.name to severalJoins, - ::someSelect.name to someSelect, - ::severalSelect.name to severalSelect, - ::someProjections.name to someProjections, - ::severalProjections.name to severalProjections, - ::queryFunc.name to queryFunc, - ::queryFuncInProjection.name to queryFuncInProjection, - ::queryList.name to queryList, - ::query15OrsAndLikes.name to query15OrsAndLikes, - ::query30Plus.name to query30Plus, - ::queryNestedSelect.name to queryNestedSelect, - ::graphPattern.name to graphPattern, - ::graphPreFilters.name to graphPreFilters, - ::manyJoins.name to manyJoins, - ::timeZone.name to timeZone, - ::caseWhenThen.name to caseWhenThen, - ::simpleInsert.name to simpleInsert, - ::exceptUnionIntersectSixty.name to exceptUnionIntersectSixty, - ::exec20Expressions.name to exec20Expressions, - ::fromLet.name to fromLet, - ::groupLimit.name to groupLimit, - ::pivot.name to pivot, - ::longFromSourceOrderBy.name to longFromSourceOrderBy, - ::nestedAggregates.name to nestedAggregates, - ::complexQuery.name to complexQuery, - ::complexQuery01.name to complexQuery01, - ::complexQuery02.name to complexQuery02, - ::veryLongQuery.name to veryLongQuery, - ::veryLongQuery01.name to veryLongQuery01, - ) - - val queriesFail = queries.map { (name, query) -> - val splitQuery = query.split("\\s".toRegex()).toMutableList() - val index = (splitQuery.lastIndex * .50).toInt() - splitQuery.add(index, ";") - name to splitQuery.joinToString(separator = " ") - }.toMap() - } -} diff --git a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLBenchmark.kt b/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLBenchmark.kt deleted file mode 100644 index 2d97ed3ecc..0000000000 --- a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLBenchmark.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.jmh.benchmarks - -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.BenchmarkMode -import org.openjdk.jmh.annotations.Fork -import org.openjdk.jmh.annotations.Measurement -import org.openjdk.jmh.annotations.Mode -import org.openjdk.jmh.annotations.OutputTimeUnit -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import org.openjdk.jmh.annotations.Warmup -import org.openjdk.jmh.infra.Blackhole -import org.partiql.jmh.utils.FORK_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_TIME_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_TIME_VALUE_RECOMMENDED -import org.partiql.lang.CompilerPipeline -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.syntax.PartiQLParserBuilder -import java.util.concurrent.TimeUnit - -/** - * These are the sample benchmarks to demonstrate how JMH benchmarks in PartiQL should be set up. - * Refer this [JMH tutorial](http://tutorials.jenkov.com/java-performance/jmh.html) for more information on [Benchmark]s, - * [BenchmarkMode]s, etc. - */ -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -open class PartiQLBenchmark { - - companion object { - private const val FORK_VALUE: Int = FORK_VALUE_RECOMMENDED - private const val MEASUREMENT_ITERATION_VALUE: Int = MEASUREMENT_ITERATION_VALUE_RECOMMENDED - private const val MEASUREMENT_TIME_VALUE: Int = MEASUREMENT_TIME_VALUE_RECOMMENDED - private const val WARMUP_ITERATION_VALUE: Int = WARMUP_ITERATION_VALUE_RECOMMENDED - private const val WARMUP_TIME_VALUE: Int = WARMUP_TIME_VALUE_RECOMMENDED - } - - @State(Scope.Thread) - open class MyState { - val parser = PartiQLParserBuilder.standard().build() - val pipeline = CompilerPipeline.standard() - - val data = """ - { - 'hr': { - 'employeesNestScalars': << - { - 'id': 3, - 'name': 'Bob Smith', - 'title': null, - 'projects': [ - 'AWS Redshift Spectrum querying', - 'AWS Redshift security', - 'AWS Aurora security' - ] - }, - { - 'id': 4, - 'name': 'Susan Smith', - 'title': 'Dev Mgr', - 'projects': [] - }, - { - 'id': 6, - 'name': 'Jane Smith', - 'title': 'Software Eng 2', - 'projects': [ 'AWS Redshift security' ] - } - >> - } - } - """.trimIndent() - val bindings = pipeline.compile(parser.parseAstStatement(data)).eval(EvaluationSession.standard()).bindings - val session = EvaluationSession.build { globals(bindings) } - - val query = "SELECT * FROM hr.employeesNestScalars" - val astStatement = parser.parseAstStatement(query) - val expression = pipeline.compile(astStatement) - } - - /** - * Example PartiQL benchmark for parsing a query - */ - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLParser(state: MyState, blackhole: Blackhole) { - val expr = state.parser.parseAstStatement(state.query) - blackhole.consume(expr) - } - - /** - * Example PartiQL benchmark for compiling a query - */ - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLCompiler(state: MyState, blackhole: Blackhole) { - val exprValue = state.pipeline.compile(state.astStatement) - blackhole.consume(exprValue) - } - - /** - * Example PartiQL benchmark for evaluating a query - */ - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testPartiQLEvaluator(state: MyState, blackhole: Blackhole) { - val exprValue = state.expression.eval(state.session) - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } -} diff --git a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLCompilerPipelineAsyncBenchmark.kt b/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLCompilerPipelineAsyncBenchmark.kt deleted file mode 100644 index 5e9b2f3c90..0000000000 --- a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLCompilerPipelineAsyncBenchmark.kt +++ /dev/null @@ -1,380 +0,0 @@ -package org.partiql.jmh.benchmarks - -import com.amazon.ion.IonSystem -import com.amazon.ion.system.IonSystemBuilder -import kotlinx.coroutines.runBlocking -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.BenchmarkMode -import org.openjdk.jmh.annotations.Fork -import org.openjdk.jmh.annotations.Measurement -import org.openjdk.jmh.annotations.Mode -import org.openjdk.jmh.annotations.OutputTimeUnit -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import org.openjdk.jmh.annotations.Warmup -import org.openjdk.jmh.infra.Blackhole -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.jmh.utils.FORK_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_TIME_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_TIME_VALUE_RECOMMENDED -import org.partiql.lang.compiler.PartiQLCompilerPipelineAsync -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.PartiQLResult -import org.partiql.lang.planner.GlobalResolutionResult -import org.partiql.lang.syntax.PartiQLParserBuilder -import java.util.concurrent.TimeUnit - -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -open class PartiQLCompilerPipelineAsyncBenchmark { - companion object { - private const val FORK_VALUE: Int = FORK_VALUE_RECOMMENDED - private const val MEASUREMENT_ITERATION_VALUE: Int = MEASUREMENT_ITERATION_VALUE_RECOMMENDED - private const val MEASUREMENT_TIME_VALUE: Int = MEASUREMENT_TIME_VALUE_RECOMMENDED - private const val WARMUP_ITERATION_VALUE: Int = WARMUP_ITERATION_VALUE_RECOMMENDED - private const val WARMUP_TIME_VALUE: Int = WARMUP_TIME_VALUE_RECOMMENDED - } - - @State(Scope.Thread) - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - open class MyState { - private val parser = PartiQLParserBuilder.standard().build() - private val myIonSystem: IonSystem = IonSystemBuilder.standard().build() - - private fun tableWithRows(numRows: Int): ExprValue { - val allRows = (1..numRows).joinToString { index -> - """ - { - "id": $index, - "someString": "some string foo $index", - "someDecimal": $index.00, - "someBlob": {{ dHdvIHBhZGRpbmcgY2hhcmFjdGVycw== }}, - "someTimestamp": 2007-02-23T12:14:15.${index}Z - } - """.trimIndent() - } - val data = "[ $allRows ]" - return ExprValue.of( - myIonSystem.singleValue(data) - ) - } - - private val bindings = Bindings.ofMap( - mapOf( - "t1" to tableWithRows(1), - "t10" to tableWithRows(10), - "t100" to tableWithRows(100), - "t1000" to tableWithRows(1000), - "t10000" to tableWithRows(10000), - "t100000" to tableWithRows(100000), - ) - ) - - private val parameters = listOf( - ExprValue.newInt(5), // WHERE `id` > 5 - ExprValue.newInt(1000000), // LIMIT 1000000 - ExprValue.newInt(3), // OFFSET 3 * 2 - ExprValue.newInt(2), // ------------^ - ) - val session = EvaluationSession.build { - globals(bindings) - parameters(parameters) - } - - val pipeline = PartiQLCompilerPipelineAsync.build { - planner.globalVariableResolver { - val value = session.globals[it] - if (value != null) { - GlobalResolutionResult.GlobalVariable(it.name) - } else { - GlobalResolutionResult.Undefined - } - } - } - - val query1 = parser.parseAstStatement( - """ - SELECT * FROM t100000 - """.trimIndent() - ) - val query2 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() - """.trimIndent() - ) - val query3 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() - LIMIT ${Int.MAX_VALUE} - """.trimIndent() - ) - val query4 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() - ORDER BY t100000.id DESC - """.trimIndent() - ) - val query5 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() AND t100000.id > ? - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - val query6 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() AND t100000.id > ? - ORDER BY t100000.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - val query7 = parser.parseAstStatement( - """ - SELECT * - FROM t10000 - WHERE t10000.someTimestamp < UTCNOW() AND t10000.id > ? - ORDER BY t10000.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - val query8 = parser.parseAstStatement( - """ - SELECT * - FROM t1000 - WHERE t1000.someTimestamp < UTCNOW() AND t1000.id > ? - ORDER BY t1000.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - val query9 = parser.parseAstStatement( - """ - SELECT * - FROM t100 - WHERE t100.someTimestamp < UTCNOW() AND t100.id > ? - ORDER BY t100.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - val query10 = parser.parseAstStatement( - """ - SELECT * - FROM t10 - WHERE t10.someTimestamp < UTCNOW() AND t10.id > ? - ORDER BY t10.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - val query11 = parser.parseAstStatement( - """ - SELECT * - FROM t1 - WHERE t1.someTimestamp < UTCNOW() AND t1.id > ? - ORDER BY t1.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - - val statement1 = runBlocking { pipeline.compile(query1) } - val statement2 = runBlocking { pipeline.compile(query2) } - val statement3 = runBlocking { pipeline.compile(query3) } - val statement4 = runBlocking { pipeline.compile(query4) } - val statement5 = runBlocking { pipeline.compile(query5) } - val statement6 = runBlocking { pipeline.compile(query6) } - val statement7 = runBlocking { pipeline.compile(query7) } - val statement8 = runBlocking { pipeline.compile(query8) } - val statement9 = runBlocking { pipeline.compile(query9) } - val statement10 = runBlocking { pipeline.compile(query10) } - val statement11 = runBlocking { pipeline.compile(query11) } - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery1(state: MyState, blackhole: Blackhole) = runBlocking { - val statement = state.pipeline.compile(state.query1) - blackhole.consume(statement) - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery2(state: MyState, blackhole: Blackhole) = runBlocking { - val statement = state.pipeline.compile(state.query2) - blackhole.consume(statement) - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery3(state: MyState, blackhole: Blackhole) = runBlocking { - val statement = state.pipeline.compile(state.query3) - blackhole.consume(statement) - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery4(state: MyState, blackhole: Blackhole) = runBlocking { - val statement = state.pipeline.compile(state.query4) - blackhole.consume(statement) - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery5(state: MyState, blackhole: Blackhole) = runBlocking { - val statement = state.pipeline.compile(state.query5) - blackhole.consume(statement) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery1(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement1.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery2(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement2.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery3(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement3.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery4(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement4.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery5(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement5.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery6(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement6.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery7(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement7.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery8(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement8.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery9(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement9.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery10(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement10.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery11(state: MyState, blackhole: Blackhole) = runBlocking { - val result = state.statement11.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } -} diff --git a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLCompilerPipelineBenchmark.kt b/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLCompilerPipelineBenchmark.kt deleted file mode 100644 index 4647524908..0000000000 --- a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLCompilerPipelineBenchmark.kt +++ /dev/null @@ -1,381 +0,0 @@ -package org.partiql.jmh.benchmarks - -import com.amazon.ion.IonSystem -import com.amazon.ion.system.IonSystemBuilder -import kotlinx.coroutines.runBlocking -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.BenchmarkMode -import org.openjdk.jmh.annotations.Fork -import org.openjdk.jmh.annotations.Measurement -import org.openjdk.jmh.annotations.Mode -import org.openjdk.jmh.annotations.OutputTimeUnit -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import org.openjdk.jmh.annotations.Warmup -import org.openjdk.jmh.infra.Blackhole -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.jmh.utils.FORK_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_TIME_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_TIME_VALUE_RECOMMENDED -import org.partiql.lang.compiler.PartiQLCompilerPipeline -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.PartiQLResult -import org.partiql.lang.planner.GlobalResolutionResult -import org.partiql.lang.syntax.PartiQLParserBuilder -import java.util.concurrent.TimeUnit - -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -@Deprecated("To be removed in the next major version once the synchronous physical plan compiler is removed.") -open class PartiQLCompilerPipelineBenchmark { - companion object { - private const val FORK_VALUE: Int = FORK_VALUE_RECOMMENDED - private const val MEASUREMENT_ITERATION_VALUE: Int = MEASUREMENT_ITERATION_VALUE_RECOMMENDED - private const val MEASUREMENT_TIME_VALUE: Int = MEASUREMENT_TIME_VALUE_RECOMMENDED - private const val WARMUP_ITERATION_VALUE: Int = WARMUP_ITERATION_VALUE_RECOMMENDED - private const val WARMUP_TIME_VALUE: Int = WARMUP_TIME_VALUE_RECOMMENDED - } - - @State(Scope.Thread) - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - open class MyState { - private val parser = PartiQLParserBuilder.standard().build() - private val myIonSystem: IonSystem = IonSystemBuilder.standard().build() - - private fun tableWithRows(numRows: Int): ExprValue { - val allRows = (1..numRows).joinToString { index -> - """ - { - "id": $index, - "someString": "some string foo $index", - "someDecimal": $index.00, - "someBlob": {{ dHdvIHBhZGRpbmcgY2hhcmFjdGVycw== }}, - "someTimestamp": 2007-02-23T12:14:15.${index}Z - } - """.trimIndent() - } - val data = "[ $allRows ]" - return ExprValue.of( - myIonSystem.singleValue(data) - ) - } - - private val bindings = Bindings.ofMap( - mapOf( - "t1" to tableWithRows(1), - "t10" to tableWithRows(10), - "t100" to tableWithRows(100), - "t1000" to tableWithRows(1000), - "t10000" to tableWithRows(10000), - "t100000" to tableWithRows(100000), - ) - ) - - private val parameters = listOf( - ExprValue.newInt(5), // WHERE `id` > 5 - ExprValue.newInt(1000000), // LIMIT 1000000 - ExprValue.newInt(3), // OFFSET 3 * 2 - ExprValue.newInt(2), // ------------^ - ) - val session = EvaluationSession.build { - globals(bindings) - parameters(parameters) - } - - val pipeline = PartiQLCompilerPipeline.build { - planner.globalVariableResolver { - val value = session.globals[it] - if (value != null) { - GlobalResolutionResult.GlobalVariable(it.name) - } else { - GlobalResolutionResult.Undefined - } - } - } - - val query1 = parser.parseAstStatement( - """ - SELECT * FROM t100000 - """.trimIndent() - ) - val query2 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() - """.trimIndent() - ) - val query3 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() - LIMIT ${Int.MAX_VALUE} - """.trimIndent() - ) - val query4 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() - ORDER BY t100000.id DESC - """.trimIndent() - ) - val query5 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() AND t100000.id > ? - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - private val query6 = parser.parseAstStatement( - """ - SELECT * - FROM t100000 - WHERE t100000.someTimestamp < UTCNOW() AND t100000.id > ? - ORDER BY t100000.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - private val query7 = parser.parseAstStatement( - """ - SELECT * - FROM t10000 - WHERE t10000.someTimestamp < UTCNOW() AND t10000.id > ? - ORDER BY t10000.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - private val query8 = parser.parseAstStatement( - """ - SELECT * - FROM t1000 - WHERE t1000.someTimestamp < UTCNOW() AND t1000.id > ? - ORDER BY t1000.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - private val query9 = parser.parseAstStatement( - """ - SELECT * - FROM t100 - WHERE t100.someTimestamp < UTCNOW() AND t100.id > ? - ORDER BY t100.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - private val query10 = parser.parseAstStatement( - """ - SELECT * - FROM t10 - WHERE t10.someTimestamp < UTCNOW() AND t10.id > ? - ORDER BY t10.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - private val query11 = parser.parseAstStatement( - """ - SELECT * - FROM t1 - WHERE t1.someTimestamp < UTCNOW() AND t1.id > ? - ORDER BY t1.id DESC - LIMIT ? - OFFSET ? * ? - """.trimIndent() - ) - - val statement1 = pipeline.compile(query1) - val statement2 = pipeline.compile(query2) - val statement3 = pipeline.compile(query3) - val statement4 = pipeline.compile(query4) - val statement5 = pipeline.compile(query5) - val statement6 = pipeline.compile(query6) - val statement7 = pipeline.compile(query7) - val statement8 = pipeline.compile(query8) - val statement9 = pipeline.compile(query9) - val statement10 = pipeline.compile(query10) - val statement11 = pipeline.compile(query11) - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery1(state: MyState, blackhole: Blackhole) = runBlocking { - val statement = state.pipeline.compile(state.query1) - blackhole.consume(statement) - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery2(state: MyState, blackhole: Blackhole) = runBlocking { - val statement = state.pipeline.compile(state.query2) - blackhole.consume(statement) - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery3(state: MyState, blackhole: Blackhole) = runBlocking { - val statement = state.pipeline.compile(state.query3) - blackhole.consume(statement) - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery4(state: MyState, blackhole: Blackhole) { - val statement = state.pipeline.compile(state.query4) - blackhole.consume(statement) - } - - @OptIn(ExperimentalPartiQLCompilerPipeline::class) - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testCompileQuery5(state: MyState, blackhole: Blackhole) { - val statement = state.pipeline.compile(state.query5) - blackhole.consume(statement) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery1(state: MyState, blackhole: Blackhole) { - val result = state.statement1.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery2(state: MyState, blackhole: Blackhole) { - val result = state.statement2.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery3(state: MyState, blackhole: Blackhole) { - val result = state.statement3.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery4(state: MyState, blackhole: Blackhole) { - val result = state.statement4.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery5(state: MyState, blackhole: Blackhole) { - val result = state.statement5.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery6(state: MyState, blackhole: Blackhole) { - val result = state.statement6.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery7(state: MyState, blackhole: Blackhole) { - val result = state.statement7.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery8(state: MyState, blackhole: Blackhole) { - val result = state.statement8.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery9(state: MyState, blackhole: Blackhole) { - val result = state.statement9.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery10(state: MyState, blackhole: Blackhole) { - val result = state.statement10.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - fun testEvalQuery11(state: MyState, blackhole: Blackhole) { - val result = state.statement11.eval(state.session) - val exprValue = (result as PartiQLResult.Value).value - blackhole.consume(exprValue) - blackhole.consume(exprValue.iterator().forEach { }) - } -} diff --git a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLParserBenchmark.kt b/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLParserBenchmark.kt deleted file mode 100644 index ec9f312521..0000000000 --- a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/benchmarks/PartiQLParserBenchmark.kt +++ /dev/null @@ -1,1342 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.jmh.benchmarks - -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.BenchmarkMode -import org.openjdk.jmh.annotations.Fork -import org.openjdk.jmh.annotations.Measurement -import org.openjdk.jmh.annotations.Mode -import org.openjdk.jmh.annotations.OutputTimeUnit -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import org.openjdk.jmh.annotations.Warmup -import org.openjdk.jmh.infra.Blackhole -import org.partiql.jmh.utils.FORK_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.MEASUREMENT_TIME_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_ITERATION_VALUE_RECOMMENDED -import org.partiql.jmh.utils.WARMUP_TIME_VALUE_RECOMMENDED -import org.partiql.parser.PartiQLParser -import org.partiql.parser.PartiQLParserException -import java.util.concurrent.TimeUnit - -// TODO: If https://github.com/benchmark-action/github-action-benchmark/issues/141 gets fixed, we can move to using -// parameterized tests. This file intentionally uses the same prefix `parse` and `parseFail` for each benchmark. It -// expects that the parameter `name` will be used in the future, so it adds `Name` to prefix each argument. This will -// potentially make it easier to transition to the continuous benchmarking framework if parameterized benchmarks -// are supported. - -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -internal open class PartiQLParserBenchmark { - - companion object { - private const val FORK_VALUE: Int = FORK_VALUE_RECOMMENDED - private const val MEASUREMENT_ITERATION_VALUE: Int = MEASUREMENT_ITERATION_VALUE_RECOMMENDED - private const val MEASUREMENT_TIME_VALUE: Int = MEASUREMENT_TIME_VALUE_RECOMMENDED - private const val WARMUP_ITERATION_VALUE: Int = WARMUP_ITERATION_VALUE_RECOMMENDED - private const val WARMUP_TIME_VALUE: Int = WARMUP_TIME_VALUE_RECOMMENDED - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQuerySimple(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::querySimple.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameNestedParen(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::nestedParen.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSomeJoins(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::someJoins.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSeveralJoins(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::severalJoins.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSomeSelect(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::someSelect.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSeveralSelect(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::severalSelect.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSomeProjections(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::someProjections.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSeveralProjections(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::severalProjections.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQueryFunc(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::queryFunc.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQueryFuncInProjection(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::queryFuncInProjection.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQueryList(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::queryList.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQuery15OrsAndLikes(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::query15OrsAndLikes.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQuery30Plus(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::query30Plus.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameQueryNestedSelect(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::queryNestedSelect.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameGraphPattern(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::graphPattern.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameGraphPreFilters(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::graphPreFilters.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameManyJoins(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::manyJoins.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameTimeZone(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::timeZone.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameCaseWhenThen(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::caseWhenThen.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameSimpleInsert(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::simpleInsert.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameExceptUnionIntersectSixty(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::exceptUnionIntersectSixty.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameExec20Expressions(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::exec20Expressions.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameFromLet(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::fromLet.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameGroupLimit(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::groupLimit.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNamePivot(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::pivot.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameLongFromSourceOrderBy(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::longFromSourceOrderBy.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameNestedAggregates(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::nestedAggregates.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameComplexQuery(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::complexQuery.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameComplexQuery01(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::complexQuery01.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameComplexQuery02(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::complexQuery02.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameVeryLongQuery(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::veryLongQuery.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseNameVeryLongQuery01(state: MyState, blackhole: Blackhole) { - val result = state.parser.parse(state.queries[state::veryLongQuery01.name]!!) - blackhole.consume(result) - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQuerySimple(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::querySimple.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameNestedParen(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::nestedParen.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSomeJoins(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::someJoins.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSeveralJoins(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::severalJoins.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSomeSelect(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::someSelect.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSeveralSelect(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::severalSelect.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSomeProjections(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::someProjections.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSeveralProjections(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::severalProjections.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQueryFunc(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::queryFunc.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQueryFuncInProjection(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::queryFuncInProjection.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQueryList(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::queryList.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQuery15OrsAndLikes(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::query15OrsAndLikes.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQuery30Plus(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::query30Plus.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameQueryNestedSelect(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::queryNestedSelect.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameGraphPattern(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::graphPattern.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameGraphPreFilters(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::graphPreFilters.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameManyJoins(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::manyJoins.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameTimeZone(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::timeZone.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameCaseWhenThen(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::caseWhenThen.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameSimpleInsert(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::simpleInsert.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameExceptUnionIntersectSixty(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::exceptUnionIntersectSixty.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameExec20Expressions(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::exec20Expressions.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameFromLet(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::fromLet.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameGroupLimit(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::groupLimit.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNamePivot(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::pivot.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameLongFromSourceOrderBy(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::longFromSourceOrderBy.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameNestedAggregates(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::nestedAggregates.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameComplexQuery(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::complexQuery.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameComplexQuery01(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::complexQuery01.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameComplexQuery02(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::complexQuery02.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameVeryLongQuery(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::veryLongQuery.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @Benchmark - @Fork(value = FORK_VALUE) - @Measurement(iterations = MEASUREMENT_ITERATION_VALUE, time = MEASUREMENT_TIME_VALUE) - @Warmup(iterations = WARMUP_ITERATION_VALUE, time = WARMUP_TIME_VALUE) - @Suppress("UNUSED") - fun parseFailNameVeryLongQuery01(state: MyState, blackhole: Blackhole) { - try { - val result = state.parser.parse(state.queriesFail[state::veryLongQuery01.name]!!) - blackhole.consume(result) - throw RuntimeException() - } catch (ex: PartiQLParserException) { - blackhole.consume(ex) - } - } - - @State(Scope.Thread) - open class MyState { - - val parser = PartiQLParser.default() - - val query15OrsAndLikes = """ - SELECT * - FROM hr.employees as emp - WHERE lower(emp.name) LIKE '%bob smith%' - OR lower(emp.name) LIKE '%gage swanson%' - OR lower(emp.name) LIKE '%riley perry%' - OR lower(emp.name) LIKE '%sandra woodward%' - OR lower(emp.name) LIKE '%abagail oconnell%' - OR lower(emp.name) LIKE '%amari duke%' - OR lower(emp.name) LIKE '%elisha wyatt%' - OR lower(emp.name) LIKE '%aryanna hess%' - OR lower(emp.name) LIKE '%bryanna jones%' - OR lower(emp.name) LIKE '%trace gilmore%' - OR lower(emp.name) LIKE '%antwan stevenson%' - OR lower(emp.name) LIKE '%julianna callahan%' - OR lower(emp.name) LIKE '%jaelynn trevino%' - OR lower(emp.name) LIKE '%kadence bates%' - OR lower(emp.name) LIKE '%jakobe townsend%' - """ - - val query30Plus = """ - 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 - """ - - val querySimple = """ - SELECT a FROM t - """ - - val queryNestedSelect = """ - SELECT - ( - SELECT a AS p - FROM ( - SELECT VALUE b - FROM some_table - WHERE 3 = 4 - ) AS some_wrapped_table - WHERE id = 3 - ) AS projectionQuery - FROM ( - SELECT everything - FROM ( - SELECT * - FROM someSourceTable AS t - LET 5 + t.b AS x - WHERE x = 2 - GROUP BY t.a AS k - GROUP AS g - ORDER BY t.d - ) AS someTable - ) - LET (SELECT a FROM smallTable) AS letVariable - WHERE letVariable > 4 - GROUP BY t.a AS groupKey - """ - - val queryList = """ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] - """ - - val queryFunc = """ - f(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29) - """ - - val queryFuncInProjection = """ - SELECT - f(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29) - FROM t - """ - - val someJoins = """ - SELECT a - FROM a, b, c - """ - - val severalJoins = """ - SELECT a - FROM a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p - """ - - val someProjections = """ - SELECT a, b, c - FROM t - """ - - val severalProjections = """ - SELECT a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p - FROM t - """ - - val someSelect = """ - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) - """ - - val severalSelect = """ - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) + - (SELECT a FROM t) - """ - - val nestedParen = """ - ((((((((((((((((((((((((((((((0)))))))))))))))))))))))))))))) - """ - - val graphPreFilters = """ - SELECT u as banCandidate - FROM g - MATCH (p:Post Where p.isFlagged = true) <-[:createdPost]- (u:Usr WHERE u.isBanned = false AND u.karma < 20) -[:createdComment]->(c:Comment WHERE c.isFlagged = true) - WHERE p.title LIKE '%considered harmful%' - """.trimIndent() - - val graphPattern = """ - SELECT the_a.name AS src, the_b.name AS dest - FROM my_graph MATCH (the_a:a) -[the_y:y]-> (the_b:b) - WHERE the_y.score > 10 - """.trimIndent() - - val manyJoins = """ - SELECT x FROM a INNER CROSS JOIN b CROSS JOIN c LEFT JOIN d ON e RIGHT OUTER CROSS JOIN f OUTER JOIN g ON h - """ - - val timeZone = "TIME WITH TIME ZONE '23:59:59.123456789+18:00'" - - val caseWhenThen = "CASE WHEN name = 'zoe' THEN 1 WHEN name > 'kumo' THEN 2 ELSE 0 END" - - val simpleInsert = """ - INSERT INTO foo VALUE 1 AT bar RETURNING MODIFIED OLD bar, MODIFIED NEW bar, ALL NEW * - """ - - val exceptUnionIntersectSixty = """ - a EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - EXCEPT a INTERSECT a UNION a - """ - - val exec20Expressions = """ - EXEC - a - b, - a, - b, - c, - d, - 123, - "aaaaa", - 'aaaaa', - @ident, - 1 + 1, - 2 + 2, - a, - a, - a, - a, - a, - a, - a, - a - """ - - val fromLet = - "SELECT C.region, MAX(nameLength) AS maxLen FROM C LET char_length(C.name) AS nameLength GROUP BY C.region" - - val groupLimit = - "SELECT g FROM `[{foo: 1, bar: 10}, {foo: 1, bar: 11}]` AS f GROUP BY f.foo GROUP AS g LIMIT 1" - - val pivot = """ - PIVOT foo.a AT foo.b - FROM <<{'a': 1, 'b':'I'}, {'a': 2, 'b':'II'}, {'a': 3, 'b':'III'}>> AS foo - LIMIT 1 OFFSET 1 - """.trimIndent() - - val longFromSourceOrderBy = """ - SELECT * - FROM [{'a': {'a': 5}}, {'a': {'a': 'b'}}, {'a': {'a': true}}, {'a': {'a': []}}, {'a': {'a': {}}}, {'a': {'a': <<>>}}, {'a': {'a': `{{}}`}}, {'a': {'a': null}}] - ORDER BY a DESC - """.trimIndent() - - val nestedAggregates = """ - SELECT - i2 AS outerKey, - g2 AS outerGroupAs, - MIN(innerQuery.innerSum) AS outerMin, - ( - SELECT VALUE SUM(i2) - FROM << 0, 1 >> - ) AS projListSubQuery - FROM ( - SELECT - i, - g, - SUM(col1) AS innerSum - FROM simple_1_col_1_group_2 AS innerFromSource - GROUP BY col1 AS i GROUP AS g - ) AS innerQuery - GROUP BY innerQuery.i AS i2, innerQuery.g AS g2 - """.trimIndent() - - val complexQuery = """ - 1 + ( - SELECT a, b, c - FROM [ - { 'a': 1} - ] AS t - LET x AS y - WHERE y > 2 AND y > 3 AND y > 4 - GROUP BY t.a, t.b AS b, t.c AS c - GROUP AS d - ORDER BY x - LIMIT 1 + 22222222222222222 - OFFSET x + y + z + a + b + c - ) + ( - CAST( - '45678920irufji332r94832fhedjcd2wqbxucri3' - AS INT - ) - ) + [ - 1, 2, 3, 4, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 - ] - ((((((((((2)))))))))) + ( - SELECT VALUE { 'a': a } FROM t WHERE t.a > 3 - ) - """.trimIndent() - - val complexQuery01 = """ - SELECT - DATE_FORMAT(co.order_date, '%Y-%m') AS order_month, - DATE_FORMAT(co.order_date, '%Y-%m-%d') AS order_day, - COUNT(DISTINCT co.order_id) AS num_orders, - COUNT(ol.book_id) AS num_books, - SUM(ol.price) AS total_price - FROM cust_order co - INNER JOIN order_line ol ON co.order_id = ol.order_id - GROUP BY - DATE_FORMAT(co.order_date, '%Y-%m'), - DATE_FORMAT(co.order_date, '%Y-%m-%d') - ORDER BY co.order_date ASC; - """.trimIndent() - - val complexQuery02 = """ - SELECT - c.calendar_date, - c.calendar_year, - c.calendar_month, - c.calendar_dayName, - COUNT(DISTINCT sub.order_id) AS num_orders, - COUNT(sub.book_id) AS num_books, - SUM(sub.price) AS total_price, - SUM(COUNT(sub.book_id)) AS running_total_num_books, - LAG(COUNT(sub.book_id), 7) AS prev_books - FROM calendar_days c - LEFT JOIN ( - SELECT - co.order_date, - co.order_id, - ol.book_id, - ol.price - FROM cust_order co - INNER JOIN order_line ol ON co.order_id = ol.order_id - ) sub ON c.calendar_date = sub.order_date - GROUP BY c.calendar_date, c.calendar_year, c.calendar_month, c.calendar_dayname - ORDER BY c.calendar_date ASC; - """.trimIndent() - - val veryLongQuery = """ - SELECT - e.employee_id AS "Employee#", e.first_name || '' || e.last_name AS "Name", e.email AS "Email", - e.phone_number AS "Phone", TO_CHAR(e.hire_date, 'MM/DD/YYYY') AS "Hire Date", - TO_CHAR(e.salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS=''.,''NLS_CURRENCY=''${'$'}''') AS "Salary", - e.commission_pct AS "Comission%", - 'works as' || j.job_title || 'in' || d.department_name || ' department (manager: ' - || dm.first_name || '' || dm.last_name || ')andimmediatesupervisor:' || m.first_name || '' || m.last_name AS "CurrentJob", - TO_CHAR(j.min_salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS=''.,''NLS_CURRENCY=''${'$'}''') || '-' || - TO_CHAR(j.max_salary, 'L99G999D99', 'NLS_NUMERIC_CHARACTERS=''.,''NLS_CURRENCY=''${'$'}''') AS "CurrentSalary", - l.street_address || ',' || l.postal_code || ',' || l.city || ',' || l.state_province || ',' - || c.country_name || '(' || r.region_name || ')' AS "Location", - jh.job_id AS "HistoryJobID", - 'worked from' || TO_CHAR(jh.start_date, 'MM/DD/YYYY') || 'to' || TO_CHAR(jh.end_date, 'MM/DD/YYYY') || - 'as' || jj.job_title || 'in' || dd.department_name || 'department' AS "HistoryJobTitle" - FROM employees e - JOIN jobs j - ON e.job_id = j.job_id - LEFT JOIN employees m - ON e.manager_id = m.employee_id - LEFT JOIN departments d - ON d.department_id = e.department_id - LEFT JOIN employees dm - ON d.manager_id = dm.employee_id - LEFT JOIN locations l - ON d.location_id = l.location_id - LEFT JOIN countries c - ON l.country_id = c.country_id - LEFT JOIN regions r - ON c.region_id = r.region_id - LEFT JOIN job_history jh - ON e.employee_id = jh.employee_id - LEFT JOIN jobs jj - ON jj.job_id = jh.job_id - LEFT JOIN departments dd - ON dd.department_id = jh.department_id - - ORDER BY e.employee_id; - """.trimIndent() - - val veryLongQuery01 = """ - SELECT - id as feedId, - (IF(groupId > 0, groupId, IF(friendId > 0, friendId, userId))) as wallOwnerId, - (IF(groupId > 0 or friendId > 0, userId, NULL)) as guestWriterId, - (IF(groupId > 0 or friendId > 0, userId, NULL)) as guestWriterType, - case - when type = 2 then 1 - when type = 1 then IF(media_count = 1, 2, 4) - when type = 5 then IF(media_count = 1, IF(albumName = 'Audio Feeds', 5, 6), 7) - when type = 6 then IF(media_count = 1, IF(albumName = 'Video Feeds', 8, 9), 10) - end as contentType, - albumId, - albumName, - addTime, - IF(validity > 0,IF((validity - updateTime) / 86400000 > 1,(validity - updateTime) / 86400000, 1),0) as validity, - updateTime, - status, - location, - latitude as locationLat, - longitude as locationLon, - sharedFeedId as parentFeedId, - case - when privacy = 2 or privacy = 10 then 15 - when privacy = 3 then 25 - else 1 - end as privacy, - pagefeedcategoryid, - case - when lastSharedFeedId = 2 then 10 - when lastSharedFeedId = 3 then 15 - when lastSharedFeedId = 4 then 25 - when lastSharedFeedId = 5 then 20 - when lastSharedFeedId = 6 then 99 - else 1 - end as wallOwnerType, - (ISNULL(latitude) or latitude = 9999.0 or ISNULL(longitude) or longitude = 9999.0) as latlongexists, - (SELECT concat('[',GROUP_CONCAT(moodId),']') FROM feedactivities WHERE newsFeedId = newsfeed.id) as feelings, - (SELECT concat('[',GROUP_CONCAT(userId),']') FROM feedtags WHERE newsFeedId = newsfeed.id) as withTag, - (SELECT concat('{',GROUP_CONCAT(pos,':', friendId),'}') FROM statustags WHERE newsFeedId = newsfeed.id) as textTag, - albumType, - defaultCategoryType, - linkType,linkTitle,linkURL,linkDesc,linkImageURL,linkDomain, -- Link Content - title,description,shortDescription,newsUrl,externalUrlOption, -- Additional Content - url, height, width, thumnail_url, thumnail_height, thumbnail_width, duration, artist -- Media - FROM - (newsfeed LEFT JOIN - ( - SELECT - case - when (mediaalbums.media_type = 1 and album_name = 'AudioFeeds') - or (mediaalbums.media_type = 2 and album_name = 'VideoFeeds') - then -1 * mediaalbums.user_id else mediaalbums.id - end as albumId, - album_name as albumName, - newsFeedId, - (NULL) as height, - (NULL) as width, - media_thumbnail_url as thumnail_url, - max(thumb_image_height) as thumnail_height, - max(thumb_image_width) as thumbnail_width, - max(media_duration) as duration, - case - when mediaalbums.media_type = 1 and album_name = 'AudioFeeds' - then 4 - when mediaalbums.media_type = 2 and album_name = 'VideoFeeds' - then 5 else 8 - end as albumType, - count(mediacontents.id) as media_count, - media_artist as artist - FROM - (mediaalbums INNER JOIN mediacontents ON mediaalbums.id = mediacontents.album_id) - INNER JOIN newsfeedmediacontents - ON newsfeedmediacontents.contentId = mediacontents.id group by newsfeedid - UNION - SELECT - -1 * userId as albumId, - newsFeedId,imageUrl as url, - max(imageHeight) as height, - max(imageWidth) as width, - (NULL) as thumnail_url, - (NULL) as thumnail_height, - (NULL) as thumbnail_width, - (NULL) as duration, - case - when albumId = 'default' then 1 - when albumId = 'profileimages' then 2 - when albumId = 'coverimages' then 3 - end as albumType, - count(imageid) as media_count, - (NULL) as artist - FROM userimages - INNER JOIN newsfeedimages on userimages.id = newsfeedimages.imageId - group by newsfeedid - ) album - ON newsfeed.id = album.newsfeedId - ) - LEFT JOIN - ( - select newsPortalFeedId as feedid, - title,description,shortDescription,newsUrl,externalUrlOption, newsPortalCategoryId as pagefeedcategoryid, - (15) as defaultCategoryType from newsportalFeedInfo - UNION - select businessPageFeedId as feedid,title,description,shortDescription,newsUrl,externalUrlOption, - businessPageCategoryId as pagefeedcategoryid,(25) as defaultCategoryType from businessPageFeedInfo - UNION - select newsfeedId as feedid,(NULL) as title,description,(NULL) as shortDescription,(NULL) as newsUrl, - (NULL) as externalUrlOption, categoryMappingId as pagefeedcategoryid, - (20) as defaultCategoryType from mediaPageFeedInfo - ) page - ON newsfeed.id = page.feedId WHERE privacy != 10 - """.trimIndent() - - val queries = mapOf( - ::querySimple.name to querySimple, - ::nestedParen.name to nestedParen, - ::someJoins.name to someJoins, - ::severalJoins.name to severalJoins, - ::someSelect.name to someSelect, - ::severalSelect.name to severalSelect, - ::someProjections.name to someProjections, - ::severalProjections.name to severalProjections, - ::queryFunc.name to queryFunc, - ::queryFuncInProjection.name to queryFuncInProjection, - ::queryList.name to queryList, - ::query15OrsAndLikes.name to query15OrsAndLikes, - ::query30Plus.name to query30Plus, - ::queryNestedSelect.name to queryNestedSelect, - ::graphPattern.name to graphPattern, - ::graphPreFilters.name to graphPreFilters, - ::manyJoins.name to manyJoins, - ::timeZone.name to timeZone, - ::caseWhenThen.name to caseWhenThen, - ::simpleInsert.name to simpleInsert, - ::exceptUnionIntersectSixty.name to exceptUnionIntersectSixty, - ::exec20Expressions.name to exec20Expressions, - ::fromLet.name to fromLet, - ::groupLimit.name to groupLimit, - ::pivot.name to pivot, - ::longFromSourceOrderBy.name to longFromSourceOrderBy, - ::nestedAggregates.name to nestedAggregates, - ::complexQuery.name to complexQuery, - ::complexQuery01.name to complexQuery01, - ::complexQuery02.name to complexQuery02, - ::veryLongQuery.name to veryLongQuery, - ::veryLongQuery01.name to veryLongQuery01, - ) - - val queriesFail = queries.map { (name, query) -> - val splitQuery = query.split("\\s".toRegex()).toMutableList() - val index = (splitQuery.lastIndex * .50).toInt() - splitQuery.add(index, ";") - name to splitQuery.joinToString(separator = " ") - }.toMap() - } -} diff --git a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/utils/BenchmarkConstants.kt b/partiql-lang/src/jmh/kotlin/org/partiql/jmh/utils/BenchmarkConstants.kt deleted file mode 100644 index af487388ee..0000000000 --- a/partiql-lang/src/jmh/kotlin/org/partiql/jmh/utils/BenchmarkConstants.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.jmh.utils - -internal const val FORK_VALUE_RECOMMENDED: Int = 2 -internal const val MEASUREMENT_ITERATION_VALUE_RECOMMENDED: Int = 10 -internal const val MEASUREMENT_TIME_VALUE_RECOMMENDED: Int = 1 -internal const val WARMUP_ITERATION_VALUE_RECOMMENDED: Int = 5 -internal const val WARMUP_TIME_VALUE_RECOMMENDED: Int = 1 diff --git a/partiql-lang/src/main/kotlin/org/partiql/annotations/Experimental.kt b/partiql-lang/src/main/kotlin/org/partiql/annotations/Experimental.kt deleted file mode 100644 index 7a4322c23e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/annotations/Experimental.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.partiql.annotations - -/** - * This file holds annotation classes to mark a certain feature as experimental in the codebase - * - * To use features that are marked experimental in Kotlin, users are required to give explicit consent. - * - * To read more on the opt-in requirements, see https://kotlinlang.org/docs/opt-in-requirements.html#opt-in-to-using-api. - * - * Unfortunately, the annotation does not work when calling the classes with experimental annotation from Java. - * - * This means the java users will not receive such enforced communication, and can use the experimental annotation without warning. - * - * See: https://github.com/partiql/partiql-lang-kotlin/issues/965 - */ - -@RequiresOptIn(message = "PartiQLCompilerPipeline is experimental. It may be changed in the future without notice.", level = RequiresOptIn.Level.ERROR) -annotation class ExperimentalPartiQLCompilerPipeline - -// TODO: Remove from experimental once https://github.com/partiql/partiql-docs/issues/31 is resolved and a RFC is approved -@RequiresOptIn(message = "Window Function is experimental. It may be changed in the future without notice.", level = RequiresOptIn.Level.ERROR) -annotation class ExperimentalWindowFunctions diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/CompilerPipeline.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/CompilerPipeline.kt deleted file mode 100644 index c0a9eb7b41..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/CompilerPipeline.kt +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang - -import com.amazon.ion.IonSystem -import com.amazon.ion.system.IonSystemBuilder -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.CompileOptions -import org.partiql.lang.eval.EvaluatingCompiler -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.Expression -import org.partiql.lang.eval.ThunkReturnTypeAssertions -import org.partiql.lang.eval.builtins.SCALAR_BUILTINS_DEFAULT -import org.partiql.lang.eval.builtins.definitionalBuiltins -import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure -import org.partiql.lang.eval.impl.CoverageCompiler -import org.partiql.lang.eval.visitors.PipelinedVisitorTransform -import org.partiql.lang.eval.visitors.StaticTypeInferenceVisitorTransform -import org.partiql.lang.eval.visitors.StaticTypeVisitorTransform -import org.partiql.lang.syntax.Parser -import org.partiql.lang.syntax.PartiQLParserBuilder -import org.partiql.lang.types.CustomType -import org.partiql.lang.util.interruptibleFold -import org.partiql.types.StaticType - -/** - * Contains all information needed for processing steps. - */ -data class StepContext( - /** The compilation options. */ - val compileOptions: CompileOptions, - - /** - * Returns a list of all functions which are available for execution. - * Includes built-in functions as well as custom functions added while the [CompilerPipeline] - * was being built. - */ - val functions: @JvmSuppressWildcards Map, - - /** - * Returns a list of all stored procedures which are available for execution. - * Only includes the custom stored procedures added while the [CompilerPipeline] was being built. - */ - val procedures: @JvmSuppressWildcards Map -) - -/** - * [ProcessingStep] functions accept an [PartiqlAst.Statement] and [StepContext] as an arguments and processes them in some - * way and then returns either the original [PartiqlAst.Statement] or a modified [PartiqlAst.Statement]. - */ -typealias ProcessingStep = (PartiqlAst.Statement, StepContext) -> PartiqlAst.Statement - -/** - * [CompilerPipeline] is the main interface for compiling PartiQL queries into instances of [Expression] which - * can be executed. - * - * The provided builder companion creates an instance of [CompilerPipeline] that is NOT thread safe and should NOT be - * used to compile queries concurrently. If used in a multithreaded application, use one instance of [CompilerPipeline] - * per thread. - */ -interface CompilerPipeline { - /** The compilation options. */ - val compileOptions: CompileOptions - - /** - * Returns a list of all functions which are available for execution. - * Includes built-in functions as well as custom functions added while the [CompilerPipeline] - * was being built. - */ - val functions: @JvmSuppressWildcards Map - - /** - * Returns list of custom data types that are available in typed operators (i.e CAST/IS). - * - * This does not include core PartiQL parameters. - */ - val customDataTypes: List - - /** - * Returns a list of all stored procedures which are available for execution. - * Only includes the custom stored procedures added while the [CompilerPipeline] was being built. - */ - val procedures: @JvmSuppressWildcards Map - - /** - * The configured global type bindings. - */ - val globalTypeBindings: Bindings? - - /** Compiles the specified PartiQL query using the configured parser. */ - fun compile(query: String): Expression - - /** Compiles the specified [PartiqlAst.Statement] instance. */ - fun compile(query: PartiqlAst.Statement): Expression - - companion object { - /** Kotlin style builder for [CompilerPipeline]. If calling from Java instead use [builder]. */ - fun build(block: Builder.() -> Unit) = Builder().apply(block).build() - - /** Fluent style builder. If calling from Kotlin instead use the [build] method. */ - @JvmStatic - fun builder(): Builder = Builder() - - /** Returns an implementation of [CompilerPipeline] with all properties set to their defaults. */ - @JvmStatic - fun standard(): CompilerPipeline = builder().build() - } - - /** - * An implementation of the builder pattern for instances of [CompilerPipeline]. The created instance of - * [CompilerPipeline] is NOT thread safe and should NOT be used to compile queries concurrently. If used in a - * multithreaded application, use one instance of [CompilerPipeline] per thread. - */ - class Builder() { - - // TODO: remove this once we migrate from `IonValue` to `IonElement`. - private val ion = IonSystemBuilder.standard().build() - private var parser: Parser? = null - private var compileOptions: CompileOptions? = null - private val customFunctions: MutableList = ArrayList() - private var customDataTypes: List = listOf() - private val customProcedures: MutableMap = HashMap() - private val preProcessingSteps: MutableList = ArrayList() - private var globalTypeBindings: Bindings? = null - private var withCoverageStatistics: Boolean = false - - /** - * Specifies the [Parser] to be used to turn an PartiQL query into an instance of [PartiqlAst]. - */ - fun sqlParser(p: Parser): Builder = this.apply { parser = p } - - /** - * The options to be used during compilation. The default is [CompileOptions.standard]. - * - */ - fun compileOptions(options: CompileOptions): Builder = this.apply { compileOptions = options } - - /** - * A nested builder for compilation options. The default is [CompileOptions.standard]. - * - * Avoid the use of this overload if calling from Java and instead use the overload accepting an instance - * of [CompileOptions]. - * - * There is no need to call [Builder.build] when using this method. - */ - fun compileOptions(block: CompileOptions.Builder.() -> Unit): Builder = compileOptions(CompileOptions.build(block)) - - /** - * Add a custom function which will be callable by the compiled queries. - * - * Functions added here will replace any built-in function with the same name. - */ - fun addFunction(function: ExprFunction): Builder = this.apply { customFunctions.add(function) } - - /** - * Add custom types to CAST/IS operators to. - * - * Built-in types will take precedence over custom types in case of a name collision. - */ - fun customDataTypes(customTypes: List) = this.apply { - customDataTypes = customTypes - } - - /** - * Add a custom stored procedure which will be callable by the compiled queries. - * - * Stored procedures added here will replace any built-in procedure with the same name. - */ - fun addProcedure(procedure: StoredProcedure): Builder = this.apply { customProcedures[procedure.signature.name] = procedure } - - /** Adds a preprocessing step to be executed after parsing but before compilation. */ - fun addPreprocessingStep(step: ProcessingStep): Builder = this.apply { preProcessingSteps.add(step) } - - /** Adds the [Bindings] for global variables. */ - fun globalTypeBindings(bindings: Bindings): Builder = this.apply { this.globalTypeBindings = bindings } - - /** Modifies [CompilerPipeline] to also emit coverage statistics of PartiQL statements. */ - fun withCoverageStatistics(value: Boolean): Builder = this.apply { this.withCoverageStatistics = value } - - /** Builds the actual implementation of [CompilerPipeline]. */ - fun build(): CompilerPipeline { - val compileOptionsToUse = compileOptions ?: CompileOptions.standard() - - when (compileOptionsToUse.thunkOptions.thunkReturnTypeAssertions) { - ThunkReturnTypeAssertions.DISABLED -> { /* intentionally blank */ } - ThunkReturnTypeAssertions.ENABLED -> { - check(this.globalTypeBindings != null) { - "EvaluationTimeTypeChecks.ENABLED does not work if globalTypeBindings have not been specified" - } - } - } - - val definitionalBuiltins = definitionalBuiltins(compileOptionsToUse.typingMode) - val builtinFunctions = SCALAR_BUILTINS_DEFAULT - - // customFunctions must be on the right side of + here to ensure that they overwrite any - // built-in functions with the same name. - val allFunctions = definitionalBuiltins + builtinFunctions + customFunctions - - val compiler: EvaluatingCompiler = when (withCoverageStatistics) { - false -> EvaluatingCompiler( - allFunctions, - customDataTypes.map { customType -> - (customType.aliases + customType.name).map { alias -> - Pair(alias.lowercase(), customType.typedOpParameter) - } - }.flatten().toMap(), - customProcedures, - compileOptionsToUse - ) - true -> CoverageCompiler( - allFunctions, - customDataTypes.map { customType -> - (customType.aliases + customType.name).map { alias -> - Pair(alias.lowercase(), customType.typedOpParameter) - } - }.flatten().toMap(), - customProcedures, - compileOptionsToUse - ) - } - - return CompilerPipelineImpl( - ion = ion, - parser = parser ?: PartiQLParserBuilder().customTypes(customDataTypes).build(), - compileOptions = compileOptionsToUse, - functions = allFunctions.mapIndexed { idx, func -> idx to func }.associate { it.first.toString() to it.second }, - customDataTypes = customDataTypes, - procedures = customProcedures, - preProcessingSteps = preProcessingSteps, - globalTypeBindings = globalTypeBindings, - compiler = compiler - ) - } - } -} - -internal class CompilerPipelineImpl( - private val ion: IonSystem, - private val parser: Parser, - override val compileOptions: CompileOptions, - override val functions: Map, - override val customDataTypes: List, - override val procedures: Map, - private val preProcessingSteps: List, - override val globalTypeBindings: Bindings?, - private val compiler: EvaluatingCompiler -) : CompilerPipeline { - - override fun compile(query: String): Expression = compile(parser.parseAstStatement(query)) - - override fun compile(query: PartiqlAst.Statement): Expression { - val context = StepContext(compileOptions, functions, procedures) - - val preProcessedQuery = executePreProcessingSteps(query, context) - - val transforms = PipelinedVisitorTransform( - *listOfNotNull( - listOf(compileOptions.visitorTransformMode.createVisitorTransform()), - // if [typeBindings] was specified, enable [StaticTypeVisitorTransform] and [StaticTypeInferenceVisitorTransform]. - when (globalTypeBindings) { - null -> null - else -> { - listOf( - StaticTypeVisitorTransform(ion, globalTypeBindings), - StaticTypeInferenceVisitorTransform( - globalBindings = globalTypeBindings, - customFunctionSignatures = functions.values.map { it.signature }, - customTypedOpParameters = customDataTypes.map { customType -> - (customType.aliases + customType.name).map { alias -> - Pair(alias.lowercase(), customType.typedOpParameter) - } - }.flatten().toMap() - ) - ) - } - } - ).flatten().toTypedArray() - ) - - val queryToCompile = transforms.transformStatement(preProcessedQuery) - - return compiler.compile(queryToCompile) - } - - internal fun executePreProcessingSteps(query: PartiqlAst.Statement, context: StepContext) = preProcessingSteps - .interruptibleFold(query) { currentAstStatement, step -> step(currentAstStatement, context) } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/SqlException.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/SqlException.kt deleted file mode 100644 index 1e4682521f..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/SqlException.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.errors.UNKNOWN - -/** - * General exception class for the interpreter. - * - * Three configurations for an [SqlException] - * - * 1. Provide a [message] and optionally a [cause] - * * Used when an error occurs and we can only provide a general human friendly text message - * 1. Provide a [message] an [ErrorCode] and context as a [PropertyValueMap] and optionally a [cause] - * * Used when an error occurs and we want a **custom** message as well as an auto-generated message from error code and error context - * 1. Provide an [ErrorCode] and context as a [PropertyValueMap] and optionally a [cause] - * * Used when an error occurs and we want an auto-generated message from the given error code and error context - * - * @param message the message for this exception - * @param errorCode the error code for this exception - * @param errorContext context for this error, includes details like line & character offsets, among others. - * @param cause for this exception - */ -open class SqlException( - override var message: String, - val errorCode: ErrorCode, - val errorContext: PropertyValueMap, - cause: Throwable? = null -) : RuntimeException(message, cause) { - - /** - * Indicates if this exception is due to an internal error or not. - * - * Internal errors are those that are likely due to a bug in PartiQL itself or in the usage of its APIs. - * - * Non-internal errors are caused by query authors--i.e. syntax errors, or semantic errors such as undefined - * variables, etc. - */ - open val internal: Boolean get() = false - - /** - * Given the [errorCode], error context as a [propertyValueMap] and optional [cause] creates an - * [SqlException] with an auto-generated error message. - * This is the constructor for the third configuration explained above. - * - * @param errorCode the error code for this exception - * @param propertyValueMap context for this error - * @param cause for this exception - */ - constructor(errorCode: ErrorCode, propertyValueMap: PropertyValueMap, cause: Throwable? = null) : - this("", errorCode, propertyValueMap, cause) - - /** - * Auto-generated message has the structure - * - * ``` - * ErrorCategory ': ' ErrorLocation ': ' ErrorMessage - * ``` - * - * where - * - * * ErrorCategory is one of `Lexer Error`, `Parser Error`, `Runtime Error` - * * ErrorLocation is the line and column where the error occurred - * * ErrorMessage is the **generated** error message - * - * - * TODO: Prepend to the auto-generated message the file name. - * - */ - fun generateMessage(): String = - "${errorCategory(errorCode)}: ${errorLocation(errorContext)}: ${errorMessage(errorCode, errorContext)}" - - /** Same as [generateMessage] but without the location. */ - fun generateMessageNoLocation(): String = - "${errorCategory(errorCode)}: ${errorMessage(errorCode, errorContext)}" - - private fun errorMessage(errorCode: ErrorCode?, propertyValueMap: PropertyValueMap?): String = - errorCode?.getErrorMessage(propertyValueMap) ?: UNKNOWN - - private fun errorLocation(propertyValueMap: PropertyValueMap?): String { - val lineNo = propertyValueMap?.get(Property.LINE_NUMBER)?.longValue() - val columnNo = propertyValueMap?.get(Property.COLUMN_NUMBER)?.longValue() - - return "at line ${lineNo ?: UNKNOWN}, column ${columnNo ?: UNKNOWN}" - } - - private fun errorCategory(errorCode: ErrorCode?): String = - errorCode?.category?.message ?: UNKNOWN - - override fun toString(): String = - when (this.message.isNotBlank()) { - true -> { - val msg = this.message - this.message = "${this.message}\n\t${generateMessage()}\n" - val result = super.toString() - this.message = msg - result - } - else -> { - this.message = "${generateMessage()}\n" - val result = super.toString() - this.message = "" - result - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/SemanticException.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/SemanticException.kt deleted file mode 100644 index 094e6017c3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/SemanticException.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.ast.passes - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Problem -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.SqlException -import org.partiql.lang.util.propertyValueMapOf - -/** - * The exception to be thrown by semantic passes. - */ -class SemanticException( - message: String = "", - errorCode: ErrorCode, - errorContext: PropertyValueMap, - cause: Throwable? = null -) : SqlException(message, errorCode, errorContext, cause) { - - /** - * Alternate constructor using a [Problem]. Error message is generated using [ProblemDetails.message]. - */ - constructor(err: Problem, cause: Throwable? = null) : - this( - message = "", - errorCode = ErrorCode.SEMANTIC_PROBLEM, - errorContext = propertyValueMapOf( - Property.LINE_NUMBER to err.sourceLocation.lineNum, - Property.COLUMN_NUMBER to err.sourceLocation.charOffset, - Property.MESSAGE to err.details.message - ), - cause = cause - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/SemanticProblemDetails.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/SemanticProblemDetails.kt deleted file mode 100644 index df4b0cfa3e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/SemanticProblemDetails.kt +++ /dev/null @@ -1,92 +0,0 @@ -package org.partiql.lang.ast.passes - -import org.partiql.errors.ProblemDetails -import org.partiql.errors.ProblemSeverity -import org.partiql.lang.ast.passes.inference.stringWithoutNullMissing -import org.partiql.types.StaticType - -/** - * Variants of [SemanticProblemDetails] contain info about various problems that can be encountered through semantic - * passes. - */ -sealed class SemanticProblemDetails(override val severity: ProblemSeverity, val messageFormatter: () -> String) : ProblemDetails { - override val message: String - get() = messageFormatter() - - data class IncorrectNumberOfArgumentsToFunctionCall(val functionName: String, val expectedArity: IntRange, val actualArity: Int) : - SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { - "Incorrect number of arguments for '$functionName'. " + - "Expected $expectedArity but was supplied $actualArity." - } - ) - - data class CoercionError(val actualType: StaticType) : SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { - "Unable to coerce $actualType into a single value." - } - ) - - object DuplicateAliasesInSelectListItem : - SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { "Duplicate projection field encountered in SelectListItem expression" } - ) - - data class NoSuchFunction(val functionName: String) : - SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { "No such function '$functionName'" } - ) - - data class IncompatibleDatatypesForOp(val actualArgumentTypes: List, val nAryOp: String) : - SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { "${stringWithoutNullMissing(actualArgumentTypes)} is/are incompatible data types for the '$nAryOp' operator." } - ) - - data class IncompatibleDataTypeForExpr(val expectedType: StaticType, val actualType: StaticType) : - SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { "In this context, $expectedType is expected but expression returns $actualType" } - ) - - object ExpressionAlwaysReturnsMissing : - SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { "Expression always returns missing." } - ) - - /** - * Should be used when the inferred type is always null or unionOf(null, missing) - */ - object ExpressionAlwaysReturnsNullOrMissing : - SemanticProblemDetails( - severity = ProblemSeverity.WARNING, - messageFormatter = { "Expression always returns null or missing." } - ) - - data class InvalidArgumentTypeForFunction(val functionName: String, val expectedType: StaticType, val actualType: StaticType) : - SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { "Invalid argument type for $functionName. Expected $expectedType but got $actualType" } - ) - - data class NullOrMissingFunctionArgument(val functionName: String) : - SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { - "Function $functionName given an argument that will always be null or missing. " + - "As a result, this function call will always return null or missing." - } - ) - - object MissingAlias : SemanticProblemDetails( - severity = ProblemSeverity.ERROR, - messageFormatter = { - "Missing a required ALIAS." - } - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/StatementRedactor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/StatementRedactor.kt deleted file mode 100644 index 0c7bf5ee79..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/StatementRedactor.kt +++ /dev/null @@ -1,351 +0,0 @@ -package org.partiql.lang.ast.passes - -import com.amazon.ionelement.api.StringElement -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.ast.sourceLocation -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.syntax.PartiQLParserBuilder - -/** - * This is a function alias for determining which UDF input arguments need to be redacted. - * - * There are two components needed for implementation: - * 1. Which arguments are needed for [SafeFieldName] validation - * 2. Which arguments are needed for redaction should be returned - * - * For example, for a given function in which argument number is static, func(a, b, c, d), - * we can validate whether `a` and `b` are a [SafeFieldName], if yes, `c` and `d` will be redacted. - */ -typealias UserDefinedFunctionRedactionLambda = (List) -> List - -private val parser = PartiQLParserBuilder().build() -private const val maskPattern = "***(Redacted)" - -const val INVALID_NUM_ARGS = "Invalid number of args in node" -const val INPUT_AST_STATEMENT_MISMATCH = "Unable to redact the statement. Please check that the input ast is the parsed result of the input statement" - -/** - * Returns true if the given [node] type is to be skipped for redaction or its text is one of the [safeFieldNames]. - */ -fun skipRedaction(node: PartiqlAst.Expr, safeFieldNames: Set): Boolean { - if (safeFieldNames.isEmpty()) { - return false - } - - return when (node) { - is PartiqlAst.Expr.Id -> safeFieldNames.contains(node.name.text) - is PartiqlAst.Expr.Lit -> { - when (node.value) { - is StringElement -> safeFieldNames.contains(node.value.stringValue) - else -> false - } - } - is PartiqlAst.Expr.Path -> { - node.steps.any { - when (it) { - is PartiqlAst.PathStep.PathExpr -> skipRedaction(it.index, safeFieldNames) - else -> false - } - } - } - else -> true // Skip redaction for other nodes - } -} - -/** - * From the input PartiQL [statement], returns a statement in which [PartiqlAst.Expr.Lit]s not assigned with - * [providedSafeFieldNames] are redacted to "***(Redacted)". - * - * [providedSafeFieldNames] is an optional set of fields whose values are not to be redacted. If no set is provided, - * all literals will be redacted. - * For example, given a [providedSafeFieldNames] of Set('hashkey') - * The query of `SELECT * FROM tb WHERE hashkey = 'a' AND attr = 12345` will be redacted to: - * `SELECT * FROM tb WHERE hashkey = 'a' AND attr = ***(Redacted)` - * [userDefinedFunctionRedactionConfig] is an optional mapping of UDF names to functions determining which call - * arguments are to be redacted. For an example, please check StatementRedactorTest.kt for more details. - */ -fun redact( - statement: String, - providedSafeFieldNames: Set = emptySet(), - userDefinedFunctionRedactionConfig: Map = emptyMap() -): String { - return redact(statement, parser.parseAstStatement(statement), providedSafeFieldNames, userDefinedFunctionRedactionConfig) -} - -/** - * From the input PartiQL [statement], returns a statement in which [PartiqlAst.Expr.Lit]s not assigned with - * [providedSafeFieldNames] are redacted to "***(Redacted)". Assumes that the parsed PartiQL [statement] is the same - * as the input [ast]. - * - * [providedSafeFieldNames] is an optional set of fields whose values are not to be redacted. If no set is provided, - * all literals will be redacted. - * For example, given a [providedSafeFieldNames] of Set('hashkey') - * The query of `SELECT * FROM tb WHERE hashkey = 'a' AND attr = 12345` will be redacted to: - * `SELECT * FROM tb WHERE hashkey = 'a' AND attr = ***(Redacted)` - * [userDefinedFunctionRedactionConfig] is an optional mapping of UDF names to functions determining which call - * arguments are to be redacted. For an example, please check StatementRedactorTest.kt for more details. - */ -fun redact( - statement: String, - partiqlAst: PartiqlAst.Statement, - providedSafeFieldNames: Set = emptySet(), - userDefinedFunctionRedactionConfig: Map = emptyMap() -): String { - - val statementRedactionVisitor = StatementRedactionVisitor(statement, providedSafeFieldNames, userDefinedFunctionRedactionConfig) - statementRedactionVisitor.walkStatement(partiqlAst) - return statementRedactionVisitor.getRedactedStatement() -} - -/** - * Redact [PartiqlAst.Expr.Lit]s not assigned with [safeFieldNames] to "***(Redacted)". Function calls that have an - * entry in [userDefinedFunctionRedactionConfig] will have their arguments redacted based on the redaction lambda. - */ -private class StatementRedactionVisitor( - private val statement: String, - private val safeFieldNames: Set, - private val userDefinedFunctionRedactionConfig: Map -) : PartiqlAst.Visitor() { - private val sourceLocationMetaForRedaction = arrayListOf() - - /** - * Returns the redacted [statement]. - */ - fun getRedactedStatement(): String { - val lines = statement.lines() - val totalCharactersInPreviousLines = IntArray(lines.size) - for (lineNum in 1 until lines.size) { - totalCharactersInPreviousLines[lineNum] = totalCharactersInPreviousLines[lineNum - 1] + lines[lineNum - 1].length + 1 - } - - val redactedStatement = StringBuilder(statement) - var offset = 0 - sourceLocationMetaForRedaction.sortWith(compareBy { it.lineNum }.thenBy { it.charOffset }) - - sourceLocationMetaForRedaction.map { - val length = it.length.toInt() - val lineNum = it.lineNum.toInt() - if (lineNum < 1 || lineNum > totalCharactersInPreviousLines.size) { - throw IllegalArgumentException("$INPUT_AST_STATEMENT_MISMATCH, line number: $lineNum") - } - val start = totalCharactersInPreviousLines[lineNum - 1] + it.charOffset.toInt() - 1 + offset - if (start < 0 || length < 0 || start >= redactedStatement.length || start > redactedStatement.length - length) { - throw IllegalArgumentException(INPUT_AST_STATEMENT_MISMATCH) - } - redactedStatement.replace(start, start + length, maskPattern) - offset = offset + maskPattern.length - length - } - return redactedStatement.toString() - } - - override fun visitExprSelect(node: PartiqlAst.Expr.Select) { - node.where?.let { redactExpr(it) } - } - - override fun visitStatementDml(node: PartiqlAst.Statement.Dml) { - node.where?.let { redactExpr(it) } - } - - override fun visitAssignment(node: PartiqlAst.Assignment) { - if (!skipRedaction(node.target, safeFieldNames)) { - redactExpr(node.value) - } - } - - override fun visitDmlOpInsertValue(node: PartiqlAst.DmlOp.InsertValue) { - when (val value = node.value) { - is PartiqlAst.Expr.Struct -> redactStructInInsertValueOp(value) - else -> redactExpr(node.value) - } - } - - override fun visitDmlOpInsert(node: PartiqlAst.DmlOp.Insert) { - when (val values = node.values) { - is PartiqlAst.Expr.Bag -> redactBagInInserOpValues(values) - else -> redactExpr(node.values) - } - } - - private fun redactExpr(node: PartiqlAst.Expr) { - if (node.isNAry()) { - redactNAry(node) - } else when (node) { - is PartiqlAst.Expr.Lit -> redactLiteral(node) - is PartiqlAst.Expr.List -> redactSeq(node) - is PartiqlAst.Expr.Sexp -> redactSeq(node) - is PartiqlAst.Expr.Bag -> redactSeq(node) - is PartiqlAst.Expr.Struct -> redactStruct(node) - is PartiqlAst.Expr.IsType -> redactTypes(node) - else -> { /* other nodes are not currently redacted */ } - } - } - - private fun redactLogicalOp(args: List) { args.forEach { redactExpr(it) } } - - private fun redactComparisonOp(args: List) { - if (args.size != 2) { - throw IllegalArgumentException(INVALID_NUM_ARGS) - } - if (!skipRedaction(args[0], safeFieldNames)) { - redactExpr(args[1]) - } - } - - private fun plusMinusRedaction(args: List) { - when (args.size) { - 2 -> { - redactExpr(args[0]) - redactExpr(args[1]) - } - else -> throw IllegalArgumentException(INVALID_NUM_ARGS) - } - } - - private fun posNegRedaction(expr: PartiqlAst.Expr) { - redactExpr(expr) - } - - private fun arithmeticOpRedaction(args: List) { - if (args.size != 2) { - throw IllegalArgumentException(INVALID_NUM_ARGS) - } - redactExpr(args[0]) - redactExpr(args[1]) - } - - private fun redactNAry(node: PartiqlAst.Expr) { - when (node) { - // Logical Ops - is PartiqlAst.Expr.And -> redactLogicalOp(node.operands) - is PartiqlAst.Expr.Or -> redactLogicalOp(node.operands) - is PartiqlAst.Expr.Not -> redactExpr(node.expr) - // Comparison Ops - is PartiqlAst.Expr.Eq -> redactComparisonOp(node.operands) - is PartiqlAst.Expr.Ne -> redactComparisonOp(node.operands) - is PartiqlAst.Expr.Gt -> redactComparisonOp(node.operands) - is PartiqlAst.Expr.Gte -> redactComparisonOp(node.operands) - is PartiqlAst.Expr.Lt -> redactComparisonOp(node.operands) - is PartiqlAst.Expr.Lte -> redactComparisonOp(node.operands) - is PartiqlAst.Expr.InCollection -> redactComparisonOp(node.operands) - // Arithmetic Ops - is PartiqlAst.Expr.Plus -> plusMinusRedaction(node.operands) - is PartiqlAst.Expr.Minus -> plusMinusRedaction(node.operands) - is PartiqlAst.Expr.Pos -> posNegRedaction(node.expr) - is PartiqlAst.Expr.Neg -> posNegRedaction(node.expr) - is PartiqlAst.Expr.Times -> arithmeticOpRedaction(node.operands) - is PartiqlAst.Expr.Divide -> arithmeticOpRedaction(node.operands) - is PartiqlAst.Expr.Modulo -> arithmeticOpRedaction(node.operands) - is PartiqlAst.Expr.Concat -> arithmeticOpRedaction(node.operands) - // BETWEEN - is PartiqlAst.Expr.Between -> { - if (!skipRedaction(node.value, safeFieldNames)) { - redactExpr(node.from) - redactExpr(node.to) - } - } - // CALL - is PartiqlAst.Expr.Call -> redactCall(node) - else -> { /* intentionally blank */ } - } - } - - private fun redactLiteral(literal: PartiqlAst.Expr.Lit) { - val sourceLocation = literal.metas.sourceLocation ?: error("Cannot redact due to missing source location") - sourceLocationMetaForRedaction.add(sourceLocation) - } - - // once `bag`, `list`, and `sexp` modeled as described here: https://github.com/partiql/partiql-lang-kotlin/issues/239, - // delete duplicated code - private fun redactSeq(seq: PartiqlAst.Expr.List) = seq.values.map { redactExpr(it) } - private fun redactSeq(seq: PartiqlAst.Expr.Bag) = seq.values.map { redactExpr(it) } - private fun redactSeq(seq: PartiqlAst.Expr.Sexp) = seq.values.map { redactExpr(it) } - - private fun redactStruct(struct: PartiqlAst.Expr.Struct) { - struct.fields.map { - if (it.first is PartiqlAst.Expr.Lit) { - redactLiteral(it.first as PartiqlAst.Expr.Lit) - } - redactExpr(it.second) - } - } - - private fun redactCall(node: PartiqlAst.Expr.Call) { - val funcName = node.funcName.text - when (val redactionLambda = userDefinedFunctionRedactionConfig[funcName]) { - null -> node.args.map { redactExpr(it) } - else -> { - redactionLambda(node.args).map { redactExpr(it) } - } - } - } - - private fun redactTypes(typed: PartiqlAst.Expr.IsType) { - if (typed.value is PartiqlAst.Expr.Id && !skipRedaction(typed.value, safeFieldNames)) { - val sourceLocation = typed.type.metas.sourceLocation ?: error("Cannot redact due to missing source location") - sourceLocationMetaForRedaction.add(sourceLocation) - } - } - - /** - * For [PartiqlAst.DmlOp.Insert], redacts every element of VALUES clause BAG value; for struct elements in the bag, it - * follows the redaction rules that [redactStructInInsertValueOp] applies. - * For example, given: - * INSERT INTO tb <<{ 'hk': 'a', 'rk': 1, 'attr': { 'hk': 'a' }}>>" - * REPLACE INTO tb << { 'dummy1' : 'hashKey', 'dummy2' : 'rangeKey', 'dummyTestAttribute' : '123' } >> - * - * Expected: - * INSERT INTO tb <<{ 'hk': 'a', 'rk': 1, 'attr': { ***(Redacted): ***(Redacted) }}>> - * REPLACE INTO tb <<{ 'dummy1' : ***(Redacted), 'dummy2' : ***(Redacted), 'dummyTestAttribute' : ***(Redacted) }>> - */ - private fun redactBagInInserOpValues(bag: PartiqlAst.Expr.Bag) { - bag.values.map { - if (it is PartiqlAst.Expr.Struct) { - redactStructInInsertValueOp(it) - } else { - redactExpr(it) - } - } - } - - /** - * For [PartiqlAst.DmlOp.InsertValue], only the outermost level of struct files could have a key attribute. - * For example, in the struct { 'hk': 'a', 'rk': 1, 'attr': { 'hk': 'a' }}, - * only 'hk' in 'attr': { 'hk': 'a' } will be redacted - */ - private fun redactStructInInsertValueOp(struct: PartiqlAst.Expr.Struct) { - struct.fields.map { - when (it.first) { - is PartiqlAst.Expr.Lit -> - if (!skipRedaction(it.first, safeFieldNames)) { - redactExpr(it.second) - } else { /* intentionally blank */ } - } - } - } - - // once NAry node modeled better in PIG (https://github.com/partiql/partiql-lang-kotlin/issues/241), this code can be - // refactored - // TODO: other NAry ops that not modeled (LIKE, INTERSECT, INTERSECT_ALL, EXCEPT, EXCEPT_ALL, UNION, UNION_ALL) - private fun PartiqlAst.Expr.isNAry(): Boolean { - return this is PartiqlAst.Expr.And || - this is PartiqlAst.Expr.Or || - this is PartiqlAst.Expr.Not || - this is PartiqlAst.Expr.Eq || - this is PartiqlAst.Expr.Ne || - this is PartiqlAst.Expr.Gt || - this is PartiqlAst.Expr.Gte || - this is PartiqlAst.Expr.Lt || - this is PartiqlAst.Expr.Lte || - this is PartiqlAst.Expr.InCollection || - this is PartiqlAst.Expr.Pos || - this is PartiqlAst.Expr.Neg || - this is PartiqlAst.Expr.Plus || - this is PartiqlAst.Expr.Minus || - this is PartiqlAst.Expr.Times || - this is PartiqlAst.Expr.Divide || - this is PartiqlAst.Expr.Modulo || - this is PartiqlAst.Expr.Concat || - this is PartiqlAst.Expr.Between || - this is PartiqlAst.Expr.Call - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeExtensions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeExtensions.kt deleted file mode 100644 index 73ad6deb2e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeExtensions.kt +++ /dev/null @@ -1,233 +0,0 @@ -package org.partiql.lang.ast.passes.inference - -import org.partiql.lang.eval.ExprValue -import org.partiql.types.AnyOfType -import org.partiql.types.AnyType -import org.partiql.types.BlobType -import org.partiql.types.BoolType -import org.partiql.types.ClobType -import org.partiql.types.CollectionType -import org.partiql.types.DecimalType -import org.partiql.types.FloatType -import org.partiql.types.IntType -import org.partiql.types.MissingType -import org.partiql.types.NullType -import org.partiql.types.SingleType -import org.partiql.types.StaticType -import org.partiql.types.StringType -import org.partiql.types.StructType -import org.partiql.types.SymbolType -import org.partiql.types.TimestampType - -internal fun StaticType.isNullOrMissing(): Boolean = (this is NullType || this is MissingType) -internal fun StaticType.isNumeric(): Boolean = (this is IntType || this is FloatType || this is DecimalType) -internal fun StaticType.isText(): Boolean = (this is SymbolType || this is StringType) -internal fun StaticType.isLob(): Boolean = (this is BlobType || this is ClobType) -internal fun StaticType.isUnknown(): Boolean = (this.isNullOrMissing() || this == StaticType.NULL_OR_MISSING) - -/** - * Returns the maximum number of digits a decimal can hold after reserving digits for scale - * - * For example: The maximum value a DECIMAL(5,2) can represent is 999.99, therefore the maximum - * number of digits it can hold is 3 (i.e up to 999). - */ -private fun DecimalType.maxDigits(): Int { - val precision = when (precisionScaleConstraint) { - // TODO: What's PartiQL's max allowed precision? - DecimalType.PrecisionScaleConstraint.Unconstrained -> Int.MAX_VALUE - is DecimalType.PrecisionScaleConstraint.Constrained -> (precisionScaleConstraint as DecimalType.PrecisionScaleConstraint.Constrained).precision - } - - val scale = when (precisionScaleConstraint) { - DecimalType.PrecisionScaleConstraint.Unconstrained -> 0 - is DecimalType.PrecisionScaleConstraint.Constrained -> (precisionScaleConstraint as DecimalType.PrecisionScaleConstraint.Constrained).scale - } - - return precision - scale -} - -/** - * Casts [this] static to the given target type. - * - * This replicates the behavior of its runtime equivalent [ExprValue.cast]. - * @see [ExprValue.cast] for documentation. - */ -internal fun StaticType.cast(targetType: StaticType): StaticType { - when (targetType) { - is AnyOfType -> { - // TODO we should do more sophisticated inference based on the source like we do for single types - val includesNull = this.allTypes.any { it.isNullable() } - return when { - includesNull -> StaticType.unionOf(StaticType.MISSING, StaticType.NULL, targetType) - else -> StaticType.unionOf(StaticType.MISSING, targetType) - } - } - is AnyType -> { - // casting to `ANY` is the identity - return this - } - is SingleType -> {} - } - - // union source types, recursively process them - when (this) { - is AnyType -> return AnyOfType(this.toAnyOfType().types.map { it.cast(targetType) }.toSet()).flatten() - is AnyOfType -> return when (val flattened = this.flatten()) { - is SingleType, is AnyType -> flattened.cast(targetType) - is AnyOfType -> AnyOfType(flattened.types.map { it.cast(targetType) }.toSet()).flatten() - } - } - - // single source type - when { - this.isNullOrMissing() && targetType == StaticType.MISSING -> return StaticType.MISSING - this.isNullOrMissing() && targetType == StaticType.NULL -> return StaticType.NULL - // `MISSING` and `NULL` always convert to themselves no matter the target type - this.isNullOrMissing() -> return this - else -> { - when (targetType) { - is BoolType -> when { - this is BoolType || this.isNumeric() || this.isText() -> return targetType - } - is IntType -> when { - this is BoolType -> return targetType - this is IntType -> { - return when (targetType.rangeConstraint) { - IntType.IntRangeConstraint.SHORT -> when (this.rangeConstraint) { - IntType.IntRangeConstraint.SHORT -> targetType - IntType.IntRangeConstraint.INT4, IntType.IntRangeConstraint.LONG, IntType.IntRangeConstraint.UNCONSTRAINED -> StaticType.unionOf(StaticType.MISSING, targetType) - } - IntType.IntRangeConstraint.INT4 -> when (this.rangeConstraint) { - IntType.IntRangeConstraint.SHORT, IntType.IntRangeConstraint.INT4 -> targetType - IntType.IntRangeConstraint.LONG, IntType.IntRangeConstraint.UNCONSTRAINED -> StaticType.unionOf(StaticType.MISSING, targetType) - } - IntType.IntRangeConstraint.LONG -> when (this.rangeConstraint) { - IntType.IntRangeConstraint.SHORT, IntType.IntRangeConstraint.INT4, IntType.IntRangeConstraint.LONG -> targetType - IntType.IntRangeConstraint.UNCONSTRAINED -> StaticType.unionOf(StaticType.MISSING, targetType) - } - IntType.IntRangeConstraint.UNCONSTRAINED -> targetType - } - } - this is FloatType -> return when (targetType.rangeConstraint) { - IntType.IntRangeConstraint.UNCONSTRAINED -> targetType - else -> StaticType.unionOf(StaticType.MISSING, targetType) - } - - this is DecimalType -> return when (targetType.rangeConstraint) { - IntType.IntRangeConstraint.UNCONSTRAINED -> targetType - IntType.IntRangeConstraint.SHORT, IntType.IntRangeConstraint.INT4, IntType.IntRangeConstraint.LONG -> - return when (this.precisionScaleConstraint) { - DecimalType.PrecisionScaleConstraint.Unconstrained -> StaticType.unionOf(StaticType.MISSING, targetType) - is DecimalType.PrecisionScaleConstraint.Constrained -> { - - // Max value of SMALLINT is 32767. - // Conversion to SMALLINT will work as long as the decimal holds up 4 to digits. There is a chance of overflow beyond that. - // Similarly - - // Max value of INT4 is 2,147,483,647 - // Max value of BIGINT is 9,223,372,036,854,775,807 for BIGINT - // TODO: Move these magic numbers out. - val maxDigitsWithoutPrecisionLoss = when (targetType.rangeConstraint) { - IntType.IntRangeConstraint.SHORT -> 4 - IntType.IntRangeConstraint.INT4 -> 9 - IntType.IntRangeConstraint.LONG -> 18 - IntType.IntRangeConstraint.UNCONSTRAINED -> error("Un-constrained is handled above. This code shouldn't be reached.") - } - - if (this.maxDigits() > maxDigitsWithoutPrecisionLoss) { - StaticType.unionOf(StaticType.MISSING, targetType) - } else { - targetType - } - } - } - } - this.isText() -> return StaticType.unionOf(targetType, StaticType.MISSING) - } - is FloatType -> when { - this is BoolType -> return targetType - // Conversion to float will always succeed for numeric types - this.isNumeric() -> return targetType - this.isText() -> return StaticType.unionOf(targetType, StaticType.MISSING) - } - is DecimalType -> when { - this is DecimalType -> { - return if (targetType.maxDigits() >= this.maxDigits()) { - targetType - } else { - StaticType.unionOf(targetType, StaticType.MISSING) - } - } - this is IntType -> return when (targetType.precisionScaleConstraint) { - DecimalType.PrecisionScaleConstraint.Unconstrained -> targetType - is DecimalType.PrecisionScaleConstraint.Constrained -> when (this.rangeConstraint) { - IntType.IntRangeConstraint.UNCONSTRAINED -> StaticType.unionOf(StaticType.MISSING, targetType) - IntType.IntRangeConstraint.SHORT -> - // TODO: Move the magic numbers out - // max smallint value 32,767, so the decimal needs to be able to hold at least 5 digits - if (targetType.maxDigits() >= 5) { - targetType - } else { - StaticType.unionOf(StaticType.MISSING, targetType) - } - IntType.IntRangeConstraint.INT4 -> - // max int4 value 2,147,483,647 so the decimal needs to be able to hold at least 10 digits - if (targetType.maxDigits() >= 10) { - targetType - } else { - StaticType.unionOf(StaticType.MISSING, targetType) - } - IntType.IntRangeConstraint.LONG -> - // max bigint value 9,223,372,036,854,775,807 so the decimal needs to be able to hold at least 19 digits - if (targetType.maxDigits() >= 19) { - targetType - } else { - StaticType.unionOf(StaticType.MISSING, targetType) - } - } - } - - this is BoolType || this.isNumeric() -> return targetType - this.isText() -> return StaticType.unionOf(targetType, StaticType.MISSING) - } - is TimestampType -> when { - this is TimestampType -> return targetType - this.isText() -> return StaticType.unionOf(targetType, StaticType.MISSING) - } - is StringType, is SymbolType -> when { - this.isNumeric() || this.isText() -> return targetType - this is BoolType || this is TimestampType -> return targetType - } - is ClobType -> when (this) { - is ClobType, is BlobType -> return targetType - } - is BlobType -> when (this) { - is ClobType, is BlobType -> return targetType - } - is CollectionType -> when (this) { - is CollectionType -> return targetType - } - is StructType -> when (this) { - is StructType -> return targetType - } - } - // TODO: support non-permissive mode(s) here by throwing an exception to indicate cast is not possible - return StaticType.MISSING - } - } -} - -/** - * For [this] [StaticType], filters out [NullType] and [MissingType] from [AnyOfType]s. Otherwise, returns [this]. - */ -internal fun StaticType.filterNullMissing(): StaticType = - when (this) { - is AnyOfType -> AnyOfType(this.types.filter { !it.isNullOrMissing() }.toSet()).flatten() - else -> this - } - -/** - * Returns a human-readable string of [argTypes]. Additionally, for each [AnyOfType], [NullType] and [MissingType] will - * be filtered. - */ -internal fun stringWithoutNullMissing(argTypes: List): String = - argTypes.joinToString { it.filterNullMissing().toString() } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeInferencer.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeInferencer.kt deleted file mode 100644 index e6fa3b8f1a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeInferencer.kt +++ /dev/null @@ -1,110 +0,0 @@ -package org.partiql.lang.ast.passes.inference - -import org.partiql.errors.Problem -import org.partiql.errors.ProblemSeverity -import org.partiql.lang.ast.passes.SemanticException -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.staticType -import org.partiql.lang.errors.ProblemCollector -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.visitors.StaticTypeInferenceVisitorTransform -import org.partiql.lang.eval.visitors.StaticTypeVisitorTransform -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.types.TypedOpParameter -import org.partiql.types.StaticType - -/** - * Infers the [StaticType] of a [PartiqlAst.Statement]. Assumes [StaticTypeVisitorTransform] was run before on this - * [PartiqlAst.Statement] and all implicit variables have been resolved. - * - * @param globalBindings The global bindings to the static environment. This is a data catalog purely from a lookup - * perspective. - * @param customFunctionSignatures Custom user-defined function signatures that can be called by the query. - * @param customTypedOpParameters Mapping of custom type name to [TypedOpParameter] to be used for typed operators - * (i.e CAST/IS) inference. - */ -class StaticTypeInferencer( - private val globalBindings: Bindings, - private val customFunctionSignatures: List, - private val customTypedOpParameters: Map, -) { - /** - * Infers the [StaticType] of [node] and returns an [InferenceResult]. Currently does not support inference for - * [PartiqlAst.Statement.Dml] and [PartiqlAst.Statement.Ddl] statements. - * - * Returns [InferenceResult.Success] if no encountered [Problem] has severity of [ProblemSeverity.ERROR]. This - * result will contain the [node]'s [StaticType] and encountered problems (all of which will have - * [ProblemSeverity.WARNING]). - * - * Otherwise, returns [InferenceResult.Failure] with all of the [Problem]s encountered regardless of severity. - * - * @param node [PartiqlAst.Statement] to perform static type inference on - */ - fun inferStaticType(node: PartiqlAst.Statement): InferenceResult { - val problemCollector = ProblemCollector() - val inferencer = StaticTypeInferenceVisitorTransform(globalBindings, customFunctionSignatures, customTypedOpParameters, problemCollector) - val transformedPartiqlAst = inferencer.transformStatement(node) - val inferredStaticType = when (transformedPartiqlAst) { - is PartiqlAst.Statement.Query -> - transformedPartiqlAst.expr.metas.staticType?.type - ?: error("Expected query's inferred StaticType to not be null") - is PartiqlAst.Statement.Dml, - is PartiqlAst.Statement.Ddl, - is PartiqlAst.Statement.Explain, - is PartiqlAst.Statement.Exec -> error("Type inference for DML, DDL, EXEC, and EXPLAIN statements is not currently supported") - } - return when { - problemCollector.hasErrors -> InferenceResult.Failure(inferredStaticType, transformedPartiqlAst, problemCollector.problems) - else -> InferenceResult.Success(inferredStaticType, problemCollector.problems) - } - } - - /** - * Result of static type inference on a query. Can be one of: - * - [InferenceResult.Success] or - * - [InferenceResult.Failure] - */ - sealed class InferenceResult { - /** - * List of all the [Problem]s encountered during static type inference. - */ - abstract val problems: List - - /** - * Successful static type inference result (i.e. no problems encountered with [ProblemSeverity.ERROR]). - * - * @param staticType overall query's [StaticType] - * @param problems all of the [Problem]s encountered through static type inference, which will all have - * [ProblemSeverity.WARNING] - */ - data class Success(val staticType: StaticType, override val problems: List) : InferenceResult() - - /** - * Unsuccessful static type inference result due to at least one [Problem] encountered with - * [ProblemSeverity.ERROR] - * - * @param staticType overall query's [StaticType]. This is internal because the static type cannot be relied - * upon when a static inference problem is encountered with severity [ProblemSeverity.ERROR]. It is used - * internally for testing the query's type inference behavior after an error is encountered. - * @param partiqlAst query's [StaticType]-annotated [PartiqlAst]. This is internal because the static type - * cannot be relied upon when a static inference problem is encountered with severity [ProblemSeverity.ERROR]. - * It is used internally for testing the query's type inference behavior after an error is encountered. - * @param problems all of the [Problem]s encountered through static type inference. - */ - data class Failure(internal val staticType: StaticType, internal val partiqlAst: PartiqlAst.Statement, override val problems: List) : InferenceResult() - } -} - -/** - * From [this] [StaticTypeInferencer.InferenceResult], returns the [StaticType] if and only if there are no [Problem]s - * encountered with [ProblemSeverity.ERROR]. Otherwise, throws the first [Problem] with [ProblemSeverity.ERROR] as - * a [SemanticException]. - */ -fun StaticTypeInferencer.InferenceResult.staticTypeOrError(): StaticType = - when (this) { - is StaticTypeInferencer.InferenceResult.Success -> staticType - is StaticTypeInferencer.InferenceResult.Failure -> { - val firstError = problems.first { it.details.severity == ProblemSeverity.ERROR } - throw SemanticException(firstError) - } - } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompiler.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompiler.kt deleted file mode 100644 index 1f08f2311b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompiler.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.compiler - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.PartiQLStatement -import org.partiql.lang.planner.PartiQLPlanner - -/** - * [PartiQLCompiler] is responsible for transforming a [PartiqlPhysical.Plan] into an executable [PartiQLStatement]. - */ -@ExperimentalPartiQLCompilerPipeline -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsync")) -interface PartiQLCompiler { - - /** - * Compiles the [PartiqlPhysical.Plan] to an executable [PartiQLStatement]. - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsync.compile")) - fun compile(statement: PartiqlPhysical.Plan): PartiQLStatement - - /** - * Compiles the [PartiqlPhysical.Statement.Explain] with the details provided in [details] - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsync.compile")) - fun compile(statement: PartiqlPhysical.Plan, details: PartiQLPlanner.PlanningDetails): PartiQLStatement -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerAsync.kt deleted file mode 100644 index c8a2ba10d8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerAsync.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.compiler - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.PartiQLStatementAsync -import org.partiql.lang.planner.PartiQLPlanner - -/** - * [PartiQLCompilerAsync] is responsible for transforming a [PartiqlPhysical.Plan] into an executable [PartiQLStatementAsync]. - */ -@ExperimentalPartiQLCompilerPipeline -interface PartiQLCompilerAsync { - - /** - * Compiles the [PartiqlPhysical.Plan] to an executable [PartiQLStatementAsync]. - */ - suspend fun compile(statement: PartiqlPhysical.Plan): PartiQLStatementAsync - - /** - * Compiles the [PartiqlPhysical.Statement.Explain] with the details provided in [details] - */ - suspend fun compile(statement: PartiqlPhysical.Plan, details: PartiQLPlanner.PlanningDetails): PartiQLStatementAsync -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerAsyncBuilder.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerAsyncBuilder.kt deleted file mode 100644 index 321bbf0949..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerAsyncBuilder.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.compiler - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ThunkReturnTypeAssertions -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.eval.builtins.DynamicLookupExprFunction -import org.partiql.lang.eval.builtins.SCALAR_BUILTINS_DEFAULT -import org.partiql.lang.eval.builtins.definitionalBuiltins -import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure -import org.partiql.lang.eval.physical.operators.AggregateOperatorFactoryDefaultAsync -import org.partiql.lang.eval.physical.operators.FilterRelationalOperatorFactoryDefaultAsync -import org.partiql.lang.eval.physical.operators.JoinRelationalOperatorFactoryDefaultAsync -import org.partiql.lang.eval.physical.operators.LetRelationalOperatorFactoryDefaultAsync -import org.partiql.lang.eval.physical.operators.LimitRelationalOperatorFactoryDefaultAsync -import org.partiql.lang.eval.physical.operators.OffsetRelationalOperatorFactoryDefaultAsync -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.ScanRelationalOperatorFactoryDefaultAsync -import org.partiql.lang.eval.physical.operators.SortOperatorFactoryDefaultAsync -import org.partiql.lang.eval.physical.operators.UnpivotOperatorFactoryDefaultAsync -import org.partiql.lang.eval.physical.operators.WindowRelationalOperatorFactoryDefaultAsync -import org.partiql.lang.planner.EvaluatorOptions -import org.partiql.lang.types.CustomType - -/** - * Builder class to instantiate a [PartiQLCompilerAsync]. - * - * Example usages: - * - * ``` - * // Default - * val compiler = PartiQLCompilerAsyncBuilder.standard().build() - * - * // Fluent builder - * val compiler = PartiQLCompilerAsyncBuilder.standard() - * .customFunctions(myCustomFunctionList) - * .build() - * ``` - */ - -@ExperimentalPartiQLCompilerPipeline -class PartiQLCompilerAsyncBuilder private constructor() { - - private var options: EvaluatorOptions = EvaluatorOptions.standard() - private var customTypes: List = emptyList() - private var customFunctions: List = emptyList() - private var customProcedures: List = emptyList() - private var customOperatorFactories: List = emptyList() - - companion object { - - /** - * A collection of all the default relational operator implementations provided by PartiQL. - * - * By default, the query planner will select these as the implementations for all relational operators, but - * alternate implementations may be provided and chosen by physical plan passes. - * - * @see [org.partiql.lang.planner.PlannerPipeline.Builder.addPhysicalPlanPass] - * @see [org.partiql.lang.planner.PlannerPipeline.Builder.addRelationalOperatorFactory] - */ - - private val DEFAULT_RELATIONAL_OPERATOR_FACTORIES = listOf( - AggregateOperatorFactoryDefaultAsync, - SortOperatorFactoryDefaultAsync, - UnpivotOperatorFactoryDefaultAsync, - FilterRelationalOperatorFactoryDefaultAsync, - ScanRelationalOperatorFactoryDefaultAsync, - JoinRelationalOperatorFactoryDefaultAsync, - OffsetRelationalOperatorFactoryDefaultAsync, - LimitRelationalOperatorFactoryDefaultAsync, - LetRelationalOperatorFactoryDefaultAsync, - // Notice here we will not propagate the optin requirement to the user - @OptIn(ExperimentalWindowFunctions::class) - WindowRelationalOperatorFactoryDefaultAsync, - ) - - @JvmStatic - fun standard() = PartiQLCompilerAsyncBuilder() - } - - fun build(): PartiQLCompilerAsync { - if (options.thunkOptions.thunkReturnTypeAssertions == ThunkReturnTypeAssertions.ENABLED) { - TODO("ThunkReturnTypeAssertions.ENABLED requires a static type pass") - } - return PartiQLCompilerAsyncDefault( - evaluatorOptions = options, - customTypedOpParameters = customTypes.associateBy( - keySelector = { it.name }, - valueTransform = { it.typedOpParameter } - ), - functions = allFunctions(options.typingMode), - procedures = customProcedures.associateBy( - keySelector = { it.signature.name }, - valueTransform = { it } - ), - operatorFactories = allOperatorFactories() - ) - } - - fun options(options: EvaluatorOptions) = this.apply { - this.options = options - } - - fun customFunctions(customFunctions: List) = this.apply { - this.customFunctions = customFunctions - } - - fun customTypes(customTypes: List) = this.apply { - this.customTypes = customTypes - } - - fun customProcedures(customProcedures: List) = this.apply { - this.customProcedures = customProcedures - } - - fun customOperatorFactories(customOperatorFactories: List) = this.apply { - this.customOperatorFactories = customOperatorFactories - } - - // --- Internal ---------------------------------- - - private fun allFunctions(typingMode: TypingMode): List { - val definitionalBuiltins = definitionalBuiltins(typingMode) - val builtins = SCALAR_BUILTINS_DEFAULT - return definitionalBuiltins + builtins + customFunctions + DynamicLookupExprFunction() - } - - private fun allOperatorFactories() = (DEFAULT_RELATIONAL_OPERATOR_FACTORIES + customOperatorFactories).apply { - groupBy { it.key }.entries.firstOrNull { it.value.size > 1 }?.let { - error( - "More than one BindingsOperatorFactory for ${it.key.operator} named '${it.value}' was specified." - ) - } - }.associateBy { it.key } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerAsyncDefault.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerAsyncDefault.kt deleted file mode 100644 index 0b98276430..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerAsyncDefault.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.compiler - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.PartiqlLogical -import org.partiql.lang.domains.PartiqlLogicalResolved -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.errors.PartiQLException -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.PartiQLResult -import org.partiql.lang.eval.PartiQLStatementAsync -import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure -import org.partiql.lang.eval.physical.PhysicalBexprToThunkConverterAsync -import org.partiql.lang.eval.physical.PhysicalPlanCompilerAsync -import org.partiql.lang.eval.physical.PhysicalPlanCompilerAsyncImpl -import org.partiql.lang.eval.physical.PhysicalPlanThunkAsync -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactoryKey -import org.partiql.lang.planner.EvaluatorOptions -import org.partiql.lang.planner.PartiQLPlanner -import org.partiql.lang.types.TypedOpParameter - -@ExperimentalPartiQLCompilerPipeline -internal class PartiQLCompilerAsyncDefault( - evaluatorOptions: EvaluatorOptions, - customTypedOpParameters: Map, - functions: List, - procedures: Map, - operatorFactories: Map -) : PartiQLCompilerAsync { - - private lateinit var exprConverter: PhysicalPlanCompilerAsyncImpl - private val bexprConverter = PhysicalBexprToThunkConverterAsync( - exprConverter = object : PhysicalPlanCompilerAsync { - override suspend fun convert(expr: PartiqlPhysical.Expr): PhysicalPlanThunkAsync = exprConverter.convert(expr) - }, - relationalOperatorFactory = operatorFactories - ) - - init { - exprConverter = PhysicalPlanCompilerAsyncImpl( - functions = functions, - customTypedOpParameters = customTypedOpParameters, - procedures = procedures, - evaluatorOptions = evaluatorOptions, - bexperConverter = bexprConverter - ) - } - - override suspend fun compile(statement: PartiqlPhysical.Plan): PartiQLStatementAsync { - return when (val stmt = statement.stmt) { - is PartiqlPhysical.Statement.Dml -> compileDml(stmt, statement.locals.size) - is PartiqlPhysical.Statement.Exec, - is PartiqlPhysical.Statement.Query -> { - val expression = exprConverter.compile(statement) - PartiQLStatementAsync { expression.eval(it) } - } - is PartiqlPhysical.Statement.Explain -> throw PartiQLException("Unable to compile EXPLAIN without details.") - } - } - - override suspend fun compile(statement: PartiqlPhysical.Plan, details: PartiQLPlanner.PlanningDetails): PartiQLStatementAsync { - return when (val stmt = statement.stmt) { - is PartiqlPhysical.Statement.Dml -> compileDml(stmt, statement.locals.size) - is PartiqlPhysical.Statement.Exec, - is PartiqlPhysical.Statement.Query -> compile(statement) - is PartiqlPhysical.Statement.Explain -> PartiQLStatementAsync { compileExplain(stmt, details) } - } - } - - // --- INTERNAL ------------------- - - private enum class ExplainDomains { - AST, - AST_NORMALIZED, - LOGICAL, - LOGICAL_RESOLVED, - PHYSICAL, - PHYSICAL_TRANSFORMED - } - - private suspend fun compileDml(dml: PartiqlPhysical.Statement.Dml, localsSize: Int): PartiQLStatementAsync { - val rows = exprConverter.compile(dml.rows, localsSize) - return PartiQLStatementAsync { session -> - when (dml.operation) { - is PartiqlPhysical.DmlOperation.DmlReplace -> PartiQLResult.Replace(dml.uniqueId.text, (rows.eval(session) as PartiQLResult.Value).value) - is PartiqlPhysical.DmlOperation.DmlInsert -> PartiQLResult.Insert(dml.uniqueId.text, (rows.eval(session) as PartiQLResult.Value).value) - is PartiqlPhysical.DmlOperation.DmlDelete -> PartiQLResult.Delete(dml.uniqueId.text, (rows.eval(session) as PartiQLResult.Value).value) - is PartiqlPhysical.DmlOperation.DmlUpdate -> TODO("DML Update compilation not supported yet.") - } - } - } - - private fun compileExplain(statement: PartiqlPhysical.Statement.Explain, details: PartiQLPlanner.PlanningDetails): PartiQLResult.Explain.Domain { - return when (val target = statement.target) { - is PartiqlPhysical.ExplainTarget.Domain -> compileExplainDomain(target, details) - } - } - - private fun compileExplainDomain(statement: PartiqlPhysical.ExplainTarget.Domain, details: PartiQLPlanner.PlanningDetails): PartiQLResult.Explain.Domain { - val format = statement.format?.text - val type = statement.type?.text?.uppercase() ?: ExplainDomains.AST.name - val domain = try { - ExplainDomains.valueOf(type) - } catch (ex: IllegalArgumentException) { - throw PartiQLException("Illegal argument: $type") - } - return when (domain) { - ExplainDomains.AST -> { - val explain = details.ast!! as PartiqlAst.Statement.Explain - val target = explain.target as PartiqlAst.ExplainTarget.Domain - PartiQLResult.Explain.Domain(target.statement, format) - } - ExplainDomains.AST_NORMALIZED -> { - val explain = details.astNormalized!! as PartiqlAst.Statement.Explain - val target = explain.target as PartiqlAst.ExplainTarget.Domain - PartiQLResult.Explain.Domain(target.statement, format) - } - ExplainDomains.LOGICAL -> { - val explain = details.logical!!.stmt as PartiqlLogical.Statement.Explain - val target = explain.target as PartiqlLogical.ExplainTarget.Domain - val plan = details.logical.copy(stmt = target.statement) - PartiQLResult.Explain.Domain(plan, format) - } - ExplainDomains.LOGICAL_RESOLVED -> { - val explain = details.logicalResolved!!.stmt as PartiqlLogicalResolved.Statement.Explain - val target = explain.target as PartiqlLogicalResolved.ExplainTarget.Domain - val plan = details.logicalResolved.copy(stmt = target.statement) - PartiQLResult.Explain.Domain(plan, format) - } - ExplainDomains.PHYSICAL -> { - val explain = details.physical!!.stmt as PartiqlPhysical.Statement.Explain - val target = explain.target as PartiqlPhysical.ExplainTarget.Domain - val plan = details.physical.copy(stmt = target.statement) - PartiQLResult.Explain.Domain(plan, format) - } - ExplainDomains.PHYSICAL_TRANSFORMED -> { - val explain = details.physicalTransformed!!.stmt as PartiqlPhysical.Statement.Explain - val target = explain.target as PartiqlPhysical.ExplainTarget.Domain - val plan = details.physicalTransformed.copy(stmt = target.statement) - PartiQLResult.Explain.Domain(plan, format) - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerBuilder.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerBuilder.kt deleted file mode 100644 index 6c3bbb87f7..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerBuilder.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.compiler - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ThunkReturnTypeAssertions -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.eval.builtins.DynamicLookupExprFunction -import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure -import org.partiql.lang.eval.internal.builtins.SCALAR_BUILTINS_DEFAULT -import org.partiql.lang.eval.internal.builtins.definitionalBuiltins -import org.partiql.lang.eval.physical.operators.AggregateOperatorFactoryDefault -import org.partiql.lang.eval.physical.operators.FilterRelationalOperatorFactoryDefault -import org.partiql.lang.eval.physical.operators.JoinRelationalOperatorFactoryDefault -import org.partiql.lang.eval.physical.operators.LetRelationalOperatorFactoryDefault -import org.partiql.lang.eval.physical.operators.LimitRelationalOperatorFactoryDefault -import org.partiql.lang.eval.physical.operators.OffsetRelationalOperatorFactoryDefault -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.ScanRelationalOperatorFactoryDefault -import org.partiql.lang.eval.physical.operators.SortOperatorFactoryDefault -import org.partiql.lang.eval.physical.operators.UnpivotOperatorFactoryDefault -import org.partiql.lang.eval.physical.operators.WindowRelationalOperatorFactoryDefault -import org.partiql.lang.planner.EvaluatorOptions -import org.partiql.lang.types.CustomType - -/** - * Builder class to instantiate a [PartiQLCompiler]. - * - * Example usages: - * - * ``` - * // Default - * val compiler = PartiQLCompilerBuilder.standard().build() - * - * // Fluent builder - * val compiler = PartiQLCompilerBuilder.standard() - * .customFunctions(myCustomFunctionList) - * .build() - * ``` - */ - -@ExperimentalPartiQLCompilerPipeline -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsyncBuilder")) -class PartiQLCompilerBuilder private constructor() { - - private var options: EvaluatorOptions = EvaluatorOptions.standard() - private var customTypes: List = emptyList() - private var customFunctions: List = emptyList() - private var customProcedures: List = emptyList() - private var customOperatorFactories: List = emptyList() - - companion object { - - /** - * A collection of all the default relational operator implementations provided by PartiQL. - * - * By default, the query planner will select these as the implementations for all relational operators, but - * alternate implementations may be provided and chosen by physical plan passes. - * - * @see [org.partiql.lang.planner.PlannerPipeline.Builder.addPhysicalPlanPass] - * @see [org.partiql.lang.planner.PlannerPipeline.Builder.addRelationalOperatorFactory] - */ - - private val DEFAULT_RELATIONAL_OPERATOR_FACTORIES = listOf( - AggregateOperatorFactoryDefault, - SortOperatorFactoryDefault, - UnpivotOperatorFactoryDefault, - FilterRelationalOperatorFactoryDefault, - ScanRelationalOperatorFactoryDefault, - JoinRelationalOperatorFactoryDefault, - OffsetRelationalOperatorFactoryDefault, - LimitRelationalOperatorFactoryDefault, - LetRelationalOperatorFactoryDefault, - // Notice here we will not propagate the optin requirement to the user - @OptIn(ExperimentalWindowFunctions::class) - WindowRelationalOperatorFactoryDefault, - ) - - @JvmStatic - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsyncBuilder.standard")) - fun standard() = PartiQLCompilerBuilder() - } - - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsyncBuilder.build")) - fun build(): PartiQLCompiler { - if (options.thunkOptions.thunkReturnTypeAssertions == ThunkReturnTypeAssertions.ENABLED) { - TODO("ThunkReturnTypeAssertions.ENABLED requires a static type pass") - } - return PartiQLCompilerDefault( - evaluatorOptions = options, - customTypedOpParameters = customTypes.associateBy( - keySelector = { it.name }, - valueTransform = { it.typedOpParameter } - ), - functions = allFunctions(options.typingMode), - procedures = customProcedures.associateBy( - keySelector = { it.signature.name }, - valueTransform = { it } - ), - operatorFactories = allOperatorFactories() - ) - } - - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsyncBuilder.options")) - fun options(options: EvaluatorOptions) = this.apply { - this.options = options - } - - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsyncBuilder.customFunctions")) - fun customFunctions(customFunctions: List) = this.apply { - this.customFunctions = customFunctions - } - - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsyncBuilder.customTypes")) - fun customTypes(customTypes: List) = this.apply { - this.customTypes = customTypes - } - - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsyncBuilder.customProcedures")) - fun customProcedures(customProcedures: List) = this.apply { - this.customProcedures = customProcedures - } - - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsyncBuilder.customOperatorFactories")) - fun customOperatorFactories(customOperatorFactories: List) = this.apply { - this.customOperatorFactories = customOperatorFactories - } - - // --- Internal ---------------------------------- - - private fun allFunctions(typingMode: TypingMode): List { - val definitionalBuiltins = definitionalBuiltins(typingMode) - val builtins = SCALAR_BUILTINS_DEFAULT - val allFunctions = definitionalBuiltins + builtins + customFunctions + DynamicLookupExprFunction() - return allFunctions - } - - private fun allOperatorFactories() = (DEFAULT_RELATIONAL_OPERATOR_FACTORIES + customOperatorFactories).apply { - groupBy { it.key }.entries.firstOrNull { it.value.size > 1 }?.let { - error( - "More than one BindingsOperatorFactory for ${it.key.operator} named '${it.value}' was specified." - ) - } - }.associateBy { it.key } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerDefault.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerDefault.kt deleted file mode 100644 index fbd6e426d5..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerDefault.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.compiler - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.PartiqlLogical -import org.partiql.lang.domains.PartiqlLogicalResolved -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.errors.PartiQLException -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.PartiQLResult -import org.partiql.lang.eval.PartiQLStatement -import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure -import org.partiql.lang.eval.physical.PhysicalBexprToThunkConverter -import org.partiql.lang.eval.physical.PhysicalPlanCompiler -import org.partiql.lang.eval.physical.PhysicalPlanCompilerImpl -import org.partiql.lang.eval.physical.PhysicalPlanThunk -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactoryKey -import org.partiql.lang.planner.EvaluatorOptions -import org.partiql.lang.planner.PartiQLPlanner -import org.partiql.lang.types.TypedOpParameter - -@ExperimentalPartiQLCompilerPipeline -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerAsyncDefault")) -internal class PartiQLCompilerDefault( - private val evaluatorOptions: EvaluatorOptions, - private val customTypedOpParameters: Map, - private val functions: List, - private val procedures: Map, - private val operatorFactories: Map -) : PartiQLCompiler { - - private lateinit var exprConverter: PhysicalPlanCompilerImpl - private val bexprConverter = PhysicalBexprToThunkConverter( - exprConverter = object : PhysicalPlanCompiler { - override fun convert(expr: PartiqlPhysical.Expr): PhysicalPlanThunk = exprConverter.convert(expr) - }, - relationalOperatorFactory = operatorFactories - ) - - init { - exprConverter = PhysicalPlanCompilerImpl( - functions = functions, - customTypedOpParameters = customTypedOpParameters, - procedures = procedures, - evaluatorOptions = evaluatorOptions, - bexperConverter = bexprConverter - ) - } - - override fun compile(statement: PartiqlPhysical.Plan): PartiQLStatement { - return when (val stmt = statement.stmt) { - is PartiqlPhysical.Statement.Dml -> compileDml(stmt, statement.locals.size) - is PartiqlPhysical.Statement.Exec, - is PartiqlPhysical.Statement.Query -> { - val expression = exprConverter.compile(statement) - PartiQLStatement { expression.eval(it).toValue() } - } - is PartiqlPhysical.Statement.Explain -> throw PartiQLException("Unable to compile EXPLAIN without details.") - } - } - - override fun compile(statement: PartiqlPhysical.Plan, details: PartiQLPlanner.PlanningDetails): PartiQLStatement { - return when (val stmt = statement.stmt) { - is PartiqlPhysical.Statement.Dml -> compileDml(stmt, statement.locals.size) - is PartiqlPhysical.Statement.Exec, - is PartiqlPhysical.Statement.Query -> compile(statement) - is PartiqlPhysical.Statement.Explain -> PartiQLStatement { compileExplain(stmt, details) } - } - } - - // --- INTERNAL ------------------- - - private enum class ExplainDomains { - AST, - AST_NORMALIZED, - LOGICAL, - LOGICAL_RESOLVED, - PHYSICAL, - PHYSICAL_TRANSFORMED - } - - private fun compileDml(dml: PartiqlPhysical.Statement.Dml, localsSize: Int): PartiQLStatement { - val rows = exprConverter.compile(dml.rows, localsSize) - return PartiQLStatement { session -> - when (dml.operation) { - is PartiqlPhysical.DmlOperation.DmlReplace -> PartiQLResult.Replace(dml.uniqueId.text, rows.eval(session)) - is PartiqlPhysical.DmlOperation.DmlInsert -> PartiQLResult.Insert(dml.uniqueId.text, rows.eval(session)) - is PartiqlPhysical.DmlOperation.DmlDelete -> PartiQLResult.Delete(dml.uniqueId.text, rows.eval(session)) - is PartiqlPhysical.DmlOperation.DmlUpdate -> TODO("DML Update compilation not supported yet.") - } - } - } - - private fun compileExplain(statement: PartiqlPhysical.Statement.Explain, details: PartiQLPlanner.PlanningDetails): PartiQLResult.Explain.Domain { - return when (val target = statement.target) { - is PartiqlPhysical.ExplainTarget.Domain -> compileExplainDomain(target, details) - } - } - - private fun compileExplainDomain(statement: PartiqlPhysical.ExplainTarget.Domain, details: PartiQLPlanner.PlanningDetails): PartiQLResult.Explain.Domain { - val format = statement.format?.text - val type = statement.type?.text?.toUpperCase() ?: ExplainDomains.AST.name - val domain = try { - ExplainDomains.valueOf(type) - } catch (ex: IllegalArgumentException) { - throw PartiQLException("Illegal argument: $type") - } - return when (domain) { - ExplainDomains.AST -> { - val explain = details.ast!! as PartiqlAst.Statement.Explain - val target = explain.target as PartiqlAst.ExplainTarget.Domain - PartiQLResult.Explain.Domain(target.statement, format) - } - ExplainDomains.AST_NORMALIZED -> { - val explain = details.astNormalized!! as PartiqlAst.Statement.Explain - val target = explain.target as PartiqlAst.ExplainTarget.Domain - PartiQLResult.Explain.Domain(target.statement, format) - } - ExplainDomains.LOGICAL -> { - val explain = details.logical!!.stmt as PartiqlLogical.Statement.Explain - val target = explain.target as PartiqlLogical.ExplainTarget.Domain - val plan = details.logical.copy(stmt = target.statement) - PartiQLResult.Explain.Domain(plan, format) - } - ExplainDomains.LOGICAL_RESOLVED -> { - val explain = details.logicalResolved!!.stmt as PartiqlLogicalResolved.Statement.Explain - val target = explain.target as PartiqlLogicalResolved.ExplainTarget.Domain - val plan = details.logicalResolved.copy(stmt = target.statement) - PartiQLResult.Explain.Domain(plan, format) - } - ExplainDomains.PHYSICAL -> { - val explain = details.physical!!.stmt as PartiqlPhysical.Statement.Explain - val target = explain.target as PartiqlPhysical.ExplainTarget.Domain - val plan = details.physical.copy(stmt = target.statement) - PartiQLResult.Explain.Domain(plan, format) - } - ExplainDomains.PHYSICAL_TRANSFORMED -> { - val explain = details.physicalTransformed!!.stmt as PartiqlPhysical.Statement.Explain - val target = explain.target as PartiqlPhysical.ExplainTarget.Domain - val plan = details.physicalTransformed.copy(stmt = target.statement) - PartiQLResult.Explain.Domain(plan, format) - } - } - } - - private fun ExprValue.toValue(): PartiQLResult = PartiQLResult.Value(this) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerPipeline.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerPipeline.kt deleted file mode 100644 index f0eb009375..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerPipeline.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.compiler - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.errors.PartiQLException -import org.partiql.lang.eval.PartiQLStatement -import org.partiql.lang.planner.PartiQLPlanner -import org.partiql.lang.planner.PartiQLPlannerBuilder -import org.partiql.lang.syntax.Parser -import org.partiql.lang.syntax.PartiQLParserBuilder - -/** - * [PartiQLCompilerPipeline] is the top-level class for embedded usage of PartiQL. - * - * Example usage: - * ``` - * val pipeline = PartiQLCompilerPipeline.standard() - * val session = // session bindings - * val statement = pipeline.compile("-- some PartiQL query!") - * val result = statement.eval(session) - * when (result) { - * is PartiQLResult.Value -> handle(result) // Query Result - * is PartiQLResult.Insert -> handle(result) // DML `Insert` - * is PartiQLResult.Delete -> handle(result) // DML `Delete` - * ... - * } - * ``` - */ -@ExperimentalPartiQLCompilerPipeline -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerPipelineAsync")) -class PartiQLCompilerPipeline( - private val parser: Parser, - private val planner: PartiQLPlanner, - private val compiler: PartiQLCompiler -) { - - companion object { - - /** - * Returns a [PartiQLCompilerPipeline] with default parser, planner, and compiler configurations. - */ - @JvmStatic - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerPipelineAsync.standard")) - fun standard() = PartiQLCompilerPipeline( - parser = PartiQLParserBuilder.standard().build(), - planner = PartiQLPlannerBuilder.standard().build(), - compiler = PartiQLCompilerBuilder.standard().build() - ) - - /** - * Builder utility for pipeline creation. - * - * Example usage: - * ``` - * val pipeline = PartiQLCompilerPipeline.build { - * planner.options(plannerOptions) - * .globalVariableResolver(globalVariableResolver) - * compiler.ionSystem(ION) - * .options(evaluatorOptions) - * .customTypes(myCustomTypes) - * .customFunctions(myCustomFunctions) - * } - * ``` - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerPipelineAsync.build")) - fun build(block: Builder.() -> Unit): PartiQLCompilerPipeline { - val builder = Builder() - block.invoke(builder) - return PartiQLCompilerPipeline( - parser = builder.parser.build(), - planner = builder.planner.build(), - compiler = builder.compiler.build(), - ) - } - } - - /** - * Compiles a PartiQL query into an executable [PartiQLStatement]. - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerPipelineAsync.compile")) - fun compile(statement: String): PartiQLStatement { - val ast = parser.parseAstStatement(statement) - return compile(ast) - } - - /** - * Compiles a [PartiqlAst.Statement] representation of a query into an executable [PartiQLStatement]. - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerPipelineAsync.compile")) - fun compile(statement: PartiqlAst.Statement): PartiQLStatement { - val result = planner.plan(statement) - if (result is PartiQLPlanner.Result.Error) { - throw PartiQLException(result.problems.toString()) - } - val plan = (result as PartiQLPlanner.Result.Success).plan - return compile(plan, result.details) - } - - /** - * Compiles a [PartiqlPhysical.Plan] representation of a query into an executable [PartiQLStatement]. - */ - @JvmOverloads - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerPipelineAsync.compile")) - fun compile(statement: PartiqlPhysical.Plan, details: PartiQLPlanner.PlanningDetails = PartiQLPlanner.PlanningDetails()): PartiQLStatement { - return compiler.compile(statement, details) - } - - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLCompilerPipelineAsync.Builder")) - class Builder internal constructor() { - var parser = PartiQLParserBuilder.standard() - var planner = PartiQLPlannerBuilder.standard() - var compiler = PartiQLCompilerBuilder.standard() - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerPipelineAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerPipelineAsync.kt deleted file mode 100644 index 18e11870fd..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerPipelineAsync.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.compiler - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.errors.PartiQLException -import org.partiql.lang.eval.PartiQLStatementAsync -import org.partiql.lang.planner.PartiQLPlanner -import org.partiql.lang.planner.PartiQLPlannerBuilder -import org.partiql.lang.syntax.Parser -import org.partiql.lang.syntax.PartiQLParserBuilder - -/** - * [PartiQLCompilerPipelineAsync] is the top-level class for embedded usage of PartiQL. - * - * Example usage: - * ``` - * // Within a coroutine scope or `suspend fun` - * val pipeline = PartiQLCompilerPipelineAsync.standard() - * val session = // session bindings - * val statement = pipeline.compile("-- some PartiQL query!") - * val result = statement.eval(session) - * when (result) { - * is PartiQLResult.Value -> handle(result) // Query Result - * is PartiQLResult.Insert -> handle(result) // DML `Insert` - * is PartiQLResult.Delete -> handle(result) // DML `Delete` - * ... - * } - * ``` - */ -@ExperimentalPartiQLCompilerPipeline -class PartiQLCompilerPipelineAsync( - private val parser: Parser, - private val planner: PartiQLPlanner, - private val compiler: PartiQLCompilerAsync -) { - - companion object { - - /** - * Returns a [PartiQLCompilerPipelineAsync] with default parser, planner, and compiler configurations. - */ - @JvmStatic - fun standard() = PartiQLCompilerPipelineAsync( - parser = PartiQLParserBuilder.standard().build(), - planner = PartiQLPlannerBuilder.standard().build(), - compiler = PartiQLCompilerAsyncBuilder.standard().build() - ) - - /** - * Builder utility for pipeline creation. - * - * Example usage: - * ``` - * val pipeline = PartiQLCompilerPipelineAsync.build { - * planner.options(plannerOptions) - * .globalVariableResolver(globalVariableResolver) - * compiler.ionSystem(ION) - * .options(evaluatorOptions) - * .customTypes(myCustomTypes) - * .customFunctions(myCustomFunctions) - * } - * ``` - */ - fun build(block: Builder.() -> Unit): PartiQLCompilerPipelineAsync { - val builder = Builder() - block.invoke(builder) - return PartiQLCompilerPipelineAsync( - parser = builder.parser.build(), - planner = builder.planner.build(), - compiler = builder.compiler.build(), - ) - } - } - - /** - * Compiles a PartiQL query into an executable [PartiQLStatementAsync]. - */ - suspend fun compile(statement: String): PartiQLStatementAsync { - val ast = parser.parseAstStatement(statement) - return compile(ast) - } - - /** - * Compiles a [PartiqlAst.Statement] representation of a query into an executable [PartiQLStatementAsync]. - */ - suspend fun compile(statement: PartiqlAst.Statement): PartiQLStatementAsync { - val result = planner.plan(statement) - if (result is PartiQLPlanner.Result.Error) { - throw PartiQLException(result.problems.toString()) - } - val plan = (result as PartiQLPlanner.Result.Success).plan - return compile(plan, result.details) - } - - /** - * Compiles a [PartiqlPhysical.Plan] representation of a query into an executable [PartiQLStatementAsync]. - */ - @JvmOverloads - suspend fun compile(statement: PartiqlPhysical.Plan, details: PartiQLPlanner.PlanningDetails = PartiQLPlanner.PlanningDetails()): PartiQLStatementAsync { - return compiler.compile(statement, details) - } - - class Builder internal constructor() { - var parser = PartiQLParserBuilder.standard() - var planner = PartiQLPlannerBuilder.standard() - var compiler = PartiQLCompilerAsyncBuilder.standard() - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/domains/util.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/domains/util.kt deleted file mode 100644 index fe63f15ff2..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/domains/util.kt +++ /dev/null @@ -1,84 +0,0 @@ -package org.partiql.lang.domains - -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.emptyMetaContainer -import com.amazon.ionelement.api.metaContainerOf -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.Meta -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.ast.StaticTypeMeta -import org.partiql.lang.eval.BindingCase - -// TODO: once https://github.com/partiql/partiql-ir-generator/issues/6 has been completed, we can delete this. -fun PartiqlAst.Builder.id(name: String) = - id(name, caseInsensitive(), unqualified()) - -// TODO: once https://github.com/partiql/partiql-ir-generator/issues/6 has been completed, we can delete this. -fun PartiqlLogical.Builder.id(name: String) = - id(name, caseInsensitive(), unqualified()) - -// TODO: once https://github.com/partiql/partiql-ir-generator/issues/6 has been completed, we can delete this. -fun PartiqlLogical.Builder.pathExpr(exp: PartiqlLogical.Expr) = - pathExpr(exp, caseInsensitive()) - -val MetaContainer.staticType: StaticTypeMeta? get() = this[StaticTypeMeta.TAG] as StaticTypeMeta? - -/** Constructs a container with the specified metas. */ -fun metaContainerOf(vararg metas: Meta): MetaContainer = - metaContainerOf(metas.map { Pair(it.tag, it) }) - -/** - * Returns a [MetaContainer] with *only* the source location of the receiver [MetaContainer], if present. - * - * Avoids creating a new [MetaContainer] if its not needed. - */ -fun PartiqlAst.PartiqlAstNode.extractSourceLocation(): MetaContainer { - return when (this.metas.size) { - 0 -> emptyMetaContainer() - 1 -> when { - this.metas.containsKey(SourceLocationMeta.TAG) -> this.metas - else -> emptyMetaContainer() - } - else -> { - this.metas[SourceLocationMeta.TAG]?.let { metaContainerOf(SourceLocationMeta.TAG to it) } - ?: emptyMetaContainer() - } - } -} - -/** - * Adds [Property.LINE_NUMBER] and [Property.COLUMN_NUMBER] to the [PropertyValueMap] if the [SourceLocationMeta.TAG] - * is present in the passed [metas]. Otherwise, returns the unchanged [PropertyValueMap]. - */ -fun PropertyValueMap.addSourceLocation(metas: MetaContainer): PropertyValueMap { - (metas[SourceLocationMeta.TAG] as? SourceLocationMeta)?.let { - this[Property.LINE_NUMBER] = it.lineNum - this[Property.COLUMN_NUMBER] = it.charOffset - } - return this -} - -/** - * Converts a [PartiqlAst.CaseSensitivity] to a [BindingCase]. - */ -fun PartiqlAst.CaseSensitivity.toBindingCase(): BindingCase = when (this) { - is PartiqlAst.CaseSensitivity.CaseInsensitive -> BindingCase.INSENSITIVE - is PartiqlAst.CaseSensitivity.CaseSensitive -> BindingCase.SENSITIVE -} - -/** - * Converts a [PartiqlLogical.CaseSensitivity] to a [BindingCase]. - */ -fun PartiqlLogical.CaseSensitivity.toBindingCase(): BindingCase = when (this) { - is PartiqlLogical.CaseSensitivity.CaseInsensitive -> BindingCase.INSENSITIVE - is PartiqlLogical.CaseSensitivity.CaseSensitive -> BindingCase.SENSITIVE -} - -/** - * Converts a [PartiqlLogical.CaseSensitivity] to a [BindingCase]. - */ -fun PartiqlPhysical.CaseSensitivity.toBindingCase(): BindingCase = when (this) { - is PartiqlPhysical.CaseSensitivity.CaseInsensitive -> BindingCase.INSENSITIVE - is PartiqlPhysical.CaseSensitivity.CaseSensitive -> BindingCase.SENSITIVE -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/errors/PartiQLException.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/errors/PartiQLException.kt deleted file mode 100644 index 02f0182351..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/errors/PartiQLException.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.errors - -/** - * Base class for PartiQL Exceptions - */ -class PartiQLException(override val message: String) : RuntimeException() diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/errors/ProblemHandlers.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/errors/ProblemHandlers.kt deleted file mode 100644 index 4438dcb45b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/errors/ProblemHandlers.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.partiql.lang.errors - -import org.partiql.errors.Problem -import org.partiql.errors.ProblemHandler -import org.partiql.errors.ProblemSeverity -import org.partiql.lang.ast.passes.SemanticException - -/** - * A [ProblemHandler] that collects all of the encountered [Problem]s without throwing. - * - * This is intended to be used when wanting to collect multiple problems that may be encountered (e.g. a static type - * inference pass that can result in multiple errors and/or warnings). This handler does not collect other exceptions - * that may be thrown. - */ -internal class ProblemCollector : ProblemHandler { - private val problemList = mutableListOf() - - val problems: List - get() = problemList - - val hasErrors: Boolean - get() = problemList.any { it.details.severity == ProblemSeverity.ERROR } - - val hasWarnings: Boolean - get() = problemList.any { it.details.severity == ProblemSeverity.WARNING } - - override fun handleProblem(problem: Problem) { - problemList.add(problem) - } -} - -/** - * A [ProblemHandler] that throws the first [Problem] that has a [ProblemSeverity] of [ProblemSeverity.ERROR] as a - * [SemanticException]. - * - * This is intended to support existing internal code (e.g. CompilerPipeline, StaticTypeInferenceVisitorTransform) - * behavior that expects the first encountered problem to be thrown. Once multiple problem handling is supported - * in that code, this class can be removed. - * - * @throws SemanticException on the first [Problem] logged with severity of [ProblemSeverity.ERROR] - */ -internal class ProblemThrower : ProblemHandler { - override fun handleProblem(problem: Problem) { - if (problem.details.severity == ProblemSeverity.ERROR) { - throw SemanticException(problem) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Addressed.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Addressed.kt deleted file mode 100644 index fd391b2784..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Addressed.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * Facet for a value to indicate that it has an application-defined "address" for this value. - * - * The "address" should be a value which is unique such as a UUID. - * - * An application should not provide this facet if it does not provide a value which uniquely identifies the - * value in the underlying data source. - */ -interface Addressed { - /** - * The address of this value. - */ - val address: ExprValue -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/AnyOfCastTable.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/AnyOfCastTable.kt deleted file mode 100644 index 020323b10f..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/AnyOfCastTable.kt +++ /dev/null @@ -1,269 +0,0 @@ -package org.partiql.lang.eval - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.sourceLocation -import org.partiql.lang.types.StaticTypeUtils.getRuntimeType -import org.partiql.types.AnyOfType -import org.partiql.types.AnyType -import org.partiql.types.CollectionType -import org.partiql.types.SingleType -import org.partiql.types.StaticType -import org.partiql.types.StructType - -/** - * The template table that encodes the type conversion precedence for a source type to target type. - * This is defined in terms of the [ExprValueType] type instead of the [StaticType] because is - * an enumeration (versus algebraic data type) and is trivially mapped to the [StaticType] instances we care - * to associate with. - */ -private val CAST_ANY_OF_PRECEDENCE_TABLE = mapOf( - ExprValueType.BOOL to listOf( - ExprValueType.BOOL, - ExprValueType.INT, - ExprValueType.DECIMAL, - ExprValueType.FLOAT, - ExprValueType.STRING, - ExprValueType.SYMBOL - ), - ExprValueType.INT to listOf( - ExprValueType.INT, - ExprValueType.DECIMAL, - ExprValueType.FLOAT, - ExprValueType.STRING, - ExprValueType.SYMBOL, - ExprValueType.BOOL - ), - ExprValueType.FLOAT to listOf( - ExprValueType.FLOAT, - ExprValueType.DECIMAL, - ExprValueType.INT, - ExprValueType.STRING, - ExprValueType.SYMBOL, - ExprValueType.BOOL - ), - ExprValueType.DECIMAL to listOf( - ExprValueType.DECIMAL, - ExprValueType.FLOAT, - ExprValueType.INT, - ExprValueType.STRING, - ExprValueType.SYMBOL, - ExprValueType.BOOL - ), - ExprValueType.TIMESTAMP to listOf( - ExprValueType.STRING, - ExprValueType.SYMBOL - // TODO define for DATE/TIME - ), - ExprValueType.SYMBOL to listOf( - ExprValueType.SYMBOL, - ExprValueType.STRING, - ExprValueType.DECIMAL, - ExprValueType.INT, - ExprValueType.FLOAT, - ExprValueType.BOOL, - ExprValueType.TIMESTAMP - // TODO define for DATE/TIME/INTERVAL - ), - ExprValueType.STRING to listOf( - ExprValueType.STRING, - ExprValueType.SYMBOL, - ExprValueType.DECIMAL, - ExprValueType.INT, - ExprValueType.FLOAT, - ExprValueType.BOOL, - ExprValueType.TIMESTAMP - // TODO define for DATE/TIME/INTERVAL - ), - ExprValueType.CLOB to listOf( - ExprValueType.CLOB, - ExprValueType.BLOB - ), - ExprValueType.BLOB to listOf( - ExprValueType.BLOB, - ExprValueType.CLOB - ), - ExprValueType.LIST to listOf( - ExprValueType.LIST, - ExprValueType.SEXP, - ExprValueType.BAG - ), - ExprValueType.SEXP to listOf( - ExprValueType.SEXP, - ExprValueType.LIST, - ExprValueType.BAG - ), - ExprValueType.BAG to listOf( - ExprValueType.BAG, - ExprValueType.LIST, - ExprValueType.SEXP - ), - ExprValueType.STRUCT to listOf( - ExprValueType.STRUCT - ) -) - -/** A partial compilation of the cast operation, allowing the source operand to be passed in. */ -internal typealias CastFunc = (source: ExprValue) -> ExprValue - -/** Represents a casted value, a failure to cast, or no possible cast target. */ -private sealed class CastResult { - /** Returns the underlying [ExprValue] or throws if in the [CastError] or [CastNil] state. */ - abstract fun unwrap(): ExprValue -} - -private data class CastError(val error: EvaluationException) : CastResult() { - override fun unwrap() = throw error -} - -private data class CastValue(val value: ExprValue) : CastResult() { - override fun unwrap() = value -} - -/** Sentinel case to deal with empty target table--no compatible cast available for the source. */ -private data class CastNil(val sourceType: ExprValueType, val metas: MetaContainer) : CastResult() { - override fun unwrap(): Nothing { - val errorContext = PropertyValueMap().also { - it[Property.CAST_FROM] = sourceType.toString() - // TODO put the right type name here - it[Property.CAST_TO] = "" - } - metas.sourceLocation?.let { fillErrorContext(errorContext, it) } - err( - "No compatible types in union to cast from $sourceType", - ErrorCode.EVALUATOR_CAST_FAILED, - errorContext, - internal = false - ) - } -} - -/** - * Represents the candidate conversion table for compiling the casting to an [AnyOfType]. - * - * Note that currently, we cannot define recursive [StaticType] for container element types, so the - * [CollectionType.elementType] must be `null`, but is implied to be the type given to this table. - * - * A further restriction is that [StructType.fields] must be empty and [StructType.contentClosed] must be - * `false`. It is implied similarly that the value of any `struct` fields are of the given [AnyOfType]. - * - * @param anyOfType The union type to determine the precedence for. - * @param metas The metadata of the compilation context. - * @param singleTypeCast The function to delegate the implementation of a cast to a single type. - */ -internal class AnyOfCastTable( - private val anyOfType: AnyOfType, - private val metas: MetaContainer, - singleTypeCast: (SingleType) -> CastFunc -) { - val castFuncTable: Map> - val castTypeTable: Map> - - init { - val typeMap = mutableMapOf() - - // validate the union type here - anyOfType.types.forEach { - when (it) { - is AnyType -> typeErr("Union type cannot have ANY in it") - is AnyOfType -> typeErr("Union type cannot have a Union type in it") - is SingleType -> { - val runtimeType = getRuntimeType(it) - if (typeMap.contains(runtimeType)) { - typeErr("Duplicate core type in union type not supported ($runtimeType)") - } - typeMap.put(runtimeType, it) - when (it) { - is CollectionType -> when { - it.elementType !is AnyType -> typeErr( - "Union type must have unconstrained container type (${it.elementType})" - ) - } - is StructType -> when { - it.fields.isNotEmpty() -> typeErr( - "Union type must have no field constraints for struct (${it.fields}" - ) - it.contentClosed -> typeErr("Union type must not be closed") - } - else -> {} - } - } - } - } - - // generate the precedence table of cast target types and functions - castTypeTable = CAST_ANY_OF_PRECEDENCE_TABLE.map { (srcType, destTypes) -> - srcType to destTypes.filter { t -> typeMap.containsKey(t) } - }.toMap() - castFuncTable = castTypeTable.map { (srcType, destTypes) -> - srcType to destTypes.mapNotNull { t -> typeMap[t] }.map(singleTypeCast) - }.toMap() - } - - private fun getCasts(sourceType: ExprValueType): List = castFuncTable[sourceType] - ?: throw IllegalStateException("Missing type in union cast function table: $sourceType") - - private fun firstCompatible(sourceType: ExprValueType): ExprValueType { - val types = castTypeTable[sourceType] - ?: throw IllegalStateException("Missing type in union cast type table: $sourceType") - return types.firstOrNull() ?: CastNil(sourceType, metas).unwrap() - } - - /** Evaluates the `CAST` operation over the table. */ - fun cast(source: ExprValue): ExprValue = when { - source.isUnknown() -> source - else -> when { - source.type.isSequence || source.type == ExprValueType.STRUCT -> { - // TODO honor any constraints on the container/struct type (statically) - // sequences are a special case, we recursively cast the children into a new container - val targetType = firstCompatible(source.type) - val children = source.asSequence().map { cast(it) } - - when (targetType) { - ExprValueType.LIST -> ExprValue.newList(children) - ExprValueType.SEXP -> ExprValue.newSexp(children) - ExprValueType.BAG -> ExprValue.newBag(children) - ExprValueType.STRUCT -> { - if (source.type != ExprValueType.STRUCT) { - // Should not be possible - throw IllegalStateException("Cannot cast from non-struct to struct") - } - ExprValue.newStruct( - children.zip(source.asSequence()).map { (child, original) -> - child.namedValue(original.name!!) - }, - StructOrdering.UNORDERED - ) - } - else -> throw IllegalStateException("Invalid collection target type: $targetType") - } - } else -> { - // for the scalar case, we apply the available cast functions in order - // and either we succeed with a converted value, or we get an error and keep trying - var result: CastResult = CastNil(source.type, metas) - loop@ for (castFunc in getCasts(source.type)) { - when (result) { - is CastNil, is CastError -> { - try { - result = CastValue(castFunc(source)) - } catch (e: EvaluationException) { - result = CastError(e) - } - } - is CastValue -> break@loop - } - } - result.unwrap() - } - } - } - - private fun typeErr(message: String): Nothing = err( - message, - ErrorCode.SEMANTIC_UNION_TYPE_INVALID, - errorContextFrom(metas), - internal = true - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/BagOp.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/BagOp.kt deleted file mode 100644 index 728219d9de..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/BagOp.kt +++ /dev/null @@ -1,117 +0,0 @@ -package org.partiql.lang.eval - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.pig.runtime.DomainNode - -/** - * Evaluable representation of PartiQL bag operators. - * - * ``` - * - [OUTER] UNION [ALL|DISTINCT] - * - [OUTER] INTERSECT [ALL|DISTINCT] - * - [OUTER] EXCEPT [ALL|DISTINCT] - * ``` - * - * @see [RFC-0007](https://github.com/partiql/partiql-docs/blob/main/RFCs/0007-rfc-bag-operators.md). - */ -fun interface ExprValueBagOp { - fun eval(lhs: ExprValue, rhs: ExprValue): Sequence { - val l = lhs.coerceToBag() - val r = rhs.coerceToBag() - return eval(l, r) - } - - fun eval(lhs: Sequence, rhs: Sequence): Sequence - - companion object { - - fun create(node: DomainNode, metas: MetaContainer): ExprValueBagOp = when (node) { - is PartiqlAst.BagOpType.Union, - is PartiqlPhysical.BagOpType.Union, - is PartiqlAst.BagOpType.Intersect, - is PartiqlPhysical.BagOpType.Intersect, - is PartiqlAst.BagOpType.Except, - is PartiqlPhysical.BagOpType.Except -> { - throw EvaluationException( - message = "${node.javaClass.simpleName} operator is not support yet", - errorCode = ErrorCode.EVALUATOR_FEATURE_NOT_SUPPORTED_YET, - errorContextFrom(metas), - internal = false - ) - } - is PartiqlAst.BagOpType.OuterUnion, - is PartiqlPhysical.BagOpType.OuterUnion -> outerUnion - is PartiqlAst.BagOpType.OuterIntersect, - is PartiqlPhysical.BagOpType.OuterIntersect -> outerIntersect - is PartiqlAst.BagOpType.OuterExcept, - is PartiqlPhysical.BagOpType.OuterExcept -> outerExcept - else -> { - throw EvaluationException( - message = "Invalid bag operator ${node.javaClass.simpleName}", - errorCode = ErrorCode.INTERNAL_ERROR, - errorContextFrom(metas), - internal = false - ) - } - } - } -} - -/** - * Coercion function F for bag operators described in RFC-0007 - * - F(absent_value) -> << >> - * - F(scalar_value) -> << scalar_value >> # singleton bag - * - F(tuple_value) -> << tuple_value >> # singleton bag, see future extensions - * - F(array_value) -> bag_value # discard ordering - * - F(bag_value) -> bag_value # identity - */ -internal fun ExprValue.coerceToBag(): Sequence = when { - isUnknown() -> emptySequence() - type === ExprValueType.STRUCT || type.isScalar -> sequenceOf(this) - else -> this.asSequence() -} - -private val outerUnion = ExprValueBagOp { lhs, rhs -> - sequence { - val multiplicities = lhs.multiplicities() - yieldAll(lhs) - rhs.forEach { - val m = multiplicities.getOrDefault(it, 0) - if (m > 0) { - multiplicities[it] = m - 1 - } else { - yield(it) - } - } - } -} - -private val outerIntersect = ExprValueBagOp { lhs, rhs -> - sequence { - val multiplicities = lhs.multiplicities() - rhs.forEach { - val m = multiplicities.getOrDefault(it, 0) - if (m > 0) { - yield(it) - multiplicities[it] = m - 1 - } - } - } -} - -private val outerExcept = ExprValueBagOp { lhs, rhs -> - sequence { - val multiplicities = rhs.multiplicities() - lhs.forEach { - val m = multiplicities.getOrDefault(it, 0) - if (m > 0) { - multiplicities[it] = m - 1 - } else { - yield(it) - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/BaseExprValue.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/BaseExprValue.kt deleted file mode 100644 index 8b7ac3cb97..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/BaseExprValue.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import org.partiql.lang.graph.Graph -import org.partiql.lang.graph.SimpleGraph -import org.partiql.lang.util.downcast - -/** - * Base implementation of [ExprValue] that provides a bare minimum implementation of - * a value. - */ -abstract class BaseExprValue : ExprValue { - - override val scalar: Scalar - get() = Scalar.EMPTY - override val bindings: Bindings - get() = Bindings.empty() - override val ordinalBindings: OrdinalBindings - get() = OrdinalBindings.EMPTY - - override fun iterator(): Iterator = emptyList().iterator() - - override val graphValue: Graph - get() = SimpleGraph.empty - - final override fun asFacet(type: Class?): T? = - downcast(type) ?: provideFacet(type) - - /** - * Provides a fall-back for providing facets if a sub-class doesn't inherit the facet interface - * or class. - * - * This implementation returns `null`. - */ - open fun provideFacet(type: Class?): T? = null - - override fun toString(): String = stringify() -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Bindings.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Bindings.kt deleted file mode 100644 index 00980e438d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Bindings.kt +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval -import com.amazon.ion.IonStruct -import com.amazon.ion.IonSystem -import com.amazon.ion.IonValue -import org.partiql.errors.ErrorCode -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.util.errAmbiguousBinding -import org.partiql.lang.util.isBindingNameEquivalent -import org.partiql.lang.util.stringValue - -/** Indicates if the lookup of a particular binding should be case-sensitive or not. */ -enum class BindingCase { - SENSITIVE, INSENSITIVE; - - companion object { - fun fromIonValue(sym: IonValue): BindingCase = - when (sym.stringValue()) { - "case_sensitive" -> SENSITIVE - "case_insensitive" -> INSENSITIVE - else -> errNoContext( - "Unable to convert ion value '${sym.stringValue()}' to a BindingCase instance", - errorCode = ErrorCode.EVALUATOR_INVALID_CONVERSION, - internal = true - ) - } - } - - fun toSymbol(ions: IonSystem) = - ions.newSymbol( - when (this) { - SENSITIVE -> "case_sensitive" - INSENSITIVE -> "case_insensitive" - } - ) -} - -/** - * Converts a [CaseSensitivity] to a [BindingCase]. - */ -fun PartiqlAst.CaseSensitivity.toBindingCase(): BindingCase = when (this) { - is PartiqlAst.CaseSensitivity.CaseInsensitive -> BindingCase.INSENSITIVE - is PartiqlAst.CaseSensitivity.CaseSensitive -> BindingCase.SENSITIVE -} - -/** - * Encapsulates the data necessary to perform a binding lookup. - */ -data class BindingName(val name: String, val bindingCase: BindingCase) { - val loweredName: String by lazy(LazyThreadSafetyMode.PUBLICATION) { name.lowercase() } - /** - * Compares [name] to [otherName] using the rules specified by [bindingCase]. - */ - fun isEquivalentTo(otherName: String?) = otherName != null && name.isBindingNameEquivalent(otherName, bindingCase) -} - -/** - * A mapping of name to [ExprValue]. - * - * Due to the need to throw a consistent [EvaluationException] in the event of an ambiguous - * case-insensitive binding lookup, customers should avoid implementing this interface directly - * and should instead use one of the static factory methods or the lazy bindings builder to obtain - * an instance of [Bindings] that correctly complies with the exception contract. See - * [org.partiql.lang.examples.Evaluation] for examples. - */ -interface Bindings { - - /** - * Looks up a name within the environment. - * - * Implementations should respect the value of [BindingName.bindingCase]. - * - * @param bindingName The binding to look up. - * - * @return The value mapped to the binding, or `null` if no such binding exists. - * @throws [EvaluationException] If multiple bindings matching the specified [BindingName] are found. - * Clients should use [BindingHelper.throwAmbiguousBindingEvaluationException] to throw this exception. - */ - @Throws(EvaluationException::class) - operator fun get(bindingName: BindingName): T? - - companion object { - private val EMPTY = over { _ -> null } - - @Suppress("UNCHECKED_CAST") - fun empty(): Bindings = EMPTY as Bindings - - /** - * A SAM conversion for [Bindings] from a function object. - * - * This is necessary as Kotlin currently doesn't support SAM conversions to - * Kotlin defined interfaces (only Java defined interfaces). - */ - fun over(func: (BindingName) -> T?): Bindings = object : Bindings { - override fun get(bindingName: BindingName): T? = func(bindingName) - } - - /** - * Returns an instance of [LazyBindingsBuilder]. If calling from Kotlin, prefer to use [buildLazyBindings] - * instead. - */ - @JvmStatic - fun lazyBindingsBuilder(): LazyBindingBuilder = LazyBindingBuilder() - - /** - * Invokes [block], passing an instance of [LazyBindingBuilder]. If calling from Java, prefer to use - * [lazyBindingsBuilder] instead. - */ - fun buildLazyBindings(block: LazyBindingBuilder.() -> Unit): Bindings = LazyBindingBuilder().apply(block).build() - - // TODO This API needs to be fleshed out with respect to mutable maps (the Map interface doesn't guaranteed immutability) - /** - * Returns an instance of [Bindings] that is backed by a `Map`. - * - * A current limitation of this factory method is that [backingMap] must not change. In other words, - * any [Map] that is passed that can be mutated, is not guaranteed to work correctly with this API. - */ - @JvmStatic - fun ofMap(backingMap: Map): Bindings = MapBindings(backingMap) - - /** - * Returns an instance of [Bindings] that is backed by an [IonStruct]. - */ - @JvmStatic - fun ofIonStruct(struct: IonStruct): Bindings = IonStructBindings(struct) - } - - /** An implementation of the builder pattern for instances of [Bindings]. */ - class LazyBindingBuilder { - private val bindings = HashMap> () - - fun addBinding(name: String, getter: () -> T): LazyBindingBuilder = - this.apply { bindings[name] = lazy(getter) } - - fun build(): Bindings = - LazyBindings(bindings) - } -} - -/** - * A [Bindings] implementation that is backed by a [Map]. - * - * [originalCaseMap] is the backing [Map]. Important note: - * *this must be immutable! Changes to this map will not be reflected in [loweredCaseMap] - * after it has been instantiated. - * - * [loweredCaseMap] is based on [originalCaseMap] and is lazily calculated the first time - * a case-insensitive lookup is performed. - * - * If an ambiguous binding match is found during a case-insensitive lookup, [errAmbiguousBinding] - * is invoked to report the error to the customer. - */ -class MapBindings(val originalCaseMap: Map) : Bindings { - private val loweredCaseMap: Map>> by lazy { - originalCaseMap.entries.groupBy { it.key.lowercase() } - } - - override fun get(bindingName: BindingName): T? = - when (bindingName.bindingCase) { - BindingCase.SENSITIVE -> originalCaseMap[bindingName.name] - BindingCase.INSENSITIVE -> { - val foundBindings = loweredCaseMap[bindingName.loweredName] - when { - foundBindings == null -> null - foundBindings.size == 1 -> foundBindings.first().value - else -> - errAmbiguousBinding(bindingName.name, foundBindings.map { it.key }) - } - } - } -} - -/** A [Bindings] implementation that lazily materializes the values of the bindings contained within. */ -private class LazyBindings(originalCaseMap: Map>) : Bindings { - private val delegate: Bindings> = MapBindings(originalCaseMap) - - override fun get(bindingName: BindingName): T? = delegate[bindingName]?.value -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/BindingsExtensions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/BindingsExtensions.kt deleted file mode 100644 index 3a0a6f7d6f..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/BindingsExtensions.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * Wraps these [Bindings] to delegate lookup to another instance when lookup on this - * one fails. - * - * Note that this doesn't modify an existing [Bindings] but creates a new instance that - * does delegation. - * - * @param fallback The bindings to delegate to when lookup fails to find a name. - */ -fun Bindings.delegate(fallback: Bindings): Bindings = - object : Bindings { - override fun get(bindingName: BindingName): T? { - val binding = this@delegate[bindingName] - return binding ?: fallback[bindingName] - } - } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/CompileOptions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/CompileOptions.kt deleted file mode 100644 index 7083c9dc12..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/CompileOptions.kt +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.eval.ProjectionIterationBehavior.FILTER_MISSING -import org.partiql.lang.eval.ProjectionIterationBehavior.UNFILTERED -import org.partiql.lang.eval.ThunkReturnTypeAssertions.ENABLED -import org.partiql.lang.eval.VisitorTransformMode.DEFAULT -import org.partiql.lang.eval.VisitorTransformMode.NONE -import org.partiql.lang.eval.visitors.IDENTITY_VISITOR_TRANSFORM -import org.partiql.lang.eval.visitors.basicVisitorTransforms -import java.time.ZoneOffset - -/** - * Defines the behavior when a non-existent variable is referenced. - * - * When `ERROR` any reference to a non-existent variable results in an [EvaluationException]. - * - * When 'MISSING`, any reference to a non-existent variable results in an Ion `MISSING` value.] - */ -enum class UndefinedVariableBehavior { - ERROR, MISSING -} - -/** - * Controls the behavior of [ExprValue.iterator] in the projection result. - * For the query `Select a,b,c From <<{a:null, c:3}>>`; - * * [FILTER_MISSING] will iterate over `[null,3]` - * * [UNFILTERED] will iterate over `[null, missing, 3]` - */ -enum class ProjectionIterationBehavior { - FILTER_MISSING, UNFILTERED -} - -/** - * Indicates how the evaluator is to handle type checking errors and how `MISSING` values are propagated - * when encountered while evaluating binary operators and function calls. - */ -enum class TypingMode { - /** - * Affects the following evaluation-time behavior: - * - * - Most evaluation-time errors due to type mismatches are surfaced in the form of an [EvaluationException] - * immediately. - * - `IN` operator returns `FALSE` when the right-hand side is not a `BAG`, `LIST` or `SEXP`. - * - For binary operators, if any operand is `MISSING` the result is `NULL`. - * - For functions other than `COALESCE`, if any argument is `MISSING`, the result is `NULL`. - */ - LEGACY, - - /** - * Affects the following evaluation-time behavior: - * - * - Most exceptions that would stop query execution under [LEGACY] mode result in a `MISSING` value - * instead. Mostly, this relates to data type mismatch errors. - * - `IN` operator returns `MISSING` when the right-hand side is not a `BAG`, `LIST` or `SEXP`. - * - For binary operators, if any operand is `MISSING` the result is `MISSING`. - * - For functions other than `COALESCE`, if any argument is `MISSING`, the result is `MISSING`. - */ - PERMISSIVE - - // TODO: STRICT -} - -/** - * Indicates how CAST should behave. - */ -enum class TypedOpBehavior { - /** - * CAST and IS operators respect type parameters. - * - * The following behavior is added to `CAST`: - * - * - When casting a `DECIMAL(precision, scale)` with a greater scale to a `DECIMAL(precision, scale)` type with a - * lower scale, rounds [half to even](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even) as needed. - * - When casting to `CHAR(n)` and `VARCHAR(n)`, if after conversion to unicode string, the value has more unicode - * codepoints than `n`, truncation is performed. Trailing spaces (`U+0020`) are fully preserved when casting to - * `VARCHAR(n)`, but trimmed when casting to `CHAR(n). - * - * The following behavior is added to `IS`: - * - * - For string type `VARCHAR(n)`, the left-hand side of `IS` must evaluate be a string (not a symbol) - * where the number of unicode code points is less than or equal `n`. - * - When casting a `DECIMAL(precision, scale)` with a greater scale to a `DECIMAL(precision, scale)` type with a - * lower scale, rounds [half to even](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even) as needed. - * - When casting to `CHAR(n)` and `VARCHAR(n)`, if after conversion to unicode string, the value has more unicode - * codepoints than `n`, truncation is performed. Trailing spaces (`U+0020`) are fully preserved when casting to - * `VARCHAR(n)`, but trimmed when casting to `CHAR(n). - **/ - HONOR_PARAMETERS -} - -/** - * Controls the behavior of intrinsic AST visitor transforms with [EvaluatingCompiler.compile]. - * - * Most users will want [DEFAULT], which does the built-in visitor transforms for them, while - * users wanting full control of the visitor transform process should use [NONE]. - */ -enum class VisitorTransformMode { - DEFAULT { - override fun createVisitorTransform() = basicVisitorTransforms() - }, - NONE { - override fun createVisitorTransform() = IDENTITY_VISITOR_TRANSFORM - }; - - internal abstract fun createVisitorTransform(): PartiqlAst.VisitorTransform -} - -/** - * When [ENABLED], the compiler adds additional evaluation-time checks to every thunk that verify that the - * [ExprValue] instance returned conforms to the expected [org.partiql.types.StaticType]. - * - * This is intended only for testing and diagnostic purposes as it likely comes with a significant performance penalty. - * Production use may not be desirable. - */ -enum class ThunkReturnTypeAssertions { - DISABLED, - ENABLED -} - -/** - * Specifies options that effect the behavior of the PartiQL compiler. - * - * @param defaultTimezoneOffset Default timezone offset to be used when TIME WITH TIME ZONE does not explicitly - * specify the time zone. Defaults to [ZoneOffset.UTC] - * @param interruptible specifies whether the compilation and execution of the compiled statement is interruptible. If - * set to true, the compilation and execution of statements will check [Thread.interrupted] frequently. If set to - * false, the compilation and execution of statements is not guaranteed to be interruptible. It *may* still be interrupted, - * however, it is not guaranteed. The default is false. - */ -@Suppress("DataClassPrivateConstructor") -data class CompileOptions private constructor ( - val undefinedVariable: UndefinedVariableBehavior, - val projectionIteration: ProjectionIterationBehavior = ProjectionIterationBehavior.FILTER_MISSING, - val visitorTransformMode: VisitorTransformMode = VisitorTransformMode.DEFAULT, - val thunkOptions: ThunkOptions = ThunkOptions.standard(), - val typingMode: TypingMode = TypingMode.LEGACY, - val typedOpBehavior: TypedOpBehavior = TypedOpBehavior.HONOR_PARAMETERS, - val defaultTimezoneOffset: ZoneOffset = ZoneOffset.UTC, - val interruptible: Boolean = false -) { - - companion object { - - /** - * Creates a java style builder that will choose the default values for any unspecified options. - */ - @JvmStatic - fun builder() = Builder() - - /** - * Creates a java style builder that will clone the [CompileOptions] passed to the constructor. - */ - @JvmStatic - fun builder(options: CompileOptions) = Builder(options) - - /** - * Kotlin style builder that will choose the default values for any unspecified options. - */ - fun build(block: Builder.() -> Unit) = Builder().apply(block).build() - - /** - * Kotlin style builder that will clone the [CompileOptions] passed to the constructor. - */ - fun build(options: CompileOptions, block: Builder.() -> Unit) = Builder(options).apply(block).build() - - /** - * Creates a [CompileOptions] instance with the standard values for use by the legacy AST compiler. - */ - @JvmStatic - fun standard() = Builder().build() - } - - /** - * Builds a [CompileOptions] instance. - */ - class Builder(private var options: CompileOptions = CompileOptions(UndefinedVariableBehavior.ERROR)) { - - fun undefinedVariable(value: UndefinedVariableBehavior) = set { copy(undefinedVariable = value) } - fun projectionIteration(value: ProjectionIterationBehavior) = set { copy(projectionIteration = value) } - fun visitorTransformMode(value: VisitorTransformMode) = set { copy(visitorTransformMode = value) } - fun typingMode(value: TypingMode) = set { copy(typingMode = value) } - fun typedOpBehavior(value: TypedOpBehavior) = set { copy(typedOpBehavior = value) } - fun thunkOptions(value: ThunkOptions) = set { copy(thunkOptions = value) } - fun thunkOptions(build: ThunkOptions.Builder.() -> Unit) = set { copy(thunkOptions = ThunkOptions.build(build)) } - fun defaultTimezoneOffset(value: ZoneOffset) = set { copy(defaultTimezoneOffset = value) } - fun isInterruptible(value: Boolean) = set { copy(interruptible = value) } - - private inline fun set(block: CompileOptions.() -> CompileOptions): Builder { - options = block(options) - return this - } - - fun build() = options - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/CoverageData.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/CoverageData.kt deleted file mode 100644 index 73214d4bc7..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/CoverageData.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * Represents the execution data of a PartiQL Statement as it relates to Code Coverage. This structure specifically - * represents the aggregation of data of a particular statement. For example, a PartiQL Statement containing a single - * boolean expression would be represented with a [CoverageStructure] containing two branches. However, upon execution - * of the [Expression], this structure ([CoverageData]) will be populated with information related to which branches - * were taken and their frequency. If the execution of a compiler query results in only a single branch being taken, this - * class shall reflect that. - * - * NOTE: It's important to note that many implementations of [ExprValue] are **lazy**. Therefore, in order to retrieve - * accurate [CoverageData], one must access each individual [ExprValue] of the [PartiQLResult] before accessing the - * [CoverageData]. - * - * @param branchCount represents the branch name (String) and the corresponding number of times it was executed. - * @param branchConditionCount represents the branch-condition name (String) and the corresponding number of times it was executed. - * - * @see CoverageStructure - * @see ExecutionCount - */ -public data class CoverageData( - val branchConditionCount: ExecutionCount = ExecutionCount(emptyMap()), - val branchCount: ExecutionCount = ExecutionCount(emptyMap()) -) { - /** - * Holds the number of times each branch or branch-condition of a [CoverageStructure] has been executed. - * @see CoverageData - * @see CoverageStructure - */ - public class ExecutionCount(private val _map: Map) : Map { - override val entries: Set> = this._map.entries - - override val keys: Set = this._map.keys - - override val size: Int = this._map.size - - override val values: Collection = this._map.values - - override fun containsKey(key: String): Boolean = this._map.containsKey(key) - - override fun containsValue(value: Long): Boolean = this._map.containsValue(value) - - override fun get(key: String): Long? = this._map[key] - - override fun isEmpty(): Boolean = this._map.isEmpty() - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/CoverageStructure.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/CoverageStructure.kt deleted file mode 100644 index a5a524efd6..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/CoverageStructure.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * Represents the static structure of a compiled [Expression]. This structure is distinct from the execution data found - * within [CoverageData]. - * - * @param branches represents the distinct outcome of an expression/clause that dictates control flow. For example, - * a WHERE clause has two potential branches/outcomes. - */ -public data class CoverageStructure( - val branches: Map = emptyMap(), - val branchConditions: Map = emptyMap() -) { - public data class Branch( - val id: String, - val type: Type, - val outcome: Outcome, - val line: Long - ) { - public enum class Type { - WHERE, - HAVING, - CASE_WHEN - } - - public enum class Outcome { - TRUE, - FALSE - } - } - - public data class BranchCondition( - val id: String, - val type: Type, - val outcome: Outcome, - val line: Long - ) { - public enum class Type { - GT, - GTE, - LT, - LTE, - EQ, - NEQ, - AND, - OR, - NOT, - BETWEEN, - LIKE, - IS, - IN, - } - public enum class Outcome { - TRUE, - FALSE, - NULL, - MISSING - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Environment.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Environment.kt deleted file mode 100644 index 5b9a6ea081..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Environment.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import java.util.TreeMap - -/** - * The environment for execution. - * - * @param locals The current local bindings. - * @param current The current bindings to use for evaluation which is generally - * `globals` or `locals` depending on the context. - * @param session The evaluation session. - * @param groups The map of [Group]s that is currently being built during query execution. - */ -internal data class Environment( - internal val locals: Bindings, - val current: Bindings = locals, - val session: EvaluationSession, - val groups: MutableMap = createGroupMap(), - val currentGroup: Group? = null, - val branchCounts: MutableMap? = null, - val branchConditionCounts: MutableMap? = null -) { - - companion object { - fun standard() = Environment(locals = Bindings.empty(), session = EvaluationSession.standard()) - - private fun createGroupMap() = TreeMap(DEFAULT_COMPARATOR) - } - - internal enum class CurrentMode { - LOCALS, - GLOBALS_THEN_LOCALS - } - - /** Constructs a new nested environment with the locals being the [current] bindings. */ - internal fun nest( - newLocals: Bindings, - currentMode: CurrentMode = CurrentMode.LOCALS, - newGroup: Group? = currentGroup - ): Environment { - - val derivedLocals = newLocals.delegate(locals) - val newCurrent = when (currentMode) { - CurrentMode.LOCALS -> derivedLocals - CurrentMode.GLOBALS_THEN_LOCALS -> session.globals.delegate(derivedLocals) - } - return copy(locals = derivedLocals, current = newCurrent, currentGroup = newGroup) - } - - /** - * Creates a new environment with the same [Bindings] and session but with empty grouping state. - * This is what allows GROUP BY to work in sub-queries without running into any grouping state - * from the outer query. - */ - internal fun nestQuery() = copy( - currentGroup = null, - groups = createGroupMap() - ) - - /** Constructs a copy of this environment with the locals being the current bindings. */ - internal fun flipToLocals(): Environment = copy(current = locals) - - /** Constructs a copy of this environment with the [globals] being the current bindings. */ - internal fun flipToGlobalsFirst(): Environment = copy(current = session.globals.delegate(locals)) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ErrorSignaler.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ErrorSignaler.kt deleted file mode 100644 index 58afea4316..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ErrorSignaler.kt +++ /dev/null @@ -1,101 +0,0 @@ -package org.partiql.lang.eval - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorBehaviorInPermissiveMode -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.util.propertyValueMapOf - -/** Provides a common interface controlling the evaluation-time error signaling of [CompileOptions.typingMode]. */ -internal interface ErrorSignaler { - /** Depending on the error mode, either throws an [EvaluationException] using the specified [ErrorDetails] - * or returns `MISSING`. */ - fun error(errorCode: ErrorCode, createErrorDetails: () -> ErrorDetails): ExprValue -} - -/** - * Syntactic sugar for error signaling according to the current [TypingMode]. - * - * Depending on the [TypingMode] mode, if [test] is true, either [createErrorDetails] will be - * invoked and used to instantiate and throw an [EvaluationException], or the PartiQL `MISSING` - * value will be returned, depending on the [TypingMode]. - * - * If [test] is false, [otherwise] is invoked. Any exception thrown within [otherwise] is left alone - * to propagate up as usual. Be aware that this can *still* mean that the current thunk can result in - * `MISSING`, depending on the [ThunkFactory] currently in use. - */ -internal inline fun ErrorSignaler.errorIf( - test: Boolean, - errorCode: ErrorCode, - crossinline createErrorDetails: () -> ErrorDetails, - crossinline otherwise: () -> ExprValue -): ExprValue = - when { - test -> this.error(errorCode) { createErrorDetails() } - else -> otherwise() - } - -/** - * Contains the details of an error. - * - * [errorCode] and [errorContext] together are used to compose an error message for the end-user - * while [message] is meant for to help developers of services that use PartiQL. - */ -internal class ErrorDetails( - /** Meta information of the node that is to blame for the error. */ - val metas: MetaContainer, - /** The programmer readable exception message. */ - val message: String, - val errorContext: PropertyValueMap? = null -) - -internal fun TypingMode.createErrorSignaler() = - when (this) { - TypingMode.LEGACY -> LegacyErrorSignaler() - TypingMode.PERMISSIVE -> PermissiveErrorSignaler() - } - -/** Defines legacy error signaling. */ -private class LegacyErrorSignaler : ErrorSignaler { - /** Invokes [createErrorDetails] and uses the return value to construct and throw an [EvaluationException]. */ - override fun error(errorCode: ErrorCode, createErrorDetails: () -> ErrorDetails): ExprValue = - throwEE(errorCode, createErrorDetails) -} - -/** Defines permissive error signaling. */ -private class PermissiveErrorSignaler() : ErrorSignaler { - - /** Ignores [createErrorDetails] and simply returns MISSING. */ - override fun error(errorCode: ErrorCode, createErrorDetails: () -> ErrorDetails): ExprValue = - when (errorCode.errorBehaviorInPermissiveMode) { - ErrorBehaviorInPermissiveMode.THROW_EXCEPTION -> throwEE(errorCode, createErrorDetails) - ErrorBehaviorInPermissiveMode.RETURN_MISSING -> ExprValue.missingValue - } -} - -/** Throws an [EvaluationException] using the specified error details. */ -private fun throwEE(errorCode: ErrorCode, createErrorDetails: () -> ErrorDetails): Nothing { - with(createErrorDetails()) { - // Add source location if we need to and if we can - val srcLoc = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - val errCtx = this.errorContext ?: propertyValueMapOf() - if (srcLoc != null) { - if (!errCtx.hasProperty(Property.LINE_NUMBER)) { - errCtx[Property.LINE_NUMBER] = srcLoc.lineNum - } - if (!errCtx.hasProperty(Property.COLUMN_NUMBER)) { - errCtx[Property.COLUMN_NUMBER] = srcLoc.charOffset - } - } - - throw EvaluationException( - message = message, - errorCode = errorCode, - errorContext = errCtx, - cause = null, - internal = false - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluatingCompiler.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluatingCompiler.kt deleted file mode 100644 index e09f2aba86..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluatingCompiler.kt +++ /dev/null @@ -1,3528 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ion.IonString -import com.amazon.ion.IonType -import com.amazon.ion.IonValue -import com.amazon.ion.Timestamp -import com.amazon.ion.system.IonSystemBuilder -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.emptyMetaContainer -import com.amazon.ionelement.api.ionBool -import com.amazon.ionelement.api.toIonValue -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.AggregateCallSiteListMeta -import org.partiql.lang.ast.AggregateRegisterIdMeta -import org.partiql.lang.ast.IsCountStarMeta -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.ast.UniqueNameMeta -import org.partiql.lang.ast.find -import org.partiql.lang.ast.sourceLocation -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.staticType -import org.partiql.lang.domains.toBindingCase -import org.partiql.lang.eval.binding.Alias -import org.partiql.lang.eval.binding.localsBinder -import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure -import org.partiql.lang.eval.impl.FunctionManager -import org.partiql.lang.eval.like.parsePattern -import org.partiql.lang.eval.time.Time -import org.partiql.lang.eval.visitors.PartiqlAstSanityValidator -import org.partiql.lang.graph.EdgeSpec -import org.partiql.lang.graph.GpmlTranslator -import org.partiql.lang.graph.Graph -import org.partiql.lang.graph.GraphEngine -import org.partiql.lang.graph.NodeSpec -import org.partiql.lang.graph.Stride -import org.partiql.lang.graph.StrideSpec -import org.partiql.lang.types.StaticTypeUtils.getRuntimeType -import org.partiql.lang.types.StaticTypeUtils.isInstance -import org.partiql.lang.types.StaticTypeUtils.staticTypeFromExprValue -import org.partiql.lang.types.TypedOpParameter -import org.partiql.lang.types.UnknownArguments -import org.partiql.lang.types.toTypedOpParameter -import org.partiql.lang.util.bigDecimalOf -import org.partiql.lang.util.checkThreadInterrupted -import org.partiql.lang.util.codePointSequence -import org.partiql.lang.util.div -import org.partiql.lang.util.drop -import org.partiql.lang.util.exprValue -import org.partiql.lang.util.foldLeftProduct -import org.partiql.lang.util.interruptibleFold -import org.partiql.lang.util.isZero -import org.partiql.lang.util.minus -import org.partiql.lang.util.plus -import org.partiql.lang.util.rem -import org.partiql.lang.util.stringValue -import org.partiql.lang.util.take -import org.partiql.lang.util.times -import org.partiql.lang.util.totalMinutes -import org.partiql.lang.util.unaryMinus -import org.partiql.pig.runtime.SymbolPrimitive -import org.partiql.types.AnyOfType -import org.partiql.types.AnyType -import org.partiql.types.IntType -import org.partiql.types.SingleType -import org.partiql.types.StaticType -import org.partiql.types.UnsupportedTypeCheckException -import java.util.LinkedList -import java.util.Stack -import java.util.TreeSet -import java.util.regex.Pattern - -/** - * A thunk with no parameters other than the current environment. - * - * See https://en.wikipedia.org/wiki/Thunk - * - * This name was chosen because it is a thunk that accepts an instance of `Environment`. - */ -internal typealias ThunkEnv = Thunk - -/** - * A thunk taking a single [T] argument and the current environment. - * - * See https://en.wikipedia.org/wiki/Thunk - * - * This name was chosen because it is a thunk that accepts an instance of `Environment` and an [ExprValue] as - * its arguments. - */ -private typealias ThunkEnvValue = ThunkValue - -/** - * A basic compiler that converts an instance of [PartiqlAst] to an [Expression]. - * - * This implementation produces a "compiled" form consisting of context-threaded - * code in the form of a tree of [ThunkEnv]s. An overview of this technique can be found - * [here][1]. - * - * **Note:** *threaded* in this context is used in how the code gets *threaded* together for - * interpretation and **not** the concurrency primitive. That is to say this code is NOT thread - * safe. - * - * [1]: https://www.complang.tuwien.ac.at/anton/lvas/sem06w/fest.pdf - * - * Note that this is not implemented in the pattern of a typical visitor pattern. The visitor pattern isn't a good - * match for all scenarios. It's great for simple needs such as the types of checks performed or simple transformations - * such as partial evaluation and transforming variable references to De Bruijn indices, however a compiler needs - * much finer grain of control over exactly how and when each node is walked, visited, and transformed. - * - * @param functions A map of functions keyed by function name that will be available during compilation. - * @param compileOptions Various options that effect how the source code is compiled. - */ -internal open class EvaluatingCompiler( - private val functions: List, - private val customTypedOpParameters: Map, - private val procedures: Map, - internal val compileOptions: CompileOptions = CompileOptions.standard() -) { - - // TODO: remove this once we migrate from `IonValue` to `IonElement`. - private val ion = IonSystemBuilder.standard().build() - - private val errorSignaler = compileOptions.typingMode.createErrorSignaler() - internal val thunkFactory = compileOptions.typingMode.createThunkFactory(compileOptions.thunkOptions) - private val functionManager = FunctionManager(functions) - - private val compilationContextStack = Stack() - - private val UNBOUND_QUOTED_IDENTIFIER_HINT: String = - "Hint: did you intend to use single quotes (') here instead of double quotes (\")? " + - "Use single quotes (') for string literals and double quotes (\") for quoted identifiers." - - private val currentCompilationContext: CompilationContext - get() = compilationContextStack.peek() ?: errNoContext( - "compilationContextStack was empty.", ErrorCode.EVALUATOR_UNEXPECTED_VALUE, internal = true - ) - - /** - * This checks whether the thread has been interrupted. Specifically, it currently checks during the compilation - * of aggregations and joins, the "evaluation" of aggregations and joins, and the materialization of joins - * and from source scans. - * - * Note: This is essentially a way to avoid constantly checking [CompileOptions.interruptible]. By writing it this - * way, we statically determine whether to introduce checks. If the compiler has specified - * [CompileOptions.interruptible], the invocation of this function will insert a Thread interruption check. If not - * specified, it will not perform the check during compilation/evaluation/materialization. - */ - private val interruptionCheck: () -> Unit = when (compileOptions.interruptible) { - true -> { -> - if (Thread.interrupted()) { - throw InterruptedException() - } - } - false -> { -> Unit } - } - - // Note: please don't make this inline -- it messes up [EvaluationException] stack traces and - // isn't a huge benefit because this is only used at SQL-compile time anyway. - internal fun nestCompilationContext( - expressionContext: ExpressionContext, - fromSourceNames: Set, - block: () -> R - ): R { - compilationContextStack.push( - when { - compilationContextStack.empty() -> CompilationContext(expressionContext, fromSourceNames) - else -> compilationContextStack.peek().createNested( - expressionContext, - fromSourceNames - ) - } - ) - - try { - return block() - } finally { - compilationContextStack.pop() - } - } - - private fun Boolean.exprValue(): ExprValue = ExprValue.newBoolean(this) - private fun String.exprValue(): ExprValue = ExprValue.newString(this) - - /** Represents an instance of a compiled `GROUP BY` expression and alias. */ - private class CompiledGroupByItem(val alias: ExprValue, val uniqueId: String?, val thunk: ThunkEnv) - - /** - * Represents a memoized binding [BindingName] and an [ExprValue] of the same name. - * Used during evaluation og `GROUP BY`. - */ - private data class FromSourceBindingNamePair(val bindingName: BindingName, val nameExprValue: ExprValue) - - /** Represents an instance of a compiled `ORDER BY` expression, orderingSpec and nulls type. */ - private class CompiledOrderByItem(val comparator: NaturalExprValueComparators, val thunk: ThunkEnv) - - /** - * Base class for [ExprAggregator] instances which accumulate values and perform a final computation. - */ - private inner class Accumulator( - var current: ExprValue?, - val nextFunc: (ExprValue?, ExprValue) -> ExprValue, - val valueFilter: (ExprValue) -> Boolean = { _ -> true } - ) : ExprAggregator { - - override fun next(value: ExprValue) { - // skip the accumulation function if the value is unknown or if the value is filtered out - if (value.isNotUnknown() && valueFilter.invoke(value)) { - current = nextFunc(current, value) - } - } - - override fun compute() = current ?: ExprValue.nullValue - } - - private fun comparisonAccumulator(comparator: NaturalExprValueComparators): (ExprValue?, ExprValue) -> ExprValue = - { left, right -> - when { - left == null || comparator.compare(left, right) > 0 -> right - else -> left - } - } - - /** Dispatch table for built-in aggregate functions. */ - private val builtinAggregates: Map, ExprAggregatorFactory> = - run { - fun checkIsNumberType(funcName: String, value: ExprValue) { - if (!value.type.isNumber) { - errNoContext( - message = "Aggregate function $funcName expects arguments of NUMBER type but the following value was provided: $value, with type of ${value.type}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_AGG_FUNCTION, - internal = false - ) - } - } - - fun checkIsBooleanType(funcName: String, value: ExprValue) { - if (value.type != ExprValueType.BOOL) { - errNoContext( - message = "Aggregate function $funcName expects arguments of BOOL type but the following value was provided: $value, with type of ${value.type}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_AGG_FUNCTION, - internal = false - ) - } - } - - val countAccFunc: (ExprValue?, ExprValue) -> ExprValue = { accumulated, _ -> (accumulated!!.longValue() + 1L).exprValue() } - val sumAccFunc: (ExprValue?, ExprValue) -> ExprValue = { accumulated, nextItem -> - checkIsNumberType("SUM", nextItem) - accumulated?.let { (it.numberValue() + nextItem.numberValue()).exprValue() } ?: nextItem - } - val minAccFunc = comparisonAccumulator(NaturalExprValueComparators.NULLS_LAST_ASC) - val maxAccFunc = comparisonAccumulator(NaturalExprValueComparators.NULLS_LAST_DESC) - val avgAggregateGenerator = { filter: (ExprValue) -> Boolean -> - object : ExprAggregator { - var sum: Number? = null - var count = 0L - - override fun next(value: ExprValue) { - if (value.isNotUnknown() && filter.invoke(value)) { - checkIsNumberType("AVG", value) - sum = sum?.let { it + value.numberValue() } ?: value.numberValue() - count++ - } - } - - override fun compute() = - sum?.let { (it / bigDecimalOf(count)).exprValue() } - ?: ExprValue.nullValue - } - } - val everyAccFunc: (ExprValue?, ExprValue) -> ExprValue = { accumulated, nextItem -> - checkIsBooleanType("EVERY", nextItem) - accumulated?.let { ExprValue.newBoolean(it.booleanValue() && nextItem.booleanValue()) } ?: nextItem - } - val anySomeAccFunc: (ExprValue?, ExprValue) -> ExprValue = { accumulated, nextItem -> - checkIsBooleanType("ANY/SOME", nextItem) - accumulated?.let { ExprValue.newBoolean(it.booleanValue() || nextItem.booleanValue()) } ?: nextItem - } - val allFilter: (ExprValue) -> Boolean = { _ -> true } - // each distinct ExprAggregator must get its own createUniqueExprValueFilter() - mapOf( - Pair("count", PartiqlAst.SetQuantifier.All()) to ExprAggregatorFactory.over { - Accumulator((0L).exprValue(), countAccFunc, allFilter) - }, - - Pair("count", PartiqlAst.SetQuantifier.Distinct()) to ExprAggregatorFactory.over { - Accumulator((0L).exprValue(), countAccFunc, createUniqueExprValueFilter()) - }, - - Pair("sum", PartiqlAst.SetQuantifier.All()) to ExprAggregatorFactory.over { - Accumulator(null, sumAccFunc, allFilter) - }, - - Pair("sum", PartiqlAst.SetQuantifier.Distinct()) to ExprAggregatorFactory.over { - Accumulator(null, sumAccFunc, createUniqueExprValueFilter()) - }, - - Pair("avg", PartiqlAst.SetQuantifier.All()) to ExprAggregatorFactory.over { - avgAggregateGenerator(allFilter) - }, - - Pair("avg", PartiqlAst.SetQuantifier.Distinct()) to ExprAggregatorFactory.over { - avgAggregateGenerator(createUniqueExprValueFilter()) - }, - - Pair("max", PartiqlAst.SetQuantifier.All()) to ExprAggregatorFactory.over { - Accumulator(null, maxAccFunc, allFilter) - }, - - Pair("max", PartiqlAst.SetQuantifier.Distinct()) to ExprAggregatorFactory.over { - Accumulator(null, maxAccFunc, createUniqueExprValueFilter()) - }, - - Pair("min", PartiqlAst.SetQuantifier.All()) to ExprAggregatorFactory.over { - Accumulator(null, minAccFunc, allFilter) - }, - - Pair("min", PartiqlAst.SetQuantifier.Distinct()) to ExprAggregatorFactory.over { - Accumulator(null, minAccFunc, createUniqueExprValueFilter()) - }, - - Pair("every", PartiqlAst.SetQuantifier.All()) to ExprAggregatorFactory.over { - Accumulator(null, everyAccFunc, allFilter) - }, - - Pair("every", PartiqlAst.SetQuantifier.Distinct()) to ExprAggregatorFactory.over { - Accumulator(null, everyAccFunc, createUniqueExprValueFilter()) - }, - - Pair("any", PartiqlAst.SetQuantifier.All()) to ExprAggregatorFactory.over { - Accumulator(null, anySomeAccFunc, allFilter) - }, - - Pair("any", PartiqlAst.SetQuantifier.Distinct()) to ExprAggregatorFactory.over { - Accumulator(null, anySomeAccFunc, createUniqueExprValueFilter()) - }, - - Pair("some", PartiqlAst.SetQuantifier.All()) to ExprAggregatorFactory.over { - Accumulator(null, anySomeAccFunc, allFilter) - }, - - Pair("some", PartiqlAst.SetQuantifier.Distinct()) to ExprAggregatorFactory.over { - Accumulator(null, anySomeAccFunc, createUniqueExprValueFilter()) - }, - ) - } - - /** - * Compiles a [PartiqlAst.Statement] tree to an [Expression]. - * - * Checks [Thread.interrupted] before every expression and sub-expression is compiled - * and throws [InterruptedException] if [Thread.interrupted] it has been set in the - * hope that long-running compilations may be aborted by the caller. - */ - open fun compile(originalAst: PartiqlAst.Statement): Expression { - val visitorTransform = compileOptions.visitorTransformMode.createVisitorTransform() - val transformedAst = visitorTransform.transformStatement(originalAst) - val partiqlAstSanityValidator = PartiqlAstSanityValidator() - - partiqlAstSanityValidator.validate(transformedAst, compileOptions) - - val thunk = nestCompilationContext(ExpressionContext.NORMAL, emptySet()) { - compileAstStatement(transformedAst) - } - - return object : Expression { - override val coverageStructure: CoverageStructure? = null - - override fun eval(session: EvaluationSession): ExprValue { - val env = Environment( - session = session, - locals = session.globals, - current = session.globals - ) - return thunk(env) - } - - override fun evaluate(session: EvaluationSession): PartiQLResult { - val env = Environment( - session = session, - locals = session.globals, - current = session.globals - ) - val value = thunk(env) - return PartiQLResult.Value(value = value) - } - } - } - - /** - * Evaluates an instance of [PartiqlAst.Statement] against a global set of bindings. - */ - fun eval(ast: PartiqlAst.Statement, session: EvaluationSession): ExprValue = compile(ast).eval(session) - - /** - * Compiles the specified [PartiqlAst.Statement] into a [ThunkEnv]. - * - * This function will [InterruptedException] if [Thread.interrupted] has been set. - */ - internal open fun compileAstStatement(ast: PartiqlAst.Statement): ThunkEnv { - checkThreadInterrupted() - return when (ast) { - is PartiqlAst.Statement.Query -> compileAstExpr(ast.expr) - is PartiqlAst.Statement.Ddl -> compileDdl(ast) - is PartiqlAst.Statement.Dml -> compileDml(ast) - is PartiqlAst.Statement.Exec -> compileExec(ast) - is PartiqlAst.Statement.Explain -> throw EvaluationException( - "EXPLAIN is not supported in the Evaluating Compiler", - ErrorCode.UNIMPLEMENTED_FEATURE, - internal = false - ) - } - } - - internal open fun compileAstExpr(expr: PartiqlAst.Expr): ThunkEnv { - val metas = expr.metas - - return when (expr) { - is PartiqlAst.Expr.Lit -> compileLit(expr, metas) - is PartiqlAst.Expr.Missing -> compileMissing(metas) - is PartiqlAst.Expr.Id -> compileId(expr, metas) - is PartiqlAst.Expr.SimpleCase -> compileSimpleCase(expr, metas) - is PartiqlAst.Expr.SearchedCase -> compileSearchedCase(expr, metas) - is PartiqlAst.Expr.Path -> compilePath(expr, metas) - is PartiqlAst.Expr.Struct -> compileStruct(expr, metas) - is PartiqlAst.Expr.Select -> compileSelect(expr, metas) - is PartiqlAst.Expr.CallAgg -> compileCallAgg(expr, metas) - is PartiqlAst.Expr.Parameter -> compileParameter(expr, metas) - is PartiqlAst.Expr.Date -> compileDate(expr, metas) - is PartiqlAst.Expr.LitTime -> compileLitTime(expr, metas) - - // arithmetic operations - is PartiqlAst.Expr.Plus -> compilePlus(expr, metas) - is PartiqlAst.Expr.Times -> compileTimes(expr, metas) - is PartiqlAst.Expr.Minus -> compileMinus(expr, metas) - is PartiqlAst.Expr.Divide -> compileDivide(expr, metas) - is PartiqlAst.Expr.Modulo -> compileModulo(expr, metas) - is PartiqlAst.Expr.BitwiseAnd -> compileBitwiseAnd(expr, metas) - - // comparison operators - is PartiqlAst.Expr.And -> compileAnd(expr, metas) - is PartiqlAst.Expr.Between -> compileBetween(expr, metas) - is PartiqlAst.Expr.Eq -> compileEq(expr, metas) - is PartiqlAst.Expr.Gt -> compileGt(expr, metas) - is PartiqlAst.Expr.Gte -> compileGte(expr, metas) - is PartiqlAst.Expr.Lt -> compileLt(expr, metas) - is PartiqlAst.Expr.Lte -> compileLte(expr, metas) - is PartiqlAst.Expr.Like -> compileLike(expr, metas) - is PartiqlAst.Expr.InCollection -> compileIn(expr, metas) - - // logical operators - is PartiqlAst.Expr.Ne -> compileNe(expr, metas) - is PartiqlAst.Expr.Or -> compileOr(expr, metas) - - // unary - is PartiqlAst.Expr.Not -> compileNot(expr, metas) - is PartiqlAst.Expr.Pos -> compilePos(expr, metas) - is PartiqlAst.Expr.Neg -> compileNeg(expr, metas) - - // other operators - is PartiqlAst.Expr.Concat -> compileConcat(expr, metas) - is PartiqlAst.Expr.Call -> compileCall(expr, metas) - is PartiqlAst.Expr.NullIf -> compileNullIf(expr, metas) - is PartiqlAst.Expr.Coalesce -> compileCoalesce(expr, metas) - - // "typed" operators (RHS is a data type and not an expression) - is PartiqlAst.Expr.Cast -> compileCast(expr, metas) - is PartiqlAst.Expr.IsType -> compileIs(expr, metas) - is PartiqlAst.Expr.CanCast -> compileCanCast(expr, metas) - is PartiqlAst.Expr.CanLosslessCast -> compileCanLosslessCast(expr, metas) - - // sequence constructors - is PartiqlAst.Expr.List -> compileSeq(ExprValueType.LIST, expr.values, metas) - is PartiqlAst.Expr.Sexp -> compileSeq(ExprValueType.SEXP, expr.values, metas) - is PartiqlAst.Expr.Bag -> compileSeq(ExprValueType.BAG, expr.values, metas) - - // bag operators - is PartiqlAst.Expr.BagOp -> compileBagOp(expr, metas) - - // Session Attributes - is PartiqlAst.Expr.SessionAttribute -> compileSessionAttribute(expr, metas) - - is PartiqlAst.Expr.GraphMatch -> compileGraphMatch(expr, metas) - is PartiqlAst.Expr.CallWindow -> TODO("Evaluating Compiler doesn't support window function") - is PartiqlAst.Expr.Timestamp -> TODO() - } - } - - private fun compileAstExprs(args: List) = args.map { compileAstExpr(it) } - - private fun compileNullIf(expr: PartiqlAst.Expr.NullIf, metas: MetaContainer): ThunkEnv { - val expr1Thunk = compileAstExpr(expr.expr1) - val expr2Thunk = compileAstExpr(expr.expr2) - - // Note: NULLIF does not propagate the unknown values and .exprEquals provides the correct semantics. - return thunkFactory.thunkEnv(metas) { env -> - val expr1Value = expr1Thunk(env) - val expr2Value = expr2Thunk(env) - when { - expr1Value.exprEquals(expr2Value) -> ExprValue.nullValue - else -> expr1Value - } - } - } - - private fun compileCoalesce(expr: PartiqlAst.Expr.Coalesce, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.args) - - return thunkFactory.thunkEnv(metas) { env -> - var nullFound = false - var knownValue: ExprValue? = null - for (thunk in argThunks) { - val argValue = thunk(env) - if (argValue.isNotUnknown()) { - knownValue = argValue - // No need to execute remaining thunks to save computation as first non-unknown value is found - break - } - if (argValue.type == ExprValueType.NULL) { - nullFound = true - } - } - when (knownValue) { - null -> when { - compileOptions.typingMode == TypingMode.PERMISSIVE && !nullFound -> ExprValue.missingValue - else -> ExprValue.nullValue - } - else -> knownValue - } - } - } - - /** - * Returns a function that accepts an [ExprValue] as an argument and returns true it is `NULL`, `MISSING`, or - * within the range specified by [range]. - */ - private fun integerValueValidator( - range: LongRange - ): (ExprValue) -> Boolean = { value -> - when (value.type) { - ExprValueType.NULL, ExprValueType.MISSING -> true - ExprValueType.INT -> { - val longValue: Long = value.scalar.numberValue()?.toLong() - ?: error( - "ExprValue.numberValue() must not be `NULL` when its type is INT." + - "This indicates that the ExprValue instance has a bug." - ) - - // PRO-TIP: make sure to use the `Long` primitive type here with `.contains` otherwise - // Kotlin will use the version of `.contains` that treats [range] as a collection, and it will - // be very slow! - range.contains(longValue) - } - else -> error( - "IntegerValueValidator can only accept ExprValue with type INT, NULL, and MISSING." - ) - } - } - - /** - * For operators which could return integer type, check integer overflow in case of [TypingMode.PERMISSIVE]. - */ - private fun checkIntegerOverflow(computeThunk: ThunkEnv, metas: MetaContainer): ThunkEnv = - when (val staticTypes = metas.staticType?.type?.getTypes()) { - // No staticType, can't validate integer size. - null -> computeThunk - else -> { - when (compileOptions.typingMode) { - TypingMode.LEGACY -> { - // integer size constraints have not been tested under [TypingMode.LEGACY] because the - // [StaticTypeInferenceVisitorTransform] doesn't support being used with legacy mode yet. - // throw an exception in case we encounter this untested scenario. This might work fine, but I - // wouldn't bet on it. - val hasConstrainedInteger = staticTypes.any { - it is IntType && it.rangeConstraint != IntType.IntRangeConstraint.UNCONSTRAINED - } - if (hasConstrainedInteger) { - TODO("Legacy mode doesn't support integer size constraints yet.") - } else { - computeThunk - } - } - - TypingMode.PERMISSIVE -> { - val biggestIntegerType = staticTypes.filterIsInstance().maxByOrNull { - it.rangeConstraint.numBytes - } - when (biggestIntegerType) { - // static type contains one or more IntType - is IntType -> { - val validator = integerValueValidator(biggestIntegerType.rangeConstraint.validRange) - thunkFactory.thunkEnv(metas) { env -> - val naryResult = computeThunk(env) - // validation shall only happen when the result is INT/MISSING/NULL - // this is important as StaticType may contain a mixture of multiple types - when (val type = naryResult.type) { - ExprValueType.INT, ExprValueType.MISSING, ExprValueType.NULL -> errorSignaler.errorIf( - !validator(naryResult), - ErrorCode.EVALUATOR_INTEGER_OVERFLOW, - { ErrorDetails(metas, "Integer overflow", errorContextFrom(metas)) }, - { naryResult } - ) - - else -> { - if (staticTypes.all { it is IntType }) { - error( - "The expression's static type was supposed to be INT but instead it was $type" + - "This may indicate the presence of a bug in the type inferencer." - ) - } else { - naryResult - } - } - } - } - } - // If there is no IntType StaticType, can't validate the integer size either. - null -> computeThunk - else -> computeThunk - } - } - } - } - } - - private fun compilePlus(expr: PartiqlAst.Expr.Plus, metas: MetaContainer): ThunkEnv { - if (expr.operands.size < 2) { - error("Internal Error: PartiqlAst.Expr.Plus must have at least 2 arguments") - } - - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.numberValue() + rValue.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileMinus(expr: PartiqlAst.Expr.Minus, metas: MetaContainer): ThunkEnv { - if (expr.operands.size < 2) { - error("Internal Error: PartiqlAst.Expr.Minus must have at least 2 arguments") - } - - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.numberValue() - rValue.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compilePos(expr: PartiqlAst.Expr.Pos, metas: MetaContainer): ThunkEnv { - val exprThunk = compileAstExpr(expr.expr) - - val computeThunk = thunkFactory.thunkEnvOperands(metas, exprThunk) { _, value -> - // Invoking .numberValue() here makes this essentially just a type check - value.numberValue() - // Original value is returned unmodified. - value - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileNeg(expr: PartiqlAst.Expr.Neg, metas: MetaContainer): ThunkEnv { - val exprThunk = compileAstExpr(expr.expr) - - val computeThunk = thunkFactory.thunkEnvOperands(metas, exprThunk) { _, value -> - (-value.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileTimes(expr: PartiqlAst.Expr.Times, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.numberValue() * rValue.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileDivide(expr: PartiqlAst.Expr.Divide, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - val denominator = rValue.numberValue() - - errorSignaler.errorIf( - denominator.isZero(), - ErrorCode.EVALUATOR_DIVIDE_BY_ZERO, - { ErrorDetails(metas, "/ by zero") } - ) { - try { - (lValue.numberValue() / denominator).exprValue() - } catch (e: ArithmeticException) { - // Setting the internal flag as true as it is not clear what - // ArithmeticException may be thrown by the above - throw EvaluationException( - cause = e, - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - errorContext = errorContextFrom(metas), - internal = true - ) - } - } - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileModulo(expr: PartiqlAst.Expr.Modulo, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - val denominator = rValue.numberValue() - if (denominator.isZero()) { - err("% by zero", ErrorCode.EVALUATOR_MODULO_BY_ZERO, errorContextFrom(metas), false) - } - - (lValue.numberValue() % denominator).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileBitwiseAnd(expr: PartiqlAst.Expr.BitwiseAnd, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - // Bitwise operator will not overflow - return thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.longValue() and rValue.longValue()).exprValue() - } - } - - internal open fun compileEq(expr: PartiqlAst.Expr.Eq, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> - (lValue.exprEquals(rValue)) - } - } - - internal open fun compileNe(expr: PartiqlAst.Expr.Ne, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - ((!lValue.exprEquals(rValue)).exprValue()) - } - } - - internal open fun compileLt(expr: PartiqlAst.Expr.Lt, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue < rValue } - } - - internal open fun compileLte(expr: PartiqlAst.Expr.Lte, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue <= rValue } - } - - internal open fun compileGt(expr: PartiqlAst.Expr.Gt, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue > rValue } - } - - internal open fun compileGte(expr: PartiqlAst.Expr.Gte, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue >= rValue } - } - - internal open fun compileBetween(expr: PartiqlAst.Expr.Between, metas: MetaContainer): ThunkEnv { - val valueThunk = compileAstExpr(expr.value) - val fromThunk = compileAstExpr(expr.from) - val toThunk = compileAstExpr(expr.to) - - return thunkFactory.thunkEnvOperands(metas, valueThunk, fromThunk, toThunk) { _, v, f, t -> - (v >= f && v <= t).exprValue() - } - } - - /** - * `IN` can *almost* be thought of has being syntactic sugar for the `OR` operator. - * - * `a IN (b, c, d)` is equivalent to `a = b OR a = c OR a = d`. On deep inspection, there - * are important implications to this regarding propagation of unknown values. Specifically, the - * presence of any unknown in `b`, `c`, or `d` will result in unknown propagation iif `a` does not - * equal `b`, `c`, or `d`. i.e.: - * - * - `1 in (null, 2, 3)` -> `null` - * - `2 in (null, 2, 3)` -> `true` - * - `2 in (1, 2, 3)` -> `true` - * - `0 in (1, 2, 4)` -> `false` - * - * `IN` is varies from the `OR` operator in that this behavior holds true when other types of expressions are - * used on the right side of `IN` such as sub-queries and variables whose value is that of a list or bag. - */ - internal open fun compileIn(expr: PartiqlAst.Expr.InCollection, metas: MetaContainer): ThunkEnv { - val args = expr.operands - val leftThunk = compileAstExpr(args[0]) - val rightOp = args[1] - - fun isOptimizedCase(values: List): Boolean = - values.all { it is PartiqlAst.Expr.Lit && !it.value.isNull } - - fun optimizedCase(values: List): ThunkEnv { - // Put all the literals in the sequence into a pre-computed map to be checked later by the thunk. - // If the left-hand value is one of these we can short-circuit with a result of TRUE. - // This is the fastest possible case and allows for hundreds of literal values (or more) in the - // sequence without a huge performance penalty. - // NOTE: we cannot use a [HashSet<>] here because [ExprValue] does not implement [Object.hashCode] or - // [Object.equals]. - val precomputedLiteralsMap = values - .filterIsInstance() - .mapTo(TreeSet(DEFAULT_COMPARATOR)) { - ExprValue.of( - it.value.toIonValue(ion) - ) - } - - // the compiled thunk simply checks if the left side is contained on the right side. - // thunkEnvOperands takes care of unknown propagation for the left side; for the right, - // this unknown propagation does not apply since we've eliminated the possibility of unknowns above. - return thunkFactory.thunkEnvOperands(metas, leftThunk) { _, leftValue -> - precomputedLiteralsMap.contains(leftValue).exprValue() - } - } - - return when { - // We can significantly optimize this if rightArg is a sequence constructor which is composed of entirely - // of non-null literal values. - rightOp is PartiqlAst.Expr.List && isOptimizedCase(rightOp.values) -> optimizedCase(rightOp.values) - rightOp is PartiqlAst.Expr.Bag && isOptimizedCase(rightOp.values) -> optimizedCase(rightOp.values) - rightOp is PartiqlAst.Expr.Sexp && isOptimizedCase(rightOp.values) -> optimizedCase(rightOp.values) - // The unoptimized case... - else -> { - val rightThunk = compileAstExpr(rightOp) - - // Legacy mode: - // Returns FALSE when the right side of IN is not a sequence - // Returns NULL if the right side is MISSING or any value on the right side is MISSING - // Permissive mode: - // Returns MISSING when the right side of IN is not a sequence - // Returns MISSING if the right side is MISSING or any value on the right side is MISSING - val (propagateMissingAs, propagateNotASeqAs) = when (compileOptions.typingMode) { - TypingMode.LEGACY -> ExprValue.nullValue to ExprValue.newBoolean(false) - TypingMode.PERMISSIVE -> ExprValue.missingValue to ExprValue.missingValue - } - - // Note that standard unknown propagation applies to the left and right operands. Both [TypingMode]s - // are handled by [ThunkFactory.thunkEnvOperands] and that additional rules for unknown propagation are - // implemented within the thunk for the values within the sequence on the right side of IN. - thunkFactory.thunkEnvOperands(metas, leftThunk, rightThunk) { _, leftValue, rightValue -> - var nullSeen = false - var missingSeen = false - - when { - rightValue.type == ExprValueType.MISSING -> propagateMissingAs - !rightValue.type.isSequence -> propagateNotASeqAs - else -> { - rightValue.forEach { - when (it.type) { - ExprValueType.NULL -> nullSeen = true - ExprValueType.MISSING -> missingSeen = true - // short-circuit to TRUE on the first matching value - else -> if (it.exprEquals(leftValue)) { - return@thunkEnvOperands ExprValue.newBoolean(true) - } - } - } - // If we make it here then there was no match. Propagate MISSING, NULL or return false. - // Note that if both MISSING and NULL was encountered, MISSING takes precedence. - when { - missingSeen -> propagateMissingAs - nullSeen -> ExprValue.nullValue - else -> ExprValue.newBoolean(false) - } - } - } - } - } - } - } - - internal open fun compileNot(expr: PartiqlAst.Expr.Not, metas: MetaContainer): ThunkEnv { - val argThunk = compileAstExpr(expr.expr) - - return thunkFactory.thunkEnvOperands(metas, argThunk) { _, value -> - (!value.booleanValue()).exprValue() - } - } - - internal open fun compileAnd(expr: PartiqlAst.Expr.And, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - // can't use the null propagation supplied by [ThunkFactory.thunkEnv] here because AND short-circuits on - // false values and *NOT* on NULL or MISSING - return when (compileOptions.typingMode) { - TypingMode.LEGACY -> thunkFactory.thunkEnv(metas) thunk@{ env -> - var hasUnknowns = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - when { - currValue.isUnknown() -> hasUnknowns = true - // Short circuit only if we encounter a known false value. - !currValue.booleanValue() -> return@thunk ExprValue.newBoolean(false) - } - } - - when (hasUnknowns) { - true -> ExprValue.nullValue - false -> ExprValue.newBoolean(true) - } - } - TypingMode.PERMISSIVE -> thunkFactory.thunkEnv(metas) thunk@{ env -> - var hasNull = false - var hasMissing = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - when (currValue.type) { - // Short circuit only if we encounter a known false value. - ExprValueType.BOOL -> if (!currValue.booleanValue()) return@thunk ExprValue.newBoolean(false) - ExprValueType.NULL -> hasNull = true - // type mismatch, return missing - else -> hasMissing = true - } - } - - when { - hasMissing -> ExprValue.missingValue - hasNull -> ExprValue.nullValue - else -> ExprValue.newBoolean(true) - } - } - } - } - - internal open fun compileOr(expr: PartiqlAst.Expr.Or, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - // can't use the null propagation supplied by [ThunkFactory.thunkEnv] here because OR short-circuits on - // true values and *NOT* on NULL or MISSING - return when (compileOptions.typingMode) { - TypingMode.LEGACY -> - thunkFactory.thunkEnv(metas) thunk@{ env -> - var hasUnknowns = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - // How null-propagation works for OR is rather weird according to the SQL-92 spec. - // Nulls are propagated like other expressions only when none of the terms are TRUE. - // If any one of them is TRUE, then the entire expression evaluates to TRUE, i.e.: - // NULL OR TRUE -> TRUE - // NULL OR FALSE -> NULL - // (strange but true) - when { - currValue.isUnknown() -> hasUnknowns = true - currValue.booleanValue() -> return@thunk ExprValue.newBoolean(true) - } - } - - when (hasUnknowns) { - true -> ExprValue.nullValue - false -> ExprValue.newBoolean(false) - } - } - TypingMode.PERMISSIVE -> thunkFactory.thunkEnv(metas) thunk@{ env -> - var hasNull = false - var hasMissing = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - when (currValue.type) { - // Short circuit only if we encounter a known true value. - ExprValueType.BOOL -> if (currValue.booleanValue()) return@thunk ExprValue.newBoolean(true) - ExprValueType.NULL -> hasNull = true - else -> hasMissing = true // type mismatch, return missing. - } - } - - when { - hasMissing -> ExprValue.missingValue - hasNull -> ExprValue.nullValue - else -> ExprValue.newBoolean(false) - } - } - } - } - - private fun compileConcat(expr: PartiqlAst.Expr.Concat, metas: MetaContainer): ThunkEnv { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - val lType = lValue.type - val rType = rValue.type - - if (lType.isText && rType.isText) { - // null/missing propagation is handled before getting here - (lValue.stringValue() + rValue.stringValue()).exprValue() - } else { - err( - "Wrong argument type for ||", - ErrorCode.EVALUATOR_CONCAT_FAILED_DUE_TO_INCOMPATIBLE_TYPE, - errorContextFrom(metas).also { - it[Property.ACTUAL_ARGUMENT_TYPES] = listOf(lType, rType).toString() - }, - internal = false - ) - } - } - } - - internal open fun compileCall(expr: PartiqlAst.Expr.Call, metas: MetaContainer): ThunkEnv { - val funcArgThunks = compileAstExprs(expr.args) - val arity = funcArgThunks.size - val name = expr.funcName.text - return thunkFactory.thunkEnv(metas) { env -> - val args = funcArgThunks.map { thunk -> thunk(env) } - val argTypes = args.map { staticTypeFromExprValue(it) } - try { - val func = functionManager.get(name = name, arity = arity, args = argTypes) - val computeThunk = when (func.signature.unknownArguments) { - UnknownArguments.PROPAGATE -> thunkFactory.thunkEnvOperands(metas, funcArgThunks) { env, values -> - func.call(env.session, args) - } - UnknownArguments.PASS_THRU -> thunkFactory.thunkEnv(metas) { env -> - func.call(env.session, args) - } - } - checkIntegerOverflow(computeThunk, metas)(env) - } catch (e: FunctionNotFoundException) { - err( - "No such function: $name", - ErrorCode.EVALUATOR_NO_SUCH_FUNCTION, - errorContextFrom(metas).also { - it[Property.FUNCTION_NAME] = name - }, - internal = false - ) - } catch (e: ArityMismatchException) { - val (minArity, maxArity) = e.arity - val errorContext = errorContextFrom(metas).also { - it[Property.FUNCTION_NAME] = name - it[Property.EXPECTED_ARITY_MIN] = minArity - it[Property.EXPECTED_ARITY_MAX] = maxArity - it[Property.ACTUAL_ARITY] = arity - } - err( - "No function found with matching arity: $name", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - errorContext, - internal = false - ) - } - } - } - - internal open fun compileLit(expr: PartiqlAst.Expr.Lit, metas: MetaContainer): ThunkEnv { - val value = ExprValue.of(expr.value.toIonValue(ion)) - - return thunkFactory.thunkEnv(metas) { value } - } - - private fun compileMissing(metas: MetaContainer): ThunkEnv = - thunkFactory.thunkEnv(metas) { ExprValue.missingValue } - - internal open fun compileId(expr: PartiqlAst.Expr.Id, metas: MetaContainer): ThunkEnv { - val uniqueNameMeta = metas[UniqueNameMeta.TAG] as? UniqueNameMeta - val fromSourceNames = currentCompilationContext.fromSourceNames - - return when (uniqueNameMeta) { - null -> { - val bindingName = BindingName(expr.name.text, expr.case.toBindingCase()) - val evalVariableReference = when (compileOptions.undefinedVariable) { - UndefinedVariableBehavior.ERROR -> - thunkFactory.thunkEnv(metas) { env -> - when (val value = env.current[bindingName]) { - null -> { - if (fromSourceNames.any { bindingName.isEquivalentTo(it) }) { - err( - "Variable not in GROUP BY or aggregation function: ${bindingName.name}", - ErrorCode.EVALUATOR_VARIABLE_NOT_INCLUDED_IN_GROUP_BY, - errorContextFrom(metas).also { - it[Property.BINDING_NAME] = bindingName.name - }, - internal = false - ) - } else { - val (errorCode, hint) = when (expr.case) { - is PartiqlAst.CaseSensitivity.CaseSensitive -> - Pair( - ErrorCode.EVALUATOR_QUOTED_BINDING_DOES_NOT_EXIST, - " $UNBOUND_QUOTED_IDENTIFIER_HINT" - ) - is PartiqlAst.CaseSensitivity.CaseInsensitive -> - Pair(ErrorCode.EVALUATOR_BINDING_DOES_NOT_EXIST, "") - } - err( - "No such binding: ${bindingName.name}.$hint", - errorCode, - errorContextFrom(metas).also { - it[Property.BINDING_NAME] = bindingName.name - }, - internal = false - ) - } - } - else -> value - } - } - UndefinedVariableBehavior.MISSING -> - thunkFactory.thunkEnv(metas) { env -> - env.current[bindingName] ?: ExprValue.missingValue - } - } - - when (expr.qualifier) { - is PartiqlAst.ScopeQualifier.Unqualified -> evalVariableReference - is PartiqlAst.ScopeQualifier.LocalsFirst -> thunkFactory.thunkEnv(metas) { env -> - evalVariableReference(env.flipToLocals()) - } - } - } - else -> { - val bindingName = BindingName(uniqueNameMeta.uniqueName, BindingCase.SENSITIVE) - - thunkFactory.thunkEnv(metas) { env -> - // Unique identifiers are generated by the compiler and should always resolve. If they - // don't for some reason we have a bug. - env.current[bindingName] ?: err( - "Uniquely named binding \"${bindingName.name}\" does not exist for some reason", - ErrorCode.INTERNAL_ERROR, - errorContextFrom(metas), - internal = true - ) - } - } - } - } - - private fun compileParameter(expr: PartiqlAst.Expr.Parameter, metas: MetaContainer): ThunkEnv { - val ordinal = expr.index.value.toInt() - val index = ordinal - 1 - - return { env -> - val params = env.session.parameters - if (params.size <= index) { - err( - "Unbound parameter for ordinal: $ordinal", - ErrorCode.EVALUATOR_UNBOUND_PARAMETER, - errorContextFrom(metas).also { - it[Property.EXPECTED_PARAMETER_ORDINAL] = ordinal - it[Property.BOUND_PARAMETER_COUNT] = params.size - }, - internal = false - ) - } - - params[index] - } - } - - /** - * Returns a lambda that implements the `IS` operator type check according to the current - * [TypedOpBehavior]. - */ - private fun makeIsCheck( - staticType: SingleType, - typedOpParameter: TypedOpParameter, - metas: MetaContainer - ): (ExprValue) -> Boolean { - return when (compileOptions.typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> { expValue: ExprValue -> - staticType.allTypes.any { - val matchesStaticType = try { - isInstance(expValue, it) - } catch (e: UnsupportedTypeCheckException) { - err( - e.message!!, - ErrorCode.UNIMPLEMENTED_FEATURE, - errorContextFrom(metas), - internal = true - ) - } - - when { - !matchesStaticType -> false - else -> when (val validator = typedOpParameter.validationThunk) { - null -> true - else -> validator(expValue) - } - } - } - } - } - } - - internal open fun compileIs(expr: PartiqlAst.Expr.IsType, metas: MetaContainer): ThunkEnv { - val expThunk = compileAstExpr(expr.value) - val typedOpParameter = expr.type.toTypedOpParameter() - if (typedOpParameter.staticType is AnyType) { - return thunkFactory.thunkEnv(metas) { ExprValue.newBoolean(true) } - } - if (compileOptions.typedOpBehavior == TypedOpBehavior.HONOR_PARAMETERS && expr.type is PartiqlAst.Type.FloatType && (expr.type as PartiqlAst.Type.FloatType).precision != null) { - err( - "FLOAT precision parameter is unsupported", - ErrorCode.SEMANTIC_FLOAT_PRECISION_UNSUPPORTED, - errorContextFrom(expr.type.metas), - internal = false - ) - } - - val typeMatchFunc = when (val staticType = typedOpParameter.staticType) { - is SingleType -> makeIsCheck(staticType, typedOpParameter, metas) - is AnyOfType -> staticType.types.map { childType -> - when (childType) { - is SingleType -> makeIsCheck(childType, typedOpParameter, metas) - else -> err( - "Union type cannot have ANY or nested AnyOf type for IS", - ErrorCode.SEMANTIC_UNION_TYPE_INVALID, - errorContextFrom(metas), - internal = true - ) - } - }.let { typeMatchFuncs -> - { expValue: ExprValue -> typeMatchFuncs.any { func -> func(expValue) } } - } - is AnyType -> throw IllegalStateException("Unexpected ANY type in IS compilation") - } - - return thunkFactory.thunkEnv(metas) { env -> - val expValue = expThunk(env) - typeMatchFunc(expValue).exprValue() - } - } - - private fun compileCastHelper(value: PartiqlAst.Expr, asType: PartiqlAst.Type, metas: MetaContainer): ThunkEnv { - val expThunk = compileAstExpr(value) - val typedOpParameter = asType.toTypedOpParameter() - if (typedOpParameter.staticType is AnyType) { - return expThunk - } - if (compileOptions.typedOpBehavior == TypedOpBehavior.HONOR_PARAMETERS && asType is PartiqlAst.Type.FloatType && asType.precision != null) { - err( - "FLOAT precision parameter is unsupported", - ErrorCode.SEMANTIC_FLOAT_PRECISION_UNSUPPORTED, - errorContextFrom(asType.metas), - internal = false - ) - } - - fun typeOpValidate( - value: ExprValue, - castOutput: ExprValue, - typeName: String, - locationMeta: SourceLocationMeta? - ) { - if (typedOpParameter.validationThunk?.let { it(castOutput) } == false) { - val errorContext = PropertyValueMap().also { - it[Property.CAST_FROM] = value.type.toString() - it[Property.CAST_TO] = typeName - } - - locationMeta?.let { fillErrorContext(errorContext, it) } - - err( - "Validation failure for $asType", - ErrorCode.EVALUATOR_CAST_FAILED, - errorContext, - internal = false - ) - } - } - - fun singleTypeCastFunc(singleType: SingleType): CastFunc { - val locationMeta = metas.sourceLocationMeta - return { value -> - val castOutput = value.cast( - singleType, - compileOptions.typedOpBehavior, - locationMeta, - compileOptions.defaultTimezoneOffset - ) - typeOpValidate(value, castOutput, getRuntimeType(singleType).toString(), locationMeta) - castOutput - } - } - - fun compileSingleTypeCast(singleType: SingleType): ThunkEnv { - val castFunc = singleTypeCastFunc(singleType) - // We do not use thunkFactory here because we want to explicitly avoid - // the optional evaluation-time type check for CAN_CAST below. - // Can cast needs that returns false if an - // exception is thrown during a normal cast operation. - return { env -> - val valueToCast = expThunk(env) - castFunc(valueToCast) - } - } - - fun compileCast(type: StaticType): ThunkEnv = when (type) { - is SingleType -> compileSingleTypeCast(type) - is AnyOfType -> { - val locationMeta = metas.sourceLocationMeta - val castTable = AnyOfCastTable(type, metas, ::singleTypeCastFunc); - - // We do not use thunkFactory here because we want to explicitly avoid - // the optional evaluation-time type check for CAN_CAST below. - // note that this would interfere with the error handling for can_cast that returns false if an - // exception is thrown during a normal cast operation. - { env -> - val sourceValue = expThunk(env) - castTable.cast(sourceValue).also { - // TODO put the right type name here - typeOpValidate(sourceValue, it, "", locationMeta) - } - } - } - is AnyType -> throw IllegalStateException("Unreachable code") - } - - return compileCast(typedOpParameter.staticType) - } - - private fun compileCast(expr: PartiqlAst.Expr.Cast, metas: MetaContainer): ThunkEnv = - thunkFactory.thunkEnv(metas, compileCastHelper(expr.value, expr.asType, metas)) - - internal open fun compileCanCast(expr: PartiqlAst.Expr.CanCast, metas: MetaContainer): ThunkEnv { - val typedOpParameter = expr.asType.toTypedOpParameter() - if (typedOpParameter.staticType is AnyType) { - return thunkFactory.thunkEnv(metas) { ExprValue.newBoolean(true) } - } - - val expThunk = compileAstExpr(expr.value) - - // TODO consider making this more efficient by not directly delegating to CAST - // TODO consider also making the operand not double evaluated (e.g. having expThunk memoize) - val castThunkEnv = compileCastHelper(expr.value, expr.asType, expr.metas) - return thunkFactory.thunkEnv(metas) { env -> - val sourceValue = expThunk(env) - try { - when { - // NULL/MISSING can cast to anything as themselves - sourceValue.isUnknown() -> ExprValue.newBoolean(true) - else -> { - val castedValue = castThunkEnv(env) - when { - // NULL/MISSING from cast is a permissive way to signal failure - castedValue.isUnknown() -> ExprValue.newBoolean(false) - else -> ExprValue.newBoolean(true) - } - } - } - } catch (e: EvaluationException) { - if (e.internal) { - throw e - } - ExprValue.newBoolean(false) - } - } - } - - internal open fun compileCanLosslessCast(expr: PartiqlAst.Expr.CanLosslessCast, metas: MetaContainer): ThunkEnv { - val typedOpParameter = expr.asType.toTypedOpParameter() - if (typedOpParameter.staticType is AnyType) { - return thunkFactory.thunkEnv(metas) { ExprValue.newBoolean(true) } - } - - val expThunk = compileAstExpr(expr.value) - - // TODO consider making this more efficient by not directly delegating to CAST - val castThunkEnv = compileCastHelper(expr.value, expr.asType, expr.metas) - return thunkFactory.thunkEnv(metas) { env -> - val sourceValue = expThunk(env) - val sourceType = staticTypeFromExprValue(sourceValue) - - fun roundTrip(): ExprValue { - val castedValue = castThunkEnv(env) - - val locationMeta = metas.sourceLocationMeta - fun castFunc(singleType: SingleType) = - { value: ExprValue -> - value.cast( - singleType, - compileOptions.typedOpBehavior, - locationMeta, - compileOptions.defaultTimezoneOffset - ) - } - - val roundTripped = when (sourceType) { - is SingleType -> castFunc(sourceType)(castedValue) - is AnyOfType -> { - val castTable = AnyOfCastTable(sourceType, metas, ::castFunc) - castTable.cast(sourceValue) - } - // Should not be possible - is AnyType -> throw IllegalStateException("ANY type is not configured correctly in compiler") - } - - val lossless = sourceValue.exprEquals(roundTripped) - return ExprValue.newBoolean(lossless) - } - - try { - when (sourceValue.type) { - // NULL can cast to anything as itself - ExprValueType.NULL -> ExprValue.newBoolean(true) - - // Short-circuit timestamp -> date roundtrip if precision isn't [Timestamp.Precision.DAY] or - // [Timestamp.Precision.MONTH] or [Timestamp.Precision.YEAR] - ExprValueType.TIMESTAMP -> when (typedOpParameter.staticType) { - StaticType.DATE -> when (sourceValue.timestampValue().precision) { - Timestamp.Precision.DAY, Timestamp.Precision.MONTH, Timestamp.Precision.YEAR -> roundTrip() - else -> ExprValue.newBoolean(false) - } - StaticType.TIME -> ExprValue.newBoolean(false) - else -> roundTrip() - } - - // For all other cases, attempt a round-trip of the value through the source and dest types - else -> roundTrip() - } - } catch (e: EvaluationException) { - if (e.internal) { - throw e - } - ExprValue.newBoolean(false) - } - } - } - - internal open fun compileSimpleCase(expr: PartiqlAst.Expr.SimpleCase, metas: MetaContainer): ThunkEnv { - val valueThunk = compileAstExpr(expr.expr) - val branchThunks = expr.cases.pairs.map { Pair(compileAstExpr(it.first), compileAstExpr(it.second)) } - val elseThunk = when (val default = expr.default) { - null -> thunkFactory.thunkEnv(metas) { ExprValue.nullValue } - else -> compileAstExpr(default) - } - - return thunkFactory.thunkEnv(metas) thunk@{ env -> - val caseValue = valueThunk(env) - // if the case value is unknown then we can short-circuit to the elseThunk directly - when { - caseValue.isUnknown() -> elseThunk(env) - else -> { - branchThunks.forEach { bt -> - val branchValue = bt.first(env) - // Just skip any branch values that are unknown, which we consider the same as false here. - when { - branchValue.isUnknown() -> { /* intentionally blank */ - } - else -> { - if (caseValue.exprEquals(branchValue)) { - return@thunk bt.second(env) - } - } - } - } - } - } - elseThunk(env) - } - } - - internal open fun compileSearchedCase(expr: PartiqlAst.Expr.SearchedCase, metas: MetaContainer): ThunkEnv { - val branchThunks = expr.cases.pairs.map { compileAstExpr(it.first) to compileAstExpr(it.second) } - val elseThunk = when (val default = expr.default) { - null -> thunkFactory.thunkEnv(metas) { ExprValue.nullValue } - else -> compileAstExpr(default) - } - - return when (compileOptions.typingMode) { - TypingMode.LEGACY -> thunkFactory.thunkEnv(metas) thunk@{ env -> - branchThunks.forEach { bt -> - val conditionValue = bt.first(env) - // Any unknown value is considered the same as false. - // Note that .booleanValue() here will throw an EvaluationException if - // the data type is not boolean. - // TODO: .booleanValue does not have access to metas, so the EvaluationException is reported to be - // at the line & column of the CASE statement, not the predicate, unfortunately. - if (conditionValue.isNotUnknown() && conditionValue.booleanValue()) { - return@thunk bt.second(env) - } - } - elseThunk(env) - } - // Permissive mode propagates data type mismatches as MISSING, which is - // equivalent to false for searched CASE predicates. To simplify this, - // all we really need to do is consider any non-boolean result from the - // predicate to be false. - TypingMode.PERMISSIVE -> thunkFactory.thunkEnv(metas) thunk@{ env -> - branchThunks.forEach { bt -> - val conditionValue = bt.first(env) - if (conditionValue.type == ExprValueType.BOOL && conditionValue.booleanValue()) { - return@thunk bt.second(env) - } - } - elseThunk(env) - } - } - } - - private fun compileStruct(expr: PartiqlAst.Expr.Struct, metas: MetaContainer): ThunkEnv { - class StructFieldThunks(val nameThunk: ThunkEnv, val valueThunk: ThunkEnv) - - val fieldThunks = expr.fields.map { - StructFieldThunks(compileAstExpr(it.first), compileAstExpr(it.second)) - } - - return when (compileOptions.typingMode) { - TypingMode.LEGACY -> thunkFactory.thunkEnv(metas) { env -> - val seq = fieldThunks.map { - val nameValue = it.nameThunk(env) - if (!nameValue.type.isText) { - // Evaluation time error where variable reference might be evaluated to non-text struct field. - err( - "Found struct field key to be of type ${nameValue.type}", - ErrorCode.EVALUATOR_NON_TEXT_STRUCT_FIELD_KEY, - errorContextFrom(metas.sourceLocationMeta).also { pvm -> - pvm[Property.ACTUAL_TYPE] = nameValue.type.toString() - }, - internal = false - ) - } - it.valueThunk(env).namedValue(nameValue) - }.asSequence() - createStructExprValue(seq, StructOrdering.ORDERED) - } - TypingMode.PERMISSIVE -> thunkFactory.thunkEnv(metas) { env -> - val seq = fieldThunks.map { it.valueThunk(env).namedValue(it.nameThunk(env)) }.asSequence() - .filter { - // fields with non-text keys are filtered out of the struct - val keyType = it.name?.type - keyType != null && keyType.isText - } - createStructExprValue(seq, StructOrdering.ORDERED) - } - } - } - - /** - * Compiles a Session Attribute. Currently supports CURRENT_USER. - */ - private fun compileSessionAttribute(attr: PartiqlAst.Expr.SessionAttribute, metas: MetaContainer): ThunkEnv { - return when (attr.value.text.lowercase()) { - "current_user" -> compileCurrentUser(metas) - else -> err( - message = "${attr.value.text} is not a valid session attribute.", - errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE, - errorContext = errorContextFrom(metas), - internal = false - ) - } - } - - /** - * Looks up CURRENT_USER within Session's Context - */ - private fun compileCurrentUser(metas: MetaContainer): ThunkEnv { - return thunkFactory.thunkEnv(metas) { env -> - when (val user = env.session.context[EvaluationSession.Constants.CURRENT_USER_KEY]) { - null -> ExprValue.newNull(IonType.STRING) - is String -> ExprValue.newString(user) - else -> err( - message = "CURRENT_USER must be either a STRING or NULL.", - errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE, - errorContext = errorContextFrom(metas), - internal = false - ) - } - } - } - - private fun compileSeq(seqType: ExprValueType, itemExprs: List, metas: MetaContainer): ThunkEnv { - require(seqType.isSequence) { "seqType must be a sequence!" } - - val itemThunks = compileAstExprs(itemExprs) - - val makeItemThunkSequence = when (seqType) { - ExprValueType.BAG -> { env: Environment -> - itemThunks.asSequence().map { itemThunk -> - // call to unnamedValue() makes sure we don't expose any underlying value name/ordinal - itemThunk(env).unnamedValue() - } - } - else -> { env: Environment -> - itemThunks.asSequence().mapIndexed { i, itemThunk -> itemThunk(env).namedValue(i.exprValue()) } - } - } - - return thunkFactory.thunkEnv(metas) { env -> - when (seqType) { - ExprValueType.BAG -> ExprValue.newBag(makeItemThunkSequence(env)) - ExprValueType.LIST -> ExprValue.newList(makeItemThunkSequence(env)) - ExprValueType.SEXP -> ExprValue.newSexp(makeItemThunkSequence(env)) - else -> error("sequence type required") - } - } - } - - private fun compileBagOp(node: PartiqlAst.Expr.BagOp, metas: MetaContainer): ThunkEnv { - val lhs = compileAstExpr(node.operands[0]) - val rhs = compileAstExpr(node.operands[1]) - val op = ExprValueBagOp.create(node.op, metas) - return thunkFactory.thunkEnv(metas) { env -> - val l = lhs(env) - val r = rhs(env) - val result = when (node.quantifier) { - is PartiqlAst.SetQuantifier.All -> op.eval(l, r) - is PartiqlAst.SetQuantifier.Distinct -> op.eval(l, r).distinct() - } - ExprValue.newBag(result) - } - } - - private fun compileGraphMatch(node: PartiqlAst.Expr.GraphMatch, metas: MetaContainer): ThunkEnv { - val graphExpr = compileAstExpr(node.expr) - val pattern = GpmlTranslator.translateGpmlPattern(node.gpmlPattern) - val nonGraphOutcome = - when (compileOptions.typingMode) { - TypingMode.LEGACY -> { g: ExprValue -> - err( - message = "Lhs of MATCH must be a graph, but got: $g.", - errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, - errorContext = errorContextFrom(metas), - internal = false - ) - } - TypingMode.PERMISSIVE -> { _ -> ExprValue.missingValue } - } - return thunkFactory.thunkEnv(metas) { env -> - val g = graphExpr(env) - when (g.type) { - ExprValueType.GRAPH -> { - val graph = g.graphValue - val matchResult = GraphEngine.evaluate(graph, pattern) - ExprValue.newBag(matchResult2Table(matchResult)) - } - else -> nonGraphOutcome(g) - } - } - } - - /** Given the result of a graph pattern match, read off payloads at bound pattern variables - * for further consumption in PartiQL, as a table, i.e. a collection of PartiQL structs. - */ - private fun matchResult2Table(result: org.partiql.lang.graph.MatchResult): Sequence = - result.result.asSequence().map { map2struct(strides2map(result.specs, it)) } - - /** Given a single match result, produces a map that associates each binder variable - * with its matched graph element. - */ - private fun strides2map(specs: List, strides: List): Map { - check(specs.size == strides.size) - val elemSpecs = specs.flatMap { it.elems } - val elems = strides.flatMap { it.elems } - check(elemSpecs.size == elems.size) - val m = mutableMapOf() - for ((s, e) in elemSpecs zip elems) { - check((s is NodeSpec && e is Graph.Node) || (s is EdgeSpec && e is Graph.Edge)) - // Populate the map for each binder variable in the spec: - if (s.binder != null) { - val v = s.binder!! - if (m.containsKey(v)) { - val d = m[v]!! - // This should not happen if the joins were done properly: - // a multiply-occurring variable should bind to the same element at each occurrence. - // (Note: only singleton variables are currently supported; no group variables yet.) - if (d != e) error( - """Bug: For variable $v in a strides match, encountered a binding to $e[${e.payload}], - | but $v's previous occurrence was bound to $d[${d.payload}] """.trimMargin() - ) - } else { - m.put(v, e) - } - } - } - return m.toMap() - } - - private fun map2struct(m: Map): ExprValue = - ExprValue.newStruct( - m.entries.map { it.value.payload.namedValue(ExprValue.newSymbol(it.key)) }, - StructOrdering.UNORDERED - ) - - private fun evalLimit(limitThunk: ThunkEnv, env: Environment, limitLocationMeta: SourceLocationMeta?): Long { - val limitExprValue = limitThunk(env) - - if (limitExprValue.type != ExprValueType.INT) { - err( - "LIMIT value was not an integer", - ErrorCode.EVALUATOR_NON_INT_LIMIT_VALUE, - errorContextFrom(limitLocationMeta).also { - it[Property.ACTUAL_TYPE] = limitExprValue.type.toString() - }, - internal = false - ) - } - - val originalLimitValue = limitExprValue.numberValue() - val limitValue = originalLimitValue.toLong() - if (originalLimitValue != limitValue as Number) { // Make sure `Number.toLong()` is a lossless transformation - err( - "Integer exceeds Long.MAX_VALUE provided as LIMIT value", - ErrorCode.INTERNAL_ERROR, - errorContextFrom(limitLocationMeta), - internal = true - ) - } - - if (limitValue < 0) { - err( - "negative LIMIT", - ErrorCode.EVALUATOR_NEGATIVE_LIMIT, - errorContextFrom(limitLocationMeta), - internal = false - ) - } - - // we can't use the Kotlin's Sequence.take(n) for this since it accepts only an integer. - // this references [Sequence.take(count: Long): Sequence] defined in [org.partiql.util]. - return limitValue - } - - private fun evalOffset(offsetThunk: ThunkEnv, env: Environment, offsetLocationMeta: SourceLocationMeta?): Long { - val offsetExprValue = offsetThunk(env) - - if (offsetExprValue.type != ExprValueType.INT) { - err( - "OFFSET value was not an integer", - ErrorCode.EVALUATOR_NON_INT_OFFSET_VALUE, - errorContextFrom(offsetLocationMeta).also { - it[Property.ACTUAL_TYPE] = offsetExprValue.type.toString() - }, - internal = false - ) - } - - val originalOffsetValue = offsetExprValue.numberValue() - val offsetValue = originalOffsetValue.toLong() - if (originalOffsetValue != offsetValue as Number) { // Make sure `Number.toLong()` is a lossless transformation - err( - "Integer exceeds Long.MAX_VALUE provided as OFFSET value", - ErrorCode.INTERNAL_ERROR, - errorContextFrom(offsetLocationMeta), - internal = true - ) - } - - if (offsetValue < 0) { - err( - "negative OFFSET", - ErrorCode.EVALUATOR_NEGATIVE_OFFSET, - errorContextFrom(offsetLocationMeta), - internal = false - ) - } - - return offsetValue - } - - internal open fun compileWhere(node: PartiqlAst.Expr): ThunkEnv { - return compileAstExpr(node) - } - - internal open fun compileHaving(node: PartiqlAst.Expr): ThunkEnv { - return compileAstExpr(node) - } - - internal open fun compileSelect(selectExpr: PartiqlAst.Expr.Select, metas: MetaContainer): ThunkEnv { - - // Get all the FROM source aliases and LET bindings for binding error checks - val fold = object : PartiqlAst.VisitorFold>() { - /** Store all the visited FROM source aliases in the accumulator */ - override fun visitFromSourceScan(node: PartiqlAst.FromSource.Scan, accumulator: Set): Set { - val aliases = listOfNotNull(node.asAlias?.text, node.atAlias?.text, node.byAlias?.text) - return accumulator + aliases.toSet() - } - - override fun visitLetBinding(node: PartiqlAst.LetBinding, accumulator: Set): Set { - val aliases = listOfNotNull(node.name.text) - return accumulator + aliases - } - - /** Prevents visitor from recursing into nested select statements */ - override fun walkExprSelect(node: PartiqlAst.Expr.Select, accumulator: Set): Set { - return accumulator - } - } - - val allFromSourceAliases = fold.walkFromSource(selectExpr.from, emptySet()) - .union(selectExpr.fromLet?.let { fold.walkLet(it, emptySet()) } ?: emptySet()) - - return nestCompilationContext(ExpressionContext.NORMAL, emptySet()) { - val fromSourceThunks = compileFromSources(selectExpr.from) - val letSourceThunks = selectExpr.fromLet?.let { compileLetSources(it) } - val compiledWhere = selectExpr.where?.let { compileWhere(it) } - val sourceThunks = compileFromLetWhere(fromSourceThunks, letSourceThunks, compiledWhere) - - val orderByThunk = selectExpr.order?.let { compileOrderByExpression(it.sortSpecs) } - val orderByLocationMeta = selectExpr.order?.metas?.sourceLocation - - val offsetThunk = selectExpr.offset?.let { compileAstExpr(it) } - val offsetLocationMeta = selectExpr.offset?.metas?.sourceLocation - - val limitThunk = selectExpr.limit?.let { compileAstExpr(it) } - val limitLocationMeta = selectExpr.limit?.metas?.sourceLocation - - val excludeExprs = selectExpr.excludeClause?.let { compileExcludeClause(it) } - - fun rowsWithOffsetAndLimit(rows: Sequence, env: Environment): Sequence { - val rowsWithOffset = when (offsetThunk) { - null -> rows - else -> rows.drop(evalOffset(offsetThunk, env, offsetLocationMeta)) - } - return when (limitThunk) { - null -> rowsWithOffset - else -> rowsWithOffset.take(evalLimit(limitThunk, env, limitLocationMeta)) - } - } - - // Returns a thunk that invokes [sourceThunks], and invokes [projectionThunk] to perform the projection. - fun getQueryThunk(selectProjectionThunk: ThunkEnvValue>): ThunkEnv { - val groupByItems = selectExpr.group?.keyList?.keys ?: listOf() - val groupAsName = selectExpr.group?.groupAsAlias - - val aggregateListMeta = metas[AggregateCallSiteListMeta.TAG] as AggregateCallSiteListMeta? - val hasAggregateCallSites = aggregateListMeta?.aggregateCallSites?.any() ?: false - - val queryThunk = when { - groupByItems.isEmpty() && !hasAggregateCallSites -> - // Grouping is not needed -- simply project the results from the FROM clause directly. - thunkFactory.thunkEnv(metas) { env -> - - val sourcedRows = sourceThunks(env).map { - interruptionCheck() - it - } - - val orderedRows = when (orderByThunk) { - null -> sourcedRows - else -> evalOrderBy(sourcedRows, orderByThunk, orderByLocationMeta) - } - - val excludedBindings = when (excludeExprs) { - null -> orderedRows - else -> { - orderedRows.map { row -> - FromProduction(values = row.values, env = evalExclude(row.env, excludeExprs)) - } - } - } - - val projectedRows = excludedBindings.map { (joinedValues, projectEnv) -> - selectProjectionThunk(projectEnv, joinedValues) - } - - val quantifiedRows = when (selectExpr.setq ?: PartiqlAst.SetQuantifier.All()) { - // wrap the ExprValue to use ExprValue.equals as the equality - is PartiqlAst.SetQuantifier.Distinct -> projectedRows.distinct() - is PartiqlAst.SetQuantifier.All -> projectedRows - }.let { rowsWithOffsetAndLimit(it, env) } - - // if order by is specified, return list otherwise bag - when (orderByThunk) { - null -> ExprValue.newBag( - quantifiedRows.map { - // TODO make this expose the ordinal for ordered sequences - // make sure we don't expose the underlying value's name out of a SELECT - it.unnamedValue() - } - ) - else -> ExprValue.newList(quantifiedRows.map { it.unnamedValue() }) - } - } - else -> { - // Grouping is needed - - class CompiledAggregate(val factory: ExprAggregatorFactory, val argThunk: ThunkEnv) - - // These aggregate call sites are collected in [AggregateSupportVisitorTransform]. - val compiledAggregates = aggregateListMeta?.aggregateCallSites?.map { it -> - val funcName = it.funcName.text - CompiledAggregate( - factory = getAggregatorFactory( - funcName, - it.setq, - it.metas - ), - argThunk = compileAstExpr(it.arg) - ) - } - - // This closure will be invoked to create and initialize a [RegisterBank] for new [Group]s. - val createRegisterBank: () -> RegisterBank = when (aggregateListMeta) { - // If there are no aggregates, create an empty register bank - null -> { -> // -> here forces this block to be a lambda - RegisterBank(0) - } - else -> { -> - RegisterBank(aggregateListMeta.aggregateCallSites.size).apply { - // set up aggregate registers - compiledAggregates?.forEachIndexed { index, ca -> - set(index, ca.factory.create()) - } - } - } - } - - when { - groupByItems.isEmpty() -> { // There are aggregates but no group by items - // Create a closure that groups all the rows in the FROM source into a single group. - thunkFactory.thunkEnv(metas) { env -> - // Evaluate the FROM clause - val orderedRows = when (orderByThunk) { - null -> sourceThunks(env) - else -> evalOrderBy(sourceThunks(env), orderByThunk, orderByLocationMeta) - } - - val excludedBindings = when (excludeExprs) { - null -> orderedRows - else -> { - orderedRows.map { row -> - FromProduction(values = row.values, env = evalExclude(row.env, excludeExprs)) - } - } - } - - val fromProductions: Sequence = - rowsWithOffsetAndLimit(excludedBindings, env) - val registers = createRegisterBank() - - // note: the group key can be anything here because we only ever have a single - // group when aggregates are used without GROUP BY expression - val syntheticGroup = Group(ExprValue.nullValue, registers) - - // iterate over the values from the FROM clause and populate our - // aggregate register values. - fromProductions.forEach { fromProduction -> - interruptionCheck() - compiledAggregates?.forEachIndexed { index, ca -> - registers[index].aggregator.next(ca.argThunk(fromProduction.env)) - } - } - - // generate the final group projection - val groupResult = selectProjectionThunk( - env.copy(currentGroup = syntheticGroup), - listOf(syntheticGroup.key) - ) - - // if order by is specified, return list otherwise bag - when (orderByThunk) { - null -> ExprValue.newBag(listOf(groupResult).asSequence()) - else -> ExprValue.newList(listOf(groupResult).asSequence()) - } - } - } - else -> { - // There are GROUP BY expressions and possibly aggregates. (The most complex scenario.) - - val compiledGroupByItems = compileGroupByExpressions(groupByItems) - - val groupKeyThunk = compileGroupKeyThunk(compiledGroupByItems, metas) - - // Memoize a [BindingName] and an [ExprValue] containing the name of each FromSource - // otherwise we would be re-creating them for every row. - val fromSourceBindingNames = fromSourceThunks.map { - FromSourceBindingNamePair( - BindingName(it.alias.asName, BindingCase.SENSITIVE), - it.alias.asName.exprValue() - ) - } - - val havingThunk = selectExpr.having?.let { compileHaving(it) } - - val getGroupEnv: (Environment, Group) -> Environment = - createGetGroupEnvClosure(groupAsName) - - thunkFactory.thunkEnv(metas) { env -> - // Execute the FROM clause - val fromProductions: Sequence = sourceThunks(env) - - // For each "row" in the output of the FROM clause - fromProductions.forEach { fromProduction -> - - // Determine the group key for this value - val groupKey = groupKeyThunk(fromProduction.env) - - // look up existing group using group key (this is slow) - // We can't do that yet because ExprValue does not implement .hashCode() - // and .equals() - val group: Group = env.groups.getOrPut(groupKey) { - // An existing group was not found so create a new one - Group(groupKey, createRegisterBank()) - } - - compiledAggregates!!.forEachIndexed { index, ca -> - group.registers[index].aggregator.next(ca.argThunk(fromProduction.env)) - } - - groupAsName.run { - val seq = fromSourceBindingNames.asSequence().map { pair -> - ( - fromProduction.env.current[pair.bindingName] ?: errNoContext( - "Could not resolve from source binding name during group as variable mapping", - errorCode = ErrorCode.INTERNAL_ERROR, - internal = true - ) - ).namedValue(pair.nameExprValue) - }.asSequence() - - group.groupValues.add(createStructExprValue(seq, StructOrdering.UNORDERED)) - } - } - - val groupByEnvValuePairs = env.groups.mapNotNull { g -> getGroupEnv(env, g.value) to g.value }.asSequence() - val orderedGroupEnvPairs = when (orderByThunk) { - null -> groupByEnvValuePairs - else -> evalOrderBy(groupByEnvValuePairs, orderByThunk, orderByLocationMeta) - } - - // apply HAVING row filter - val havingGroupEnvPairs = when (havingThunk) { - null -> orderedGroupEnvPairs - else -> { - orderedGroupEnvPairs.filter { (groupByEnv, _) -> - val havingClauseResult = havingThunk(groupByEnv) - havingClauseResult.isNotUnknown() && havingClauseResult.booleanValue() - } - } - } - - // apply EXCLUDE expressions - val excludedBindings = when (excludeExprs) { - null -> havingGroupEnvPairs - else -> { - havingGroupEnvPairs.map { (groupByEnv, currentGroup) -> - Pair(evalExclude(groupByEnv, excludeExprs), currentGroup) - } - } - } - // generate the final group by projection - val projectedRows = excludedBindings.map { (groupByEnv, currentGroup) -> - selectProjectionThunk(groupByEnv, listOf(currentGroup.key)) - }.let { rowsWithOffsetAndLimit(it, env) } - - // if order by is specified, return list otherwise bag - when (orderByThunk) { - null -> ExprValue.newBag(projectedRows) - else -> ExprValue.newList(projectedRows) - } - } - } - } - } - } // do normal map/filter - - return thunkFactory.thunkEnv(metas) { env -> - queryThunk(env.nestQuery()) - } - } // end of getQueryThunk(...) - - when (val project = selectExpr.project) { - is PartiqlAst.Projection.ProjectValue -> { - nestCompilationContext(ExpressionContext.NORMAL, allFromSourceAliases) { - val valueThunk = compileAstExpr(project.value) - getQueryThunk( - thunkFactory.thunkEnvValueList(project.metas) { env, _ -> - valueThunk( - env - ) - } - ) - } - } - is PartiqlAst.Projection.ProjectPivot -> { - val asExpr = project.value - val atExpr = project.key - nestCompilationContext(ExpressionContext.NORMAL, allFromSourceAliases) { - val asThunk = compileAstExpr(asExpr) - val atThunk = compileAstExpr(atExpr) - thunkFactory.thunkEnv(metas) { env -> - val excludedBindings = when (excludeExprs) { - null -> sourceThunks(env) - else -> { - sourceThunks(env).map { row -> - FromProduction(values = row.values, env = evalExclude(row.env, excludeExprs)) - } - } - } - val sourceValue = rowsWithOffsetAndLimit(excludedBindings, env) - val seq = sourceValue - .map { (_, env) -> Pair(asThunk(env), atThunk(env)) } - .filter { (name, _) -> name.type.isText } - .map { (name, value) -> value.namedValue(name) } - createStructExprValue(seq, StructOrdering.UNORDERED) - } - } - } - is PartiqlAst.Projection.ProjectList -> { - val items = project.projectItems - nestCompilationContext(ExpressionContext.SELECT_LIST, allFromSourceAliases) { - val projectionThunk: ThunkEnvValue> = - when { - items.filterIsInstance().any() -> { - errNoContext( - "Encountered a PartiqlAst.Projection.ProjectStar--did SelectStarVisitorTransform execute?", - errorCode = ErrorCode.INTERNAL_ERROR, - internal = true - ) - } - else -> { - val projectionElements = - compileSelectListToProjectionElements(project) - - val ordering = if (items.none { it is PartiqlAst.ProjectItem.ProjectAll }) - StructOrdering.ORDERED - else - StructOrdering.UNORDERED - - thunkFactory.thunkEnvValueList(project.metas) { env, _ -> - val columns = mutableListOf() - for (element in projectionElements) { - when (element) { - is SingleProjectionElement -> { - val eval = element.thunk(env) - columns.add(eval.namedValue(element.name)) - } - is MultipleProjectionElement -> { - for (projThunk in element.thunks) { - val value = projThunk(env) - if (value.type == ExprValueType.MISSING) continue - - val children = value.asSequence() - if (!children.any() || value.type.isSequence) { - val name = syntheticColumnName(columns.size).exprValue() - columns.add(value.namedValue(name)) - } else { - val valuesToProject = - when (compileOptions.projectionIteration) { - ProjectionIterationBehavior.FILTER_MISSING -> { - value.filter { it.type != ExprValueType.MISSING } - } - ProjectionIterationBehavior.UNFILTERED -> value - } - for (childValue in valuesToProject) { - val namedFacet = childValue.asFacet(Named::class.java) - val name = namedFacet?.name - ?: syntheticColumnName(columns.size).exprValue() - columns.add(childValue.namedValue(name)) - } - } - } - } - } - } - createStructExprValue(columns.asSequence(), ordering) - } - } - } - getQueryThunk(projectionThunk) - } // nestCompilationContext(ExpressionContext.SELECT_LIST) - } // is SelectProjectionList - is PartiqlAst.Projection.ProjectStar -> error("Internal Error: PartiqlAst.Projection.ProjectStar can only be wrapped in PartiqlAst.Projection.ProjectList") - } - } - } - - private fun compileGroupByExpressions(groupByItems: List): List = - groupByItems.map { - val alias = it.asAlias - ?: errNoContext( - "GroupByItem.asName was not specified", - errorCode = ErrorCode.INTERNAL_ERROR, - internal = true - ) - val uniqueName = - (alias.metas.find(UniqueNameMeta.TAG) as UniqueNameMeta?)?.uniqueName - - CompiledGroupByItem(alias.text.exprValue(), uniqueName, compileAstExpr(it.expr)) - } - - /** - * Represents all the compiled `EXCLUDE` paths that start with the same [CompiledExcludeExpr.root]. Notably, - * redundant paths (i.e. exclude paths that exclude values already excluded by other paths) will be removed. - */ - internal data class CompiledExcludeExpr(val root: PartiqlAst.Identifier, val exclusions: RemoveAndOtherSteps) - - /** - * Represents all the exclusions at the current level and other nested levels. - * - * The idea behind this data structure is that at a current level (i.e. path step index), we keep track of the - * - Exclude paths that have a final exclude step at the current level. This set of tuple attributes and collection - * indexes to remove at the current level is modeled as a set of exclude steps (i.e. [RemoveAndOtherSteps.remove]). - * - Exclude paths that have additional steps (their final step is at a deeper level). This is modeled as a mapping - * of exclude steps to other [RemoveAndOtherSteps] to group all exclude paths that share the same current step. - * - * For example, let's say we have exclude paths (ignoring the exclude path root) of - * a.b, - * x.y.z1, - * x.y.z2 - * ^ ^ ^ - * Level 1 2 3 - * - * These exclude paths would be converted to the following in [RemoveAndOtherSteps]. - * ``` - * // For demonstration purposes, the syntax '' corresponds to the exclude tuple attribute step of - * RemoveAndOtherSteps( // Level 1 (no exclusions at level 1) - * remove = emptySet(), - * steps = mapOf( - * 'a' to RemoveAndOtherSteps( // Level 2 for paths that have `'a'` in Level 1 - * remove = setOf('b'), // path `a.b` has final step at level 2 - * steps = emptyMap() - * ), - * 'x' to RemoveAndOtherSteps( // Level 2 for paths that have `'x'` in Level 1 - * remove = emptySet(), - * steps = mapOf( - * 'y' to RemoveAndOtherSteps( // Level 3 for paths that have `'y'` in Level 2 and `'x'` in Level 1 - * remove = setOf('z1', 'z2'), // paths `x.y.z1` and `x.y.z2` have final step at level 3 - * steps = emptyMap() - * ) - * ) - * ), - * ) - * ) - * ``` - */ - internal data class RemoveAndOtherSteps(val remove: Set, val steps: Map) { - companion object { - fun empty(): RemoveAndOtherSteps { - return RemoveAndOtherSteps(emptySet(), emptyMap()) - } - } - } - - private fun addToCompiledExcludeExprs(curCompiledExpr: RemoveAndOtherSteps, steps: List): RemoveAndOtherSteps { - // subsumption cases - // when steps.size == 1: possibly add to remove set - // when steps.size > 1: possibly add to steps map - val first = steps.first() - var entryRemove = curCompiledExpr.remove.toMutableSet() - var entrySteps = curCompiledExpr.steps.toMutableMap() - if (steps.size == 1) { - when (first) { - is PartiqlAst.ExcludeStep.ExcludeTupleAttr -> { - if (entryRemove.contains(PartiqlAst.build { excludeTupleWildcard() })) { - // contains wildcard; do not add; e.g. a.* and a.b -> keep a.* - } else { - // add to entries to remove - entryRemove.add(first) - // remove from other steps; e.g. a.b.c and a.b -> keep a.b - entrySteps.remove(first) - } - } - is PartiqlAst.ExcludeStep.ExcludeTupleWildcard -> { - entryRemove.add(first) - // remove all tuple attribute exclude steps - entryRemove = entryRemove.filterNot { - it is PartiqlAst.ExcludeStep.ExcludeTupleAttr - }.toMutableSet() - // remove all tuple attribute/wildcard exclude steps from deeper levels - entrySteps = entrySteps.filterNot { - it.key is PartiqlAst.ExcludeStep.ExcludeTupleAttr || it.key is PartiqlAst.ExcludeStep.ExcludeTupleWildcard - }.toMutableMap() - } - is PartiqlAst.ExcludeStep.ExcludeCollectionIndex -> { - if (entryRemove.contains(PartiqlAst.build { excludeCollectionWildcard() })) { - // contains wildcard; do not add; e.g a[*] and a[1] -> keep a[*] - } else { - // add to entries to remove - entryRemove.add(first) - // remove from other steps; e.g. a.b[2].c and a.b[2] -> keep a.b[2] - entrySteps.remove(first) - } - } - is PartiqlAst.ExcludeStep.ExcludeCollectionWildcard -> { - entryRemove.add(first) - // remove all collection index exclude steps - entryRemove = entryRemove.filterNot { - it is PartiqlAst.ExcludeStep.ExcludeCollectionIndex - }.toMutableSet() - // remove all collection index/wildcard exclude steps from deeper levels - entrySteps = entrySteps.filterNot { - it.key is PartiqlAst.ExcludeStep.ExcludeCollectionIndex || it.key is PartiqlAst.ExcludeStep.ExcludeCollectionWildcard - }.toMutableMap() - } - } - } else { - // remove at deeper level; need to check if first step is already removed in current step - when (first) { - is PartiqlAst.ExcludeStep.ExcludeTupleAttr -> { - if (entryRemove.contains(PartiqlAst.build { excludeTupleWildcard() }) || entryRemove.contains(first)) { - // remove set contains tuple wildcard or attr; do not add to other steps; - // e.g. a.* and a.b.c -> a.* - } else { - val existingEntry = entrySteps.getOrDefault(first, RemoveAndOtherSteps.empty()) - val newEntry = addToCompiledExcludeExprs(existingEntry, steps.drop(1)) - entrySteps[first] = newEntry - } - } - is PartiqlAst.ExcludeStep.ExcludeTupleWildcard -> { - if (entryRemove.any { it is PartiqlAst.ExcludeStep.ExcludeTupleWildcard }) { - // tuple wildcard at current level; do nothing - } else { - val existingEntry = entrySteps.getOrDefault(first, RemoveAndOtherSteps.empty()) - val newEntry = addToCompiledExcludeExprs(existingEntry, steps.drop(1)) - entrySteps[first] = newEntry - } - } - is PartiqlAst.ExcludeStep.ExcludeCollectionIndex -> { - if (entryRemove.contains(PartiqlAst.build { excludeCollectionWildcard() }) || entryRemove.contains(first)) { - // remove set contains collection wildcard or index; do not add to other steps; - // e.g. a[*] and a[*][1] -> a[*] - } else { - val existingEntry = entrySteps.getOrDefault(first, RemoveAndOtherSteps.empty()) - val newEntry = addToCompiledExcludeExprs(existingEntry, steps.drop(1)) - entrySteps[first] = newEntry - } - } - is PartiqlAst.ExcludeStep.ExcludeCollectionWildcard -> { - if (entryRemove.any { it is PartiqlAst.ExcludeStep.ExcludeCollectionWildcard }) { - // collection wildcard at current level; do nothing - } else { - val existingEntry = entrySteps.getOrDefault(first, RemoveAndOtherSteps.empty()) - val newEntry = addToCompiledExcludeExprs(existingEntry, steps.drop(1)) - entrySteps[first] = newEntry - } - } - } - } - return RemoveAndOtherSteps(entryRemove, entrySteps) - } - - /** - * Creates a list of compiled exclude expressions with each index of the resulting list corresponding to a different - * exclude path root. - */ - internal fun compileExcludeClause(excludeClause: PartiqlAst.ExcludeOp): List { - val excludeExprs = excludeClause.exprs - val compiledExcludeExprs = excludeExprs - .groupBy { it.root } - .map { (root, exclusions) -> - val compiledExclusions = exclusions.fold(RemoveAndOtherSteps.empty()) { acc, exclusion -> - addToCompiledExcludeExprs(acc, exclusion.steps) - } - CompiledExcludeExpr(root, compiledExclusions) - } - return compiledExcludeExprs - } - - /** - * Create a thunk that uses the compiled GROUP BY expressions to create the group key. - */ - private fun compileGroupKeyThunk(compiledGroupByItems: List, selectMetas: MetaContainer) = - thunkFactory.thunkEnv(selectMetas) { env -> - val uniqueNames = HashMap(compiledGroupByItems.size, 1f) - val keyValues = compiledGroupByItems.map { cgbi -> - val value = cgbi.thunk(env).namedValue(cgbi.alias) - if (cgbi.uniqueId != null) { - uniqueNames[cgbi.uniqueId] = value - } - value - } - GroupKeyExprValue(keyValues.asSequence(), uniqueNames) - } - - private fun compileOrderByExpression(sortSpecs: List): List = - sortSpecs.map { - val comparator = when (it.orderingSpec ?: PartiqlAst.OrderingSpec.Asc()) { - is PartiqlAst.OrderingSpec.Asc -> - when (it.nullsSpec) { - is PartiqlAst.NullsSpec.NullsFirst -> NaturalExprValueComparators.NULLS_FIRST_ASC - is PartiqlAst.NullsSpec.NullsLast -> NaturalExprValueComparators.NULLS_LAST_ASC - else -> NaturalExprValueComparators.NULLS_LAST_ASC - } - - is PartiqlAst.OrderingSpec.Desc -> - when (it.nullsSpec) { - is PartiqlAst.NullsSpec.NullsFirst -> NaturalExprValueComparators.NULLS_FIRST_DESC - is PartiqlAst.NullsSpec.NullsLast -> NaturalExprValueComparators.NULLS_LAST_DESC - else -> NaturalExprValueComparators.NULLS_FIRST_DESC - } - } - - CompiledOrderByItem(comparator, compileAstExpr(it.expr)) - } - - /** - * Returns an [ExprValue] created from a sequence of [seq]. Requires [type] to be a sequence type - * (i.e. [ExprValueType.isSequence] == true). - */ - private fun newSequence(type: ExprValueType, seq: Sequence): ExprValue { - return when (type) { - ExprValueType.LIST -> ExprValue.newList(seq) - ExprValueType.BAG -> ExprValue.newBag(seq) - ExprValueType.SEXP -> ExprValue.newSexp(seq) - else -> error("Sequence type required") - } - } - - private fun excludeStructExprValue( - structExprValue: StructExprValue, - exclusions: RemoveAndOtherSteps - ): ExprValue { - val toRemove = exclusions.remove - val otherSteps = exclusions.steps - if (toRemove.any { it is PartiqlAst.ExcludeStep.ExcludeTupleWildcard }) { - // tuple wildcard at current level. return empty struct - return StructExprValue(sequence = emptySequence(), ordering = structExprValue.ordering) - } - val attrsToRemove = toRemove.filterIsInstance() - .map { it.attr.name.text } - .toSet() - val sequenceWithRemoved = structExprValue.mapNotNull { structField -> - if (attrsToRemove.contains(structField.name?.stringValue())) { - null - } else { - structField as NamedExprValue - } - } - val finalSequence = sequenceWithRemoved.map { structField -> - var expr = structField.value - val name = structField.name - // apply case-sensitive tuple attr exclusions - val structFieldCaseSensitiveKey = PartiqlAst.build { - excludeTupleAttr( - identifier( - name.stringValue(), - caseSensitive() - ) - ) - } - otherSteps[structFieldCaseSensitiveKey]?.let { - expr = excludeExprValue(expr, it) - } - // apply case-insensitive tuple attr exclusions - val structFieldCaseInsensitiveKey = PartiqlAst.build { - excludeTupleAttr( - identifier( - name.stringValue(), - caseInsensitive() - ) - ) - } - otherSteps[structFieldCaseInsensitiveKey]?.let { - expr = excludeExprValue(expr, it) - } - // apply tuple wildcard exclusions - val tupleWildcardKey = PartiqlAst.build { excludeTupleWildcard(emptyMetaContainer()) } - otherSteps[tupleWildcardKey]?.let { - expr = excludeExprValue(expr, it) - } - expr.namedValue(name) - } - return ExprValue.newStruct(values = finalSequence, ordering = structExprValue.ordering) - } - - private fun excludeCollectionExprValue( - initialExprValue: ExprValue, - exprValueType: ExprValueType, - exclusions: RemoveAndOtherSteps - ): ExprValue { - val toRemove = exclusions.remove - val otherSteps = exclusions.steps - if (toRemove.any { it is PartiqlAst.ExcludeStep.ExcludeCollectionWildcard }) { - // collection wildcard at current level. return empty collection - return newSequence(exprValueType, emptySequence()) - } else { - val indexesToRemove = toRemove.filterIsInstance() - .map { it.index.value } - .toSet() - val sequenceWithRemoved = initialExprValue.mapNotNull { element -> - if (indexesToRemove.contains(element.name?.longValue())) { - null - } else { - element - } - }.asSequence() - val finalSequence = sequenceWithRemoved.map { element -> - var expr = element - if (initialExprValue is ExprValue.Companion.ListExprValue || initialExprValue is ExprValue.Companion.SexpExprValue) { - element as NamedExprValue - val index = element.name.longValue() - // apply collection index exclusions for lists and sexps - val elementKey = PartiqlAst.build { - excludeCollectionIndex( - index - ) - } - otherSteps[elementKey]?.let { - expr = excludeExprValue(element.value, it) - } - } - // apply collection wildcard exclusions for lists, bags, and sexps - val collectionWildcardKey = PartiqlAst.build { excludeCollectionWildcard(emptyMetaContainer()) } - otherSteps[collectionWildcardKey]?.let { - expr = excludeExprValue(expr, it) - } - expr - } - return newSequence(exprValueType, finalSequence) - } - } - - private fun excludeExprValue(initialExprValue: ExprValue, exclusions: RemoveAndOtherSteps): ExprValue { - return when (initialExprValue) { - is NamedExprValue -> excludeExprValue(initialExprValue.value, exclusions) - is StructExprValue -> excludeStructExprValue(initialExprValue, exclusions) - is ExprValue.Companion.ListExprValue -> excludeCollectionExprValue(initialExprValue, ExprValueType.LIST, exclusions) - is ExprValue.Companion.BagExprValue -> excludeCollectionExprValue(initialExprValue, ExprValueType.BAG, exclusions) - is ExprValue.Companion.SexpExprValue -> excludeCollectionExprValue(initialExprValue, ExprValueType.SEXP, exclusions) - else -> { - initialExprValue - } - } - } - - private fun excludeBindings( - initialBindings: Bindings, - root: PartiqlAst.Identifier, - exclusions: RemoveAndOtherSteps - ): Bindings { - val bindingNameString = root.name.text - val bindingName = BindingName(bindingNameString, root.case.toBindingCase()) - val bindingAtAttr = initialBindings[bindingName] - return if (bindingAtAttr != null) { - val newBindings = Bindings.buildLazyBindings { - val newExprValue = excludeExprValue(bindingAtAttr, exclusions) - addBinding(bindingNameString) { - newExprValue - } - } - newBindings.delegate(initialBindings) - } else { - initialBindings - } - } - - private fun evalExclude( - env: Environment, - excludeExprs: List - ): Environment { - val newBindings = excludeExprs.fold(env.current) { accBindings, expr -> - excludeBindings(accBindings, expr.root, expr.exclusions) - } - return env.nest(newLocals = newBindings) - } - - private fun evalOrderBy( - rows: Sequence, - orderByItems: List, - offsetLocationMeta: SourceLocationMeta? - ): Sequence { - val initialComparator: Comparator? = null - val resultComparator = orderByItems.interruptibleFold(initialComparator) { intermediateComparator, orderByItem -> - if (intermediateComparator == null) { - return@interruptibleFold compareBy(orderByItem.comparator) { row -> - val env = resolveEnvironment(row, offsetLocationMeta) - orderByItem.thunk(env) - } - } - - return@interruptibleFold intermediateComparator.thenBy(orderByItem.comparator) { row -> - val env = resolveEnvironment(row, offsetLocationMeta) - orderByItem.thunk(env) - } - } ?: errNoContext( - "Order BY comparator cannot be null", - ErrorCode.EVALUATOR_ORDER_BY_NULL_COMPARATOR, - internal = true - ) - - return rows.sortedWith(resultComparator) - } - - private fun resolveEnvironment(envWrapper: T, offsetLocationMeta: SourceLocationMeta?): Environment { - return when (envWrapper) { - is FromProduction -> envWrapper.env - is Pair<*, *> -> { - if (envWrapper.first is Environment) { - envWrapper.first as Environment - } else if (envWrapper.second is Environment) { - envWrapper.second as Environment - } else { - err( - "Environment cannot be resolved from pair", - ErrorCode.EVALUATOR_ENVIRONMENT_CANNOT_BE_RESOLVED, - errorContextFrom(offsetLocationMeta), - internal = true - ) - } - } - else -> err( - "Environment cannot be resolved", - ErrorCode.EVALUATOR_ENVIRONMENT_CANNOT_BE_RESOLVED, - errorContextFrom(offsetLocationMeta), - internal = true - ) - } - } - - /** - * Returns a closure which creates an [Environment] for the specified [Group]. - * If a GROUP AS name was specified, also nests that [Environment] in another that - * has a binding for the GROUP AS name. - */ - private fun createGetGroupEnvClosure(groupAsName: SymbolPrimitive?): (Environment, Group) -> Environment = - when { - groupAsName != null -> { groupByEnv, currentGroup -> - val groupAsBindings = Bindings.buildLazyBindings { - addBinding(groupAsName.text) { - ExprValue.newBag(currentGroup.groupValues.asSequence()) - } - } - - groupByEnv.nest(currentGroup.key.bindings, newGroup = currentGroup) - .nest(groupAsBindings) - } - else -> { groupByEnv, currentGroup -> - groupByEnv.nest(currentGroup.key.bindings, newGroup = currentGroup) - } - } - - private fun compileCallAgg(expr: PartiqlAst.Expr.CallAgg, metas: MetaContainer): ThunkEnv { - if (metas.containsKey(IsCountStarMeta.TAG) && currentCompilationContext.expressionContext != ExpressionContext.SELECT_LIST) { - err( - "COUNT(*) is not allowed in this context", - ErrorCode.EVALUATOR_COUNT_START_NOT_ALLOWED, - errorContextFrom(metas), - internal = false - ) - } - - val aggFactory = getAggregatorFactory(expr.funcName.text.lowercase(), expr.setq, metas) - - val argThunk = nestCompilationContext(ExpressionContext.AGG_ARG, emptySet()) { - compileAstExpr(expr.arg) - } - - return when (currentCompilationContext.expressionContext) { - ExpressionContext.AGG_ARG -> { - err( - "The arguments of an aggregate function cannot contain aggregate functions", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_AGG_FUNCTION, - errorContextFrom(metas), - internal = false - ) - } - ExpressionContext.NORMAL -> - thunkFactory.thunkEnv(metas) { env -> - val aggregator = aggFactory.create() - val argValue = argThunk(env) - argValue.forEach { - aggregator.next(it) - } - aggregator.compute() - } - ExpressionContext.SELECT_LIST -> { - val registerIdMeta = metas[AggregateRegisterIdMeta.TAG] as AggregateRegisterIdMeta - val registerId = registerIdMeta.registerId - thunkFactory.thunkEnv(metas) { env -> - // Note: env.currentGroup must be set by caller. - val registers = env.currentGroup?.registers - ?: err( - "No current group or current group has no registers", - ErrorCode.INTERNAL_ERROR, - errorContextFrom(metas), - internal = true - ) - - registers[registerId].aggregator.compute() - } - } - } - } - - private fun getAggregatorFactory( - funcName: String, - setQuantifier: PartiqlAst.SetQuantifier, - metas: MetaContainer - ): ExprAggregatorFactory { - val key = funcName.lowercase() to setQuantifier - - return builtinAggregates[key] ?: err( - "No such built-in aggregate function: $funcName", - ErrorCode.EVALUATOR_NO_SUCH_FUNCTION, - errorContextFrom(metas).also { it[Property.FUNCTION_NAME] = funcName }, - internal = false - ) - } - - /** Compile the data sources in the join of the FROM clause and convert the join's tree structure into a list. */ - // TODO: Process tree-structured joins correctly -- see https://github.com/partiql/partiql-lang-kotlin/issues/1019 - // (In general, a tree-structured join is not semantically equivalent to a list-structured join produced here.) - private fun compileFromSources( - fromSource: PartiqlAst.FromSource, - sources: MutableList = ArrayList(), - joinExpansion: JoinExpansion = JoinExpansion.INNER, - conditionThunk: ThunkEnv? = null - ): List { - val metas = fromSource.metas - - fun addAliases( - thunk: ThunkEnv, - asName: String?, - atName: String?, - byName: String?, - metas: MetaContainer - ) { - sources.add( - CompiledFromSource( - alias = Alias( - asName = asName ?: err( - "PartiqlAst.FromSource.Scan.variables.asName was null", ErrorCode.INTERNAL_ERROR, - errorContextFrom(metas), internal = true - ), - atName = atName, - byName = byName - ), - thunk = thunk, - joinExpansion = joinExpansion, - filter = conditionThunk - ) - ) - } - - when (fromSource) { - is PartiqlAst.FromSource.Scan -> { - val thunk = compileAstExpr(fromSource.expr) - addAliases( - thunk, - fromSource.asAlias?.text, - fromSource.atAlias?.text, - fromSource.byAlias?.text, - fromSource.metas - ) - } - is PartiqlAst.FromSource.Unpivot -> { - val exprThunk = compileAstExpr(fromSource.expr) - val thunk = thunkFactory.thunkEnv(metas) { env -> exprThunk(env).unpivot() } - addAliases( - thunk, - fromSource.asAlias?.text, - fromSource.atAlias?.text, - fromSource.byAlias?.text, - fromSource.metas - ) - } - is PartiqlAst.FromSource.Join -> { - val joinOp = fromSource.type - val left = fromSource.left - val right = fromSource.right - val condition = fromSource.predicate - - val leftSources = compileFromSources(left) - sources.addAll(leftSources) - - val joinExpansionInner = when (joinOp) { - is PartiqlAst.JoinType.Inner -> JoinExpansion.INNER - is PartiqlAst.JoinType.Left -> JoinExpansion.OUTER - is PartiqlAst.JoinType.Right, - is PartiqlAst.JoinType.Full -> - err( - "RIGHT and FULL JOIN not supported", - ErrorCode.EVALUATOR_FEATURE_NOT_SUPPORTED_YET, - errorContextFrom(metas).also { - it[Property.FEATURE_NAME] = "RIGHT and FULL JOIN" - }, - internal = false - ) - } - val conditionThunkInner = condition?.let { compileAstExpr(it) } - ?: compileAstExpr(PartiqlAst.build { lit(ionBool(true)) }) - - // The right side of a FromSourceJoin can never be another FromSourceJoin -- the parser will currently - // never construct an AST in that fashion. - - // TODO: do we need to modify the AST to include this enforce this as a constraint? - - // How to modify the ast so it properly constrains the right side - // of FromSourceJoin? - - // This means that the call to compileFromSources below can only ever yield one - // additional FromClauseSource, which must contain the JoinExpansion and join - // condition of this current node, so here we pass those down to the next level - // of recursion. - compileFromSources(right, sources, joinExpansionInner, conditionThunkInner) - } - } - - return sources - } - - private fun compileLetSources(letSource: PartiqlAst.Let): List = - letSource.letBindings.map { - CompiledLetSource(name = it.name.text, thunk = compileAstExpr(it.expr)) - } - - private fun Environment.extend(alias: Alias, value: ExprValue): Environment = - this.nest( - Bindings.buildLazyBindings { - addBinding(alias.asName) { value } - if (alias.atName != null) - addBinding(alias.atName) { - value.name ?: ExprValue.missingValue - } - if (alias.byName != null) - addBinding(alias.byName) { - value.address ?: ExprValue.missingValue - } - }, - Environment.CurrentMode.GLOBALS_THEN_LOCALS - ) - - /** - * Compiles FROM, LET, and WHERE clauses of a SELECT or PIVOT into a thunk that generates - * their cumulative binding tuples. - */ - private fun compileFromLetWhere( - compiledSources: List, - compiledLetSources: List?, - whereThunk: ThunkEnv? - ): (Environment) -> Sequence { - - val localsBinder = compiledSources.map { it.alias }.localsBinder(ExprValue.missingValue) - - return { rootEnv -> - val fromEnv = rootEnv.flipToGlobalsFirst() - // compute the join over the data sources - var seq = compiledSources - .foldLeftProduct({ env: Environment -> env }) { currEnvT: (Environment) -> Environment, currSource: CompiledFromSource -> - interruptionCheck() - // [currSource] - the next FROM currSource to add to the join - // [currEnvT] - the environment add-on that previous sources of the join have constructed - // and that can be used for evaluating this [currSource] (if it depends on the previous sources) - - // Given a [value] drawn from the current [currSource], construct the next environment add-on, - // to use later with the next source, as well as with the ON condition when joining the current [currSource]. - // It is constructed by adding bindings for [value] to the current add-on [currEnvT]. - fun correlatedBind(value: ExprValue): Pair<(Environment) -> Environment, ExprValue> { - // add the correlated binding environment thunk - val nextEnvT = { env: Environment -> - val childEnv = currEnvT(env) - childEnv.extend(currSource.alias, value) - } - return Pair(nextEnvT, value) - } - - val sourceComputed = currSource.thunk(currEnvT(fromEnv)) - var pairSeq = sourceComputed - .rangeOver() - .asSequence() - .map { correlatedBind(it) } - - val joinON = currSource.filter - if (joinON != null) { - // evaluate the ON-clause (before calculating the outer join NULL) - // TODO add facet for ExprValue to directly evaluate theta-joins - pairSeq = pairSeq - .filter { (nextEnvT: (Environment) -> Environment, _) -> - // make sure we operate with lexical scoping - val currEnv = nextEnvT(rootEnv).flipToLocals() - val joinONresult = joinON(currEnv) - if (joinONresult.isUnknown()) { - false - } else { - joinONresult.booleanValue() - } - } - } - - pairSeq = when (currSource.joinExpansion) { - JoinExpansion.INNER -> pairSeq - JoinExpansion.OUTER -> pairSeq.ifEmpty { sequenceOf(correlatedBind(ExprValue.nullValue)) } - } - - pairSeq.iterator() - } - .asSequence() - .map { joinedValues -> - interruptionCheck() - // bind the joined value to the bindings for the filter/project - FromProduction(joinedValues, fromEnv.nest(localsBinder.bindLocals(joinedValues))) - } - // Nest LET bindings in the FROM environment - if (compiledLetSources != null) { - seq = seq.map { fromProduction -> - val parentEnv = fromProduction.env - - val letEnv: Environment = compiledLetSources.fold(parentEnv) { accEnvironment, curLetSource -> - val letValue = curLetSource.thunk(accEnvironment) - val binding = Bindings.over { bindingName -> - when { - bindingName.isEquivalentTo(curLetSource.name) -> letValue - else -> null - } - } - accEnvironment.nest(newLocals = binding) - } - fromProduction.copy(env = letEnv) - } - } - if (whereThunk != null) { - seq = seq.filter { (_, env) -> - val whereClauseResult = whereThunk(env) - when (whereClauseResult.isUnknown()) { - true -> false - false -> whereClauseResult.booleanValue() - } - } - } - seq - } - } - - private fun compileSelectListToProjectionElements( - selectList: PartiqlAst.Projection.ProjectList - ): List = - selectList.projectItems.mapIndexed { idx, it -> - when (it) { - is PartiqlAst.ProjectItem.ProjectExpr -> { - val alias = it.asAlias?.text ?: it.expr.extractColumnAlias(idx) - val thunk = compileAstExpr(it.expr) - SingleProjectionElement(ExprValue.newString(alias), thunk) - } - is PartiqlAst.ProjectItem.ProjectAll -> { - MultipleProjectionElement(listOf(compileAstExpr(it.expr))) - } - } - } - - private fun compilePath(expr: PartiqlAst.Expr.Path, metas: MetaContainer): ThunkEnv { - val rootThunk = compileAstExpr(expr.root) - val remainingComponents = LinkedList() - - expr.steps.forEach { remainingComponents.addLast(it) } - - val componentThunk = compilePathComponents(remainingComponents, metas) - - return thunkFactory.thunkEnv(metas) { env -> - val rootValue = rootThunk(env) - componentThunk(env, rootValue) - } - } - - private fun compilePathComponents( - remainingComponents: LinkedList, - pathMetas: MetaContainer - ): ThunkEnvValue { - - val componentThunks = ArrayList>() - - while (!remainingComponents.isEmpty()) { - val pathComponent = remainingComponents.removeFirst() - val componentMetas = pathComponent.metas - componentThunks.add( - when (pathComponent) { - is PartiqlAst.PathStep.PathExpr -> { - val indexExpr = pathComponent.index - val caseSensitivity = pathComponent.case - when { - // If indexExpr is a literal string, there is no need to evaluate it--just compile a - // thunk that directly returns a bound value - indexExpr is PartiqlAst.Expr.Lit && indexExpr.value.toIonValue(ion) is IonString -> { - val lookupName = BindingName( - indexExpr.value.toIonValue(ion).stringValue()!!, - caseSensitivity.toBindingCase() - ) - thunkFactory.thunkEnvValue(componentMetas) { _, componentValue -> - componentValue.bindings[lookupName] ?: ExprValue.missingValue - } - } - else -> { - val indexThunk = compileAstExpr(indexExpr) - thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val indexValue = indexThunk(env) - when { - indexValue.type == ExprValueType.INT -> { - componentValue.ordinalBindings[indexValue.numberValue().toInt()] - } - indexValue.type.isText -> { - val lookupName = - BindingName(indexValue.stringValue(), caseSensitivity.toBindingCase()) - componentValue.bindings[lookupName] - } - else -> { - when (compileOptions.typingMode) { - TypingMode.LEGACY -> err( - "Cannot convert index to int/string: $indexValue", - ErrorCode.EVALUATOR_INVALID_CONVERSION, - errorContextFrom(componentMetas), - internal = false - ) - TypingMode.PERMISSIVE -> ExprValue.missingValue - } - } - } ?: ExprValue.missingValue - } - } - } - } - is PartiqlAst.PathStep.PathUnpivot -> { - when { - !remainingComponents.isEmpty() -> { - val tempThunk = compilePathComponents(remainingComponents, pathMetas) - thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val mapped = componentValue.unpivot() - .flatMap { tempThunk(env, it).rangeOver() } - .asSequence() - ExprValue.newBag(mapped) - } - } - else -> - thunkFactory.thunkEnvValue(componentMetas) { _, componentValue -> - ExprValue.newBag(componentValue.unpivot().asSequence()) - } - } - } - // this is for `path[*].component` - is PartiqlAst.PathStep.PathWildcard -> { - when { - !remainingComponents.isEmpty() -> { - val hasMoreWildCards = - remainingComponents.filterIsInstance().any() - val tempThunk = compilePathComponents(remainingComponents, pathMetas) - - when { - !hasMoreWildCards -> thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val mapped = componentValue - .rangeOver() - .map { tempThunk(env, it) } - .asSequence() - - ExprValue.newBag(mapped) - } - else -> thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val mapped = componentValue - .rangeOver() - .flatMap { - val tempValue = tempThunk(env, it) - tempValue - } - .asSequence() - - ExprValue.newBag(mapped) - } - } - } - else -> { - thunkFactory.thunkEnvValue(componentMetas) { _, componentValue -> - val mapped = componentValue.rangeOver().asSequence() - ExprValue.newBag(mapped) - } - } - } - } - } - ) - } - return when (componentThunks.size) { - 1 -> componentThunks.first() - else -> thunkFactory.thunkEnvValue(pathMetas) { env, rootValue -> - componentThunks.fold(rootValue) { componentValue, componentThunk -> - componentThunk(env, componentValue) - } - } - } - } - - /** - * Given an AST node that represents a `LIKE` predicate, return an ExprThunk that evaluates a `LIKE` predicate. - * - * Three cases - * - * 1. All arguments are literals, then compile and run the pattern - * 1. Search pattern and escape pattern are literals, compile the pattern. Running the pattern deferred to evaluation time. - * 1. Pattern or escape (or both) are *not* literals, compile and running of pattern deferred to evaluation time. - * - * ``` - * LIKE [ESCAPE ] - * ``` - * - * @return a thunk that when provided with an environment evaluates the `LIKE` predicate - */ - private fun compileLike(expr: PartiqlAst.Expr.Like, metas: MetaContainer): ThunkEnv { - val valueExpr = expr.value - val patternExpr = expr.pattern - val escapeExpr = expr.escape - - val patternLocationMeta = patternExpr.metas.sourceLocation - val escapeLocationMeta = escapeExpr?.metas?.sourceLocation - - // This is so that null short-circuits can be supported. - fun getRegexPattern(pattern: ExprValue, escape: ExprValue?): (() -> Pattern)? { - val patternArgs = listOfNotNull(pattern, escape) - when { - patternArgs.any { it.type.isUnknown } -> return null - patternArgs.any { !it.type.isText } -> return { - err( - "LIKE expression must be given non-null strings as input", - ErrorCode.EVALUATOR_LIKE_INVALID_INPUTS, - errorContextFrom(metas).also { - it[Property.LIKE_PATTERN] = pattern.toString() - if (escape != null) it[Property.LIKE_ESCAPE] = escape.toString() - }, - internal = false - ) - } - else -> { - val (patternString: String, escapeChar: Int?) = - checkPattern(pattern.stringValue(), patternLocationMeta, escape?.stringValue(), escapeLocationMeta) - val likeRegexPattern = when { - patternString.isEmpty() -> Pattern.compile("") - else -> parsePattern(patternString, escapeChar) - } - return { likeRegexPattern } - } - } - } - - fun matchRegexPattern(value: ExprValue, likePattern: (() -> Pattern)?): ExprValue { - return when { - likePattern == null || value.type.isUnknown -> ExprValue.nullValue - !value.type.isText -> err( - "LIKE expression must be given non-null strings as input", - ErrorCode.EVALUATOR_LIKE_INVALID_INPUTS, - errorContextFrom(metas).also { - it[Property.LIKE_VALUE] = value.toString() - }, - internal = false - ) - else -> ExprValue.newBoolean(likePattern().matcher(value.stringValue()).matches()) - } - } - - val valueThunk = compileAstExpr(valueExpr) - - // If the pattern and escape expressions are literals then we can compile the pattern now and - // re-use it with every execution. Otherwise, we must re-compile the pattern every time. - return when { - patternExpr is PartiqlAst.Expr.Lit && (escapeExpr == null || escapeExpr is PartiqlAst.Expr.Lit) -> { - val patternParts = getRegexPattern( - ExprValue.of(patternExpr.value.toIonValue(ion)), - (escapeExpr as? PartiqlAst.Expr.Lit)?.value?.toIonValue(ion) - ?.let { ExprValue.of(it) } - ) - - // If valueExpr is also a literal then we can evaluate this at compile time and return a constant. - if (valueExpr is PartiqlAst.Expr.Lit) { - val resultValue = matchRegexPattern( - ExprValue.of(valueExpr.value.toIonValue(ion)), - patternParts - ) - return thunkFactory.thunkEnv(metas) { resultValue } - } else { - thunkFactory.thunkEnvOperands(metas, valueThunk) { _, value -> - matchRegexPattern(value, patternParts) - } - } - } - else -> { - val patternThunk = compileAstExpr(patternExpr) - when (escapeExpr) { - null -> { - // thunk that re-compiles the DFA every evaluation without a custom escape sequence - thunkFactory.thunkEnvOperands(metas, valueThunk, patternThunk) { _, value, pattern -> - val pps = getRegexPattern(pattern, null) - matchRegexPattern(value, pps) - } - } - else -> { - // thunk that re-compiles the pattern every evaluation but *with* a custom escape sequence - val escapeThunk = compileAstExpr(escapeExpr) - thunkFactory.thunkEnvOperands( - metas, - valueThunk, - patternThunk, - escapeThunk - ) { _, value, pattern, escape -> - val pps = getRegexPattern(pattern, escape) - matchRegexPattern(value, pps) - } - } - } - } - } - } - - /** - * Given the pattern and optional escape character in a `LIKE` predicate as [IonValue]s - * check their validity based on the SQL92 spec and return a triple that contains in order - * - * - the search pattern as a string - * - the escape character, possibly `null` - * - the length of the search pattern. The length of the search pattern is either - * - the length of the string representing the search pattern when no escape character is used - * - the length of the string representing the search pattern without counting uses of the escape character - * when an escape character is used - * - * A search pattern is valid when - * 1. pattern is not null - * 1. pattern contains characters where `_` means any 1 character and `%` means any string of length 0 or more - * 1. if the escape character is specified then pattern can be deterministically partitioned into character groups where - * 1. A length 1 character group consists of any character other than the ESCAPE character - * 1. A length 2 character group consists of the ESCAPE character followed by either `_` or `%` or the ESCAPE character itself - * - * @param pattern search pattern - * @param escape optional escape character provided in the `LIKE` predicate - * - * @return a triple that contains in order the search pattern as a [String], optionally the code point for the escape character if one was provided - * and the size of the search pattern excluding uses of the escape character - */ - private fun checkPattern( - pattern: String, - patternLocationMeta: SourceLocationMeta?, - escape: String?, - escapeLocationMeta: SourceLocationMeta? - ): Pair { - - escape?.let { - val escapeCharString = checkEscapeChar(escape, escapeLocationMeta) - val escapeCharCodePoint = escapeCharString.codePointAt(0) // escape is a string of length 1 - val validEscapedChars = setOf('_'.toInt(), '%'.toInt(), escapeCharCodePoint) - val iter = pattern.codePointSequence().iterator() - - while (iter.hasNext()) { - val current = iter.next() - if (current == escapeCharCodePoint && (!iter.hasNext() || !validEscapedChars.contains(iter.next()))) { - err( - "Invalid escape sequence : $pattern", - ErrorCode.EVALUATOR_LIKE_PATTERN_INVALID_ESCAPE_SEQUENCE, - errorContextFrom(patternLocationMeta).apply { - set(Property.LIKE_PATTERN, pattern) - set(Property.LIKE_ESCAPE, escapeCharString) - }, - internal = false - ) - } - } - return Pair(pattern, escapeCharCodePoint) - } - return Pair(pattern, null) - } - - /** - * Given an [IonValue] to be used as the escape character in a `LIKE` predicate check that it is - * a valid character based on the SQL Spec. - * - * - * A value is a valid escape when - * 1. it is 1 character long, and, - * 1. Cannot be null (SQL92 spec marks this cases as *unknown*) - * - * @param escape value provided as an escape character for a `LIKE` predicate - * - * @return the escape character as a [String] or throws an exception when the input is invalid - */ - private fun checkEscapeChar(escape: String, locationMeta: SourceLocationMeta?): String { - when (escape) { - "" -> { - err( - "Cannot use empty character as ESCAPE character in a LIKE predicate: $escape", - ErrorCode.EVALUATOR_LIKE_PATTERN_INVALID_ESCAPE_SEQUENCE, - errorContextFrom(locationMeta), - internal = false - ) - } - else -> { - if (escape.trim().length != 1) { - err( - "Escape character must have size 1 : $escape", - ErrorCode.EVALUATOR_LIKE_PATTERN_INVALID_ESCAPE_SEQUENCE, - errorContextFrom(locationMeta), - internal = false - ) - } - } - } - return escape - } - - private fun compileDdl(node: PartiqlAst.Statement.Ddl): ThunkEnv = - { _ -> - err( - "DDL operations are not supported yet", - ErrorCode.EVALUATOR_FEATURE_NOT_SUPPORTED_YET, - errorContextFrom(node.metas).also { - it[Property.FEATURE_NAME] = "DDL Operations" - }, - internal = false - ) - } - - private fun compileDml(node: PartiqlAst.Statement.Dml): ThunkEnv = - { _ -> - err( - "DML operations are not supported yet", - ErrorCode.EVALUATOR_FEATURE_NOT_SUPPORTED_YET, - errorContextFrom(node.metas).also { - it[Property.FEATURE_NAME] = "DML Operations" - }, - internal = false - ) - } - - private fun compileExec(node: PartiqlAst.Statement.Exec): ThunkEnv { - val metas = node.metas - val procedureName = node.procedureName.text - val procedure = procedures[procedureName] ?: err( - "No such stored procedure: $procedureName", - ErrorCode.EVALUATOR_NO_SUCH_PROCEDURE, - errorContextFrom(metas).also { - it[Property.PROCEDURE_NAME] = procedureName - }, - internal = false - ) - - val args = node.args - // Check arity - if (args.size !in procedure.signature.arity) { - val errorContext = errorContextFrom(metas).also { - it[Property.EXPECTED_ARITY_MIN] = procedure.signature.arity.first - it[Property.EXPECTED_ARITY_MAX] = procedure.signature.arity.last - } - - val message = when { - procedure.signature.arity.first == 1 && procedure.signature.arity.last == 1 -> - "${procedure.signature.name} takes a single argument, received: ${args.size}" - procedure.signature.arity.first == procedure.signature.arity.last -> - "${procedure.signature.name} takes exactly ${procedure.signature.arity.first} arguments, received: ${args.size}" - else -> - "${procedure.signature.name} takes between ${procedure.signature.arity.first} and " + - "${procedure.signature.arity.last} arguments, received: ${args.size}" - } - - err( - message, - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_PROCEDURE_CALL, - errorContext, - internal = false - ) - } - - // Compile the procedure's arguments - val argThunks = compileAstExprs(args) - - return thunkFactory.thunkEnv(metas) { env -> - val procedureArgValues = argThunks.map { it(env) } - procedure.call(env.session, procedureArgValues) - } - } - - private fun compileDate(expr: PartiqlAst.Expr.Date, metas: MetaContainer): ThunkEnv = - thunkFactory.thunkEnv(metas) { - ExprValue.newDate( - expr.year.value.toInt(), - expr.month.value.toInt(), - expr.day.value.toInt() - ) - } - - private fun compileLitTime(expr: PartiqlAst.Expr.LitTime, metas: MetaContainer): ThunkEnv = - thunkFactory.thunkEnv(metas) { - // Add the default time zone if the type "TIME WITH TIME ZONE" does not have an explicitly specified time zone. - ExprValue.newTime( - Time.of( - expr.value.hour.value.toInt(), - expr.value.minute.value.toInt(), - expr.value.second.value.toInt(), - expr.value.nano.value.toInt(), - expr.value.precision.value.toInt(), - if (expr.value.withTimeZone.value && expr.value.tzMinutes == null) compileOptions.defaultTimezoneOffset.totalMinutes else expr.value.tzMinutes?.value?.toInt() - ) - ) - } - - /** A special wrapper for `UNPIVOT` values as a BAG. */ - private class UnpivotedExprValue(private val values: Iterable) : BaseExprValue() { - override val type = ExprValueType.BAG - override fun iterator() = values.iterator() - } - - /** Unpivots a `struct`, and synthesizes a synthetic singleton `struct` for other [ExprValue]. */ - internal fun ExprValue.unpivot(): ExprValue = when { - // special case for our special UNPIVOT value to avoid double wrapping - this is UnpivotedExprValue -> this - // Wrap into a pseudo-BAG - type == ExprValueType.STRUCT || type == ExprValueType.MISSING -> UnpivotedExprValue(this) - // for non-struct, this wraps any value into a BAG with a synthetic name - else -> UnpivotedExprValue( - listOf( - this.namedValue(ExprValue.newString(syntheticColumnName(0))) - ) - ) - } - - private fun createStructExprValue(seq: Sequence, ordering: StructOrdering) = - ExprValue.newStruct( - when (compileOptions.projectionIteration) { - ProjectionIterationBehavior.FILTER_MISSING -> seq.filter { it.type != ExprValueType.MISSING } - ProjectionIterationBehavior.UNFILTERED -> seq - }, - ordering - ) - - /** Helper to convert [PartiqlAst.Type] in AST to a [TypedOpParameter]. */ - private fun PartiqlAst.Type.toTypedOpParameter(): TypedOpParameter = this.toTypedOpParameter(customTypedOpParameters) -} - -/** - * Contains data about a compiled from source, including its [Alias], [thunk], - * type of [JoinExpansion] ([JoinExpansion.INNER] for single tables or `CROSS JOIN`S.) and [filter] criteria. - */ -private data class CompiledFromSource( - val alias: Alias, - val thunk: ThunkEnv, - val joinExpansion: JoinExpansion, - val filter: ThunkEnv? -) - -/** - * Represents a single `FROM` source production of values. - * - * @param values A single production of values from the `FROM` source. - * @param env The environment scoped to the values of this production. - */ -private data class FromProduction( - val values: List, - val env: Environment -) - -/** Specifies the expansion for joins. */ -private enum class JoinExpansion { - /** Default for non-joined values, CROSS and INNER JOIN. */ - INNER, - - /** Expansion mode for LEFT/RIGHT/FULL JOIN. */ - OUTER -} - -private data class CompiledLetSource( - val name: String, - val thunk: ThunkEnv -) - -internal enum class ExpressionContext { - /** - * Indicates that the compiler is compiling a normal expression (i.e. not one of the other - * contexts). - */ - NORMAL, - - /** - * Indicates that the compiler is compiling an expression in a select list. - */ - SELECT_LIST, - - /** - * Indicates that the compiler is compiling an expression that is the argument to an aggregate function. - */ - AGG_ARG -} - -/** - * Tracks state used by the compiler while compiling. - * - * @param expressionContext Indicates what part of the grammar is currently being compiled. - * @param fromSourceNames Set of all FROM source aliases for binding error checks. - */ -private class CompilationContext(val expressionContext: ExpressionContext, val fromSourceNames: Set) { - fun createNested(expressionContext: ExpressionContext, fromSourceNames: Set) = - CompilationContext(expressionContext, fromSourceNames) -} - -/** - * Represents an element in a select list that is to be projected into the final result. - * i.e. an expression, or a (project_all) node. - */ -private sealed class ProjectionElement - -/** - * Represents a single compiled expression to be projected into the final result. - * Given `SELECT a + b as value FROM foo`: - * - `name` is "value" - * - `thunk` is compiled expression, i.e. `a + b` - */ -private class SingleProjectionElement(val name: ExprValue, val thunk: ThunkEnv) : ProjectionElement() - -/** - * Represents a wildcard ((path_project_all) node) expression to be projected into the final result. - * This covers two cases. For `SELECT foo.* FROM foo`, `exprThunks` contains a single compiled expression - * `foo`. - * - * For `SELECT * FROM foo, bar, bat`, `exprThunks` would contain a compiled expression for each of `foo`, `bar` and - * `bat`. - */ -private class MultipleProjectionElement(val thunks: List) : ProjectionElement() - -internal val MetaContainer.sourceLocationMeta get() = this[SourceLocationMeta.TAG] as? SourceLocationMeta - -private fun StaticType.getTypes() = when (val flattened = this.flatten()) { - is AnyOfType -> flattened.types - else -> listOf(this) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluationSession.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluationSession.kt deleted file mode 100644 index c360b2225b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/EvaluationSession.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ion.Timestamp - -/** - * Evaluation Session. Holds user defined constants used during evaluation. Each value has a default value that can - * be overridden by the client - * - * @property globals The global bindings. Defaults to [Bindings.empty] - * @property parameters List of parameters to be substituted for positional placeholders - * @property context A string-keyed map of arbitrary data associated with this [EvaluationSession]. This - * provides a way to make custom session state such as current user and transaction details available to - * custom [ExprFunction] implementations and custom physical operator implementations. - * @property now Timestamp to consider as the current time, used by functions like `utcnow()` and `now()`. Defaults to [Timestamp.nowZ] - */ -class EvaluationSession private constructor( - val globals: Bindings, - val parameters: List, - val context: Map, - val now: Timestamp -) { - - companion object { - /** - * Java style builder to construct a new [EvaluationSession]. Uses the default value for any non specified field - */ - @JvmStatic - fun builder() = Builder() - - /** - * Kotlin style builder for an [EvaluationSession]. Uses the default value for any non specified field - */ - fun build(block: Builder.() -> Unit) = Builder().apply(block).build() - - /** - * Builds a [EvaluationSession] using standard values for all fields - */ - @JvmStatic - fun standard() = builder().build() - } - - public object Constants { - public const val CURRENT_USER_KEY = "CURRENT_USER" - } - - class Builder { - private fun Timestamp.toUtc() = this.withLocalOffset(0)!! - - // using null to postpone defaulting to when the session is created - private var now: Timestamp? = null - fun now(value: Timestamp): Builder { - now = value.toUtc() - return this - } - - private var globals: Bindings = Bindings.empty() - fun globals(value: Bindings): Builder { - globals = value - return this - } - - private var parameters: List = listOf() - fun parameters(value: List): Builder { - parameters = value - return this - } - - private var user: String? = null - fun user(value: String): Builder { - contextVariables[Constants.CURRENT_USER_KEY] = value - return this - } - - private val contextVariables = HashMap() - fun withContextVariable(name: String, value: Any): Builder { - contextVariables[name] = value - return this - } - - fun build(): EvaluationSession = EvaluationSession( - now = now ?: Timestamp.nowZ(), - parameters = parameters, - context = contextVariables, - globals = globals - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Exceptions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Exceptions.kt deleted file mode 100644 index 0a559f8b7c..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Exceptions.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.SqlException -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.types.StaticTypeUtils -import org.partiql.lang.util.propertyValueMapOf -import org.partiql.lang.util.to -import org.partiql.types.SingleType -import org.partiql.types.StaticType - -/** Error for evaluation problems. */ -open class EvaluationException( - message: String, - errorCode: ErrorCode, - errorContext: PropertyValueMap = PropertyValueMap(), - cause: Throwable? = null, - override val internal: Boolean -) : SqlException(message, errorCode, errorContext, cause) { - - constructor( - cause: Throwable, - errorCode: ErrorCode, - errorContext: PropertyValueMap = PropertyValueMap(), - internal: Boolean - ) : this( - message = cause.message ?: "", - errorCode = errorCode, - errorContext = errorContext, - internal = internal, - cause = cause - ) -} - -/** - * Shorthand for throwing function evaluation. Separated from [err] to avoid loosing the context unintentionally - */ -internal fun errNoContext(message: String, errorCode: ErrorCode, internal: Boolean): Nothing = - err(message, errorCode, PropertyValueMap(), internal) - -/** Shorthand for throwing evaluation with context with an error code.. */ -internal fun err(message: String, errorCode: ErrorCode, errorContext: PropertyValueMap, internal: Boolean): Nothing = - throw EvaluationException(message, errorCode, errorContext, internal = internal) - -internal fun expectedArgTypeErrorMsg(types: List): String = when (types.size) { - 0 -> throw IllegalStateException("Should have at least one expected argument type. ") - 1 -> types[0].toString() - else -> { - val window = types.size - 1 - val (most, last) = types.windowed(window, window, true) - most.joinToString(", ") + ", or ${last.first()}" - } -} - -/** Throw an [ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL] error */ -internal fun errInvalidArgumentType( - signature: FunctionSignature, - position: Int, - expectedTypes: List, - actualType: StaticType -): Nothing { - - val expectedTypeMsg = expectedArgTypeErrorMsg(expectedTypes) - - val actual = when (actualType) { - is SingleType -> StaticTypeUtils.getRuntimeType(actualType).toString() - else -> actualType.toString() - } - - val errorContext = propertyValueMapOf( - Property.FUNCTION_NAME to signature.name, - Property.EXPECTED_ARGUMENT_TYPES to expectedTypeMsg, - Property.ARGUMENT_POSITION to position, - Property.ACTUAL_ARGUMENT_TYPES to actual - ) - - err( - message = "Invalid type for argument $position of ${signature.name}.", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - errorContext = errorContext, - internal = false - ) -} - -internal fun errIntOverflow(intSizeInBytes: Int, errorContext: PropertyValueMap? = null): Nothing { - throw EvaluationException( - message = "Int overflow or underflow", - errorCode = ErrorCode.EVALUATOR_INTEGER_OVERFLOW, - errorContext = (errorContext ?: PropertyValueMap()).also { - it[Property.INT_SIZE_IN_BYTES] = intSizeInBytes - }, - internal = false - ) -} - -fun errorContextFrom(location: SourceLocationMeta?): PropertyValueMap { - val errorContext = PropertyValueMap() - if (location != null) { - fillErrorContext(errorContext, location) - } - return errorContext -} - -fun fillErrorContext(errorContext: PropertyValueMap, location: SourceLocationMeta?) { - if (location != null) { - errorContext[Property.LINE_NUMBER] = location.lineNum - errorContext[Property.COLUMN_NUMBER] = location.charOffset - } -} - -/** - * Returns the [SourceLocationMeta] as an error context if the [SourceLocationMeta.TAG] exists in the passed - * [metaContainer]. Otherwise, returns an empty map. - */ -fun errorContextFrom(metaContainer: MetaContainer?): PropertyValueMap { - if (metaContainer == null) { - return PropertyValueMap() - } - val location = metaContainer[SourceLocationMeta.TAG] as? SourceLocationMeta - return if (location != null) { - errorContextFrom(location) - } else { - PropertyValueMap() - } -} - -/** Throw a function not found error when function name matching fails */ -class FunctionNotFoundException(message: String) : Exception(message) - -/** Throw an arity mismatch error when function arity matching fails */ -internal class ArityMismatchException(message: String, val arity: Pair) : Exception(message) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprAggregator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprAggregator.kt deleted file mode 100644 index 5c9464a755..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprAggregator.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -// TODO consider making this API stateless and purely functional, AVG makes this ugly - -/** - * Defines an aggregate function in the evaluator in terms of a stateful accumulator. - * An aggregate function is always unary, and effectively operates over a collection - * (e.g. `BAG`/`LIST`) of values. This API defines the accumulator function over elements of the - * operand. The evaluator's responsibility is to effectively compile this definition - * into a form of [ExprFunction] that operates over the collection as an [ExprValue]. - */ -internal interface ExprAggregator { - /** Accumulates the next value into this [ExprAggregator]. */ - fun next(value: ExprValue) - - /** Digests the result of the accumulated values. */ - fun compute(): ExprValue -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprAggregatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprAggregatorFactory.kt deleted file mode 100644 index 3a9f45d15e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprAggregatorFactory.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * Simple functional interface to create [ExprAggregator] instances. - * - * This is the entry point for aggregate function definitions in the evaluator. - */ -internal interface ExprAggregatorFactory { - companion object { - fun over(func: () -> ExprAggregator): ExprAggregatorFactory = - object : ExprAggregatorFactory { - override fun create() = func() - } - } - - /** Generates a new instance of an aggregator. */ - fun create(): ExprAggregator -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprFunction.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprFunction.kt deleted file mode 100644 index 6788f2f78b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprFunction.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import org.partiql.errors.ErrorCode -import org.partiql.lang.types.FunctionSignature - -@Deprecated("As function overloading is now supported, this class is deprecated.", level = DeprecationLevel.ERROR) -sealed class Arguments - -@Suppress("DEPRECATION_ERROR") -@Deprecated("As function overloading is now supported, this class is deprecated.", level = DeprecationLevel.ERROR) -data class RequiredArgs(val required: List) : Arguments() - -@Suppress("DEPRECATION_ERROR") -@Deprecated("As function overloading is now supported, this class is deprecated.", level = DeprecationLevel.ERROR) -data class RequiredWithOptional(val required: List, val opt: ExprValue) : Arguments() - -@Suppress("DEPRECATION_ERROR") -@Deprecated("As function overloading is now supported, this class is deprecated.", level = DeprecationLevel.ERROR) -data class RequiredWithVariadic(val required: List, val variadic: List) : Arguments() - -/** - * Represents a function that can be invoked from within an [EvaluatingCompiler] - * compiled [Expression]. - * - * Note that [ExprFunction] implementations do not need to deal with propagation of - * unknown values `MISSING` or `NULL` as this is handled by [EvaluatingCompiler]. - */ -interface ExprFunction { - /** - * Static signature of this function. - * - */ - val signature: FunctionSignature - - /** - * Invokes the function with its required parameters only. - * - * It is assumed that the [ExprFunction]s will always be called after validating - * the arguments for the `arity` and the types of arguments. - * [EvaluatingCompiler] validates the arguments before calling [ExprFunction]s. - * Hence the implementations are not required to deal with it. - * - * @param session The current [EvaluationSession]. - * @param required The required arguments. - */ - fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - errNoContext("Invalid implementation for ${signature.name}#call", ErrorCode.INTERNAL_ERROR, true) - } - - @Deprecated("Please define overloaded functions by providing each alternative in required parameter as its own ExprFunction; each function is invoked by callWithRequired() rather than callWithOptional().", level = DeprecationLevel.ERROR) - fun callWithOptional(session: EvaluationSession, required: List, opt: ExprValue): ExprValue { - // Deriving ExprFunctions must implement this if they have a valid call form including required parameters and optional - errNoContext("Invalid implementation for ${signature.name}#call", ErrorCode.INTERNAL_ERROR, true) - } - - @Deprecated("Please define overloaded functions by providing the LIST ExprValue in required parameter to represent variadic parameters as its own ExprFunction; each function is invoked by callWithRequired() rather than callWithVariadic().", level = DeprecationLevel.ERROR) - fun callWithVariadic(session: EvaluationSession, required: List, variadic: List): ExprValue { - // Deriving ExprFunctions must implement this if they have a valid call form including required parameters and variadic - errNoContext("Invalid implementation for ${signature.name}#call", ErrorCode.INTERNAL_ERROR, true) - } -} - -/** - * Invokes the function. - * - * It is assumed that the [ExprFunction]s will always be called after validating - * the arguments for the `arity` and the types of arguments. - * [EvaluatingCompiler] validates the arguments before calling [ExprFunction]s. - * Hence the implementations are not required to deal with it. - * - * @param session The current [EvaluationSession]. - * @param args The argument list. - */ -fun ExprFunction.call(session: EvaluationSession, args: List): ExprValue = callWithRequired(session, args) - -@Suppress("DEPRECATION_ERROR") -@Deprecated("As function overloading is now supported, this class is deprecated. Please use use call(session: EvaluationSession, args: List) instead", level = DeprecationLevel.ERROR) -fun ExprFunction.call(session: EvaluationSession, args: Arguments): ExprValue = - when (args) { - is RequiredArgs -> callWithRequired(session, args.required) - is RequiredWithOptional -> callWithOptional(session, args.required, args.opt) - is RequiredWithVariadic -> callWithVariadic(session, args.required, args.variadic) - } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprValue.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprValue.kt deleted file mode 100644 index 29021b7dff..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprValue.kt +++ /dev/null @@ -1,430 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ion.IonBlob -import com.amazon.ion.IonBool -import com.amazon.ion.IonClob -import com.amazon.ion.IonDatagram -import com.amazon.ion.IonDecimal -import com.amazon.ion.IonFloat -import com.amazon.ion.IonInt -import com.amazon.ion.IonList -import com.amazon.ion.IonReader -import com.amazon.ion.IonSexp -import com.amazon.ion.IonString -import com.amazon.ion.IonStruct -import com.amazon.ion.IonSymbol -import com.amazon.ion.IonSystem -import com.amazon.ion.IonTimestamp -import com.amazon.ion.IonType -import com.amazon.ion.IonValue -import com.amazon.ion.Timestamp -import com.amazon.ion.facet.Faceted -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.time.NANOS_PER_SECOND -import org.partiql.lang.eval.time.Time -import org.partiql.lang.graph.ExternalGraphReader -import org.partiql.lang.graph.Graph -import org.partiql.lang.util.bytesValue -import org.partiql.lang.util.propertyValueMapOf -import java.math.BigDecimal -import java.time.LocalDate -/** - * Representation of a value within the context of an [Expression]. - */ -interface ExprValue : Iterable, Faceted { - /** The type of value independent of its implementation. */ - val type: ExprValueType - - /** Returns the [Scalar] view of this value. */ - val scalar: Scalar - - /** - * Returns the [Bindings] over this value. - * - * This is generally used to access a component of a value by name. - */ - // TODO: Improve ergonomics of this API - val bindings: Bindings - - /** - * Returns the [OrdinalBindings] over this value. - * - * This is generally used to access a component of a value by index. - */ - // TODO: Improve ergonomics of this API - val ordinalBindings: OrdinalBindings - - /** - * Iterates over this value's *child* elements. - * - * If this value has no children, then it should return the empty iterator. - */ - override operator fun iterator(): Iterator - - val graphValue: Graph - - companion object { - // Constructor classes - private class NullExprValue(val ionType: IonType = IonType.NULL) : BaseExprValue() { - override val type = ExprValueType.NULL - - @Suppress("UNCHECKED_CAST") - override fun provideFacet(type: Class?): T? = when (type) { - IonType::class.java -> ionType as T? - else -> null - } - } - - private abstract class ScalarExprValue : BaseExprValue(), Scalar { - override val scalar: Scalar - get() = this - } - - private class BooleanExprValue(val value: Boolean) : ScalarExprValue() { - override val type = ExprValueType.BOOL - override fun booleanValue(): Boolean = value - } - - private class StringExprValue(val value: String) : ScalarExprValue() { - override val type = ExprValueType.STRING - override fun stringValue() = value - } - - private class SymbolExprValue(val value: String) : ScalarExprValue() { - override val type = ExprValueType.SYMBOL - override fun stringValue() = value - } - - private class IntExprValue(val value: Long) : ScalarExprValue() { - override val type = ExprValueType.INT - override fun numberValue() = value - } - - private class FloatExprValue(val value: Double) : ScalarExprValue() { - override val type = ExprValueType.FLOAT - override fun numberValue() = value - } - - private class DecimalExprValue(val value: BigDecimal) : ScalarExprValue() { - override val type = ExprValueType.DECIMAL - override fun numberValue() = value - } - - private class DateExprValue(val value: LocalDate) : ScalarExprValue() { - init { - // validate that the local date is not an extended date. - if (value.year < 0 || value.year > 9999) { - err( - "Year should be in the range 0 to 9999 inclusive.", - ErrorCode.EVALUATOR_DATE_FIELD_OUT_OF_RANGE, - propertyValueMapOf(), - false - ) - } - } - - override val type: ExprValueType = ExprValueType.DATE - override fun dateValue(): LocalDate = value - } - - private class TimestampExprValue(val value: Timestamp) : ScalarExprValue() { - override val type: ExprValueType = ExprValueType.TIMESTAMP - override fun timestampValue(): Timestamp = value - } - - private class TimeExprValue(val value: Time) : ScalarExprValue() { - override val type = ExprValueType.TIME - override fun timeValue(): Time = value - } - - private class ClobExprValue(val value: ByteArray) : ScalarExprValue() { - override val type: ExprValueType = ExprValueType.CLOB - override fun bytesValue() = value - } - - private class BlobExprValue(val value: ByteArray) : ScalarExprValue() { - override val type: ExprValueType = ExprValueType.BLOB - override fun bytesValue() = value - } - - internal class ListExprValue(val values: Sequence) : BaseExprValue() { - override val type = ExprValueType.LIST - override val ordinalBindings by lazy { OrdinalBindings.ofList(toList()) } - override fun iterator() = values.mapIndexed { i, v -> v.namedValue(newInt(i)) }.iterator() - } - - internal class BagExprValue(val values: Sequence) : BaseExprValue() { - override val type = ExprValueType.BAG - override val ordinalBindings = OrdinalBindings.EMPTY - override fun iterator() = values.iterator() - } - - internal class SexpExprValue(val values: Sequence) : BaseExprValue() { - override val type = ExprValueType.SEXP - override val ordinalBindings by lazy { OrdinalBindings.ofList(toList()) } - override fun iterator() = values.mapIndexed { i, v -> v.namedValue(newInt(i)) }.iterator() - } - - private class GraphExprValue(graph: Graph) : BaseExprValue() { - override val type = ExprValueType.GRAPH - override val graphValue: Graph = graph - } - - // Memoized values for optimization - private val trueValue = BooleanExprValue(true) - private val falseValue = BooleanExprValue(false) - private val emptyString = StringExprValue("") - private val emptySymbol = SymbolExprValue("") - - // Public API - - /** A possibly memoized, immutable [ExprValue] representing the PartiQL missing value. */ - @JvmStatic - val missingValue: ExprValue = object : BaseExprValue() { - override val type = ExprValueType.MISSING - } - - /** A possibly memoized, immutable [ExprValue] representing the PartiQL null value. */ - @JvmStatic - val nullValue: ExprValue = NullExprValue() - - @JvmStatic - fun newNull(ionType: IonType): ExprValue = - when (ionType) { - IonType.NULL -> nullValue - else -> NullExprValue(ionType) - } - - /** Returns a possibly memoized instance of [ExprValue] representing the specified [Boolean]. */ - @JvmStatic - fun newBoolean(value: Boolean): ExprValue = - when (value) { - true -> trueValue - false -> falseValue - } - - /** Returns a possibly memoized [ExprValue] instance representing the specified [String]. */ - @JvmStatic - fun newString(value: String): ExprValue = - when { - value.isEmpty() -> emptyString - else -> StringExprValue(value) - } - - /** Returns an PartiQL `SYMBOL` [ExprValue] instance representing the specified [String]. */ - @JvmStatic - fun newSymbol(value: String): ExprValue = - when { - value.isEmpty() -> emptySymbol - else -> SymbolExprValue(value) - } - - /** Returns a PartiQL `INT` [ExprValue] instance representing the specified [Long]. */ - @JvmStatic - fun newInt(value: Long): ExprValue = - IntExprValue(value) - - /** Returns a PartiQL `INT` ][ExprValue] instance representing the specified [Int]. */ - @JvmStatic - fun newInt(value: Int): ExprValue = - IntExprValue(value.toLong()) - - /** Returns a PartiQL `FLOAT` [ExprValue] instance representing the specified [Float]. */ - @JvmStatic - fun newFloat(value: Double): ExprValue = - FloatExprValue(value) - - /** Returns a PartiQL `DECIMAL` [ExprValue] instance representing the specified [BigDecimal]. */ - @JvmStatic - fun newDecimal(value: BigDecimal): ExprValue = - DecimalExprValue(value) - - /** Returns a PartiQL `DECIMAL` [ExprValue] instance representing the specified [Int]. */ - @JvmStatic - fun newDecimal(value: Int): ExprValue = - DecimalExprValue(BigDecimal.valueOf(value.toLong())) - - /** Returns a PartiQL `DECIMAL` [ExprValue] instance representing the specified [Long]. */ - @JvmStatic - fun newDecimal(value: Long): ExprValue = - DecimalExprValue(BigDecimal.valueOf(value)) - - /** Returns a PartiQL `DATE` [ExprValue] instance representing the specified [LocalDate]. */ - @JvmStatic - fun newDate(value: LocalDate): ExprValue = - DateExprValue(value) - - /** Returns a PartiQL `DATE` [ExprValue] instance representing the specified year, month and day. */ - @JvmStatic - fun newDate(year: Int, month: Int, day: Int): ExprValue = - DateExprValue(LocalDate.of(year, month, day)) - - /** Returns a PartiQL `DATE` [ExprValue] instance representing the specified date string of the format yyyy-MM-dd. */ - @JvmStatic - fun newDate(value: String): ExprValue = - DateExprValue(LocalDate.parse(value)) - - /** Returns a PartiQL `TIMESTAMP` [ExprValue] instance representing the specified [Timestamp]. */ - @JvmStatic - fun newTimestamp(value: Timestamp): ExprValue = - TimestampExprValue(value) - - /** Returns a PartiQL `TIME` [ExprValue] instance representing the specified [Time]. */ - @JvmStatic - fun newTime(value: Time): ExprValue = - TimeExprValue(value) - - /** Returns a PartiQL `CLOB` [ExprValue] instance representing the specified [ByteArray]. */ - @JvmStatic - fun newClob(value: ByteArray): ExprValue = - ClobExprValue(value) - - /** Returns a PartiQL `BLOB` [ExprValue] instance representing the specified [ByteArray]. */ - @JvmStatic - fun newBlob(value: ByteArray): ExprValue = - BlobExprValue(value) - - /** Returns a possibly lazily evaluated instance of [ExprValue] representing a `PartiQL` `LIST`. */ - @JvmStatic - fun newList(values: Sequence): ExprValue = - ListExprValue(values) - - /** See newList(Sequence) */ - @JvmStatic - fun newList(values: Iterable): ExprValue = - ListExprValue(values.asSequence()) - - /** A possibly memoized, immutable [ExprValue] representing an empty list. */ - @JvmStatic - val emptyList: ExprValue = ListExprValue(sequenceOf()) - - /** Returns a possibly lazily evaluated instance of [ExprValue] representing a `PartiQL` `BAG`. */ - @JvmStatic - fun newBag(values: Sequence): ExprValue = - BagExprValue(values) - - /** See newBag(Sequence) */ - @JvmStatic - fun newBag(values: Iterable): ExprValue = - BagExprValue(values.asSequence()) - - /** A possibly memoized, immutable [ExprValue] representing an empty bag. */ - @JvmStatic - val emptyBag: ExprValue = BagExprValue(sequenceOf()) - - /** Returns a possibly lazily evaluated instance of [ExprValue] representing a `PartiQL` `SEXP`. */ - @JvmStatic - fun newSexp(values: Sequence): ExprValue = - SexpExprValue(values) - - /** See newSexp(Sequence) */ - @JvmStatic - fun newSexp(values: Iterable): ExprValue = - SexpExprValue(values.asSequence()) - - /** A possibly memoized, immutable [ExprValue] representing an empty sexp. */ - @JvmStatic - val emptySexp: ExprValue = SexpExprValue(sequenceOf()) - - /** - * Returns a possibly lazily evaluated instance of [ExprValue] representing a `PartiQL` `STRUCT`. - * The [ExprValue] instances within [values] should be [Named]. - * - * [ordering] specifies if the field order is to be preserved or not. - */ - @JvmStatic - fun newStruct(values: Sequence, ordering: StructOrdering): ExprValue = - StructExprValue(ordering, values) - - /** See newStruct(Sequence) */ - @JvmStatic - fun newStruct(values: Iterable, ordering: StructOrdering): ExprValue = - StructExprValue(ordering, values.asSequence()) - - /** A possibly memoized, immutable [ExprValue] representing an empty struct. */ - @JvmStatic - val emptyStruct: ExprValue = StructExprValue(StructOrdering.UNORDERED, sequenceOf()) - - @JvmStatic - fun newGraph(graph: Graph): ExprValue = - GraphExprValue(graph) - - /** - * Creates a new [ExprValue] instance from the next value available from the specified [IonReader]. - * - * Implementations should not close the [IonReader]. - */ - @JvmStatic - fun newFromIonReader(ion: IonSystem, reader: IonReader): ExprValue = - of(ion.newValue(reader)) - - /** - * Creates a new [ExprValue] instance from any Ion value. - * - * If possible, prefer the use of the other methods instead because they might return [ExprValue] instances - * that are better optimized for their specific data type (depending on implementation). - */ - @JvmStatic - fun of(value: IonValue): ExprValue { - return when { - value.isNullValue && value.hasTypeAnnotation(MISSING_ANNOTATION) -> missingValue // MISSING - value.isNullValue -> newNull(value.type) // NULL - value is IonBool -> newBoolean(value.booleanValue()) // BOOL - value is IonInt -> newInt(value.longValue()) // INT - value is IonFloat -> newFloat(value.doubleValue()) // FLOAT - value is IonDecimal -> newDecimal(value.decimalValue()) // DECIMAL - value is IonTimestamp && value.hasTypeAnnotation(DATE_ANNOTATION) -> { // DATE - val timestampValue = value.timestampValue() - newDate(timestampValue.year, timestampValue.month, timestampValue.day) - } - value is IonTimestamp -> newTimestamp(value.timestampValue()) // TIMESTAMP - value is IonStruct && value.hasTypeAnnotation(TIME_ANNOTATION) -> { // TIME - val hourValue = (value["hour"] as IonInt).intValue() - val minuteValue = (value["minute"] as IonInt).intValue() - val secondInDecimal = (value["second"] as IonDecimal).decimalValue() - val secondValue = secondInDecimal.toInt() - val nanoValue = secondInDecimal.remainder(BigDecimal.ONE).multiply(NANOS_PER_SECOND.toBigDecimal()).toInt() - val timeZoneHourValue = (value["timezone_hour"] as IonInt).intValue() - val timeZoneMinuteValue = (value["timezone_minute"] as IonInt).intValue() - newTime(Time.of(hourValue, minuteValue, secondValue, nanoValue, secondInDecimal.scale(), timeZoneHourValue * 60 + timeZoneMinuteValue)) - } - value is IonStruct && value.hasTypeAnnotation(GRAPH_ANNOTATION) -> // GRAPH - newGraph(ExternalGraphReader.read(value)) - value is IonSymbol -> newSymbol(value.stringValue()) // SYMBOL - value is IonString -> newString(value.stringValue()) // STRING - value is IonClob -> newClob(value.bytesValue()) // CLOB - value is IonBlob -> newBlob(value.bytesValue()) // BLOB - value is IonList && value.hasTypeAnnotation(BAG_ANNOTATION) -> newBag(value.map { of(it) }) // BAG - value is IonList -> newList(value.map { of(it) }) // LIST - value is IonSexp -> newSexp(value.map { of(it) }) // SEXP - value is IonStruct -> IonStructExprValue(value) // STRUCT - value is IonDatagram -> newBag(value.map { of(it) }) // DATAGRAM represented as BAG ExprValue - else -> error("Unrecognized IonValue to transform to ExprValue: $value") - } - } - } - - private class IonStructExprValue( - ionStruct: IonStruct - ) : StructExprValue( - StructOrdering.UNORDERED, - ionStruct.asSequence().map { of(it).namedValue(newString(it.fieldName)) } - ) { - override val bindings: Bindings = - IonStructBindings(ionStruct) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprValueExtensions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprValueExtensions.kt deleted file mode 100644 index 7629e328f9..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprValueExtensions.kt +++ /dev/null @@ -1,774 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ion.IntegerSize -import com.amazon.ion.IonInt -import com.amazon.ion.IonStruct -import com.amazon.ion.IonSystem -import com.amazon.ion.IonType -import com.amazon.ion.IonValue -import com.amazon.ion.Timestamp -import com.amazon.ion.system.IonSystemBuilder -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.eval.time.NANOS_PER_SECOND -import org.partiql.lang.eval.time.Time -import org.partiql.lang.syntax.impl.DateTimePart -import org.partiql.lang.types.StaticTypeUtils.getRuntimeType -import org.partiql.lang.util.ConfigurableExprValueFormatter -import org.partiql.lang.util.bigDecimalOf -import org.partiql.lang.util.coerce -import org.partiql.lang.util.compareTo -import org.partiql.lang.util.downcast -import org.partiql.lang.util.getPrecisionFromTimeString -import org.partiql.lang.util.isNaN -import org.partiql.lang.util.isNegInf -import org.partiql.lang.util.isPosInf -import org.partiql.types.BagType -import org.partiql.types.BlobType -import org.partiql.types.BoolType -import org.partiql.types.ClobType -import org.partiql.types.DateType -import org.partiql.types.DecimalType -import org.partiql.types.FloatType -import org.partiql.types.IntType -import org.partiql.types.ListType -import org.partiql.types.MissingType -import org.partiql.types.NullType -import org.partiql.types.NumberConstraint -import org.partiql.types.SexpType -import org.partiql.types.SingleType -import org.partiql.types.StringType -import org.partiql.types.SymbolType -import org.partiql.types.TimeType -import org.partiql.types.TimestampType -import java.math.BigDecimal -import java.math.MathContext -import java.math.RoundingMode -import java.time.LocalDate -import java.time.LocalTime -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeParseException -import java.util.TreeMap -import java.util.TreeSet -import kotlin.math.round - -const val MISSING_ANNOTATION = "\$missing" -const val BAG_ANNOTATION = "\$bag" -const val DATE_ANNOTATION = "\$date" -const val TIME_ANNOTATION = "\$time" -const val GRAPH_ANNOTATION = "\$graph" - -/** - * Wraps the given [ExprValue] with a delegate that provides the [OrderedBindNames] facet. - */ -fun ExprValue.orderedNamesValue(names: List): ExprValue = - object : ExprValue by this, OrderedBindNames { - override val orderedNames = names - override fun asFacet(type: Class?): T? = - downcast(type) ?: this@orderedNamesValue.asFacet(type) - override fun toString(): String = stringify() - } - -val ExprValue.orderedNames: List? - get() = asFacet(OrderedBindNames::class.java)?.orderedNames - -/** Wraps this [ExprValue] as a [Named] instance. */ -fun ExprValue.asNamed(): Named = object : Named { - override val name: ExprValue - get() = this@asNamed -} - -/** Binds the given name value as a [Named] facet delegate over this [ExprValue]. */ -fun ExprValue.namedValue(nameValue: ExprValue): ExprValue = NamedExprValue(nameValue, this) - -/** Wraps this [ExprValue] in a delegate that always masks the [Named] facet. */ -fun ExprValue.unnamedValue(): ExprValue = when (asFacet(Named::class.java)) { - null -> this - else -> object : ExprValue by this { - override fun asFacet(type: Class?): T? = - when (type) { - // always mask the name facet - Named::class.java -> null - else -> this@unnamedValue.asFacet(type) - } - override fun toString(): String = stringify() - } -} - -val ExprValue.name: ExprValue? - get() = asFacet(Named::class.java)?.name - -val ExprValue.address: ExprValue? - get() = asFacet(Addressed::class.java)?.address - -fun ExprValue.booleanValue(): Boolean = - scalar.booleanValue() ?: errNoContext("Expected boolean: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -fun ExprValue.numberValue(): Number = - scalar.numberValue() ?: errNoContext("Expected number: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -fun ExprValue.dateValue(): LocalDate = - scalar.dateValue() ?: errNoContext("Expected date: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -fun ExprValue.timeValue(): Time = - scalar.timeValue() ?: errNoContext("Expected time: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -fun ExprValue.timestampValue(): Timestamp = - scalar.timestampValue() ?: errNoContext("Expected timestamp: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -fun ExprValue.stringValue(): String = - scalar.stringValue() ?: errNoContext("Expected string: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -fun ExprValue.bytesValue(): ByteArray = - scalar.bytesValue() ?: errNoContext("Expected byte array: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -internal fun ExprValue.dateTimePartValue(): DateTimePart = - try { - DateTimePart.valueOf(this.stringValue().toUpperCase()) - } catch (e: IllegalArgumentException) { - throw EvaluationException( - cause = e, - message = "invalid datetime part, valid values: [${DateTimePart.values().joinToString()}]", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_DATE_PART, - internal = false - ) - } - -fun ExprValue.intValue(): Int = this.numberValue().toInt() - -fun ExprValue.longValue(): Long = this.numberValue().toLong() - -fun ExprValue.bigDecimalValue(): BigDecimal = this.numberValue().toString().toBigDecimal() - -/** - * Implements the `FROM` range operation. - * Specifically, this is distinct from the normal [ExprValue.iterator] in that - * types that are **not** [ExprValueType.isRangeFrom] get treated as a singleton - * as per PartiQL specification. - */ -fun ExprValue.rangeOver(): Iterable = when { - type.isRangedFrom -> this - // everything else ranges as a singleton unnamed value - else -> listOf(this.unnamedValue()) -} - -/** A very simple string representation--to be used for diagnostic purposes only. */ -fun ExprValue.stringify(): String = - ConfigurableExprValueFormatter.standard.format(this) - -val DEFAULT_COMPARATOR = NaturalExprValueComparators.NULLS_FIRST_ASC - -/** Provides the default equality function. */ -fun ExprValue.exprEquals(other: ExprValue): Boolean = DEFAULT_COMPARATOR.compare(this, other) == 0 - -/** - * Provides the comparison predicate--which is not a total ordering. - * - * In particular, this operation will fail for non-comparable types. - * For a total ordering over the PartiQL type space, see [NaturalExprValueComparators] - */ -operator fun ExprValue.compareTo(other: ExprValue): Int { - return when { - type.isUnknown || other.type.isUnknown -> - throw EvaluationException("Null value cannot be compared: $this, $other", errorCode = ErrorCode.EVALUATOR_INVALID_COMPARISION, internal = false) - isDirectlyComparableTo(other) -> DEFAULT_COMPARATOR.compare(this, other) - else -> errNoContext("Cannot compare values: $this, $other", errorCode = ErrorCode.EVALUATOR_INVALID_COMPARISION, internal = false) - } -} - -/** - * Checks if the two ExprValues are directly comparable. - * Directly comparable is used in the context of the `<`/`<=`/`>`/`>=` operators. - */ -internal fun ExprValue.isDirectlyComparableTo(other: ExprValue): Boolean = - when { - // The ExprValue type for TIME and TIME WITH TIME ZONE is same - // and thus needs to be checked explicitly for the timezone values. - type == ExprValueType.TIME && other.type == ExprValueType.TIME -> - timeValue().isDirectlyComparableTo(other.timeValue()) - else -> type.isDirectlyComparableTo(other.type) - } - -/** Types that are cast to the [ExprValueType.isText] types by calling `IonValue.toString()`. */ -private val ION_TEXT_STRING_CAST_TYPES = setOf(ExprValueType.BOOL, ExprValueType.TIMESTAMP) - -/** Regex to match DATE strings of the format yyyy-MM-dd */ -private val datePatternRegex = Regex("\\d\\d\\d\\d-\\d\\d-\\d\\d") - -private val genericTimeRegex = Regex("\\d\\d:\\d\\d:\\d\\d(\\.\\d*)?([+|-]\\d\\d:\\d\\d)?") - -/** - * Casts this [ExprValue] to the target type. - * - * `MISSING` and `NULL` always convert to themselves no matter the target type. When the - * source type and target type are the same, this operation is a no-op. - * - * The conversion *to* a particular type is as follows, any conversion not specified raises - * an [EvaluationException]: - * - * * `BOOL` - * * Number types will convert to `false` if numerically equal to zero, `true` otherwise. - * * Text types will convert to `true` if case-insensitive text is `"true"`, - * convert to `false` if case-insensitive text is `"true"` and throw an error otherwise. - * * `INT`, `FLOAT`, and `DECIMAL` - * * `BOOL` converts as `1` for `true` and `0` for `false` - * * Number types will narrow or widen from the source type. Narrowing is a truncation - * * Text types will convert using base-10 integral notation - * * For `FLOAT` and `DECIMAL` targets, decimal and e-notation is also supported. - * * `TIMESTAMP` - * * Text types will convert using the Ion text notation for timestamp (W3C/ISO-8601). - * * `DATE` - * * `TIMESTAMP` converts as `DATE` throwing away the additional information such as time. - * * Text types converts as `DATE` if the case-insensitive text is a valid ISO 8601 format date string. - * * `TIME` - * * `TIMESTAMP` converts as `TIME` throwing away the additional information such as date and time zone. - * * Text types converts as `TIME` if the case-insensitive text is a valid ISO 8601 format time string. - * * `TIME` and `TIME WITH TIME ZONE` converts as `TIME` throwing away the time zone information. - * * `TIME WITH TIME ZONE` - * * `TIMESTAMP` converts as `TIME WITH TIME ZONE` only if the timezone is defined in the TIMESTAMP value. - * The conversion throws away the additional information such as date. - * * Text types converts as `TIME WITH TIME ZONE` if the case-insensitive text is a valid ISO 8601 format time string. - * If the time zone is not specified, then the default time zone is used. - * * `TIME` and `TIME WITH TIME ZONE` converts as `TIME WITH TIME ZONE`. - * If the time zone is not specified, then the default time zone is used. - * * `STRING` and `SYMBOL` - * * `BOOL` converts to `STRING` as `"true"` and `"false"`; - * converts to `SYMBOL` as `'true'` and `'false'`. - * * Number types convert to decimal form with optional e-notation. - * * `TIMESTAMP` converts to the ISO-8601 format. - * * `BLOB` and `CLOB` can only convert between each other directly. - * * `LIST` and `SEXP` - * * Convert directly between each other. - * * `BAG` converts with an *arbitrary* order. - * * `STRUCT` only supports casting from itself. - * * `BAG` converts from `LIST` and `SEXP` by drops order guarantees. - * - * Note that *text types* is defined by [ExprValueType.isText], *number types* is defined by - * [ExprValueType.isNumber], and *LOB types* is defined by [ExprValueType.isLob] - * - * @param targetType The target type to cast this value to. - * @param valueFactory The ExprValueFactory used to create ExprValues. - * @param typedOpBehavior TypedOpBehavior indicating how CAST should behave. - * @param locationMeta The source location for the CAST. Used for error reporting. - * @param defaultTimezoneOffset Default timezone offset to be used when TIME WITH TIME ZONE does not explicitly - * specify the time zone. - */ -fun ExprValue.cast( - targetType: SingleType, - typedOpBehavior: TypedOpBehavior, - locationMeta: SourceLocationMeta?, - defaultTimezoneOffset: ZoneOffset -): ExprValue { - fun castExceptionContext(): PropertyValueMap { - val errorContext = PropertyValueMap().also { - it[Property.CAST_FROM] = this.type.toString() - it[Property.CAST_TO] = getRuntimeType(targetType).toString() - } - - locationMeta?.let { fillErrorContext(errorContext, it) } - - return errorContext - } - - fun castFailedErr(message: String, internal: Boolean, cause: Throwable? = null): Nothing { - val errorContext = castExceptionContext() - - val errorCode = if (locationMeta == null) { - ErrorCode.EVALUATOR_CAST_FAILED_NO_LOCATION - } else { - ErrorCode.EVALUATOR_CAST_FAILED - } - - throw EvaluationException( - message = message, - errorCode = errorCode, - errorContext = errorContext, - internal = internal, - cause = cause - ) - } - - val longMaxDecimal = bigDecimalOf(Long.MAX_VALUE) - val longMinDecimal = bigDecimalOf(Long.MIN_VALUE) - - fun Number.exprValue(type: SingleType) = when (type) { - is IntType -> { - // If the source is Positive/Negative Infinity or Nan, We throw an error - if (this.isNaN || this.isNegInf || this.isPosInf) { - castFailedErr("Can't convert Infinity or NaN to INT.", internal = false) - } - - val rangeForType = when (typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> - when (type.rangeConstraint) { - // There is not CAST syntax to that can execute this branch today. - IntType.IntRangeConstraint.SHORT -> LongRange(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()) - IntType.IntRangeConstraint.INT4 -> LongRange(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()) - IntType.IntRangeConstraint.LONG, IntType.IntRangeConstraint.UNCONSTRAINED -> - LongRange(Long.MIN_VALUE, Long.MAX_VALUE) - } - } - - // Here, we check if there is a possibility of being able to fit this number into - // any of the integer types. We allow the buffer of 1 because we allow rounding into min/max values. - if (this <= (longMinDecimal - BigDecimal.ONE) || this >= (longMaxDecimal + BigDecimal.ONE)) { - errIntOverflow(8) - } - - // We round the value to the nearest integral value - // In legacy behavior, this always picks the floor integer value - // Else, rounding is done through https://en.wikipedia.org/wiki/Rounding#Round_half_to_even - // We don't convert the result to Long within the when block here - // because the rounded values can still be out of range for Kotlin's Long. - val result = when (typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> when (this) { - is BigDecimal -> this.setScale(0, RoundingMode.HALF_EVEN) - // [kotlin.math.round] rounds towards the closes even number on tie - // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.math/round.html - is Float -> round(this) - is Double -> round(this) - else -> this - } - }.let { - // after rounding, check that the value can fit into range of the type being casted into - if (it < rangeForType.first || it > rangeForType.last) { - errIntOverflow(8) - } - it.toLong() - } - ExprValue.newInt(result) - } - is FloatType -> ExprValue.newFloat(this.toDouble()) - is DecimalType -> { - if (this.isNaN || this.isNegInf || this.isPosInf) { - castFailedErr("Can't convert Infinity or NaN to DECIMAL.", internal = false) - } - - when (typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> - when (val constraint = type.precisionScaleConstraint) { - DecimalType.PrecisionScaleConstraint.Unconstrained -> ExprValue.newDecimal(this.coerce(BigDecimal::class.java)) - is DecimalType.PrecisionScaleConstraint.Constrained -> { - val decimal = this.coerce(BigDecimal::class.java) - val result = decimal.round(MathContext(constraint.precision)) - .setScale(constraint.scale, RoundingMode.HALF_UP) - if (result.precision() > constraint.precision) { - // Following PostgresSQL behavior here. Java will increase precision if needed. - castFailedErr("target type DECIMAL(${constraint.precision}, ${constraint.scale}) too small for value $decimal.", internal = false) - } else { - ExprValue.newDecimal(result) - } - } - } - } - } - else -> castFailedErr("Invalid type for numeric conversion: $type (this code should be unreachable)", internal = true) - } - - fun String.exprValue(type: SingleType) = when (type) { - is StringType -> when (typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> when (val constraint = type.lengthConstraint) { - StringType.StringLengthConstraint.Unconstrained -> ExprValue.newString(this) - is StringType.StringLengthConstraint.Constrained -> { - val actualCodepointCount = this.codePointCount(0, this.length) - val lengthConstraint = constraint.length.value - val truncatedString = if (actualCodepointCount <= lengthConstraint) { - this // no truncation needed - } else { - this.substring(0, this.offsetByCodePoints(0, lengthConstraint)) - } - - ExprValue.newString( - when (constraint.length) { - is NumberConstraint.Equals -> truncatedString.trimEnd { c -> c == '\u0020' } - is NumberConstraint.UpTo -> truncatedString - } - ) - } - } - } - is SymbolType -> ExprValue.newSymbol(this) - - else -> castFailedErr("Invalid type for textual conversion: $type (this code should be unreachable)", internal = true) - } - - when { - type.isUnknown && targetType is MissingType -> return ExprValue.missingValue - type.isUnknown && targetType is NullType -> return ExprValue.nullValue - type.isUnknown -> return this - // Note that the ExprValueType for TIME and TIME WITH TIME ZONE is the same i.e. ExprValueType.TIME. - // We further need to check for the time zone and hence we do not short circuit here when the type is TIME. - type == getRuntimeType(targetType) && type != ExprValueType.TIME -> { - return when (targetType) { - is IntType, is FloatType, is DecimalType -> numberValue().exprValue(targetType) - is StringType -> stringValue().exprValue(targetType) - else -> this - } - } - else -> { - when (targetType) { - is BoolType -> when { - type.isNumber -> return when { - numberValue().compareTo(0L) == 0 -> ExprValue.newBoolean(false) - else -> ExprValue.newBoolean(true) - } - type.isText -> return when (stringValue().lowercase()) { - "true" -> ExprValue.newBoolean(true) - "false" -> ExprValue.newBoolean(false) - else -> castFailedErr("can't convert string value to BOOL", internal = false) - } - } - is IntType -> when { - type == ExprValueType.BOOL -> return if (booleanValue()) 1L.exprValue(targetType) else 0L.exprValue(targetType) - type.isNumber -> return numberValue().exprValue(targetType) - type.isText -> { - // Here, we use ion java library to help the transform from string to int - // TODO: have our own parser implemented and remove dependency on Ion, https://github.com/partiql/partiql-lang-kotlin/issues/956 - fun parseToLong(s: String): Long { - val ion = IonSystemBuilder.standard().build() - val value = try { - val normalized = s.normalizeForCastToInt() - ion.singleValue(normalized) as IonInt - } catch (e: Exception) { - castFailedErr("can't convert string value to INT", internal = false, cause = e) - } - return when (value.integerSize) { - // Our numbers comparison machinery does not handle big integers yet, fail fast - IntegerSize.BIG_INTEGER -> errIntOverflow(8, errorContextFrom(locationMeta)) - else -> value.longValue() - } - } - - return parseToLong(stringValue()).exprValue(targetType) - } - } - is FloatType -> when { - type == ExprValueType.BOOL -> return if (booleanValue()) 1.0.exprValue(targetType) else 0.0.exprValue(targetType) - type.isNumber -> return numberValue().toDouble().exprValue(targetType) - type.isText -> - try { - return stringValue().toDouble().exprValue(targetType) - } catch (e: NumberFormatException) { - castFailedErr("can't convert string value to FLOAT", internal = false, cause = e) - } - } - is DecimalType -> when { - type == ExprValueType.BOOL -> return if (booleanValue()) { - BigDecimal.ONE.exprValue(targetType) - } else { - BigDecimal.ZERO.exprValue(targetType) - } - type.isNumber -> return numberValue().exprValue(targetType) - type.isText -> try { - return bigDecimalOf(stringValue()).exprValue(targetType) - } catch (e: NumberFormatException) { - castFailedErr("can't convert string value to DECIMAL", internal = false, cause = e) - } - } - is TimestampType -> when { - type.isText -> try { - return ExprValue.newTimestamp(Timestamp.valueOf(stringValue())) - } catch (e: IllegalArgumentException) { - castFailedErr("can't convert string value to TIMESTAMP", internal = false, cause = e) - } - } - is DateType -> when { - type == ExprValueType.TIMESTAMP -> { - val ts = timestampValue() - return ExprValue.newDate(LocalDate.of(ts.year, ts.month, ts.day)) - } - type.isText -> try { - // validate that the date string follows the format YYYY-MM-DD - if (!datePatternRegex.matches(stringValue())) { - castFailedErr( - "Can't convert string value to DATE. Expected valid date string " + - "and the date format to be YYYY-MM-DD", - internal = false - ) - } - val date = LocalDate.parse(stringValue()) - return ExprValue.newDate(date) - } catch (e: DateTimeParseException) { - castFailedErr( - "Can't convert string value to DATE. Expected valid date string " + - "and the date format to be YYYY-MM-DD", - internal = false, cause = e - ) - } - } - is TimeType -> { - val precision = targetType.precision - when { - type == ExprValueType.TIME -> { - val time = timeValue() - val timeZoneOffset = when (targetType.withTimeZone) { - true -> time.zoneOffset ?: defaultTimezoneOffset - else -> null - } - return ExprValue.newTime( - Time.of( - time.localTime, - precision ?: time.precision, - timeZoneOffset - ) - ) - } - type == ExprValueType.TIMESTAMP -> { - val ts = timestampValue() - val timeZoneOffset = when (targetType.withTimeZone) { - true -> ts.localOffset ?: castFailedErr( - "Can't convert timestamp value with unknown local offset (i.e. -00:00) to TIME WITH TIME ZONE.", - internal = false - ) - else -> null - } - return ExprValue.newTime( - Time.of( - ts.hour, - ts.minute, - ts.second, - (ts.decimalSecond.remainder(BigDecimal.ONE).multiply(NANOS_PER_SECOND.toBigDecimal())).toInt(), - precision ?: ts.decimalSecond.scale(), - timeZoneOffset - ) - ) - } - type.isText -> try { - // validate that the time string follows the format HH:MM:SS[.ddddd...][+|-HH:MM] - val matcher = genericTimeRegex.toPattern().matcher(stringValue()) - if (!matcher.find()) { - castFailedErr( - "Can't convert string value to TIME. Expected valid time string " + - "and the time to be of the format HH:MM:SS[.ddddd...][+|-HH:MM]", - internal = false - ) - } - - val localTime = LocalTime.parse(stringValue(), DateTimeFormatter.ISO_TIME) - - // Note that the [genericTimeRegex] has a group to extract the zone offset. - val zoneOffsetString = matcher.group(2) - val zoneOffset = zoneOffsetString?.let { ZoneOffset.of(it) } ?: defaultTimezoneOffset - - return ExprValue.newTime( - Time.of( - localTime, - precision ?: getPrecisionFromTimeString(stringValue()), - when (targetType.withTimeZone) { - true -> zoneOffset - else -> null - } - ) - ) - } catch (e: DateTimeParseException) { - castFailedErr( - "Can't convert string value to TIME. Expected valid time string " + - "and the time format to be HH:MM:SS[.ddddd...][+|-HH:MM]", - internal = false, cause = e - ) - } - } - } - is StringType, is SymbolType -> when { - type.isNumber -> return numberValue().toString().exprValue(targetType) - type.isText -> return stringValue().exprValue(targetType) - type == ExprValueType.DATE -> return dateValue().toString().exprValue(targetType) - type == ExprValueType.TIME -> return timeValue().toString().exprValue(targetType) - type == ExprValueType.BOOL -> return booleanValue().toString().exprValue(targetType) - type == ExprValueType.TIMESTAMP -> return timestampValue().toString().exprValue(targetType) - } - is ClobType -> when { - type.isLob -> return ExprValue.newClob(bytesValue()) - } - is BlobType -> when { - type.isLob -> return ExprValue.newBlob(bytesValue()) - } - is ListType -> if (type.isSequence) return ExprValue.newList(asSequence()) - is SexpType -> if (type.isSequence) return ExprValue.newSexp(asSequence()) - is BagType -> if (type.isSequence) return ExprValue.newBag(asSequence()) - // no support for anything else - else -> {} - } - } - } - - val errorCode = if (locationMeta == null) { - ErrorCode.EVALUATOR_INVALID_CAST_NO_LOCATION - } else { - ErrorCode.EVALUATOR_INVALID_CAST - } - - // incompatible types - err("Cannot convert $type to $targetType", errorCode, castExceptionContext(), internal = false) -} -/** - * Remove leading spaces in decimal notation and the plus sign - * - * Examples: - * - `"00001".normalizeForIntCast() == "1"` - * - `"-00001".normalizeForIntCast() == "-1"` - * - `"0x00001".normalizeForIntCast() == "0x00001"` - * - `"+0x00001".normalizeForIntCast() == "0x00001"` - * - `"000a".normalizeForIntCast() == "a"` - */ -private fun String.normalizeForCastToInt(): String { - fun Char.isSign() = this == '-' || this == '+' - fun Char.isHexOrBase2Marker(): Boolean { - val c = this.lowercaseChar() - - return c == 'x' || c == 'b' - } - - fun String.possiblyHexOrBase2() = (length >= 2 && this[1].isHexOrBase2Marker()) || - (length >= 3 && this[0].isSign() && this[2].isHexOrBase2Marker()) - - return when { - length == 0 -> this - possiblyHexOrBase2() -> { - if (this[0] == '+') { - this.drop(1) - } else { - this - } - } - else -> { - val (isNegative, startIndex) = when (this[0]) { - '-' -> Pair(true, 1) - '+' -> Pair(false, 1) - else -> Pair(false, 0) - } - - var toDrop = startIndex - while (toDrop < length && this[toDrop] == '0') { - toDrop += 1 - } - - when { - toDrop == length -> "0" // string is all zeros - toDrop == 0 -> this - toDrop == 1 && isNegative -> this - toDrop > 1 && isNegative -> '-' + this.drop(toDrop) - else -> this.drop(toDrop) - } - } - } -} - -/** - * An Unknown value is one of `MISSING` or `NULL` - */ -internal fun ExprValue.isUnknown(): Boolean = this.type.isUnknown -/** - * The opposite of [isUnknown]. - */ -internal fun ExprValue.isNotUnknown(): Boolean = !this.type.isUnknown - -/** - * Creates a filter for unique ExprValues consistent with exprEquals. This filter is stateful keeping track of - * seen [ExprValue]s. - * - * This filter is **stateful**! - * - * @return false if the value was seen before - */ -internal fun createUniqueExprValueFilter(): (ExprValue) -> Boolean { - val seen = TreeSet(DEFAULT_COMPARATOR) - - return { exprValue -> seen.add(exprValue) } -} - -fun Sequence.distinct(): Sequence { - return sequence { - val seen = TreeSet(DEFAULT_COMPARATOR) - this@distinct.forEach { - if (!seen.contains(it)) { - seen.add(it.unnamedValue()) - yield(it) - } - } - } -} - -fun Sequence.multiplicities(): TreeMap { - val multiplicities: TreeMap = TreeMap(DEFAULT_COMPARATOR) - this.forEach { - multiplicities.compute(it) { _, v -> (v ?: 0) + 1 } - } - return multiplicities -} - -/** - * This method should only be used in case we want to get result from querying an Ion file or an [IonValue] - */ -fun ExprValue.toIonValue(ion: IonSystem): IonValue = - when (type) { - ExprValueType.NULL -> ion.newNull(asFacet(IonType::class.java)) - ExprValueType.MISSING -> ion.newNull().apply { addTypeAnnotation(MISSING_ANNOTATION) } - ExprValueType.BOOL -> ion.newBool(booleanValue()) - ExprValueType.INT -> ion.newInt(longValue()) - ExprValueType.FLOAT -> ion.newFloat(numberValue().toDouble()) - ExprValueType.DECIMAL -> ion.newDecimal(bigDecimalValue()) - ExprValueType.DATE -> { - val value = dateValue() - ion.newTimestamp(Timestamp.forDay(value.year, value.monthValue, value.dayOfMonth)).apply { - addTypeAnnotation(DATE_ANNOTATION) - } - } - ExprValueType.TIMESTAMP -> ion.newTimestamp(timestampValue()) - ExprValueType.TIME -> timeValue().toIonValue(ion) - ExprValueType.SYMBOL -> ion.newSymbol(stringValue()) - ExprValueType.STRING -> ion.newString(stringValue()) - ExprValueType.CLOB -> ion.newClob(bytesValue()) - ExprValueType.BLOB -> ion.newBlob(bytesValue()) - ExprValueType.LIST -> mapTo(ion.newEmptyList()) { - if (it is StructExprValue) - it.toIonStruct(ion) - else - it.toIonValue(ion).clone() - } - ExprValueType.SEXP -> mapTo(ion.newEmptySexp()) { - if (it is StructExprValue) - it.toIonStruct(ion) - else - it.toIonValue(ion).clone() - } - ExprValueType.BAG -> mapTo( - ion.newEmptyList().apply { addTypeAnnotation(BAG_ANNOTATION) } - ) { - if (it is StructExprValue) - it.toIonStruct(ion) - else - it.toIonValue(ion).clone() - } - ExprValueType.STRUCT -> toIonStruct(ion) - ExprValueType.GRAPH -> TODO("Ion representation for graph values, maybe?") - } - -private fun ExprValue.toIonStruct(ion: IonSystem): IonStruct { - return ion.newEmptyStruct().apply { - this@toIonStruct.forEach { - val nameVal = it.name - if (nameVal != null && nameVal.type.isText && it.type != ExprValueType.MISSING) { - val name = nameVal.stringValue() - add(name, it.toIonValue(ion).clone()) - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprValueType.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprValueType.kt deleted file mode 100644 index 88a7635172..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExprValueType.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ion.IonType - -/** - * The core types of [ExprValue] that exist within the type system of the evaluator. - * There is a correspondence to [IonType], but it isn't quite one-to-one. - * - * @param isRangedFrom Whether or not the `FROM` clause uses the value's iterator directly. - */ -enum class ExprValueType( - val isUnknown: Boolean = false, - val isNumber: Boolean = false, - val isText: Boolean = false, - val isLob: Boolean = false, - val isSequence: Boolean = false, - val isRangedFrom: Boolean = false, - val isScalar: Boolean = false -) { - MISSING(isUnknown = true), - NULL(isUnknown = true), - BOOL(isScalar = true), - INT(isNumber = true, isScalar = true), - FLOAT(isNumber = true, isScalar = true), - DECIMAL(isNumber = true, isScalar = true), - DATE(isScalar = true), - TIMESTAMP(isScalar = true), - TIME(isScalar = true), - SYMBOL(isText = true, isScalar = true), - STRING(isText = true, isScalar = true), - CLOB(isLob = true, isScalar = true), - BLOB(isLob = true, isScalar = true), - LIST(isSequence = true, isRangedFrom = true), - SEXP(isSequence = true), - STRUCT, - BAG(isSequence = true, isRangedFrom = true), - GRAPH; - - /** Whether or not the given type is in the same type grouping as another. */ - fun isDirectlyComparableTo(other: ExprValueType): Boolean = - (this == other) || - (isNumber && other.isNumber) || - (isText && other.isText) || - (isLob && other.isLob) - - companion object { - private val ION_TYPE_MAP = enumValues().asSequence() - .map { - val ourType = when (it) { - IonType.DATAGRAM -> BAG - else -> valueOf(it.name) - } - Pair(it, ourType) - }.toMap() - - /** Maps an [IonType] to an [ExprValueType]. */ - fun fromIonType(ionType: IonType): ExprValueType = ION_TYPE_MAP[ionType]!! - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Expression.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Expression.kt deleted file mode 100644 index e3e866cdfa..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Expression.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * An expression that can be evaluated to [ExprValue]. - */ -interface Expression { - - /** - * Pre-Execution Coverage Statistics - */ - val coverageStructure: CoverageStructure? - - /** - * Evaluates the expression with the given Session - */ - @Deprecated("To be removed in the next release.", replaceWith = ReplaceWith("evaluate")) - fun eval(session: EvaluationSession): ExprValue - - /** - * Evaluates the expression with the given Session - */ - fun evaluate(session: EvaluationSession): PartiQLResult -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExpressionAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExpressionAsync.kt deleted file mode 100644 index c3bf60291f..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ExpressionAsync.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * An expression that can be evaluated to [ExprValue]. - */ -internal interface ExpressionAsync { - /** - * Evaluates the [ExpressionAsync] with the given Session - */ - suspend fun eval(session: EvaluationSession): PartiQLResult -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Group.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Group.kt deleted file mode 100644 index 36cf91f14e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Group.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * Defines a group and tracks the values of each group as they are processed into a final result. - */ -internal class Group(val key: ExprValue, val registers: RegisterBank) { - val groupValues: MutableList = ArrayList() -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/GroupKeyExprValue.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/GroupKeyExprValue.kt deleted file mode 100644 index 00625f56e9..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/GroupKeyExprValue.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import org.partiql.lang.eval.visitors.GroupByItemAliasVisitorTransform - -/** - * This is a special [ExprValue] just for group keys. - * - * It derives from [StructExprValue] and adds a second set of bindings for the "unique name" assigned to - * group by expressions. See [GroupByItemAliasVisitorTransform] and other uses of - * [org.partiql.lang.ast.UniqueNameMeta]. - */ -internal class GroupKeyExprValue(sequence: Sequence, private val uniqueNames: Map) : - StructExprValue(StructOrdering.UNORDERED, sequence) { - - private val keyBindings by lazy { - when { - uniqueNames.any() -> Bindings.ofMap(uniqueNames).delegate(super.bindings) - else -> super.bindings - } - } - - override val bindings: Bindings - get() = keyBindings -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/IonStructBindings.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/IonStructBindings.kt deleted file mode 100644 index 9893c92171..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/IonStructBindings.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ion.IonStruct -import com.amazon.ion.IonValue -import org.partiql.lang.util.errAmbiguousBinding - -/** - * Custom implementation of [Bindings] that lazily computes case sensitive or insensitive hash tables which - * will speed up the lookup of bindings within structs. - * - * The key difference in behavior between this and other [Bindings] implementations is that it - * can throw an ambiguous binding [EvaluationException] even for case-sensitive lookups as it is - * entirely possible that fields with identical names can appear within [IonStruct]s. - * - * Important: this class is critical to performance for many queries. Change with caution. - */ -internal class IonStructBindings(private val myStruct: IonStruct) : Bindings { - - private val caseInsensitiveFieldMap by lazy { - HashMap>().apply { - for (field in myStruct) { - val entries = getOrPut(field.fieldName.lowercase()) { ArrayList(1) } - entries.add(field) - } - } - } - - private val caseSensitiveFieldMap by lazy { - HashMap>().apply { - for (field in myStruct) { - val entries = getOrPut(field.fieldName) { ArrayList(1) } - entries.add(field) - } - } - } - - private fun caseSensitiveLookup(fieldName: String): IonValue? = - caseSensitiveFieldMap[fieldName]?.let { entries -> handleMatches(entries, fieldName) } - - private fun caseInsensitiveLookup(fieldName: String): IonValue? = - caseInsensitiveFieldMap[fieldName.lowercase()]?.let { entries -> handleMatches(entries, fieldName) } - - private fun handleMatches(entries: List, fieldName: String): IonValue? = - when (entries.size) { - 0 -> null - 1 -> entries[0] - else -> - errAmbiguousBinding(fieldName, entries.map { it.fieldName }) - } - - override operator fun get(bindingName: BindingName): ExprValue? = - when (bindingName.bindingCase) { - BindingCase.SENSITIVE -> caseSensitiveLookup(bindingName.name) - BindingCase.INSENSITIVE -> caseInsensitiveLookup(bindingName.name) - }?.let { - ExprValue.of(it).namedValue(ExprValue.newString(it.fieldName)) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Named.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Named.kt deleted file mode 100644 index 2ea4199ddd..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Named.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import org.partiql.lang.util.downcast - -/** - * Facet for a value to indicate that it either has a name within some context - * or an ordinal position. - * - * An implementation should not provide this facet if it does not provide a meaningful name. - */ -interface Named { - /** - * The name of this value, generally a `string` for values that have a field name in - * a `struct` or an `int` for values that have some ordinal in a collection. - */ - val name: ExprValue -} - -/** - * An [ExprValue] that also implements [Named]. - */ -internal class NamedExprValue(override val name: ExprValue, val value: ExprValue) : ExprValue by value, Named { - override fun asFacet(type: Class?): T? = downcast(type) ?: value.asFacet(type) - - override fun toString(): String = stringify() -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/NaturalExprValueComparators.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/NaturalExprValueComparators.kt deleted file mode 100644 index 034229b94b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/NaturalExprValueComparators.kt +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.NaturalExprValueComparators.NullOrder -import org.partiql.lang.eval.NaturalExprValueComparators.Order -import org.partiql.lang.util.compareTo -import org.partiql.lang.util.isNaN -import org.partiql.lang.util.isNegInf -import org.partiql.lang.util.isPosInf -import org.partiql.lang.util.isZero - -/** - * Provides a total, natural ordering over [ExprValue]. This ordering is consistent with - * [ExprValue.exprEquals] with the exception that `NULL` and `MISSING` compare with themselves - * and have order. PartiQL treats Ion typed nulls as `NULL` for the purposes of comparisons - * and Ion annotations are not considered for comparison purposes. - * - * The ordering rules are as follows: - * - * * `NULL` and `MISSING` are always first or last and compare equally. In other words, - * comparison cannot distinguish between `NULL` or `MISSING`. - * * The `BOOL` values follow with `false` coming before `true`. - * * The [ExprValueType.isNumber] types come next ordered by their numerical value irrespective - * of precision or specific type. - * For `FLOAT` special values, `nan` comes before `-inf`, which comes before all normal - * numeric values, which is followed by `+inf`. - * * `DATE` values follow and are compared by the date from earliest to latest. - * * `TIME` values follow and are compared by the time of the day (point of time in a day of 24 hours) - * from earliest to latest. Note that time without time zone is not directly comparable with time with time zone. - * However, time without time zone value comes before time with time zone value when compared in the natural order. - * * `TIMESTAMP` values follow and are compared by the point of time irrespective of precision and - * local UTC offset. - * * The [ExprValueType.isText] types come next ordered by their lexicographical ordering by - * Unicode scalar irrespective of their specific type. - * * The [ExprValueType.isLob] types follow and are ordered by their lexicographical ordering - * by octet. - * * `LIST` comes next, and their values compare lexicographically based on their - * child elements recursively based on this definition. - * * `SEXP` follows and compares within its type similar to `LIST`. - * * `STRUCT` values follow and compare lexicographically based on the *sorted* - * (as defined by this definition) members, as pairs of field name and the member value. - * * `BAG` values come finally (except with [NullOrder.NULLS_LAST]), and their values - * compare lexicographically based on the *sorted* child elements. - * - * @param order that compares left and right values by [Order.ASC] (ascending) or [Order.DESC] (descending) order - * @param nullOrder that places `NULL`/`MISSING` values first or last - */ -enum class NaturalExprValueComparators(private val order: Order, private val nullOrder: NullOrder) : Comparator { - NULLS_FIRST_ASC(Order.ASC, NullOrder.FIRST), - NULLS_FIRST_DESC(Order.DESC, NullOrder.FIRST), - NULLS_LAST_ASC(Order.ASC, NullOrder.LAST), - NULLS_LAST_DESC(Order.DESC, NullOrder.LAST); - - /** Compare items by ascending or descending order */ - private enum class Order { - ASC, - DESC - } - - /** Whether or not null values come first or last. */ - private enum class NullOrder { - FIRST, - LAST - } - - companion object { - private const val EQUAL = 0 - private const val LESS = -1 - private const val MORE = 1 - } - - /** - * Generalizes the type handling predicates in the comparator. - * The premise is that this is used in the order of ascending types so if - * the left type is the specified condition and the right type isn't this implies - * that the left value is less than the right and vice versa. - */ - private inline fun handle( - leftTypeCond: Boolean, - rightTypeCond: Boolean, - sameTypeHandler: () -> Int - ): Int? = when { - - leftTypeCond && rightTypeCond -> sameTypeHandler() - leftTypeCond -> LESS - rightTypeCond -> MORE - else -> null - } - - private inline fun ifCompared(value: Int?, handler: (Int) -> Unit) { - if (value != null) { - handler(value) - } - } - - private fun compareOrdered( - left: Iterable, - right: Iterable, - comparator: Comparator - ): Int { - val lIter = left.iterator() - val rIter = right.iterator() - - while (lIter.hasNext() && rIter.hasNext()) { - val (lChild, rChild) = when (order) { - Order.ASC -> lIter.next() to rIter.next() - Order.DESC -> rIter.next() to lIter.next() - } - val cmp = comparator.compare(lChild, rChild) - if (cmp != 0) { - return cmp - } - } - - return when { - lIter.hasNext() -> MORE - rIter.hasNext() -> LESS - else -> EQUAL - } - } - - private fun compareUnordered( - left: Iterable, - right: Iterable, - entityCmp: Comparator - ): Int { - val pairCmp = object : Comparator> { - override fun compare(o1: Pair, o2: Pair): Int { - val (leftPair, rightPair) = when (order) { - Order.ASC -> o1 to o2 - Order.DESC -> o2 to o1 - } - val cmp = entityCmp.compare(leftPair.first, rightPair.first) - if (cmp != 0) { - return cmp - } - return rightPair.second - leftPair.second - } - } - - fun Iterable.sorted(): Iterable = - this.mapIndexed { i, e -> Pair(e, i) } - .toSortedSet(pairCmp) - .asSequence() - .map { (e, _) -> e } - .asIterable() - - return compareOrdered(left.sorted(), right.sorted(), entityCmp) - } - - private val structFieldComparator = object : Comparator { - override fun compare(left: ExprValue, right: ExprValue): Int { - val lName = left.name ?: errNoContext("Internal error: left struct field has no name", errorCode = ErrorCode.INTERNAL_ERROR, internal = true) - val rName = right.name ?: errNoContext("Internal error: right struct field has no name", errorCode = ErrorCode.INTERNAL_ERROR, internal = true) - val cmp = this@NaturalExprValueComparators.compare(lName, rName) - if (cmp != 0) { - return cmp - } - - return this@NaturalExprValueComparators.compare(left, right) - } - } - - private fun compareInternal(left: ExprValue, right: ExprValue, nullOrder: NullOrder): Int { - if (left === right) return EQUAL - - val lType = left.type - val rType = right.type - - if (nullOrder == NullOrder.FIRST) { - ifCompared(handle(lType.isUnknown, rType.isUnknown) { EQUAL }) { return it } - } - - // Bool - ifCompared( - handle(lType == ExprValueType.BOOL, rType == ExprValueType.BOOL) { - val lVal = left.booleanValue() - val rVal = right.booleanValue() - - when { - lVal == rVal -> EQUAL - !lVal -> LESS - else -> MORE - } - } - ) { return it } - - // Numbers - ifCompared( - handle(lType.isNumber, rType.isNumber) { - val lVal = left.numberValue() - val rVal = right.numberValue() - - ifCompared(handle(lVal.isNaN, rVal.isNaN) { EQUAL }) { return it } - ifCompared(handle(lVal.isNegInf, rVal.isNegInf) { EQUAL }) { return it } - // +inf gets handled in a slightly reverse way than the normal pattern - // because it comes after every other value - when { - lVal.isPosInf && rVal.isPosInf -> return EQUAL - lVal.isPosInf -> return MORE - rVal.isPosInf -> return LESS - lVal.isZero() && rVal.isZero() -> return EQUAL // for negative zero - else -> return lVal.compareTo(rVal) - } - } - ) { return it } - - // Date - ifCompared( - handle(lType == ExprValueType.DATE, rType == ExprValueType.DATE) { - val lVal = left.dateValue() - val rVal = right.dateValue() - - return lVal.compareTo(rVal) - } - ) { return it } - - // Time - ifCompared( - handle(lType == ExprValueType.TIME, rType == ExprValueType.TIME) { - val lVal = left.timeValue() - val rVal = right.timeValue() - - return lVal.naturalOrderCompareTo(rVal) - } - ) { return it } - - // Timestamp - ifCompared( - handle(lType == ExprValueType.TIMESTAMP, rType == ExprValueType.TIMESTAMP) { - val lVal = left.timestampValue() - val rVal = right.timestampValue() - - return lVal.compareTo(rVal) - } - ) { return it } - - // Text - ifCompared( - handle(lType.isText, rType.isText) { - val lVal = left.stringValue() - val rVal = right.stringValue() - - return lVal.compareTo(rVal) - } - ) { return it } - - // LOB - ifCompared( - handle(lType.isLob, rType.isLob) { - val lVal = left.bytesValue() - val rVal = right.bytesValue() - - val commonLen = minOf(lVal.size, rVal.size) - for (i in 0 until commonLen) { - val lOctet = lVal[i].toInt() and 0xFF - val rOctet = rVal[i].toInt() and 0xFF - val diff = lOctet - rOctet - if (diff != 0) { - return diff - } - } - return lVal.size - rVal.size - } - ) { return it } - - // List - ifCompared( - handle(lType == ExprValueType.LIST, rType == ExprValueType.LIST) { - return compareOrdered(left, right, this) - } - ) { return it } - - // Sexp - ifCompared( - handle(lType == ExprValueType.SEXP, rType == ExprValueType.SEXP) { - return compareOrdered(left, right, this) - } - ) { return it } - - // Struct - ifCompared( - handle(lType == ExprValueType.STRUCT, rType == ExprValueType.STRUCT) { - compareUnordered(left, right, structFieldComparator) - } - ) { return it } - - // Bag - ifCompared( - handle(lType == ExprValueType.BAG, rType == ExprValueType.BAG) { - compareUnordered(left, right, this) - } - ) { return it } - - // Graph - // TODO: what should be "PartiQL equality" for graphs? https://github.com/partiql/partiql-spec/issues/55 - // Short of implementing a graph isomorphism check here (expensive in general!), - // it is hard to see what another principled solution can be. - // For now, graphs will be equal only when they are the same object by reference. - // This should be sufficient for the current purposes. - // Fortunately, we do not yet have means to construct graphs in the language, - // so it is only externally-loaded graphs that can bump into each other here. - // It is fairly reasonable to posit that those should be considered distinct. - // (Just make sure not to load the same graph twice, when that matters.) - ifCompared( - handle(lType == ExprValueType.GRAPH, rType == ExprValueType.GRAPH) { - val g1 = left.graphValue - val g2 = right.graphValue - g1.hashCode().compareTo(g2.hashCode()) - } - ) { return it } - - if (nullOrder == NullOrder.LAST) { - ifCompared(handle(lType.isUnknown, rType.isUnknown) { EQUAL }) { return it } - } - - throw IllegalStateException("Could not compare: $left and $right") - } - - // can think of `DESC` as the converse/reverse of `ASC` - // - ASC with NULLS FIRST == DESC with NULLS LAST (reverse) - // - ASC with NULLS LAST == DESC with NULLS FIRST (reverse) - // for `DESC`, return the converse result by multiplying by -1 - // need to also flip the NullOrder in the `DESC` case - override fun compare(left: ExprValue, right: ExprValue): Int { - return when (order) { - Order.ASC -> compareInternal(left, right, nullOrder) - Order.DESC -> compareInternal( - left, right, - when (nullOrder) { - NullOrder.FIRST -> NullOrder.LAST - NullOrder.LAST -> NullOrder.FIRST - } - ) * -1 - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/OrderedBindNames.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/OrderedBindNames.kt deleted file mode 100644 index 716e907c14..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/OrderedBindNames.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * Facet to provide an ordered list of [String] names that are directly bound to - * a given [ExprValue]. - */ -interface OrderedBindNames { - val orderedNames: List -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/OrdinalBindings.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/OrdinalBindings.kt deleted file mode 100644 index b64ed4ac31..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/OrdinalBindings.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** A simple mapping of ordinal index to [ExprValue]. */ -interface OrdinalBindings { - companion object { - // @JvmField - // Note: for some reason, @JvmField being present causes IntelliJ's test runner to throw - // an exception and fail to run any tests when you tell it to run tests in a package. It still works - // when you tell it to run all tests in a single class or individual tests, though. - val EMPTY = object : OrdinalBindings { - override fun get(index: Int): ExprValue? = null - } - - @JvmStatic - fun ofList(list: List) = object : OrdinalBindings { - override fun get(index: Int): ExprValue? = list.getOrNull(index) - } - } - - /** - * Looks up an index within this binding. - * - * @param index The binding to look up. The index is zero-based. - * - * @return The value mapped to the binding, or `null` if no such binding exists. - */ - operator fun get(index: Int): ExprValue? -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiQLResult.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiQLResult.kt deleted file mode 100644 index 2b5b5fc9c2..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiQLResult.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import org.partiql.pig.runtime.DomainNode - -/** - * Result of an evaluated PartiQLStatement. - * - */ -sealed class PartiQLResult { - - /** - * @return information relevant to which branches and branch conditions were executed. As [ExprValue]s - * are lazily created, please materialize any relevant [ExprValue]s before accessing [getCoverageData]. - */ - public abstract fun getCoverageData(): CoverageData? - - /** - * @return static information relevant to what branches/branch conditions are present. - */ - public abstract fun getCoverageStructure(): CoverageStructure? - - class Value( - val value: ExprValue, - private val coverageData: () -> CoverageData? = { null }, - private val coverageStructure: () -> CoverageStructure? = { null } - ) : PartiQLResult() { - override fun getCoverageData(): CoverageData? = coverageData.invoke() - override fun getCoverageStructure(): CoverageStructure? = coverageStructure.invoke() - } - - class Insert( - val target: String, - val rows: Iterable, - private val coverageData: () -> CoverageData? = { null }, - private val coverageStructure: () -> CoverageStructure? = { null } - ) : PartiQLResult() { - override fun getCoverageData(): CoverageData? = coverageData.invoke() - override fun getCoverageStructure(): CoverageStructure? = coverageStructure.invoke() - } - - class Delete( - val target: String, - val rows: Iterable, - private val coverageData: () -> CoverageData? = { null }, - private val coverageStructure: () -> CoverageStructure? = { null } - ) : PartiQLResult() { - override fun getCoverageData(): CoverageData? = coverageData.invoke() - override fun getCoverageStructure(): CoverageStructure? = coverageStructure.invoke() - } - - class Replace( - val target: String, - val rows: Iterable, - private val coverageData: () -> CoverageData? = { null }, - private val coverageStructure: () -> CoverageStructure? = { null } - ) : PartiQLResult() { - override fun getCoverageData(): CoverageData? = coverageData.invoke() - override fun getCoverageStructure(): CoverageStructure? = coverageStructure.invoke() - } - - sealed class Explain : PartiQLResult() { - data class Domain(val value: DomainNode, val format: String?) : Explain() { - override fun getCoverageData(): CoverageData? = null - override fun getCoverageStructure(): CoverageStructure? = null - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiQLStatement.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiQLStatement.kt deleted file mode 100644 index 43dfdb51c9..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiQLStatement.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * A compiled PartiQL statement - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("PartiQLStatementAsync")) -fun interface PartiQLStatement { - @Deprecated("To be removed in next major version.", replaceWith = ReplaceWith("PartiQLStatementAsync.eval")) - fun eval(session: EvaluationSession): PartiQLResult -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiQLStatementAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiQLStatementAsync.kt deleted file mode 100644 index af3fbb6b0d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiQLStatementAsync.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * A compiled PartiQL statement intended to be evaluated from a Kotlin coroutine. - */ -fun interface PartiQLStatementAsync { - - suspend fun eval(session: EvaluationSession): PartiQLResult -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiqlAstExtensions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiqlAstExtensions.kt deleted file mode 100644 index 7c6602766b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/PartiqlAstExtensions.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.partiql.lang.eval - -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.TextElement -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.ast.sourceLocation -import org.partiql.lang.domains.PartiqlAst - -/** - * Determines an appropriate column name for the given [PartiqlAst.Expr]. - * - * If [this] is a [PartiqlAst.Expr.Id], returns the name of the variable. - * - * If [this] is a [PartiqlAst.Expr.Path], invokes [PartiqlAst.Expr.Path.extractColumnAlias] to determine the alias. - * - * If [this] is a [PartiqlAst.Expr.Cast], the column alias is the same as would be given to the [PartiqlAst.Expr.Cast.value] to be `CAST`. - * - * Otherwise, returns the column index prefixed with `_`. - */ -fun PartiqlAst.Expr.extractColumnAlias(idx: Int): String = - when (this) { - is PartiqlAst.Expr.Id -> this.name.text - is PartiqlAst.Expr.Path -> { - this.extractColumnAlias(idx) - } - is PartiqlAst.Expr.Cast -> { - this.value.extractColumnAlias(idx) - } - else -> syntheticColumnName(idx) - } - -/** - * Returns the name of the last component if it is a string literal, otherwise returns the - * column index prefixed with `_`. - */ -fun PartiqlAst.Expr.Path.extractColumnAlias(idx: Int): String { - return when (val nameOrigin = this.steps.last()) { - is PartiqlAst.PathStep.PathExpr -> { - val maybeLiteral = nameOrigin.index - when { - maybeLiteral is PartiqlAst.Expr.Lit && maybeLiteral.value is TextElement -> maybeLiteral.value.textValue - else -> syntheticColumnName(idx) - } - } - else -> syntheticColumnName(idx) - } -} - -/** - * Returns the starting [SourceLocationMeta] found through walking through all nodes of [this] [PartiqlAst.Expr]. - * Starting is defined to be the [SourceLocationMeta] with the lowest [SourceLocationMeta.lineNum] and in the event of - * a tie, the lowest [SourceLocationMeta.charOffset]. - */ -fun PartiqlAst.Expr.getStartingSourceLocationMeta(): SourceLocationMeta { - val visitorFold = object : PartiqlAst.VisitorFold() { - override fun visitMetas(node: MetaContainer, accumulator: SourceLocationMeta): SourceLocationMeta { - val nodeSourceLocation = node.sourceLocation - return nodeSourceLocation?.takeIf { - ( - nodeSourceLocation.lineNum < accumulator.lineNum || - (nodeSourceLocation.lineNum == accumulator.lineNum && nodeSourceLocation.charOffset < accumulator.charOffset) - ) - } ?: accumulator - } - } - return visitorFold.walkExpr(this, SourceLocationMeta(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE)) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Register.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Register.kt deleted file mode 100644 index 485601fe2a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Register.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * Variant type over elements of a [RegisterBank]. - * - * Currently this supports registers that are [ExprAggregator], but may be expanded in the future - * to support other things like [ExprValue] for things like optimized local variable access. - */ -internal abstract class Register { - companion object { - /** The empty register. */ - val EMPTY = object : Register() { - override val aggregator: ExprAggregator - get() = throw UnsupportedOperationException("Register is not an aggregator") - } - } - - /** The [ExprAggregator] value stored in this register. */ - abstract val aggregator: ExprAggregator -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/RegisterBank.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/RegisterBank.kt deleted file mode 100644 index 7395b96605..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/RegisterBank.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** - * Represents a set of internal compiler slots for intermediate execution states. - */ -internal class RegisterBank(size: Int) { - - private val bank: Array = Array(size) { Register.EMPTY } - - /** Retrieves the register stored at the given index. */ - operator fun get(index: Int) = bank[index] - - /** Stores the given [ExprAggregator] into the given register index. */ - operator fun set(index: Int, aggregator: ExprAggregator) { - bank[index] = object : Register() { - override val aggregator = aggregator - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Scalar.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Scalar.kt deleted file mode 100644 index b94bae10d1..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Scalar.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ion.Timestamp -import org.partiql.lang.eval.time.Time -import java.time.LocalDate - -/** - * Represents a scalar view over an [ExprValue]. - */ -interface Scalar { - companion object { - @JvmField - val EMPTY = object : Scalar { } - } - - /** - * Returns this value as a [Boolean] or `null` if not applicable. - * This operation is only applicable for [ExprValueType.BOOL] - */ - fun booleanValue(): Boolean? = null - - /** - * Returns this value as a [Long], [Double], [BigDecimal], or `null` if not applicable. - * This operation is only applicable for [ExprValueType.isNumber] - */ - fun numberValue(): Number? = null - - /** - * Returns this value as a [Timestamp] or `null` if not applicable. - * This operation is only applicable for [ExprValueType.TIMESTAMP] - */ - fun timestampValue(): Timestamp? = null - - /** - * Returns this value as a [LocalDate] or `null` if not applicable. - * This operation is only applicable for [ExprValueType.DATE] - */ - fun dateValue(): LocalDate? = null - - /** - * Returns this value as a [Time] or `null` if not applicable. - * This operation is only applicable for [ExprValueType.TIME]. - */ - fun timeValue(): Time? = null - - /** - * Returns this value as a [String] or `null` if not applicable. - * This operation is only applicable for [ExprValueType.isText] - */ - fun stringValue(): String? = null - - /** - * Returns this value as a [ByteArray], or `null` if not applicable. - * This operation is only applicable for [ExprValueType.isLob] - */ - fun bytesValue(): ByteArray? = null -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/StandardNames.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/StandardNames.kt deleted file mode 100644 index 0dfa3ea9f2..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/StandardNames.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -/** Constructs the column name based on the zero-based index of that column. */ -fun syntheticColumnName(col: Int): String = "_${col + 1}" diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/StructExprValue.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/StructExprValue.kt deleted file mode 100644 index bc0b78369c..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/StructExprValue.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import org.partiql.errors.ErrorCode - -/** Indicates if a struct is ordered or not. */ -enum class StructOrdering { - UNORDERED, - ORDERED -} - -/** - * Provides a [ExprValueType.STRUCT] implementation lazily backed by a sequence. - */ -internal open class StructExprValue( - internal val ordering: StructOrdering, - private val sequence: Sequence -) : BaseExprValue() { - - override val type = ExprValueType.STRUCT - - /** The backing data structured for operations that require materialization. */ - private data class Materialized( - val bindings: Bindings, - val ordinalBindings: OrdinalBindings, - val orderedBindNames: OrderedBindNames? - ) - - private val materialized by lazy { - val bindMap = HashMap() - val bindList = ArrayList() - val bindNames = ArrayList() - sequence.forEach { - val name = it.name?.stringValue() ?: errNoContext("Expected non-null name for lazy struct", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE, internal = false) - bindMap.putIfAbsent(name, it) - if (ordering == StructOrdering.ORDERED) { - bindList.add(it) - bindNames.add(name) - } - } - - val bindings = Bindings.ofMap(bindMap) - val ordinalBindings = OrdinalBindings.ofList(bindList) - val orderedBindNames = when (ordering) { - StructOrdering.ORDERED -> object : OrderedBindNames { - override val orderedNames = bindNames - } - StructOrdering.UNORDERED -> null - } - - Materialized(bindings, ordinalBindings, orderedBindNames) - } - - override val bindings: Bindings - get() = materialized.bindings - - override val ordinalBindings: OrdinalBindings - get() = materialized.ordinalBindings - - @Suppress("UNCHECKED_CAST") - override fun provideFacet(type: Class?): T? = when (type) { - OrderedBindNames::class.java -> when (ordering) { - StructOrdering.ORDERED -> materialized.orderedBindNames - else -> null - } as T? - else -> null - } - - override fun iterator() = sequence.iterator() -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Thunk.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Thunk.kt deleted file mode 100644 index 5187f30204..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/Thunk.kt +++ /dev/null @@ -1,715 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorBehaviorInPermissiveMode -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.domains.staticType -import org.partiql.lang.types.StaticTypeUtils.isInstance - -/** - * A thunk with no parameters other than the current environment. - * - * See https://en.wikipedia.org/wiki/Thunk - * - * @param TEnv The type of the environment. Generic so that the legacy AST compiler and the new compiler may use - * different types here. - */ -internal typealias Thunk = (TEnv) -> ExprValue - -/** - * A thunk taking a single argument and the current environment. - * - * See https://en.wikipedia.org/wiki/Thunk - * - * @param TEnv The type of the environment. Generic so that the legacy AST compiler and the new compiler may use - * different types here. - * @param TArg The type of the additional argument. - */ -internal typealias ThunkValue = (TEnv, TArg) -> ExprValue - -/** - * A type alias for an exception handler which always throws(primarily used for [TypingMode.LEGACY]). - */ -internal typealias ThunkExceptionHandlerForLegacyMode = (Throwable, SourceLocationMeta?) -> Nothing - -/** - * A type alias for an exception handler which does not always throw(primarily used for [TypingMode.PERMISSIVE]). - */ -internal typealias ThunkExceptionHandlerForPermissiveMode = (Throwable, SourceLocationMeta?) -> Unit - -/** - * Options for thunk construction. - * - * - [handleExceptionForLegacyMode] will be called when in [TypingMode.LEGACY] mode - * - [handleExceptionForPermissiveMode] will be called when in [TypingMode.PERMISSIVE] mode - * - [thunkReturnTypeAssertions] is intended for testing only, and ensures that the return value of every expression - * conforms to its `StaticType` meta. This has negative performance implications so should be avoided in production - * environments. This only be used for testing and diagnostic purposes only. - * The default exception handler wraps any [Throwable] exception and throws [EvaluationException] - */ -data class ThunkOptions private constructor( - val handleExceptionForLegacyMode: ThunkExceptionHandlerForLegacyMode = DEFAULT_EXCEPTION_HANDLER_FOR_LEGACY_MODE, - val handleExceptionForPermissiveMode: ThunkExceptionHandlerForPermissiveMode = DEFAULT_EXCEPTION_HANDLER_FOR_PERMISSIVE_MODE, - val thunkReturnTypeAssertions: ThunkReturnTypeAssertions = ThunkReturnTypeAssertions.DISABLED, -) { - - companion object { - - /** - * Creates a java style builder that will choose the default values for any unspecified options. - */ - @JvmStatic - fun builder() = Builder() - - /** - * Kotlin style builder that will choose the default values for any unspecified options. - */ - fun build(block: Builder.() -> Unit) = Builder().apply(block).build() - - /** - * Creates a [ThunkOptions] instance with the standard values. - */ - @JvmStatic - fun standard() = Builder().build() - } - - /** - * Builds a [ThunkOptions] instance. - */ - class Builder { - private var options = ThunkOptions() - fun handleExceptionForLegacyMode(value: ThunkExceptionHandlerForLegacyMode) = set { copy(handleExceptionForLegacyMode = value) } - fun handleExceptionForPermissiveMode(value: ThunkExceptionHandlerForPermissiveMode) = set { copy(handleExceptionForPermissiveMode = value) } - fun evaluationTimeTypeChecks(value: ThunkReturnTypeAssertions) = set { copy(thunkReturnTypeAssertions = value) } - private inline fun set(block: ThunkOptions.() -> ThunkOptions): Builder { - options = block(options) - return this - } - - fun build() = options - } -} - -internal val DEFAULT_EXCEPTION_HANDLER_FOR_LEGACY_MODE: ThunkExceptionHandlerForLegacyMode = { e, sourceLocation -> - val message = e.message ?: "" - throw EvaluationException( - "Internal error, $message", - errorCode = (e as? EvaluationException)?.errorCode ?: ErrorCode.EVALUATOR_GENERIC_EXCEPTION, - errorContext = errorContextFrom(sourceLocation), - cause = e, - internal = true - ) -} - -internal val DEFAULT_EXCEPTION_HANDLER_FOR_PERMISSIVE_MODE: ThunkExceptionHandlerForPermissiveMode = { e, _ -> - when (e) { - is InterruptedException -> { throw e } - is StackOverflowError -> { throw e } - else -> {} - } -} - -/** - * An extension method for creating [ThunkFactory] based on the type of [TypingMode] - * - when [TypingMode] is [TypingMode.LEGACY], creates [LegacyThunkFactory] - * - when [TypingMode] is [TypingMode.PERMISSIVE], creates [PermissiveThunkFactory] - */ -internal fun TypingMode.createThunkFactory( - thunkOptions: ThunkOptions -): ThunkFactory = when (this) { - TypingMode.LEGACY -> LegacyThunkFactory(thunkOptions) - TypingMode.PERMISSIVE -> PermissiveThunkFactory(thunkOptions) -} -/** - * Provides methods for constructing new thunks according to the specified [CompileOptions]. - */ -internal abstract class ThunkFactory( - val thunkOptions: ThunkOptions -) { - private fun checkEvaluationTimeType(thunkResult: ExprValue, metas: MetaContainer): ExprValue { - // When this check is enabled we throw an exception the [MetaContainer] does not have a - // [StaticTypeMeta]. This indicates a bug or unimplemented support for an AST node in - // [StaticTypeInferenceVisitorTransform]. - val staticType = metas.staticType?.type ?: error("Metas collection does not have a StaticTypeMeta") - if (!isInstance(thunkResult, staticType)) { - throw EvaluationException( - "Runtime type does not match the expected StaticType", - ErrorCode.EVALUATOR_VALUE_NOT_INSTANCE_OF_EXPECTED_TYPE, - errorContext = errorContextFrom(metas).apply { - this[Property.EXPECTED_STATIC_TYPE] = staticType.toString() - }, - internal = true - ) - } - return thunkResult - } - - /** - * If [ThunkReturnTypeAssertions.ENABLED] is set, wraps the receiver thunk in another thunk - * that verifies that the value returned from the receiver thunk matches the type found in the [StaticTypeMeta] - * contained within [metas]. - * - * If [metas] contains does not contain [StaticTypeMeta], an [IllegalStateException] is thrown. This is to prevent - * confusion in the case [StaticTypeInferenceVisitorTransform] has a bug which prevents it from assigning a - * [StaticTypeMeta] or in case it is not run at all. - */ - protected fun Thunk.typeCheck(metas: MetaContainer): Thunk = - when (thunkOptions.thunkReturnTypeAssertions) { - ThunkReturnTypeAssertions.DISABLED -> this - ThunkReturnTypeAssertions.ENABLED -> { - val wrapper = { env: TEnv -> - val thunkResult: ExprValue = this(env) - checkEvaluationTimeType(thunkResult, metas) - } - wrapper - } - } - - /** Same as [typeCheck] but works on a [ThunkEnvValue] instead of a [Thunk]. */ - protected fun ThunkValue.typeCheckEnvValue(metas: MetaContainer): ThunkValue = - when (thunkOptions.thunkReturnTypeAssertions) { - ThunkReturnTypeAssertions.DISABLED -> this - ThunkReturnTypeAssertions.ENABLED -> { - val wrapper = { env: TEnv, value: ExprValue -> - val thunkResult: ExprValue = this(env, value) - checkEvaluationTimeType(thunkResult, metas) - } - wrapper - } - } - - /** Same as [typeCheck] but works on a [ThunkEnvValue>] instead of a [Thunk]. */ - protected fun ThunkValue>.typeCheckEnvValueList(metas: MetaContainer): ThunkValue> = - when (thunkOptions.thunkReturnTypeAssertions) { - ThunkReturnTypeAssertions.DISABLED -> this - ThunkReturnTypeAssertions.ENABLED -> { - val wrapper = { env: TEnv, value: List -> - val thunkResult: ExprValue = this(env, value) - checkEvaluationTimeType(thunkResult, metas) - } - wrapper - } - } - - /** - * Creates a [Thunk] which handles exceptions by wrapping them into an [EvaluationException] which uses - * [handleException] to handle exceptions appropriately. - * - * Literal lambdas passed to this function as [t] are inlined into the body of the function being returned, which - * reduces the need to create additional call contexts. The lambdas passed as [t] may not contain non-local returns - * (`crossinline`). - */ - internal inline fun thunkEnv(metas: MetaContainer, crossinline t: Thunk): Thunk { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - - return { env: TEnv -> - handleException(sourceLocationMeta) { - t(env) - } - }.typeCheck(metas) - } - - /** - * Defines the strategy for unknown propagation of 1-3 operands. - * - * This is the [TypingMode] specific implementation of unknown-propagation, used by the [thunkEnvOperands] - * functions. [getVal1], [getVal2] and [getVal2] are lambdas to allow for differences in short-circuiting. - * - * For all [TypingMode]s, if the values returned by [getVal1], [getVal2] and [getVal2] are all known, - * [compute] is invoked to perform the operation-specific computation. - * - * Note: this must be public due to a Kotlin compiler bug: https://youtrack.jetbrains.com/issue/KT-22625. - * This shouldn't matter though because this class is still `internal`. - */ - abstract fun propagateUnknowns( - getVal1: () -> ExprValue, - getVal2: (() -> ExprValue)?, - getVal3: (() -> ExprValue)?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue - - /** - * Similar to the other [propagateUnknowns] overload, performs unknown propagation for a variadic sequence of - * operations. - * - * Note: this must be public due to a Kotlin compiler bug: https://youtrack.jetbrains.com/issue/KT-22625. - * This shouldn't matter though because this class is still `internal`. - */ - abstract fun propagateUnknowns( - operands: Sequence, - compute: (List) -> ExprValue - ): ExprValue - - /** - * Creates a thunk that accepts three [Thunk] operands ([t1], [t2], and [t3]), evaluates them and propagates - * unknowns according to the current [TypingMode]. When possible, use this function or one of its overloads - * instead of [thunkEnv] when the operation requires propagation of unknown values. - * - * [t1], [t2] and [t3] are each evaluated in with short circuiting depending on the current [TypingMode]: - * - * - In [TypingMode.PERMISSIVE] mode, the first `MISSING` returned from one of the thunks causes a short-circuit, - * and `MISSING` is returned immediately without evaluating the remaining thunks. If none of the thunks return - * `MISSING`, if any of them has returned `NULL`, `NULL` is returned. - * - In [TypingMode.LEGACY] mode, the first `NULL` or `MISSING` returned from one of the thunks causes a - * short-circuit, and returns `NULL` without evaluating the remaining thunks. - * - * In both modes, if none of the thunks returns `MISSING` or `NULL`, [compute] is invoked to perform the final - * computation on values of the operands which are guaranteed to be known. - * - * Overloads of this function exist that accept 1 and 2 arguments. We do not make [t2] and [t3] nullable with a - * default value of `null` instead of supplying those overloads primarily because [compute] has a different - * signature for each, but also because that would prevent [thunkEnvOperands] from being `inline`. - */ - internal inline fun thunkEnvOperands( - metas: MetaContainer, - crossinline t1: Thunk, - crossinline t2: Thunk, - crossinline t3: Thunk, - crossinline compute: (TEnv, ExprValue, ExprValue, ExprValue) -> ExprValue - ): Thunk = - thunkEnv(metas) { env -> - propagateUnknowns({ t1(env) }, { t2(env) }, { t3(env) }) { v1, v2, v3 -> - compute(env, v1, v2!!, v3!!) - } - }.typeCheck(metas) - - /** See the [thunkEnvOperands] with three [Thunk] operands. */ - internal inline fun thunkEnvOperands( - metas: MetaContainer, - crossinline t1: Thunk, - crossinline t2: Thunk, - crossinline compute: (TEnv, ExprValue, ExprValue) -> ExprValue - ): Thunk = - this.thunkEnv(metas) { env -> - propagateUnknowns({ t1(env) }, { t2(env) }, null) { v1, v2, _ -> - compute(env, v1, v2!!) - } - }.typeCheck(metas) - - /** See the [thunkEnvOperands] with three [Thunk] operands. */ - internal inline fun thunkEnvOperands( - metas: MetaContainer, - crossinline t1: Thunk, - crossinline compute: (TEnv, ExprValue) -> ExprValue - ): Thunk = - this.thunkEnv(metas) { env -> - propagateUnknowns({ t1(env) }, null, null) { v1, _, _ -> - compute(env, v1) - } - }.typeCheck(metas) - - /** See the [thunkEnvOperands] with a variadic list of [Thunk] operands. */ - internal inline fun thunkEnvOperands( - metas: MetaContainer, - operandThunks: List>, - crossinline compute: (TEnv, List) -> ExprValue - ): Thunk { - - return this.thunkEnv(metas) { env -> - val operandSeq = sequence { operandThunks.forEach { yield(it(env)) } } - propagateUnknowns(operandSeq) { values -> - compute(env, values) - } - }.typeCheck(metas) - } - - /** Similar to [thunkEnv], but creates a [ThunkEnvValue] instead. */ - internal inline fun thunkEnvValue( - metas: MetaContainer, - crossinline t: ThunkValue - ): ThunkValue { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - - return { env: TEnv, arg1: ExprValue -> - handleException(sourceLocationMeta) { - t(env, arg1) - } - }.typeCheckEnvValue(metas) - } - - /** Similar to [thunkEnv], but creates a [ThunkEnvValue>] instead. */ - internal inline fun thunkEnvValueList( - metas: MetaContainer, - crossinline t: ThunkValue> - ): ThunkValue> { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - - return { env: TEnv, arg1: List -> - handleException(sourceLocationMeta) { - t(env, arg1) - } - }.typeCheckEnvValueList(metas) - } - - /** - * Similar to [thunkEnv] but evaluates all [argThunks] and performs a fold using [op] as the operation. - * - * Also handles null propagation appropriately for [NAryOp] arithmetic operations. Each thunk in [argThunks] - * is evaluated in turn and: - * - * - for [TypingMode.LEGACY], the first unknown operand short-circuits, returning `NULL`. - * - for [TypingMode.PERMISSIVE], the first missing operand short-circuits, returning `MISSING`. Then, if one - * of the operands returned `NULL`, `NULL` is returned. - * - * For both modes, if all of the operands are known, performs a fold over them with [op]. - */ - internal abstract fun thunkFold( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> ExprValue - ): Thunk - - /** - * Similar to [thunkFold] but intended for comparison operators, i.e. `=`, `>`, `>=`, `<`, `<=`. - * - * The first argument of [op] is always the value of `argThunks[n]` and - * the second is always `argThunks[n + 1]` where `n` is 0 to `argThunks.size - 2`. - * - * - If [op] returns false, the thunk short circuits and the result of the thunk becomes `false`. - * - for [TypingMode.LEGACY], the first unknown operand short-circuits, returning `NULL`. - * - for [TypingMode.PERMISSIVE], the first missing operand short-circuits, returning `MISSING`. Then, if one - * of the operands returned `NULL`, `NULL` is returned. - * - * If [op] is true for all invocations then the result of the thunk becomes `true`, otherwise the reuslt is `false`. - * - * The name of this function was inspired by Racket's `andmap` procedure. - */ - internal abstract fun thunkAndMap( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> Boolean - ): Thunk - - /** Populates [exception] with the line & column from the specified [SourceLocationMeta]. */ - protected fun populateErrorContext( - exception: EvaluationException, - sourceLocation: SourceLocationMeta? - ): EvaluationException { - // Only add source location data to the error context if it doesn't already exist - // in [errorContext]. - if (!exception.errorContext.hasProperty(Property.LINE_NUMBER)) { - sourceLocation?.let { fillErrorContext(exception.errorContext, sourceLocation) } - } - return exception - } - - /** - * Handles exceptions appropriately for a run-time [Thunk]. - * - * - The [SourceLocationMeta] will be extracted from [MetaContainer] and included in any [EvaluationException] that - * is thrown, if present. - * - The location information is added to the [EvaluationException]'s `errorContext`, if it is not already present. - * - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown - * with the original exception as the cause. - */ - abstract fun handleException( - sourceLocation: SourceLocationMeta?, - block: () -> ExprValue - ): ExprValue -} - -/** - * Provides methods for constructing new thunks according to the specified [CompileOptions] for [TypingMode.LEGACY] behaviour. - */ -internal class LegacyThunkFactory( - thunkOptions: ThunkOptions -) : ThunkFactory(thunkOptions) { - - override fun propagateUnknowns( - getVal1: () -> ExprValue, - getVal2: (() -> ExprValue)?, - getVal3: (() -> ExprValue)?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue { - val val1 = getVal1() - return when { - val1.isUnknown() -> ExprValue.nullValue - else -> { - val val2 = getVal2?.let { it() } - when { - val2 == null -> compute(val1, null, null) - val2.isUnknown() -> ExprValue.nullValue - else -> { - val val3 = getVal3?.let { it() } - when { - val3 == null -> compute(val1, val2, null) - val3.isUnknown() -> ExprValue.nullValue - else -> compute(val1, val2, val3) - } - } - } - } - } - } - - override fun propagateUnknowns( - operands: Sequence, - compute: (List) -> ExprValue - ): ExprValue { - // Because we need to short-circuit on the first unknown value and [operands] is a sequence, - // we can't use .map here. (non-local returns on `.map` are not allowed) - val argValues = mutableListOf() - operands.forEach { - when { - it.isUnknown() -> return ExprValue.nullValue - else -> argValues.add(it) - } - } - return compute(argValues) - } - - /** See [ThunkFactory.thunkFold]. */ - override fun thunkFold( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> ExprValue - ): Thunk { - require(argThunks.isNotEmpty()) { "argThunks must not be empty" } - - val firstThunk = argThunks.first() - val otherThunks = argThunks.drop(1) - return thunkEnv(metas) thunkBlock@{ env -> - val firstValue = firstThunk(env) - when { - // Short-circuit at first NULL or MISSING value and return NULL. - firstValue.isUnknown() -> ExprValue.nullValue - else -> { - otherThunks.fold(firstValue) { acc, curr -> - val currValue = curr(env) - if (currValue.type.isUnknown) { - return@thunkBlock ExprValue.nullValue - } - op(acc, currValue) - } - } - } - }.typeCheck(metas) - } - - /** See [ThunkFactory.thunkAndMap]. */ - override fun thunkAndMap( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> Boolean - ): Thunk { - require(argThunks.size >= 2) { "argThunks must have at least two elements" } - - val firstThunk = argThunks.first() - val otherThunks = argThunks.drop(1) - - return thunkEnv(metas) thunkBlock@{ env -> - val firstValue = firstThunk(env) - when { - // If the first value is unknown, short circuit returning null. - firstValue.isUnknown() -> ExprValue.nullValue - else -> { - otherThunks.fold(firstValue) { lastValue, currentThunk -> - - val currentValue = currentThunk(env) - if (currentValue.isUnknown()) { - return@thunkBlock ExprValue.nullValue - } - - val result = op(lastValue, currentValue) - if (!result) { - return@thunkBlock ExprValue.newBoolean(false) - } - - currentValue - } - - ExprValue.newBoolean(true) - } - } - } - } - - /** - * Handles exceptions appropriately for a run-time [Thunk] respecting [TypingMode.LEGACY] behaviour. - * - * - The [SourceLocationMeta] will be extracted from [MetaContainer] and included in any [EvaluationException] that - * is thrown, if present. - * - The location information is added to the [EvaluationException]'s `errorContext`, if it is not already present. - * - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown - * with the original exception as the cause. - */ - override fun handleException( - sourceLocation: SourceLocationMeta?, - block: () -> ExprValue - ): ExprValue = - try { - block() - } catch (e: EvaluationException) { - throw populateErrorContext(e, sourceLocation) - } catch (e: Exception) { - thunkOptions.handleExceptionForLegacyMode(e, sourceLocation) - } -} - -/** - * Provides methods for constructing new thunks according to the specified [CompileOptions] and for - * [TypingMode.PERMISSIVE] behaviour. - */ -internal class PermissiveThunkFactory( - thunkOptions: ThunkOptions -) : ThunkFactory(thunkOptions) { - - override fun propagateUnknowns( - getVal1: () -> ExprValue, - getVal2: (() -> ExprValue)?, - getVal3: (() -> ExprValue)?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue { - val val1 = getVal1() - return when (val1.type) { - ExprValueType.MISSING -> ExprValue.missingValue - else -> { - val val2 = getVal2?.let { it() } - when { - val2 == null -> nullOrCompute(val1, null, null, compute) - val2.type == ExprValueType.MISSING -> ExprValue.missingValue - else -> { - val val3 = getVal3?.let { it() } - when { - val3 == null -> nullOrCompute(val1, val2, null, compute) - val3.type == ExprValueType.MISSING -> ExprValue.missingValue - else -> nullOrCompute(val1, val2, val3, compute) - } - } - } - } - } - } - - override fun propagateUnknowns( - operands: Sequence, - compute: (List) -> ExprValue - ): ExprValue { - - // Because we need to short-circuit on the first MISSING value and [operands] is a sequence, - // we can't use .map here. (non-local returns on `.map` are not allowed) - val argValues = mutableListOf() - operands.forEach { - when (it.type) { - ExprValueType.MISSING -> return ExprValue.missingValue - else -> argValues.add(it) - } - } - return when { - // if any result is `NULL`, propagate return null instead. - argValues.any { it.type == ExprValueType.NULL } -> ExprValue.nullValue - else -> compute(argValues) - } - } - - private fun nullOrCompute( - v1: ExprValue, - v2: ExprValue?, - v3: ExprValue?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue = - when { - v1.type == ExprValueType.NULL || - (v2?.let { it.type == ExprValueType.NULL }) ?: false || - (v3?.let { it.type == ExprValueType.NULL }) ?: false -> ExprValue.nullValue - else -> compute(v1, v2, v3) - } - - /** See [ThunkFactory.thunkFold]. */ - override fun thunkFold( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> ExprValue - ): Thunk { - require(argThunks.isNotEmpty()) { "argThunks must not be empty" } - - return thunkEnv(metas) { env -> - val values = argThunks.map { - val v = it(env) - when (v.type) { - // Short-circuit at first detected MISSING value. - ExprValueType.MISSING -> return@thunkEnv ExprValue.missingValue - else -> v - } - } - when { - // Propagate NULL if any operand is NULL. - values.any { it.type == ExprValueType.NULL } -> ExprValue.nullValue - // compute the final value. - else -> values.reduce { first, second -> op(first, second) } - } - }.typeCheck(metas) - } - - /** See [ThunkFactory.thunkAndMap]. */ - override fun thunkAndMap( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> Boolean - ): Thunk { - require(argThunks.size >= 2) { "argThunks must have at least two elements" } - - return thunkEnv(metas) thunkBlock@{ env -> - val values = argThunks.map { - val v = it(env) - when (v.type) { - // Short-circuit at first detected MISSING value. - ExprValueType.MISSING -> return@thunkBlock ExprValue.missingValue - else -> v - } - } - when { - // Propagate NULL if any operand is NULL. - values.any { it.type == ExprValueType.NULL } -> ExprValue.nullValue - else -> { - (0..(values.size - 2)).forEach { i -> - if (!op(values[i], values[i + 1])) - return@thunkBlock ExprValue.newBoolean(false) - } - - return@thunkBlock ExprValue.newBoolean(true) - } - } - } - } - - /** - * Handles exceptions appropriately for a run-time [Thunk] respecting [TypingMode.PERMISSIVE] behaviour. - * - * - Exceptions thrown by [block] that are [EvaluationException] are caught and [MissingExprValue] is returned. - * - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown - * with the original exception as the cause. - */ - override fun handleException( - sourceLocation: SourceLocationMeta?, - block: () -> ExprValue - ): ExprValue = - try { - block() - } catch (e: EvaluationException) { - thunkOptions.handleExceptionForPermissiveMode(e, sourceLocation) - when (e.errorCode.errorBehaviorInPermissiveMode) { - // Rethrows the exception as it does in LEGACY mode. - ErrorBehaviorInPermissiveMode.THROW_EXCEPTION -> throw populateErrorContext(e, sourceLocation) - ErrorBehaviorInPermissiveMode.RETURN_MISSING -> ExprValue.missingValue - } - } catch (e: Exception) { - thunkOptions.handleExceptionForLegacyMode(e, sourceLocation) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ThunkAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ThunkAsync.kt deleted file mode 100644 index e40014f719..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/ThunkAsync.kt +++ /dev/null @@ -1,612 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval - -import com.amazon.ionelement.api.MetaContainer -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.toList -import org.partiql.errors.ErrorBehaviorInPermissiveMode -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.domains.staticType -import org.partiql.lang.types.StaticTypeUtils.isInstance - -/** - * A thunk with no parameters other than the current environment. - * - * See https://en.wikipedia.org/wiki/Thunk - * - * @param TEnv The type of the environment. Generic so that the legacy AST compiler and the new compiler may use - * different types here. - */ -internal typealias ThunkAsync = suspend (TEnv) -> ExprValue - -/** - * A thunk taking a single argument and the current environment. - * - * See https://en.wikipedia.org/wiki/Thunk - * - * @param TEnv The type of the environment. Generic so that the legacy AST compiler and the new compiler may use - * different types here. - * @param TArg The type of the additional argument. - */ -internal typealias ThunkValueAsync = suspend (TEnv, TArg) -> ExprValue - -/** - * An extension method for creating [ThunkFactoryAsync] based on the type of [TypingMode] - * - when [TypingMode] is [TypingMode.LEGACY], creates [LegacyThunkFactoryAsync] - * - when [TypingMode] is [TypingMode.PERMISSIVE], creates [PermissiveThunkFactoryAsync] - */ -internal fun TypingMode.createThunkFactoryAsync( - thunkOptions: ThunkOptions -): ThunkFactoryAsync = when (this) { - TypingMode.LEGACY -> LegacyThunkFactoryAsync(thunkOptions) - TypingMode.PERMISSIVE -> PermissiveThunkFactoryAsync(thunkOptions) -} -/** - * Provides methods for constructing new thunks according to the specified [CompileOptions]. - */ -internal abstract class ThunkFactoryAsync( - val thunkOptions: ThunkOptions -) { - private fun checkEvaluationTimeType(thunkResult: ExprValue, metas: MetaContainer): ExprValue { - // When this check is enabled we throw an exception the [MetaContainer] does not have a - // [StaticTypeMeta]. This indicates a bug or unimplemented support for an AST node in - // [StaticTypeInferenceVisitorTransform]. - val staticType = metas.staticType?.type ?: error("Metas collection does not have a StaticTypeMeta") - if (!isInstance(thunkResult, staticType)) { - throw EvaluationException( - "Runtime type does not match the expected StaticType", - ErrorCode.EVALUATOR_VALUE_NOT_INSTANCE_OF_EXPECTED_TYPE, - errorContext = errorContextFrom(metas).apply { - this[Property.EXPECTED_STATIC_TYPE] = staticType.toString() - }, - internal = true - ) - } - return thunkResult - } - - /** - * If [ThunkReturnTypeAssertions.ENABLED] is set, wraps the receiver thunk in another thunk - * that verifies that the value returned from the receiver thunk matches the type found in the [StaticTypeMeta] - * contained within [metas]. - * - * If [metas] contains does not contain [StaticTypeMeta], an [IllegalStateException] is thrown. This is to prevent - * confusion in the case [org.partiql.lang.eval.visitors.StaticTypeInferenceVisitorTransform] has a bug which - * prevents it from assigning a [StaticTypeMeta] or in case it is not run at all. - */ - protected suspend fun ThunkAsync.typeCheck(metas: MetaContainer): ThunkAsync = - when (thunkOptions.thunkReturnTypeAssertions) { - ThunkReturnTypeAssertions.DISABLED -> this - ThunkReturnTypeAssertions.ENABLED -> { - val wrapper: ThunkAsync = { env: TEnv -> - val thunkResult: ExprValue = this(env) - checkEvaluationTimeType(thunkResult, metas) - } - wrapper - } - } - - /** Same as [typeCheck] but works on a [ThunkEnvValue] instead of a [Thunk]. */ - protected suspend fun ThunkValueAsync.typeCheckEnvValue(metas: MetaContainer): ThunkValueAsync = - when (thunkOptions.thunkReturnTypeAssertions) { - ThunkReturnTypeAssertions.DISABLED -> this - ThunkReturnTypeAssertions.ENABLED -> { - val wrapper: ThunkValueAsync = { env: TEnv, value: ExprValue -> - val thunkResult: ExprValue = this(env, value) - checkEvaluationTimeType(thunkResult, metas) - } - wrapper - } - } - - /** - * Creates a [Thunk] which handles exceptions by wrapping them into an [EvaluationException] which uses - * [handleExceptionAsync] to handle exceptions appropriately. - * - * Literal lambdas passed to this function as [t] are inlined into the body of the function being returned, which - * reduces the need to create additional call contexts. The lambdas passed as [t] may not contain non-local returns - * (`crossinline`). - */ - internal suspend inline fun thunkEnvAsync(metas: MetaContainer, crossinline t: ThunkAsync): ThunkAsync { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - - val thunkAsync: ThunkAsync = { env: TEnv -> - this.handleExceptionAsync(sourceLocationMeta) { - t(env) - } - } - return thunkAsync.typeCheck(metas) - } - - /** - * Defines the strategy for unknown propagation of 1-3 operands. - * - * This is the [TypingMode] specific implementation of unknown-propagation, used by the [thunkEnvOperands] - * functions. [getVal1], [getVal2] and [getVal2] are lambdas to allow for differences in short-circuiting. - * - * For all [TypingMode]s, if the values returned by [getVal1], [getVal2] and [getVal2] are all known, - * [compute] is invoked to perform the operation-specific computation. - * - * Note: this must be public due to a Kotlin compiler bug: https://youtrack.jetbrains.com/issue/KT-22625. - * This shouldn't matter though because this class is still `internal`. - */ - abstract suspend fun propagateUnknowns( - getVal1: suspend () -> ExprValue, - getVal2: (suspend () -> ExprValue)?, - getVal3: (suspend () -> ExprValue)?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue - - /** - * Similar to the other [propagateUnknowns] overload, performs unknown propagation for a variadic sequence of - * operations. - * - * Note: this must be public due to a Kotlin compiler bug: https://youtrack.jetbrains.com/issue/KT-22625. - * This shouldn't matter though because this class is still `internal`. - */ - abstract suspend fun propagateUnknowns( - operands: Sequence, - compute: (List) -> ExprValue - ): ExprValue - - /** - * Creates a thunk that accepts three [Thunk] operands ([t1], [t2], and [t3]), evaluates them and propagates - * unknowns according to the current [TypingMode]. When possible, use this function or one of its overloads - * instead of [thunkEnvAsync] when the operation requires propagation of unknown values. - * - * [t1], [t2] and [t3] are each evaluated in with short-circuiting depending on the current [TypingMode]: - * - * - In [TypingMode.PERMISSIVE] mode, the first `MISSING` returned from one of the thunks causes a short-circuit, - * and `MISSING` is returned immediately without evaluating the remaining thunks. If none of the thunks return - * `MISSING`, if any of them has returned `NULL`, `NULL` is returned. - * - In [TypingMode.LEGACY] mode, the first `NULL` or `MISSING` returned from one of the thunks causes a - * short-circuit, and returns `NULL` without evaluating the remaining thunks. - * - * In both modes, if none of the thunks returns `MISSING` or `NULL`, [compute] is invoked to perform the final - * computation on values of the operands which are guaranteed to be known. - * - * Overloads of this function exist that accept 1 and 2 arguments. We do not make [t2] and [t3] nullable with a - * default value of `null` instead of supplying those overloads primarily because [compute] has a different - * signature for each, but also because that would prevent [thunkEnvOperands] from being `inline`. - */ - internal suspend inline fun thunkEnvOperands( - metas: MetaContainer, - crossinline t1: ThunkAsync, - crossinline t2: ThunkAsync, - crossinline t3: ThunkAsync, - crossinline compute: (TEnv, ExprValue, ExprValue, ExprValue) -> ExprValue - ): ThunkAsync = - this.thunkEnvAsync(metas) { env -> - propagateUnknowns({ t1(env) }, { t2(env) }, { t3(env) }) { v1, v2, v3 -> - compute(env, v1, v2!!, v3!!) - } - }.typeCheck(metas) - - /** See the [thunkEnvOperands] with three [Thunk] operands. */ - internal suspend inline fun thunkEnvOperands( - metas: MetaContainer, - crossinline t1: ThunkAsync, - crossinline t2: ThunkAsync, - crossinline compute: (TEnv, ExprValue, ExprValue) -> ExprValue - ): ThunkAsync = - this.thunkEnvAsync(metas) { env -> - propagateUnknowns({ t1(env) }, { t2(env) }, null) { v1, v2, _ -> - compute(env, v1, v2!!) - } - }.typeCheck(metas) - - /** See the [thunkEnvOperands] with three [Thunk] operands. */ - internal suspend inline fun thunkEnvOperands( - metas: MetaContainer, - crossinline t1: ThunkAsync, - crossinline compute: (TEnv, ExprValue) -> ExprValue - ): ThunkAsync = - this.thunkEnvAsync(metas) { env -> - propagateUnknowns({ t1(env) }, null, null) { v1, _, _ -> - compute(env, v1) - } - }.typeCheck(metas) - - /** See the [thunkEnvOperands] with a variadic list of [Thunk] operands. */ - internal suspend inline fun thunkEnvOperands( - metas: MetaContainer, - operandThunks: List>, - crossinline compute: (TEnv, List) -> ExprValue - ): ThunkAsync { - - return this.thunkEnvAsync(metas) { env -> - val operandSeq = flow { - operandThunks.forEach { emit(it(env)) } - } - propagateUnknowns(operandSeq.toList().asSequence()) { values -> - compute(env, values) - } - }.typeCheck(metas) - } - - /** Similar to [thunkEnvAsync], but creates a [ThunkEnvValue] instead. */ - internal suspend inline fun thunkEnvValue( - metas: MetaContainer, - crossinline t: ThunkValueAsync - ): ThunkValueAsync { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - - val tVal: ThunkValueAsync = { env: TEnv, arg1: ExprValue -> - this.handleExceptionAsync(sourceLocationMeta) { - t(env, arg1) - } - } - return tVal.typeCheckEnvValue(metas) - } - - /** - * Similar to [thunkEnvAsync] but evaluates all [argThunks] and performs a fold using [op] as the operation. - * - * Also handles null propagation appropriately for NAryOp arithmetic operations. Each thunk in [argThunks] - * is evaluated in turn and: - * - * - for [TypingMode.LEGACY], the first unknown operand short-circuits, returning `NULL`. - * - for [TypingMode.PERMISSIVE], the first missing operand short-circuits, returning `MISSING`. Then, if one - * of the operands returned `NULL`, `NULL` is returned. - * - * For both modes, if all the operands are known, performs a fold over them with [op]. - */ - internal abstract suspend fun thunkFold( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> ExprValue - ): ThunkAsync - - /** - * Similar to [thunkFold] but intended for comparison operators, i.e. `=`, `>`, `>=`, `<`, `<=`. - * - * The first argument of [op] is always the value of `argThunks[n]` and - * the second is always `argThunks[n + 1]` where `n` is 0 to `argThunks.size - 2`. - * - * - If [op] returns false, the thunk short circuits and the result of the thunk becomes `false`. - * - for [TypingMode.LEGACY], the first unknown operand short-circuits, returning `NULL`. - * - for [TypingMode.PERMISSIVE], the first missing operand short-circuits, returning `MISSING`. Then, if one - * of the operands returned `NULL`, `NULL` is returned. - * - * If [op] is true for all invocations then the result of the thunk becomes `true`, otherwise the result is `false`. - * - * The name of this function was inspired by Racket's `andmap` procedure. - */ - internal abstract suspend fun thunkAndMap( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> Boolean - ): ThunkAsync - - /** Populates [exception] with the line & column from the specified [SourceLocationMeta]. */ - protected fun populateErrorContext( - exception: EvaluationException, - sourceLocation: SourceLocationMeta? - ): EvaluationException { - // Only add source location data to the error context if it doesn't already exist - // in [errorContext]. - if (!exception.errorContext.hasProperty(Property.LINE_NUMBER)) { - sourceLocation?.let { fillErrorContext(exception.errorContext, sourceLocation) } - } - return exception - } - - /** - * Handles exceptions appropriately for a run-time [ThunkAsync]. - * - * - The [SourceLocationMeta] will be extracted from [MetaContainer] and included in any [EvaluationException] that - * is thrown, if present. - * - The location information is added to the [EvaluationException]'s `errorContext`, if it is not already present. - * - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown - * with the original exception as the cause. - */ - abstract suspend fun handleExceptionAsync( - sourceLocation: SourceLocationMeta?, - block: suspend () -> ExprValue - ): ExprValue -} - -/** - * Provides methods for constructing new thunks according to the specified [CompileOptions] for [TypingMode.LEGACY] behaviour. - */ -internal class LegacyThunkFactoryAsync( - thunkOptions: ThunkOptions -) : ThunkFactoryAsync(thunkOptions) { - - override suspend fun propagateUnknowns( - getVal1: suspend () -> ExprValue, - getVal2: (suspend () -> ExprValue)?, - getVal3: (suspend () -> ExprValue)?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue { - val val1 = getVal1() - return when { - val1.isUnknown() -> ExprValue.nullValue - else -> { - val val2 = getVal2?.let { it() } - when { - val2 == null -> compute(val1, null, null) - val2.isUnknown() -> ExprValue.nullValue - else -> { - val val3 = getVal3?.let { it() } - when { - val3 == null -> compute(val1, val2, null) - val3.isUnknown() -> ExprValue.nullValue - else -> compute(val1, val2, val3) - } - } - } - } - } - } - - override suspend fun propagateUnknowns( - operands: Sequence, - compute: (List) -> ExprValue - ): ExprValue { - // Because we need to short-circuit on the first unknown value and [operands] is a sequence, - // we can't use .map here. (non-local returns on `.map` are not allowed) - val argValues = mutableListOf() - operands.forEach { - when { - it.isUnknown() -> return ExprValue.nullValue - else -> argValues.add(it) - } - } - return compute(argValues) - } - - /** See [ThunkFactoryAsync.thunkFold]. */ - override suspend fun thunkFold( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> ExprValue - ): ThunkAsync { - require(argThunks.isNotEmpty()) { "argThunks must not be empty" } - - val firstThunk = argThunks.first() - val otherThunks = argThunks.drop(1) - return thunkEnvAsync(metas) thunkBlock@{ env -> - val firstValue = firstThunk(env) - when { - // Short-circuit at first NULL or MISSING value and return NULL. - firstValue.isUnknown() -> ExprValue.nullValue - else -> { - otherThunks.fold(firstValue) { acc, curr -> - val currValue = curr(env) - if (currValue.type.isUnknown) { - return@thunkBlock ExprValue.nullValue - } - op(acc, currValue) - } - } - } - }.typeCheck(metas) - } - - /** See [ThunkFactoryAsync.thunkAndMap]. */ - override suspend fun thunkAndMap( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> Boolean - ): ThunkAsync { - require(argThunks.size >= 2) { "argThunks must have at least two elements" } - - val firstThunk = argThunks.first() - val otherThunks = argThunks.drop(1) - - return thunkEnvAsync(metas) thunkBlock@{ env -> - val firstValue = firstThunk(env) - when { - // If the first value is unknown, short circuit returning null. - firstValue.isUnknown() -> ExprValue.nullValue - else -> { - otherThunks.fold(firstValue) { lastValue, currentThunk -> - - val currentValue = currentThunk(env) - if (currentValue.isUnknown()) { - return@thunkBlock ExprValue.nullValue - } - - val result = op(lastValue, currentValue) - if (!result) { - return@thunkBlock ExprValue.newBoolean(false) - } - - currentValue - } - - ExprValue.newBoolean(true) - } - } - } - } - - /** - * Handles exceptions appropriately for a run-time [ThunkAsync] respecting [TypingMode.LEGACY] behaviour. - * - * - The [SourceLocationMeta] will be extracted from [MetaContainer] and included in any [EvaluationException] that - * is thrown, if present. - * - The location information is added to the [EvaluationException]'s `errorContext`, if it is not already present. - * - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown - * with the original exception as the cause. - */ - override suspend fun handleExceptionAsync( - sourceLocation: SourceLocationMeta?, - block: suspend () -> ExprValue - ): ExprValue = - try { - block() - } catch (e: EvaluationException) { - throw populateErrorContext(e, sourceLocation) - } catch (e: Exception) { - thunkOptions.handleExceptionForLegacyMode(e, sourceLocation) - } -} - -/** - * Provides methods for constructing new thunks according to the specified [CompileOptions] and for - * [TypingMode.PERMISSIVE] behaviour. - */ -internal class PermissiveThunkFactoryAsync( - thunkOptions: ThunkOptions -) : ThunkFactoryAsync(thunkOptions) { - - override suspend fun propagateUnknowns( - getVal1: suspend () -> ExprValue, - getVal2: (suspend () -> ExprValue)?, - getVal3: (suspend () -> ExprValue)?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue { - val val1 = getVal1() - return when (val1.type) { - ExprValueType.MISSING -> ExprValue.missingValue - else -> { - val val2 = getVal2?.let { it() } - when { - val2 == null -> nullOrCompute(val1, null, null, compute) - val2.type == ExprValueType.MISSING -> ExprValue.missingValue - else -> { - val val3 = getVal3?.let { it() } - when { - val3 == null -> nullOrCompute(val1, val2, null, compute) - val3.type == ExprValueType.MISSING -> ExprValue.missingValue - else -> nullOrCompute(val1, val2, val3, compute) - } - } - } - } - } - } - - override suspend fun propagateUnknowns( - operands: Sequence, - compute: (List) -> ExprValue - ): ExprValue { - - // Because we need to short-circuit on the first MISSING value and [operands] is a sequence, - // we can't use .map here. (non-local returns on `.map` are not allowed) - val argValues = mutableListOf() - operands.forEach { - when (it.type) { - ExprValueType.MISSING -> return ExprValue.missingValue - else -> argValues.add(it) - } - } - return when { - // if any result is `NULL`, propagate return null instead. - argValues.any { it.type == ExprValueType.NULL } -> ExprValue.nullValue - else -> compute(argValues) - } - } - - private fun nullOrCompute( - v1: ExprValue, - v2: ExprValue?, - v3: ExprValue?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue = - when { - v1.type == ExprValueType.NULL || - (v2?.let { it.type == ExprValueType.NULL }) ?: false || - (v3?.let { it.type == ExprValueType.NULL }) ?: false -> ExprValue.nullValue - else -> compute(v1, v2, v3) - } - - /** See [ThunkFactoryAsync.thunkFold]. */ - override suspend fun thunkFold( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> ExprValue - ): ThunkAsync { - require(argThunks.isNotEmpty()) { "argThunks must not be empty" } - - return thunkEnvAsync(metas) { env -> - val values = argThunks.map { - val v = it(env) - when (v.type) { - // Short-circuit at first detected MISSING value. - ExprValueType.MISSING -> return@thunkEnvAsync ExprValue.missingValue - else -> v - } - } - when { - // Propagate NULL if any operand is NULL. - values.any { it.type == ExprValueType.NULL } -> ExprValue.nullValue - // compute the final value. - else -> values.reduce { first, second -> op(first, second) } - } - }.typeCheck(metas) - } - - /** See [ThunkFactoryAsync.thunkAndMap]. */ - override suspend fun thunkAndMap( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> Boolean - ): ThunkAsync { - require(argThunks.size >= 2) { "argThunks must have at least two elements" } - - return thunkEnvAsync(metas) thunkBlock@{ env -> - val values = argThunks.map { - val v = it(env) - when (v.type) { - // Short-circuit at first detected MISSING value. - ExprValueType.MISSING -> return@thunkBlock ExprValue.missingValue - else -> v - } - } - when { - // Propagate NULL if any operand is NULL. - values.any { it.type == ExprValueType.NULL } -> ExprValue.nullValue - else -> { - (0..(values.size - 2)).forEach { i -> - if (!op(values[i], values[i + 1])) - return@thunkBlock ExprValue.newBoolean(false) - } - - return@thunkBlock ExprValue.newBoolean(true) - } - } - } - } - - /** - * Handles exceptions appropriately for a run-time [Thunk] respecting [TypingMode.PERMISSIVE] behaviour. - * - * - Exceptions thrown by [block] that are [EvaluationException] are caught and [ExprValue.missingValue] is returned. - * - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown - * with the original exception as the cause. - */ - override suspend fun handleExceptionAsync( - sourceLocation: SourceLocationMeta?, - block: suspend () -> ExprValue - ): ExprValue = - try { - block() - } catch (e: EvaluationException) { - thunkOptions.handleExceptionForPermissiveMode(e, sourceLocation) - when (e.errorCode.errorBehaviorInPermissiveMode) { - // Rethrows the exception as it does in LEGACY mode. - ErrorBehaviorInPermissiveMode.THROW_EXCEPTION -> throw populateErrorContext(e, sourceLocation) - ErrorBehaviorInPermissiveMode.RETURN_MISSING -> ExprValue.missingValue - } - } catch (e: Exception) { - thunkOptions.handleExceptionForLegacyMode(e, sourceLocation) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/UndefinedVariableUtil.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/UndefinedVariableUtil.kt deleted file mode 100644 index 21b5cfb98d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/UndefinedVariableUtil.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.partiql.lang.eval - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.util.propertyValueMapOf - -private const val UNBOUND_QUOTED_IDENTIFIER_HINT: String = - "Hint: did you intend to use single quotes (') here instead of double quotes (\")? " + - "Use single quotes (') for string literals and double quotes (\") for quoted identifiers." - -internal fun throwUndefinedVariableException( - bindingName: BindingName, - metas: MetaContainer? -): Nothing { - val (errorCode, hint) = when (bindingName.bindingCase) { - BindingCase.SENSITIVE -> - ErrorCode.EVALUATOR_QUOTED_BINDING_DOES_NOT_EXIST to " $UNBOUND_QUOTED_IDENTIFIER_HINT" - BindingCase.INSENSITIVE -> - ErrorCode.EVALUATOR_BINDING_DOES_NOT_EXIST to "" - } - throw EvaluationException( - message = "No such binding: ${bindingName.name}.$hint", - errorCode = errorCode, - errorContext = (metas?.let { errorContextFrom(metas) } ?: propertyValueMapOf()).also { - it[Property.BINDING_NAME] = bindingName.name - }, - internal = false - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/binding/LocalsBinder.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/binding/LocalsBinder.kt deleted file mode 100644 index a43cd0381f..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/binding/LocalsBinder.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.binding - -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.address -import org.partiql.lang.eval.name -import org.partiql.lang.util.errAmbiguousBinding - -/** - * Creates a list of bindings from a list of locals. - * The various implementations (such as List.[localsBinder]) will assign names to the locals. - * Think of this as a factory which precomputes the name-bindings map for a list of locals. - */ -abstract class LocalsBinder { - fun bindLocals(locals: List): Bindings { - return object : Bindings { - override fun get(bindingName: BindingName): ExprValue? = binderForName(bindingName)(locals) - } - } - - /** This method is the backbone of [bindLocals] and should be used when optimizing lookups. */ - abstract fun binderForName(bindingName: BindingName): (List) -> ExprValue? -} - -/** Sources can be aliased to names with 'AS', 'AT' or 'BY' */ -data class Alias(val asName: String, val atName: String?, val byName: String?) - -/** - * Returns a [LocalsBinder] for the bindings specified in the [Alias] ('AS' and optionally 'AT'). - * Each local [ExprValue] will be bound to the [Alias] with the same ordinal. - * - * For example, `[(as: 'x'), (as: 'y', at: 'z')].localsBinder.bindLocals(a, b)` will return the [Bindings] - * `x => a, y => b, z => b.name`. - * - * A name can resolve to 0,1,.. bindings. For these cases: - * * 0 could be optimized (see [dynamicLocalsBinder]). - * * 1 is optimized and is fast. Further optimization most likely requires caching lookups by name (see [LocalsBinder.binderForName]). - * * 2+ throws an error. - */ -fun List.localsBinder(missingValue: ExprValue): LocalsBinder { - - // For each 'as' and 'at' alias, create a locals accessor => { name: binding_accessor } - fun compileBindings(keyMangler: (String) -> String = { it }): Map) -> ExprValue?> { - data class Binder(val name: String, val func: (List) -> ExprValue) - return this.mapIndexed { index, alias -> - sequenceOf( - // the alias binds to the value itself - Binder(alias.asName) { it[index] }, - // the alias binds to the name of the value - when { - alias.atName == null -> null - else -> Binder(alias.atName) { it[index].name ?: missingValue } - }, - when { - alias.byName == null -> null - else -> Binder(alias.byName) { it[index].address ?: missingValue } - } - ) - }.asSequence() - .flatten() - .filterNotNull() - // There may be multiple accessors per name. - // Squash the accessor list to either the sole element or an error function - .groupBy { keyMangler(it.name) } - .mapValues { (name, binders) -> - when (binders.size) { - 1 -> binders[0].func - else -> { _ -> - errAmbiguousBinding(name, binders.map { it.name }) - } - } - } - } - - /** - * Nothing found at our scope, attempt to look at the attributes in our variables - * TODO fix dynamic scoping to be in line with PartiQL rules - */ - val dynamicLocalsBinder: (BindingName) -> (List) -> ExprValue? = when (this.count()) { - 0 -> { _ -> { _ -> null } } - 1 -> { name -> { locals -> locals.first().bindings[name] } } - else -> { name -> - { locals -> - locals.asSequence() - .map { it.bindings[name] } - .filterNotNull() - .firstOrNull() - } - } - } - - // Compile case-[in]sensitive bindings and return the accessor - return object : LocalsBinder() { - val caseSensitiveBindings = compileBindings() - val caseInsensitiveBindings = compileBindings { it.lowercase() } - override fun binderForName(bindingName: BindingName): (List) -> ExprValue? { - return when (bindingName.bindingCase) { - BindingCase.INSENSITIVE -> caseInsensitiveBindings[bindingName.name.lowercase()] - BindingCase.SENSITIVE -> caseSensitiveBindings[bindingName.name] - } ?: dynamicLocalsBinder(bindingName) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/Accumulator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/Accumulator.kt deleted file mode 100644 index 807a4a99b1..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/Accumulator.kt +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.builtins - -import org.partiql.errors.ErrorCode -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprAggregator -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.NaturalExprValueComparators -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.createUniqueExprValueFilter -import org.partiql.lang.eval.errNoContext -import org.partiql.lang.eval.isUnknown -import org.partiql.lang.eval.numberValue -import org.partiql.lang.util.bigDecimalOf -import org.partiql.lang.util.div -import org.partiql.lang.util.exprValue -import org.partiql.lang.util.plus - -internal sealed class Accumulator( - internal open val filter: (ExprValue) -> Boolean -) : ExprAggregator { - companion object { - internal fun create(funcName: String, quantifier: PartiqlPhysical.SetQuantifier): Accumulator { - val filter = when (quantifier) { - is PartiqlPhysical.SetQuantifier.Distinct -> createUniqueExprValueFilter() - is PartiqlPhysical.SetQuantifier.All -> { _: ExprValue -> true } - } - return when (funcName.trim().lowercase()) { - "min" -> AccumulatorMin(filter) - "max" -> AccumulatorMax(filter) - "avg" -> AccumulatorAvg(filter) - "count" -> AccumulatorCount(filter) - "sum" -> AccumulatorSum(filter) - "group_as" -> AccumulatorGroupAs(filter) - "every" -> AccumulatorEvery(filter) - "any" -> AccumulatorAnySome(filter) - "some" -> AccumulatorAnySome(filter) - else -> throw IllegalArgumentException("Unsupported aggregation function: $funcName") - } - } - } - - override fun next(value: ExprValue) { - if (value.isUnknown() || filter.invoke(value).not()) return - nextValue(value) - } - - abstract fun nextValue(value: ExprValue) -} - -internal class AccumulatorSum( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var sum: Number? = null - - override fun nextValue(value: ExprValue) { - checkIsNumberType(funcName = "SUM", value = value) - if (sum == null) sum = 0L - this.sum = value.numberValue() + this.sum!! - } - - override fun compute(): ExprValue { - return sum?.exprValue() ?: ExprValue.nullValue - } -} - -internal class AccumulatorAvg( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var sum: Number = 0.0 - var count: Long = 0L - - override fun nextValue(value: ExprValue) { - checkIsNumberType(funcName = "AVG", value = value) - this.sum += value.numberValue() - this.count += 1L - } - - override fun compute(): ExprValue = when (count) { - 0L -> ExprValue.nullValue - else -> (sum / bigDecimalOf(count)).exprValue() - } -} - -internal class AccumulatorMax( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var max: ExprValue = ExprValue.nullValue - - override fun nextValue(value: ExprValue) { - max = comparisonAccumulator(NaturalExprValueComparators.NULLS_LAST_DESC)(max, value) - } - - override fun compute(): ExprValue = max -} - -internal class AccumulatorMin( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var min: ExprValue = ExprValue.nullValue - - override fun nextValue(value: ExprValue) { - min = comparisonAccumulator(NaturalExprValueComparators.NULLS_LAST_ASC)(min, value) - } - - override fun compute(): ExprValue = min -} - -internal class AccumulatorCount( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var count: Long = 0L - - override fun nextValue(value: ExprValue) { - this.count += 1L - } - - override fun compute(): ExprValue = count.exprValue() -} - -internal class AccumulatorEvery( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - private var res: ExprValue? = null - override fun nextValue(value: ExprValue) { - checkIsBooleanType("EVERY", value) - res = res?.let { ExprValue.newBoolean(it.booleanValue() && value.booleanValue()) } ?: value - } - - override fun compute(): ExprValue = res ?: ExprValue.nullValue -} - -internal class AccumulatorAnySome( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - private var res: ExprValue? = null - override fun nextValue(value: ExprValue) { - checkIsBooleanType("ANY/SOME", value) - res = res?.let { ExprValue.newBoolean(it.booleanValue() || value.booleanValue()) } ?: value - } - - override fun compute(): ExprValue = res ?: ExprValue.nullValue -} - -internal class AccumulatorGroupAs( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - val exprValues = mutableListOf() - - override fun nextValue(value: ExprValue) { - exprValues.add(value) - } - - override fun compute(): ExprValue = ExprValue.newBag(exprValues) -} - -private fun comparisonAccumulator(comparator: NaturalExprValueComparators): (ExprValue?, ExprValue) -> ExprValue = - { left, right -> - when { - left == null || comparator.compare(left, right) > 0 -> right - else -> left - } - } - -internal fun checkIsNumberType(funcName: String, value: ExprValue) { - if (!value.type.isNumber) { - errNoContext( - message = "Aggregate function $funcName expects arguments of NUMBER type but the following value was provided: $value, with type of ${value.type}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_AGG_FUNCTION, - internal = false - ) - } -} - -internal fun checkIsBooleanType(funcName: String, value: ExprValue) { - if (value.type != ExprValueType.BOOL) { - errNoContext( - message = "Aggregate function $funcName expects arguments of BOOL type but the following value was provided: $value, with type of ${value.type}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_AGG_FUNCTION, - internal = false - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/Builtins.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/Builtins.kt deleted file mode 100644 index c4f4cf7255..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/Builtins.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.partiql.lang.eval.builtins - -/** - * TODO replace this internal value once we have function libraries - */ -internal val SCALAR_BUILTINS_DEFAULT = - SCALAR_BUILTINS_SQL + SCALAR_BUILTINS_EXT + SCALAR_BUILTINS_COLL_AGG + SYSTEM_BUILTINS_SQL diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/DefinitionalBuiltins.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/DefinitionalBuiltins.kt deleted file mode 100644 index 41c1e85033..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/DefinitionalBuiltins.kt +++ /dev/null @@ -1,88 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.ErrorDetails -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.eval.createErrorSignaler -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType - -/** Built-in functions specific to the PartiQL language definition. - * - * These functions are defined in the language specification in order to explain some of the PartiQL semantics, - * but are also made available to the user. - * Implementations of these built-ins can depend on compilation options and specific compilation options - * must be chosen in order to obtain an [ExprFunction] instance of these built-ins. - * It is intended that the compilation options used for instantiating a compiler are the same ones - * as used for instantiating these built-ins when adding them as functions to the compiler's environment. - * - */ -// TODO: Currently, the only compilation option in use is [TypingMode], so it is passed here by itself. -// Ideally, something like [CompileOptions] would be the argument instead, but [CompileOptions] is only used -// by the "evaluating compiler" and not by the "planning compiler", while this parameterization -// of the built-ins needs to be applicable in both. -internal fun definitionalBuiltins(typingMode: TypingMode): List = - listOf( - ExprFunctionCollToScalar(typingMode), - ) - -/** `coll_to_scalar` extracts the scalar value contained in a "singleton table", - * as performed in most cases of subquery coercion. - * If the input is a collection consisting of a single element, - * which in turn is a struct with exactly one attribute, - * `coll_to_scalar` returns the value of the attribute. - * For all other inputs, the result is either `MISSING` or an error, - * depending on the typing mode. - */ -internal class ExprFunctionCollToScalar(typingMode: TypingMode) : ExprFunction { - override val signature = FunctionSignature( - name = "coll_to_scalar", - requiredParameters = listOf(StaticType.ANY), - returnType = StaticType.ANY - ) - - // TODO: Is ErrorSignaler most appropriate to use here? - // By its external structure, ErrorSignaler is exactly what is needed: - // based on TypingMode, it either produces MISSING or an error. - // However, it is not used much, the existing usage is for a different setting - // (defined on top of the basic setting needed here), - // and the final error message is not best formatted. - // The latter appears to be a symptom of general accumulated cruft in error-handling. - private val signaler = typingMode.createErrorSignaler() - - /** Handler for situations when extraction cannot succeed. - * Produces either the MISSING or an error. */ - private fun hiccup(reason: String): ExprValue { - return signaler.error( - ErrorCode.EVALUATOR_NON_SINGLETON_COLLECTION - ) { ErrorDetails(metas = emptyMap(), message = reason) } - } - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val coll = required[0] - if (!coll.type.isSequence) { - return hiccup("because it is not a collection.") - } else { // coll is a LIST, BAG, or SEXP - val seq = coll.asSequence() - if (seq.count() != 1) { - return hiccup("because the collection does not contain exactly one member.") - } else { // we have a singleton collection - val struct = seq.first() - if (struct.type != ExprValueType.STRUCT) { - return hiccup("because the only member of the collection is not a struct.") - } else { // the single element is a struct - val vals = struct.asSequence() - if (vals.count() != 1) { - return hiccup("because the only struct member of the collection does not contain exactly one attribute.") - } else { // the single struct has exactly one attribute - return vals.first() - } - } - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/DynamicLookupExprFunction.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/DynamicLookupExprFunction.kt deleted file mode 100644 index 355ed45428..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/DynamicLookupExprFunction.kt +++ /dev/null @@ -1,104 +0,0 @@ -package org.partiql.lang.eval.builtins - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.stringValue -import org.partiql.lang.eval.throwUndefinedVariableException -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType - -/** - * The name of this function is [DYNAMIC_LOOKUP_FUNCTION_NAME], which includes a unique prefix and suffix so as to - * avoid clashes with user-defined functions. - */ -const val DYNAMIC_LOOKUP_FUNCTION_NAME = "\$__dynamic_lookup__" - -/** - * Performs dynamic variable resolution. Query authors should never call this function directly (and indeed it is - * named to avoid collision with the names of custom functions)--instead, the query planner injects call sites - * to this function to perform dynamic variable resolution of undefined variables. This provides a migration path - * for legacy customers that depend on this behavior. - * - * Arguments: - * - * 1. variable name (must be a symbol) - * 2. case sensitivity (must be a symbol; one of: `case_insensitive` or `case_sensitive`) - * 3. lookup strategy (must be a symbol; one of: `globals_then_locals` or `locals_then_globals`) - * 4. A variadic list of values to be searched. Only struct are searched. This is required because it is not - * currently possible to know the types of these arguments within the variable resolution pass - * ([org.partiql.lang.planner.transforms.LogicalToLogicalResolvedVisitorTransform]). Therefore all variables - * in the current scope must be included in the list of values to be searched. - * TODO: when the open type system's static type inferencer is working, static type information can be used to identify - * and remove non-struct types from call sites to this function. - */ -class DynamicLookupExprFunction : ExprFunction { - - override val signature = FunctionSignature( - name = DYNAMIC_LOOKUP_FUNCTION_NAME, - // Required parameters are: variable name, case sensitivity, lookup strategy and variadic list. - requiredParameters = listOf(StaticType.SYMBOL, StaticType.SYMBOL, StaticType.SYMBOL, StaticType.LIST), - returnType = StaticType.ANY - ) - - override fun callWithRequired( - session: EvaluationSession, - required: List - ): ExprValue { - val variadic = required[3].toList() - val variableName = required[0].stringValue() - val caseSensitivity = when (val caseSensitivityParameterValue = required[1].stringValue()) { - "case_sensitive" -> BindingCase.SENSITIVE - "case_insensitive" -> BindingCase.INSENSITIVE - else -> throw EvaluationException( - message = "Invalid case sensitivity: $caseSensitivityParameterValue", - errorCode = ErrorCode.INTERNAL_ERROR, - internal = true - ) - } - - val bindingName = BindingName(variableName, caseSensitivity) - - val globalsFirst = when (val lookupStrategyParameterValue = required[2].stringValue()) { - "locals_then_globals" -> false - "globals_then_locals" -> true - else -> throw EvaluationException( - message = "Invalid lookup strategy: $lookupStrategyParameterValue", - errorCode = ErrorCode.INTERNAL_ERROR, - internal = true - ) - } - - val found = when { - globalsFirst -> { - session.globals[bindingName] ?: searchLocals(variadic, bindingName) - } - else -> { - searchLocals(variadic, bindingName) ?: session.globals[bindingName] - } - } - - if (found == null) { - // We don't know the metas inside ExprFunction implementations. The ThunkFactory error handlers - // should add line & col info to the exception & rethrow anyway. - throwUndefinedVariableException(bindingName, metas = null) - } else { - return found - } - } - - private fun searchLocals(possibleLocations: List, bindingName: BindingName) = - possibleLocations.asSequence().map { - when (it.type) { - ExprValueType.STRUCT -> - it.bindings[bindingName] - else -> - null - } - }.firstOrNull { it != null } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsCollAgg.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsCollAgg.kt deleted file mode 100644 index 7fddcf3b04..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsCollAgg.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.builtins - -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.createUniqueExprValueFilter -import org.partiql.lang.eval.stringValue -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType -import org.partiql.types.StaticType.Companion.unionOf - -/** - * TODO replace this internal value once we have function libraries - */ -internal val SCALAR_BUILTINS_COLL_AGG = listOf( - ExprFunctionCollMax, - ExprFunctionCollMin, - ExprFunctionCollAvg, - ExprFunctionCollSum, - ExprFunctionCollCount, - ExprFunctionCollEvery, - ExprFunctionCollAny, - ExprFunctionCollSome, -) - -/** - * This class represents an aggregation function call (such as AVG, MAX, MIN, etc) -- but is meant to be operated outside - * of the relational algebra implementation of `aggregate`. In other words, the [CollectionAggregationFunction] allows - * users to call aggregation functions such as "AVG" on collections of scalars. While a user may use the function with the - * "Direct Usage" below, this function is also used within PartiQL to convert aggregate function calls that are outside - * of the scope of the relational algebra operator of aggregations. AKA -- we use this when the aggregation function calls - * are made outside of the projection clause, the HAVING clause, and ORDER BY clause. - * - * Direct Usage: coll_{AGGREGATE}('all', [0, 1, 2, 3]) - * where ${AGGREGATE} can be replaced with MAX, MIN, AVG, COUNT, and SUM - * - * Example (Direct) Usage: - * ``` - * SELECT a AS inputA, COLL_AVG(a) AS averagedA - * FROM << {'a': [0, 1]}, {'a': [10, 11]} >> - * WHERE COLL_AVG(a) > 0.5 - * ``` - * - * Example (Indirect) Usage: - * ``` - * SELECT a - * FROM << {'a': [0, 1]}, {'a': [10, 11]} >> - * WHERE AVG(a) > 0.5 - * ``` - * - * The above indirect example shows how this is leveraged. The WHERE clause does not allow aggregation functions to be passed to the - * aggregate operator, so we internally convert the AVG to a [CollectionAggregationFunction] (which is just an expression - * function call). - */ -internal sealed class CollectionAggregationFunction( - val name: String, - val accumulator: ((ExprValue) -> Boolean) -> Accumulator, -) : ExprFunction { - - companion object { - const val PREFIX = "coll_" - } - - private val collection = unionOf(StaticType.LIST, StaticType.BAG, StaticType.STRUCT, StaticType.SEXP) - - override val signature: FunctionSignature = FunctionSignature( - name = "$PREFIX$name", - requiredParameters = listOf(StaticType.STRING, collection), - returnType = StaticType.NUMERIC - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val filter = required[0].asQuantifierFilter() - val collection = required[1].asSequence() - // instantiate a fresh accumulator for each invocation as this is a "tuple-level" function - val acc = accumulator(filter) - collection.forEach { v -> acc.next(v) } - return acc.compute() - } - - private fun ExprValue.asQuantifierFilter() = when (stringValue().lowercase().trim()) { - "all" -> { _: ExprValue -> true } - "distinct" -> createUniqueExprValueFilter() - else -> throw IllegalArgumentException("Unrecognized set quantifier: $this") - } -} - -internal object ExprFunctionCollMax : CollectionAggregationFunction( - name = "max", - accumulator = ::AccumulatorMax, -) - -internal object ExprFunctionCollMin : CollectionAggregationFunction( - name = "min", - accumulator = ::AccumulatorMin -) - -internal object ExprFunctionCollAvg : CollectionAggregationFunction( - name = "avg", - accumulator = ::AccumulatorAvg -) - -internal object ExprFunctionCollSum : CollectionAggregationFunction( - name = "sum", - accumulator = ::AccumulatorSum -) - -internal object ExprFunctionCollCount : CollectionAggregationFunction( - name = "count", - accumulator = ::AccumulatorCount -) - -internal object ExprFunctionCollEvery : CollectionAggregationFunction( - name = "every", - accumulator = ::AccumulatorEvery -) - -internal object ExprFunctionCollAny : CollectionAggregationFunction( - name = "any", - accumulator = ::AccumulatorAnySome -) - -internal object ExprFunctionCollSome : CollectionAggregationFunction( - name = "some", - accumulator = ::AccumulatorAnySome -) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsExt.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsExt.kt deleted file mode 100644 index 1daa4860f7..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsExt.kt +++ /dev/null @@ -1,551 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.builtins - -import com.amazon.ion.Timestamp -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.eval.DEFAULT_COMPARATOR -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.bigDecimalValue -import org.partiql.lang.eval.builtins.internal.TimestampParser -import org.partiql.lang.eval.builtins.internal.adjustPrecisionTo -import org.partiql.lang.eval.builtins.internal.toOffsetDateTime -import org.partiql.lang.eval.builtins.timestamp.TimestampTemporalAccessor -import org.partiql.lang.eval.err -import org.partiql.lang.eval.errNoContext -import org.partiql.lang.eval.intValue -import org.partiql.lang.eval.stringValue -import org.partiql.lang.eval.time.NANOS_PER_SECOND -import org.partiql.lang.eval.time.Time -import org.partiql.lang.eval.timestampValue -import org.partiql.lang.eval.unnamedValue -import org.partiql.lang.syntax.impl.DateTimePart -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.types.UnknownArguments -import org.partiql.lang.util.propertyValueMapOf -import org.partiql.types.StaticType -import org.partiql.types.StaticType.Companion.unionOf -import java.math.BigDecimal -import java.time.DateTimeException -import java.time.Duration -import java.time.Period -import java.time.format.DateTimeFormatter -import java.time.temporal.UnsupportedTemporalTypeException -import java.util.TreeSet - -/** - * TODO replace this internal value once we have function libraries - */ -internal val SCALAR_BUILTINS_EXT = listOf( - ExprFunctionExists, - ExprFunctionUtcNow, - ExprFunctionFilterDistinct, - ExprFunctionDateAdd, - ExprFunctionDateDiff, - ExprFunctionMakeDate, - ExprFunctionMakeTime_1, - ExprFunctionMakeTime_2, - ExprFunctionToTimestamp_1, - ExprFunctionToTimestamp_2, - ExprFunctionSize, - ExprFunctionFromUnix, - ExprFunctionUnixTimestamp_1, - ExprFunctionUnixTimestamp_2, - ExprFunctionToString, - ExprFunctionTextReplace, -) - -/** - * Given a PartiQL value returns true if and only if the value is a non-empty container(bag, sexp, list or struct), - * returns false otherwise - */ -internal object ExprFunctionExists : ExprFunction { - - override val signature = FunctionSignature( - name = "exists", - requiredParameters = listOf(unionOf(StaticType.SEXP, StaticType.LIST, StaticType.BAG, StaticType.STRUCT)), - returnType = StaticType.BOOL, - unknownArguments = UnknownArguments.PASS_THRU - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val collection = required[0].asSequence() - val result = collection.any() - return ExprValue.newBoolean(result) - } -} - -/** - * Returns the current time in UTC as a timestamp. - */ -internal object ExprFunctionUtcNow : ExprFunction { - - override val signature = FunctionSignature( - name = "utcnow", - requiredParameters = listOf(), - returnType = StaticType.TIMESTAMP - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return ExprValue.newTimestamp(session.now) - } -} - -/** - * Returns a bag or list of distinct values contained within a bag, list, sexp, or struct. - * If the container is a struct, the field names are not considered. - */ -internal object ExprFunctionFilterDistinct : ExprFunction { - - override val signature = FunctionSignature( - name = "filter_distinct", - requiredParameters = listOf(unionOf(StaticType.BAG, StaticType.LIST, StaticType.SEXP, StaticType.STRUCT)), - returnType = unionOf(StaticType.BAG, StaticType.LIST) - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val argument = required.first() - // We cannot use a [HashSet] here because [ExprValue] does not implement .equals() and .hashCode() - val encountered = TreeSet(DEFAULT_COMPARATOR) - val seq = sequence { - argument.asSequence().forEach { - if (!encountered.contains(it)) { - encountered.add(it.unnamedValue()) - yield(it) - } - } - } - return when (argument.type) { - ExprValueType.LIST -> ExprValue.newList(seq) - else -> ExprValue.newBag(seq) - } - } -} - -/** - * Given a data part, a quantity and a timestamp, returns an updated timestamp by altering datetime part by quantity - * - * Where DateTimePart is one of - * * year - * * month - * * day - * * hour - * * minute - * * second - */ -internal object ExprFunctionDateAdd : ExprFunction { - - override val signature = FunctionSignature( - name = "date_add", - requiredParameters = listOf(StaticType.SYMBOL, StaticType.INT, StaticType.TIMESTAMP), - returnType = StaticType.TIMESTAMP - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val arg0 = required[0].stringValue() - val part = DateTimePart.safeValueOf(arg0) - val quantity = required[1].intValue() - val timestamp = required[2].timestampValue() - // TODO add a function lowering pass - return try { - val result = when (part) { - DateTimePart.YEAR -> timestamp.adjustPrecisionTo(part).addYear(quantity) - DateTimePart.MONTH -> timestamp.adjustPrecisionTo(part).addMonth(quantity) - DateTimePart.DAY -> timestamp.adjustPrecisionTo(part).addDay(quantity) - DateTimePart.HOUR -> timestamp.adjustPrecisionTo(part).addHour(quantity) - DateTimePart.MINUTE -> timestamp.adjustPrecisionTo(part).addMinute(quantity) - DateTimePart.SECOND -> timestamp.adjustPrecisionTo(part).addSecond(quantity) - else -> errNoContext( - "invalid datetime part for date_add: $arg0", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_DATE_PART, - internal = false - ) - } - ExprValue.newTimestamp(result) - } catch (e: IllegalArgumentException) { - // IllegalArgumentExcept is thrown when the resulting timestamp go out of supported timestamp boundaries - throw EvaluationException(e, errorCode = ErrorCode.EVALUATOR_TIMESTAMP_OUT_OF_BOUNDS, internal = false) - } - } -} - -/** - * Difference in datetime parts between two timestamps. If the first timestamp is later than the second the result is negative. - * - * Syntax: `DATE_DIFF(, , )` - * Where date time part is one of the following keywords: `year, month, day, hour, minute, second` - * - * Timestamps without all datetime parts are considered to be in the beginning of the missing parts to make calculation possible. - * For example: - * - 2010T is interpreted as 2010-01-01T00:00:00.000Z - * - date_diff(month, `2010T`, `2010-05T`) results in 4 - * - * If one of the timestamps has a time component then they are a day apart only if they are 24h apart, examples: - * - date_diff(day, `2010-01-01T`, `2010-01-02T`) results in 1 - * - date_diff(day, `2010-01-01T23:00Z`, `2010-01-02T01:00Z`) results in 0 as they are only 2h apart - */ -internal object ExprFunctionDateDiff : ExprFunction { - - override val signature = FunctionSignature( - name = "date_diff", - requiredParameters = listOf(StaticType.SYMBOL, StaticType.TIMESTAMP, StaticType.TIMESTAMP), - returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val arg0 = required[0].stringValue() - val part = DateTimePart.safeValueOf(arg0) - val l = required[1].timestampValue().toOffsetDateTime() - val r = required[2].timestampValue().toOffsetDateTime() - // TODO add a function lowering pass - val result = when (part) { - DateTimePart.YEAR -> Period.between(l.toLocalDate(), r.toLocalDate()).years - DateTimePart.MONTH -> Period.between(l.toLocalDate(), r.toLocalDate()).toTotalMonths() - DateTimePart.DAY -> Duration.between(l, r).toDays() - DateTimePart.HOUR -> Duration.between(l, r).toHours() - DateTimePart.MINUTE -> Duration.between(l, r).toMinutes() - DateTimePart.SECOND -> Duration.between(l, r).toMillis() / 1_000 - else -> errNoContext( - "invalid datetime part for date_diff: $arg0", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_DATE_PART, - internal = false - ) - } - return ExprValue.newInt(result.toLong()) - } -} - -/** - * Creates a DATE ExprValue from the date fields year, month and day. - * Takes year, month and day as integers and propagates NULL if any of these arguments is unknown (i.e. NULL or MISSING) - * - * make_date(, , ) - */ -internal object ExprFunctionMakeDate : ExprFunction { - - override val signature = FunctionSignature( - name = "make_date", - requiredParameters = listOf(StaticType.INT, StaticType.INT, StaticType.INT), - returnType = StaticType.DATE - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val (year, month, day) = required.map { - // TODO this should be handled by the signature validation, keeping it now as to not change any behavior - if (it.type != ExprValueType.INT) { - err( - message = "Invalid argument type for make_date", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - errorContext = propertyValueMapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.FUNCTION_NAME to "make_date", - Property.ACTUAL_ARGUMENT_TYPES to it.type.name - ), - internal = false - ) - } - it.intValue() - } - return try { - ExprValue.newDate(year, month, day) - } catch (e: DateTimeException) { - err( - message = "Date field value out of range. $year-$month-$day", - errorCode = ErrorCode.EVALUATOR_DATE_FIELD_OUT_OF_RANGE, - errorContext = propertyValueMapOf(), - internal = false - ) - } - } -} - -/** - * Creates a TIME ExprValue from the time fields hour, minute, second and optional timezone_minutes. - * Takes hour, minute and optional timezone_minutes as integers, second as decimal and propagates NULL if any of these arguments is unknown (i.e. NULL or MISSING) - * - * make_time(, , ) - * make_time(, , , ) - */ - -internal abstract class ExprFunctionMakeTime : ExprFunction { - protected fun makeTime( - hour: Int, - minute: Int, - second: BigDecimal, - tzMinutes: Int? - ): ExprValue { - try { - return ExprValue.newTime( - Time.of( - hour, - minute, - second.toInt(), - (second.remainder(BigDecimal.ONE).multiply(NANOS_PER_SECOND.toBigDecimal())).toInt(), - second.scale(), - tzMinutes - ) - ) - } catch (e: EvaluationException) { - err( - message = e.message, - errorCode = ErrorCode.EVALUATOR_TIME_FIELD_OUT_OF_RANGE, - errorContext = e.errorContext, - internal = false - ) - } - } -} - -internal object ExprFunctionMakeTime_1 : ExprFunctionMakeTime() { - override val signature = FunctionSignature( - name = "make_time", - requiredParameters = listOf(StaticType.INT, StaticType.INT, StaticType.DECIMAL), - returnType = StaticType.TIME - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val (hour, min, sec) = required - return makeTime(hour.intValue(), min.intValue(), sec.bigDecimalValue(), null) - } -} - -internal object ExprFunctionMakeTime_2 : ExprFunctionMakeTime() { - override val signature = FunctionSignature( - name = "make_time", - requiredParameters = listOf(StaticType.INT, StaticType.INT, StaticType.DECIMAL, StaticType.INT), - returnType = StaticType.TIME - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val (hour, min, sec, opt) = required - return makeTime(hour.intValue(), min.intValue(), sec.bigDecimalValue(), opt.intValue()) - } -} - -/** - * PartiQL function to convert a formatted string into an Ion Timestamp. - */ -internal abstract class ExprFunctionToTimestamp : ExprFunction - -internal object ExprFunctionToTimestamp_1 : ExprFunctionToTimestamp() { - - override val signature = FunctionSignature( - name = "to_timestamp", - requiredParameters = listOf(StaticType.STRING), - returnType = StaticType.TIMESTAMP - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val ts = try { - Timestamp.valueOf(required[0].stringValue()) - } catch (ex: IllegalArgumentException) { - throw EvaluationException( - message = "Timestamp was not a valid ion timestamp", - errorCode = ErrorCode.EVALUATOR_ION_TIMESTAMP_PARSE_FAILURE, - errorContext = PropertyValueMap(), - cause = ex, - internal = false - ) - } - return ExprValue.newTimestamp(ts) - } -} - -internal object ExprFunctionToTimestamp_2 : ExprFunctionToTimestamp() { - - override val signature = FunctionSignature( - name = "to_timestamp", - requiredParameters = listOf(StaticType.STRING, StaticType.STRING), - returnType = StaticType.TIMESTAMP - ) - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val ts = TimestampParser.parseTimestamp(required[0].stringValue(), required[1].stringValue()) - return ExprValue.newTimestamp(ts) - } -} - -/** - * Builtin function to return the size of a container type, i.e. size of Lists, Structs and Bags. This function - * propagates null and missing values as described in docs/Functions.md - * - * syntax: `size()` where container can be a BAG, SEXP, STRUCT or LIST. - */ -internal object ExprFunctionSize : ExprFunction { - - override val signature = FunctionSignature( - name = "size", - requiredParameters = listOf(unionOf(StaticType.LIST, StaticType.BAG, StaticType.STRUCT, StaticType.SEXP)), - returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val collection = required[0] - val result = collection.count() - return ExprValue.newInt(result) - } -} - -/** - * Builtin function to convert the given unix epoch into a PartiQL `TIMESTAMP` [ExprValue]. A unix epoch represents - * the seconds since '1970-01-01 00:00:00' UTC. Largely based off MySQL's FROM_UNIXTIME. - * - * Syntax: `FROM_UNIXTIME(unix_timestamp)` - * Where unix_timestamp is a (potentially decimal) numeric value. If unix_timestamp is a decimal, the returned - * `TIMESTAMP` will have fractional seconds. If unix_timestamp is an integer, the returned `TIMESTAMP` will not have - * fractional seconds. - * - * When given a negative numeric value, this function returns a PartiQL `TIMESTAMP` [ExprValue] before the last epoch. - * When given a non-negative numeric value, this function returns a PartiQL `TIMESTAMP` [ExprValue] after the last - * epoch. - */ -internal object ExprFunctionFromUnix : ExprFunction { - - private val millisPerSecond = BigDecimal(1000) - - override val signature = FunctionSignature( - name = "from_unixtime", - requiredParameters = listOf(unionOf(StaticType.DECIMAL, StaticType.INT)), - returnType = StaticType.TIMESTAMP - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val unixTimestamp = required[0].bigDecimalValue() - val numMillis = unixTimestamp.times(millisPerSecond).stripTrailingZeros() - val timestamp = Timestamp.forMillis(numMillis, null) - return ExprValue.newTimestamp(timestamp) - } -} - -/** - * Builtin function to convert the given PartiQL `TIMESTAMP` [ExprValue] into a unix epoch, where a unix epoch - * represents the seconds since '1970-01-01 00:00:00' UTC. Largely based off MySQL's UNIX_TIMESTAMP. - * - * Syntax: `UNIX_TIMESTAMP([timestamp])` - * - * If UNIX_TIMESTAMP() is called with no [timestamp] argument, it returns the number of whole seconds since - * '1970-01-01 00:00:00' UTC as a PartiQL `INT` [ExprValue] - * - * If UNIX_TIMESTAMP() is called with a [timestamp] argument, it returns the number of seconds from - * '1970-01-01 00:00:00' UTC to the given [timestamp] argument. If given a [timestamp] before the last epoch, will - * return the number of seconds before the last epoch as a negative number. The return value will be a decimal if and - * only if the given [timestamp] has a fractional seconds part. - * - * The valid range of argument values is the range of PartiQL's `TIMESTAMP` value. - */ -internal abstract class ExprFunctionUnixTimestamp : ExprFunction { - private val millisPerSecond = BigDecimal(1000) - protected fun epoch(timestamp: Timestamp): BigDecimal = timestamp.decimalMillis.divide(millisPerSecond) -} - -internal object ExprFunctionUnixTimestamp_1 : ExprFunctionUnixTimestamp() { - - override val signature = FunctionSignature( - name = "unix_timestamp", - requiredParameters = listOf(), - returnType = unionOf(StaticType.INT, StaticType.DECIMAL) - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return ExprValue.newInt(epoch(session.now).toLong()) - } -} - -internal object ExprFunctionUnixTimestamp_2 : ExprFunctionUnixTimestamp() { - - override val signature = FunctionSignature( - name = "unix_timestamp", - requiredParameters = listOf(StaticType.TIMESTAMP), - returnType = unionOf(StaticType.INT, StaticType.DECIMAL) - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val timestamp = required[0].timestampValue() - val epochTime = epoch(timestamp) - return if (timestamp.decimalSecond.scale() == 0) { - ExprValue.newInt(epochTime.toLong()) - } else { - ExprValue.newDecimal(epochTime) - } - } -} - -/** - * Given a timestamp and a format pattern return a string representation of the timestamp in the given format. - * - * Where TimeFormatPattern is a String with the following special character interpretations - */ -internal object ExprFunctionToString : ExprFunction { - - override val signature = FunctionSignature( - name = "to_string", - requiredParameters = listOf(StaticType.TIMESTAMP, StaticType.STRING), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val pattern = required[1].stringValue() - - val formatter: DateTimeFormatter = try { - DateTimeFormatter.ofPattern(pattern) - } catch (ex: IllegalArgumentException) { - errInvalidFormatPattern(pattern, ex) - } - - val timestamp = required[0].timestampValue() - val temporalAccessor = TimestampTemporalAccessor(timestamp) - try { - return ExprValue.newString(formatter.format(temporalAccessor)) - } catch (ex: UnsupportedTemporalTypeException) { - errInvalidFormatPattern(pattern, ex) - } catch (ex: DateTimeException) { - errInvalidFormatPattern(pattern, ex) - } - } - - private fun errInvalidFormatPattern(pattern: String, cause: Exception): Nothing { - val pvmap = PropertyValueMap() - pvmap[Property.TIMESTAMP_FORMAT_PATTERN] = pattern - throw EvaluationException( - "Invalid DateTime format pattern", - ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, - pvmap, - cause, - internal = false - ) - } -} - -/** text_replace(string, from, to) -- in [string], replaces each occurrence of [from] with [to]. - */ -internal object ExprFunctionTextReplace : ExprFunction { - override val signature = FunctionSignature( - name = "text_replace", - requiredParameters = listOf(StaticType.TEXT, StaticType.TEXT, StaticType.TEXT), - returnType = StaticType.TEXT, - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val string = required[0].stringValue() - val from = required[1].stringValue() - val to = required[2].stringValue() - return ExprValue.newString(string.replace(from, to)) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsSql.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsSql.kt deleted file mode 100644 index 34dc6822be..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsSql.kt +++ /dev/null @@ -1,860 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.builtins - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.builtins.internal.ExprFunctionBinaryNumeric -import org.partiql.lang.eval.builtins.internal.ExprFunctionMeasure -import org.partiql.lang.eval.builtins.internal.ExprFunctionUnaryNumeric -import org.partiql.lang.eval.builtins.internal.codepointLeadingTrim -import org.partiql.lang.eval.builtins.internal.codepointOverlay -import org.partiql.lang.eval.builtins.internal.codepointPosition -import org.partiql.lang.eval.builtins.internal.codepointTrailingTrim -import org.partiql.lang.eval.builtins.internal.codepointTrim -import org.partiql.lang.eval.builtins.internal.extractedValue -import org.partiql.lang.eval.builtins.internal.transformIntType -import org.partiql.lang.eval.bytesValue -import org.partiql.lang.eval.dateTimePartValue -import org.partiql.lang.eval.dateValue -import org.partiql.lang.eval.errIntOverflow -import org.partiql.lang.eval.errNoContext -import org.partiql.lang.eval.intValue -import org.partiql.lang.eval.isUnknown -import org.partiql.lang.eval.stringValue -import org.partiql.lang.eval.timeValue -import org.partiql.lang.eval.timestampValue -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.util.bigDecimalOf -import org.partiql.lang.util.coerceNumbers -import org.partiql.lang.util.compareTo -import org.partiql.lang.util.exp -import org.partiql.lang.util.isNaN -import org.partiql.lang.util.isNegInf -import org.partiql.lang.util.isPosInf -import org.partiql.lang.util.ln -import org.partiql.lang.util.power -import org.partiql.lang.util.squareRoot -import org.partiql.types.AnyOfType -import org.partiql.types.StaticType -import org.partiql.types.StaticType.Companion.unionOf -import java.math.BigDecimal -import java.math.RoundingMode -import kotlin.math.pow - -/** - * Reference SQL-99 20.70 - * - * TODO replace this internal value once we have function libraries - */ -internal val SCALAR_BUILTINS_SQL = listOf( - ExprFunctionAbs, - ExprFunctionMod, - ExprFunctionCeil, - ExprFunctionCeiling, - ExprFunctionFloor, - ExprFunctionSqrt, - ExprFunctionExp, - ExprFunctionPow, - ExprFunctionLn, - ExprFunctionLower, - ExprFunctionUpper, - ExprFunctionBitLength, - ExprFunctionCharLength, - ExprFunctionCharacterLength, - ExprFunctionOctetLength, - ExprFunctionSubstring_1, - ExprFunctionSubstring_2, - ExprFunctionTrim_1, - ExprFunctionTrim_2, - ExprFunctionTrim_3, - ExprFunctionPosition, - ExprFunctionOverlay_1, - ExprFunctionOverlay_2, - ExprFunctionExtract, - ExprFunctionCardinality -) - -/** - * ABS operates on a numeric argument and returns its absolute value in the same most specific type. - */ -internal object ExprFunctionAbs : ExprFunctionUnaryNumeric("abs") { - - override fun call(x: Number): Number = when (x) { - is Long -> { - if (x == Long.MIN_VALUE) { - errIntOverflow(8) - } else { - kotlin.math.abs(x) - } - } - is Double -> kotlin.math.abs(x) - is Float -> kotlin.math.abs(x) - is BigDecimal -> x.abs() - else -> errNoContext( - message = "Unknown number type", - errorCode = ErrorCode.INTERNAL_ERROR, - internal = true - ) - } -} - -/** - * MOD operates on two exact numeric arguments with scale 0 (zero) and returns - * the modulus (remainder) of the first argument divided by the second argument as an exact - * numeric with scale 0 (zero). - * - * If the second argument is zero, an EVALUATOR_ARITHMETIC_EXCEPTION will be thrown. - */ -internal object ExprFunctionMod : ExprFunction { - - override val signature = FunctionSignature( - name = "mod", - requiredParameters = listOf(StaticType.INT, StaticType.INT), - returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val x = required[0].intValue() - val y = required[1].intValue() - if (y == 0) { - errNoContext( - message = "Division by zero", - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = true - ) - } - val result = x % y - return ExprValue.newInt(result) - } -} - -/** - * Returns the nearest integer greater than or equal to the input. - */ -internal object ExprFunctionCeil : ExprFunctionUnaryNumeric("ceil") { - - override fun call(x: Number): Number = when (x) { - java.lang.Double.POSITIVE_INFINITY, java.lang.Double.NEGATIVE_INFINITY, java.lang.Double.NaN -> x - // support for numbers that are larger than 64 bits. - else -> transformIntType(bigDecimalOf(x).setScale(0, RoundingMode.CEILING).toBigIntegerExact()) - } -} - -/** - * Returns the nearest integer greater than or equal to the input. - */ -internal object ExprFunctionCeiling : ExprFunctionUnaryNumeric("ceiling") { - - override fun call(x: Number): Number = ExprFunctionCeil.call(x) -} - -/** - * Returns the absolute value of the given number. - * Note that abs(n) will throw an EVALUATOR_INTEGER_OVERFLOW when n is both of type INT and n = INT.MIN_VALUE. - */ -internal object ExprFunctionFloor : ExprFunctionUnaryNumeric("floor") { - - override fun call(x: Number): Number = when (x) { - java.lang.Double.POSITIVE_INFINITY, java.lang.Double.NEGATIVE_INFINITY, java.lang.Double.NaN -> x - else -> transformIntType(bigDecimalOf(x).setScale(0, RoundingMode.FLOOR).toBigIntegerExact()) - } -} - -/** - * Returns the square root of the given number. - * The input number is required to be non-negative. - */ -internal object ExprFunctionSqrt : ExprFunctionUnaryNumeric("sqrt") { - - override fun call(x: Number): Number { - if (x < 0L) { - errNoContext( - "Cannot take root of a negative number", - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = false - ) - } - return when (x) { - is Long -> kotlin.math.sqrt(x.toDouble()) - is Double -> kotlin.math.sqrt(x) - is Float -> kotlin.math.sqrt(x) - is BigDecimal -> x.squareRoot() - else -> errNoContext( - message = "Unknown number type", - errorCode = ErrorCode.INTERNAL_ERROR, - internal = true - ) - } - } -} - -/** - * Returns e^x for a given x. - * - * - exp(NaN) is NaN - * - exp(+Inf) is +Inf - * - exp(-Inf) is 0.0 - */ -internal object ExprFunctionExp : ExprFunctionUnaryNumeric("exp") { - override fun call(x: Number): Number = when (x) { - is Long -> kotlin.math.exp(x.toDouble()) - is Double -> kotlin.math.exp(x) - is Float -> kotlin.math.exp(x) - is BigDecimal -> x.exp() - else -> errNoContext( - message = "Unknown number type", - errorCode = ErrorCode.INTERNAL_ERROR, - internal = true - ) - } -} - -/** - * Coercion is needed for this operation, since it is binary. - * if the operation involves special value `+inf`, `-inf`, `nan`, the result will be a float. - * else if the operation involves decimal, the result will be a decimal - * else the result will be a float. - * - * Note that if x is a negative number, than y must be an integer value, (not necessarily integer type), - * otherwise an EVALUATOR_ARITHMETIC_EXCEPTION will be thrown. - * Special Case: - * pow(x, 0.0) is 1.0; - * pow(x, 1.0) == x; - * pow(x, NaN) is NaN; - * pow(NaN, x) is NaN for x != 0.0; - * pow(x, Inf) is NaN for abs(x) == 1.0 - */ -internal object ExprFunctionPow : ExprFunctionBinaryNumeric("pow") { - - override fun call(x: Number, y: Number): Number { - // CoerceNumber(double, bigDecimal) will attempt to convert the double value to bigDecimal - // and in case of the double value being one of the special number, `+inf`, `-inf`, `nan`, - // an error will be thrown. - // we (presumably) want to avoid this - val (first, second) = if (x.isPosInf || x.isNegInf || x.isNaN) { - x to y.toDouble() - } else if (y.isPosInf || y.isNegInf || y.isNaN) { - x.toDouble() to y - } else { - coerceNumbers(x, y) - } - - return when (first) { - is Long -> first.toDouble().pow(second.toDouble()) - is Double -> { - if (first < 0.0 && ((second as Double) % 1.0 != 0.0)) { - errNoContext( - message = "a negative number raised to a non-integer power yields a complex result", - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = false - ) - } - first.pow(second as Double) - } - is BigDecimal -> - try { - first.power(second as BigDecimal) - } catch (e: Exception) { - errNoContext( - message = e.message ?: "Arithmetic Error", - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = false - ) - } - else -> throw IllegalStateException() - } - } -} - -/** - * Returns the natural log of the given number. - * - * The input number is required to be a positive number, otherwise an EVALUATOR_ARITHMETIC_EXCEPTION will be thrown. - */ -internal object ExprFunctionLn : ExprFunctionUnaryNumeric("ln") { - - override fun call(x: Number): Number { - if (x <= 0L) { - errNoContext( - "Cannot take root of a non-positive number", - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = false - ) - } - return when (x) { - is Long -> kotlin.math.ln(x.toDouble()) - is Double -> kotlin.math.ln(x) - is Float -> kotlin.math.ln(x) - is BigDecimal -> x.ln() - else -> errNoContext( - message = "Unknown number type", - errorCode = ErrorCode.INTERNAL_ERROR, - internal = true - ) - } - } -} - -/** - * Given a string convert all upper case characters to lower case characters. - * - * Any non-upper cased characters remain unchanged. This operation does rely on the locale specified by the runtime - * configuration. This implementation uses Java's String.lowercase(). - */ -internal object ExprFunctionLower : ExprFunction { - - override val signature = FunctionSignature( - name = "lower", - requiredParameters = listOf(StaticType.TEXT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val str = required[0].stringValue() - val result = str.lowercase() - return ExprValue.newString(result) - } -} - -/** - * Given a string convert all lower case characters to upper case characters. - * - * Any non-lower cases characters remain unchanged. This operation does rely on the locale specified by the runtime - * configuration. The implementation uses Java's String.lowercase(). - */ -internal object ExprFunctionUpper : ExprFunction { - - override val signature = FunctionSignature( - name = "upper", - requiredParameters = listOf(AnyOfType(setOf(StaticType.STRING, StaticType.SYMBOL))), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val str = required[0].stringValue() - val result = str.toUpperCase() - return ExprValue.newString(result) - } -} - -/** - * Returns the number of bits in the input string - */ -internal object ExprFunctionBitLength : ExprFunctionMeasure("bit_length", BITSTRING) { - - override fun call(value: ExprValue): Int = ExprFunctionOctetLength.call(value) * 8 -} - -/** - * Counts the number of characters in the specified string, where 'character' is defined as a single unicode code point. - * - * Same as CHARACTER_LENGTH - */ -internal object ExprFunctionCharLength : ExprFunctionMeasure("char_length", StaticType.TEXT) { - - override fun call(value: ExprValue): Int = codepointLength(value) -} - -/** - * Counts the number of characters in the specified string, where 'character' is defined as a single unicode code point. - * - * Same as CHAR_LENGTH - */ -internal object ExprFunctionCharacterLength : ExprFunctionMeasure("character_length", StaticType.TEXT) { - - override fun call(value: ExprValue): Int = codepointLength(value) -} - -private fun codepointLength(value: ExprValue): Int { - val str = value.stringValue() - return str.codePointCount(0, str.length) -} - -/** - * If an is specified, then let S be the . The - * result of the is the smallest integer not less than the quotient of the - * division (BIT_LENGTH(S)/8). - */ -internal object ExprFunctionOctetLength : ExprFunctionMeasure("octet_length", BITSTRING) { - - override fun call(value: ExprValue): Int { - val bytes = when { - value.type.isText -> value.stringValue().toByteArray(Charsets.UTF_8) - else -> { - // Does not throw if value.type.isLob, otherwise will throw the appropriate evaluation exception - value.bytesValue() - } - } - return bytes.size - } -} - -/** - * Built in function to return the substring of an existing string. This function - * propagates null and missing values as described in docs/Functions.md - * - * From the SQL-92 spec, page 135: - * ``` - * 1) If is specified, then: - * a) Let C be the value of the , - * let LC be the length of C, and - * let S be the value of the . - * - * b) If is specified, then: - * let L be the value of and - * let E be S+L. - * Otherwise: - * let E be the larger of LC + 1 and S. - * - * c) If either C, S, or L is the null value, then the result of - * the is the null value. - * - * d) If E is less than S, then an exception condition is raised: - * data exception-substring error. - * - * e) Case: - * i) If S is greater than LC or if E is less than 1, then the - * result of the is a zero- - * length string. - * - * ii) Otherwise, - * 1) Let S1 be the larger of S and 1. Let E1 be the smaller - * of E and LC+1. Let L1 be E1-S1. - * - * 2) The result of the is - * a character string containing the L1 characters of C - * starting at character number S1 in the same order that - * the characters appear in C. - * - * Pseudocode: - * func substring(): - * # Section 1-a - * str = - * strLength = LENGTH(str) - * startPos = - * - * # Section 1-b - * sliceLength = - * if sliceLength is specified: - * endPos = startPos + sliceLength - * else: - * endPos = greater_of(strLength + 1, startPos) - * - * # Section 1-c: - * if str, startPos, or (sliceLength is specified and is null): - * return null - * - * # Section 1-d - * if endPos < startPos: - * throw exception - * - * # Section 1-e-i - * if startPos > strLength or endPos < 1: - * return '' - * else: - * # Section 1-e-ii - * S1 = greater_of(startPos, 1) - * E1 = lesser_of(endPos, strLength + 1) - * L1 = E1 - S1 - * return java's substring(C, S1, E1) - */ - -internal abstract class ExprFunctionSubstring : ExprFunction { - protected fun substring(target: String, startPosition: Int, quantity: Int? = null): ExprValue { - val codePointCount = target.codePointCount(0, target.length) - if (startPosition > codePointCount) { - return ExprValue.newString("") - } - - // startPosition starts at 1 - // calculate this before adjusting start position to account for negative startPosition - val endPosition = when (quantity) { - null -> codePointCount - else -> Integer.min(codePointCount, startPosition + quantity - 1) - } - - // Clamp start indexes to values that make sense for java substring - val adjustedStartPosition = Integer.max(0, startPosition - 1) - - if (endPosition < adjustedStartPosition) { - return ExprValue.newString("") - } - - val byteIndexStart = target.offsetByCodePoints(0, adjustedStartPosition) - val byteIndexEnd = target.offsetByCodePoints(0, endPosition) - - return ExprValue.newString(target.substring(byteIndexStart, byteIndexEnd)) - } -} -internal object ExprFunctionSubstring_1 : ExprFunctionSubstring() { - - /** - * TODO implement substring pattern (STRING, STRING, INT) -> STRING, requires sql regex pattern parsing - */ - override val signature = FunctionSignature( - name = "substring", - requiredParameters = listOf(StaticType.STRING, StaticType.INT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val target = required[0].stringValue() - if (required[1].type != ExprValueType.INT) { - errNoContext( - message = "Function substring with two parameters must be of form substring( FROM )", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } - val startPosition = required[1].intValue() - return substring(target, startPosition) - } -} - -internal object ExprFunctionSubstring_2 : ExprFunctionSubstring() { - - /** - * TODO implement substring pattern (STRING, STRING, INT) -> STRING, requires sql regex pattern parsing - */ - override val signature = FunctionSignature( - name = "substring", - requiredParameters = listOf(StaticType.STRING, StaticType.INT, StaticType.INT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val quantity = required.last().intValue() - if (quantity < 0) { - errNoContext( - message = "Argument 3 of substring has to be greater than 0.", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } - val target = required[0].stringValue() - if (required[1].type != ExprValueType.INT) { - errNoContext( - message = "Regular expression substring (SQL T581) currently not supported", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } - val startPosition = required[1].intValue() - return substring(target, startPosition, quantity) - } -} - -/** - * From section 6.7 of SQL 92 spec: - * ``` - * 6) If is specified, then - * a) If FROM is specified, then either or or both shall be specified. - * - * b) If is not specified, then BOTH is implicit. - * - * c) If is not specified, then ' ' is implicit. - * - * d) If TRIM ( SRC ) is specified, then TRIM ( BOTH ' ' FROM SRC ) is implicit. - * - * e) The data type of the is variable-length character string with maximum length equal to the - * fixed length or maximum variable length of the . - * - * f) If a is specified, then and shall be comparable. - * - * g) The character repertoire and form-of-use of the are the same as those of the . - * - * h) The collating sequence and the coercibility attribute are determined as specified for monadic operators in - * Subclause 4.2.3, "Rules determining collating sequence usage", where the of TRIM plays the - * role of the monadic operand. - * ``` - * - * Where: - * * ` ::= LEADING | TRAILING | BOTH` - * * ` ::= ` - * * ` ::= ` - */ -internal abstract class ExprFunctionTrim : ExprFunction { - protected fun trim1Arg(sourceString: ExprValue): String = codepointTrim(sourceString.stringValue()) - - /** - * Small optimization to eliminate the TrimSpecification enum, still temporary since we'll add function lowering. - * Return the behavior on switch rather than switch to get an enum then switch again on the enum for behavior. - */ - private fun getTrimFnOrNull(trimSpecification: String): ((String, String?) -> String)? = - when (trimSpecification.lowercase().trim()) { - "both" -> ::codepointTrim - "leading" -> ::codepointLeadingTrim - "trailing" -> ::codepointTrailingTrim - else -> null - } - - protected fun trim2Arg(specificationOrToRemove: ExprValue, sourceString: ExprValue): String { - // Type signature checking should have handled this - if (!specificationOrToRemove.type.isText) { - errNoContext( - message = "with two arguments trim's first argument must be either the specification or a 'to remove' string", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_TRIM, - internal = false - ) - } - val arg0 = specificationOrToRemove.stringValue() - val arg1 = sourceString.stringValue() - return when (val trimFn = getTrimFnOrNull(arg0)) { - null -> codepointTrim(arg1, arg0) - else -> trimFn.invoke(arg1, null) - } - } - - protected fun trim3Arg(specification: ExprValue, toRemove: ExprValue, sourceString: ExprValue): String { - val arg0 = specification.stringValue() - val arg1 = toRemove.stringValue() - val arg2 = sourceString.stringValue() - return when (val trimFn = getTrimFnOrNull(arg0)) { - null -> { - // TODO with ANTLR, the invalid_argument should be caught in visitTrimFunction in PartiQLVisitor - // We should decide where this error shall be caught and whether it is a parsing error or an evaluator error. - // This error should also be caught in the function lowering as part of logical planning - errNoContext( - message = "'$arg0' is an unknown trim specification, valid values: BOTH, TRAILING, LEADING", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_TRIM, - internal = false - ) - } - else -> trimFn.invoke(arg2, arg1) - } - } -} - -internal object ExprFunctionTrim_1 : ExprFunctionTrim() { - - override val signature = FunctionSignature( - name = "trim", - requiredParameters = listOf(StaticType.TEXT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val result = trim1Arg(required[0]) - return ExprValue.newString(result) - } -} - -internal object ExprFunctionTrim_2 : ExprFunctionTrim() { - - override val signature = FunctionSignature( - name = "trim", - requiredParameters = listOf(StaticType.TEXT, StaticType.STRING), - returnType = StaticType.STRING - ) - - override fun callWithRequired( - session: EvaluationSession, - required: List, - ): ExprValue { - val result = trim2Arg(required[0], required[1]) - return ExprValue.newString(result) - } -} - -internal object ExprFunctionTrim_3 : ExprFunctionTrim() { - - override val signature = FunctionSignature( - name = "trim", - requiredParameters = listOf(StaticType.TEXT, StaticType.STRING, StaticType.STRING), - returnType = StaticType.STRING - ) - - override fun callWithRequired( - session: EvaluationSession, - required: List, - ): ExprValue { - val result = trim3Arg(required[0], required[1], required[2]) - return ExprValue.newString(result) - } -} - -/** - * SQL-99 p.15 and p.21 - * - * determines the first position, if any, at which one string, S1, occurs within - * another, S2. If S1 is of length zero, then it occurs at position 1 (one) for any value of S2. If S1 - * does not occur in S2, then zero is returned. The declared type of a is exact numeric - * - * when applied to binary strings is identical in syntax and semantics to the - * corresponding operation on character strings except that the operands are binary strings. - */ -internal object ExprFunctionPosition : ExprFunction { - - override val signature = FunctionSignature( - name = "position", - requiredParameters = listOf(StaticType.TEXT, StaticType.TEXT), - returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - // POSITION(s1 IN s2) - val s1 = required[0].stringValue() - val s2 = required[1].stringValue() - val result = codepointPosition(s2, s1) - return ExprValue.newInt(result) - } -} - -/** - * ::= - * OVERLAY - * PLACING - * FROM - * [ FOR ] - * - * - * is a function, OVERLAY, that modifies a string argument by replacing - * a given substring of the string, which is specified by a given numeric starting position and a - * given numeric length, with another string (called the replacement string). When the length of - * the substring is zero, nothing is removed from the original string and the string returned by the - * function is the result of inserting the replacement string into the original string at the starting position. - * - * The is equivalent to: - * - * SUBSTRING ( CV FROM 1 FOR SP - 1 ) || RS || SUBSTRING ( CV FROM SP + SL ) - * - * Where CV is the characters value, RS is the replacement string, SP is start position, SL is CV length - */ -internal abstract class ExprFunctionOverlay : ExprFunction { - protected fun overlay(arg0: ExprValue, arg1: ExprValue, arg2: ExprValue, arg3: ExprValue? = null): ExprValue { - val position = arg2.intValue() - if (position < 1) { - errNoContext( - message = "invalid position '$position', must be at least 1", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_TRIM, - internal = false - ) - } - val source = arg0.stringValue() - val overlay = arg1.stringValue() - val length = arg3?.intValue() ?: overlay.length - val result = codepointOverlay(source, overlay, position, length) - return ExprValue.newString(result) - } -} -internal object ExprFunctionOverlay_1 : ExprFunctionOverlay() { - - override val signature = FunctionSignature( - name = "overlay", - requiredParameters = listOf(StaticType.TEXT, StaticType.TEXT, StaticType.INT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return overlay(required[0], required[1], required[2]) - } -} - -internal object ExprFunctionOverlay_2 : ExprFunctionOverlay() { - - override val signature = FunctionSignature( - name = "overlay", - requiredParameters = listOf(StaticType.TEXT, StaticType.TEXT, StaticType.INT, StaticType.INT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return overlay(required[0], required[1], required[2], required[3]) - } -} - -/** - * Given a datetime part and a datetime type returns then datetime's datetime part value. - * - * ExtractDateTimePart is one of - * * year - * * month - * * day - * * hour - * * minute - * * second - * * timezone_hour - * * timezone_minute - * - * DateTime type is one of - * * DATE - * * TIME - * * TIMESTAMP - * - * Note that ExtractDateTimePart differs from DateTimePart in DATE_ADD. - * - * SQL Note: - * Header : EXTRACT(edp FROM t) - * Purpose : Given a datetime part, edp, and a datetime type t return t's value for edp. This function allows for t to - * be unknown (null or missing) but not edp. If t is unknown the function returns null. - */ -internal object ExprFunctionExtract : ExprFunction { - - private val DATETIME = unionOf(StaticType.TIMESTAMP, StaticType.TIME, StaticType.DATE) - - override val signature = FunctionSignature( - name = "extract", - requiredParameters = listOf(StaticType.SYMBOL, DATETIME), - returnType = StaticType.DECIMAL - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return when { - required[1].isUnknown() -> ExprValue.nullValue - else -> eval(required) - } - } - - private fun eval(args: List): ExprValue { - val dateTimePart = args[0].dateTimePartValue() - val extractedValue = when (args[1].type) { - ExprValueType.TIMESTAMP -> args[1].timestampValue().extractedValue(dateTimePart) - ExprValueType.DATE -> args[1].dateValue().extractedValue(dateTimePart) - ExprValueType.TIME -> args[1].timeValue().extractedValue(dateTimePart) - else -> errNoContext( - "Expected date, time or timestamp: ${args[1]}", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } - - return ExprValue.newDecimal(extractedValue) - } -} - -/** - * Builtin function to return the size of a container type, i.e. size of Lists, Structs and Bags. This function - * propagates null and missing values as described in docs/Functions.md - * - * syntax: `size()` where container can be a BAG, SEXP, STRUCT or LIST. - */ -internal object ExprFunctionCardinality : ExprFunction { - - override val signature = FunctionSignature( - name = "cardinality", - requiredParameters = listOf(unionOf(StaticType.LIST, StaticType.BAG, StaticType.STRUCT, StaticType.SEXP)), - returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val collection = required[0] - val result = collection.count() - return ExprValue.newInt(result) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/SystemBuiltinsSql.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/SystemBuiltinsSql.kt deleted file mode 100644 index 389b9d9ba1..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/SystemBuiltinsSql.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.partiql.lang.eval.builtins - -import com.amazon.ion.IonType -import com.amazon.ionelement.api.emptyMetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.err -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType - -internal val SYSTEM_BUILTINS_SQL = listOf( - ExprFunctionCurrentUser -) - -internal object ExprFunctionCurrentUser : ExprFunction { - internal const val FUNCTION_NAME: String = "\$__current_user" - - override val signature: FunctionSignature = FunctionSignature( - name = FUNCTION_NAME, - requiredParameters = emptyList(), - returnType = StaticType.unionOf(StaticType.STRING, StaticType.NULL) - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return when (val user = session.context[EvaluationSession.Constants.CURRENT_USER_KEY]) { - is String -> ExprValue.newString(user) - null -> ExprValue.newNull(IonType.STRING) - else -> err( - message = "CURRENT_USER must be either a STRING or NULL.", - errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE, - errorContext = errorContextFrom(emptyMetaContainer()), - internal = false - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/CodePointExtensions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/CodePointExtensions.kt deleted file mode 100644 index 4b39ee22c8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/CodePointExtensions.kt +++ /dev/null @@ -1,126 +0,0 @@ -package org.partiql.lang.eval.builtins.internal - -// String.codePoints() is from Java 9+ -@Suppress("Since15") -private fun String.toIntArray() = this.codePoints().toArray() - -// Default codepoints to remove -private val SPACE = intArrayOf(" ".codePointAt(0)) - -/** - * Removes the given string (" " by default) from both ends of sourceString - */ -internal fun codepointTrim(sourceString: String, toRemove: String? = null): String { - val codepoints = sourceString.toIntArray() - val codepointsToRemove = toRemove?.toIntArray() ?: SPACE - return codepoints.trim(codepointsToRemove) -} - -/** - * Removes the given string (" " by default) from the leading end of sourceString - */ -internal fun codepointLeadingTrim(sourceString: String, toRemove: String? = null): String { - val codepoints = sourceString.toIntArray() - val codepointsToRemove = toRemove?.toIntArray() ?: SPACE - return codepoints.leadingTrim(codepointsToRemove) -} - -/** - * Removes the given string (" " by default) from the trailing end of sourceString - */ -internal fun codepointTrailingTrim(sourceString: String, toRemove: String? = null): String { - val codepoints = sourceString.toIntArray() - val codepointsToRemove = toRemove?.toIntArray() ?: SPACE - return codepoints.trailingTrim(codepointsToRemove) -} - -/** - * Returns the first 1-indexed position of probe in sourceString; else 0 - */ -internal fun codepointPosition(sourceString: String, probe: String): Int { - if (probe.length > sourceString.length) return 0 - val codepoints = sourceString.toIntArray() - val codepointsToFind = probe.toIntArray() - return codepoints.positionOf(codepointsToFind) -} - -/** - * Replaces sourceString with overlay from 1-indexed position `startPosition` for up to `length` codepoints - */ -internal fun codepointOverlay(sourceString: String, overlay: String, position: Int, length: Int? = null): String { - if (sourceString.isEmpty()) return sourceString - val codepoints = sourceString.toIntArray() - val codepointsToOverlay = overlay.toIntArray() - return codepoints.overlay(codepointsToOverlay, position, length) -} - -internal fun IntArray.trim(toRemove: IntArray? = null): String { - val codepointsToRemove = toRemove ?: SPACE - val leadingOffset = leadingTrimOffset(this, codepointsToRemove) - val trailingOffset = trailingTrimOffSet(this, codepointsToRemove) - val length = Math.max(0, this.size - trailingOffset - leadingOffset) - return String(this, leadingOffset, length) -} - -internal fun IntArray.leadingTrim(toRemove: IntArray? = null): String { - val codepointsToRemove = toRemove ?: SPACE - val offset = leadingTrimOffset(this, codepointsToRemove) - return String(this, offset, this.size - offset) -} - -internal fun IntArray.trailingTrim(toRemove: IntArray? = null): String { - val codepointsToRemove = toRemove ?: SPACE - val offset = trailingTrimOffSet(this, codepointsToRemove) - return String(this, 0, this.size - offset) -} - -internal fun IntArray.leadingTrimOffset(codepoints: IntArray, toRemove: IntArray): Int { - var offset = 0 - while (offset < this.size && toRemove.contains(codepoints[offset])) offset += 1 - return offset -} - -internal fun IntArray.trailingTrimOffSet(codepoints: IntArray, toRemove: IntArray): Int { - var offset = 0 - while (offset < this.size && toRemove.contains(codepoints[this.size - offset - 1])) offset += 1 - return offset -} - -internal fun IntArray.positionOf(probe: IntArray): Int { - val extent = this.size - probe.size - if (extent < 0) return 0 - var start = 0 - window@ while (start <= extent) { - // check current window for equality - for (i in probe.indices) { - if (probe[i] != this[start + i]) { - start += 1 - continue@window - } - } - // nothing was not equal — everything was equal - return start + 1 - } - return 0 -} - -internal fun IntArray.overlay(overlay: IntArray, position: Int, length: Int? = null): String { - val len = (length ?: overlay.size) - val prefixLen = (position - 1).coerceAtMost(this.size) - val suffixLen = (this.size - (len + prefixLen)).coerceAtLeast(0) - val buffer = IntArray(prefixLen + overlay.size + suffixLen) - var i = 0 - // Fill prefix - for (j in 0 until prefixLen) { - buffer[i++] = this[j] - } - // Fill overlay - for (j in overlay.indices) { - buffer[i++] = overlay[j] - } - // Fill suffix - for (j in 0 until suffixLen) { - buffer[i++] = this[prefixLen + len + j] - } - return String(buffer, 0, buffer.size) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/ExprFunctions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/ExprFunctions.kt deleted file mode 100644 index ce7990d3dd..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/ExprFunctions.kt +++ /dev/null @@ -1,91 +0,0 @@ -package org.partiql.lang.eval.builtins.internal - -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.errIntOverflow -import org.partiql.lang.eval.numberValue -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.util.exprValue -import org.partiql.types.StaticType -import java.math.BigInteger - -/** - * Prototype of `(Number) -> Number` as a PartiQL ExprFunction. - */ -internal abstract class ExprFunctionUnaryNumeric(name: String) : ExprFunction { - - abstract fun call(x: Number): Number - - override val signature = FunctionSignature( - name = name, - requiredParameters = listOf(StaticType.NUMERIC), - returnType = StaticType.NUMERIC, - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val x = required[0].numberValue() - val result = call(x) - return result.exprValue() - } -} - -/** - * Prototype of `(Number, Number) -> Number` as a PartiQL ExprFunction. - */ -internal abstract class ExprFunctionBinaryNumeric(name: String) : ExprFunction { - - abstract fun call(x: Number, y: Number): Number - - override val signature = FunctionSignature( - name = name, - requiredParameters = listOf(StaticType.NUMERIC, StaticType.NUMERIC), - returnType = StaticType.NUMERIC, - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val x = required[0].numberValue() - val y = required[1].numberValue() - val result = call(x, y) - return result.exprValue() - } -} - -/** - * Prototype of `(v: T) -> Int` where the action applies some measure to v - */ -internal abstract class ExprFunctionMeasure(name: String, type: StaticType) : ExprFunction { - - companion object { - - /** - * Placed here rather than StaticType as an internal helper rather than an extension of StaticType - */ - @JvmField - val BITSTRING = StaticType.unionOf(StaticType.SYMBOL, StaticType.STRING, StaticType.BLOB, StaticType.CLOB) - } - - abstract fun call(value: ExprValue): Int - - override val signature = FunctionSignature( - name = name, - requiredParameters = listOf(type), - returnType = StaticType.INT, - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val value = required[0] - val units = call(value) - return ExprValue.newInt(units) - } -} - -// wrapper for transform function result to corresponding integer type -internal fun transformIntType(n: BigInteger): Number = when (n) { - in Int.MIN_VALUE.toBigInteger()..Int.MAX_VALUE.toBigInteger() -> n.toInt() - in Long.MIN_VALUE.toBigInteger()..Long.MAX_VALUE.toBigInteger() -> n.toLong() - /** - * currently PariQL-lang-kotlin did not support integer value bigger than 64 bits. - */ - else -> errIntOverflow(8) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/TimestampExtensions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/TimestampExtensions.kt deleted file mode 100644 index 060df27643..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/TimestampExtensions.kt +++ /dev/null @@ -1,121 +0,0 @@ -package org.partiql.lang.eval.builtins.internal - -import com.amazon.ion.Timestamp -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.errNoContext -import org.partiql.lang.eval.time.SECONDS_PER_MINUTE -import org.partiql.lang.eval.time.Time -import org.partiql.lang.syntax.impl.DateTimePart -import java.math.BigDecimal -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.ZoneOffset - -internal val precisionOrder = listOf( - Timestamp.Precision.YEAR, - Timestamp.Precision.MONTH, - Timestamp.Precision.DAY, - Timestamp.Precision.MINUTE, - Timestamp.Precision.SECOND -) - -internal val dateTimePartToPrecision = mapOf( - DateTimePart.YEAR to Timestamp.Precision.YEAR, - DateTimePart.MONTH to Timestamp.Precision.MONTH, - DateTimePart.DAY to Timestamp.Precision.DAY, - DateTimePart.HOUR to Timestamp.Precision.MINUTE, - DateTimePart.MINUTE to Timestamp.Precision.MINUTE, - DateTimePart.SECOND to Timestamp.Precision.SECOND -) - -internal fun Timestamp.hasSufficientPrecisionFor(requiredPrecision: Timestamp.Precision): Boolean { - val requiredPrecisionPos = precisionOrder.indexOf(requiredPrecision) - val precisionPos = precisionOrder.indexOf(precision) - - return precisionPos >= requiredPrecisionPos -} - -internal fun Timestamp.adjustPrecisionTo(dateTimePart: DateTimePart): Timestamp { - val requiredPrecision = dateTimePartToPrecision[dateTimePart]!! - - if (this.hasSufficientPrecisionFor(requiredPrecision)) { - return this - } - - return when (requiredPrecision) { - Timestamp.Precision.YEAR -> Timestamp.forYear(this.year) - Timestamp.Precision.MONTH -> Timestamp.forMonth(this.year, this.month) - Timestamp.Precision.DAY -> Timestamp.forDay(this.year, this.month, this.day) - Timestamp.Precision.SECOND -> Timestamp.forSecond( - this.year, this.month, this.day, this.hour, this.minute, this.second, this.localOffset - ) - Timestamp.Precision.MINUTE -> Timestamp.forMinute( - this.year, this.month, this.day, this.hour, this.minute, this.localOffset - ) - else -> errNoContext( - "invalid datetime part for date_add: ${dateTimePart.toString().lowercase()}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_DATE_PART, - internal = false - ) - } -} - -internal fun Timestamp.toOffsetDateTime() = OffsetDateTime.of( - year, month, day, hour, minute, second, 0, ZoneOffset.ofTotalSeconds((localOffset ?: 0) * 60) -) - -// IonJava Timestamp.localOffset is the offset in minutes, e.g.: `+01:00 = 60` and `-1:20 = -80` -internal fun Timestamp.hourOffset() = (localOffset ?: 0) / SECONDS_PER_MINUTE - -internal fun Timestamp.minuteOffset() = (localOffset ?: 0) % SECONDS_PER_MINUTE - -internal fun Timestamp.extractedValue(dateTimePart: DateTimePart): BigDecimal { - return when (dateTimePart) { - DateTimePart.YEAR -> year - DateTimePart.MONTH -> month - DateTimePart.DAY -> day - DateTimePart.HOUR -> hour - DateTimePart.MINUTE -> minute - DateTimePart.SECOND -> second - DateTimePart.TIMEZONE_HOUR -> hourOffset() - DateTimePart.TIMEZONE_MINUTE -> minuteOffset() - }.toBigDecimal() -} - -internal fun LocalDate.extractedValue(dateTimePart: DateTimePart): BigDecimal { - return when (dateTimePart) { - DateTimePart.YEAR -> year - DateTimePart.MONTH -> monthValue - DateTimePart.DAY -> dayOfMonth - DateTimePart.TIMEZONE_HOUR, - DateTimePart.TIMEZONE_MINUTE -> errNoContext( - "Timestamp unit ${dateTimePart.name.lowercase()} not supported for DATE type", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - DateTimePart.HOUR, DateTimePart.MINUTE, DateTimePart.SECOND -> 0 - }.toBigDecimal() -} - -internal fun Time.extractedValue(dateTimePart: DateTimePart): BigDecimal { - return when (dateTimePart) { - DateTimePart.HOUR -> localTime.hour.toBigDecimal() - DateTimePart.MINUTE -> localTime.minute.toBigDecimal() - DateTimePart.SECOND -> secondsWithFractionalPart - DateTimePart.TIMEZONE_HOUR -> timezoneHour?.toBigDecimal() ?: errNoContext( - "Time unit ${dateTimePart.name.lowercase()} not supported for TIME type without TIME ZONE", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - DateTimePart.TIMEZONE_MINUTE -> timezoneMinute?.toBigDecimal() ?: errNoContext( - "Time unit ${dateTimePart.name.lowercase()} not supported for TIME type without TIME ZONE", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - DateTimePart.YEAR, DateTimePart.MONTH, DateTimePart.DAY -> errNoContext( - "Time unit ${dateTimePart.name.lowercase()} not supported for TIME type.", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/TimestampParser.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/TimestampParser.kt deleted file mode 100644 index 4dcfe74746..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/internal/TimestampParser.kt +++ /dev/null @@ -1,187 +0,0 @@ -package org.partiql.lang.eval.builtins.internal - -import com.amazon.ion.Timestamp -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.builtins.timestamp.FormatPattern -import org.partiql.lang.eval.builtins.timestamp.TimestampField -import org.partiql.lang.eval.errNoContext -import org.partiql.lang.util.propertyValueMapOf -import java.math.BigDecimal -import java.time.DateTimeException -import java.time.format.DateTimeFormatterBuilder -import java.time.temporal.ChronoField -import java.time.temporal.TemporalAccessor - -/** - * Uses Java 8's DateTimeFormatter to parse an Ion Timestamp value. - * - * Note: this is effected by https://bugs.openjdk.java.net/browse/JDK-8066806 which is not fixed until JDK-9. - * - * There are a few differences between Ion's timestamp and the {@ref java.time} package that create a few caveats - * that we hope will be encountered very infrequently. - * - * - The Ion specification allows for explicitly signifying of an unknown timestamp offset with a negative zero offset - * (i.e. the "-00:00" at the end of "2007-02-23T20:14:33.079-00:00") but Java 8's DateTimeFormatter simply doesn't - * recognize this and there's no reliable workaround that we've yet been able to determine. Unfortunately, this - * means that unknown offsets specified are parsed as if they were explicitly UTC (i.e. "+00:00" or "Z"). - * - DateTimeFormatter is capable of parsing UTC offsets to the precision of seconds, but Ion Timestamp's precision - * for offsets is 1 minute. [TimestampParser] currently handles this by throwing an exception when an attempt - * is made to parse a timestamp with an offset that does does not land on a minute boundary. - * - Ion Java's Timestamp allows specification of offsets up to +/- 24h, while an exception is thrown by - * DateTimeFormatter for any attempt to parse an offset greater than +/- 18h. The Ion specification does not seem - * to indicate minimum and maximum allowable values for offsets. In practice this may not be an issue for systems - * that use Timestamps correctly because real-life offsets do not exceed +/- 12h. - */ -internal class TimestampParser { - - companion object { - val TWO_DIGIT_PIVOT_YEAR = 70 - - /** Converts the offset seconds value returned from the TemporalAccessor into the minutes value. - * @throws EvaluationException if the offset seconds value was not a multiple of 60. - */ - private fun TemporalAccessor.getLocalOffset(): Int? = - if (!this.isSupported(ChronoField.OFFSET_SECONDS)) - null - else { - val offsetSeconds = this.get(ChronoField.OFFSET_SECONDS) - if (offsetSeconds % 60 != 0) { - throw EvaluationException( - "The parsed timestamp has a UTC offset that not a multiple of 1 minute. " + - "This timestamp cannot be parsed accurately because the maximum " + - "resolution for an Ion timestamp offset is 1 minute.", - ErrorCode.EVALUATOR_PRECISION_LOSS_WHEN_PARSING_TIMESTAMP, - internal = false - ) - } - offsetSeconds / 60 - } - - /** - * Parses a string given the specified format pattern. - */ - fun parseTimestamp(timestampString: String, formatPattern: String): Timestamp { - val pattern = FormatPattern.fromString(formatPattern) - // TODO: do this during compilation - pattern.validateForTimestampParsing() - - val accessor: TemporalAccessor by lazy { - try { - DateTimeFormatterBuilder() - .parseCaseInsensitive() - .appendPattern(pattern.formatPatternString) - .toFormatter() - .parse(timestampString) - - // DateTimeFormatter.ofPattern(formatPattern).parse(timestampString) - } catch (ex: IllegalArgumentException) { - throw EvaluationException( - ex, ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, - internal = false - ) - } - } - val year: Int by lazy { - val year = accessor.get(ChronoField.YEAR) - when { - !pattern.has2DigitYear || year < TWO_DIGIT_PIVOT_YEAR + 2000 -> year - else -> year - 100 - } - } - - return try { - when (pattern.leastSignificantField) { - TimestampField.FRACTION_OF_SECOND -> { - val nanoSeconds = BigDecimal.valueOf(accessor.getLong(ChronoField.NANO_OF_SECOND)) - val secondsFraction = nanoSeconds.scaleByPowerOfTen(-9).stripTrailingZeros() - // Note that this overload of Timestamp.forSecond(...) creates a timestamp with "fraction" precision. - Timestamp.forSecond( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH), - accessor.get(ChronoField.HOUR_OF_DAY), - accessor.get(ChronoField.MINUTE_OF_HOUR), - BigDecimal.valueOf(accessor.getLong(ChronoField.SECOND_OF_MINUTE)).add( - secondsFraction - ) as BigDecimal, - accessor.getLocalOffset() - ) - } - TimestampField.SECOND_OF_MINUTE -> { - // Note that this overload of Timestamp.forSecond(...) creates a timestamp with "second" precision. - Timestamp.forSecond( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH), - accessor.get(ChronoField.HOUR_OF_DAY), - accessor.get(ChronoField.MINUTE_OF_HOUR), - accessor.get(ChronoField.SECOND_OF_MINUTE), - accessor.getLocalOffset() - ) - } - TimestampField.MINUTE_OF_HOUR -> { - Timestamp.forMinute( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH), - accessor.get(ChronoField.HOUR_OF_DAY), - accessor.get(ChronoField.MINUTE_OF_HOUR), - accessor.getLocalOffset() - ) - } - TimestampField.HOUR_OF_DAY -> { - Timestamp.forMinute( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH), - accessor.get(ChronoField.HOUR_OF_DAY), - 0, // Ion Timestamp has no HOUR precision -- default minutes to 0 - accessor.getLocalOffset() - ) - } - TimestampField.DAY_OF_MONTH -> { - Timestamp.forDay( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH) - ) - } - TimestampField.MONTH_OF_YEAR -> { - Timestamp.forMonth(year, accessor.get(ChronoField.MONTH_OF_YEAR)) - } - TimestampField.YEAR -> { - Timestamp.forYear(year) - } - TimestampField.AM_PM, TimestampField.OFFSET, null -> { - errNoContext( - "This code should be unreachable because AM_PM or OFFSET or null" + - "should never the value of formatPattern.leastSignificantField by at this point", - errorCode = ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, - internal = true - ) - } - } - } - // Can be thrown by Timestamp.for*(...) methods. - catch (ex: IllegalArgumentException) { - throw EvaluationException( - ex, - ErrorCode.EVALUATOR_CUSTOM_TIMESTAMP_PARSE_FAILURE, - propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPattern), - internal = false - ) - } - // Can be thrown by TemporalAccessor.get(ChronoField) - catch (ex: DateTimeException) { - throw EvaluationException( - ex, - ErrorCode.EVALUATOR_CUSTOM_TIMESTAMP_PARSE_FAILURE, - propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPattern), - internal = false - ) - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/storedprocedure/StoredProcedure.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/storedprocedure/StoredProcedure.kt deleted file mode 100644 index 6d3e79000d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/storedprocedure/StoredProcedure.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.partiql.lang.eval.builtins.storedprocedure - -import org.partiql.lang.eval.EvaluatingCompiler -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.Expression - -/** - * A typed version of a stored procedure signature. This signature includes the stored procedure's [name] and [arity]. - */ -data class StoredProcedureSignature(val name: String, val arity: IntRange) { - constructor(name: String, arity: Int) : this(name, (arity..arity)) -} - -/** - * Represents a stored procedure that can be invoked. - * - * Stored procedures differ from functions (i.e. [ExprFunction]) in that: - * 1. stored procedures are allowed to have side-effects - * 2. stored procedures are only allowed at the top level of a query and cannot be used as an [Expression] (i.e. stored - * procedures can only be called using `EXEC sproc 'arg1', 'arg2'` and cannot be called from queries such as - * `SELECT * FROM (EXEC sproc 'arg1', 'arg2')` - */ -interface StoredProcedure { - /** - * [StoredProcedureSignature] representing the stored procedure's name and arity to be referenced in stored - * procedure calls. - */ - val signature: StoredProcedureSignature - - /** - * Invokes the stored procedure. Proper arity is checked by the [EvaluatingCompiler], but argument type checking - * is left to the implementation. - * - * @param session the calling environment session - * @param args argument list supplied to the stored procedure - */ - fun call(session: EvaluationSession, args: List): ExprValue -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/FormatItem.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/FormatItem.kt deleted file mode 100644 index 10fe95fd37..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/FormatItem.kt +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.builtins.timestamp - -/** - * A item that is parsed from the format pattern. i.e. text or one of many symbols corresponding to a - * field and its formatting options. - */ -internal sealed class FormatItem -/** - * Literal text to be included in the timestamp format. Typically, `-`, `/` `:` ` ` or literal strings enclosed in `'`. - */ -internal data class TextItem(val raw: String) : FormatItem() - -/** - * Indicates the various fields of a timestamp. - * The [precisionRank] field specifies the order of the precision such that higher [precisionRank] values are more - * precise fields. The [precisionRank] field is null for fields such as [AM_PM] and [OFFSET] which do not imply - * a precision. - */ -internal enum class TimestampField(val precisionRank: Int? = null) { - YEAR(0), MONTH_OF_YEAR(1), DAY_OF_MONTH(2), HOUR_OF_DAY(3), AM_PM(), MINUTE_OF_HOUR(4), SECOND_OF_MINUTE(5), FRACTION_OF_SECOND(6), OFFSET() -} - -/** - * Base class for all format symbols - */ -internal sealed class PatternSymbol : FormatItem() { - abstract val field: TimestampField -} - -/** - * Species specific format for the year field. - */ -internal enum class YearFormat { - /** - * Format symbol: `yy`. - */ - TWO_DIGIT, - /** - * Format symbol: `y`. - */ - FOUR_DIGIT, - /** - * Format symbol: `yyyy`. - */ - FOUR_DIGIT_ZERO_PADDED -} - -/** - * One of the format symbols corresponding to the year timestamp field, i.e. y, yy, yyyy. - */ -internal data class YearPatternSymbol(val format: YearFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.YEAR -} - -/** - * Specifies specific format for the month field. - */ -internal enum class MonthFormat { - /** - * Format symbol: `M`. - */ - MONTH_NUMBER, - - /** - * Format symbol: `MM`. - */ - MONTH_NUMBER_ZERO_PADDED, - - /** - * Format symbol: `MMM`. - */ - ABBREVIATED_MONTH_NAME, - - /** - * Format symbol: `MMMM`. - */ - FULL_MONTH_NAME, - - /** - * Format symbol: `MMMMM`. - */ - FIRST_LETTER_OF_MONTH_NAME, -} - -/** - * One of the format symbols corresponding to the month timestamp field, i.e. `M, `MM`, `MMM` or `MMMM`. - */ -internal data class MonthPatternSymbol(val format: MonthFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.MONTH_OF_YEAR -} - -/** - * Generic formatting options shared by [DayOfMonthPatternSymbol], [MinuteOfHourPatternSymbol] - * and [SecondOfMinutePatternPatternSymbol]. - */ -internal enum class TimestampFieldFormat { - - /** - * Format symbol: `y`. - */ - NUMBER, - ZERO_PADDED_NUMBER -} - -/** - * One of the format symbols corresponding to the day-of-month timestamp field, i.e. `d` or `dd`. - */ -internal data class DayOfMonthPatternSymbol(val format: TimestampFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.DAY_OF_MONTH -} - -/** - * Indicates if the hour-of-day field is in 12 or 24-hour format. - */ -internal enum class HourClock { - TwelveHour, - TwentyFourHour -} - -/** - * Specifies the specific format of the hour-of-day timestamp field. - */ -internal enum class HourOfDayFormatFieldFormat(val clock: HourClock) { - /** - * Format symbol: `h`. - */ - NUMBER_12_HOUR(HourClock.TwelveHour), - /** - * Format symbol: `hh`. - */ - ZERO_PADDED_NUMBER_12_HOUR(HourClock.TwelveHour), - - /** - * Format symbol: `H`. - */ - NUMBER_24_HOUR(HourClock.TwentyFourHour), - - /** - * Format symbol: `HH`. - */ - ZERO_PADDED_NUMBER_24_HOUR(HourClock.TwentyFourHour) -} - -/** - * One one of the format symbols corresponding to the hour-of-day timestamp field, i.e. `h`, `hh`, `H` or `HH`. - */ -internal data class HourOfDayPatternSymbol(val format: HourOfDayFormatFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.HOUR_OF_DAY -} - -/** - * One of the format symbols corresponding to the minute-of-hour timestamp field, i.e. `m` or `mm`. - */ -internal data class MinuteOfHourPatternSymbol(val format: TimestampFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.MINUTE_OF_HOUR -} - -/** - * One of the format symbols corresponding to the second-of-minute timestamp field, i.e. `s` or `ss`. - */ -internal data class SecondOfMinutePatternPatternSymbol(val format: TimestampFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.SECOND_OF_MINUTE -} - -/** - * Represents the nano-of-second timestamp field: `n`. - */ -internal class NanoOfSecondPatternSymbol : PatternSymbol() { - override val field: TimestampField = TimestampField.FRACTION_OF_SECOND - - /** - * This is normally provided by kotlin for data classes but this can't be a data class because it doesn't require - * any constructor arguments. - */ - override fun equals(other: Any?) = this.javaClass.isInstance(other) - - /** - * This is normally provided by kotlin for data classes but this can't be a data class because it doesn't require - * any constructor arguments. - */ - override fun hashCode(): Int = field.hashCode() -} - -/** - * Represents the AM-PM "pseudo" timestamp field: `a`. - */ -internal class AmPmPatternSymbol : PatternSymbol() { - override val field: TimestampField = TimestampField.AM_PM - - /** - * This is normally provided by kotlin for data classes but this can't be a data class because it doesn't require - * any constructor arguments. - */ - override fun equals(other: Any?) = this.javaClass.isInstance(other) - - /** - * This is normally provided by kotlin for data classes but this can't be a data class because it doesn't require - * any constructor arguments. - */ - override fun hashCode(): Int = field.hashCode() -} - -/** - * Represents the fraction-of-second timestamp field: `S`, which has variable precision indicated by the number of - * consecutive `S` symbols and is specified by [precision], i.e. `S` has a precision of 1 whlie `SSSSSS` has a - * precision of 6. - */ -internal data class FractionOfSecondPatternSymbol(val precision: Int) : PatternSymbol() { - override val field: TimestampField = TimestampField.FRACTION_OF_SECOND -} - -internal enum class OffsetFieldFormat { - /** - * Format symbol: `x`. - */ - ZERO_PADDED_HOUR, - /** - * Format symbol: `xx` or `xxxx`. - */ - - ZERO_PADDED_HOUR_MINUTE, - /** - * Format symbol: `xxx` or `xxxxx`. - */ - ZERO_PADDED_HOUR_COLON_MINUTE, - - /** - * Format symbol: `X`. - */ - ZERO_PADDED_HOUR_OR_Z, - - /** - * Format symbol: `XX` or `XXXX`. - */ - ZERO_PADDED_HOUR_MINUTE_OR_Z, - - /** - * Format symbol: `XXX` or `XXXXX` - */ - ZERO_PADDED_HOUR_COLON_MINUTE_OR_Z, -} - -/** - * One of the format symbols corresponding to the offset timestamp field, i.e.: `x`, `xx`, `xxx`, `xxxx`, `X`, `XX`, - * `XXX`, or `XXXX`. - */ -internal data class OffsetPatternSymbol(val format: OffsetFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.OFFSET -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/FormatPattern.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/FormatPattern.kt deleted file mode 100644 index a0bcea956b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/FormatPattern.kt +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.builtins.timestamp - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.util.propertyValueMapOf - -/** - * Represents a parsed timestamp format pattern. - */ -internal class FormatPattern(val formatPatternString: String, val formatItems: List) { - - companion object { - - /** - * Constructs a new instance of [FormatPattern] using the specified format pattern string. - */ - fun fromString(pattern: String): FormatPattern = TimestampFormatPatternParser().parse(pattern) - } - - /** - * Indicates the least significant of the fields in this format pattern or `null` if no fields are specified. - * When parsing, this will correspond to the precision of the Ion timestamp. - */ - val leastSignificantField: TimestampField? by lazy { - formatSymbols - .filter { it.field.precisionRank != null } - .sortedByDescending { it.field.precisionRank } - .firstOrNull() - ?.field - } - - /** - * Lazily filtered list of [PatternSymbol] instances present in [formatItems]. - */ - val formatSymbols: List by lazy { - formatItems.filterIsInstance() - } - - /** - * True if this [FormatPattern] contains a two digit year. - */ - val has2DigitYear: Boolean by lazy { - formatSymbols.filterIsInstance().any { it.format == YearFormat.TWO_DIGIT } - } - - /** - * True if this [FormatPattern] contains an offset symbol. - */ - val hasOffset: Boolean by lazy { - formatSymbols.filterIsInstance().any() - } - - /** - * True if this [FormatPattern] has an AM/PM offset symbol. - */ - val hasAmPm: Boolean by lazy { - formatSymbols.filterIsInstance().any() - } - - /** - * Validates the timestamp for parsing, throwing an `EvaluationException` if this format pattern cannot yield a - * valid Ion timestamp. - */ - fun validateForTimestampParsing() { - checkForFieldsNotValidForParsing() - checkDuplicatefields() - checkFieldCombination() - checkAmPmMismatch() - } - - /** - * Validates that duplicate fields are not included. - */ - private fun checkDuplicatefields() { - - val duplicatedField = formatSymbols.groupingBy { it.field } - .eachCount() - .filter { it.value > 1 } // Appears more than once in field - .asSequence() - .sortedByDescending { it.value } // Sort descending by number of appearances - .firstOrNull() - - if (duplicatedField != null) { - throw EvaluationException( - message = "timestamp format pattern duplicate fields", - errorCode = ErrorCode.EVALUATOR_TIMESTAMP_FORMAT_PATTERN_DUPLICATE_FIELDS, - errorContext = propertyValueMapOf( - Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString, - Property.TIMESTAMP_FORMAT_PATTERN_FIELDS to duplicatedField.key - ), - internal = false - ) - } - } - - /** - * Validates that when 12 hour hour of day field is present, am/pm must also be included - * and that when 24 hour of day field is present, am/pm is *not* included. - */ - private fun checkAmPmMismatch() { - formatSymbols.filterIsInstance().firstOrNull()?.let { - - val hasAmPm = formatSymbols.filterIsInstance().any() - when (it.format.clock) { - HourClock.TwelveHour -> { - if (!hasAmPm) { - throw EvaluationException( - message = "timestamp format pattern contains 12-hour hour of day field but doesn't " + "contain an am/pm field.", - errorCode = ErrorCode.EVALUATOR_TIMESTAMP_FORMAT_PATTERN_HOUR_CLOCK_AM_PM_MISMATCH, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString), - internal = false - ) - } - } - HourClock.TwentyFourHour -> { - if (hasAmPm) { - throw EvaluationException( - message = "timestamp format pattern contains 24-hour hour of day field and also " + "contains an am/pm field.", - errorCode = ErrorCode.EVALUATOR_TIMESTAMP_FORMAT_PATTERN_HOUR_CLOCK_AM_PM_MISMATCH, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString), - internal = false - ) - } - } - } // end when - } // end let - } - - /** - * Ensures that the format pattern includes symbols that can yield a valid Ion timestamp. - */ - private fun checkFieldCombination() { - - fun err(missingFields: String): Nothing = - throw EvaluationException( - message = "timestamp format pattern missing fields", - errorCode = ErrorCode.EVALUATOR_INCOMPLETE_TIMESTAMP_FORMAT_PATTERN, - errorContext = propertyValueMapOf( - Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString, - Property.TIMESTAMP_FORMAT_PATTERN_FIELDS to missingFields - ), - internal = false - ) - - fun errIfMissingTimestampFields(vararg fields: TimestampField) { - val missingFields = fields.filter { requiredField -> formatSymbols.all { it.field != requiredField } } - - if (missingFields.any()) { - err(missingFields.asSequence().joinToString(", ")) - } - } - - // Minimum precision for patterns containing offset or am/pm symbols is HOUR. - // NOTE: HOUR is not a valid precision for an Ion timestamp but when a format pattern's - // leastSignificantField is HOUR, the minute field defaults to 00. - if (hasOffset || hasAmPm) { - errIfMissingTimestampFields( - TimestampField.YEAR, TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH, - TimestampField.HOUR_OF_DAY - ) - } - - when (leastSignificantField) { - null -> { - // If most precise field is null there are no format symbols corresponding to any timestamp fields. - err("YEAR") - } - TimestampField.YEAR -> { - // the year field is the most coarse of the timestamp fields - // it does not require any other fields to make a complete timestamp - } - TimestampField.MONTH_OF_YEAR -> errIfMissingTimestampFields(TimestampField.YEAR) - TimestampField.DAY_OF_MONTH -> errIfMissingTimestampFields(TimestampField.YEAR, TimestampField.MONTH_OF_YEAR) - TimestampField.HOUR_OF_DAY -> errIfMissingTimestampFields( - TimestampField.YEAR, - TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH - ) - TimestampField.MINUTE_OF_HOUR -> errIfMissingTimestampFields( - TimestampField.YEAR, - TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH, TimestampField.HOUR_OF_DAY - ) - TimestampField.SECOND_OF_MINUTE -> errIfMissingTimestampFields( - TimestampField.YEAR, - TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH, TimestampField.HOUR_OF_DAY, - TimestampField.MINUTE_OF_HOUR - ) - TimestampField.FRACTION_OF_SECOND -> errIfMissingTimestampFields( - TimestampField.YEAR, - TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH, TimestampField.HOUR_OF_DAY, - TimestampField.MINUTE_OF_HOUR, TimestampField.SECOND_OF_MINUTE - ) - - TimestampField.OFFSET, TimestampField.AM_PM -> { - throw IllegalStateException("OFFSET, AM_PM should never be the least significant field!") - } - } - } - /** - * Ensures that this format pattern doesn't contain any format symbols which are valid for timestamp formatting - * but not for parsing. - */ - private fun checkForFieldsNotValidForParsing() { - if (formatSymbols.filterIsInstance().any { it.format == MonthFormat.FIRST_LETTER_OF_MONTH_NAME }) { - throw EvaluationException( - message = "timestamp format pattern missing fields", - errorCode = ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN_SYMBOL_FOR_PARSING, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString), - internal = false - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternLexer.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternLexer.kt deleted file mode 100644 index 28a2d158c7..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternLexer.kt +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.builtins.timestamp - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.util.codePointSequence -import org.partiql.lang.util.propertyValueMapOf - -private const val NON_ESCAPED_TEXT = " /-,:." -private const val SINGLE_QUOTE_CP = '\''.toInt() -private const val PATTERN = "yMdahHmsSXxn" - -// max code point range -// i.e. result of (NON_ESCAPED_TEXT + "'" + PATTERN).codePoints().max() + 1 -private const val TABLE_SIZE = 122 - -/** - * Timestamp format pattern token types. - */ -internal enum class TokenType { PATTERN, TEXT } - -/** - * Timestamp format pattern tokens. - */ -internal data class Token(val tokenType: TokenType, val value: String) - -/** - * State machine state types - */ -private enum class StateType(val beginsToken: Boolean, val endsToken: Boolean) { - /** lexer initial state */ - INITIAL(beginsToken = false, endsToken = false), - - /** an error state */ - ERROR(beginsToken = false, endsToken = false), - - /** start of a new token */ - START(beginsToken = true, endsToken = false), - - /** possible termination of a token */ - TERMINAL(beginsToken = false, endsToken = true), - - /** state that's both start and terminal */ - START_AND_TERMINAL(beginsToken = true, endsToken = true), - - /** middle of a token */ - INCOMPLETE(beginsToken = false, endsToken = false) -} - -/** - * A lexer state machine node - */ -private interface State { - val tokenType: TokenType? - val stateType: StateType - - /** - * Next state node for [cp] - * - * @throws IllegalStateException if no transition exists. - */ - fun nextFor(cp: Int): State -} - -/** - * Table backed [State]. This class is mutable through [transitionTo] so needs to be setup statically to be thread safe - */ -private class TableState( - override val tokenType: TokenType?, - override val stateType: StateType, - val delegate: State? = null -) : State { - private val transitionTable = object { - val backingArray = Array(TABLE_SIZE) { null } - - operator fun get(codePoint: Int): State? = when { - codePoint < TABLE_SIZE -> backingArray[codePoint] - else -> null - } - - operator fun set(codePoint: Int, next: State) { - backingArray[codePoint] = next - } - } - - /** - * Registers a transition for all code points in [characters] to the [next] state - */ - fun transitionTo(characters: String, next: State) { - characters.forEach { - val cp = it.toInt() - transitionTo(cp, next) - } - } - - /** - * Registers a transition for the code point to the [next] state - */ - fun transitionTo(codePoint: Int, next: State) { - transitionTable[codePoint] = next - } - - override fun nextFor(cp: Int): State = transitionTable[cp] ?: delegate?.nextFor(cp) ?: throw IllegalStateException("Unknown transition") -} - -private abstract class PatternState(val codePoint: Int, override val stateType: StateType) : State { - override val tokenType = TokenType.PATTERN -} - -private abstract class TextState(override val stateType: StateType) : State { - override val tokenType = TokenType.TEXT -} - -internal class TimestampFormatPatternLexer { - companion object { - private val ERROR_STATE: State = object : State { - override val tokenType = null - override val stateType: StateType = StateType.ERROR - - override fun nextFor(cp: Int): State = this - } - - private val INITIAL_STATE = TableState(tokenType = null, stateType = StateType.INITIAL, delegate = ERROR_STATE) - - // setups the lexer state machine - init { - val startEscapedText = TableState(TokenType.TEXT, StateType.START_AND_TERMINAL, INITIAL_STATE) - val inNonEscapedText = TableState(TokenType.TEXT, StateType.TERMINAL, INITIAL_STATE) - startEscapedText.transitionTo(NON_ESCAPED_TEXT, inNonEscapedText) - inNonEscapedText.transitionTo(NON_ESCAPED_TEXT, inNonEscapedText) - - val startQuotedText = object : TextState(StateType.START) { - val startQuotedText = this - - val endQuotedState = object : TextState(StateType.TERMINAL) { - override fun nextFor(cp: Int): State = when (cp) { - SINGLE_QUOTE_CP -> startQuotedText - else -> INITIAL_STATE.nextFor(cp) - } - } - - val inQuotedState = object : TextState(StateType.INCOMPLETE) { - override fun nextFor(cp: Int): State = when (cp) { - SINGLE_QUOTE_CP -> endQuotedState - else -> this - } - } - - override fun nextFor(cp: Int): State = when (cp) { - SINGLE_QUOTE_CP -> endQuotedState - else -> inQuotedState - } - } - - INITIAL_STATE.transitionTo(NON_ESCAPED_TEXT, startEscapedText) - INITIAL_STATE.transitionTo(SINGLE_QUOTE_CP, startQuotedText) - PATTERN.codePoints().forEach { cp -> - INITIAL_STATE.transitionTo( - cp, - object : PatternState(cp, StateType.START_AND_TERMINAL) { - val repeatingState = object : PatternState(cp, StateType.TERMINAL) { - override fun nextFor(cp: Int): State = when (cp) { - codePoint -> this - else -> INITIAL_STATE.nextFor(cp) - } - } - - override fun nextFor(cp: Int): State = when (cp) { - codePoint -> repeatingState - else -> INITIAL_STATE.nextFor(cp) - } - } - ) - } - } - } - - private fun StringBuilder.reset() = this.setLength(0) - - private fun tokenEnd(current: State, next: State) = when { - current.stateType == StateType.INITIAL -> false - current.tokenType == next.tokenType && next.stateType.beginsToken -> true - current.tokenType != next.tokenType -> true - else -> false - } - - fun tokenize(source: String): List { - val tokens = mutableListOf() - val buffer = StringBuilder() - - if (source.isEmpty()) { - return listOf() - } - - fun flushToken(tokenType: TokenType) { - tokens.add(Token(tokenType, buffer.toString())) - buffer.reset() - } - - var current: State = INITIAL_STATE - - val codePoints = source.codePointSequence() - - codePoints.forEach { cp -> - val next = current.nextFor(cp) - - if (next.stateType == StateType.ERROR) { - throw EvaluationException( - message = "Invalid token in timestamp format pattern", - errorCode = ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN_TOKEN, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to source), - internal = false - ) - } - - if (tokenEnd(current, next)) { - flushToken(current.tokenType!!) - } - - current = next - buffer.appendCodePoint(cp) - } - - if (!current.stateType.endsToken) { - throw EvaluationException( - message = "Unterminated token in timestamp format pattern", - errorCode = ErrorCode.EVALUATOR_UNTERMINATED_TIMESTAMP_FORMAT_PATTERN_TOKEN, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to source), - internal = false - ) - } - - flushToken(current.tokenType!!) - - return tokens - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternParser.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternParser.kt deleted file mode 100644 index 6de7a6ef20..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/TimestampFormatPatternParser.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.builtins.timestamp - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.util.propertyValueMapOf - -internal class TimestampFormatPatternParser { - - fun parse(formatPatternString: String): FormatPattern { - val lexer = TimestampFormatPatternLexer() - val tokens = lexer.tokenize(formatPatternString) - - var patternCounter = 0 - val formatItems = tokens.map { token -> - when (token.tokenType) { - TokenType.TEXT -> TextItem(token.value) - TokenType.PATTERN -> { - patternCounter += 1 - parsePattern(token.value) - } - } - } - - return FormatPattern(formatPatternString, formatItems) - } - - private fun parsePattern(raw: String): FormatItem = when (raw) { - // Possible optimization here: create singleton instances corresponding to each of the branches and return - // those instead of creating new instances. This could work because all of the objects here are immutable. - // This reduces the amount of garbage created during execution of this method. - "y" -> YearPatternSymbol(YearFormat.FOUR_DIGIT) - "yy" -> YearPatternSymbol(YearFormat.TWO_DIGIT) - "yyy", "yyyy" -> YearPatternSymbol(YearFormat.FOUR_DIGIT_ZERO_PADDED) - - "M" -> MonthPatternSymbol(MonthFormat.MONTH_NUMBER) - "MM" -> MonthPatternSymbol(MonthFormat.MONTH_NUMBER_ZERO_PADDED) - "MMM" -> MonthPatternSymbol(MonthFormat.ABBREVIATED_MONTH_NAME) - "MMMM" -> MonthPatternSymbol(MonthFormat.FULL_MONTH_NAME) - "MMMMM" -> MonthPatternSymbol(MonthFormat.FIRST_LETTER_OF_MONTH_NAME) - - "d" -> DayOfMonthPatternSymbol(TimestampFieldFormat.NUMBER) - "dd" -> DayOfMonthPatternSymbol(TimestampFieldFormat.ZERO_PADDED_NUMBER) - - "H" -> HourOfDayPatternSymbol(HourOfDayFormatFieldFormat.NUMBER_24_HOUR) - "HH" -> HourOfDayPatternSymbol(HourOfDayFormatFieldFormat.ZERO_PADDED_NUMBER_24_HOUR) - "h" -> HourOfDayPatternSymbol(HourOfDayFormatFieldFormat.NUMBER_12_HOUR) - "hh" -> HourOfDayPatternSymbol(HourOfDayFormatFieldFormat.ZERO_PADDED_NUMBER_12_HOUR) - - "a" -> AmPmPatternSymbol() - - "m" -> MinuteOfHourPatternSymbol(TimestampFieldFormat.NUMBER) - "mm" -> MinuteOfHourPatternSymbol(TimestampFieldFormat.ZERO_PADDED_NUMBER) - - "s" -> SecondOfMinutePatternPatternSymbol(TimestampFieldFormat.NUMBER) - "ss" -> SecondOfMinutePatternPatternSymbol(TimestampFieldFormat.ZERO_PADDED_NUMBER) - - "n" -> NanoOfSecondPatternSymbol() - - "X" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_OR_Z) - "XX", "XXXX" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_MINUTE_OR_Z) - "XXX", "XXXXX" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_COLON_MINUTE_OR_Z) - - "x" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR) - "xx", "xxxx" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_MINUTE) - "xxx", "xxxxx" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_COLON_MINUTE) - - else -> - // Note: the lexer *should* only return tokens that are full of capital S's so the precision is the length. - if (raw.first() == 'S') - FractionOfSecondPatternSymbol(raw.length) - else - throw EvaluationException( - message = "Invalid symbol in timestamp format pattern", - errorCode = ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN_SYMBOL, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to raw), - internal = false - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/TimestampTemporalAccessor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/TimestampTemporalAccessor.kt deleted file mode 100644 index 4a7a74dd39..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/timestamp/TimestampTemporalAccessor.kt +++ /dev/null @@ -1,58 +0,0 @@ -package org.partiql.lang.eval.builtins.timestamp - -import com.amazon.ion.Timestamp -import java.math.BigDecimal -import java.time.temporal.ChronoField -import java.time.temporal.IsoFields -import java.time.temporal.TemporalAccessor -import java.time.temporal.TemporalField -import java.time.temporal.UnsupportedTemporalTypeException - -private val NANOS_PER_SECOND = 1_000_000_000L -private val MILLIS_PER_SECOND = 1_000L -private val MILLIS_PER_SECOND_BD = BigDecimal.valueOf(MILLIS_PER_SECOND) -private val NANOS_PER_SECOND_BD = BigDecimal.valueOf(NANOS_PER_SECOND) - -private val Timestamp.nanoOfSecond: Long get() = this.decimalSecond.multiply(NANOS_PER_SECOND_BD).toLong() % NANOS_PER_SECOND - -private val Timestamp.milliOfSecond: Long get() = this.decimalSecond.multiply(MILLIS_PER_SECOND_BD).toLong() % MILLIS_PER_SECOND - -class TimestampTemporalAccessor(val ts: Timestamp) : TemporalAccessor { - - /** - * This method should return true to indicate whether a given TemporalField is supported. - * Note that the date-time formatting functionality in JDK8 assumes that all ChronoFields are supported and - * doesn't invoke this method to check if a ChronoField is supported. - */ - override fun isSupported(field: TemporalField?): Boolean = - when (field) { - IsoFields.QUARTER_OF_YEAR -> true - else -> false - } - - override fun getLong(field: TemporalField?): Long { - if (field == null) { - throw IllegalArgumentException("argument 'field' may not be null") - } - return when (field) { - ChronoField.YEAR_OF_ERA -> ts.year.toLong() - ChronoField.MONTH_OF_YEAR -> ts.month.toLong() - ChronoField.DAY_OF_MONTH -> ts.day.toLong() - ChronoField.HOUR_OF_DAY -> ts.hour.toLong() - ChronoField.SECOND_OF_MINUTE -> ts.second.toLong() - ChronoField.MINUTE_OF_HOUR -> ts.minute.toLong() - ChronoField.MILLI_OF_SECOND -> ts.milliOfSecond - ChronoField.NANO_OF_SECOND -> ts.nanoOfSecond - - ChronoField.AMPM_OF_DAY -> ts.hour / 12L - ChronoField.CLOCK_HOUR_OF_AMPM -> { - val hourOfAmPm = ts.hour.toLong() % 12L - if (hourOfAmPm == 0L) 12 else hourOfAmPm - } - ChronoField.OFFSET_SECONDS -> if (ts.localOffset == null) 0 else ts.localOffset * 60L - else -> throw UnsupportedTemporalTypeException( - field.javaClass.name + "." + field.toString() + " not supported" - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/impl/CoverageCompiler.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/impl/CoverageCompiler.kt deleted file mode 100644 index c4f1e61b75..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/impl/CoverageCompiler.kt +++ /dev/null @@ -1,397 +0,0 @@ -package org.partiql.lang.eval.impl - -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.emptyMetaContainer -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.eval.CompileOptions -import org.partiql.lang.eval.CoverageData -import org.partiql.lang.eval.CoverageStructure -import org.partiql.lang.eval.Environment -import org.partiql.lang.eval.EvaluatingCompiler -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.Expression -import org.partiql.lang.eval.ExpressionContext -import org.partiql.lang.eval.PartiQLResult -import org.partiql.lang.eval.ThunkEnv -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure -import org.partiql.lang.eval.exprEquals -import org.partiql.lang.eval.isNotUnknown -import org.partiql.lang.eval.isUnknown -import org.partiql.lang.eval.sourceLocationMeta -import org.partiql.lang.eval.visitors.PartiqlAstSanityValidator -import org.partiql.lang.types.TypedOpParameter -import java.util.Stack - -/** - * This should only be used for a single query's compilation due to the attachment of unique ids to the nodes. - */ -internal class CoverageCompiler( - functions: List, - customTypedOpParameters: Map, - procedures: Map, - compileOptions: CompileOptions = CompileOptions.standard() -) : EvaluatingCompiler(functions, customTypedOpParameters, procedures, compileOptions) { - - // A unique identifier for each branch - private var conditionCount: Int = 0 - private var branchCount: Int = 0 - private val conditions: MutableMap = mutableMapOf() - private val branches: MutableMap = mutableMapOf() - private val contextStack: Stack = Stack() - - private enum class Context { - IN_BRANCH, - NOT_IN_BRANCH - } - - /** - * Compiles a [PartiqlAst.Statement] tree to an [Expression]. - * - * Checks [Thread.interrupted] before every expression and sub-expression is compiled - * and throws [InterruptedException] if [Thread.interrupted] it has been set in the - * hope that long-running compilations may be aborted by the caller. - */ - override fun compile(originalAst: PartiqlAst.Statement): Expression { - contextStack.push(Context.NOT_IN_BRANCH) - val visitorTransform = compileOptions.visitorTransformMode.createVisitorTransform() - val transformedAst = visitorTransform.transformStatement(originalAst) - val partiqlAstSanityValidator = PartiqlAstSanityValidator() - - partiqlAstSanityValidator.validate(transformedAst, compileOptions) - - val thunk = nestCompilationContext(ExpressionContext.NORMAL, emptySet()) { - compileAstStatement(transformedAst) - } - - return object : Expression { - override val coverageStructure: CoverageStructure = CoverageStructure( - branches = branches.toMap(), - branchConditions = conditions.toMap() - ) - - override fun eval(session: EvaluationSession): ExprValue { - val env = Environment( - session = session, - locals = session.globals, - current = session.globals, - branchConditionCounts = mutableMapOf(), - branchCounts = mutableMapOf() - ) - return thunk(env) - } - - override fun evaluate(session: EvaluationSession): PartiQLResult { - val env = Environment( - session = session, - locals = session.globals, - current = session.globals, - branchConditionCounts = mutableMapOf(), - branchCounts = mutableMapOf() - ) - val value = thunk(env) - return PartiQLResult.Value( - value = value, - coverageData = { - CoverageData( - branchConditionCount = CoverageData.ExecutionCount(env.branchConditionCounts!!.toMap()), - branchCount = CoverageData.ExecutionCount(env.branchCounts!!.toMap()) - ) - }, - coverageStructure = { coverageStructure } - ) - } - } - } - - // TODO: Figure out how we can determine whether an ID should be a boolean. - // This will likely be addressed when we move towards static resolution in the successor to the EvaluatingCompiler - override fun compileId(expr: PartiqlAst.Expr.Id, metas: MetaContainer): ThunkEnv { - return super.compileId(expr, metas) - } - - // TODO: Figure out how we can determine whether a function call should be a boolean. - // This will likely be addressed when we move towards static resolution in the successor to the EvaluatingCompiler - override fun compileCall(expr: PartiqlAst.Expr.Call, metas: MetaContainer): ThunkEnv { - return super.compileCall(expr, metas) - } - - // NOTE: This should NOT be overridden. - override fun compileLit(expr: PartiqlAst.Expr.Lit, metas: MetaContainer): ThunkEnv = super.compileLit(expr, metas) - - // NOTE: This should NOT be overridden. - override fun compileAstExpr(expr: PartiqlAst.Expr): ThunkEnv = super.compileAstExpr(expr) - - // - // - // CONDITIONS - // - // - - override fun compileAnd(expr: PartiqlAst.Expr.And, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.AND, metas) { - super.compileAnd(expr, metas) - } - - override fun compileOr(expr: PartiqlAst.Expr.Or, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.OR, metas) { - super.compileOr(expr, metas) - } - - override fun compileNot(expr: PartiqlAst.Expr.Not, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.NOT, metas) { - super.compileNot(expr, metas) - } - - override fun compileGt(expr: PartiqlAst.Expr.Gt, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.GT, metas) { - super.compileGt(expr, metas) - } - - override fun compileGte(expr: PartiqlAst.Expr.Gte, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.GTE, metas) { - super.compileGte(expr, metas) - } - - override fun compileBetween(expr: PartiqlAst.Expr.Between, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.BETWEEN, metas) { - super.compileBetween(expr, metas) - } - - override fun compileEq(expr: PartiqlAst.Expr.Eq, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.EQ, metas) { - super.compileEq(expr, metas) - } - - override fun compileIs(expr: PartiqlAst.Expr.IsType, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.IS, metas) { - super.compileIs(expr, metas) - } - - override fun compileIn(expr: PartiqlAst.Expr.InCollection, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.IN, metas) { - super.compileIn(expr, metas) - } - - override fun compileLt(expr: PartiqlAst.Expr.Lt, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.LT, metas) { - super.compileLt(expr, metas) - } - - override fun compileLte(expr: PartiqlAst.Expr.Lte, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.LTE, metas) { - super.compileLte(expr, metas) - } - - override fun compileNe(expr: PartiqlAst.Expr.Ne, metas: MetaContainer): ThunkEnv = compileCondition(CoverageStructure.BranchCondition.Type.NEQ, metas) { - super.compileNe(expr, metas) - } - - // - // - // BRANCHES - // - // - - override fun compileWhere(node: PartiqlAst.Expr): ThunkEnv = compileBranch(CoverageStructure.Branch.Type.WHERE, node.metas) { - super.compileWhere(node) - } - - override fun compileHaving(node: PartiqlAst.Expr) = compileBranch(CoverageStructure.Branch.Type.HAVING, node.metas) { - super.compileHaving(node) - } - - /** - * This one has a larger override as we need to inject more-specific information regarding branching. - * The majority of the compilation logic should match the [EvaluatingCompiler] - */ - override fun compileSimpleCase(expr: PartiqlAst.Expr.SimpleCase, metas: MetaContainer): ThunkEnv { - val valueThunk = compileAstExpr(expr.expr) - val branchThunks = expr.cases.pairs.map { - Pair( - compileBranchWithoutCheck(CoverageStructure.Branch.Type.CASE_WHEN, it.first.metas) { compileAstExpr(it.first) }, - compileAstExpr(it.second) - ) - } - val elseThunk = when (val default = expr.default) { - null -> thunkFactory.thunkEnv(metas) { ExprValue.nullValue } - else -> compileAstExpr(default) - } - - return thunkFactory.thunkEnv(metas) thunk@{ env -> - val caseValue = valueThunk(env) - // if the case value is unknown then we can short-circuit to the elseThunk directly - when { - caseValue.isUnknown() -> elseThunk(env) - else -> { - branchThunks.forEach { bt -> - val compiledWhen = bt.first - val branchValue = compiledWhen.thunk(env) - // Just skip any branch values that are unknown, which we consider the same as false here. - when { - branchValue.isUnknown() -> { /* intentionally blank */ - } - else -> { - val result = caseValue.exprEquals(branchValue) - when (result) { - true -> incrementBranchCount(env, compiledWhen.truthId) - false -> incrementBranchCount(env, compiledWhen.falseId) - } - if (result) { - return@thunk bt.second(env) - } - } - } - } - } - } - elseThunk(env) - } - } - - override fun compileSearchedCase(expr: PartiqlAst.Expr.SearchedCase, metas: MetaContainer): ThunkEnv { - val branchThunks = expr.cases.pairs.map { - compileBranchWithoutCheck(CoverageStructure.Branch.Type.CASE_WHEN, it.first.metas) { compileAstExpr(it.first) } to compileAstExpr(it.second) - } - val elseThunk = when (val default = expr.default) { - null -> thunkFactory.thunkEnv(metas) { ExprValue.nullValue } - else -> compileAstExpr(default) - } - - return when (compileOptions.typingMode) { - TypingMode.LEGACY -> thunkFactory.thunkEnv(metas) thunk@{ env -> - branchThunks.forEach { bt -> - val compiledWhen = bt.first - val conditionValue = compiledWhen.thunk(env) - if (conditionValue.isNotUnknown()) { - val result = conditionValue.booleanValue() - when (result) { - true -> incrementBranchCount(env, compiledWhen.truthId) - false -> incrementBranchCount(env, compiledWhen.falseId) - } - if (result) { - return@thunk bt.second(env) - } - } - } - elseThunk(env) - } - // Permissive mode propagates data type mismatches as MISSING, which is - // equivalent to false for searched CASE predicates. To simplify this, - // all we really need to do is consider any non-boolean result from the - // predicate to be false. - TypingMode.PERMISSIVE -> thunkFactory.thunkEnv(metas) thunk@{ env -> - branchThunks.forEach { bt -> - val compiledWhen = bt.first - val conditionValue = compiledWhen.thunk(env) - if (conditionValue.type == ExprValueType.BOOL) { - val result = conditionValue.booleanValue() - when (result) { - true -> incrementBranchCount(env, compiledWhen.truthId) - false -> incrementBranchCount(env, compiledWhen.falseId) - } - if (result) { - return@thunk bt.second(env) - } - } - } - elseThunk(env) - } - } - } - - override fun compileSelect(selectExpr: PartiqlAst.Expr.Select, metas: MetaContainer): ThunkEnv { - this.contextStack.push(Context.NOT_IN_BRANCH) - return super.compileSelect(selectExpr, metas).also { this.contextStack.pop() } - } - - private fun compileBranch(operand: CoverageStructure.Branch.Type, metas: MetaContainer = emptyMetaContainer(), compilation: () -> ThunkEnv): ThunkEnv { - val branchThunkEnv = compileBranchWithoutCheck(operand, metas, compilation) - - // Get Boolean Decision/Outcome - val thunk = branchThunkEnv.thunk - return { env -> - val resultExprValue = thunk.invoke(env) - - when (resultExprValue.type) { - ExprValueType.BOOL -> when (resultExprValue.booleanValue()) { - true -> incrementBranchCount(env, branchThunkEnv.truthId) - false -> incrementBranchCount(env, branchThunkEnv.falseId) - } - ExprValueType.NULL -> incrementBranchCount(env, branchThunkEnv.falseId) - ExprValueType.MISSING -> incrementBranchCount(env, branchThunkEnv.falseId) - else -> { /* Do nothing */ } - } - resultExprValue - } - } - - private fun compileBranchWithoutCheck(operand: CoverageStructure.Branch.Type, metas: MetaContainer = emptyMetaContainer(), compilation: () -> ThunkEnv): BranchThunkEnv { - this.contextStack.push(Context.IN_BRANCH) - val truthId = "B${++branchCount}" - val falseId = "B${++branchCount}" - - // Add Location Information - val lineNumber = metas.sourceLocationMeta?.lineNum ?: -1L - branches[truthId] = CoverageStructure.Branch(truthId, operand, outcome = CoverageStructure.Branch.Outcome.TRUE, lineNumber) - branches[falseId] = CoverageStructure.Branch(falseId, operand, outcome = CoverageStructure.Branch.Outcome.FALSE, lineNumber) - - return BranchThunkEnv( - truthId, - falseId, - compilation.invoke() - ).also { - this.contextStack.pop() - } - } - - private class BranchThunkEnv( - val truthId: String, - val falseId: String, - val thunk: ThunkEnv - ) - - private fun compileCondition(operand: CoverageStructure.BranchCondition.Type, metas: MetaContainer = emptyMetaContainer(), compilation: () -> ThunkEnv): ThunkEnv { - // Make sure the condition is in a BRANCH - if (this.contextStack.peek() == Context.NOT_IN_BRANCH) { return compilation.invoke() } - - val truthId = "C${++conditionCount}" - val falseId = "C${++conditionCount}" - val nullId = "C${++conditionCount}" - val missingId = "C${++conditionCount}" - - // Add Location Information - val lineNumber = metas.sourceLocationMeta?.lineNum ?: 1L - conditions[truthId] = CoverageStructure.BranchCondition(truthId, operand, outcome = CoverageStructure.BranchCondition.Outcome.TRUE, lineNumber) - conditions[falseId] = CoverageStructure.BranchCondition(falseId, operand, outcome = CoverageStructure.BranchCondition.Outcome.FALSE, lineNumber) - conditions[nullId] = CoverageStructure.BranchCondition(nullId, operand, outcome = CoverageStructure.BranchCondition.Outcome.NULL, lineNumber) - - // Handle Permissive Mode - if (compileOptions.typingMode == TypingMode.PERMISSIVE) { - conditions[missingId] = CoverageStructure.BranchCondition( - missingId, - operand, - outcome = CoverageStructure.BranchCondition.Outcome.MISSING, - lineNumber - ) - } - - // Get Boolean Decision/Outcome - val thunk = compilation.invoke() - return { env -> - val resultExprValue = thunk.invoke(env) - when (resultExprValue.type) { - ExprValueType.BOOL -> when (resultExprValue.booleanValue()) { - true -> incrementConditionCount(env, truthId) - false -> incrementConditionCount(env, falseId) - } - ExprValueType.NULL -> incrementConditionCount(env, nullId) - // We should never receive MISSING unless in permissive mode - ExprValueType.MISSING -> incrementConditionCount(env, missingId) - else -> { /* Do nothing */ } - } - resultExprValue - } - } - - private fun incrementConditionCount(env: Environment, id: String) { - env.branchConditionCounts?.let { it[id] = (it[id] ?: 0L) + 1L } - } - - private fun incrementBranchCount(env: Environment, id: String) { - env.branchCounts?.let { it[id] = (it[id] ?: 0L) + 1L } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/impl/FunctionManager.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/impl/FunctionManager.kt deleted file mode 100644 index a800ade05b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/impl/FunctionManager.kt +++ /dev/null @@ -1,77 +0,0 @@ -package org.partiql.lang.eval.impl - -import org.partiql.lang.eval.ArityMismatchException -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.FunctionNotFoundException -import org.partiql.lang.eval.errInvalidArgumentType -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.types.StaticTypeUtils -import org.partiql.types.StaticType - -/** - * Replaces the map of functions to achieve function overloading. - * Supports getting all functions and getting the function map by name - */ -internal class FunctionManager( - private val functions: List -) { - private val functionMap: Map> = functions.groupBy { it.signature.name } - - /** - * Get function from the list by name, arity, and argumentTypes(ExprValue) - */ - @Throws(FunctionNotFoundException::class, ArityMismatchException::class) - internal fun get(name: String, arity: Int, args: List): ExprFunction { - val funcs = functionMap[name] ?: throw FunctionNotFoundException("Name check fails") - - val funcsMatchingArity = funcs.filter { it.signature.arity.contains(arity) } - if (funcsMatchingArity.isEmpty()) { - throw ArityMismatchException("Arity check fails", getMinMaxArities(funcs)) - } - - val errorList = mutableListOf() - for (func in funcsMatchingArity) { - try { - checkArgumentTypesEnableNullMissing(func.signature, args) - return func - } catch (e: Exception) { - errorList.add(e) - } - } - throw errorList.first() - } - - /** - * Check argument types(ExprValue) by requiredArgs, OptionalArgs and VariadicArgs. This function will pass all arg values including MISSING or NULL values. - */ - internal fun checkArgumentTypesEnableNullMissing(signature: FunctionSignature, args: List) { - fun checkArgumentType(formalStaticType: StaticType, actualStaticType: StaticType, position: Int) { - val formalExprValueTypeDomain = StaticTypeUtils.getTypeDomain(formalStaticType) - - if (actualStaticType == StaticType.NULL || actualStaticType == StaticType.MISSING) { - // Skip if NULL/MISSING - } else if (!StaticTypeUtils.isSubTypeOf(actualStaticType, formalStaticType)) { - errInvalidArgumentType( - signature = signature, - position = position, - expectedTypes = formalExprValueTypeDomain.toList(), - actualType = actualStaticType - ) - } - } - - signature.requiredParameters.zip(args).forEachIndexed { idx, (expected, actual) -> - checkArgumentType(expected, actual, idx + 1) - } - } - - /** - * Get minArity and maxArity by looping candidate functions filtered by function name. - */ - internal fun getMinMaxArities(funcs: List): Pair { - val minArity = funcs.map { it.signature.arity.first }.minOrNull() ?: Int.MAX_VALUE - val maxArity = funcs.map { it.signature.arity.last }.maxOrNull() ?: Int.MIN_VALUE - - return Pair(minArity, maxArity) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/AnyOfCastTable.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/AnyOfCastTable.kt deleted file mode 100644 index a25d9658a8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/AnyOfCastTable.kt +++ /dev/null @@ -1,276 +0,0 @@ -package org.partiql.lang.eval.internal - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.sourceLocation -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.StructOrdering -import org.partiql.lang.eval.internal.ext.isUnknown -import org.partiql.lang.eval.name -import org.partiql.lang.eval.namedValue -import org.partiql.lang.types.StaticTypeUtils -import org.partiql.types.AnyOfType -import org.partiql.types.AnyType -import org.partiql.types.CollectionType -import org.partiql.types.SingleType -import org.partiql.types.StructType - -/** - * The template table that encodes the type conversion precedence for a source type to target type. - * This is defined in terms of the [ExprValueType] type instead of the [StaticType] because is - * an enumeration (versus algebraic data type) and is trivially mapped to the [StaticType] instances we care - * to associate with. - */ -private val CAST_ANY_OF_PRECEDENCE_TABLE = mapOf( - ExprValueType.BOOL to listOf( - ExprValueType.BOOL, - ExprValueType.INT, - ExprValueType.DECIMAL, - ExprValueType.FLOAT, - ExprValueType.STRING, - ExprValueType.SYMBOL - ), - ExprValueType.INT to listOf( - ExprValueType.INT, - ExprValueType.DECIMAL, - ExprValueType.FLOAT, - ExprValueType.STRING, - ExprValueType.SYMBOL, - ExprValueType.BOOL - ), - ExprValueType.FLOAT to listOf( - ExprValueType.FLOAT, - ExprValueType.DECIMAL, - ExprValueType.INT, - ExprValueType.STRING, - ExprValueType.SYMBOL, - ExprValueType.BOOL - ), - ExprValueType.DECIMAL to listOf( - ExprValueType.DECIMAL, - ExprValueType.FLOAT, - ExprValueType.INT, - ExprValueType.STRING, - ExprValueType.SYMBOL, - ExprValueType.BOOL - ), - ExprValueType.TIMESTAMP to listOf( - ExprValueType.STRING, - ExprValueType.SYMBOL - // TODO define for DATE/TIME - ), - ExprValueType.SYMBOL to listOf( - ExprValueType.SYMBOL, - ExprValueType.STRING, - ExprValueType.DECIMAL, - ExprValueType.INT, - ExprValueType.FLOAT, - ExprValueType.BOOL, - ExprValueType.TIMESTAMP - // TODO define for DATE/TIME/INTERVAL - ), - ExprValueType.STRING to listOf( - ExprValueType.STRING, - ExprValueType.SYMBOL, - ExprValueType.DECIMAL, - ExprValueType.INT, - ExprValueType.FLOAT, - ExprValueType.BOOL, - ExprValueType.TIMESTAMP - // TODO define for DATE/TIME/INTERVAL - ), - ExprValueType.CLOB to listOf( - ExprValueType.CLOB, - ExprValueType.BLOB - ), - ExprValueType.BLOB to listOf( - ExprValueType.BLOB, - ExprValueType.CLOB - ), - ExprValueType.LIST to listOf( - ExprValueType.LIST, - ExprValueType.SEXP, - ExprValueType.BAG - ), - ExprValueType.SEXP to listOf( - ExprValueType.SEXP, - ExprValueType.LIST, - ExprValueType.BAG - ), - ExprValueType.BAG to listOf( - ExprValueType.BAG, - ExprValueType.LIST, - ExprValueType.SEXP - ), - ExprValueType.STRUCT to listOf( - ExprValueType.STRUCT - ) -) - -/** A partial compilation of the cast operation, allowing the source operand to be passed in. */ -internal typealias CastFunc = (source: ExprValue) -> ExprValue - -/** Represents a casted value, a failure to cast, or no possible cast target. */ -private sealed class CastResult { - /** Returns the underlying [ExprValue] or throws if in the [CastError] or [CastNil] state. */ - abstract fun unwrap(): ExprValue -} - -private data class CastError(val error: EvaluationException) : CastResult() { - override fun unwrap() = throw error -} - -private data class CastValue(val value: ExprValue) : CastResult() { - override fun unwrap() = value -} - -/** Sentinel case to deal with empty target table--no compatible cast available for the source. */ -private data class CastNil(val sourceType: ExprValueType, val metas: MetaContainer) : CastResult() { - override fun unwrap(): Nothing { - val errorContext = PropertyValueMap().also { - it[Property.CAST_FROM] = sourceType.toString() - // TODO put the right type name here - it[Property.CAST_TO] = "" - } - metas.sourceLocation?.let { fillErrorContext(errorContext, it) } - err( - "No compatible types in union to cast from $sourceType", - ErrorCode.EVALUATOR_CAST_FAILED, - errorContext, - internal = false - ) - } -} - -/** - * Represents the candidate conversion table for compiling the casting to an [AnyOfType]. - * - * Note that currently, we cannot define recursive [StaticType] for container element types, so the - * [CollectionType.elementType] must be `null`, but is implied to be the type given to this table. - * - * A further restriction is that [StructType.fields] must be empty and [StructType.contentClosed] must be - * `false`. It is implied similarly that the value of any `struct` fields are of the given [AnyOfType]. - * - * @param anyOfType The union type to determine the precedence for. - * @param metas The metadata of the compilation context. - * @param singleTypeCast The function to delegate the implementation of a cast to a single type. - */ -internal class AnyOfCastTable( - private val anyOfType: AnyOfType, - private val metas: MetaContainer, - singleTypeCast: (SingleType) -> CastFunc, -) { - val castFuncTable: Map> - val castTypeTable: Map> - - init { - val typeMap = mutableMapOf() - - // validate the union type here - anyOfType.types.forEach { - when (it) { - is AnyType -> typeErr("Union type cannot have ANY in it") - is AnyOfType -> typeErr("Union type cannot have a Union type in it") - is SingleType -> { - val runtimeType = StaticTypeUtils.getRuntimeType(it) - if (typeMap.contains(runtimeType)) { - typeErr("Duplicate core type in union type not supported ($runtimeType)") - } - typeMap.put(runtimeType, it) - when (it) { - is CollectionType -> when { - it.elementType !is AnyType -> typeErr( - "Union type must have unconstrained container type (${it.elementType})" - ) - } - is StructType -> when { - it.fields.isNotEmpty() -> typeErr( - "Union type must have no field constraints for struct (${it.fields}" - ) - it.contentClosed -> typeErr("Union type must not be closed") - } - else -> {} - } - } - } - } - - // generate the precedence table of cast target types and functions - castTypeTable = CAST_ANY_OF_PRECEDENCE_TABLE.map { (srcType, destTypes) -> - srcType to destTypes.filter { t -> typeMap.containsKey(t) } - }.toMap() - castFuncTable = castTypeTable.map { (srcType, destTypes) -> - srcType to destTypes.mapNotNull { t -> typeMap[t] }.map(singleTypeCast) - }.toMap() - } - - private fun getCasts(sourceType: ExprValueType): List = castFuncTable[sourceType] - ?: throw IllegalStateException("Missing type in union cast function table: $sourceType") - - private fun firstCompatible(sourceType: ExprValueType): ExprValueType { - val types = castTypeTable[sourceType] - ?: throw IllegalStateException("Missing type in union cast type table: $sourceType") - return types.firstOrNull() ?: CastNil(sourceType, metas).unwrap() - } - - /** Evaluates the `CAST` operation over the table. */ - fun cast(source: ExprValue): ExprValue = when { - source.isUnknown() -> source - else -> when { - source.type.isSequence || source.type == ExprValueType.STRUCT -> { - // TODO honor any constraints on the container/struct type (statically) - // sequences are a special case, we recursively cast the children into a new container - val targetType = firstCompatible(source.type) - val children = source.asSequence().map { cast(it) } - - when (targetType) { - ExprValueType.LIST -> ExprValue.newList(children) - ExprValueType.SEXP -> ExprValue.newSexp(children) - ExprValueType.BAG -> ExprValue.newBag(children) - ExprValueType.STRUCT -> { - if (source.type != ExprValueType.STRUCT) { - // Should not be possible - throw IllegalStateException("Cannot cast from non-struct to struct") - } - ExprValue.Companion.newStruct( - children.zip(source.asSequence()).map { (child, original) -> - child.namedValue(original.name!!) - }, - StructOrdering.UNORDERED - ) - } - else -> throw IllegalStateException("Invalid collection target type: $targetType") - } - } - else -> { - // for the scalar case, we apply the available cast functions in order - // and either we succeed with a converted value, or we get an error and keep trying - var result: CastResult = CastNil(source.type, metas) - loop@ for (castFunc in getCasts(source.type)) { - when (result) { - is CastNil, is CastError -> { - try { - result = CastValue(castFunc(source)) - } catch (e: EvaluationException) { - result = CastError(e) - } - } - is CastValue -> break@loop - } - } - result.unwrap() - } - } - } - - private fun typeErr(message: String): Nothing = err( - message, - ErrorCode.SEMANTIC_UNION_TYPE_INVALID, - org.partiql.lang.eval.errorContextFrom(metas), - internal = true - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ErrorSignaler.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ErrorSignaler.kt deleted file mode 100644 index f47ddb8bb8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ErrorSignaler.kt +++ /dev/null @@ -1,104 +0,0 @@ -package org.partiql.lang.eval.internal - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorBehaviorInPermissiveMode -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.util.propertyValueMapOf - -/** Provides a common interface controlling the evaluation-time error signaling of [CompileOptions.typingMode]. */ -internal interface ErrorSignaler { - /** Depending on the error mode, either throws an [EvaluationException] using the specified [ErrorDetails] - * or returns `MISSING`. */ - fun error(errorCode: ErrorCode, createErrorDetails: () -> ErrorDetails): ExprValue -} - -/** - * Syntactic sugar for error signaling according to the current [TypingMode]. - * - * Depending on the [TypingMode] mode, if [test] is true, either [createErrorDetails] will be - * invoked and used to instantiate and throw an [EvaluationException], or the PartiQL `MISSING` - * value will be returned, depending on the [TypingMode]. - * - * If [test] is false, [otherwise] is invoked. Any exception thrown within [otherwise] is left alone - * to propagate up as usual. Be aware that this can *still* mean that the current thunk can result in - * `MISSING`, depending on the [ThunkFactory] currently in use. - */ -internal inline fun ErrorSignaler.errorIf( - test: Boolean, - errorCode: ErrorCode, - crossinline createErrorDetails: () -> ErrorDetails, - crossinline otherwise: () -> ExprValue -): ExprValue = - when { - test -> this.error(errorCode) { createErrorDetails() } - else -> otherwise() - } - -/** - * Contains the details of an error. - * - * [errorCode] and [errorContext] together are used to compose an error message for the end-user - * while [message] is meant for to help developers of services that use PartiQL. - */ -internal class ErrorDetails( - /** Meta information of the node that is to blame for the error. */ - val metas: MetaContainer, - /** The programmer readable exception message. */ - val message: String, - val errorContext: PropertyValueMap? = null -) - -internal fun TypingMode.createErrorSignaler() = - when (this) { - TypingMode.LEGACY -> LegacyErrorSignaler() - TypingMode.PERMISSIVE -> PermissiveErrorSignaler() - } - -/** Defines legacy error signaling. */ -private class LegacyErrorSignaler : ErrorSignaler { - /** Invokes [createErrorDetails] and uses the return value to construct and throw an [EvaluationException]. */ - override fun error(errorCode: ErrorCode, createErrorDetails: () -> ErrorDetails): ExprValue = - throwEE(errorCode, createErrorDetails) -} - -/** Defines permissive error signaling. */ -private class PermissiveErrorSignaler() : ErrorSignaler { - - /** Ignores [createErrorDetails] and simply returns MISSING. */ - override fun error(errorCode: ErrorCode, createErrorDetails: () -> ErrorDetails): ExprValue = - when (errorCode.errorBehaviorInPermissiveMode) { - ErrorBehaviorInPermissiveMode.THROW_EXCEPTION -> throwEE(errorCode, createErrorDetails) - ErrorBehaviorInPermissiveMode.RETURN_MISSING -> ExprValue.missingValue - } -} - -/** Throws an [EvaluationException] using the specified error details. */ -private fun throwEE(errorCode: ErrorCode, createErrorDetails: () -> ErrorDetails): Nothing { - with(createErrorDetails()) { - // Add source location if we need to and if we can - val srcLoc = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - val errCtx = this.errorContext ?: propertyValueMapOf() - if (srcLoc != null) { - if (!errCtx.hasProperty(Property.LINE_NUMBER)) { - errCtx[Property.LINE_NUMBER] = srcLoc.lineNum - } - if (!errCtx.hasProperty(Property.COLUMN_NUMBER)) { - errCtx[Property.COLUMN_NUMBER] = srcLoc.charOffset - } - } - - throw EvaluationException( - message = message, - errorCode = errorCode, - errorContext = errCtx, - cause = null, - internal = false - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Exceptions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Exceptions.kt deleted file mode 100644 index a1435a57fe..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Exceptions.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.types.StaticTypeUtils -import org.partiql.lang.util.propertyValueMapOf -import org.partiql.lang.util.to -import org.partiql.types.SingleType -import org.partiql.types.StaticType - -/** - * Shorthand for throwing function evaluation. Separated from [err] to avoid loosing the context unintentionally - */ -internal fun errNoContext(message: String, errorCode: ErrorCode, internal: Boolean): Nothing = - err(message, errorCode, PropertyValueMap(), internal) - -/** Shorthand for throwing evaluation with context with an error code.. */ -internal fun err(message: String, errorCode: ErrorCode, errorContext: PropertyValueMap, internal: Boolean): Nothing = - throw EvaluationException(message, errorCode, errorContext, internal = internal) - -internal fun expectedArgTypeErrorMsg(types: List): String = when (types.size) { - 0 -> throw IllegalStateException("Should have at least one expected argument type. ") - 1 -> types[0].toString() - else -> { - val window = types.size - 1 - val (most, last) = types.windowed(window, window, true) - most.joinToString(", ") + ", or ${last.first()}" - } -} - -/** Throw an [ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL] error */ -internal fun errInvalidArgumentType( - signature: FunctionSignature, - position: Int, - expectedTypes: List, - actualType: StaticType, -): Nothing { - - val expectedTypeMsg = expectedArgTypeErrorMsg(expectedTypes) - - val actual = when (actualType) { - is SingleType -> StaticTypeUtils.getRuntimeType(actualType).toString() - else -> actualType.toString() - } - - val errorContext = propertyValueMapOf( - Property.FUNCTION_NAME to signature.name, - Property.EXPECTED_ARGUMENT_TYPES to expectedTypeMsg, - Property.ARGUMENT_POSITION to position, - Property.ACTUAL_ARGUMENT_TYPES to actual - ) - - err( - message = "Invalid type for argument $position of ${signature.name}.", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - errorContext = errorContext, - internal = false - ) -} - -internal fun errIntOverflow(intSizeInBytes: Int, errorContext: PropertyValueMap? = null): Nothing { - throw EvaluationException( - message = "Int overflow or underflow", - errorCode = ErrorCode.EVALUATOR_INTEGER_OVERFLOW, - errorContext = (errorContext ?: PropertyValueMap()).also { - it[Property.INT_SIZE_IN_BYTES] = intSizeInBytes - }, - internal = false - ) -} - -internal fun errorContextFrom(location: SourceLocationMeta?): PropertyValueMap { - val errorContext = PropertyValueMap() - if (location != null) { - fillErrorContext(errorContext, location) - } - return errorContext -} - -internal fun fillErrorContext(errorContext: PropertyValueMap, location: SourceLocationMeta?) { - if (location != null) { - errorContext[Property.LINE_NUMBER] = location.lineNum - errorContext[Property.COLUMN_NUMBER] = location.charOffset - } -} - -/** - * Returns the [SourceLocationMeta] as an error context if the [SourceLocationMeta.TAG] exists in the passed - * [metaContainer]. Otherwise, returns an empty map. - */ -internal fun errorContextFrom(metaContainer: MetaContainer?): PropertyValueMap { - if (metaContainer == null) { - return PropertyValueMap() - } - val location = metaContainer[SourceLocationMeta.TAG] as? SourceLocationMeta - return if (location != null) { - errorContextFrom(location) - } else { - PropertyValueMap() - } -} - -/** Throw a function not found error when function name matching fails */ -internal class FunctionNotFoundException(message: String) : Exception(message) - -/** Throw an arity mismatch error when function arity matching fails */ -internal class ArityMismatchException(message: String, val arity: Pair) : Exception(message) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ExprAggregator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ExprAggregator.kt deleted file mode 100644 index fc6a8b7f5b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ExprAggregator.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal - -import org.partiql.lang.eval.ExprValue - -// TODO consider making this API stateless and purely functional, AVG makes this ugly - -/** - * Defines an aggregate function in the evaluator in terms of a stateful accumulator. - * An aggregate function is always unary, and effectively operates over a collection - * (e.g. `BAG`/`LIST`) of values. This API defines the accumulator function over elements of the - * operand. The evaluator's responsibility is to effectively compile this definition - * into a form of [ExprFunction] that operates over the collection as an [ExprValue]. - */ -internal interface ExprAggregator { - /** Accumulates the next value into this [ExprAggregator]. */ - fun next(value: ExprValue) - - /** Digests the result of the accumulated values. */ - fun compute(): ExprValue -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/FunctionManager.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/FunctionManager.kt deleted file mode 100644 index 7401d5994a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/FunctionManager.kt +++ /dev/null @@ -1,77 +0,0 @@ -package org.partiql.lang.eval.internal - -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.types.StaticTypeUtils -import org.partiql.types.StaticType - -/** - * Replaces the map of functions to achieve function overloading. - * Supports getting all functions and getting the function map by name - */ -internal class FunctionManager( - private val functions: List, -) { - private val functionMap: Map> = functions.groupBy { it.signature.name } - - /** - * Get function from the list by name, arity, and argumentTypes(ExprValue) - */ - @Throws( - FunctionNotFoundException::class, - ArityMismatchException::class - ) - internal fun get(name: String, arity: Int, args: List): ExprFunction { - val funcs = functionMap[name] ?: throw FunctionNotFoundException("Name check fails") - - val funcsMatchingArity = funcs.filter { it.signature.arity.contains(arity) } - if (funcsMatchingArity.isEmpty()) { - throw ArityMismatchException("Arity check fails", getMinMaxArities(funcs)) - } - - val errorList = mutableListOf() - for (func in funcsMatchingArity) { - try { - checkArgumentTypesEnableNullMissing(func.signature, args) - return func - } catch (e: Exception) { - errorList.add(e) - } - } - throw errorList.first() - } - - /** - * Check argument types(ExprValue) by requiredArgs, OptionalArgs and VariadicArgs. This function will pass all arg values including MISSING or NULL values. - */ - internal fun checkArgumentTypesEnableNullMissing(signature: FunctionSignature, args: List) { - fun checkArgumentType(formalStaticType: StaticType, actualStaticType: StaticType, position: Int) { - val formalExprValueTypeDomain = StaticTypeUtils.getTypeDomain(formalStaticType) - - if (actualStaticType == StaticType.NULL || actualStaticType == StaticType.MISSING) { - // Skip if NULL/MISSING - } else if (!StaticTypeUtils.isSubTypeOf(actualStaticType, formalStaticType)) { - errInvalidArgumentType( - signature = signature, - position = position, - expectedTypes = formalExprValueTypeDomain.toList(), - actualType = actualStaticType - ) - } - } - - signature.requiredParameters.zip(args).forEachIndexed { idx, (expected, actual) -> - checkArgumentType(expected, actual, idx + 1) - } - } - - /** - * Get minArity and maxArity by looping candidate functions filtered by function name. - */ - internal fun getMinMaxArities(funcs: List): Pair { - val minArity = funcs.map { it.signature.arity.first }.minOrNull() ?: Int.MAX_VALUE - val maxArity = funcs.map { it.signature.arity.last }.maxOrNull() ?: Int.MIN_VALUE - - return Pair(minArity, maxArity) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Pattern.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Pattern.kt deleted file mode 100644 index fb043c8d74..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Pattern.kt +++ /dev/null @@ -1,117 +0,0 @@ -package org.partiql.lang.eval.internal - -import java.util.regex.Pattern - -private const val ANY_MANY = '%'.toInt() -private const val ANY_ONE = '_'.toInt() - -private const val PATTERN_ADDITIONAL_BUFFER = 8 - -/** - * Translates a SQL-style `LIKE` pattern to a regular expression. - * - * Roughly the algorithm is to - * - call `Pattern.quote` on the literal parts of the pattern - * - translate a single `_` (with no contiguous `%`) to `.` - * - translate a consecutive `_` (with no contiguous `%`) to `.{n,n}` - * - translate any number of consecutive `%` to `.*?` - * - translate any number of consecutive `%` with a `_` contiguous to `.+?` - * - translate any number of consecutive `%` with `_` contiguous to `.{n,}?` - * - prefix the pattern translated via the above rule with '^' and suffix with '$' - * - * @param likePattern A `LIKE` match pattern (i.e. a string where '%' means zero or more and '_' means 1 ). - * @param escapeChar The escape character for the `LIKE` pattern. - * - * @return a [Pattern] which is a regular expression corresponding to the specified `LIKE` pattern. - * - * Examples: - * ``` - * val ESCAPE = '\\'.toInt() - * - * assertEquals("^.*?\\Qfoo\\E$", parsePattern("%foo", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?$", parsePattern("foo%", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%%bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%%%bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%%%%bar", ESCAPE).pattern()) - * assertEquals("^.*?\\Qfoo\\E.*?\\Qbar\\E.*?$", - * parsePattern("%foo%%%%bar%", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.{2,}?\\Qbar\\E$", parsePattern("foo_%_bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.{2,}?\\Qbar\\E$", parsePattern("foo_%_%bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.{2,}?\\Qbar\\E$", parsePattern("foo%_%%_%bar", ESCAPE).pattern()) - * ``` - * - * - * @see java.util.regex.Pattern - */ -internal fun parsePattern(likePattern: String, escapeChar: Int?): Pattern { - val buf = StringBuilder(likePattern.length + PATTERN_ADDITIONAL_BUFFER) - buf.append("^") - - var isEscaped = false - var wildcardMin = -1 - var wildcardUnbounded = false - val literal = StringBuilder() - - // If a wildcard (e.g. a sequence of '%' and '_') has been accumulated, write out the regex equivalent - val flushWildcard = { - if (wildcardMin != -1) { - if (wildcardUnbounded) { - when (wildcardMin) { - 0 -> buf.append(".*?") - 1 -> buf.append(".+?") - else -> buf.append(".{$wildcardMin,}?") - } - } else { - when (wildcardMin) { - 1 -> buf.append(".") - else -> buf.append(".{$wildcardMin,$wildcardMin}") - } - } - wildcardMin = -1 - wildcardUnbounded = false - } - } - - // if a literal has been accumulated, write it out, regex-quoted - val flushLiteral = { - if (literal.isNotEmpty()) { - buf.append(Pattern.quote(literal.toString())) - literal.clear() - } - } - - for (codepoint in likePattern.codePoints()) { - if (!isEscaped) { - if (codepoint == escapeChar) { - isEscaped = true - continue // skip to the next codepoint - } - when (codepoint) { - ANY_ONE -> { - flushLiteral() - wildcardMin = maxOf(wildcardMin, 0) + 1 - } - ANY_MANY -> { - flushLiteral() - wildcardMin = maxOf(wildcardMin, 0) - wildcardUnbounded = true - } - else -> { - flushWildcard() - literal.appendCodePoint(codepoint) - } - } - } else { - flushWildcard() - literal.appendCodePoint(codepoint) - isEscaped = false - } - } - - flushLiteral() - flushWildcard() - - buf.append("$") - return Pattern.compile(buf.toString()) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ProblemHandlers.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ProblemHandlers.kt deleted file mode 100644 index a92e6e0053..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ProblemHandlers.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.partiql.lang.eval.internal - -import org.partiql.errors.Problem -import org.partiql.errors.ProblemHandler -import org.partiql.errors.ProblemSeverity -import org.partiql.lang.ast.passes.SemanticException - -/** - * A [ProblemHandler] that collects all of the encountered [Problem]s without throwing. - * - * This is intended to be used when wanting to collect multiple problems that may be encountered (e.g. a static type - * inference pass that can result in multiple errors and/or warnings). This handler does not collect other exceptions - * that may be thrown. - */ -internal class ProblemCollector : ProblemHandler { - private val problemList = mutableListOf() - - val problems: List - get() = problemList - - val hasErrors: Boolean - get() = problemList.any { it.details.severity == ProblemSeverity.ERROR } - - val hasWarnings: Boolean - get() = problemList.any { it.details.severity == ProblemSeverity.WARNING } - - override fun handleProblem(problem: Problem) { - problemList.add(problem) - } -} - -/** - * A [ProblemHandler] that throws the first [Problem] that has a [ProblemSeverity] of [ProblemSeverity.ERROR] as a - * [SemanticException]. - * - * This is intended to support existing internal code (e.g. CompilerPipeline, StaticTypeInferenceVisitorTransform) - * behavior that expects the first encountered problem to be thrown. Once multiple problem handling is supported - * in that code, this class can be removed. - * - * @throws SemanticException on the first [Problem] logged with severity of [ProblemSeverity.ERROR] - */ -internal class ProblemThrower : ProblemHandler { - override fun handleProblem(problem: Problem) { - if (problem.details.severity == ProblemSeverity.ERROR) { - throw SemanticException(problem) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/StructExprValue.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/StructExprValue.kt deleted file mode 100644 index 28ab8f347b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/StructExprValue.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.BaseExprValue -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.OrderedBindNames -import org.partiql.lang.eval.OrdinalBindings -import org.partiql.lang.eval.internal.ext.name -import org.partiql.lang.eval.internal.ext.stringValue - -/** Indicates if a struct is ordered or not. */ -internal enum class StructOrdering { - UNORDERED, - ORDERED -} - -/** - * Provides a [ExprValueType.STRUCT] implementation lazily backed by a sequence. - */ -internal open class StructExprValue( - private val ordering: StructOrdering, - private val sequence: Sequence -) : BaseExprValue() { - - override val type = ExprValueType.STRUCT - - /** The backing data structured for operations that require materialization. */ - private data class Materialized( - val bindings: Bindings, - val ordinalBindings: OrdinalBindings, - val orderedBindNames: OrderedBindNames? - ) - - private val materialized by lazy { - val bindMap = HashMap() - val bindList = ArrayList() - val bindNames = ArrayList() - sequence.forEach { - val name = it.name?.stringValue() ?: errNoContext("Expected non-null name for lazy struct", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE, internal = false) - bindMap.putIfAbsent(name, it) - if (ordering == StructOrdering.ORDERED) { - bindList.add(it) - bindNames.add(name) - } - } - - val bindings = Bindings.ofMap(bindMap) - val ordinalBindings = OrdinalBindings.ofList(bindList) - val orderedBindNames = when (ordering) { - StructOrdering.ORDERED -> object : OrderedBindNames { - override val orderedNames = bindNames - } - StructOrdering.UNORDERED -> null - } - - Materialized(bindings, ordinalBindings, orderedBindNames) - } - - override val bindings: Bindings - get() = materialized.bindings - - override val ordinalBindings: OrdinalBindings - get() = materialized.ordinalBindings - - @Suppress("UNCHECKED_CAST") - override fun provideFacet(type: Class?): T? = when (type) { - OrderedBindNames::class.java -> when (ordering) { - StructOrdering.ORDERED -> materialized.orderedBindNames - else -> null - } as T? - else -> null - } - - override fun iterator() = sequence.iterator() -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Thunk.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Thunk.kt deleted file mode 100644 index c43873c3ff..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Thunk.kt +++ /dev/null @@ -1,671 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorBehaviorInPermissiveMode -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.ast.StaticTypeMeta -import org.partiql.lang.domains.staticType -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.ThunkOptions -import org.partiql.lang.eval.ThunkReturnTypeAssertions -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.eval.fillErrorContext -import org.partiql.lang.eval.internal.ext.isUnknown -import org.partiql.lang.types.StaticTypeUtils.isInstance - -/** - * A thunk with no parameters other than the current environment. - * - * See https://en.wikipedia.org/wiki/Thunk - * - * @param TEnv The type of the environment. Generic so that the legacy AST compiler and the new compiler may use - * different types here. - */ -internal typealias Thunk = (TEnv) -> ExprValue - -/** - * A thunk taking a single argument and the current environment. - * - * See https://en.wikipedia.org/wiki/Thunk - * - * @param TEnv The type of the environment. Generic so that the legacy AST compiler and the new compiler may use - * different types here. - * @param TArg The type of the additional argument. - */ -internal typealias ThunkValue = (TEnv, TArg) -> ExprValue - -/** - * A type alias for an exception handler which always throws(primarily used for [TypingMode.LEGACY]). - */ -internal typealias ThunkExceptionHandlerForLegacyMode = (Throwable, SourceLocationMeta?) -> Nothing - -/** - * A type alias for an exception handler which does not always throw(primarily used for [TypingMode.PERMISSIVE]). - */ -internal typealias ThunkExceptionHandlerForPermissiveMode = (Throwable, SourceLocationMeta?) -> Unit - -internal val DEFAULT_EXCEPTION_HANDLER_FOR_LEGACY_MODE: ThunkExceptionHandlerForLegacyMode = { e, sourceLocation -> - val message = e.message ?: "" - throw EvaluationException( - "Internal error, $message", - errorCode = (e as? EvaluationException)?.errorCode ?: ErrorCode.EVALUATOR_GENERIC_EXCEPTION, - errorContext = errorContextFrom(sourceLocation), - cause = e, - internal = true - ) -} - -internal val DEFAULT_EXCEPTION_HANDLER_FOR_PERMISSIVE_MODE: ThunkExceptionHandlerForPermissiveMode = { e, _ -> - when (e) { - is InterruptedException -> { throw e } - is StackOverflowError -> { throw e } - else -> {} - } -} - -/** - * An extension method for creating [ThunkFactory] based on the type of [TypingMode] - * - when [TypingMode] is [TypingMode.LEGACY], creates [LegacyThunkFactory] - * - when [TypingMode] is [TypingMode.PERMISSIVE], creates [PermissiveThunkFactory] - */ -internal fun TypingMode.createThunkFactory( - thunkOptions: ThunkOptions -): ThunkFactory = when (this) { - TypingMode.LEGACY -> LegacyThunkFactory(thunkOptions) - TypingMode.PERMISSIVE -> PermissiveThunkFactory(thunkOptions) -} -/** - * Provides methods for constructing new thunks according to the specified [CompileOptions]. - */ -internal abstract class ThunkFactory( - val thunkOptions: ThunkOptions -) { - private fun checkEvaluationTimeType(thunkResult: ExprValue, metas: MetaContainer): ExprValue { - // When this check is enabled we throw an exception the [MetaContainer] does not have a - // [StaticTypeMeta]. This indicates a bug or unimplemented support for an AST node in - // [StaticTypeInferenceVisitorTransform]. - val staticType = metas.staticType?.type ?: error("Metas collection does not have a StaticTypeMeta") - if (!isInstance(thunkResult, staticType)) { - throw EvaluationException( - "Runtime type does not match the expected StaticType", - ErrorCode.EVALUATOR_VALUE_NOT_INSTANCE_OF_EXPECTED_TYPE, - errorContext = errorContextFrom(metas).apply { - this[Property.EXPECTED_STATIC_TYPE] = staticType.toString() - }, - internal = true - ) - } - return thunkResult - } - - /** - * If [ThunkReturnTypeAssertions.ENABLED] is set, wraps the receiver thunk in another thunk - * that verifies that the value returned from the receiver thunk matches the type found in the [StaticTypeMeta] - * contained within [metas]. - * - * If [metas] contains does not contain [StaticTypeMeta], an [IllegalStateException] is thrown. This is to prevent - * confusion in the case [StaticTypeInferenceVisitorTransform] has a bug which prevents it from assigning a - * [StaticTypeMeta] or in case it is not run at all. - */ - protected fun Thunk.typeCheck(metas: MetaContainer): Thunk = - when (thunkOptions.thunkReturnTypeAssertions) { - ThunkReturnTypeAssertions.DISABLED -> this - ThunkReturnTypeAssertions.ENABLED -> { - val wrapper = { env: TEnv -> - val thunkResult: ExprValue = this(env) - checkEvaluationTimeType(thunkResult, metas) - } - wrapper - } - } - - /** Same as [typeCheck] but works on a [ThunkEnvValue] instead of a [Thunk]. */ - protected fun ThunkValue.typeCheckEnvValue(metas: MetaContainer): ThunkValue = - when (thunkOptions.thunkReturnTypeAssertions) { - ThunkReturnTypeAssertions.DISABLED -> this - ThunkReturnTypeAssertions.ENABLED -> { - val wrapper = { env: TEnv, value: ExprValue -> - val thunkResult: ExprValue = this(env, value) - checkEvaluationTimeType(thunkResult, metas) - } - wrapper - } - } - - /** Same as [typeCheck] but works on a [ThunkEnvValue>] instead of a [Thunk]. */ - protected fun ThunkValue>.typeCheckEnvValueList(metas: MetaContainer): ThunkValue> = - when (thunkOptions.thunkReturnTypeAssertions) { - ThunkReturnTypeAssertions.DISABLED -> this - ThunkReturnTypeAssertions.ENABLED -> { - val wrapper = { env: TEnv, value: List -> - val thunkResult: ExprValue = this(env, value) - checkEvaluationTimeType(thunkResult, metas) - } - wrapper - } - } - - /** - * Creates a [Thunk] which handles exceptions by wrapping them into an [EvaluationException] which uses - * [handleException] to handle exceptions appropriately. - * - * Literal lambdas passed to this function as [t] are inlined into the body of the function being returned, which - * reduces the need to create additional call contexts. The lambdas passed as [t] may not contain non-local returns - * (`crossinline`). - */ - internal inline fun thunkEnv(metas: MetaContainer, crossinline t: Thunk): Thunk { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - - return { env: TEnv -> - handleException(sourceLocationMeta) { - t(env) - } - }.typeCheck(metas) - } - - /** - * Defines the strategy for unknown propagation of 1-3 operands. - * - * This is the [TypingMode] specific implementation of unknown-propagation, used by the [thunkEnvOperands] - * functions. [getVal1], [getVal2] and [getVal2] are lambdas to allow for differences in short-circuiting. - * - * For all [TypingMode]s, if the values returned by [getVal1], [getVal2] and [getVal2] are all known, - * [compute] is invoked to perform the operation-specific computation. - * - * Note: this must be public due to a Kotlin compiler bug: https://youtrack.jetbrains.com/issue/KT-22625. - * This shouldn't matter though because this class is still `internal`. - */ - abstract fun propagateUnknowns( - getVal1: () -> ExprValue, - getVal2: (() -> ExprValue)?, - getVal3: (() -> ExprValue)?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue - - /** - * Similar to the other [propagateUnknowns] overload, performs unknown propagation for a variadic sequence of - * operations. - * - * Note: this must be public due to a Kotlin compiler bug: https://youtrack.jetbrains.com/issue/KT-22625. - * This shouldn't matter though because this class is still `internal`. - */ - abstract fun propagateUnknowns( - operands: Sequence, - compute: (List) -> ExprValue - ): ExprValue - - /** - * Creates a thunk that accepts three [Thunk] operands ([t1], [t2], and [t3]), evaluates them and propagates - * unknowns according to the current [TypingMode]. When possible, use this function or one of its overloads - * instead of [thunkEnv] when the operation requires propagation of unknown values. - * - * [t1], [t2] and [t3] are each evaluated in with short circuiting depending on the current [TypingMode]: - * - * - In [TypingMode.PERMISSIVE] mode, the first `MISSING` returned from one of the thunks causes a short-circuit, - * and `MISSING` is returned immediately without evaluating the remaining thunks. If none of the thunks return - * `MISSING`, if any of them has returned `NULL`, `NULL` is returned. - * - In [TypingMode.LEGACY] mode, the first `NULL` or `MISSING` returned from one of the thunks causes a - * short-circuit, and returns `NULL` without evaluating the remaining thunks. - * - * In both modes, if none of the thunks returns `MISSING` or `NULL`, [compute] is invoked to perform the final - * computation on values of the operands which are guaranteed to be known. - * - * Overloads of this function exist that accept 1 and 2 arguments. We do not make [t2] and [t3] nullable with a - * default value of `null` instead of supplying those overloads primarily because [compute] has a different - * signature for each, but also because that would prevent [thunkEnvOperands] from being `inline`. - */ - internal inline fun thunkEnvOperands( - metas: MetaContainer, - crossinline t1: Thunk, - crossinline t2: Thunk, - crossinline t3: Thunk, - crossinline compute: (TEnv, ExprValue, ExprValue, ExprValue) -> ExprValue - ): Thunk = - thunkEnv(metas) { env -> - propagateUnknowns({ t1(env) }, { t2(env) }, { t3(env) }) { v1, v2, v3 -> - compute(env, v1, v2!!, v3!!) - } - }.typeCheck(metas) - - /** See the [thunkEnvOperands] with three [Thunk] operands. */ - internal inline fun thunkEnvOperands( - metas: MetaContainer, - crossinline t1: Thunk, - crossinline t2: Thunk, - crossinline compute: (TEnv, ExprValue, ExprValue) -> ExprValue - ): Thunk = - this.thunkEnv(metas) { env -> - propagateUnknowns({ t1(env) }, { t2(env) }, null) { v1, v2, _ -> - compute(env, v1, v2!!) - } - }.typeCheck(metas) - - /** See the [thunkEnvOperands] with three [Thunk] operands. */ - internal inline fun thunkEnvOperands( - metas: MetaContainer, - crossinline t1: Thunk, - crossinline compute: (TEnv, ExprValue) -> ExprValue - ): Thunk = - this.thunkEnv(metas) { env -> - propagateUnknowns({ t1(env) }, null, null) { v1, _, _ -> - compute(env, v1) - } - }.typeCheck(metas) - - /** See the [thunkEnvOperands] with a variadic list of [Thunk] operands. */ - internal inline fun thunkEnvOperands( - metas: MetaContainer, - operandThunks: List>, - crossinline compute: (TEnv, List) -> ExprValue - ): Thunk { - - return this.thunkEnv(metas) { env -> - val operandSeq = sequence { operandThunks.forEach { yield(it(env)) } } - propagateUnknowns(operandSeq) { values -> - compute(env, values) - } - }.typeCheck(metas) - } - - /** Similar to [thunkEnv], but creates a [ThunkEnvValue] instead. */ - internal inline fun thunkEnvValue( - metas: MetaContainer, - crossinline t: ThunkValue - ): ThunkValue { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - - return { env: TEnv, arg1: ExprValue -> - handleException(sourceLocationMeta) { - t(env, arg1) - } - }.typeCheckEnvValue(metas) - } - - /** Similar to [thunkEnv], but creates a [ThunkEnvValue>] instead. */ - internal inline fun thunkEnvValueList( - metas: MetaContainer, - crossinline t: ThunkValue> - ): ThunkValue> { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - - return { env: TEnv, arg1: List -> - handleException(sourceLocationMeta) { - t(env, arg1) - } - }.typeCheckEnvValueList(metas) - } - - /** - * Similar to [thunkEnv] but evaluates all [argThunks] and performs a fold using [op] as the operation. - * - * Also handles null propagation appropriately for [NAryOp] arithmetic operations. Each thunk in [argThunks] - * is evaluated in turn and: - * - * - for [TypingMode.LEGACY], the first unknown operand short-circuits, returning `NULL`. - * - for [TypingMode.PERMISSIVE], the first missing operand short-circuits, returning `MISSING`. Then, if one - * of the operands returned `NULL`, `NULL` is returned. - * - * For both modes, if all of the operands are known, performs a fold over them with [op]. - */ - internal abstract fun thunkFold( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> ExprValue - ): Thunk - - /** - * Similar to [thunkFold] but intended for comparison operators, i.e. `=`, `>`, `>=`, `<`, `<=`. - * - * The first argument of [op] is always the value of `argThunks[n]` and - * the second is always `argThunks[n + 1]` where `n` is 0 to `argThunks.size - 2`. - * - * - If [op] returns false, the thunk short circuits and the result of the thunk becomes `false`. - * - for [TypingMode.LEGACY], the first unknown operand short-circuits, returning `NULL`. - * - for [TypingMode.PERMISSIVE], the first missing operand short-circuits, returning `MISSING`. Then, if one - * of the operands returned `NULL`, `NULL` is returned. - * - * If [op] is true for all invocations then the result of the thunk becomes `true`, otherwise the reuslt is `false`. - * - * The name of this function was inspired by Racket's `andmap` procedure. - */ - internal abstract fun thunkAndMap( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> Boolean - ): Thunk - - /** Populates [exception] with the line & column from the specified [SourceLocationMeta]. */ - protected fun populateErrorContext( - exception: EvaluationException, - sourceLocation: SourceLocationMeta? - ): EvaluationException { - // Only add source location data to the error context if it doesn't already exist - // in [errorContext]. - if (!exception.errorContext.hasProperty(Property.LINE_NUMBER)) { - sourceLocation?.let { fillErrorContext(exception.errorContext, sourceLocation) } - } - return exception - } - - /** - * Handles exceptions appropriately for a run-time [Thunk]. - * - * - The [SourceLocationMeta] will be extracted from [MetaContainer] and included in any [EvaluationException] that - * is thrown, if present. - * - The location information is added to the [EvaluationException]'s `errorContext`, if it is not already present. - * - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown - * with the original exception as the cause. - */ - abstract fun handleException( - sourceLocation: SourceLocationMeta?, - block: () -> ExprValue - ): ExprValue -} - -/** - * Provides methods for constructing new thunks according to the specified [CompileOptions] for [TypingMode.LEGACY] behaviour. - */ -internal class LegacyThunkFactory( - thunkOptions: ThunkOptions -) : ThunkFactory(thunkOptions) { - - override fun propagateUnknowns( - getVal1: () -> ExprValue, - getVal2: (() -> ExprValue)?, - getVal3: (() -> ExprValue)?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue { - val val1 = getVal1() - return when { - val1.isUnknown() -> ExprValue.nullValue - else -> { - val val2 = getVal2?.let { it() } - when { - val2 == null -> compute(val1, null, null) - val2.isUnknown() -> ExprValue.nullValue - else -> { - val val3 = getVal3?.let { it() } - when { - val3 == null -> compute(val1, val2, null) - val3.isUnknown() -> ExprValue.nullValue - else -> compute(val1, val2, val3) - } - } - } - } - } - } - - override fun propagateUnknowns( - operands: Sequence, - compute: (List) -> ExprValue - ): ExprValue { - // Because we need to short-circuit on the first unknown value and [operands] is a sequence, - // we can't use .map here. (non-local returns on `.map` are not allowed) - val argValues = mutableListOf() - operands.forEach { - when { - it.isUnknown() -> return ExprValue.nullValue - else -> argValues.add(it) - } - } - return compute(argValues) - } - - /** See [ThunkFactory.thunkFold]. */ - override fun thunkFold( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> ExprValue - ): Thunk { - require(argThunks.isNotEmpty()) { "argThunks must not be empty" } - - val firstThunk = argThunks.first() - val otherThunks = argThunks.drop(1) - return thunkEnv(metas) thunkBlock@{ env -> - val firstValue = firstThunk(env) - when { - // Short-circuit at first NULL or MISSING value and return NULL. - firstValue.isUnknown() -> ExprValue.nullValue - else -> { - otherThunks.fold(firstValue) { acc, curr -> - val currValue = curr(env) - if (currValue.type.isUnknown) { - return@thunkBlock ExprValue.nullValue - } - op(acc, currValue) - } - } - } - }.typeCheck(metas) - } - - /** See [ThunkFactory.thunkAndMap]. */ - override fun thunkAndMap( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> Boolean - ): Thunk { - require(argThunks.size >= 2) { "argThunks must have at least two elements" } - - val firstThunk = argThunks.first() - val otherThunks = argThunks.drop(1) - - return thunkEnv(metas) thunkBlock@{ env -> - val firstValue = firstThunk(env) - when { - // If the first value is unknown, short circuit returning null. - firstValue.isUnknown() -> ExprValue.nullValue - else -> { - otherThunks.fold(firstValue) { lastValue, currentThunk -> - - val currentValue = currentThunk(env) - if (currentValue.isUnknown()) { - return@thunkBlock ExprValue.nullValue - } - - val result = op(lastValue, currentValue) - if (!result) { - return@thunkBlock ExprValue.newBoolean(false) - } - - currentValue - } - - ExprValue.newBoolean(true) - } - } - } - } - - /** - * Handles exceptions appropriately for a run-time [Thunk] respecting [TypingMode.LEGACY] behaviour. - * - * - The [SourceLocationMeta] will be extracted from [MetaContainer] and included in any [EvaluationException] that - * is thrown, if present. - * - The location information is added to the [EvaluationException]'s `errorContext`, if it is not already present. - * - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown - * with the original exception as the cause. - */ - override fun handleException( - sourceLocation: SourceLocationMeta?, - block: () -> ExprValue - ): ExprValue = - try { - block() - } catch (e: EvaluationException) { - throw populateErrorContext(e, sourceLocation) - } catch (e: Exception) { - thunkOptions.handleExceptionForLegacyMode(e, sourceLocation) - } -} - -/** - * Provides methods for constructing new thunks according to the specified [CompileOptions] and for - * [TypingMode.PERMISSIVE] behaviour. - */ -internal class PermissiveThunkFactory( - thunkOptions: ThunkOptions -) : ThunkFactory(thunkOptions) { - - override fun propagateUnknowns( - getVal1: () -> ExprValue, - getVal2: (() -> ExprValue)?, - getVal3: (() -> ExprValue)?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue { - val val1 = getVal1() - return when (val1.type) { - ExprValueType.MISSING -> ExprValue.missingValue - else -> { - val val2 = getVal2?.let { it() } - when { - val2 == null -> nullOrCompute(val1, null, null, compute) - val2.type == ExprValueType.MISSING -> ExprValue.missingValue - else -> { - val val3 = getVal3?.let { it() } - when { - val3 == null -> nullOrCompute(val1, val2, null, compute) - val3.type == ExprValueType.MISSING -> ExprValue.missingValue - else -> nullOrCompute(val1, val2, val3, compute) - } - } - } - } - } - } - - override fun propagateUnknowns( - operands: Sequence, - compute: (List) -> ExprValue - ): ExprValue { - - // Because we need to short-circuit on the first MISSING value and [operands] is a sequence, - // we can't use .map here. (non-local returns on `.map` are not allowed) - val argValues = mutableListOf() - operands.forEach { - when (it.type) { - ExprValueType.MISSING -> return ExprValue.missingValue - else -> argValues.add(it) - } - } - return when { - // if any result is `NULL`, propagate return null instead. - argValues.any { it.type == ExprValueType.NULL } -> ExprValue.nullValue - else -> compute(argValues) - } - } - - private fun nullOrCompute( - v1: ExprValue, - v2: ExprValue?, - v3: ExprValue?, - compute: (ExprValue, ExprValue?, ExprValue?) -> ExprValue - ): ExprValue = - when { - v1.type == ExprValueType.NULL || - (v2?.let { it.type == ExprValueType.NULL }) ?: false || - (v3?.let { it.type == ExprValueType.NULL }) ?: false -> ExprValue.nullValue - else -> compute(v1, v2, v3) - } - - /** See [ThunkFactory.thunkFold]. */ - override fun thunkFold( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> ExprValue - ): Thunk { - require(argThunks.isNotEmpty()) { "argThunks must not be empty" } - - return thunkEnv(metas) { env -> - val values = argThunks.map { - val v = it(env) - when (v.type) { - // Short-circuit at first detected MISSING value. - ExprValueType.MISSING -> return@thunkEnv ExprValue.missingValue - else -> v - } - } - when { - // Propagate NULL if any operand is NULL. - values.any { it.type == ExprValueType.NULL } -> ExprValue.nullValue - // compute the final value. - else -> values.reduce { first, second -> op(first, second) } - } - }.typeCheck(metas) - } - - /** See [ThunkFactory.thunkAndMap]. */ - override fun thunkAndMap( - metas: MetaContainer, - argThunks: List>, - op: (ExprValue, ExprValue) -> Boolean - ): Thunk { - require(argThunks.size >= 2) { "argThunks must have at least two elements" } - - return thunkEnv(metas) thunkBlock@{ env -> - val values = argThunks.map { - val v = it(env) - when (v.type) { - // Short-circuit at first detected MISSING value. - ExprValueType.MISSING -> return@thunkBlock ExprValue.missingValue - else -> v - } - } - when { - // Propagate NULL if any operand is NULL. - values.any { it.type == ExprValueType.NULL } -> ExprValue.nullValue - else -> { - (0..(values.size - 2)).forEach { i -> - if (!op(values[i], values[i + 1])) - return@thunkBlock ExprValue.newBoolean(false) - } - - return@thunkBlock ExprValue.newBoolean(true) - } - } - } - } - - /** - * Handles exceptions appropriately for a run-time [Thunk] respecting [TypingMode.PERMISSIVE] behaviour. - * - * - Exceptions thrown by [block] that are [EvaluationException] are caught and [MissingExprValue] is returned. - * - Exceptions thrown by [block] that are not an [EvaluationException] cause an [EvaluationException] to be thrown - * with the original exception as the cause. - */ - override fun handleException( - sourceLocation: SourceLocationMeta?, - block: () -> ExprValue - ): ExprValue = - try { - block() - } catch (e: EvaluationException) { - thunkOptions.handleExceptionForPermissiveMode(e, sourceLocation) - when (e.errorCode.errorBehaviorInPermissiveMode) { - // Rethrows the exception as it does in LEGACY mode. - ErrorBehaviorInPermissiveMode.THROW_EXCEPTION -> throw populateErrorContext(e, sourceLocation) - ErrorBehaviorInPermissiveMode.RETURN_MISSING -> ExprValue.missingValue - } - } catch (e: Exception) { - thunkOptions.handleExceptionForLegacyMode(e, sourceLocation) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Time.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Time.kt deleted file mode 100644 index 53f62a2323..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/Time.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.partiql.lang.eval.internal - -// Constants related to the TIME - -internal const val HOURS_PER_DAY = 24 -internal const val MINUTES_PER_HOUR = 60 -internal const val SECONDS_PER_MINUTE = 60 -internal const val SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR -internal const val NANOS_PER_SECOND = 1000000000 -internal const val MAX_PRECISION_FOR_TIME = 9 - -internal enum class DateTimePart { - YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, TIMEZONE_HOUR, TIMEZONE_MINUTE; - - companion object { - fun safeValueOf(value: String): DateTimePart? = try { - valueOf(value.uppercase().trim()) - } catch (_: IllegalArgumentException) { - null - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/Accumulator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/Accumulator.kt deleted file mode 100644 index 82639eb6b3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/Accumulator.kt +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.builtins - -import org.partiql.errors.ErrorCode -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.NaturalExprValueComparators -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.internal.ExprAggregator -import org.partiql.lang.eval.internal.errNoContext -import org.partiql.lang.eval.internal.ext.bigDecimalOf -import org.partiql.lang.eval.internal.ext.createUniqueExprValueFilter -import org.partiql.lang.eval.internal.ext.exprValue -import org.partiql.lang.eval.internal.ext.isUnknown -import org.partiql.lang.eval.numberValue -import org.partiql.lang.util.div -import org.partiql.lang.util.plus - -internal sealed class Accumulator( - internal open val filter: (ExprValue) -> Boolean -) : ExprAggregator { - companion object { - internal fun create(funcName: String, quantifier: PartiqlPhysical.SetQuantifier): Accumulator { - val filter = when (quantifier) { - is PartiqlPhysical.SetQuantifier.Distinct -> createUniqueExprValueFilter() - is PartiqlPhysical.SetQuantifier.All -> { _: ExprValue -> true } - } - return when (funcName.trim().lowercase()) { - "min" -> AccumulatorMin(filter) - "max" -> AccumulatorMax(filter) - "avg" -> AccumulatorAvg(filter) - "count" -> AccumulatorCount(filter) - "sum" -> AccumulatorSum(filter) - "group_as" -> AccumulatorGroupAs(filter) - "every" -> AccumulatorEvery(filter) - "any" -> AccumulatorAnySome(filter) - "some" -> AccumulatorAnySome(filter) - else -> throw IllegalArgumentException("Unsupported aggregation function: $funcName") - } - } - } - - override fun next(value: ExprValue) { - if (value.isUnknown() || filter.invoke(value).not()) return - nextValue(value) - } - - abstract fun nextValue(value: ExprValue) -} - -internal class AccumulatorSum( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var sum: Number? = null - - override fun nextValue(value: ExprValue) { - checkIsNumberType(funcName = "SUM", value = value) - if (sum == null) sum = 0L - this.sum = value.numberValue() + this.sum!! - } - - override fun compute(): ExprValue { - return sum?.exprValue() ?: ExprValue.nullValue - } -} - -internal class AccumulatorAvg( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var sum: Number = 0.0 - var count: Long = 0L - - override fun nextValue(value: ExprValue) { - checkIsNumberType(funcName = "AVG", value = value) - this.sum += value.numberValue() - this.count += 1L - } - - override fun compute(): ExprValue = when (count) { - 0L -> ExprValue.nullValue - else -> (sum / bigDecimalOf(count)).exprValue() - } -} - -internal class AccumulatorMax( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var max: ExprValue = ExprValue.nullValue - - override fun nextValue(value: ExprValue) { - max = comparisonAccumulator(NaturalExprValueComparators.NULLS_LAST_DESC)(max, value) - } - - override fun compute(): ExprValue = max -} - -internal class AccumulatorMin( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var min: ExprValue = ExprValue.nullValue - - override fun nextValue(value: ExprValue) { - min = comparisonAccumulator(NaturalExprValueComparators.NULLS_LAST_ASC)(min, value) - } - - override fun compute(): ExprValue = min -} - -internal class AccumulatorCount( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var count: Long = 0L - - override fun nextValue(value: ExprValue) { - this.count += 1L - } - - override fun compute(): ExprValue = count.exprValue() -} - -internal class AccumulatorEvery( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - private var res: ExprValue? = null - override fun nextValue(value: ExprValue) { - checkIsBooleanType("EVERY", value) - res = res?.let { ExprValue.newBoolean(it.booleanValue() && value.booleanValue()) } ?: value - } - - override fun compute(): ExprValue = res ?: ExprValue.nullValue -} - -internal class AccumulatorAnySome( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - private var res: ExprValue? = null - override fun nextValue(value: ExprValue) { - checkIsBooleanType("ANY/SOME", value) - res = res?.let { ExprValue.newBoolean(it.booleanValue() || value.booleanValue()) } ?: value - } - - override fun compute(): ExprValue = res ?: ExprValue.nullValue -} - -internal class AccumulatorGroupAs( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - val exprValues = mutableListOf() - - override fun nextValue(value: ExprValue) { - exprValues.add(value) - } - - override fun compute(): ExprValue = ExprValue.newBag(exprValues) -} - -private fun comparisonAccumulator(comparator: NaturalExprValueComparators): (ExprValue?, ExprValue) -> ExprValue = - { left, right -> - when { - left == null || comparator.compare(left, right) > 0 -> right - else -> left - } - } - -internal fun checkIsNumberType(funcName: String, value: ExprValue) { - if (!value.type.isNumber) { - errNoContext( - message = "Aggregate function $funcName expects arguments of NUMBER type but the following value was provided: $value, with type of ${value.type}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_AGG_FUNCTION, - internal = false - ) - } -} - -internal fun checkIsBooleanType(funcName: String, value: ExprValue) { - if (value.type != ExprValueType.BOOL) { - errNoContext( - message = "Aggregate function $funcName expects arguments of BOOL type but the following value was provided: $value, with type of ${value.type}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_AGG_FUNCTION, - internal = false - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/Builtins.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/Builtins.kt deleted file mode 100644 index b4d425b3d8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/Builtins.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.partiql.lang.eval.internal.builtins - -/** - * TODO replace this internal value once we have function libraries - */ -internal val SCALAR_BUILTINS_DEFAULT = - SCALAR_BUILTINS_SQL + SCALAR_BUILTINS_EXT + SCALAR_BUILTINS_COLL_AGG + SYSTEM_BUILTINS_SQL diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/DefinitionalBuiltins.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/DefinitionalBuiltins.kt deleted file mode 100644 index 8769b64d56..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/DefinitionalBuiltins.kt +++ /dev/null @@ -1,88 +0,0 @@ -package org.partiql.lang.eval.internal.builtins - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.eval.internal.ErrorDetails -import org.partiql.lang.eval.internal.createErrorSignaler -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType - -/** Built-in functions specific to the PartiQL language definition. - * - * These functions are defined in the language specification in order to explain some of the PartiQL semantics, - * but are also made available to the user. - * Implementations of these built-ins can depend on compilation options and specific compilation options - * must be chosen in order to obtain an [ExprFunction] instance of these built-ins. - * It is intended that the compilation options used for instantiating a compiler are the same ones - * as used for instantiating these built-ins when adding them as functions to the compiler's environment. - * - */ -// TODO: Currently, the only compilation option in use is [TypingMode], so it is passed here by itself. -// Ideally, something like [CompileOptions] would be the argument instead, but [CompileOptions] is only used -// by the "evaluating compiler" and not by the "planning compiler", while this parameterization -// of the built-ins needs to be applicable in both. -internal fun definitionalBuiltins(typingMode: TypingMode): List = - listOf( - ExprFunctionCollToScalar(typingMode), - ) - -/** `coll_to_scalar` extracts the scalar value contained in a "singleton table", - * as performed in most cases of subquery coercion. - * If the input is a collection consisting of a single element, - * which in turn is a struct with exactly one attribute, - * `coll_to_scalar` returns the value of the attribute. - * For all other inputs, the result is either `MISSING` or an error, - * depending on the typing mode. - */ -internal class ExprFunctionCollToScalar(typingMode: TypingMode) : ExprFunction { - override val signature = FunctionSignature( - name = "coll_to_scalar", - requiredParameters = listOf(StaticType.ANY), - returnType = StaticType.ANY - ) - - // TODO: Is ErrorSignaler most appropriate to use here? - // By its external structure, ErrorSignaler is exactly what is needed: - // based on TypingMode, it either produces MISSING or an error. - // However, it is not used much, the existing usage is for a different setting - // (defined on top of the basic setting needed here), - // and the final error message is not best formatted. - // The latter appears to be a symptom of general accumulated cruft in error-handling. - private val signaler = typingMode.createErrorSignaler() - - /** Handler for situations when extraction cannot succeed. - * Produces either the MISSING or an error. */ - private fun hiccup(reason: String): ExprValue { - return signaler.error( - ErrorCode.EVALUATOR_NON_SINGLETON_COLLECTION - ) { ErrorDetails(metas = emptyMap(), message = reason) } - } - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val coll = required[0] - if (!coll.type.isSequence) { - return hiccup("because it is not a collection.") - } else { // coll is a LIST, BAG, or SEXP - val seq = coll.asSequence() - if (seq.count() != 1) { - return hiccup("because the collection does not contain exactly one member.") - } else { // we have a singleton collection - val struct = seq.first() - if (struct.type != ExprValueType.STRUCT) { - return hiccup("because the only member of the collection is not a struct.") - } else { // the single element is a struct - val vals = struct.asSequence() - if (vals.count() != 1) { - return hiccup("because the only struct member of the collection does not contain exactly one attribute.") - } else { // the single struct has exactly one attribute - return vals.first() - } - } - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ExprFunctionBinaryNumeric.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ExprFunctionBinaryNumeric.kt deleted file mode 100644 index a8f1ee2233..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ExprFunctionBinaryNumeric.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.partiql.lang.eval.internal.builtins - -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.internal.ext.exprValue -import org.partiql.lang.eval.numberValue -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType - -/** - * Prototype of `(Number, Number) -> Number` as a PartiQL ExprFunction. - */ -internal abstract class ExprFunctionBinaryNumeric(name: String) : ExprFunction { - - abstract fun call(x: Number, y: Number): Number - - override val signature = FunctionSignature( - name = name, - requiredParameters = listOf(StaticType.NUMERIC, StaticType.NUMERIC), - returnType = StaticType.NUMERIC, - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val x = required[0].numberValue() - val y = required[1].numberValue() - val result = call(x, y) - return result.exprValue() - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ExprFunctionMeasure.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ExprFunctionMeasure.kt deleted file mode 100644 index cf38824946..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ExprFunctionMeasure.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.partiql.lang.eval.internal.builtins - -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType - -/** - * Prototype of `(v: T) -> Int` where the action applies some measure to v - */ -internal abstract class ExprFunctionMeasure(name: String, type: StaticType) : ExprFunction { - - companion object { - - /** - * Placed here rather than StaticType as an internal helper rather than an extension of StaticType - */ - @JvmField - val BITSTRING = StaticType.unionOf(StaticType.SYMBOL, StaticType.STRING, StaticType.BLOB, StaticType.CLOB) - } - - abstract fun call(value: ExprValue): Int - - override val signature = FunctionSignature( - name = name, - requiredParameters = listOf(type), - returnType = StaticType.INT, - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val value = required[0] - val units = call(value) - return ExprValue.newInt(units) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ExprFunctionUnaryNumeric.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ExprFunctionUnaryNumeric.kt deleted file mode 100644 index 6096e34b94..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ExprFunctionUnaryNumeric.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.partiql.lang.eval.internal.builtins - -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.internal.ext.exprValue -import org.partiql.lang.eval.numberValue -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType - -/** - * Prototype of `(Number) -> Number` as a PartiQL ExprFunction. - */ -internal abstract class ExprFunctionUnaryNumeric(name: String) : ExprFunction { - - abstract fun call(x: Number): Number - - override val signature = FunctionSignature( - name = name, - requiredParameters = listOf(StaticType.NUMERIC), - returnType = StaticType.NUMERIC, - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val x = required[0].numberValue() - val result = call(x) - return result.exprValue() - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ScalarBuiltinsCollAgg.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ScalarBuiltinsCollAgg.kt deleted file mode 100644 index dc7e7e9b8d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ScalarBuiltinsCollAgg.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.builtins - -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.internal.ext.createUniqueExprValueFilter -import org.partiql.lang.eval.stringValue -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType -import org.partiql.types.StaticType.Companion.unionOf - -/** - * TODO replace this internal value once we have function libraries - */ -internal val SCALAR_BUILTINS_COLL_AGG = listOf( - ExprFunctionCollMax, - ExprFunctionCollMin, - ExprFunctionCollAvg, - ExprFunctionCollSum, - ExprFunctionCollCount, - ExprFunctionCollEvery, - ExprFunctionCollAny, - ExprFunctionCollSome, -) - -/** - * This class represents an aggregation function call (such as AVG, MAX, MIN, etc) -- but is meant to be operated outside - * of the relational algebra implementation of `aggregate`. In other words, the [CollectionAggregationFunction] allows - * users to call aggregation functions such as "AVG" on collections of scalars. While a user may use the function with the - * "Direct Usage" below, this function is also used within PartiQL to convert aggregate function calls that are outside - * of the scope of the relational algebra operator of aggregations. AKA -- we use this when the aggregation function calls - * are made outside of the projection clause, the HAVING clause, and ORDER BY clause. - * - * Direct Usage: coll_{AGGREGATE}('all', [0, 1, 2, 3]) - * where ${AGGREGATE} can be replaced with MAX, MIN, AVG, COUNT, and SUM - * - * Example (Direct) Usage: - * ``` - * SELECT a AS inputA, COLL_AVG(a) AS averagedA - * FROM << {'a': [0, 1]}, {'a': [10, 11]} >> - * WHERE COLL_AVG(a) > 0.5 - * ``` - * - * Example (Indirect) Usage: - * ``` - * SELECT a - * FROM << {'a': [0, 1]}, {'a': [10, 11]} >> - * WHERE AVG(a) > 0.5 - * ``` - * - * The above indirect example shows how this is leveraged. The WHERE clause does not allow aggregation functions to be passed to the - * aggregate operator, so we internally convert the AVG to a [CollectionAggregationFunction] (which is just an expression - * function call). - */ -internal sealed class CollectionAggregationFunction( - val name: String, - val accumulator: ((ExprValue) -> Boolean) -> Accumulator, -) : ExprFunction { - - companion object { - const val PREFIX = "coll_" - } - - private val collection = unionOf(StaticType.LIST, StaticType.BAG, StaticType.STRUCT, StaticType.SEXP) - - override val signature: FunctionSignature = FunctionSignature( - name = "$PREFIX$name", - requiredParameters = listOf(StaticType.STRING, collection), - returnType = StaticType.NUMERIC - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val filter = required[0].asQuantifierFilter() - val collection = required[1].asSequence() - // instantiate a fresh accumulator for each invocation as this is a "tuple-level" function - val acc = accumulator(filter) - collection.forEach { v -> acc.next(v) } - return acc.compute() - } - - private fun ExprValue.asQuantifierFilter() = when (stringValue().lowercase().trim()) { - "all" -> { _: ExprValue -> true } - "distinct" -> createUniqueExprValueFilter() - else -> throw IllegalArgumentException("Unrecognized set quantifier: $this") - } -} - -internal object ExprFunctionCollMax : CollectionAggregationFunction( - name = "max", - accumulator = ::AccumulatorMax, -) - -internal object ExprFunctionCollMin : CollectionAggregationFunction( - name = "min", - accumulator = ::AccumulatorMin -) - -internal object ExprFunctionCollAvg : CollectionAggregationFunction( - name = "avg", - accumulator = ::AccumulatorAvg -) - -internal object ExprFunctionCollSum : CollectionAggregationFunction( - name = "sum", - accumulator = ::AccumulatorSum -) - -internal object ExprFunctionCollCount : CollectionAggregationFunction( - name = "count", - accumulator = ::AccumulatorCount -) - -internal object ExprFunctionCollEvery : CollectionAggregationFunction( - name = "every", - accumulator = ::AccumulatorEvery -) - -internal object ExprFunctionCollAny : CollectionAggregationFunction( - name = "any", - accumulator = ::AccumulatorAnySome -) - -internal object ExprFunctionCollSome : CollectionAggregationFunction( - name = "some", - accumulator = ::AccumulatorAnySome -) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ScalarBuiltinsExt.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ScalarBuiltinsExt.kt deleted file mode 100644 index 1463107d68..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ScalarBuiltinsExt.kt +++ /dev/null @@ -1,551 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.builtins - -import com.amazon.ion.Timestamp -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.eval.DEFAULT_COMPARATOR -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.internal.DateTimePart -import org.partiql.lang.eval.internal.NANOS_PER_SECOND -import org.partiql.lang.eval.internal.err -import org.partiql.lang.eval.internal.errNoContext -import org.partiql.lang.eval.internal.ext.adjustPrecisionTo -import org.partiql.lang.eval.internal.ext.bigDecimalValue -import org.partiql.lang.eval.internal.ext.intValue -import org.partiql.lang.eval.internal.ext.toOffsetDateTime -import org.partiql.lang.eval.internal.timestamp.TimestampParser -import org.partiql.lang.eval.internal.timestamp.TimestampTemporalAccessor -import org.partiql.lang.eval.stringValue -import org.partiql.lang.eval.time.Time -import org.partiql.lang.eval.timestampValue -import org.partiql.lang.eval.unnamedValue -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.types.UnknownArguments -import org.partiql.lang.util.propertyValueMapOf -import org.partiql.types.StaticType -import org.partiql.types.StaticType.Companion.unionOf -import java.math.BigDecimal -import java.time.DateTimeException -import java.time.Duration -import java.time.Period -import java.time.format.DateTimeFormatter -import java.time.temporal.UnsupportedTemporalTypeException -import java.util.TreeSet - -/** - * TODO replace this internal value once we have function libraries - */ -internal val SCALAR_BUILTINS_EXT = listOf( - ExprFunctionExists, - ExprFunctionUtcNow, - ExprFunctionFilterDistinct, - ExprFunctionDateAdd, - ExprFunctionDateDiff, - ExprFunctionMakeDate, - ExprFunctionMakeTime_1, - ExprFunctionMakeTime_2, - ExprFunctionToTimestamp_1, - ExprFunctionToTimestamp_2, - ExprFunctionSize, - ExprFunctionFromUnix, - ExprFunctionUnixTimestamp_1, - ExprFunctionUnixTimestamp_2, - ExprFunctionToString, - ExprFunctionTextReplace, -) - -/** - * Given a PartiQL value returns true if and only if the value is a non-empty container(bag, sexp, list or struct), - * returns false otherwise - */ -internal object ExprFunctionExists : ExprFunction { - - override val signature = FunctionSignature( - name = "exists", - requiredParameters = listOf(unionOf(StaticType.SEXP, StaticType.LIST, StaticType.BAG, StaticType.STRUCT)), - returnType = StaticType.BOOL, - unknownArguments = UnknownArguments.PASS_THRU - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val collection = required[0].asSequence() - val result = collection.any() - return ExprValue.newBoolean(result) - } -} - -/** - * Returns the current time in UTC as a timestamp. - */ -internal object ExprFunctionUtcNow : ExprFunction { - - override val signature = FunctionSignature( - name = "utcnow", - requiredParameters = listOf(), - returnType = StaticType.TIMESTAMP - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return ExprValue.newTimestamp(session.now) - } -} - -/** - * Returns a bag or list of distinct values contained within a bag, list, sexp, or struct. - * If the container is a struct, the field names are not considered. - */ -internal object ExprFunctionFilterDistinct : ExprFunction { - - override val signature = FunctionSignature( - name = "filter_distinct", - requiredParameters = listOf(unionOf(StaticType.BAG, StaticType.LIST, StaticType.SEXP, StaticType.STRUCT)), - returnType = unionOf(StaticType.BAG, StaticType.LIST) - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val argument = required.first() - // We cannot use a [HashSet] here because [ExprValue] does not implement .equals() and .hashCode() - val encountered = TreeSet(DEFAULT_COMPARATOR) - val seq = sequence { - argument.asSequence().forEach { - if (!encountered.contains(it)) { - encountered.add(it.unnamedValue()) - yield(it) - } - } - } - return when (argument.type) { - ExprValueType.LIST -> ExprValue.newList(seq) - else -> ExprValue.newBag(seq) - } - } -} - -/** - * Given a data part, a quantity and a timestamp, returns an updated timestamp by altering datetime part by quantity - * - * Where DateTimePart is one of - * * year - * * month - * * day - * * hour - * * minute - * * second - */ -internal object ExprFunctionDateAdd : ExprFunction { - - override val signature = FunctionSignature( - name = "date_add", - requiredParameters = listOf(StaticType.SYMBOL, StaticType.INT, StaticType.TIMESTAMP), - returnType = StaticType.TIMESTAMP - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val arg0 = required[0].stringValue() - val part = DateTimePart.safeValueOf(arg0) - val quantity = required[1].intValue() - val timestamp = required[2].timestampValue() - // TODO add a function lowering pass - return try { - val result = when (part) { - DateTimePart.YEAR -> timestamp.adjustPrecisionTo(part).addYear(quantity) - DateTimePart.MONTH -> timestamp.adjustPrecisionTo(part).addMonth(quantity) - DateTimePart.DAY -> timestamp.adjustPrecisionTo(part).addDay(quantity) - DateTimePart.HOUR -> timestamp.adjustPrecisionTo(part).addHour(quantity) - DateTimePart.MINUTE -> timestamp.adjustPrecisionTo(part).addMinute(quantity) - DateTimePart.SECOND -> timestamp.adjustPrecisionTo(part).addSecond(quantity) - else -> errNoContext( - "invalid datetime part for date_add: $arg0", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_DATE_PART, - internal = false - ) - } - ExprValue.newTimestamp(result) - } catch (e: IllegalArgumentException) { - // IllegalArgumentExcept is thrown when the resulting timestamp go out of supported timestamp boundaries - throw EvaluationException(e, errorCode = ErrorCode.EVALUATOR_TIMESTAMP_OUT_OF_BOUNDS, internal = false) - } - } -} - -/** - * Difference in datetime parts between two timestamps. If the first timestamp is later than the second the result is negative. - * - * Syntax: `DATE_DIFF(, , )` - * Where date time part is one of the following keywords: `year, month, day, hour, minute, second` - * - * Timestamps without all datetime parts are considered to be in the beginning of the missing parts to make calculation possible. - * For example: - * - 2010T is interpreted as 2010-01-01T00:00:00.000Z - * - date_diff(month, `2010T`, `2010-05T`) results in 4 - * - * If one of the timestamps has a time component then they are a day apart only if they are 24h apart, examples: - * - date_diff(day, `2010-01-01T`, `2010-01-02T`) results in 1 - * - date_diff(day, `2010-01-01T23:00Z`, `2010-01-02T01:00Z`) results in 0 as they are only 2h apart - */ -internal object ExprFunctionDateDiff : ExprFunction { - - override val signature = FunctionSignature( - name = "date_diff", - requiredParameters = listOf(StaticType.SYMBOL, StaticType.TIMESTAMP, StaticType.TIMESTAMP), - returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val arg0 = required[0].stringValue() - val part = DateTimePart.safeValueOf(arg0) - val l = required[1].timestampValue().toOffsetDateTime() - val r = required[2].timestampValue().toOffsetDateTime() - // TODO add a function lowering pass - val result = when (part) { - DateTimePart.YEAR -> Period.between(l.toLocalDate(), r.toLocalDate()).years - DateTimePart.MONTH -> Period.between(l.toLocalDate(), r.toLocalDate()).toTotalMonths() - DateTimePart.DAY -> Duration.between(l, r).toDays() - DateTimePart.HOUR -> Duration.between(l, r).toHours() - DateTimePart.MINUTE -> Duration.between(l, r).toMinutes() - DateTimePart.SECOND -> Duration.between(l, r).toMillis() / 1_000 - else -> errNoContext( - "invalid datetime part for date_diff: $arg0", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_DATE_PART, - internal = false - ) - } - return ExprValue.newInt(result.toLong()) - } -} - -/** - * Creates a DATE ExprValue from the date fields year, month and day. - * Takes year, month and day as integers and propagates NULL if any of these arguments is unknown (i.e. NULL or MISSING) - * - * make_date(, , ) - */ -internal object ExprFunctionMakeDate : ExprFunction { - - override val signature = FunctionSignature( - name = "make_date", - requiredParameters = listOf(StaticType.INT, StaticType.INT, StaticType.INT), - returnType = StaticType.DATE - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val (year, month, day) = required.map { - // TODO this should be handled by the signature validation, keeping it now as to not change any behavior - if (it.type != ExprValueType.INT) { - err( - message = "Invalid argument type for make_date", - errorCode = ErrorCode.EVALUATOR_INCORRECT_TYPE_OF_ARGUMENTS_TO_FUNC_CALL, - errorContext = propertyValueMapOf( - Property.EXPECTED_ARGUMENT_TYPES to "INT", - Property.FUNCTION_NAME to "make_date", - Property.ACTUAL_ARGUMENT_TYPES to it.type.name - ), - internal = false - ) - } - it.intValue() - } - return try { - ExprValue.newDate(year, month, day) - } catch (e: DateTimeException) { - err( - message = "Date field value out of range. $year-$month-$day", - errorCode = ErrorCode.EVALUATOR_DATE_FIELD_OUT_OF_RANGE, - errorContext = propertyValueMapOf(), - internal = false - ) - } - } -} - -/** - * Creates a TIME ExprValue from the time fields hour, minute, second and optional timezone_minutes. - * Takes hour, minute and optional timezone_minutes as integers, second as decimal and propagates NULL if any of these arguments is unknown (i.e. NULL or MISSING) - * - * make_time(, , ) - * make_time(, , , ) - */ - -internal abstract class ExprFunctionMakeTime : ExprFunction { - protected fun makeTime( - hour: Int, - minute: Int, - second: BigDecimal, - tzMinutes: Int? - ): ExprValue { - try { - return ExprValue.newTime( - Time.of( - hour, - minute, - second.toInt(), - (second.remainder(BigDecimal.ONE).multiply(NANOS_PER_SECOND.toBigDecimal())).toInt(), - second.scale(), - tzMinutes - ) - ) - } catch (e: EvaluationException) { - err( - message = e.message, - errorCode = ErrorCode.EVALUATOR_TIME_FIELD_OUT_OF_RANGE, - errorContext = e.errorContext, - internal = false - ) - } - } -} - -internal object ExprFunctionMakeTime_1 : ExprFunctionMakeTime() { - override val signature = FunctionSignature( - name = "make_time", - requiredParameters = listOf(StaticType.INT, StaticType.INT, StaticType.DECIMAL), - returnType = StaticType.TIME - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val (hour, min, sec) = required - return makeTime(hour.intValue(), min.intValue(), sec.bigDecimalValue(), null) - } -} - -internal object ExprFunctionMakeTime_2 : ExprFunctionMakeTime() { - override val signature = FunctionSignature( - name = "make_time", - requiredParameters = listOf(StaticType.INT, StaticType.INT, StaticType.DECIMAL, StaticType.INT), - returnType = StaticType.TIME - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val (hour, min, sec, opt) = required - return makeTime(hour.intValue(), min.intValue(), sec.bigDecimalValue(), opt.intValue()) - } -} - -/** - * PartiQL function to convert a formatted string into an Ion Timestamp. - */ -internal abstract class ExprFunctionToTimestamp : ExprFunction - -internal object ExprFunctionToTimestamp_1 : ExprFunctionToTimestamp() { - - override val signature = FunctionSignature( - name = "to_timestamp", - requiredParameters = listOf(StaticType.STRING), - returnType = StaticType.TIMESTAMP - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val ts = try { - Timestamp.valueOf(required[0].stringValue()) - } catch (ex: IllegalArgumentException) { - throw EvaluationException( - message = "Timestamp was not a valid ion timestamp", - errorCode = ErrorCode.EVALUATOR_ION_TIMESTAMP_PARSE_FAILURE, - errorContext = PropertyValueMap(), - cause = ex, - internal = false - ) - } - return ExprValue.newTimestamp(ts) - } -} - -internal object ExprFunctionToTimestamp_2 : ExprFunctionToTimestamp() { - - override val signature = FunctionSignature( - name = "to_timestamp", - requiredParameters = listOf(StaticType.STRING, StaticType.STRING), - returnType = StaticType.TIMESTAMP - ) - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val ts = TimestampParser.parseTimestamp(required[0].stringValue(), required[1].stringValue()) - return ExprValue.newTimestamp(ts) - } -} - -/** - * Builtin function to return the size of a container type, i.e. size of Lists, Structs and Bags. This function - * propagates null and missing values as described in docs/Functions.md - * - * syntax: `size()` where container can be a BAG, SEXP, STRUCT or LIST. - */ -internal object ExprFunctionSize : ExprFunction { - - override val signature = FunctionSignature( - name = "size", - requiredParameters = listOf(unionOf(StaticType.LIST, StaticType.BAG, StaticType.STRUCT, StaticType.SEXP)), - returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val collection = required[0] - val result = collection.count() - return ExprValue.newInt(result) - } -} - -/** - * Builtin function to convert the given unix epoch into a PartiQL `TIMESTAMP` [ExprValue]. A unix epoch represents - * the seconds since '1970-01-01 00:00:00' UTC. Largely based off MySQL's FROM_UNIXTIME. - * - * Syntax: `FROM_UNIXTIME(unix_timestamp)` - * Where unix_timestamp is a (potentially decimal) numeric value. If unix_timestamp is a decimal, the returned - * `TIMESTAMP` will have fractional seconds. If unix_timestamp is an integer, the returned `TIMESTAMP` will not have - * fractional seconds. - * - * When given a negative numeric value, this function returns a PartiQL `TIMESTAMP` [ExprValue] before the last epoch. - * When given a non-negative numeric value, this function returns a PartiQL `TIMESTAMP` [ExprValue] after the last - * epoch. - */ -internal object ExprFunctionFromUnix : ExprFunction { - - private val millisPerSecond = BigDecimal(1000) - - override val signature = FunctionSignature( - name = "from_unixtime", - requiredParameters = listOf(unionOf(StaticType.DECIMAL, StaticType.INT)), - returnType = StaticType.TIMESTAMP - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val unixTimestamp = required[0].bigDecimalValue() - val numMillis = unixTimestamp.times(millisPerSecond).stripTrailingZeros() - val timestamp = Timestamp.forMillis(numMillis, null) - return ExprValue.newTimestamp(timestamp) - } -} - -/** - * Builtin function to convert the given PartiQL `TIMESTAMP` [ExprValue] into a unix epoch, where a unix epoch - * represents the seconds since '1970-01-01 00:00:00' UTC. Largely based off MySQL's UNIX_TIMESTAMP. - * - * Syntax: `UNIX_TIMESTAMP([timestamp])` - * - * If UNIX_TIMESTAMP() is called with no [timestamp] argument, it returns the number of whole seconds since - * '1970-01-01 00:00:00' UTC as a PartiQL `INT` [ExprValue] - * - * If UNIX_TIMESTAMP() is called with a [timestamp] argument, it returns the number of seconds from - * '1970-01-01 00:00:00' UTC to the given [timestamp] argument. If given a [timestamp] before the last epoch, will - * return the number of seconds before the last epoch as a negative number. The return value will be a decimal if and - * only if the given [timestamp] has a fractional seconds part. - * - * The valid range of argument values is the range of PartiQL's `TIMESTAMP` value. - */ -internal abstract class ExprFunctionUnixTimestamp : ExprFunction { - private val millisPerSecond = BigDecimal(1000) - protected fun epoch(timestamp: Timestamp): BigDecimal = timestamp.decimalMillis.divide(millisPerSecond) -} - -internal object ExprFunctionUnixTimestamp_1 : ExprFunctionUnixTimestamp() { - - override val signature = FunctionSignature( - name = "unix_timestamp", - requiredParameters = listOf(), - returnType = unionOf(StaticType.INT, StaticType.DECIMAL) - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return ExprValue.newInt(epoch(session.now).toLong()) - } -} - -internal object ExprFunctionUnixTimestamp_2 : ExprFunctionUnixTimestamp() { - - override val signature = FunctionSignature( - name = "unix_timestamp", - requiredParameters = listOf(StaticType.TIMESTAMP), - returnType = unionOf(StaticType.INT, StaticType.DECIMAL) - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val timestamp = required[0].timestampValue() - val epochTime = epoch(timestamp) - return if (timestamp.decimalSecond.scale() == 0) { - ExprValue.newInt(epochTime.toLong()) - } else { - ExprValue.newDecimal(epochTime) - } - } -} - -/** - * Given a timestamp and a format pattern return a string representation of the timestamp in the given format. - * - * Where TimeFormatPattern is a String with the following special character interpretations - */ -internal object ExprFunctionToString : ExprFunction { - - override val signature = FunctionSignature( - name = "to_string", - requiredParameters = listOf(StaticType.TIMESTAMP, StaticType.STRING), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val pattern = required[1].stringValue() - - val formatter: DateTimeFormatter = try { - DateTimeFormatter.ofPattern(pattern) - } catch (ex: IllegalArgumentException) { - errInvalidFormatPattern(pattern, ex) - } - - val timestamp = required[0].timestampValue() - val temporalAccessor = TimestampTemporalAccessor(timestamp) - try { - return ExprValue.newString(formatter.format(temporalAccessor)) - } catch (ex: UnsupportedTemporalTypeException) { - errInvalidFormatPattern(pattern, ex) - } catch (ex: DateTimeException) { - errInvalidFormatPattern(pattern, ex) - } - } - - private fun errInvalidFormatPattern(pattern: String, cause: Exception): Nothing { - val pvmap = PropertyValueMap() - pvmap[Property.TIMESTAMP_FORMAT_PATTERN] = pattern - throw EvaluationException( - "Invalid DateTime format pattern", - ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, - pvmap, - cause, - internal = false - ) - } -} - -/** text_replace(string, from, to) -- in [string], replaces each occurrence of [from] with [to]. - */ -internal object ExprFunctionTextReplace : ExprFunction { - override val signature = FunctionSignature( - name = "text_replace", - requiredParameters = listOf(StaticType.TEXT, StaticType.TEXT, StaticType.TEXT), - returnType = StaticType.TEXT, - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val string = required[0].stringValue() - val from = required[1].stringValue() - val to = required[2].stringValue() - return ExprValue.newString(string.replace(from, to)) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ScalarBuiltinsSql.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ScalarBuiltinsSql.kt deleted file mode 100644 index ed19a132b7..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/ScalarBuiltinsSql.kt +++ /dev/null @@ -1,842 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.builtins - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.internal.errIntOverflow -import org.partiql.lang.eval.internal.errNoContext -import org.partiql.lang.eval.internal.ext.bigDecimalOf -import org.partiql.lang.eval.internal.ext.bytesValue -import org.partiql.lang.eval.internal.ext.codepointLeadingTrim -import org.partiql.lang.eval.internal.ext.codepointOverlay -import org.partiql.lang.eval.internal.ext.codepointPosition -import org.partiql.lang.eval.internal.ext.codepointTrailingTrim -import org.partiql.lang.eval.internal.ext.codepointTrim -import org.partiql.lang.eval.internal.ext.dateTimePartValue -import org.partiql.lang.eval.internal.ext.dateValue -import org.partiql.lang.eval.internal.ext.extractedValue -import org.partiql.lang.eval.internal.ext.intValue -import org.partiql.lang.eval.internal.ext.isUnknown -import org.partiql.lang.eval.internal.ext.stringValue -import org.partiql.lang.eval.internal.ext.timeValue -import org.partiql.lang.eval.internal.ext.timestampValue -import org.partiql.lang.eval.internal.ext.transformIntType -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.util.coerceNumbers -import org.partiql.lang.util.compareTo -import org.partiql.lang.util.exp -import org.partiql.lang.util.isNaN -import org.partiql.lang.util.isNegInf -import org.partiql.lang.util.isPosInf -import org.partiql.lang.util.ln -import org.partiql.lang.util.power -import org.partiql.lang.util.squareRoot -import org.partiql.types.AnyOfType -import org.partiql.types.StaticType -import org.partiql.types.StaticType.Companion.unionOf -import java.math.BigDecimal -import java.math.RoundingMode -import kotlin.math.pow - -/** - * Reference SQL-99 20.70 - * - * TODO replace this internal value once we have function libraries - */ -internal val SCALAR_BUILTINS_SQL = listOf( - ExprFunctionAbs, - ExprFunctionMod, - ExprFunctionCeil, - ExprFunctionCeiling, - ExprFunctionFloor, - ExprFunctionSqrt, - ExprFunctionExp, - ExprFunctionPow, - ExprFunctionLn, - ExprFunctionLower, - ExprFunctionUpper, - ExprFunctionBitLength, - ExprFunctionCharLength, - ExprFunctionCharacterLength, - ExprFunctionOctetLength, - ExprFunctionSubstring_1, - ExprFunctionSubstring_2, - ExprFunctionTrim_1, - ExprFunctionTrim_2, - ExprFunctionTrim_3, - ExprFunctionPosition, - ExprFunctionOverlay_1, - ExprFunctionOverlay_2, - ExprFunctionExtract, - ExprFunctionCardinality -) - -/** - * ABS operates on a numeric argument and returns its absolute value in the same most specific type. - */ -internal object ExprFunctionAbs : ExprFunctionUnaryNumeric("abs") { - - override fun call(x: Number): Number = when (x) { - is Long -> { - if (x == Long.MIN_VALUE) { - errIntOverflow(8) - } else { - kotlin.math.abs(x) - } - } - is Double -> kotlin.math.abs(x) - is Float -> kotlin.math.abs(x) - is BigDecimal -> x.abs() - else -> errNoContext( - message = "Unknown number type", errorCode = ErrorCode.INTERNAL_ERROR, internal = true - ) - } -} - -/** - * MOD operates on two exact numeric arguments with scale 0 (zero) and returns - * the modulus (remainder) of the first argument divided by the second argument as an exact - * numeric with scale 0 (zero). - * - * If the second argument is zero, an EVALUATOR_ARITHMETIC_EXCEPTION will be thrown. - */ -internal object ExprFunctionMod : ExprFunction { - - override val signature = FunctionSignature( - name = "mod", requiredParameters = listOf(StaticType.INT, StaticType.INT), returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val x = required[0].intValue() - val y = required[1].intValue() - if (y == 0) { - errNoContext( - message = "Division by zero", errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, internal = true - ) - } - val result = x % y - return ExprValue.newInt(result) - } -} - -/** - * Returns the nearest integer greater than or equal to the input. - */ -internal object ExprFunctionCeil : ExprFunctionUnaryNumeric("ceil") { - - override fun call(x: Number): Number = when (x) { - java.lang.Double.POSITIVE_INFINITY, java.lang.Double.NEGATIVE_INFINITY, java.lang.Double.NaN -> x - // support for numbers that are larger than 64 bits. - else -> { - val d = bigDecimalOf(x).setScale(0, RoundingMode.CEILING).toBigIntegerExact() - d.transformIntType() - } - } -} - -/** - * Returns the nearest integer greater than or equal to the input. - */ -internal object ExprFunctionCeiling : ExprFunctionUnaryNumeric("ceiling") { - - override fun call(x: Number): Number = ExprFunctionCeil.call(x) -} - -/** - * Returns the absolute value of the given number. - * Note that abs(n) will throw an EVALUATOR_INTEGER_OVERFLOW when n is both of type INT and n = INT.MIN_VALUE. - */ -internal object ExprFunctionFloor : ExprFunctionUnaryNumeric("floor") { - - override fun call(x: Number): Number = when (x) { - java.lang.Double.POSITIVE_INFINITY, java.lang.Double.NEGATIVE_INFINITY, java.lang.Double.NaN -> x - else -> { - val d = bigDecimalOf(x).setScale(0, RoundingMode.FLOOR).toBigIntegerExact() - d.transformIntType() - } - } -} - -/** - * Returns the square root of the given number. - * The input number is required to be non-negative. - */ -internal object ExprFunctionSqrt : ExprFunctionUnaryNumeric("sqrt") { - - override fun call(x: Number): Number { - if (x < 0L) { - errNoContext( - "Cannot take root of a negative number", - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = false - ) - } - return when (x) { - is Long -> kotlin.math.sqrt(x.toDouble()) - is Double -> kotlin.math.sqrt(x) - is Float -> kotlin.math.sqrt(x) - is BigDecimal -> x.squareRoot() - else -> errNoContext( - message = "Unknown number type", errorCode = ErrorCode.INTERNAL_ERROR, internal = true - ) - } - } -} - -/** - * Returns e^x for a given x. - * - * - exp(NaN) is NaN - * - exp(+Inf) is +Inf - * - exp(-Inf) is 0.0 - */ -internal object ExprFunctionExp : ExprFunctionUnaryNumeric("exp") { - override fun call(x: Number): Number = when (x) { - is Long -> kotlin.math.exp(x.toDouble()) - is Double -> kotlin.math.exp(x) - is Float -> kotlin.math.exp(x) - is BigDecimal -> x.exp() - else -> errNoContext( - message = "Unknown number type", errorCode = ErrorCode.INTERNAL_ERROR, internal = true - ) - } -} - -/** - * Coercion is needed for this operation, since it is binary. - * if the operation involves special value `+inf`, `-inf`, `nan`, the result will be a float. - * else if the operation involves decimal, the result will be a decimal - * else the result will be a float. - * - * Note that if x is a negative number, than y must be an integer value, (not necessarily integer type), - * otherwise an EVALUATOR_ARITHMETIC_EXCEPTION will be thrown. - * Special Case: - * pow(x, 0.0) is 1.0; - * pow(x, 1.0) == x; - * pow(x, NaN) is NaN; - * pow(NaN, x) is NaN for x != 0.0; - * pow(x, Inf) is NaN for abs(x) == 1.0 - */ -internal object ExprFunctionPow : ExprFunctionBinaryNumeric("pow") { - - override fun call(x: Number, y: Number): Number { - // CoerceNumber(double, bigDecimal) will attempt to convert the double value to bigDecimal - // and in case of the double value being one of the special number, `+inf`, `-inf`, `nan`, - // an error will be thrown. - // we (presumably) want to avoid this - val (first, second) = if (x.isPosInf || x.isNegInf || x.isNaN) { - x to y.toDouble() - } else if (y.isPosInf || y.isNegInf || y.isNaN) { - x.toDouble() to y - } else { - coerceNumbers(x, y) - } - - return when (first) { - is Long -> first.toDouble().pow(second.toDouble()) - is Double -> { - if (first < 0.0 && ((second as Double) % 1.0 != 0.0)) { - errNoContext( - message = "a negative number raised to a non-integer power yields a complex result", - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = false - ) - } - first.pow(second as Double) - } - is BigDecimal -> try { - first.power(second as BigDecimal) - } catch (e: Exception) { - errNoContext( - message = e.message ?: "Arithmetic Error", - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = false - ) - } - else -> throw IllegalStateException() - } - } -} - -/** - * Returns the natural log of the given number. - * - * The input number is required to be a positive number, otherwise an EVALUATOR_ARITHMETIC_EXCEPTION will be thrown. - */ -internal object ExprFunctionLn : ExprFunctionUnaryNumeric("ln") { - - override fun call(x: Number): Number { - if (x <= 0L) { - errNoContext( - "Cannot take root of a non-positive number", - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = false - ) - } - return when (x) { - is Long -> kotlin.math.ln(x.toDouble()) - is Double -> kotlin.math.ln(x) - is Float -> kotlin.math.ln(x) - is BigDecimal -> x.ln() - else -> errNoContext( - message = "Unknown number type", errorCode = ErrorCode.INTERNAL_ERROR, internal = true - ) - } - } -} - -/** - * Given a string convert all upper case characters to lower case characters. - * - * Any non-upper cased characters remain unchanged. This operation does rely on the locale specified by the runtime - * configuration. This implementation uses Java's String.lowercase(). - */ -internal object ExprFunctionLower : ExprFunction { - - override val signature = FunctionSignature( - name = "lower", requiredParameters = listOf(StaticType.TEXT), returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val str = required[0].stringValue() - val result = str.lowercase() - return ExprValue.newString(result) - } -} - -/** - * Given a string convert all lower case characters to upper case characters. - * - * Any non-lower cases characters remain unchanged. This operation does rely on the locale specified by the runtime - * configuration. The implementation uses Java's String.lowercase(). - */ -internal object ExprFunctionUpper : ExprFunction { - - override val signature = FunctionSignature( - name = "upper", - requiredParameters = listOf(AnyOfType(setOf(StaticType.STRING, StaticType.SYMBOL))), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val str = required[0].stringValue() - val result = str.toUpperCase() - return ExprValue.newString(result) - } -} - -/** - * Returns the number of bits in the input string - */ -internal object ExprFunctionBitLength : ExprFunctionMeasure("bit_length", BITSTRING) { - - override fun call(value: ExprValue): Int = ExprFunctionOctetLength.call(value) * 8 -} - -/** - * Counts the number of characters in the specified string, where 'character' is defined as a single unicode code point. - * - * Same as CHARACTER_LENGTH - */ -internal object ExprFunctionCharLength : ExprFunctionMeasure("char_length", StaticType.TEXT) { - - override fun call(value: ExprValue): Int = codepointLength(value) -} - -/** - * Counts the number of characters in the specified string, where 'character' is defined as a single unicode code point. - * - * Same as CHAR_LENGTH - */ -internal object ExprFunctionCharacterLength : ExprFunctionMeasure("character_length", StaticType.TEXT) { - - override fun call(value: ExprValue): Int = codepointLength(value) -} - -private fun codepointLength(value: ExprValue): Int { - val str = value.stringValue() - return str.codePointCount(0, str.length) -} - -/** - * If an is specified, then let S be the . The - * result of the is the smallest integer not less than the quotient of the - * division (BIT_LENGTH(S)/8). - */ -internal object ExprFunctionOctetLength : ExprFunctionMeasure("octet_length", BITSTRING) { - - override fun call(value: ExprValue): Int { - val bytes = when { - value.type.isText -> value.stringValue().toByteArray(Charsets.UTF_8) - else -> { - // Does not throw if value.type.isLob, otherwise will throw the appropriate evaluation exception - value.bytesValue() - } - } - return bytes.size - } -} - -/** - * Built in function to return the substring of an existing string. This function - * propagates null and missing values as described in docs/Functions.md - * - * From the SQL-92 spec, page 135: - * ``` - * 1) If is specified, then: - * a) Let C be the value of the , - * let LC be the length of C, and - * let S be the value of the . - * - * b) If is specified, then: - * let L be the value of and - * let E be S+L. - * Otherwise: - * let E be the larger of LC + 1 and S. - * - * c) If either C, S, or L is the null value, then the result of - * the is the null value. - * - * d) If E is less than S, then an exception condition is raised: - * data exception-substring error. - * - * e) Case: - * i) If S is greater than LC or if E is less than 1, then the - * result of the is a zero- - * length string. - * - * ii) Otherwise, - * 1) Let S1 be the larger of S and 1. Let E1 be the smaller - * of E and LC+1. Let L1 be E1-S1. - * - * 2) The result of the is - * a character string containing the L1 characters of C - * starting at character number S1 in the same order that - * the characters appear in C. - * - * Pseudocode: - * func substring(): - * # Section 1-a - * str = - * strLength = LENGTH(str) - * startPos = - * - * # Section 1-b - * sliceLength = - * if sliceLength is specified: - * endPos = startPos + sliceLength - * else: - * endPos = greater_of(strLength + 1, startPos) - * - * # Section 1-c: - * if str, startPos, or (sliceLength is specified and is null): - * return null - * - * # Section 1-d - * if endPos < startPos: - * throw exception - * - * # Section 1-e-i - * if startPos > strLength or endPos < 1: - * return '' - * else: - * # Section 1-e-ii - * S1 = greater_of(startPos, 1) - * E1 = lesser_of(endPos, strLength + 1) - * L1 = E1 - S1 - * return java's substring(C, S1, E1) - */ - -internal abstract class ExprFunctionSubstring : ExprFunction { - protected fun substring(target: String, startPosition: Int, quantity: Int? = null): ExprValue { - val codePointCount = target.codePointCount(0, target.length) - if (startPosition > codePointCount) { - return ExprValue.newString("") - } - - // startPosition starts at 1 - // calculate this before adjusting start position to account for negative startPosition - val endPosition = when (quantity) { - null -> codePointCount - else -> Integer.min(codePointCount, startPosition + quantity - 1) - } - - // Clamp start indexes to values that make sense for java substring - val adjustedStartPosition = Integer.max(0, startPosition - 1) - - if (endPosition < adjustedStartPosition) { - return ExprValue.newString("") - } - - val byteIndexStart = target.offsetByCodePoints(0, adjustedStartPosition) - val byteIndexEnd = target.offsetByCodePoints(0, endPosition) - - return ExprValue.newString(target.substring(byteIndexStart, byteIndexEnd)) - } -} - -internal object ExprFunctionSubstring_1 : ExprFunctionSubstring() { - - /** - * TODO implement substring pattern (STRING, STRING, INT) -> STRING, requires sql regex pattern parsing - */ - override val signature = FunctionSignature( - name = "substring", - requiredParameters = listOf(StaticType.STRING, StaticType.INT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val target = required[0].stringValue() - if (required[1].type != ExprValueType.INT) { - errNoContext( - message = "Function substring with two parameters must be of form substring( FROM )", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } - val startPosition = required[1].intValue() - return substring(target, startPosition) - } -} - -internal object ExprFunctionSubstring_2 : ExprFunctionSubstring() { - - /** - * TODO implement substring pattern (STRING, STRING, INT) -> STRING, requires sql regex pattern parsing - */ - override val signature = FunctionSignature( - name = "substring", - requiredParameters = listOf(StaticType.STRING, StaticType.INT, StaticType.INT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val quantity = required.last().intValue() - if (quantity < 0) { - errNoContext( - message = "Argument 3 of substring has to be greater than 0.", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } - val target = required[0].stringValue() - if (required[1].type != ExprValueType.INT) { - errNoContext( - message = "Regular expression substring (SQL T581) currently not supported", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } - val startPosition = required[1].intValue() - return substring(target, startPosition, quantity) - } -} - -/** - * From section 6.7 of SQL 92 spec: - * ``` - * 6) If is specified, then - * a) If FROM is specified, then either or or both shall be specified. - * - * b) If is not specified, then BOTH is implicit. - * - * c) If is not specified, then ' ' is implicit. - * - * d) If TRIM ( SRC ) is specified, then TRIM ( BOTH ' ' FROM SRC ) is implicit. - * - * e) The data type of the is variable-length character string with maximum length equal to the - * fixed length or maximum variable length of the . - * - * f) If a is specified, then and shall be comparable. - * - * g) The character repertoire and form-of-use of the are the same as those of the . - * - * h) The collating sequence and the coercibility attribute are determined as specified for monadic operators in - * Subclause 4.2.3, "Rules determining collating sequence usage", where the of TRIM plays the - * role of the monadic operand. - * ``` - * - * Where: - * * ` ::= LEADING | TRAILING | BOTH` - * * ` ::= ` - * * ` ::= ` - */ -internal abstract class ExprFunctionTrim : ExprFunction { - protected fun trim1Arg(sourceString: ExprValue): String = codepointTrim(sourceString.stringValue()) - - /** - * Small optimization to eliminate the TrimSpecification enum, still temporary since we'll add function lowering. - * Return the behavior on switch rather than switch to get an enum then switch again on the enum for behavior. - */ - private fun getTrimFnOrNull(trimSpecification: String): ((String, String?) -> String)? = - when (trimSpecification.lowercase().trim()) { - "both" -> ::codepointTrim - "leading" -> ::codepointLeadingTrim - "trailing" -> ::codepointTrailingTrim - else -> null - } - - protected fun trim2Arg(specificationOrToRemove: ExprValue, sourceString: ExprValue): String { - // Type signature checking should have handled this - if (!specificationOrToRemove.type.isText) { - errNoContext( - message = "with two arguments trim's first argument must be either the specification or a 'to remove' string", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_TRIM, - internal = false - ) - } - val arg0 = specificationOrToRemove.stringValue() - val arg1 = sourceString.stringValue() - return when (val trimFn = getTrimFnOrNull(arg0)) { - null -> codepointTrim(arg1, arg0) - else -> trimFn.invoke(arg1, null) - } - } - - protected fun trim3Arg(specification: ExprValue, toRemove: ExprValue, sourceString: ExprValue): String { - val arg0 = specification.stringValue() - val arg1 = toRemove.stringValue() - val arg2 = sourceString.stringValue() - return when (val trimFn = getTrimFnOrNull(arg0)) { - null -> { - // TODO with ANTLR, the invalid_argument should be caught in visitTrimFunction in PartiQLVisitor - // We should decide where this error shall be caught and whether it is a parsing error or an evaluator error. - // This error should also be caught in the function lowering as part of logical planning - errNoContext( - message = "'$arg0' is an unknown trim specification, valid values: BOTH, TRAILING, LEADING", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_TRIM, - internal = false - ) - } - else -> trimFn.invoke(arg2, arg1) - } - } -} - -internal object ExprFunctionTrim_1 : ExprFunctionTrim() { - - override val signature = FunctionSignature( - name = "trim", requiredParameters = listOf(StaticType.TEXT), returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val result = trim1Arg(required[0]) - return ExprValue.newString(result) - } -} - -internal object ExprFunctionTrim_2 : ExprFunctionTrim() { - - override val signature = FunctionSignature( - name = "trim", requiredParameters = listOf(StaticType.TEXT, StaticType.STRING), returnType = StaticType.STRING - ) - - override fun callWithRequired( - session: EvaluationSession, - required: List, - ): ExprValue { - val result = trim2Arg(required[0], required[1]) - return ExprValue.newString(result) - } -} - -internal object ExprFunctionTrim_3 : ExprFunctionTrim() { - - override val signature = FunctionSignature( - name = "trim", - requiredParameters = listOf(StaticType.TEXT, StaticType.STRING, StaticType.STRING), - returnType = StaticType.STRING - ) - - override fun callWithRequired( - session: EvaluationSession, - required: List, - ): ExprValue { - val result = trim3Arg(required[0], required[1], required[2]) - return ExprValue.newString(result) - } -} - -/** - * SQL-99 p.15 and p.21 - * - * determines the first position, if any, at which one string, S1, occurs within - * another, S2. If S1 is of length zero, then it occurs at position 1 (one) for any value of S2. If S1 - * does not occur in S2, then zero is returned. The declared type of a is exact numeric - * - * when applied to binary strings is identical in syntax and semantics to the - * corresponding operation on character strings except that the operands are binary strings. - */ -internal object ExprFunctionPosition : ExprFunction { - - override val signature = FunctionSignature( - name = "position", requiredParameters = listOf(StaticType.TEXT, StaticType.TEXT), returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - // POSITION(s1 IN s2) - val s1 = required[0].stringValue() - val s2 = required[1].stringValue() - val result = codepointPosition(s2, s1) - return ExprValue.newInt(result) - } -} - -/** - * ::= - * OVERLAY - * PLACING - * FROM - * [ FOR ] - * - * - * is a function, OVERLAY, that modifies a string argument by replacing - * a given substring of the string, which is specified by a given numeric starting position and a - * given numeric length, with another string (called the replacement string). When the length of - * the substring is zero, nothing is removed from the original string and the string returned by the - * function is the result of inserting the replacement string into the original string at the starting position. - * - * The is equivalent to: - * - * SUBSTRING ( CV FROM 1 FOR SP - 1 ) || RS || SUBSTRING ( CV FROM SP + SL ) - * - * Where CV is the characters value, RS is the replacement string, SP is start position, SL is CV length - */ -internal abstract class ExprFunctionOverlay : ExprFunction { - protected fun overlay(arg0: ExprValue, arg1: ExprValue, arg2: ExprValue, arg3: ExprValue? = null): ExprValue { - val position = arg2.intValue() - if (position < 1) { - errNoContext( - message = "invalid position '$position', must be at least 1", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_TRIM, - internal = false - ) - } - val source = arg0.stringValue() - val overlay = arg1.stringValue() - val length = arg3?.intValue() ?: overlay.length - val result = codepointOverlay(source, overlay, position, length) - return ExprValue.newString(result) - } -} - -internal object ExprFunctionOverlay_1 : ExprFunctionOverlay() { - - override val signature = FunctionSignature( - name = "overlay", - requiredParameters = listOf(StaticType.TEXT, StaticType.TEXT, StaticType.INT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return overlay(required[0], required[1], required[2]) - } -} - -internal object ExprFunctionOverlay_2 : ExprFunctionOverlay() { - - override val signature = FunctionSignature( - name = "overlay", - requiredParameters = listOf(StaticType.TEXT, StaticType.TEXT, StaticType.INT, StaticType.INT), - returnType = StaticType.STRING - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return overlay(required[0], required[1], required[2], required[3]) - } -} - -/** - * Given a datetime part and a datetime type returns then datetime's datetime part value. - * - * ExtractDateTimePart is one of - * * year - * * month - * * day - * * hour - * * minute - * * second - * * timezone_hour - * * timezone_minute - * - * DateTime type is one of - * * DATE - * * TIME - * * TIMESTAMP - * - * Note that ExtractDateTimePart differs from DateTimePart in DATE_ADD. - * - * SQL Note: - * Header : EXTRACT(edp FROM t) - * Purpose : Given a datetime part, edp, and a datetime type t return t's value for edp. This function allows for t to - * be unknown (null or missing) but not edp. If t is unknown the function returns null. - */ -internal object ExprFunctionExtract : ExprFunction { - - private val DATETIME = unionOf(StaticType.TIMESTAMP, StaticType.TIME, StaticType.DATE) - - override val signature = FunctionSignature( - name = "extract", requiredParameters = listOf(StaticType.SYMBOL, DATETIME), returnType = StaticType.DECIMAL - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return when { - required[1].isUnknown() -> ExprValue.nullValue - else -> eval(required) - } - } - - private fun eval(args: List): ExprValue { - val dateTimePart = args[0].dateTimePartValue() - val extractedValue = when (args[1].type) { - ExprValueType.TIMESTAMP -> args[1].timestampValue().extractedValue(dateTimePart) - ExprValueType.DATE -> args[1].dateValue().extractedValue(dateTimePart) - ExprValueType.TIME -> args[1].timeValue().extractedValue(dateTimePart) - else -> errNoContext( - "Expected date, time or timestamp: ${args[1]}", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } - - return ExprValue.newDecimal(extractedValue) - } -} - -/** - * Builtin function to return the size of a container type, i.e. size of Lists, Structs and Bags. This function - * propagates null and missing values as described in docs/Functions.md - * - * syntax: `size()` where container can be a BAG, SEXP, STRUCT or LIST. - */ -internal object ExprFunctionCardinality : ExprFunction { - - override val signature = FunctionSignature( - name = "cardinality", - requiredParameters = listOf(unionOf(StaticType.LIST, StaticType.BAG, StaticType.STRUCT, StaticType.SEXP)), - returnType = StaticType.INT - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val collection = required[0] - val result = collection.count() - return ExprValue.newInt(result) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/SystemBuiltinsSql.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/SystemBuiltinsSql.kt deleted file mode 100644 index 79323dc1d3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/builtins/SystemBuiltinsSql.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.partiql.lang.eval.internal.builtins - -import com.amazon.ion.IonType -import com.amazon.ionelement.api.emptyMetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.internal.err -import org.partiql.lang.types.FunctionSignature -import org.partiql.types.StaticType - -internal val SYSTEM_BUILTINS_SQL = listOf( - ExprFunctionCurrentUser -) - -internal object ExprFunctionCurrentUser : ExprFunction { - internal const val FUNCTION_NAME: String = "\$__current_user" - - override val signature: FunctionSignature = FunctionSignature( - name = FUNCTION_NAME, - requiredParameters = emptyList(), - returnType = StaticType.unionOf(StaticType.STRING, StaticType.NULL) - ) - - override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - return when (val user = session.context[EvaluationSession.Constants.CURRENT_USER_KEY]) { - is String -> ExprValue.newString(user) - null -> ExprValue.newNull(IonType.STRING) - else -> err( - message = "CURRENT_USER must be either a STRING or NULL.", - errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE, - errorContext = errorContextFrom(emptyMetaContainer()), - internal = false - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/CodepointExt.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/CodepointExt.kt deleted file mode 100644 index 734a063781..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/CodepointExt.kt +++ /dev/null @@ -1,126 +0,0 @@ -package org.partiql.lang.eval.internal.ext - -// String.codePoints() is from Java 9+ -@Suppress("Since15") -private fun String.toIntArray() = this.codePoints().toArray() - -// Default codepoints to remove -private val SPACE = intArrayOf(" ".codePointAt(0)) - -/** - * Removes the given string (" " by default) from both ends of sourceString - */ -internal fun codepointTrim(sourceString: String, toRemove: String? = null): String { - val codepoints = sourceString.toIntArray() - val codepointsToRemove = toRemove?.toIntArray() ?: SPACE - return codepoints.trim(codepointsToRemove) -} - -/** - * Removes the given string (" " by default) from the leading end of sourceString - */ -internal fun codepointLeadingTrim(sourceString: String, toRemove: String? = null): String { - val codepoints = sourceString.toIntArray() - val codepointsToRemove = toRemove?.toIntArray() ?: SPACE - return codepoints.leadingTrim(codepointsToRemove) -} - -/** - * Removes the given string (" " by default) from the trailing end of sourceString - */ -internal fun codepointTrailingTrim(sourceString: String, toRemove: String? = null): String { - val codepoints = sourceString.toIntArray() - val codepointsToRemove = toRemove?.toIntArray() ?: SPACE - return codepoints.trailingTrim(codepointsToRemove) -} - -/** - * Returns the first 1-indexed position of probe in sourceString; else 0 - */ -internal fun codepointPosition(sourceString: String, probe: String): Int { - if (probe.length > sourceString.length) return 0 - val codepoints = sourceString.toIntArray() - val codepointsToFind = probe.toIntArray() - return codepoints.positionOf(codepointsToFind) -} - -/** - * Replaces sourceString with overlay from 1-indexed position `startPosition` for up to `length` codepoints - */ -internal fun codepointOverlay(sourceString: String, overlay: String, position: Int, length: Int? = null): String { - if (sourceString.isEmpty()) return sourceString - val codepoints = sourceString.toIntArray() - val codepointsToOverlay = overlay.toIntArray() - return codepoints.overlay(codepointsToOverlay, position, length) -} - -internal fun IntArray.trim(toRemove: IntArray? = null): String { - val codepointsToRemove = toRemove ?: SPACE - val leadingOffset = leadingTrimOffset(this, codepointsToRemove) - val trailingOffset = trailingTrimOffSet(this, codepointsToRemove) - val length = Math.max(0, this.size - trailingOffset - leadingOffset) - return String(this, leadingOffset, length) -} - -internal fun IntArray.leadingTrim(toRemove: IntArray? = null): String { - val codepointsToRemove = toRemove ?: SPACE - val offset = leadingTrimOffset(this, codepointsToRemove) - return String(this, offset, this.size - offset) -} - -internal fun IntArray.trailingTrim(toRemove: IntArray? = null): String { - val codepointsToRemove = toRemove ?: SPACE - val offset = trailingTrimOffSet(this, codepointsToRemove) - return String(this, 0, this.size - offset) -} - -internal fun IntArray.leadingTrimOffset(codepoints: IntArray, toRemove: IntArray): Int { - var offset = 0 - while (offset < this.size && toRemove.contains(codepoints[offset])) offset += 1 - return offset -} - -internal fun IntArray.trailingTrimOffSet(codepoints: IntArray, toRemove: IntArray): Int { - var offset = 0 - while (offset < this.size && toRemove.contains(codepoints[this.size - offset - 1])) offset += 1 - return offset -} - -internal fun IntArray.positionOf(probe: IntArray): Int { - val extent = this.size - probe.size - if (extent < 0) return 0 - var start = 0 - window@ while (start <= extent) { - // check current window for equality - for (i in probe.indices) { - if (probe[i] != this[start + i]) { - start += 1 - continue@window - } - } - // nothing was not equal — everything was equal - return start + 1 - } - return 0 -} - -internal fun IntArray.overlay(overlay: IntArray, position: Int, length: Int? = null): String { - val len = (length ?: overlay.size) - val prefixLen = (position - 1).coerceAtMost(this.size) - val suffixLen = (this.size - (len + prefixLen)).coerceAtLeast(0) - val buffer = IntArray(prefixLen + overlay.size + suffixLen) - var i = 0 - // Fill prefix - for (j in 0 until prefixLen) { - buffer[i++] = this[j] - } - // Fill overlay - for (j in overlay.indices) { - buffer[i++] = overlay[j] - } - // Fill suffix - for (j in 0 until suffixLen) { - buffer[i++] = this[prefixLen + len + j] - } - return String(buffer, 0, buffer.size) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/ExprValueExt.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/ExprValueExt.kt deleted file mode 100644 index 64faff40e0..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/ExprValueExt.kt +++ /dev/null @@ -1,789 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.ext - -import com.amazon.ion.IntegerSize -import com.amazon.ion.IonInt -import com.amazon.ion.IonStruct -import com.amazon.ion.IonSystem -import com.amazon.ion.IonType -import com.amazon.ion.IonValue -import com.amazon.ion.Timestamp -import com.amazon.ion.system.IonSystemBuilder -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.eval.Addressed -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.Named -import org.partiql.lang.eval.NaturalExprValueComparators -import org.partiql.lang.eval.OrderedBindNames -import org.partiql.lang.eval.TypedOpBehavior -import org.partiql.lang.eval.internal.DateTimePart -import org.partiql.lang.eval.internal.NANOS_PER_SECOND -import org.partiql.lang.eval.internal.StructExprValue -import org.partiql.lang.eval.internal.err -import org.partiql.lang.eval.internal.errIntOverflow -import org.partiql.lang.eval.internal.errNoContext -import org.partiql.lang.eval.internal.errorContextFrom -import org.partiql.lang.eval.internal.fillErrorContext -import org.partiql.lang.eval.time.Time -import org.partiql.lang.types.StaticTypeUtils.getRuntimeType -import org.partiql.lang.util.ConfigurableExprValueFormatter -import org.partiql.lang.util.coerce -import org.partiql.lang.util.compareTo -import org.partiql.lang.util.downcast -import org.partiql.lang.util.isNaN -import org.partiql.lang.util.isNegInf -import org.partiql.lang.util.isPosInf -import org.partiql.types.BagType -import org.partiql.types.BlobType -import org.partiql.types.BoolType -import org.partiql.types.ClobType -import org.partiql.types.DateType -import org.partiql.types.DecimalType -import org.partiql.types.FloatType -import org.partiql.types.IntType -import org.partiql.types.ListType -import org.partiql.types.MissingType -import org.partiql.types.NullType -import org.partiql.types.NumberConstraint -import org.partiql.types.SexpType -import org.partiql.types.SingleType -import org.partiql.types.StringType -import org.partiql.types.SymbolType -import org.partiql.types.TimeType -import org.partiql.types.TimestampType -import java.math.BigDecimal -import java.math.MathContext -import java.math.RoundingMode -import java.time.LocalDate -import java.time.LocalTime -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeParseException -import java.util.TreeMap -import java.util.TreeSet -import kotlin.math.round - -internal const val MISSING_ANNOTATION = "\$missing" -internal const val BAG_ANNOTATION = "\$bag" -internal const val DATE_ANNOTATION = "\$date" -internal const val TIME_ANNOTATION = "\$time" -internal const val GRAPH_ANNOTATION = "\$graph" - -/** - * Wraps the given [ExprValue] with a delegate that provides the [OrderedBindNames] facet. - */ -internal fun ExprValue.orderedNamesValue(names: List): ExprValue = - object : ExprValue by this, OrderedBindNames { - override val orderedNames = names - override fun asFacet(type: Class?): T? = - downcast(type) ?: this@orderedNamesValue.asFacet(type) - override fun toString(): String = stringify() - } - -internal val ExprValue.orderedNames: List? - get() = asFacet(OrderedBindNames::class.java)?.orderedNames - -/** Wraps this [ExprValue] as a [Named] instance. */ -internal fun ExprValue.asNamed(): Named = object : Named { - override val name: ExprValue - get() = this@asNamed -} - -/** Binds the given name value as a [Named] facet delegate over this [ExprValue]. */ -internal fun ExprValue.namedValue(nameValue: ExprValue): ExprValue = object : ExprValue by this, Named { - override val name = nameValue - override fun asFacet(type: Class?): T? = - downcast(type) ?: this@namedValue.asFacet(type) - override fun toString(): String = stringify() -} - -/** Wraps this [ExprValue] in a delegate that always masks the [Named] facet. */ -internal fun ExprValue.unnamedValue(): ExprValue = when (asFacet(Named::class.java)) { - null -> this - else -> object : ExprValue by this { - override fun asFacet(type: Class?): T? = - when (type) { - // always mask the name facet - Named::class.java -> null - else -> this@unnamedValue.asFacet(type) - } - override fun toString(): String = stringify() - } -} - -internal val ExprValue.name: ExprValue? - get() = asFacet(Named::class.java)?.name - -internal val ExprValue.address: ExprValue? - get() = asFacet(Addressed::class.java)?.address - -internal fun ExprValue.booleanValue(): Boolean = - scalar.booleanValue() ?: errNoContext("Expected boolean: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -internal fun ExprValue.numberValue(): Number = - scalar.numberValue() ?: errNoContext("Expected number: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -internal fun ExprValue.dateValue(): LocalDate = - scalar.dateValue() ?: errNoContext("Expected date: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -internal fun ExprValue.timeValue(): Time = - scalar.timeValue() ?: errNoContext("Expected time: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -internal fun ExprValue.timestampValue(): Timestamp = - scalar.timestampValue() ?: errNoContext("Expected timestamp: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -internal fun ExprValue.stringValue(): String = - scalar.stringValue() ?: errNoContext("Expected string: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -internal fun ExprValue.bytesValue(): ByteArray = - scalar.bytesValue() ?: errNoContext("Expected byte array: $this", errorCode = ErrorCode.EVALUATOR_UNEXPECTED_VALUE_TYPE, internal = false) - -internal fun ExprValue.dateTimePartValue(): DateTimePart = - try { - DateTimePart.valueOf(this.stringValue().toUpperCase()) - } catch (e: IllegalArgumentException) { - throw EvaluationException( - cause = e, - message = "invalid datetime part, valid values: [${DateTimePart.values().joinToString()}]", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_DATE_PART, - internal = false - ) - } - -internal fun ExprValue.intValue(): Int = this.numberValue().toInt() - -internal fun ExprValue.longValue(): Long = this.numberValue().toLong() - -internal fun ExprValue.bigDecimalValue(): BigDecimal = this.numberValue().toString().toBigDecimal() - -/** - * Implements the `FROM` range operation. - * Specifically, this is distinct from the normal [ExprValue.iterator] in that - * types that are **not** [ExprValueType.isRangeFrom] get treated as a singleton - * as per PartiQL specification. - */ -fun ExprValue.rangeOver(): Iterable = when { - type.isRangedFrom -> this - // everything else ranges as a singleton unnamed value - else -> listOf(this.unnamedValue()) -} - -/** A very simple string representation--to be used for diagnostic purposes only. */ -internal fun ExprValue.stringify(): String = - ConfigurableExprValueFormatter.standard.format(this) - -internal val DEFAULT_COMPARATOR = NaturalExprValueComparators.NULLS_FIRST_ASC - -/** Provides the default equality function. */ -internal fun ExprValue.exprEquals(other: ExprValue): Boolean = DEFAULT_COMPARATOR.compare(this, other) == 0 - -/** - * Provides the comparison predicate--which is not a total ordering. - * - * In particular, this operation will fail for non-comparable types. - * For a total ordering over the PartiQL type space, see [NaturalExprValueComparators] - */ -internal operator fun ExprValue.compareTo(other: ExprValue): Int { - return when { - type.isUnknown || other.type.isUnknown -> - throw EvaluationException("Null value cannot be compared: $this, $other", errorCode = ErrorCode.EVALUATOR_INVALID_COMPARISION, internal = false) - isDirectlyComparableTo(other) -> DEFAULT_COMPARATOR.compare(this, other) - else -> errNoContext("Cannot compare values: $this, $other", errorCode = ErrorCode.EVALUATOR_INVALID_COMPARISION, internal = false) - } -} - -/** - * Checks if the two ExprValues are directly comparable. - * Directly comparable is used in the context of the `<`/`<=`/`>`/`>=` operators. - */ -internal fun ExprValue.isDirectlyComparableTo(other: ExprValue): Boolean = - when { - // The ExprValue type for TIME and TIME WITH TIME ZONE is same - // and thus needs to be checked explicitly for the timezone values. - type == ExprValueType.TIME && other.type == ExprValueType.TIME -> - timeValue().isDirectlyComparableTo(other.timeValue()) - else -> type.isDirectlyComparableTo(other.type) - } - -/** Types that are cast to the [ExprValueType.isText] types by calling `IonValue.toString()`. */ -private val ION_TEXT_STRING_CAST_TYPES = setOf(ExprValueType.BOOL, ExprValueType.TIMESTAMP) - -/** Regex to match DATE strings of the format yyyy-MM-dd */ -private val datePatternRegex = Regex("\\d\\d\\d\\d-\\d\\d-\\d\\d") - -/** - * Casts this [ExprValue] to the target type. - * - * `MISSING` and `NULL` always convert to themselves no matter the target type. When the - * source type and target type are the same, this operation is a no-op. - * - * The conversion *to* a particular type is as follows, any conversion not specified raises - * an [EvaluationException]: - * - * * `BOOL` - * * Number types will convert to `false` if numerically equal to zero, `true` otherwise. - * * Text types will convert to `true` if case-insensitive text is `"true"`, - * convert to `false` if case-insensitive text is `"true"` and throw an error otherwise. - * * `INT`, `FLOAT`, and `DECIMAL` - * * `BOOL` converts as `1` for `true` and `0` for `false` - * * Number types will narrow or widen from the source type. Narrowing is a truncation - * * Text types will convert using base-10 integral notation - * * For `FLOAT` and `DECIMAL` targets, decimal and e-notation is also supported. - * * `TIMESTAMP` - * * Text types will convert using the Ion text notation for timestamp (W3C/ISO-8601). - * * `DATE` - * * `TIMESTAMP` converts as `DATE` throwing away the additional information such as time. - * * Text types converts as `DATE` if the case-insensitive text is a valid ISO 8601 format date string. - * * `TIME` - * * `TIMESTAMP` converts as `TIME` throwing away the additional information such as date and time zone. - * * Text types converts as `TIME` if the case-insensitive text is a valid ISO 8601 format time string. - * * `TIME` and `TIME WITH TIME ZONE` converts as `TIME` throwing away the time zone information. - * * `TIME WITH TIME ZONE` - * * `TIMESTAMP` converts as `TIME WITH TIME ZONE` only if the timezone is defined in the TIMESTAMP value. - * The conversion throws away the additional information such as date. - * * Text types converts as `TIME WITH TIME ZONE` if the case-insensitive text is a valid ISO 8601 format time string. - * If the time zone is not specified, then the default time zone is used. - * * `TIME` and `TIME WITH TIME ZONE` converts as `TIME WITH TIME ZONE`. - * If the time zone is not specified, then the default time zone is used. - * * `STRING` and `SYMBOL` - * * `BOOL` converts to `STRING` as `"true"` and `"false"`; - * converts to `SYMBOL` as `'true'` and `'false'`. - * * Number types convert to decimal form with optional e-notation. - * * `TIMESTAMP` converts to the ISO-8601 format. - * * `BLOB` and `CLOB` can only convert between each other directly. - * * `LIST` and `SEXP` - * * Convert directly between each other. - * * `BAG` converts with an *arbitrary* order. - * * `STRUCT` only supports casting from itself. - * * `BAG` converts from `LIST` and `SEXP` by drops order guarantees. - * - * Note that *text types* is defined by [ExprValueType.isText], *number types* is defined by - * [ExprValueType.isNumber], and *LOB types* is defined by [ExprValueType.isLob] - * - * @param targetType The target type to cast this value to. - * @param valueFactory The ExprValueFactory used to create ExprValues. - * @param typedOpBehavior TypedOpBehavior indicating how CAST should behave. - * @param locationMeta The source location for the CAST. Used for error reporting. - * @param defaultTimezoneOffset Default timezone offset to be used when TIME WITH TIME ZONE does not explicitly - * specify the time zone. - */ -internal fun ExprValue.cast( - targetType: SingleType, - typedOpBehavior: TypedOpBehavior, - locationMeta: SourceLocationMeta?, - defaultTimezoneOffset: ZoneOffset -): ExprValue { - fun castExceptionContext(): PropertyValueMap { - val errorContext = PropertyValueMap().also { - it[Property.CAST_FROM] = this.type.toString() - it[Property.CAST_TO] = getRuntimeType(targetType).toString() - } - - locationMeta?.let { fillErrorContext(errorContext, it) } - - return errorContext - } - - fun castFailedErr(message: String, internal: Boolean, cause: Throwable? = null): Nothing { - val errorContext = castExceptionContext() - - val errorCode = if (locationMeta == null) { - ErrorCode.EVALUATOR_CAST_FAILED_NO_LOCATION - } else { - ErrorCode.EVALUATOR_CAST_FAILED - } - - throw EvaluationException( - message = message, - errorCode = errorCode, - errorContext = errorContext, - internal = internal, - cause = cause - ) - } - - val longMaxDecimal = bigDecimalOf(Long.MAX_VALUE) - val longMinDecimal = bigDecimalOf(Long.MIN_VALUE) - - fun Number.exprValue(type: SingleType) = when (type) { - is IntType -> { - // If the source is Positive/Negative Infinity or Nan, We throw an error - if (this.isNaN || this.isNegInf || this.isPosInf) { - castFailedErr("Can't convert Infinity or NaN to INT.", internal = false) - } - - val rangeForType = when (typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> - when (type.rangeConstraint) { - // There is not CAST syntax to that can execute this branch today. - IntType.IntRangeConstraint.SHORT -> LongRange(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()) - IntType.IntRangeConstraint.INT4 -> LongRange(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()) - IntType.IntRangeConstraint.LONG, IntType.IntRangeConstraint.UNCONSTRAINED -> - LongRange(Long.MIN_VALUE, Long.MAX_VALUE) - } - } - - // Here, we check if there is a possibility of being able to fit this number into - // any of the integer types. We allow the buffer of 1 because we allow rounding into min/max values. - if (this <= (longMinDecimal - BigDecimal.ONE) || this >= (longMaxDecimal + BigDecimal.ONE)) { - errIntOverflow(8) - } - - // We round the value to the nearest integral value - // In legacy behavior, this always picks the floor integer value - // Else, rounding is done through https://en.wikipedia.org/wiki/Rounding#Round_half_to_even - // We don't convert the result to Long within the when block here - // because the rounded values can still be out of range for Kotlin's Long. - val result = when (typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> when (this) { - is BigDecimal -> this.setScale(0, RoundingMode.HALF_EVEN) - // [kotlin.math.round] rounds towards the closes even number on tie - // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.math/round.html - is Float -> round(this) - is Double -> round(this) - else -> this - } - }.let { - // after rounding, check that the value can fit into range of the type being casted into - if (it < rangeForType.first || it > rangeForType.last) { - errIntOverflow(8) - } - it.toLong() - } - ExprValue.newInt(result) - } - is FloatType -> ExprValue.newFloat(this.toDouble()) - is DecimalType -> { - if (this.isNaN || this.isNegInf || this.isPosInf) { - castFailedErr("Can't convert Infinity or NaN to DECIMAL.", internal = false) - } - - when (typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> - when (val constraint = type.precisionScaleConstraint) { - DecimalType.PrecisionScaleConstraint.Unconstrained -> ExprValue.newDecimal(this.coerce(BigDecimal::class.java)) - is DecimalType.PrecisionScaleConstraint.Constrained -> { - val decimal = this.coerce(BigDecimal::class.java) - val result = decimal.round(MathContext(constraint.precision)) - .setScale(constraint.scale, RoundingMode.HALF_UP) - if (result.precision() > constraint.precision) { - // Following PostgresSQL behavior here. Java will increase precision if needed. - castFailedErr("target type DECIMAL(${constraint.precision}, ${constraint.scale}) too small for value $decimal.", internal = false) - } else { - ExprValue.newDecimal(result) - } - } - } - } - } - else -> castFailedErr("Invalid type for numeric conversion: $type (this code should be unreachable)", internal = true) - } - - fun String.exprValue(type: SingleType) = when (type) { - is StringType -> when (typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> when (val constraint = type.lengthConstraint) { - StringType.StringLengthConstraint.Unconstrained -> ExprValue.newString(this) - is StringType.StringLengthConstraint.Constrained -> { - val actualCodepointCount = this.codePointCount(0, this.length) - val lengthConstraint = constraint.length.value - val truncatedString = if (actualCodepointCount <= lengthConstraint) { - this // no truncation needed - } else { - this.substring(0, this.offsetByCodePoints(0, lengthConstraint)) - } - - ExprValue.newString( - when (constraint.length) { - is NumberConstraint.Equals -> truncatedString.trimEnd { c -> c == '\u0020' } - is NumberConstraint.UpTo -> truncatedString - } - ) - } - } - } - is SymbolType -> ExprValue.newSymbol(this) - - else -> castFailedErr("Invalid type for textual conversion: $type (this code should be unreachable)", internal = true) - } - - when { - type.isUnknown && targetType is MissingType -> return ExprValue.missingValue - type.isUnknown && targetType is NullType -> return ExprValue.nullValue - type.isUnknown -> return this - // Note that the ExprValueType for TIME and TIME WITH TIME ZONE is the same i.e. ExprValueType.TIME. - // We further need to check for the time zone and hence we do not short circuit here when the type is TIME. - type == getRuntimeType(targetType) && type != ExprValueType.TIME -> { - return when (targetType) { - is IntType, is FloatType, is DecimalType -> numberValue().exprValue(targetType) - is StringType -> stringValue().exprValue(targetType) - else -> this - } - } - else -> { - when (targetType) { - is BoolType -> when { - type.isNumber -> return when { - numberValue().compareTo(0L) == 0 -> ExprValue.newBoolean(false) - else -> ExprValue.newBoolean(true) - } - type.isText -> return when (stringValue().lowercase()) { - "true" -> ExprValue.newBoolean(true) - "false" -> ExprValue.newBoolean(false) - else -> castFailedErr("can't convert string value to BOOL", internal = false) - } - } - is IntType -> when { - type == ExprValueType.BOOL -> return if (booleanValue()) 1L.exprValue(targetType) else 0L.exprValue(targetType) - type.isNumber -> return numberValue().exprValue(targetType) - type.isText -> { - // Here, we use ion java library to help the transform from string to int - // TODO: have our own parser implemented and remove dependency on Ion, https://github.com/partiql/partiql-lang-kotlin/issues/956 - fun parseToLong(s: String): Long { - val ion = IonSystemBuilder.standard().build() - val value = try { - val normalized = s.normalizeForCastToInt() - ion.singleValue(normalized) as IonInt - } catch (e: Exception) { - castFailedErr("can't convert string value to INT", internal = false, cause = e) - } - return when (value.integerSize) { - // Our numbers comparison machinery does not handle big integers yet, fail fast - IntegerSize.BIG_INTEGER -> errIntOverflow(8, errorContextFrom(locationMeta)) - else -> value.longValue() - } - } - - return parseToLong(stringValue()).exprValue(targetType) - } - } - is FloatType -> when { - type == ExprValueType.BOOL -> return if (booleanValue()) 1.0.exprValue(targetType) else 0.0.exprValue(targetType) - type.isNumber -> return numberValue().toDouble().exprValue(targetType) - type.isText -> - try { - return stringValue().toDouble().exprValue(targetType) - } catch (e: NumberFormatException) { - castFailedErr("can't convert string value to FLOAT", internal = false, cause = e) - } - } - is DecimalType -> when { - type == ExprValueType.BOOL -> return if (booleanValue()) { - BigDecimal.ONE.exprValue(targetType) - } else { - BigDecimal.ZERO.exprValue(targetType) - } - type.isNumber -> return numberValue().exprValue(targetType) - type.isText -> try { - return bigDecimalOf(stringValue()).exprValue(targetType) - } catch (e: NumberFormatException) { - castFailedErr("can't convert string value to DECIMAL", internal = false, cause = e) - } - } - is TimestampType -> when { - type.isText -> try { - return ExprValue.newTimestamp(Timestamp.valueOf(stringValue())) - } catch (e: IllegalArgumentException) { - castFailedErr("can't convert string value to TIMESTAMP", internal = false, cause = e) - } - } - is DateType -> when { - type == ExprValueType.TIMESTAMP -> { - val ts = timestampValue() - return ExprValue.newDate(LocalDate.of(ts.year, ts.month, ts.day)) - } - type.isText -> try { - // validate that the date string follows the format YYYY-MM-DD - if (!datePatternRegex.matches(stringValue())) { - castFailedErr( - "Can't convert string value to DATE. Expected valid date string " + - "and the date format to be YYYY-MM-DD", - internal = false - ) - } - val date = LocalDate.parse(stringValue()) - return ExprValue.newDate(date) - } catch (e: DateTimeParseException) { - castFailedErr( - "Can't convert string value to DATE. Expected valid date string " + - "and the date format to be YYYY-MM-DD", - internal = false, cause = e - ) - } - } - is TimeType -> { - val precision = targetType.precision - when { - type == ExprValueType.TIME -> { - val time = timeValue() - val timeZoneOffset = when (targetType.withTimeZone) { - true -> time.zoneOffset ?: defaultTimezoneOffset - else -> null - } - return ExprValue.newTime( - Time.of( - time.localTime, - precision ?: time.precision, - timeZoneOffset - ) - ) - } - type == ExprValueType.TIMESTAMP -> { - val ts = timestampValue() - val timeZoneOffset = when (targetType.withTimeZone) { - true -> ts.localOffset ?: castFailedErr( - "Can't convert timestamp value with unknown local offset (i.e. -00:00) to TIME WITH TIME ZONE.", - internal = false - ) - else -> null - } - return ExprValue.newTime( - Time.of( - ts.hour, - ts.minute, - ts.second, - (ts.decimalSecond.remainder(BigDecimal.ONE).multiply(NANOS_PER_SECOND.toBigDecimal())).toInt(), - precision ?: ts.decimalSecond.scale(), - timeZoneOffset - ) - ) - } - type.isText -> try { - // validate that the time string follows the format HH:MM:SS[.ddddd...][+|-HH:MM] - val matcher = genericTimeRegex.toPattern().matcher(stringValue()) - if (!matcher.find()) { - castFailedErr( - "Can't convert string value to TIME. Expected valid time string " + - "and the time to be of the format HH:MM:SS[.ddddd...][+|-HH:MM]", - internal = false - ) - } - - val localTime = LocalTime.parse(stringValue(), DateTimeFormatter.ISO_TIME) - - // Note that the [genericTimeRegex] has a group to extract the zone offset. - val zoneOffsetString = matcher.group(2) - val zoneOffset = zoneOffsetString?.let { ZoneOffset.of(it) } ?: defaultTimezoneOffset - - return ExprValue.newTime( - Time.of( - localTime, - precision ?: getPrecisionFromTimeString(stringValue()), - when (targetType.withTimeZone) { - true -> zoneOffset - else -> null - } - ) - ) - } catch (e: DateTimeParseException) { - castFailedErr( - "Can't convert string value to TIME. Expected valid time string " + - "and the time format to be HH:MM:SS[.ddddd...][+|-HH:MM]", - internal = false, cause = e - ) - } - } - } - is StringType, is SymbolType -> when { - type.isNumber -> return numberValue().toString().exprValue(targetType) - type.isText -> return stringValue().exprValue(targetType) - type == ExprValueType.DATE -> return dateValue().toString().exprValue(targetType) - type == ExprValueType.TIME -> return timeValue().toString().exprValue(targetType) - type == ExprValueType.BOOL -> return booleanValue().toString().exprValue(targetType) - type == ExprValueType.TIMESTAMP -> return timestampValue().toString().exprValue(targetType) - } - is ClobType -> when { - type.isLob -> return ExprValue.newClob(bytesValue()) - } - is BlobType -> when { - type.isLob -> return ExprValue.newBlob(bytesValue()) - } - is ListType -> if (type.isSequence) return ExprValue.newList(asSequence()) - is SexpType -> if (type.isSequence) return ExprValue.newSexp(asSequence()) - is BagType -> if (type.isSequence) return ExprValue.newBag(asSequence()) - // no support for anything else - else -> {} - } - } - } - - val errorCode = if (locationMeta == null) { - ErrorCode.EVALUATOR_INVALID_CAST_NO_LOCATION - } else { - ErrorCode.EVALUATOR_INVALID_CAST - } - - // incompatible types - err("Cannot convert $type to $targetType", errorCode, castExceptionContext(), internal = false) -} -/** - * Remove leading spaces in decimal notation and the plus sign - * - * Examples: - * - `"00001".normalizeForIntCast() == "1"` - * - `"-00001".normalizeForIntCast() == "-1"` - * - `"0x00001".normalizeForIntCast() == "0x00001"` - * - `"+0x00001".normalizeForIntCast() == "0x00001"` - * - `"000a".normalizeForIntCast() == "a"` - */ -private fun String.normalizeForCastToInt(): String { - fun Char.isSign() = this == '-' || this == '+' - fun Char.isHexOrBase2Marker(): Boolean { - val c = this.lowercaseChar() - - return c == 'x' || c == 'b' - } - - fun String.possiblyHexOrBase2() = (length >= 2 && this[1].isHexOrBase2Marker()) || - (length >= 3 && this[0].isSign() && this[2].isHexOrBase2Marker()) - - return when { - length == 0 -> this - possiblyHexOrBase2() -> { - if (this[0] == '+') { - this.drop(1) - } else { - this - } - } - else -> { - val (isNegative, startIndex) = when (this[0]) { - '-' -> Pair(true, 1) - '+' -> Pair(false, 1) - else -> Pair(false, 0) - } - - var toDrop = startIndex - while (toDrop < length && this[toDrop] == '0') { - toDrop += 1 - } - - when { - toDrop == length -> "0" // string is all zeros - toDrop == 0 -> this - toDrop == 1 && isNegative -> this - toDrop > 1 && isNegative -> '-' + this.drop(toDrop) - else -> this.drop(toDrop) - } - } - } -} - -/** - * An Unknown value is one of `MISSING` or `NULL` - */ -internal fun ExprValue.isUnknown(): Boolean = this.type.isUnknown -/** - * The opposite of [isUnknown]. - */ -internal fun ExprValue.isNotUnknown(): Boolean = !this.type.isUnknown - -/** - * Creates a filter for unique ExprValues consistent with exprEquals. This filter is stateful keeping track of - * seen [ExprValue]s. - * - * This filter is **stateful**! - * - * @return false if the value was seen before - */ -internal fun createUniqueExprValueFilter(): (ExprValue) -> Boolean { - val seen = TreeSet(DEFAULT_COMPARATOR) - - return { exprValue -> seen.add(exprValue) } -} - -fun Sequence.distinct(): Sequence { - return sequence { - val seen = TreeSet(DEFAULT_COMPARATOR) - this@distinct.forEach { - if (!seen.contains(it)) { - seen.add(it.unnamedValue()) - yield(it) - } - } - } -} - -internal fun Sequence.multiplicities(): TreeMap { - val multiplicities: TreeMap = TreeMap(DEFAULT_COMPARATOR) - this.forEach { - multiplicities.compute(it) { _, v -> (v ?: 0) + 1 } - } - return multiplicities -} - -/** - * This method should only be used in case we want to get result from querying an Ion file or an [IonValue] - */ -internal fun ExprValue.toIonValue(ion: IonSystem): IonValue = - when (type) { - ExprValueType.NULL -> ion.newNull(asFacet(IonType::class.java)) - ExprValueType.MISSING -> ion.newNull().apply { addTypeAnnotation(MISSING_ANNOTATION) } - ExprValueType.BOOL -> ion.newBool(booleanValue()) - ExprValueType.INT -> ion.newInt(longValue()) - ExprValueType.FLOAT -> ion.newFloat(numberValue().toDouble()) - ExprValueType.DECIMAL -> ion.newDecimal(bigDecimalValue()) - ExprValueType.DATE -> { - val value = dateValue() - ion.newTimestamp(Timestamp.forDay(value.year, value.monthValue, value.dayOfMonth)).apply { - addTypeAnnotation(DATE_ANNOTATION) - } - } - ExprValueType.TIMESTAMP -> ion.newTimestamp(timestampValue()) - ExprValueType.TIME -> timeValue().toIonValue(ion) - ExprValueType.SYMBOL -> ion.newSymbol(stringValue()) - ExprValueType.STRING -> ion.newString(stringValue()) - ExprValueType.CLOB -> ion.newClob(bytesValue()) - ExprValueType.BLOB -> ion.newBlob(bytesValue()) - ExprValueType.LIST -> mapTo(ion.newEmptyList()) { - if (it is StructExprValue) - it.toIonStruct(ion) - else - it.toIonValue(ion).clone() - } - ExprValueType.SEXP -> mapTo(ion.newEmptySexp()) { - if (it is StructExprValue) - it.toIonStruct(ion) - else - it.toIonValue(ion).clone() - } - ExprValueType.BAG -> mapTo( - ion.newEmptyList().apply { addTypeAnnotation(BAG_ANNOTATION) } - ) { - if (it is StructExprValue) - it.toIonStruct(ion) - else - it.toIonValue(ion).clone() - } - ExprValueType.STRUCT -> toIonStruct(ion) - ExprValueType.GRAPH -> TODO("Ion representation for graph values, maybe?") - } - -private fun ExprValue.toIonStruct(ion: IonSystem): IonStruct { - return ion.newEmptyStruct().apply { - this@toIonStruct.forEach { - val nameVal = it.name - if (nameVal != null && nameVal.type.isText && it.type != ExprValueType.MISSING) { - val name = nameVal.stringValue() - add(name, it.toIonValue(ion).clone()) - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/NumberExt.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/NumberExt.kt deleted file mode 100644 index 3e832e3e34..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/NumberExt.kt +++ /dev/null @@ -1,601 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.ext - -import com.amazon.ion.Decimal -import com.amazon.ion.IonSystem -import com.amazon.ion.IonValue -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.internal.errIntOverflow -import org.partiql.lang.eval.internal.errNoContext -import java.math.BigDecimal -import java.math.BigInteger -import java.math.MathContext -import java.math.RoundingMode - -// TODO should this be configurable? -private val MATH_CONTEXT = MathContext(38, RoundingMode.HALF_EVEN) - -/** - * Factory function to create a [BigDecimal] using correct precision, use it in favor of native BigDecimal constructors - * and factory methods - */ -internal fun bigDecimalOf(num: Number, mc: MathContext = MATH_CONTEXT): BigDecimal = when (num) { - is Decimal -> num - is Int -> BigDecimal(num, mc) - is Long -> BigDecimal(num, mc) - is Double -> BigDecimal(num, mc) - is BigDecimal -> num - Decimal.NEGATIVE_ZERO -> num as Decimal - else -> throw IllegalArgumentException("Unsupported number type: $num, ${num.javaClass}") -} - -internal fun bigDecimalOf(text: String, mc: MathContext = MATH_CONTEXT): BigDecimal = BigDecimal(text.trim(), mc) - -private val CONVERSION_MAP = mapOf>, Class>( - setOf(Long::class.javaObjectType, Long::class.javaObjectType) to Long::class.javaObjectType, - setOf(Long::class.javaObjectType, Double::class.javaObjectType) to Double::class.javaObjectType, - setOf(Long::class.javaObjectType, BigDecimal::class.javaObjectType) to BigDecimal::class.javaObjectType, - - setOf(Double::class.javaObjectType, Double::class.javaObjectType) to Double::class.javaObjectType, - setOf(Double::class.javaObjectType, BigDecimal::class.javaObjectType) to BigDecimal::class.javaObjectType, - - setOf(BigDecimal::class.javaObjectType, BigDecimal::class.javaObjectType) to BigDecimal::class.javaObjectType -) - -private val CONVERTERS = mapOf, (Number) -> Number>( - Long::class.javaObjectType to Number::toLong, - Double::class.javaObjectType to Number::toDouble, - BigDecimal::class.java to { num -> - when (num) { - is Long -> bigDecimalOf(num) - is Double -> bigDecimalOf(num) - is BigDecimal -> bigDecimalOf(num) - else -> throw IllegalArgumentException( - "Unsupported number for decimal conversion: $num" - ) - } - } -) - -internal fun Number.isZero() = when (this) { - // using compareTo instead of equals for BigDecimal because equality also checks same scale - - is Long -> this == 0L - is Double -> this == 0.0 || this == -0.0 - is BigDecimal -> BigDecimal.ZERO.compareTo(this) == 0 - else -> throw IllegalStateException() -} - -@Suppress("UNCHECKED_CAST") -/** Provides a narrowing or widening operator on supported numbers. */ -internal fun Number.coerce(type: Class): T where T : Number { - val conv = CONVERTERS[type] ?: throw IllegalArgumentException("No converter for $type") - return conv(this) as T -} - -/** - * Implements a very simple number tower to convert two numbers to their arithmetic - * compatible type. - * - * This is only supported on limited types needed by the expression system. - */ -internal fun coerceNumbers(first: Number, second: Number): Pair { - fun typeFor(n: Number): Class<*> = if (n is Decimal) { - BigDecimal::class.javaObjectType - } else { - n.javaClass - } - - val type = CONVERSION_MAP[setOf(typeFor(first), typeFor(second))] - ?: throw IllegalArgumentException("No coercion support for ${typeFor(first)} to ${typeFor(second)}") - - return Pair(first.coerce(type), second.coerce(type)) -} - -internal fun Number.ionValue(ion: IonSystem): IonValue = when (this) { - is Long -> ion.newInt(this) - is BigInteger -> ion.newInt(this) - is Double -> ion.newFloat(this) - is BigDecimal -> ion.newDecimal(this) - else -> throw IllegalArgumentException("Cannot convert to IonValue: $this") -} - -internal fun Number.exprValue(): ExprValue = when (this) { - is Int -> ExprValue.newInt(this) - is Long -> ExprValue.newInt(this) - is Double -> ExprValue.newFloat(this) - is BigDecimal -> ExprValue.newDecimal(this) - else -> errNoContext( - "Cannot convert number to expression value: $this", - errorCode = ErrorCode.EVALUATOR_INVALID_CONVERSION, - internal = true - ) -} - -internal operator fun Decimal.unaryMinus(): Decimal = when { - isZero() -> Decimal.negativeZero(this.scale()) - else -> Decimal.valueOf(negate()) -} - -internal operator fun Number.unaryMinus(): Number { - return when (this) { - // - LONG.MIN_VALUE will result in LONG.MIN_VALUE in JVM because LONG is a signed two's-complement integers - is Long -> if (this == Long.MIN_VALUE) BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE) else -this - is BigInteger -> this.negate() - is Double -> -this - is BigDecimal -> if (this.isZero()) { - Decimal.negativeZero(this.scale()) - } else { - this.negate() - } - else -> throw IllegalStateException() - } -} - -private fun Long.checkOverflowPlus(other: Long): Number { - // uses to XOR to check if - // this and other are >= 0 then if result < 0 means overflow - // this and other are < 0 then if result > 0 means underflow - // if this and other have different signs then no overflow can happen - - val result: Long = this + other - val overflows = ((this xor other) >= 0) and ((this xor result) < 0) - return when (overflows) { - false -> result - else -> errIntOverflow(8) - } -} - -private fun Long.checkOverflowMinus(other: Long): Number { - // uses XOR for a similar logic than plus - - val result: Long = this - other - val overflows = ((this xor other) < 0) and ((this xor result) < 0) - return when (overflows) { - false -> result - else -> errIntOverflow(8) - } -} - -private fun Long.checkOverflowTimes(other: Long): Number { - fun Long.numberOfLeadingZeros() = java.lang.Long.numberOfLeadingZeros(this) - - // Hacker's Delight, Section 2-12 - - val leadingZeros = this.numberOfLeadingZeros() + - this.inv().numberOfLeadingZeros() + - other.numberOfLeadingZeros() + - other.inv().numberOfLeadingZeros() - - val result = this * other - val longSize = java.lang.Long.SIZE - - if ((leadingZeros >= longSize) && - ((this >= 0) or (other != Long.MIN_VALUE)) && - (this == 0L || result / this == other) - ) { - return result - } - - errIntOverflow(8) -} - -private fun Long.checkOverflowDivision(other: Long): Number { - // division can only underflow Long.MIN_VALUE / -1 - // because abs(Long.MIN_VALUE) == abs(Long.MAX_VALUE) + 1 - if (this == Long.MIN_VALUE && other == -1L) { - errIntOverflow(8) - } - - return this / other -} - -internal operator fun Number.plus(other: Number): Number { - val (first, second) = coerceNumbers(this, other) - return when (first) { - is Long -> first.checkOverflowPlus(second as Long) - is Double -> first + second as Double - is BigDecimal -> first.add(second as BigDecimal, MATH_CONTEXT) - else -> throw IllegalStateException() - } -} - -internal operator fun Number.minus(other: Number): Number { - val (first, second) = coerceNumbers(this, other) - return when (first) { - is Long -> first.checkOverflowMinus(second as Long) - is Double -> first - second as Double - is BigDecimal -> first.subtract(second as BigDecimal, MATH_CONTEXT) - else -> throw IllegalStateException() - } -} - -internal operator fun Number.times(other: Number): Number { - val (first, second) = coerceNumbers(this, other) - return when (first) { - is Long -> first.checkOverflowTimes(second as Long) - is Double -> first * second as Double - is BigDecimal -> first.multiply(second as BigDecimal, MATH_CONTEXT) - else -> throw IllegalStateException() - } -} - -internal operator fun Number.div(other: Number): Number { - val (first, second) = coerceNumbers(this, other) - return when (first) { - is Long -> first.checkOverflowDivision(second as Long) - is Double -> first / second as Double - is BigDecimal -> first.divide(second as BigDecimal, MATH_CONTEXT) - else -> throw IllegalStateException() - } -} - -internal operator fun Number.rem(other: Number): Number { - val (first, second) = coerceNumbers(this, other) - return when (first) { - is Long -> first % second as Long - is Double -> first % second as Double - is BigDecimal -> first.remainder(second as BigDecimal, MATH_CONTEXT) - else -> throw IllegalStateException() - } -} - -internal operator fun Number.compareTo(other: Number): Int { - val (first, second) = coerceNumbers(this, other) - return when (first) { - is Long -> first.compareTo(second as Long) - is Double -> first.compareTo(second as Double) - is BigDecimal -> first.compareTo(second as BigDecimal) - else -> throw IllegalStateException() - } -} - -internal val Number.isNaN - get() = when (this) { - is Double -> isNaN() - else -> false - } - -internal val Number.isNegInf - get() = when (this) { - is Double -> isInfinite() && this < 0 - else -> false - } - -internal val Number.isPosInf - get() = when (this) { - is Double -> isInfinite() && this > 0 - else -> false - } - -/** - * Returns the given BigDecimal with precision equals to mathContext.precision. - * - * This is for formatting purpose, all the digit that we are supposedly saying is correct will be shown. - */ -private fun BigDecimal.roundToDigits(mathContext: MathContext): BigDecimal { - val stripped = this.stripTrailingZeros() - val scale = stripped.scale() - stripped.precision() + 1 - val mantissa = stripped.scaleByPowerOfTen(scale) - return if (mantissa.precision() != mathContext.precision) { - mantissa.round(mathContext).setScale(mathContext.precision - 1).scaleByPowerOfTen(-scale) - } else { - stripped.round(mathContext) - } -} - -/** - * Computes the nth root of a given BigDecimal x. - * where x needs to be positive integer. - */ -private fun BigDecimal.intRoot( - root: Int, - mathContext: MathContext -): BigDecimal { - if (this.signum() < 0) { - throw ArithmeticException("Cannot take root of a negative number") - } - - val operationMC = MathContext( - if (mathContext.precision + 2 < 0) Int.MAX_VALUE else mathContext.precision + 2, - mathContext.roundingMode - ) - - val tolerance: BigDecimal = BigDecimal.valueOf(5L).movePointLeft(mathContext.precision + 1) - - // using Newton's method - // x_i = ( (n-1) (x_i-1) ^ n + a) / n (x_i-1) ^(n-1) - // where a is the number whose nth root we want to compute - val n = BigDecimal.valueOf(root.toLong()) - val nMinusOne = BigDecimal.valueOf(root.toLong() - 1L) - - // The initial approximation is x/n. - var res = this.divide(n, mathContext) - - var resPrev: BigDecimal - do { - // x^(n-1) - val xToNMinusOne = res.pow(root - 1, operationMC) - // x^n - val xToN = res.multiply(xToNMinusOne, operationMC) - - // n + (n-1)*(x^n) - val numerator = this.add(nMinusOne.multiply(xToN, operationMC), operationMC) - - // (n*(x^(n-1)) - val denominator = n.multiply(xToNMinusOne, operationMC) - - // x = (n + (n-1)*(x^n)) / (n*(x^(n-1))) - resPrev = res - - res = numerator.divide(denominator, operationMC) - } while (res.round(mathContext).subtract(resPrev.round(mathContext)).abs() > tolerance) - return res -} - -/** - * Computes the square root of a given BigDecimal. - * See https://dl.acm.org/doi/pdf/10.1145/214408.214413 - */ -internal fun BigDecimal.squareRoot(mathContext: MathContext = MATH_CONTEXT): BigDecimal { - if (this.signum() < 0) { - throw ArithmeticException("Cannot take root of a negative number") - } - - // Special case: - if (this.signum() == 0) { - return BigDecimal.ZERO.roundToDigits(mathContext) - } - - // We want to utilize the floating number's sqrt method to take an educated guess - // to make sure the number is representable - // we operate on normalized mantissa, which is [0.1, 10) - val stripped = this.stripTrailingZeros() - val scale = stripped.scale() - stripped.precision() + 1 - val scaleAdj = if (scale % 2 == 0) scale else scale - 1 - val mantissa = stripped.scaleByPowerOfTen(scaleAdj) - - val guess = BigDecimal.valueOf(kotlin.math.sqrt(mantissa.toDouble())) - - // Conservative guess of the precision of the result of a floating point calculation. - var guessPrecision = 10 - - // we need this additional logic in case of overflow - val targetPrecision = mathContext.precision - val normalizedPrecision = mantissa.precision() - var approx = guess - - val zeroPointFive = BigDecimal.ONE.divide(BigDecimal.valueOf(2)) - do { - // plus 2 for precision buffering - val operatingPrecision = kotlin.math.max( - kotlin.math.max(guessPrecision, targetPrecision + 2), - normalizedPrecision - ) - val tempMC = MathContext(operatingPrecision, RoundingMode.HALF_EVEN) - approx = zeroPointFive.multiply(approx.add(mantissa.divide(approx, tempMC), tempMC)) - // the magic number here is 2p + 2, consider precision(x*x) is maxed at precision(x) + precision(x) - guessPrecision = 2 * guessPrecision + 2 - } while (guessPrecision < targetPrecision + 2) - - // scale modification - val unModifiedRes = approx.scaleByPowerOfTen(-scaleAdj / 2).round(mathContext) - - return unModifiedRes.roundToDigits(mathContext) -} - -/** - * Computes e^x of a given BigDecimal x. - */ -internal fun BigDecimal.exp(mathContext: MathContext = MATH_CONTEXT): BigDecimal { - val operationMC = MathContext( - if (10 + mathContext.precision < 0) Int.MAX_VALUE else 10 + mathContext.precision, - mathContext.roundingMode - ) - return if (this.signum() == 0) { - BigDecimal.ONE.roundToDigits(mathContext) - } else if (this.signum() == -1) { - val reciprocal = this.negate().expHelper(operationMC) - BigDecimal.valueOf(1) - .divide( - reciprocal, - operationMC - ).roundToDigits(mathContext) - } else { - this.expHelper(operationMC).roundToDigits(mathContext) - } -} - -/** - * Computes the exponential value of a BigDecimal. - */ -private fun BigDecimal.expHelper(mathContext: MathContext): BigDecimal { - // For faster convergence, we break exponent into integer and fraction parts. - // e^x = e^(i+f) = (e^(1+f/i)) ^i - // 1 + f/i < 2 - var intPart = this.setScale(0, RoundingMode.DOWN) - - if (intPart.signum() == 0) { - return this.expTaylor(mathContext) - } - - val fractionPart = this.subtract(intPart) - // 1 + f/i - val expInner = BigDecimal.ONE - .add( - fractionPart.divide( - intPart, mathContext - ) - ) - - // e^(1+f/i) - val etoExpInner = expInner.expTaylor(mathContext) - // The build in power function can only handle int type, which max out at 999999999 - val maxInt = BigDecimal.valueOf(999999999L) - var result = BigDecimal.ONE - - while (intPart >= maxInt) { - result = result.multiply( - etoExpInner.pow(999999999, mathContext), - ) - intPart = intPart.subtract(maxInt) - } - return result.multiply(etoExpInner.pow(intPart.toInt(), mathContext), mathContext) -} - -/** - * Taylor series: e^x = 1 + x + 1/2!x^2 + ..... - */ -private fun BigDecimal.expTaylor(mathContext: MathContext): BigDecimal { - - var factorial = BigDecimal.ONE - var xToN = this - var sumPrev: BigDecimal? - - var sum = this.add(BigDecimal.ONE) - - var i = 2 - - do { - xToN = xToN.multiply(this, mathContext) - - factorial = factorial.multiply(BigDecimal.valueOf(i.toLong()), mathContext) - - // x^n/factory - val term = xToN - .divide( - factorial, - mathContext - ) - - sumPrev = sum.round(mathContext) - - sum = sum.add(term, mathContext) - i += 1 - } while (sum != sumPrev) - return sum -} - -/** - * Compute the natural logarithm of a big decimal. - */ -internal fun BigDecimal.ln(mathContext: MathContext = MATH_CONTEXT): BigDecimal { - if (this.signum() <= 0) { - throw ArithmeticException("Cannot take natural log of a non-positive number") - } - if (this.compareTo(BigDecimal.ONE) == 0) { - return BigDecimal.ZERO.roundToDigits(MATH_CONTEXT) - } - val intPart = this.setScale(0, RoundingMode.DOWN) - val intPartLength = intPart.precision() - val operationMC = MathContext( - if (10 + mathContext.precision < 0) Int.MAX_VALUE else 10 + mathContext.precision, - mathContext.roundingMode - ) - // For faster converge, we calculate m*ln(root(x,m)) for m >= 3. - return if (intPartLength < 3) { - this.lnNewton(operationMC).roundToDigits(mathContext) - } else { - val root = this.intRoot(intPartLength, operationMC) - val lnRoot = root.lnNewton(operationMC) - val unModifiedRes = BigDecimal.valueOf(intPartLength.toLong()).multiply(lnRoot, operationMC) - unModifiedRes.roundToDigits(mathContext) - } -} - -/** - * Newton's method to compute natural log - */ -private fun BigDecimal.lnNewton(mathContext: MathContext): BigDecimal { - val operationMC = MathContext( - if (mathContext.precision + 2 < 0) Int.MAX_VALUE else mathContext.precision + 2, - mathContext.roundingMode - ) - - val tolerance: BigDecimal = BigDecimal.valueOf(5L).movePointLeft(mathContext.precision + 1) - - // x_i = x_i-1 - (e^x_i-1 - n) / e^x_i-1 - var x = this - val n = this - var term: BigDecimal - - do { - val eToX = x.expHelper(operationMC) - term = eToX.subtract(n) - .divide(eToX, operationMC) - x = x.subtract(term) - } while (term > tolerance) - return x -} - -/** - * Calculate the given big decimal raised to the pth power, where p is another big decimal. - */ -internal fun BigDecimal.power( - power: BigDecimal, - mathContext: MathContext = MATH_CONTEXT -): BigDecimal { - val operationMC = MathContext( - if (10 + mathContext.precision < 0) Int.MAX_VALUE else 10 + mathContext.precision, - mathContext.roundingMode - ) - - // x^(p) = x^(i + f) = x^i * x^f - var intPart = power.setScale(0, RoundingMode.DOWN) - val fractionPart = power.subtract(intPart) - - if (fractionPart.compareTo(BigDecimal.ZERO) != 0 && this < BigDecimal.ZERO) { - throw ArithmeticException("a negative number raised to a non-integer power yields a complex result") - } - - val maxInt = BigDecimal.valueOf(999999999L) - var result = BigDecimal.ONE - - while (intPart >= maxInt) { - result = result.multiply( - this.pow(999999999, operationMC), - operationMC - ) - intPart = intPart.subtract(maxInt) - } - - // x^i - result = result.multiply( - this.pow(intPart.toInt(), operationMC), - operationMC - ) - - // x^f = exp(f*ln(x)) ; - return if (fractionPart.compareTo(BigDecimal.ZERO) != 0) { - val lnX = this.ln(operationMC) - val fTimesLnX: BigDecimal = fractionPart.multiply(lnX, operationMC) - val xToF = fTimesLnX.exp(operationMC) - result.multiply(xToF, operationMC).roundToDigits(mathContext) - } else { - result.roundToDigits(mathContext) - } -} - -// wrapper for transform function result to corresponding integer type -internal fun BigInteger.transformIntType(): Number = when (this) { - in Int.MIN_VALUE.toBigInteger()..Int.MAX_VALUE.toBigInteger() -> toInt() - in Long.MIN_VALUE.toBigInteger()..Long.MAX_VALUE.toBigInteger() -> toLong() - /** - * currently PariQL-lang-kotlin did not support integer value bigger than 64 bits. - */ - else -> errIntOverflow(8) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/ThreadExt.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/ThreadExt.kt deleted file mode 100644 index 8f1f9d7ab8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/ThreadExt.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.partiql.lang.eval.internal.ext - -/** Throws [InterruptedException] if [Thread.interrupted] is set. */ -internal fun checkThreadInterrupted() { - if (Thread.interrupted()) { - throw InterruptedException() - } -} - -/** - * Like a regular [map], but checks [Thread.interrupted] before each iteration and throws - * [InterruptedException] if it is set. - * - * This should be used instead of the regular [map] where there is a potential for a large - * number of items in the receiver [List] to allow long running operations to be aborted - * by the caller. - */ -internal inline fun List.interruptibleMap(crossinline block: (T) -> R): List = - this.map { checkThreadInterrupted(); block(it) } - -/** - * Like a regular [fold], but checks [Thread.interrupted] before each iteration and throws - * [InterruptedException] if it is set. - * - * This should be used instead of the regular [fold] where there is a potential for a large - * number of items in the receiver [List] to allow long running operations to be aborted - * by the caller. - */ -internal inline fun List.interruptibleFold(initial: A, crossinline block: (A, T) -> A) = - this.fold(initial) { acc, curr -> checkThreadInterrupted(); block(acc, curr) } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/TimeExt.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/TimeExt.kt deleted file mode 100644 index b480b6b619..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/TimeExt.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.partiql.lang.eval.internal.ext - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.internal.SECONDS_PER_HOUR -import org.partiql.lang.eval.internal.SECONDS_PER_MINUTE -import org.partiql.lang.eval.internal.err -import org.partiql.lang.util.propertyValueMapOf -import java.time.ZoneOffset -import kotlin.math.absoluteValue - -// These are used to validate the generic format of the time string. -// The more involved logic such as validating the time is done by LocalTime.parse or OffsetTime.parse -internal val timeWithoutTimeZoneRegex = Regex("\\d\\d:\\d\\d:\\d\\d(\\.\\d*)?") -internal val genericTimeRegex = Regex("\\d\\d:\\d\\d:\\d\\d(\\.\\d*)?([+|-]\\d\\d:\\d\\d)?") - -/** - * Regex pattern to match date strings of the format yyyy-MM-dd - */ -internal val DATE_PATTERN_REGEX = Regex("\\d\\d\\d\\d-\\d\\d-\\d\\d") - -/** - * Returns the string representation of the [ZoneOffset] in HH:mm format. - */ -internal fun ZoneOffset.getOffsetHHmm(): String = - (if (totalSeconds >= 0) "+" else "-") + - hour.absoluteValue.toString().padStart(2, '0') + - ":" + - minute.absoluteValue.toString().padStart(2, '0') - -/** - * Get time zone offset hour - */ -internal val ZoneOffset.hour: Int - get() = totalSeconds / SECONDS_PER_HOUR - -/** - * Get time zone offset minute - */ -internal val ZoneOffset.minute: Int - get() = (totalSeconds / SECONDS_PER_MINUTE) % SECONDS_PER_MINUTE - -/** - * Get time zone offset in total minutes - */ -internal val ZoneOffset.totalMinutes: Int - get() = totalSeconds / SECONDS_PER_MINUTE - -/** - * Calculates the precision of a time string based on the fractional component of the 'HH:MM:SS[.ddd....][+|-HH:MM]' format. - */ -internal fun getPrecisionFromTimeString(timeString: String): Int { - val matcher = genericTimeRegex.toPattern().matcher(timeString) - if (!matcher.find()) { - err( - "Time string does not match the format 'HH:MM:SS[.ddd....][+|-HH:MM]'", - ErrorCode.PARSE_INVALID_TIME_STRING, - propertyValueMapOf(), - false - ) - } - // Note that the [genericTimeRegex] has a group to extract the fractional part of the second. - val fraction = matcher.group(1)?.removePrefix(".") - return fraction?.length ?: 0 -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/TimestampExt.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/TimestampExt.kt deleted file mode 100644 index 17d05e6a5d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/TimestampExt.kt +++ /dev/null @@ -1,121 +0,0 @@ -package org.partiql.lang.eval.internal.ext - -import com.amazon.ion.Timestamp -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.internal.DateTimePart -import org.partiql.lang.eval.internal.SECONDS_PER_MINUTE -import org.partiql.lang.eval.internal.errNoContext -import org.partiql.lang.eval.time.Time -import java.math.BigDecimal -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.ZoneOffset - -internal val precisionOrder = listOf( - Timestamp.Precision.YEAR, - Timestamp.Precision.MONTH, - Timestamp.Precision.DAY, - Timestamp.Precision.MINUTE, - Timestamp.Precision.SECOND -) - -internal val dateTimePartToPrecision = mapOf( - DateTimePart.YEAR to Timestamp.Precision.YEAR, - DateTimePart.MONTH to Timestamp.Precision.MONTH, - DateTimePart.DAY to Timestamp.Precision.DAY, - DateTimePart.HOUR to Timestamp.Precision.MINUTE, - DateTimePart.MINUTE to Timestamp.Precision.MINUTE, - DateTimePart.SECOND to Timestamp.Precision.SECOND -) - -internal fun Timestamp.hasSufficientPrecisionFor(requiredPrecision: Timestamp.Precision): Boolean { - val requiredPrecisionPos = precisionOrder.indexOf(requiredPrecision) - val precisionPos = precisionOrder.indexOf(precision) - - return precisionPos >= requiredPrecisionPos -} - -internal fun Timestamp.adjustPrecisionTo(dateTimePart: DateTimePart): Timestamp { - val requiredPrecision = dateTimePartToPrecision[dateTimePart]!! - - if (this.hasSufficientPrecisionFor(requiredPrecision)) { - return this - } - - return when (requiredPrecision) { - Timestamp.Precision.YEAR -> Timestamp.forYear(this.year) - Timestamp.Precision.MONTH -> Timestamp.forMonth(this.year, this.month) - Timestamp.Precision.DAY -> Timestamp.forDay(this.year, this.month, this.day) - Timestamp.Precision.SECOND -> Timestamp.forSecond( - this.year, this.month, this.day, this.hour, this.minute, this.second, this.localOffset - ) - Timestamp.Precision.MINUTE -> Timestamp.forMinute( - this.year, this.month, this.day, this.hour, this.minute, this.localOffset - ) - else -> errNoContext( - "invalid datetime part for date_add: ${dateTimePart.toString().lowercase()}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_DATE_PART, - internal = false - ) - } -} - -internal fun Timestamp.toOffsetDateTime() = OffsetDateTime.of( - year, month, day, hour, minute, second, 0, ZoneOffset.ofTotalSeconds((localOffset ?: 0) * 60) -) - -// IonJava Timestamp.localOffset is the offset in minutes, e.g.: `+01:00 = 60` and `-1:20 = -80` -internal fun Timestamp.hourOffset() = (localOffset ?: 0) / SECONDS_PER_MINUTE - -internal fun Timestamp.minuteOffset() = (localOffset ?: 0) % SECONDS_PER_MINUTE - -internal fun Timestamp.extractedValue(dateTimePart: DateTimePart): BigDecimal { - return when (dateTimePart) { - DateTimePart.YEAR -> year - DateTimePart.MONTH -> month - DateTimePart.DAY -> day - DateTimePart.HOUR -> hour - DateTimePart.MINUTE -> minute - DateTimePart.SECOND -> second - DateTimePart.TIMEZONE_HOUR -> hourOffset() - DateTimePart.TIMEZONE_MINUTE -> minuteOffset() - }.toBigDecimal() -} - -internal fun LocalDate.extractedValue(dateTimePart: DateTimePart): BigDecimal { - return when (dateTimePart) { - DateTimePart.YEAR -> year - DateTimePart.MONTH -> monthValue - DateTimePart.DAY -> dayOfMonth - DateTimePart.TIMEZONE_HOUR, - DateTimePart.TIMEZONE_MINUTE -> errNoContext( - "Timestamp unit ${dateTimePart.name.lowercase()} not supported for DATE type", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - DateTimePart.HOUR, DateTimePart.MINUTE, DateTimePart.SECOND -> 0 - }.toBigDecimal() -} - -internal fun Time.extractedValue(dateTimePart: DateTimePart): BigDecimal { - return when (dateTimePart) { - DateTimePart.HOUR -> localTime.hour.toBigDecimal() - DateTimePart.MINUTE -> localTime.minute.toBigDecimal() - DateTimePart.SECOND -> secondsWithFractionalPart - DateTimePart.TIMEZONE_HOUR -> timezoneHour?.toBigDecimal() ?: errNoContext( - "Time unit ${dateTimePart.name.lowercase()} not supported for TIME type without TIME ZONE", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - DateTimePart.TIMEZONE_MINUTE -> timezoneMinute?.toBigDecimal() ?: errNoContext( - "Time unit ${dateTimePart.name.lowercase()} not supported for TIME type without TIME ZONE", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - DateTimePart.YEAR, DateTimePart.MONTH, DateTimePart.DAY -> errNoContext( - "Time unit ${dateTimePart.name.lowercase()} not supported for TIME type.", - ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_FUNC_CALL, - internal = false - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/TypeExt.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/TypeExt.kt deleted file mode 100644 index 3f655b60dc..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/ext/TypeExt.kt +++ /dev/null @@ -1,115 +0,0 @@ -package org.partiql.lang.eval.internal.ext - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.types.TypedOpParameter -import org.partiql.types.DecimalType -import org.partiql.types.IntType -import org.partiql.types.NumberConstraint -import org.partiql.types.StaticType -import org.partiql.types.StringType -import org.partiql.types.TimeType - -/** - * Helper to convert [PartiqlPhysical.Type] in AST to a [TypedOpParameter]. - */ -internal fun PartiqlPhysical.Type.toTypedOpParameter(customTypedOpParameters: Map): TypedOpParameter = - when (this) { - is PartiqlPhysical.Type.MissingType -> TypedOpParameter(StaticType.MISSING) - is PartiqlPhysical.Type.NullType -> TypedOpParameter(StaticType.NULL) - is PartiqlPhysical.Type.BooleanType -> TypedOpParameter(StaticType.BOOL) - is PartiqlPhysical.Type.SmallintType -> TypedOpParameter(IntType(IntType.IntRangeConstraint.SHORT)) - is PartiqlPhysical.Type.Integer4Type -> TypedOpParameter(IntType(IntType.IntRangeConstraint.INT4)) - is PartiqlPhysical.Type.Integer8Type -> TypedOpParameter(IntType(IntType.IntRangeConstraint.LONG)) - is PartiqlPhysical.Type.IntegerType -> TypedOpParameter(IntType(IntType.IntRangeConstraint.LONG)) - is PartiqlPhysical.Type.FloatType, is PartiqlPhysical.Type.RealType, is PartiqlPhysical.Type.DoublePrecisionType -> TypedOpParameter( - StaticType.FLOAT - ) - is PartiqlPhysical.Type.DecimalType -> when { - this.precision == null && this.scale == null -> TypedOpParameter(StaticType.DECIMAL) - this.precision != null && this.scale == null -> TypedOpParameter( - DecimalType( - DecimalType.PrecisionScaleConstraint.Constrained( - this.precision!!.value.toInt() - ) - ) - ) - else -> TypedOpParameter( - DecimalType( - DecimalType.PrecisionScaleConstraint.Constrained( - this.precision!!.value.toInt(), - this.scale!!.value.toInt() - ) - ) - ) - } - is PartiqlPhysical.Type.NumericType -> when { - this.precision == null && this.scale == null -> TypedOpParameter(StaticType.DECIMAL) - this.precision != null && this.scale == null -> TypedOpParameter( - DecimalType( - DecimalType.PrecisionScaleConstraint.Constrained( - this.precision!!.value.toInt() - ) - ) - ) - else -> TypedOpParameter( - DecimalType( - DecimalType.PrecisionScaleConstraint.Constrained( - this.precision!!.value.toInt(), - this.scale!!.value.toInt() - ) - ) - ) - } - is PartiqlPhysical.Type.TimestampType -> TypedOpParameter(StaticType.TIMESTAMP) - is PartiqlPhysical.Type.CharacterType -> when { - this.length == null -> TypedOpParameter( - StringType( - StringType.StringLengthConstraint.Constrained( - NumberConstraint.Equals(1) - ) - ) - ) - else -> TypedOpParameter( - StringType( - StringType.StringLengthConstraint.Constrained( - NumberConstraint.Equals(this.length!!.value.toInt()) - ) - ) - ) - } - is PartiqlPhysical.Type.CharacterVaryingType -> when (val length = this.length) { - null -> TypedOpParameter(StringType(StringType.StringLengthConstraint.Unconstrained)) - else -> TypedOpParameter( - StringType( - StringType.StringLengthConstraint.Constrained( - NumberConstraint.UpTo( - length.value.toInt() - ) - ) - ) - ) - } - is PartiqlPhysical.Type.StringType -> TypedOpParameter(StaticType.STRING) - is PartiqlPhysical.Type.SymbolType -> TypedOpParameter(StaticType.SYMBOL) - is PartiqlPhysical.Type.ClobType -> TypedOpParameter(StaticType.CLOB) - is PartiqlPhysical.Type.BlobType -> TypedOpParameter(StaticType.BLOB) - is PartiqlPhysical.Type.StructType -> TypedOpParameter(StaticType.STRUCT) - is PartiqlPhysical.Type.TupleType -> TypedOpParameter(StaticType.STRUCT) - is PartiqlPhysical.Type.ListType -> TypedOpParameter(StaticType.LIST) - is PartiqlPhysical.Type.SexpType -> TypedOpParameter(StaticType.SEXP) - is PartiqlPhysical.Type.BagType -> TypedOpParameter(StaticType.BAG) - is PartiqlPhysical.Type.AnyType -> TypedOpParameter(StaticType.ANY) - is PartiqlPhysical.Type.CustomType -> - customTypedOpParameters.mapKeys { (k, _) -> - k.lowercase() - }[this.name.text.lowercase()] ?: error("Could not find parameter for $this") - is PartiqlPhysical.Type.DateType -> TypedOpParameter(StaticType.DATE) - is PartiqlPhysical.Type.TimeType -> TypedOpParameter( - TimeType(this.precision?.value?.toInt(), withTimeZone = false) - ) - is PartiqlPhysical.Type.TimeWithTimeZoneType -> TypedOpParameter( - TimeType(this.precision?.value?.toInt(), withTimeZone = true) - ) - - is PartiqlPhysical.Type.TimestampWithTimeZoneType -> TODO() - } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/FormatItem.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/FormatItem.kt deleted file mode 100644 index a59035458d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/FormatItem.kt +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.timestamp - -/** - * A item that is parsed from the format pattern. i.e. text or one of many symbols corresponding to a - * field and its formatting options. - */ -internal sealed class FormatItem - -/** - * Literal text to be included in the timestamp format. Typically, `-`, `/` `:` ` ` or literal strings enclosed in `'`. - */ -internal data class TextItem(val raw: String) : FormatItem() - -/** - * Indicates the various fields of a timestamp. - * The [precisionRank] field specifies the order of the precision such that higher [precisionRank] values are more - * precise fields. The [precisionRank] field is null for fields such as [AM_PM] and [OFFSET] which do not imply - * a precision. - */ -internal enum class TimestampField(val precisionRank: Int? = null) { - YEAR(0), MONTH_OF_YEAR(1), DAY_OF_MONTH(2), HOUR_OF_DAY(3), AM_PM(), MINUTE_OF_HOUR(4), SECOND_OF_MINUTE(5), FRACTION_OF_SECOND( - 6 - ), - OFFSET() -} - -/** - * Base class for all format symbols - */ -internal sealed class PatternSymbol : FormatItem() { - abstract val field: TimestampField -} - -/** - * Species specific format for the year field. - */ -internal enum class YearFormat { - /** - * Format symbol: `yy`. - */ - TWO_DIGIT, - - /** - * Format symbol: `y`. - */ - FOUR_DIGIT, - - /** - * Format symbol: `yyyy`. - */ - FOUR_DIGIT_ZERO_PADDED -} - -/** - * One of the format symbols corresponding to the year timestamp field, i.e. y, yy, yyyy. - */ -internal data class YearPatternSymbol(val format: YearFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.YEAR -} - -/** - * Specifies specific format for the month field. - */ -internal enum class MonthFormat { - /** - * Format symbol: `M`. - */ - MONTH_NUMBER, - - /** - * Format symbol: `MM`. - */ - MONTH_NUMBER_ZERO_PADDED, - - /** - * Format symbol: `MMM`. - */ - ABBREVIATED_MONTH_NAME, - - /** - * Format symbol: `MMMM`. - */ - FULL_MONTH_NAME, - - /** - * Format symbol: `MMMMM`. - */ - FIRST_LETTER_OF_MONTH_NAME, -} - -/** - * One of the format symbols corresponding to the month timestamp field, i.e. `M, `MM`, `MMM` or `MMMM`. - */ -internal data class MonthPatternSymbol(val format: MonthFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.MONTH_OF_YEAR -} - -/** - * Generic formatting options shared by [DayOfMonthPatternSymbol], [MinuteOfHourPatternSymbol] - * and [SecondOfMinutePatternPatternSymbol]. - */ -internal enum class TimestampFieldFormat { - - /** - * Format symbol: `y`. - */ - NUMBER, - ZERO_PADDED_NUMBER -} - -/** - * One of the format symbols corresponding to the day-of-month timestamp field, i.e. `d` or `dd`. - */ -internal data class DayOfMonthPatternSymbol(val format: TimestampFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.DAY_OF_MONTH -} - -/** - * Indicates if the hour-of-day field is in 12 or 24-hour format. - */ -internal enum class HourClock { - TwelveHour, - TwentyFourHour -} - -/** - * Specifies the specific format of the hour-of-day timestamp field. - */ -internal enum class HourOfDayFormatFieldFormat(val clock: HourClock) { - /** - * Format symbol: `h`. - */ - NUMBER_12_HOUR(HourClock.TwelveHour), - - /** - * Format symbol: `hh`. - */ - ZERO_PADDED_NUMBER_12_HOUR(HourClock.TwelveHour), - - /** - * Format symbol: `H`. - */ - NUMBER_24_HOUR(HourClock.TwentyFourHour), - - /** - * Format symbol: `HH`. - */ - ZERO_PADDED_NUMBER_24_HOUR(HourClock.TwentyFourHour) -} - -/** - * One one of the format symbols corresponding to the hour-of-day timestamp field, i.e. `h`, `hh`, `H` or `HH`. - */ -internal data class HourOfDayPatternSymbol(val format: HourOfDayFormatFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.HOUR_OF_DAY -} - -/** - * One of the format symbols corresponding to the minute-of-hour timestamp field, i.e. `m` or `mm`. - */ -internal data class MinuteOfHourPatternSymbol(val format: TimestampFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.MINUTE_OF_HOUR -} - -/** - * One of the format symbols corresponding to the second-of-minute timestamp field, i.e. `s` or `ss`. - */ -internal data class SecondOfMinutePatternPatternSymbol(val format: TimestampFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.SECOND_OF_MINUTE -} - -/** - * Represents the nano-of-second timestamp field: `n`. - */ -internal class NanoOfSecondPatternSymbol : PatternSymbol() { - override val field: TimestampField = TimestampField.FRACTION_OF_SECOND - - /** - * This is normally provided by kotlin for data classes but this can't be a data class because it doesn't require - * any constructor arguments. - */ - override fun equals(other: Any?) = this.javaClass.isInstance(other) - - /** - * This is normally provided by kotlin for data classes but this can't be a data class because it doesn't require - * any constructor arguments. - */ - override fun hashCode(): Int = field.hashCode() -} - -/** - * Represents the AM-PM "pseudo" timestamp field: `a`. - */ -internal class AmPmPatternSymbol : PatternSymbol() { - override val field: TimestampField = TimestampField.AM_PM - - /** - * This is normally provided by kotlin for data classes but this can't be a data class because it doesn't require - * any constructor arguments. - */ - override fun equals(other: Any?) = this.javaClass.isInstance(other) - - /** - * This is normally provided by kotlin for data classes but this can't be a data class because it doesn't require - * any constructor arguments. - */ - override fun hashCode(): Int = field.hashCode() -} - -/** - * Represents the fraction-of-second timestamp field: `S`, which has variable precision indicated by the number of - * consecutive `S` symbols and is specified by [precision], i.e. `S` has a precision of 1 whlie `SSSSSS` has a - * precision of 6. - */ -internal data class FractionOfSecondPatternSymbol(val precision: Int) : PatternSymbol() { - override val field: TimestampField = TimestampField.FRACTION_OF_SECOND -} - -internal enum class OffsetFieldFormat { - /** - * Format symbol: `x`. - */ - ZERO_PADDED_HOUR, - - /** - * Format symbol: `xx` or `xxxx`. - */ - - ZERO_PADDED_HOUR_MINUTE, - - /** - * Format symbol: `xxx` or `xxxxx`. - */ - ZERO_PADDED_HOUR_COLON_MINUTE, - - /** - * Format symbol: `X`. - */ - ZERO_PADDED_HOUR_OR_Z, - - /** - * Format symbol: `XX` or `XXXX`. - */ - ZERO_PADDED_HOUR_MINUTE_OR_Z, - - /** - * Format symbol: `XXX` or `XXXXX` - */ - ZERO_PADDED_HOUR_COLON_MINUTE_OR_Z, -} - -/** - * One of the format symbols corresponding to the offset timestamp field, i.e.: `x`, `xx`, `xxx`, `xxxx`, `X`, `XX`, - * `XXX`, or `XXXX`. - */ -internal data class OffsetPatternSymbol(val format: OffsetFieldFormat) : PatternSymbol() { - override val field: TimestampField = TimestampField.OFFSET -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/FormatPattern.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/FormatPattern.kt deleted file mode 100644 index dab1bd84ea..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/FormatPattern.kt +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.timestamp - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.util.propertyValueMapOf - -/** - * Represents a parsed timestamp format pattern. - */ -internal class FormatPattern(val formatPatternString: String, val formatItems: List) { - - companion object { - - /** - * Constructs a new instance of [FormatPattern] using the specified format pattern string. - */ - fun fromString(pattern: String): FormatPattern = TimestampFormatPatternParser().parse(pattern) - } - - /** - * Indicates the least significant of the fields in this format pattern or `null` if no fields are specified. - * When parsing, this will correspond to the precision of the Ion timestamp. - */ - val leastSignificantField: TimestampField? by lazy { - formatSymbols - .filter { it.field.precisionRank != null } - .sortedByDescending { it.field.precisionRank } - .firstOrNull() - ?.field - } - - /** - * Lazily filtered list of [PatternSymbol] instances present in [formatItems]. - */ - val formatSymbols: List by lazy { - formatItems.filterIsInstance() - } - - /** - * True if this [FormatPattern] contains a two digit year. - */ - val has2DigitYear: Boolean by lazy { - formatSymbols.filterIsInstance().any { it.format == YearFormat.TWO_DIGIT } - } - - /** - * True if this [FormatPattern] contains an offset symbol. - */ - val hasOffset: Boolean by lazy { - formatSymbols.filterIsInstance().any() - } - - /** - * True if this [FormatPattern] has an AM/PM offset symbol. - */ - val hasAmPm: Boolean by lazy { - formatSymbols.filterIsInstance().any() - } - - /** - * Validates the timestamp for parsing, throwing an `EvaluationException` if this format pattern cannot yield a - * valid Ion timestamp. - */ - fun validateForTimestampParsing() { - checkForFieldsNotValidForParsing() - checkDuplicatefields() - checkFieldCombination() - checkAmPmMismatch() - } - - /** - * Validates that duplicate fields are not included. - */ - private fun checkDuplicatefields() { - - val duplicatedField = formatSymbols.groupingBy { it.field } - .eachCount() - .filter { it.value > 1 } // Appears more than once in field - .asSequence() - .sortedByDescending { it.value } // Sort descending by number of appearances - .firstOrNull() - - if (duplicatedField != null) { - throw EvaluationException( - message = "timestamp format pattern duplicate fields", - errorCode = ErrorCode.EVALUATOR_TIMESTAMP_FORMAT_PATTERN_DUPLICATE_FIELDS, - errorContext = propertyValueMapOf( - Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString, - Property.TIMESTAMP_FORMAT_PATTERN_FIELDS to duplicatedField.key - ), - internal = false - ) - } - } - - /** - * Validates that when 12 hour hour of day field is present, am/pm must also be included - * and that when 24 hour of day field is present, am/pm is *not* included. - */ - private fun checkAmPmMismatch() { - formatSymbols.filterIsInstance().firstOrNull()?.let { - - val hasAmPm = formatSymbols.filterIsInstance().any() - when (it.format.clock) { - HourClock.TwelveHour -> { - if (!hasAmPm) { - throw EvaluationException( - message = "timestamp format pattern contains 12-hour hour of day field but doesn't " + "contain an am/pm field.", - errorCode = ErrorCode.EVALUATOR_TIMESTAMP_FORMAT_PATTERN_HOUR_CLOCK_AM_PM_MISMATCH, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString), - internal = false - ) - } - } - HourClock.TwentyFourHour -> { - if (hasAmPm) { - throw EvaluationException( - message = "timestamp format pattern contains 24-hour hour of day field and also " + "contains an am/pm field.", - errorCode = ErrorCode.EVALUATOR_TIMESTAMP_FORMAT_PATTERN_HOUR_CLOCK_AM_PM_MISMATCH, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString), - internal = false - ) - } - } - } // end when - } // end let - } - - /** - * Ensures that the format pattern includes symbols that can yield a valid Ion timestamp. - */ - private fun checkFieldCombination() { - - fun err(missingFields: String): Nothing = - throw EvaluationException( - message = "timestamp format pattern missing fields", - errorCode = ErrorCode.EVALUATOR_INCOMPLETE_TIMESTAMP_FORMAT_PATTERN, - errorContext = propertyValueMapOf( - Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString, - Property.TIMESTAMP_FORMAT_PATTERN_FIELDS to missingFields - ), - internal = false - ) - - fun errIfMissingTimestampFields(vararg fields: TimestampField) { - val missingFields = fields.filter { requiredField -> formatSymbols.all { it.field != requiredField } } - - if (missingFields.any()) { - err(missingFields.asSequence().joinToString(", ")) - } - } - - // Minimum precision for patterns containing offset or am/pm symbols is HOUR. - // NOTE: HOUR is not a valid precision for an Ion timestamp but when a format pattern's - // leastSignificantField is HOUR, the minute field defaults to 00. - if (hasOffset || hasAmPm) { - errIfMissingTimestampFields( - TimestampField.YEAR, TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH, - TimestampField.HOUR_OF_DAY - ) - } - - when (leastSignificantField) { - null -> { - // If most precise field is null there are no format symbols corresponding to any timestamp fields. - err("YEAR") - } - TimestampField.YEAR -> { - // the year field is the most coarse of the timestamp fields - // it does not require any other fields to make a complete timestamp - } - TimestampField.MONTH_OF_YEAR -> errIfMissingTimestampFields(TimestampField.YEAR) - TimestampField.DAY_OF_MONTH -> errIfMissingTimestampFields( - TimestampField.YEAR, - TimestampField.MONTH_OF_YEAR - ) - TimestampField.HOUR_OF_DAY -> errIfMissingTimestampFields( - TimestampField.YEAR, - TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH - ) - TimestampField.MINUTE_OF_HOUR -> errIfMissingTimestampFields( - TimestampField.YEAR, - TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH, TimestampField.HOUR_OF_DAY - ) - TimestampField.SECOND_OF_MINUTE -> errIfMissingTimestampFields( - TimestampField.YEAR, - TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH, TimestampField.HOUR_OF_DAY, - TimestampField.MINUTE_OF_HOUR - ) - TimestampField.FRACTION_OF_SECOND -> errIfMissingTimestampFields( - TimestampField.YEAR, - TimestampField.MONTH_OF_YEAR, TimestampField.DAY_OF_MONTH, TimestampField.HOUR_OF_DAY, - TimestampField.MINUTE_OF_HOUR, TimestampField.SECOND_OF_MINUTE - ) - - TimestampField.OFFSET, TimestampField.AM_PM -> { - throw IllegalStateException("OFFSET, AM_PM should never be the least significant field!") - } - } - } - - /** - * Ensures that this format pattern doesn't contain any format symbols which are valid for timestamp formatting - * but not for parsing. - */ - private fun checkForFieldsNotValidForParsing() { - if (formatSymbols.filterIsInstance() - .any { it.format == MonthFormat.FIRST_LETTER_OF_MONTH_NAME } - ) { - throw EvaluationException( - message = "timestamp format pattern missing fields", - errorCode = ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN_SYMBOL_FOR_PARSING, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPatternString), - internal = false - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampFormatPatternLexer.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampFormatPatternLexer.kt deleted file mode 100644 index c803b4e6c4..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampFormatPatternLexer.kt +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.timestamp - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.util.codePointSequence -import org.partiql.lang.util.propertyValueMapOf - -private const val NON_ESCAPED_TEXT = " /-,:." -private const val SINGLE_QUOTE_CP = '\''.toInt() -private const val PATTERN = "yMdahHmsSXxn" - -// max code point range -// i.e. result of (NON_ESCAPED_TEXT + "'" + PATTERN).codePoints().max() + 1 -private const val TABLE_SIZE = 122 - -/** - * Timestamp format pattern token types. - */ -internal enum class TokenType { PATTERN, TEXT } - -/** - * Timestamp format pattern tokens. - */ -internal data class Token(val tokenType: TokenType, val value: String) - -/** - * State machine state types - */ -private enum class StateType(val beginsToken: Boolean, val endsToken: Boolean) { - /** lexer initial state */ - INITIAL(beginsToken = false, endsToken = false), - - /** an error state */ - ERROR(beginsToken = false, endsToken = false), - - /** start of a new token */ - START(beginsToken = true, endsToken = false), - - /** possible termination of a token */ - TERMINAL(beginsToken = false, endsToken = true), - - /** state that's both start and terminal */ - START_AND_TERMINAL(beginsToken = true, endsToken = true), - - /** middle of a token */ - INCOMPLETE(beginsToken = false, endsToken = false) -} - -/** - * A lexer state machine node - */ -private interface State { - val tokenType: TokenType? - val stateType: StateType - - /** - * Next state node for [cp] - * - * @throws IllegalStateException if no transition exists. - */ - fun nextFor(cp: Int): State -} - -/** - * Table backed [State]. This class is mutable through [transitionTo] so needs to be setup statically to be thread safe - */ -private class TableState( - override val tokenType: TokenType?, - override val stateType: StateType, - val delegate: State? = null, -) : State { - private val transitionTable = object { - val backingArray = Array(TABLE_SIZE) { null } - - operator fun get(codePoint: Int): State? = when { - codePoint < TABLE_SIZE -> backingArray[codePoint] - else -> null - } - - operator fun set(codePoint: Int, next: State) { - backingArray[codePoint] = next - } - } - - /** - * Registers a transition for all code points in [characters] to the [next] state - */ - fun transitionTo(characters: String, next: State) { - characters.forEach { - val cp = it.toInt() - transitionTo(cp, next) - } - } - - /** - * Registers a transition for the code point to the [next] state - */ - fun transitionTo(codePoint: Int, next: State) { - transitionTable[codePoint] = next - } - - override fun nextFor(cp: Int): State = - transitionTable[cp] ?: delegate?.nextFor(cp) ?: throw IllegalStateException("Unknown transition") -} - -private abstract class PatternState(val codePoint: Int, override val stateType: StateType) : State { - override val tokenType = TokenType.PATTERN -} - -private abstract class TextState(override val stateType: StateType) : State { - override val tokenType = TokenType.TEXT -} - -internal class TimestampFormatPatternLexer { - companion object { - private val ERROR_STATE: State = object : State { - override val tokenType = null - override val stateType: StateType = StateType.ERROR - - override fun nextFor(cp: Int): State = this - } - - private val INITIAL_STATE = TableState(tokenType = null, stateType = StateType.INITIAL, delegate = ERROR_STATE) - - // setups the lexer state machine - init { - val startEscapedText = TableState(TokenType.TEXT, StateType.START_AND_TERMINAL, INITIAL_STATE) - val inNonEscapedText = TableState(TokenType.TEXT, StateType.TERMINAL, INITIAL_STATE) - startEscapedText.transitionTo(NON_ESCAPED_TEXT, inNonEscapedText) - inNonEscapedText.transitionTo(NON_ESCAPED_TEXT, inNonEscapedText) - - val startQuotedText = object : TextState(StateType.START) { - val startQuotedText = this - - val endQuotedState = object : TextState(StateType.TERMINAL) { - override fun nextFor(cp: Int): State = when (cp) { - SINGLE_QUOTE_CP -> startQuotedText - else -> INITIAL_STATE.nextFor(cp) - } - } - - val inQuotedState = object : TextState(StateType.INCOMPLETE) { - override fun nextFor(cp: Int): State = when (cp) { - SINGLE_QUOTE_CP -> endQuotedState - else -> this - } - } - - override fun nextFor(cp: Int): State = when (cp) { - SINGLE_QUOTE_CP -> endQuotedState - else -> inQuotedState - } - } - - INITIAL_STATE.transitionTo(NON_ESCAPED_TEXT, startEscapedText) - INITIAL_STATE.transitionTo(SINGLE_QUOTE_CP, startQuotedText) - PATTERN.codePoints().forEach { cp -> - INITIAL_STATE.transitionTo( - cp, - object : PatternState(cp, StateType.START_AND_TERMINAL) { - val repeatingState = object : PatternState(cp, StateType.TERMINAL) { - override fun nextFor(cp: Int): State = when (cp) { - codePoint -> this - else -> INITIAL_STATE.nextFor(cp) - } - } - - override fun nextFor(cp: Int): State = when (cp) { - codePoint -> repeatingState - else -> INITIAL_STATE.nextFor(cp) - } - } - ) - } - } - } - - private fun StringBuilder.reset() = this.setLength(0) - - private fun tokenEnd(current: State, next: State) = when { - current.stateType == StateType.INITIAL -> false - current.tokenType == next.tokenType && next.stateType.beginsToken -> true - current.tokenType != next.tokenType -> true - else -> false - } - - fun tokenize(source: String): List { - val tokens = mutableListOf() - val buffer = StringBuilder() - - if (source.isEmpty()) { - return listOf() - } - - fun flushToken(tokenType: TokenType) { - tokens.add(Token(tokenType, buffer.toString())) - buffer.reset() - } - - var current: State = INITIAL_STATE - - val codePoints = source.codePointSequence() - - codePoints.forEach { cp -> - val next = current.nextFor(cp) - - if (next.stateType == StateType.ERROR) { - throw EvaluationException( - message = "Invalid token in timestamp format pattern", - errorCode = ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN_TOKEN, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to source), - internal = false - ) - } - - if (tokenEnd(current, next)) { - flushToken(current.tokenType!!) - } - - current = next - buffer.appendCodePoint(cp) - } - - if (!current.stateType.endsToken) { - throw EvaluationException( - message = "Unterminated token in timestamp format pattern", - errorCode = ErrorCode.EVALUATOR_UNTERMINATED_TIMESTAMP_FORMAT_PATTERN_TOKEN, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to source), - internal = false - ) - } - - flushToken(current.tokenType!!) - - return tokens - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampFormatPatternParser.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampFormatPatternParser.kt deleted file mode 100644 index 68ceadc13c..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampFormatPatternParser.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.internal.timestamp - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.util.propertyValueMapOf - -internal class TimestampFormatPatternParser { - - fun parse(formatPatternString: String): FormatPattern { - val lexer = TimestampFormatPatternLexer() - val tokens = lexer.tokenize(formatPatternString) - - var patternCounter = 0 - val formatItems = tokens.map { token -> - when (token.tokenType) { - TokenType.TEXT -> TextItem(token.value) - TokenType.PATTERN -> { - patternCounter += 1 - parsePattern(token.value) - } - } - } - - return FormatPattern(formatPatternString, formatItems) - } - - private fun parsePattern(raw: String): FormatItem = when (raw) { - // Possible optimization here: create singleton instances corresponding to each of the branches and return - // those instead of creating new instances. This could work because all of the objects here are immutable. - // This reduces the amount of garbage created during execution of this method. - "y" -> YearPatternSymbol(YearFormat.FOUR_DIGIT) - "yy" -> YearPatternSymbol(YearFormat.TWO_DIGIT) - "yyy", "yyyy" -> YearPatternSymbol(YearFormat.FOUR_DIGIT_ZERO_PADDED) - - "M" -> MonthPatternSymbol(MonthFormat.MONTH_NUMBER) - "MM" -> MonthPatternSymbol(MonthFormat.MONTH_NUMBER_ZERO_PADDED) - "MMM" -> MonthPatternSymbol(MonthFormat.ABBREVIATED_MONTH_NAME) - "MMMM" -> MonthPatternSymbol(MonthFormat.FULL_MONTH_NAME) - "MMMMM" -> MonthPatternSymbol(MonthFormat.FIRST_LETTER_OF_MONTH_NAME) - - "d" -> DayOfMonthPatternSymbol(TimestampFieldFormat.NUMBER) - "dd" -> DayOfMonthPatternSymbol(TimestampFieldFormat.ZERO_PADDED_NUMBER) - - "H" -> HourOfDayPatternSymbol(HourOfDayFormatFieldFormat.NUMBER_24_HOUR) - "HH" -> HourOfDayPatternSymbol(HourOfDayFormatFieldFormat.ZERO_PADDED_NUMBER_24_HOUR) - "h" -> HourOfDayPatternSymbol(HourOfDayFormatFieldFormat.NUMBER_12_HOUR) - "hh" -> HourOfDayPatternSymbol(HourOfDayFormatFieldFormat.ZERO_PADDED_NUMBER_12_HOUR) - - "a" -> AmPmPatternSymbol() - - "m" -> MinuteOfHourPatternSymbol(TimestampFieldFormat.NUMBER) - "mm" -> MinuteOfHourPatternSymbol(TimestampFieldFormat.ZERO_PADDED_NUMBER) - - "s" -> SecondOfMinutePatternPatternSymbol(TimestampFieldFormat.NUMBER) - "ss" -> SecondOfMinutePatternPatternSymbol(TimestampFieldFormat.ZERO_PADDED_NUMBER) - - "n" -> NanoOfSecondPatternSymbol() - - "X" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_OR_Z) - "XX", "XXXX" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_MINUTE_OR_Z) - "XXX", "XXXXX" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_COLON_MINUTE_OR_Z) - - "x" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR) - "xx", "xxxx" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_MINUTE) - "xxx", "xxxxx" -> OffsetPatternSymbol(OffsetFieldFormat.ZERO_PADDED_HOUR_COLON_MINUTE) - - else -> - // Note: the lexer *should* only return tokens that are full of capital S's so the precision is the length. - if (raw.first() == 'S') - FractionOfSecondPatternSymbol(raw.length) - else - throw EvaluationException( - message = "Invalid symbol in timestamp format pattern", - errorCode = ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN_SYMBOL, - errorContext = propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to raw), - internal = false - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampParser.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampParser.kt deleted file mode 100644 index 078fe5039a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampParser.kt +++ /dev/null @@ -1,185 +0,0 @@ -package org.partiql.lang.eval.internal.timestamp - -import com.amazon.ion.Timestamp -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.internal.errNoContext -import org.partiql.lang.util.propertyValueMapOf -import java.math.BigDecimal -import java.time.DateTimeException -import java.time.format.DateTimeFormatterBuilder -import java.time.temporal.ChronoField -import java.time.temporal.TemporalAccessor - -/** - * Uses Java 8's DateTimeFormatter to parse an Ion Timestamp value. - * - * Note: this is effected by https://bugs.openjdk.java.net/browse/JDK-8066806 which is not fixed until JDK-9. - * - * There are a few differences between Ion's timestamp and the {@ref java.time} package that create a few caveats - * that we hope will be encountered very infrequently. - * - * - The Ion specification allows for explicitly signifying of an unknown timestamp offset with a negative zero offset - * (i.e. the "-00:00" at the end of "2007-02-23T20:14:33.079-00:00") but Java 8's DateTimeFormatter simply doesn't - * recognize this and there's no reliable workaround that we've yet been able to determine. Unfortunately, this - * means that unknown offsets specified are parsed as if they were explicitly UTC (i.e. "+00:00" or "Z"). - * - DateTimeFormatter is capable of parsing UTC offsets to the precision of seconds, but Ion Timestamp's precision - * for offsets is 1 minute. [TimestampParser] currently handles this by throwing an exception when an attempt - * is made to parse a timestamp with an offset that does does not land on a minute boundary. - * - Ion Java's Timestamp allows specification of offsets up to +/- 24h, while an exception is thrown by - * DateTimeFormatter for any attempt to parse an offset greater than +/- 18h. The Ion specification does not seem - * to indicate minimum and maximum allowable values for offsets. In practice this may not be an issue for systems - * that use Timestamps correctly because real-life offsets do not exceed +/- 12h. - */ -internal class TimestampParser { - - companion object { - val TWO_DIGIT_PIVOT_YEAR = 70 - - /** Converts the offset seconds value returned from the TemporalAccessor into the minutes value. - * @throws EvaluationException if the offset seconds value was not a multiple of 60. - */ - private fun TemporalAccessor.getLocalOffset(): Int? = - if (!this.isSupported(ChronoField.OFFSET_SECONDS)) - null - else { - val offsetSeconds = this.get(ChronoField.OFFSET_SECONDS) - if (offsetSeconds % 60 != 0) { - throw EvaluationException( - "The parsed timestamp has a UTC offset that not a multiple of 1 minute. " + - "This timestamp cannot be parsed accurately because the maximum " + - "resolution for an Ion timestamp offset is 1 minute.", - ErrorCode.EVALUATOR_PRECISION_LOSS_WHEN_PARSING_TIMESTAMP, - internal = false - ) - } - offsetSeconds / 60 - } - - /** - * Parses a string given the specified format pattern. - */ - fun parseTimestamp(timestampString: String, formatPattern: String): Timestamp { - val pattern = FormatPattern.fromString(formatPattern) - // TODO: do this during compilation - pattern.validateForTimestampParsing() - - val accessor: TemporalAccessor by lazy { - try { - DateTimeFormatterBuilder() - .parseCaseInsensitive() - .appendPattern(pattern.formatPatternString) - .toFormatter() - .parse(timestampString) - - // DateTimeFormatter.ofPattern(formatPattern).parse(timestampString) - } catch (ex: IllegalArgumentException) { - throw EvaluationException( - ex, ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, - internal = false - ) - } - } - val year: Int by lazy { - val year = accessor.get(ChronoField.YEAR) - when { - !pattern.has2DigitYear || year < TWO_DIGIT_PIVOT_YEAR + 2000 -> year - else -> year - 100 - } - } - - return try { - when (pattern.leastSignificantField) { - TimestampField.FRACTION_OF_SECOND -> { - val nanoSeconds = BigDecimal.valueOf(accessor.getLong(ChronoField.NANO_OF_SECOND)) - val secondsFraction = nanoSeconds.scaleByPowerOfTen(-9).stripTrailingZeros() - // Note that this overload of Timestamp.forSecond(...) creates a timestamp with "fraction" precision. - Timestamp.forSecond( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH), - accessor.get(ChronoField.HOUR_OF_DAY), - accessor.get(ChronoField.MINUTE_OF_HOUR), - BigDecimal.valueOf(accessor.getLong(ChronoField.SECOND_OF_MINUTE)).add( - secondsFraction - ) as BigDecimal, - accessor.getLocalOffset() - ) - } - TimestampField.SECOND_OF_MINUTE -> { - // Note that this overload of Timestamp.forSecond(...) creates a timestamp with "second" precision. - Timestamp.forSecond( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH), - accessor.get(ChronoField.HOUR_OF_DAY), - accessor.get(ChronoField.MINUTE_OF_HOUR), - accessor.get(ChronoField.SECOND_OF_MINUTE), - accessor.getLocalOffset() - ) - } - TimestampField.MINUTE_OF_HOUR -> { - Timestamp.forMinute( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH), - accessor.get(ChronoField.HOUR_OF_DAY), - accessor.get(ChronoField.MINUTE_OF_HOUR), - accessor.getLocalOffset() - ) - } - TimestampField.HOUR_OF_DAY -> { - Timestamp.forMinute( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH), - accessor.get(ChronoField.HOUR_OF_DAY), - 0, // Ion Timestamp has no HOUR precision -- default minutes to 0 - accessor.getLocalOffset() - ) - } - TimestampField.DAY_OF_MONTH -> { - Timestamp.forDay( - year, - accessor.get(ChronoField.MONTH_OF_YEAR), - accessor.get(ChronoField.DAY_OF_MONTH) - ) - } - TimestampField.MONTH_OF_YEAR -> { - Timestamp.forMonth(year, accessor.get(ChronoField.MONTH_OF_YEAR)) - } - TimestampField.YEAR -> { - Timestamp.forYear(year) - } - TimestampField.AM_PM, TimestampField.OFFSET, null -> { - errNoContext( - "This code should be unreachable because AM_PM or OFFSET or null" + - "should never the value of formatPattern.leastSignificantField by at this point", - errorCode = ErrorCode.EVALUATOR_INVALID_TIMESTAMP_FORMAT_PATTERN, - internal = true - ) - } - } - } - // Can be thrown by Timestamp.for*(...) methods. - catch (ex: IllegalArgumentException) { - throw EvaluationException( - ex, - ErrorCode.EVALUATOR_CUSTOM_TIMESTAMP_PARSE_FAILURE, - propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPattern), - internal = false - ) - } - // Can be thrown by TemporalAccessor.get(ChronoField) - catch (ex: DateTimeException) { - throw EvaluationException( - ex, - ErrorCode.EVALUATOR_CUSTOM_TIMESTAMP_PARSE_FAILURE, - propertyValueMapOf(Property.TIMESTAMP_FORMAT_PATTERN to formatPattern), - internal = false - ) - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampTemporalAccessor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampTemporalAccessor.kt deleted file mode 100644 index a79fd199cc..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/timestamp/TimestampTemporalAccessor.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.partiql.lang.eval.internal.timestamp - -import com.amazon.ion.Timestamp -import java.math.BigDecimal -import java.time.temporal.ChronoField -import java.time.temporal.IsoFields -import java.time.temporal.TemporalAccessor -import java.time.temporal.TemporalField -import java.time.temporal.UnsupportedTemporalTypeException - -private val NANOS_PER_SECOND = 1_000_000_000L -private val MILLIS_PER_SECOND = 1_000L -private val MILLIS_PER_SECOND_BD = BigDecimal.valueOf(MILLIS_PER_SECOND) -private val NANOS_PER_SECOND_BD = BigDecimal.valueOf(NANOS_PER_SECOND) - -private val Timestamp.nanoOfSecond: Long - get() = this.decimalSecond.multiply(NANOS_PER_SECOND_BD).toLong() % NANOS_PER_SECOND - -private val Timestamp.milliOfSecond: Long - get() = this.decimalSecond.multiply(MILLIS_PER_SECOND_BD).toLong() % MILLIS_PER_SECOND - -internal class TimestampTemporalAccessor(val ts: Timestamp) : TemporalAccessor { - - /** - * This method should return true to indicate whether a given TemporalField is supported. - * Note that the date-time formatting functionality in JDK8 assumes that all ChronoFields are supported and - * doesn't invoke this method to check if a ChronoField is supported. - */ - override fun isSupported(field: TemporalField?): Boolean = - when (field) { - IsoFields.QUARTER_OF_YEAR -> true - else -> false - } - - override fun getLong(field: TemporalField?): Long { - if (field == null) { - throw IllegalArgumentException("argument 'field' may not be null") - } - return when (field) { - ChronoField.YEAR_OF_ERA -> ts.year.toLong() - ChronoField.MONTH_OF_YEAR -> ts.month.toLong() - ChronoField.DAY_OF_MONTH -> ts.day.toLong() - ChronoField.HOUR_OF_DAY -> ts.hour.toLong() - ChronoField.SECOND_OF_MINUTE -> ts.second.toLong() - ChronoField.MINUTE_OF_HOUR -> ts.minute.toLong() - ChronoField.MILLI_OF_SECOND -> ts.milliOfSecond - ChronoField.NANO_OF_SECOND -> ts.nanoOfSecond - - ChronoField.AMPM_OF_DAY -> ts.hour / 12L - ChronoField.CLOCK_HOUR_OF_AMPM -> { - val hourOfAmPm = ts.hour.toLong() % 12L - if (hourOfAmPm == 0L) 12 else hourOfAmPm - } - ChronoField.OFFSET_SECONDS -> if (ts.localOffset == null) 0 else ts.localOffset * 60L - else -> throw UnsupportedTemporalTypeException( - field.javaClass.name + "." + field.toString() + " not supported" - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/visitors/AggregationVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/visitors/AggregationVisitorTransform.kt deleted file mode 100644 index 5724ddbb6c..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/visitors/AggregationVisitorTransform.kt +++ /dev/null @@ -1,363 +0,0 @@ -package org.partiql.lang.eval.internal.visitors - -import com.amazon.ionelement.api.emptyMetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Problem -import org.partiql.errors.Property -import org.partiql.errors.UNKNOWN_PROBLEM_LOCATION -import org.partiql.lang.ast.passes.SemanticException -import org.partiql.lang.ast.passes.SemanticProblemDetails -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.toBindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.extractColumnAlias -import org.partiql.lang.eval.physical.sourceLocationMeta -import org.partiql.lang.eval.visitors.VisitorTransformBase - -/** - * This VisitorTransform: - * - adds unique aliases to each group key - * - replaces group key references (ids) with their unique name - * - replaces group key references (ids) with the expression that they represent (if within an aggregation function) - * - handles scoping by keeping a context stack - * - converts project star into the aggregation outputs (group keys and group as) - * - throws errors on projection items that reference variables that are not part of the aggregation operator output - * - * This VisitorTransform is specifically used by the planner implementation. - * - * Turns: - * - * ``` - * SELECT - * k AS group_key, - * SUM(k) AS the_sum, - * g AS the_input, - * ( - * SELECT k + SUM(k), SUM(h) - * FROM t2 - * GROUP BY t2.a AS h - * ) AS projection_query - * FROM t1 - * GROUP BY t1.a AS k, t1.b AS h - * GROUP AS g - * HAVING k + SUM(k) > 0 - * ORDER BY - * k + SUM(k) - * AND - * (SELECT k + SUM(k), SUM(h) FROM t2) - * ``` - * - * Into: - * - * ``` - * SELECT - * $__partiql__group_by_0_0 AS group_key, - * SUM(t1.a) AS the_sum, - * "g" AS the_input, - * ( - * SELECT $__partiql__group_by_0_0 + SUM($__partiql__group_by_0_0), SUM($__partiql__group_by_1_0) - * FROM t2 - * GROUP BY t2.a AS $__partiql__group_by_1_0 - * ) AS projection_query - * FROM t1 - * GROUP BY t1.a AS $__partiql__group_by_0_0, t1.b AS $__partiql__group_by_0_0 - * GROUP AS g - * HAVING $__partiql__group_by_0_0 + SUM(t1.a) > 0 - * ORDER BY - * $__partiql__group_by_0_0 + SUM(t1.a) - * AND - * (SELECT $__partiql__group_by_0_0 + SUM($__partiql__group_by_0_0), SUM($__partiql__group_by_0_1) FROM t2) - * ``` - * - */ -internal class AggregationVisitorTransform( - private val contextStack: MutableList = mutableListOf() -) : VisitorTransformBase() { - - private val itemTransform = GroupKeyReferenceTransformer(contextStack) - - companion object { - internal const val GROUP_PREFIX = "\$__partiql__group_by_" - internal const val GROUP_DELIMITER = "_" - internal fun uniqueAlias(level: Int, index: Int) = "$GROUP_PREFIX$level$GROUP_DELIMITER$index" - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - // Every transformExprSelect implicitly adds to the contextStack during the nested transformExprSelect_group. - // Therefore, after transforming the ExprSelect, we need to pop from the contextStack. - return super.transformExprSelectEvaluationOrder(node).also { contextStack.removeLast() } - } - - override fun transformExprSelect_group(node: PartiqlAst.Expr.Select): PartiqlAst.GroupBy? { - // Return with Empty Context if without Group - val containsAggregations = AggregationFinder().containsAggregations(node.project) - if (node.group == null) { - val context = VisitorContext(emptyList(), null, containsAggregations) - contextStack.add(context) - return null - } - - // Add Unique Aliases to Keys and Create GroupKeyInformation - val groupAsAlias = node.group!!.groupAsAlias?.text - val transformedKeys = mutableListOf() - val groupKeyInformation = node.group!!.keyList.keys.mapIndexed { index, key -> - val publicAlias = key.asAlias?.text ?: key.expr.extractColumnAlias(index) - val uniqueAlias = uniqueAlias(this.contextStack.size, index) - val represents = key.expr - val transformedKey = PartiqlAst.build { groupKey(transformExpr(key.expr), uniqueAlias, key.metas) } - transformedKeys.add(transformedKey) - val isPublicAliasUserDefined = key.asAlias != null - GroupKeyInformation(groupKey = key, publicAlias = publicAlias, uniqueAlias = uniqueAlias, represents = represents, isPublicAliasUserDefined = isPublicAliasUserDefined) - } - - // Add to Context Stack and return modified Group Keys - val hasAggregateOperator = containsAggregations || groupKeyInformation.isNotEmpty() || groupAsAlias != null - val ctx = VisitorContext(groupKeyInformation, groupAsAlias, hasAggregateOperator) - contextStack.add(ctx) - return PartiqlAst.build { - groupBy( - strategy = transformGroupBy_strategy(node.group!!), - keyList = groupKeyList(transformedKeys), - groupAsAlias = groupAsAlias, - metas = node.group!!.metas - ) - } - } - - override fun transformExprSelect_having(node: PartiqlAst.Expr.Select): PartiqlAst.Expr? = node.having?.let { having -> - itemTransform.transformExpr(having) - } - - override fun transformSortSpec_expr(node: PartiqlAst.SortSpec) = itemTransform.transformSortSpec_expr(node) - - /** - * Replaces [node] with [PartiqlAst.Projection.ProjectList] IF there are Group Keys and/or a Group Alias - */ - override fun transformProjectionProjectStar(node: PartiqlAst.Projection.ProjectStar): PartiqlAst.Projection { - if (contextStack.last().groupKeys.isNotEmpty() || contextStack.last().groupAsAlias != null) { - return PartiqlAst.build { - val projectionItems = contextStack.last().groupKeys.map { key -> - projectExpr(id(key.uniqueAlias, caseSensitive(), unqualified()), key.publicAlias) - }.toMutableList() - - contextStack.last().groupAsAlias?.let { alias -> - val item = projectExpr(id(alias, caseSensitive(), unqualified()), alias) - projectionItems.add(item) - } - projectList(projectionItems) - } - } - return super.transformProjectionProjectStar(node) - } - - override fun transformProjectionProjectValue(node: PartiqlAst.Projection.ProjectValue): PartiqlAst.Projection = - PartiqlAst.build { - projectValue( - value = itemTransform.transformExpr(node.value), - metas = node.metas - ) - } - - override fun transformProjectionProjectList(node: PartiqlAst.Projection.ProjectList): PartiqlAst.Projection = - PartiqlAst.build { - projectList( - projectItems = node.projectItems.map { item -> - when (item) { - is PartiqlAst.ProjectItem.ProjectExpr -> { - val projectionAlias = item.asAlias ?: throw SemanticException( - err = Problem( - (item.metas.sourceLocationMeta?.toProblemLocation() ?: UNKNOWN_PROBLEM_LOCATION), - details = SemanticProblemDetails.MissingAlias - ) - ) - - projectExpr_( - expr = itemTransform.transformExpr(item.expr), - asAlias = projectionAlias - ) - } - else -> item - } - }, - metas = node.metas - ) - } - - /** - * Recursively searches through a [PartiqlAst.Projection] to find [PartiqlAst.Expr.CallAgg]'s, but does NOT recurse - * into [PartiqlAst.Expr.Select]. Designed to be called directly using [containsAggregations]. - */ - private class AggregationFinder : PartiqlAst.Visitor() { - - var hasAggregations: Boolean = false - - fun containsAggregations(node: PartiqlAst.Projection): Boolean { - this.walkProjection(node) - return this.hasAggregations.also { this.hasAggregations = false } - } - - override fun visitExprCallAgg(node: PartiqlAst.Expr.CallAgg) { - hasAggregations = true - } - - override fun walkExprSelect(node: PartiqlAst.Expr.Select) { return } - } - - /** - * This VisitorTransform: - * 1. transforms group key references (ids) to the unique name given to the group key - * 2. transforms group key references (expressions) to the unique name given to the group key (if no explicit - * user-defined alias is given. - * 3. transforms group key references (ids) to the expression that they group key represents (if the id is within - * a [PartiqlAst.Expr.CallAgg] that is in scope of the current aggregate operator) - * 4. throws exceptions when identifiers are seen within the projection list, having, or order by clauses that do - * not reference Group Keys (when the aggregate operator is defined) - * - * This also handles scoping by using the [ctxStack]. This class is designed to be used directly on the - * [PartiqlAst.Projection], the [PartiqlAst.Expr.Select.having], and the [PartiqlAst.OrderBy]. - */ - private class GroupKeyReferenceTransformer( - private val ctxStack: MutableList, - private val isWithinCallAgg: Boolean = false - ) : VisitorTransformBase() { - - override fun transformExprId(node: PartiqlAst.Expr.Id): PartiqlAst.Expr = when (this.isWithinCallAgg) { - true -> getReplacementForIdInAggregationFunction(node) - false -> getReplacementForIdOutsideOfAggregationFunction(node) - } - - override fun transformExprCallAgg(node: PartiqlAst.Expr.CallAgg): PartiqlAst.Expr = PartiqlAst.build { - val functionArgTransformer = GroupKeyReferenceTransformer(ctxStack, true) - callAgg_( - setq = transformSetQuantifier(node.setq), - funcName = node.funcName, - arg = functionArgTransformer.transformExprCallAgg_arg(node), - metas = transformMetas(node.metas) - ) - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - return AggregationVisitorTransform(ctxStack).transformExprSelect(node) - } - - override fun transformExpr(node: PartiqlAst.Expr): PartiqlAst.Expr { - return getReplacementExpression(node) ?: super.transformExpr(node) - } - - private fun getReplacementExpression(node: PartiqlAst.Expr): PartiqlAst.Expr? { - if (this.isWithinCallAgg) { return null } - - val ctxStackIter = ctxStack.listIterator(ctxStack.size) - while (ctxStackIter.hasPrevious()) { - val ctx = ctxStackIter.previous() - ctx.groupKeys.firstOrNull { key -> - key.isPublicAliasUserDefined.not() && key.represents == node - }?.let { key -> - return PartiqlAst.build { - id(key.uniqueAlias, caseSensitive(), unqualified(), emptyMetaContainer()) - } - } - } - return null - } - - /** - * IDs outside of aggregation functions should always be replaced with the Group Key unique aliases. If no - * replacement is found, we throw an EvaluationException. - */ - private fun getReplacementForIdOutsideOfAggregationFunction(node: PartiqlAst.Expr.Id): PartiqlAst.Expr { - val ctxStackIter = ctxStack.listIterator(ctxStack.size) - while (ctxStackIter.hasPrevious()) { - val ctx = ctxStackIter.previous() - getReplacementInNormalContext(node, ctx)?.let { replacementId -> return replacementId } - } - - when (ctxStack.last().hasLogicalAggregate) { - false -> return node - true -> throw EvaluationException( - "Variable not in GROUP BY or aggregation function: ${node.name.text}", - ErrorCode.EVALUATOR_VARIABLE_NOT_INCLUDED_IN_GROUP_BY, - errorContextFrom(node.metas).also { - it[Property.BINDING_NAME] = node.name.text - }, - internal = false - ) - } - } - - /** - * Called from within a CallAgg -- and therefore, all IDs should be replaced with the current context's - * [GroupKeyInformation.represents]. If not found, search for replacements within "normal" parent contexts. - */ - private fun getReplacementForIdInAggregationFunction(node: PartiqlAst.Expr.Id): PartiqlAst.Expr { - getReplacementInAggregationContext(node, ctxStack.last())?.let { replacement -> return replacement } - - val ctxStackIter = ctxStack.listIterator(ctxStack.size) - while (ctxStackIter.hasPrevious()) { - val ctx = ctxStackIter.previous() - getReplacementInNormalContext(node, ctx)?.let { replacementId -> return replacementId } - } - return node - } - - /** - * Gets replacement ID (the alias of the Group Key or Group Alias (ID)) - */ - private fun getReplacementInNormalContext(node: PartiqlAst.Expr.Id, ctx: VisitorContext): PartiqlAst.Expr.Id? { - val bindingName = BindingName(node.name.text, node.case.toBindingCase()) - val replacementKey = ctx.groupKeys.firstOrNull { key -> - bindingName.isEquivalentTo(key.publicAlias) - }?.let { key -> PartiqlAst.build { id(key.uniqueAlias, caseSensitive(), node.qualifier) } } - - return when { - replacementKey != null -> replacementKey - bindingName.isEquivalentTo(ctx.groupAsAlias) -> PartiqlAst.build { - id( - node.name.text, - caseSensitive(), - unqualified() - ) - } - else -> null - } - } - - /** - * Gets replacement Expr (what the Group Key represents, or the Group As Alias) - */ - private fun getReplacementInAggregationContext(node: PartiqlAst.Expr.Id, ctx: VisitorContext): PartiqlAst.Expr? { - val bindingName = BindingName(node.name.text, node.case.toBindingCase()) - val replacementExpression = ctx.groupKeys.firstOrNull { key -> - bindingName.isEquivalentTo(key.publicAlias) - }?.represents - - return when { - replacementExpression != null -> replacementExpression - bindingName.isEquivalentTo(ctx.groupAsAlias) -> PartiqlAst.build { - id( - node.name.text, - caseSensitive(), - unqualified() - ) - } - else -> null - } - } - } - - internal data class VisitorContext( - val groupKeys: List, - val groupAsAlias: String?, - val hasLogicalAggregate: Boolean - ) - - internal data class GroupKeyInformation( - val groupKey: PartiqlAst.GroupKey, - val represents: PartiqlAst.Expr, - val publicAlias: String, - val uniqueAlias: String, - val isPublicAliasUserDefined: Boolean - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/visitors/OrderBySortSpecVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/visitors/OrderBySortSpecVisitorTransform.kt deleted file mode 100644 index da87b41926..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/internal/visitors/OrderBySortSpecVisitorTransform.kt +++ /dev/null @@ -1,66 +0,0 @@ -package org.partiql.lang.eval.internal.visitors - -import org.partiql.lang.ast.IsTransformedOrderByAliasMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.metaContainerOf -import org.partiql.lang.eval.visitors.VisitorTransformBase -import org.partiql.pig.runtime.SymbolPrimitive - -/** - * A [PartiqlAst.VisitorTransform] to replace the [PartiqlAst.SortSpec] of a [PartiqlAst.OrderBy] with a reference to - * a [PartiqlAst.ProjectItem]'s [PartiqlAst.Expr] if an alias is provided. Also utilizes [IsTransformedOrderByAliasMeta] - * to enforce idempotency (delivering the same result through multiple passes). - * - * Turns: - * - * ```SELECT a + 1 AS b FROM c ORDER BY b``` - * - * Into: - * - * ```SELECT a + 1 AS b FROM c ORDER BY a + 1``` - */ -internal class OrderBySortSpecVisitorTransform : VisitorTransformBase() { - - private val projectionAliases: MutableMap = mutableMapOf() - - /** - * Nests itself to ensure ORDER BYs don't have access to the same [projectionAliases] - */ - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - return OrderBySortSpecVisitorTransform().transformExprSelectEvaluationOrder(node) - } - - /** - * Uses default transform and adds the alias to the [projectionAliases] map - */ - override fun transformProjectItemProjectExpr_asAlias(node: PartiqlAst.ProjectItem.ProjectExpr): SymbolPrimitive? { - val transformedAlias = super.transformProjectItemProjectExpr_asAlias(node) - if (node.asAlias != null) { projectionAliases[node.asAlias!!.text] = node.expr } - return transformedAlias - } - - /** - * Uses the [OrderByAliasSupport] class to transform any encountered IDs in ORDER BY into the appropriate - * expression using the [projectionAliases] while ensuring idempotency via [IsTransformedOrderByAliasMeta] - */ - override fun transformSortSpec_expr(node: PartiqlAst.SortSpec): PartiqlAst.Expr { - val newExpr = when (node.expr.metas.containsKey(IsTransformedOrderByAliasMeta.TAG)) { - true -> super.transformSortSpec_expr(node) - false -> OrderByAliasSupport(projectionAliases).transformSortSpec_expr(node) - } - return newExpr.copy(metas = newExpr.metas + metaContainerOf(IsTransformedOrderByAliasMeta.instance)) - } - - /** - * A [PartiqlAst.VisitorTransform] that converts any found Expr.Id's into what it is mapped to in [aliases]. - */ - private class OrderByAliasSupport(val aliases: Map) : VisitorTransformBase() { - override fun transformExprId(node: PartiqlAst.Expr.Id): PartiqlAst.Expr { - val transformedExpr = super.transformExprId(node) - return when (node.case) { - is PartiqlAst.CaseSensitivity.CaseSensitive -> aliases[node.name.text] ?: transformedExpr - else -> aliases[node.name.text.lowercase()] ?: aliases[node.name.text.toUpperCase()] ?: transformedExpr - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/io/DelimitedValues.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/io/DelimitedValues.kt deleted file mode 100644 index 7968ac67e6..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/io/DelimitedValues.kt +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.io - -import com.amazon.ion.IonDecimal -import com.amazon.ion.IonException -import com.amazon.ion.IonFloat -import com.amazon.ion.IonInt -import com.amazon.ion.IonSystem -import com.amazon.ion.IonTimestamp -import com.amazon.ion.IonType -import com.amazon.ion.IonValue -import com.amazon.ion.system.IonSystemBuilder -import org.apache.commons.csv.CSVFormat -import org.apache.commons.csv.CSVParser -import org.apache.commons.csv.CSVPrinter -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.StructOrdering -import org.partiql.lang.eval.namedValue -import org.partiql.lang.eval.orderedNames -import org.partiql.lang.eval.syntheticColumnName -import org.partiql.lang.eval.toIonValue -import org.partiql.lang.util.stringValue -import java.io.BufferedReader -import java.io.Reader -import java.io.Writer - -/** - * Provides adapters for delimited input (e.g. TSV/CSV) as lazy sequences of values. - * - * This implementation uses Apache CSVParser library and follows [RFC-4180](https://tools.ietf.org/html/rfc4180) format. - * The only difference is that it is allowed for each row to have different numbers of fields. - */ -object DelimitedValues { - /** How to convert each element. */ - enum class ConversionMode { - /** Attempt to parse each value as a scalar, and fall back to string. */ - AUTO { - override fun convert(raw: String): ExprValue = try { - val ion = IonSystemBuilder.standard().build() - when (val converted = ion.singleValue(raw)) { - is IonInt, is IonFloat, is IonDecimal, is IonTimestamp -> - ExprValue.of(converted) - // if we can't convert the above, we just use the input string as-is - else -> ExprValue.newString(raw) - } - } catch (e: IonException) { - ExprValue.newString(raw) - } - }, - /** Each field is a string. */ - NONE { - override fun convert(raw: String): ExprValue = ExprValue.newString(raw) - }; - - abstract fun convert(raw: String): ExprValue - } - - /** - * Lazily loads a stream of values from a [Reader] into a sequence backed [ExprValue]. - * This does **not** close the [Reader]. - * - * @param input The input source. - * @param csvFormat What the format of csv files is. - * @param conversionMode How column text should be converted. - */ - @JvmStatic - fun exprValue( - input: Reader, - csvFormat: CSVFormat, - conversionMode: ConversionMode - ): ExprValue { - val reader = BufferedReader(input) - val csvParser = CSVParser(reader, csvFormat) - val columns: List = csvParser.headerNames - - val seq = csvParser.asSequence().map { csvRecord -> - ExprValue.newStruct( - csvRecord.mapIndexed { i, value -> - val name = when { - i < columns.size -> columns[i] - else -> syntheticColumnName(i) - } - conversionMode.convert(value).namedValue(ExprValue.newString(name)) - }, - StructOrdering.ORDERED - ) - } - - return ExprValue.newBag(seq) - } - - // TODO make this configurable - private fun IonValue.csvStringValue(): String = when (type) { - // TODO configurable null handling - IonType.NULL, IonType.BOOL, IonType.INT, - IonType.FLOAT, IonType.DECIMAL, IonType.TIMESTAMP -> toString() - IonType.SYMBOL, IonType.STRING -> stringValue() ?: toString() - // TODO LOB/BLOB support - else -> throw IllegalArgumentException( - "Delimited data column must not be $type type" - ) - } - - /** - * Writes the given [ExprValue] to the given [Writer] as delimited text. - * The [ExprValue] **must** have the [OrderBindNames] facet (e.g. result of a `SELECT` - * expression with a list projection) in order to have the schema necessary to emit - * the columns in the right order; scalars are not allowed. - * - * @param ion The system to use. - * @param output The output sink. - * @param value The value to serialize. - * @param delimiter The column separator. - * @param newline The newline character. - * @param writeHeader Whether or not to write the header. - */ - @JvmStatic - fun writeTo( - ion: IonSystem, - output: Writer, - value: ExprValue, - delimiter: Char, - newline: String, - writeHeader: Boolean - ) { - CSVPrinter(output, CSVFormat.DEFAULT.withDelimiter(delimiter).withRecordSeparator(newline)).use { csvPrinter -> - var names: List? = null - for (row in value) { - val colNames = row.orderedNames - ?: throw IllegalArgumentException("Delimited data must be ordered tuple: $row") - if (names == null) { - // first row defines column names - names = colNames - if (writeHeader) { - csvPrinter.printRecord(names) - } - } else if (names != colNames) { // We need to check if the column names in other rows are all the same as the first one's. - throw IllegalArgumentException( - "Inconsistent row names: $colNames != $names" - ) - } - - csvPrinter.printRecord( - names.map { - val col = row.bindings[BindingName(it, BindingCase.SENSITIVE)]?.toIonValue(ion) ?: ion.newNull() - col.csvStringValue() - } - ) - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/like/Pattern.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/like/Pattern.kt deleted file mode 100644 index 49153e863b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/like/Pattern.kt +++ /dev/null @@ -1,117 +0,0 @@ -package org.partiql.lang.eval.like - -import java.util.regex.Pattern - -private const val ANY_MANY = '%'.toInt() -private const val ANY_ONE = '_'.toInt() - -private const val PATTERN_ADDITIONAL_BUFFER = 8 - -/** - * Translates a SQL-style `LIKE` pattern to a regular expression. - * - * Roughly the algorithm is to - * - call `Pattern.quote` on the literal parts of the pattern - * - translate a single `_` (with no contiguous `%`) to `.` - * - translate a consecutive `_` (with no contiguous `%`) to `.{n,n}` - * - translate any number of consecutive `%` to `.*?` - * - translate any number of consecutive `%` with a `_` contiguous to `.+?` - * - translate any number of consecutive `%` with `_` contiguous to `.{n,}?` - * - prefix the pattern translated via the above rule with '^' and suffix with '$' - * - * @param likePattern A `LIKE` match pattern (i.e. a string where '%' means zero or more and '_' means 1 ). - * @param escapeChar The escape character for the `LIKE` pattern. - * - * @return a [Pattern] which is a regular expression corresponding to the specified `LIKE` pattern. - * - * Examples: - * ``` - * val ESCAPE = '\\'.toInt() - * - * assertEquals("^.*?\\Qfoo\\E$", parsePattern("%foo", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?$", parsePattern("foo%", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%%bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%%%bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.*?\\Qbar\\E$", parsePattern("foo%%%%bar", ESCAPE).pattern()) - * assertEquals("^.*?\\Qfoo\\E.*?\\Qbar\\E.*?$", - * parsePattern("%foo%%%%bar%", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.{2,}?\\Qbar\\E$", parsePattern("foo_%_bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.{2,}?\\Qbar\\E$", parsePattern("foo_%_%bar", ESCAPE).pattern()) - * assertEquals("^\\Qfoo\\E.{2,}?\\Qbar\\E$", parsePattern("foo%_%%_%bar", ESCAPE).pattern()) - * ``` - * - * - * @see java.util.regex.Pattern - */ -internal fun parsePattern(likePattern: String, escapeChar: Int?): Pattern { - val buf = StringBuilder(likePattern.length + PATTERN_ADDITIONAL_BUFFER) - buf.append("^") - - var isEscaped = false - var wildcardMin = -1 - var wildcardUnbounded = false - val literal = StringBuilder() - - // If a wildcard (e.g. a sequence of '%' and '_') has been accumulated, write out the regex equivalent - val flushWildcard = { - if (wildcardMin != -1) { - if (wildcardUnbounded) { - when (wildcardMin) { - 0 -> buf.append(".*?") - 1 -> buf.append(".+?") - else -> buf.append(".{$wildcardMin,}?") - } - } else { - when (wildcardMin) { - 1 -> buf.append(".") - else -> buf.append(".{$wildcardMin,$wildcardMin}") - } - } - wildcardMin = -1 - wildcardUnbounded = false - } - } - - // if a literal has been accumulated, write it out, regex-quoted - val flushLiteral = { - if (literal.isNotEmpty()) { - buf.append(Pattern.quote(literal.toString())) - literal.clear() - } - } - - for (codepoint in likePattern.codePoints()) { - if (!isEscaped) { - if (codepoint == escapeChar) { - isEscaped = true - continue // skip to the next codepoint - } - when (codepoint) { - ANY_ONE -> { - flushLiteral() - wildcardMin = maxOf(wildcardMin, 0) + 1 - } - ANY_MANY -> { - flushLiteral() - wildcardMin = maxOf(wildcardMin, 0) - wildcardUnbounded = true - } - else -> { - flushWildcard() - literal.appendCodePoint(codepoint) - } - } - } else { - flushWildcard() - literal.appendCodePoint(codepoint) - isEscaped = false - } - } - - flushLiteral() - flushWildcard() - - buf.append("$") - return Pattern.compile(buf.toString()) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/EvaluatorState.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/EvaluatorState.kt deleted file mode 100644 index 0c140486c7..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/EvaluatorState.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical - -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprValue - -/** - * Contains state needed during query evaluation such as an instance of [EvaluationSession] and an array of [registers] - * for each local variable that is part of the query. - * - * Since the elements of [registers] are mutable, when/if we decide to make query execution multi-threaded, we'll have - * to take care to not share [EvaluatorState] instances among different threads. - * - * @param session The evaluation session. - */ -class EvaluatorState( - /** The current [EvaluationSession]. */ - val session: EvaluationSession, - - /** - * An array of registers containing [ExprValue]s needed during query execution. Generally, there is - * one register per local variable. This is an array (and not a [List]) because its semantics match exactly what - * we need: fixed length with mutable elements. - * - * This state should not be modified by customer provided operator implementations except through instances - * of [SetVariableFunc] that were provided by this library, thus it is marked as `internal`. - */ - internal val registers: Array -) { - internal fun load(registers: Array) = registers.forEachIndexed { index, exprValue -> - this.registers[index] = exprValue - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalBexprToThunkConverter.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalBexprToThunkConverter.kt deleted file mode 100644 index 9a89d9aa8e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalBexprToThunkConverter.kt +++ /dev/null @@ -1,326 +0,0 @@ -package org.partiql.lang.eval.physical - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.NaturalExprValueComparators -import org.partiql.lang.eval.internal.Thunk -import org.partiql.lang.eval.internal.ThunkValue -import org.partiql.lang.eval.physical.operators.AggregateOperatorFactory -import org.partiql.lang.eval.physical.operators.CompiledAggregateFunction -import org.partiql.lang.eval.physical.operators.CompiledGroupKey -import org.partiql.lang.eval.physical.operators.CompiledSortKey -import org.partiql.lang.eval.physical.operators.CompiledWindowFunction -import org.partiql.lang.eval.physical.operators.FilterRelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.JoinRelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.LetRelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.LimitRelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.OffsetRelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.ProjectRelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.RelationExpression -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactoryKey -import org.partiql.lang.eval.physical.operators.RelationalOperatorKind -import org.partiql.lang.eval.physical.operators.ScanRelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.SortOperatorFactory -import org.partiql.lang.eval.physical.operators.UnpivotOperatorFactory -import org.partiql.lang.eval.physical.operators.WindowRelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.valueExpression -import org.partiql.lang.eval.physical.window.createBuiltinWindowFunction -import org.partiql.lang.util.toIntExact - -/** A specialization of [Thunk] that we use for evaluation of physical plans. */ -internal typealias PhysicalPlanThunk = Thunk - -/** A specialization of [ThunkValue] that we use for evaluation of physical plans. */ -internal typealias PhysicalPlanThunkValue = ThunkValue - -internal class PhysicalBexprToThunkConverter( - private val exprConverter: PhysicalPlanCompiler, - private val relationalOperatorFactory: Map -) : PartiqlPhysical.Bexpr.Converter { - - private fun PhysicalPlanThunk.toValueExpr(sourceLocationMeta: SourceLocationMeta?) = - valueExpression(sourceLocationMeta) { state -> this(state) } - - private fun RelationExpression.toRelationThunk(metas: MetaContainer) = relationThunk(metas) { state -> this.evaluate(state) } - - private inline fun findOperatorFactory( - operator: RelationalOperatorKind, - name: String - ): T { - val key = RelationalOperatorFactoryKey(operator, name) - val found = - relationalOperatorFactory[key] ?: error("Factory for operator ${key.operator} named '${key.name}' does not exist.") - return found as? T - ?: error( - "Internal error: Operator factory ${key.operator} named '${key.name}' does not derive from " + - T::class.java + "." - ) - } - - override fun convertProject(node: PartiqlPhysical.Bexpr.Project): RelationThunkEnv { - // recurse into children - val argExprs = node.args.map { exprConverter.convert(it).toValueExpr(it.metas.sourceLocationMeta) } - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.PROJECT, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, node.binding.toSetVariableFunc(), argExprs) - - // wrap in thunk. - return bindingsExpr.toRelationThunk(node.metas) - } - - override fun convertAggregate(node: PartiqlPhysical.Bexpr.Aggregate): RelationThunkEnv { - val source = this.convert(node.source) - - // Compile Arguments - val compiledFunctions = node.functionList.functions.map { func -> - val setAggregateVal = func.asVar.toSetVariableFunc() - val value = exprConverter.convert(func.arg).toValueExpr(func.arg.metas.sourceLocationMeta) - CompiledAggregateFunction(func.name.text, setAggregateVal, value, func.quantifier) - } - val compiledKeys = node.groupList.keys.map { key -> - val value = exprConverter.convert(key.expr).toValueExpr(key.expr.metas.sourceLocationMeta) - val function = key.asVar.toSetVariableFunc() - CompiledGroupKey(function, value, key.asVar) - } - - // Get Implementation - val factory = findOperatorFactory(RelationalOperatorKind.AGGREGATE, node.i.name.text) - val relationExpression = factory.create(source, node.strategy, compiledKeys, compiledFunctions) - return relationExpression.toRelationThunk(node.metas) - } - - override fun convertScan(node: PartiqlPhysical.Bexpr.Scan): RelationThunkEnv { - // recurse into children - val valueExpr = exprConverter.convert(node.expr).toValueExpr(node.expr.metas.sourceLocationMeta) - val asSetter = node.asDecl.toSetVariableFunc() - val atSetter = node.atDecl?.toSetVariableFunc() - val bySetter = node.byDecl?.toSetVariableFunc() - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.SCAN, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create( - impl = node.i, - expr = valueExpr, - setAsVar = asSetter, - setAtVar = atSetter, - setByVar = bySetter - ) - - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - override fun convertUnpivot(node: PartiqlPhysical.Bexpr.Unpivot): RelationThunkEnv { - val valueExpr = exprConverter.convert(node.expr).toValueExpr(node.expr.metas.sourceLocationMeta) - val asSetter = node.asDecl.toSetVariableFunc() - val atSetter = node.atDecl?.toSetVariableFunc() - val bySetter = node.byDecl?.toSetVariableFunc() - - val factory = findOperatorFactory(RelationalOperatorKind.UNPIVOT, node.i.name.text) - - val bindingsExpr = factory.create( - expr = valueExpr, - setAsVar = asSetter, - setAtVar = atSetter, - setByVar = bySetter - ) - - return bindingsExpr.toRelationThunk(node.metas) - } - - override fun convertFilter(node: PartiqlPhysical.Bexpr.Filter): RelationThunkEnv { - // recurse into children - val predicateValueExpr = exprConverter.convert(node.predicate).toValueExpr(node.predicate.metas.sourceLocationMeta) - val sourceBindingsExpr = this.convert(node.source) - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.FILTER, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, predicateValueExpr, sourceBindingsExpr) - - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - override fun convertJoin(node: PartiqlPhysical.Bexpr.Join): RelationThunkEnv { - // recurse into children - val leftBindingsExpr = this.convert(node.left) - val rightBindingdExpr = this.convert(node.right) - val predicateValueExpr = node.predicate?.let { predicate -> - exprConverter.convert(predicate) - .takeIf { !predicate.isLitTrue() } - ?.toValueExpr(predicate.metas.sourceLocationMeta) - } - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.JOIN, node.i.name.text) - - // Compute a function to set the left-side variables to NULL. This is for use with RIGHT JOIN, when the left - // side of the join is empty or no rows match the predicate. - val leftVariableIndexes = node.left.extractAccessibleVarDecls().map { it.index.value.toIntExact() } - val setLeftSideVariablesToNull: (EvaluatorState) -> Unit = { state -> - leftVariableIndexes.forEach { state.registers[it] = ExprValue.nullValue } - } - // Compute a function to set the right-side variables to NULL. This is for use with LEFT JOIN, when the right - // side of the join is empty or no rows match the predicate. - val rightVariableIndexes = node.right.extractAccessibleVarDecls().map { it.index.value.toIntExact() } - val setRightSideVariablesToNull: (EvaluatorState) -> Unit = { state -> - rightVariableIndexes.forEach { state.registers[it] = ExprValue.nullValue } - } - - return factory.create( - impl = node.i, - joinType = node.joinType, - leftBexpr = leftBindingsExpr, - rightBexpr = rightBindingdExpr, - predicateExpr = predicateValueExpr, - setLeftSideVariablesToNull = setLeftSideVariablesToNull, - setRightSideVariablesToNull = setRightSideVariablesToNull - ).toRelationThunk(node.metas) - } - - private fun PartiqlPhysical.Bexpr.extractAccessibleVarDecls(): List = - // This fold traverses a [PartiqlPhysical.Bexpr] node and extracts all variable declarations within - // It avoids recursing into sub-queries. - object : PartiqlPhysical.VisitorFold>() { - override fun visitVarDecl( - node: PartiqlPhysical.VarDecl, - accumulator: List - ): List = accumulator + node - - /** - * Avoids recursion into expressions, since these may contain sub-queries with other var-decls that we don't - * care about here. - */ - override fun walkExpr( - node: PartiqlPhysical.Expr, - accumulator: List - ): List { - return accumulator - } - }.walkBexpr(this, emptyList()) - - override fun convertOffset(node: PartiqlPhysical.Bexpr.Offset): RelationThunkEnv { - // recurse into children - val rowCountExpr = exprConverter.convert(node.rowCount).toValueExpr(node.rowCount.metas.sourceLocationMeta) - val sourceBexpr = this.convert(node.source) - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.OFFSET, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, rowCountExpr, sourceBexpr) - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - override fun convertLimit(node: PartiqlPhysical.Bexpr.Limit): RelationThunkEnv { - // recurse into children - val rowCountExpr = exprConverter.convert(node.rowCount).toValueExpr(node.rowCount.metas.sourceLocationMeta) - val sourceBexpr = this.convert(node.source) - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.LIMIT, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, rowCountExpr, sourceBexpr) - - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - override fun convertSort(node: PartiqlPhysical.Bexpr.Sort): RelationThunkEnv { - // Compile Arguments - val source = this.convert(node.source) - val sortKeys = compileSortSpecs(node.sortSpecs) - - // Get Implementation - val factory = findOperatorFactory(RelationalOperatorKind.SORT, node.i.name.text) - val bindingsExpr = factory.create(sortKeys, source) - return bindingsExpr.toRelationThunk(node.metas) - } - - override fun convertLet(node: PartiqlPhysical.Bexpr.Let): RelationThunkEnv { - // recurse into children - val sourceBexpr = this.convert(node.source) - val compiledBindings = node.bindings.map { - VariableBinding( - it.decl.toSetVariableFunc(), - exprConverter.convert(it.value).toValueExpr(it.value.metas.sourceLocationMeta) - ) - } - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.LET, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, sourceBexpr, compiledBindings) - - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - /** - * Returns a list of [CompiledSortKey] with the aim of pre-computing the [NaturalExprValueComparators] prior to - * evaluation and leaving the [PartiqlPhysical.SortSpec]'s [PartiqlPhysical.Expr] to be evaluated later. - */ - private fun compileSortSpecs(specs: List): List = specs.map { spec -> - val comp = when (spec.orderingSpec ?: PartiqlPhysical.OrderingSpec.Asc()) { - is PartiqlPhysical.OrderingSpec.Asc -> - when (spec.nullsSpec) { - is PartiqlPhysical.NullsSpec.NullsFirst -> NaturalExprValueComparators.NULLS_FIRST_ASC - is PartiqlPhysical.NullsSpec.NullsLast -> NaturalExprValueComparators.NULLS_LAST_ASC - null -> NaturalExprValueComparators.NULLS_LAST_ASC - } - - is PartiqlPhysical.OrderingSpec.Desc -> - when (spec.nullsSpec) { - is PartiqlPhysical.NullsSpec.NullsFirst -> NaturalExprValueComparators.NULLS_FIRST_DESC - is PartiqlPhysical.NullsSpec.NullsLast -> NaturalExprValueComparators.NULLS_LAST_DESC - null -> NaturalExprValueComparators.NULLS_FIRST_DESC - } - } - val value = exprConverter.convert(spec.expr).toValueExpr(spec.expr.metas.sourceLocationMeta) - CompiledSortKey(comp, value) - } - - @OptIn(ExperimentalWindowFunctions::class) - override fun convertWindow(node: PartiqlPhysical.Bexpr.Window): RelationThunkEnv { - val source = this.convert(node.source) - - val windowPartitionList = node.windowSpecification.partitionBy - - val windowSortSpecList = node.windowSpecification.orderBy - - val compiledPartitionBy = windowPartitionList?.exprs?.map { - exprConverter.convert(it).toValueExpr(it.metas.sourceLocationMeta) - } ?: emptyList() - - val compiledOrderBy = windowSortSpecList?.sortSpecs?.let { compileSortSpecs(it) } ?: emptyList() - - val compiledWindowFunctions = node.windowExpressionList.map { windowExpression -> - CompiledWindowFunction( - createBuiltinWindowFunction(windowExpression.funcName.text), - windowExpression.args.map { exprConverter.convert(it).toValueExpr(it.metas.sourceLocationMeta) }, - windowExpression.decl - ) - } - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.WINDOW, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(source, compiledPartitionBy, compiledOrderBy, compiledWindowFunctions) - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalBexprToThunkConverterAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalBexprToThunkConverterAsync.kt deleted file mode 100644 index 5dada878e5..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalBexprToThunkConverterAsync.kt +++ /dev/null @@ -1,362 +0,0 @@ -package org.partiql.lang.eval.physical - -import com.amazon.ionelement.api.BoolElement -import com.amazon.ionelement.api.MetaContainer -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.NaturalExprValueComparators -import org.partiql.lang.eval.ThunkAsync -import org.partiql.lang.eval.ThunkValueAsync -import org.partiql.lang.eval.physical.operators.AggregateOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.CompiledAggregateFunctionAsync -import org.partiql.lang.eval.physical.operators.CompiledGroupKeyAsync -import org.partiql.lang.eval.physical.operators.CompiledSortKeyAsync -import org.partiql.lang.eval.physical.operators.CompiledWindowFunctionAsync -import org.partiql.lang.eval.physical.operators.FilterRelationalOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.JoinRelationalOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.LetRelationalOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.LimitRelationalOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.OffsetRelationalOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.ProjectRelationalOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.RelationExpressionAsync -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactory -import org.partiql.lang.eval.physical.operators.RelationalOperatorFactoryKey -import org.partiql.lang.eval.physical.operators.RelationalOperatorKind -import org.partiql.lang.eval.physical.operators.ScanRelationalOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.SortOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.UnpivotOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.WindowRelationalOperatorFactoryAsync -import org.partiql.lang.eval.physical.operators.valueExpressionAsync -import org.partiql.lang.eval.physical.window.createBuiltinWindowFunctionAsync -import org.partiql.lang.util.toIntExact - -/** Converts instances of [PartiqlPhysical.Bexpr] to any [T]. A `suspend` version of the physical plan converter - * interface is added since PIG currently does not output async functions. - */ -internal interface Converter { - suspend fun convert(node: PartiqlPhysical.Bexpr): T = when (node) { - is PartiqlPhysical.Bexpr.Project -> convertProject(node) - is PartiqlPhysical.Bexpr.Scan -> convertScan(node) - is PartiqlPhysical.Bexpr.Unpivot -> convertUnpivot(node) - is PartiqlPhysical.Bexpr.Filter -> convertFilter(node) - is PartiqlPhysical.Bexpr.Join -> convertJoin(node) - is PartiqlPhysical.Bexpr.Sort -> convertSort(node) - is PartiqlPhysical.Bexpr.Aggregate -> convertAggregate(node) - is PartiqlPhysical.Bexpr.Offset -> convertOffset(node) - is PartiqlPhysical.Bexpr.Limit -> convertLimit(node) - is PartiqlPhysical.Bexpr.Let -> convertLet(node) - is PartiqlPhysical.Bexpr.Window -> convertWindow(node) - } - - suspend fun convertProject(node: PartiqlPhysical.Bexpr.Project): T - suspend fun convertScan(node: PartiqlPhysical.Bexpr.Scan): T - suspend fun convertUnpivot(node: PartiqlPhysical.Bexpr.Unpivot): T - suspend fun convertFilter(node: PartiqlPhysical.Bexpr.Filter): T - suspend fun convertJoin(node: PartiqlPhysical.Bexpr.Join): T - suspend fun convertSort(node: PartiqlPhysical.Bexpr.Sort): T - suspend fun convertAggregate(node: PartiqlPhysical.Bexpr.Aggregate): T - suspend fun convertOffset(node: PartiqlPhysical.Bexpr.Offset): T - suspend fun convertLimit(node: PartiqlPhysical.Bexpr.Limit): T - suspend fun convertLet(node: PartiqlPhysical.Bexpr.Let): T - suspend fun convertWindow(node: PartiqlPhysical.Bexpr.Window): T -} - -/** A specialization of [ThunkAsync] that we use for evaluation of physical plans. */ -internal typealias PhysicalPlanThunkAsync = ThunkAsync - -/** A specialization of [ThunkValueAsync] that we use for evaluation of physical plans. */ -internal typealias PhysicalPlanThunkValueAsync = ThunkValueAsync - -internal class PhysicalBexprToThunkConverterAsync( - private val exprConverter: PhysicalPlanCompilerAsync, - private val relationalOperatorFactory: Map -) : Converter { - - private fun PhysicalPlanThunkAsync.toValueExpr(sourceLocationMeta: SourceLocationMeta?) = - valueExpressionAsync(sourceLocationMeta) { state -> this(state) } - - private suspend fun RelationExpressionAsync.toRelationThunk(metas: MetaContainer) = - relationThunkAsync(metas) { state -> this.evaluate(state) } - - private inline fun findOperatorFactory( - operator: RelationalOperatorKind, - name: String - ): T { - val key = RelationalOperatorFactoryKey(operator, name) - val found = - relationalOperatorFactory[key] ?: error("Factory for operator ${key.operator} named '${key.name}' does not exist.") - return found as? T - ?: error( - "Internal error: Operator factory ${key.operator} named '${key.name}' does not derive from " + - T::class.java + "." - ) - } - - override suspend fun convertProject(node: PartiqlPhysical.Bexpr.Project): RelationThunkEnvAsync { - // recurse into children - val argExprs = node.args.map { exprConverter.convert(it).toValueExpr(it.metas.sourceLocationMeta) } - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.PROJECT, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, node.binding.toSetVariableFunc(), argExprs) - - // wrap in thunk. - return bindingsExpr.toRelationThunk(node.metas) - } - - override suspend fun convertAggregate(node: PartiqlPhysical.Bexpr.Aggregate): RelationThunkEnvAsync { - val source = this.convert(node.source) - - // Compile Arguments - val compiledFunctions = node.functionList.functions.map { func -> - val setAggregateVal = func.asVar.toSetVariableFunc() - val value = exprConverter.convert(func.arg).toValueExpr(func.arg.metas.sourceLocationMeta) - CompiledAggregateFunctionAsync(func.name.text, setAggregateVal, value, func.quantifier) - } - val compiledKeys = node.groupList.keys.map { key -> - val value = exprConverter.convert(key.expr).toValueExpr(key.expr.metas.sourceLocationMeta) - val function = key.asVar.toSetVariableFunc() - CompiledGroupKeyAsync(function, value, key.asVar) - } - - // Get Implementation - val factory = findOperatorFactory(RelationalOperatorKind.AGGREGATE, node.i.name.text) - val relationExpression = factory.create({ state -> source.invoke(state) }, node.strategy, compiledKeys, compiledFunctions) - return relationExpression.toRelationThunk(node.metas) - } - - override suspend fun convertScan(node: PartiqlPhysical.Bexpr.Scan): RelationThunkEnvAsync { - // recurse into children - val valueExpr = exprConverter.convert(node.expr).toValueExpr(node.expr.metas.sourceLocationMeta) - val asSetter = node.asDecl.toSetVariableFunc() - val atSetter = node.atDecl?.toSetVariableFunc() - val bySetter = node.byDecl?.toSetVariableFunc() - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.SCAN, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create( - impl = node.i, - expr = valueExpr, - setAsVar = asSetter, - setAtVar = atSetter, - setByVar = bySetter - ) - - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - override suspend fun convertUnpivot(node: PartiqlPhysical.Bexpr.Unpivot): RelationThunkEnvAsync { - val valueExpr = exprConverter.convert(node.expr).toValueExpr(node.expr.metas.sourceLocationMeta) - val asSetter = node.asDecl.toSetVariableFunc() - val atSetter = node.atDecl?.toSetVariableFunc() - val bySetter = node.byDecl?.toSetVariableFunc() - - val factory = findOperatorFactory(RelationalOperatorKind.UNPIVOT, node.i.name.text) - - val bindingsExpr = factory.create( - expr = valueExpr, - setAsVar = asSetter, - setAtVar = atSetter, - setByVar = bySetter - ) - - return bindingsExpr.toRelationThunk(node.metas) - } - - override suspend fun convertFilter(node: PartiqlPhysical.Bexpr.Filter): RelationThunkEnvAsync { - // recurse into children - val predicateValueExpr = exprConverter.convert(node.predicate).toValueExpr(node.predicate.metas.sourceLocationMeta) - val sourceBindingsExpr = this.convert(node.source) - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.FILTER, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, predicateValueExpr) { state -> sourceBindingsExpr.invoke(state) } - - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - override suspend fun convertJoin(node: PartiqlPhysical.Bexpr.Join): RelationThunkEnvAsync { - // recurse into children - val leftBindingsExpr = this.convert(node.left) - val rightBindingsExpr = this.convert(node.right) - val predicateValueExpr = node.predicate?.let { predicate -> - exprConverter.convert(predicate) - .takeIf { !predicate.isLitTrue() } - ?.toValueExpr(predicate.metas.sourceLocationMeta) - } - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.JOIN, node.i.name.text) - - // Compute a function to set the left-side variables to NULL. This is for use with RIGHT JOIN, when the left - // side of the join is empty or no rows match the predicate. - val leftVariableIndexes = node.left.extractAccessibleVarDecls().map { it.index.value.toIntExact() } - val setLeftSideVariablesToNull: (EvaluatorState) -> Unit = { state -> - leftVariableIndexes.forEach { state.registers[it] = ExprValue.nullValue } - } - // Compute a function to set the right-side variables to NULL. This is for use with LEFT JOIN, when the right - // side of the join is empty or no rows match the predicate. - val rightVariableIndexes = node.right.extractAccessibleVarDecls().map { it.index.value.toIntExact() } - val setRightSideVariablesToNull: (EvaluatorState) -> Unit = { state -> - rightVariableIndexes.forEach { state.registers[it] = ExprValue.nullValue } - } - - return factory.create( - impl = node.i, - joinType = node.joinType, - leftBexpr = { state -> leftBindingsExpr(state) }, - rightBexpr = { state -> rightBindingsExpr(state) }, - predicateExpr = predicateValueExpr, - setLeftSideVariablesToNull = setLeftSideVariablesToNull, - setRightSideVariablesToNull = setRightSideVariablesToNull - ).toRelationThunk(node.metas) - } - - private fun PartiqlPhysical.Bexpr.extractAccessibleVarDecls(): List = - // This fold traverses a [PartiqlPhysical.Bexpr] node and extracts all variable declarations within - // It avoids recursing into sub-queries. - object : PartiqlPhysical.VisitorFold>() { - override fun visitVarDecl( - node: PartiqlPhysical.VarDecl, - accumulator: List - ): List = accumulator + node - - /** - * Avoids recursion into expressions, since these may contain sub-queries with other var-decls that we don't - * care about here. - */ - override fun walkExpr( - node: PartiqlPhysical.Expr, - accumulator: List - ): List { - return accumulator - } - }.walkBexpr(this, emptyList()) - - override suspend fun convertOffset(node: PartiqlPhysical.Bexpr.Offset): RelationThunkEnvAsync { - // recurse into children - val rowCountExpr = exprConverter.convert(node.rowCount).toValueExpr(node.rowCount.metas.sourceLocationMeta) - val sourceBexpr = this.convert(node.source) - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.OFFSET, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, rowCountExpr) { state -> sourceBexpr(state) } - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - override suspend fun convertLimit(node: PartiqlPhysical.Bexpr.Limit): RelationThunkEnvAsync { - // recurse into children - val rowCountExpr = exprConverter.convert(node.rowCount).toValueExpr(node.rowCount.metas.sourceLocationMeta) - val sourceBexpr = this.convert(node.source) - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.LIMIT, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, rowCountExpr) { state -> sourceBexpr(state) } - - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - override suspend fun convertSort(node: PartiqlPhysical.Bexpr.Sort): RelationThunkEnvAsync { - // Compile Arguments - val source = this.convert(node.source) - val sortKeys = compileSortSpecsAsync(node.sortSpecs) - - // Get Implementation - val factory = findOperatorFactory(RelationalOperatorKind.SORT, node.i.name.text) - val bindingsExpr = factory.create(sortKeys) { state -> source(state) } - return bindingsExpr.toRelationThunk(node.metas) - } - - override suspend fun convertLet(node: PartiqlPhysical.Bexpr.Let): RelationThunkEnvAsync { - // recurse into children - val sourceBexpr = this.convert(node.source) - val compiledBindings = node.bindings.map { - VariableBindingAsync( - it.decl.toSetVariableFunc(), - exprConverter.convert(it.value).toValueExpr(it.value.metas.sourceLocationMeta) - ) - } - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.LET, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create(node.i, { state -> sourceBexpr(state) }, compiledBindings) - - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } - - /** - * Returns a list of [CompiledSortKeyAsync] with the aim of pre-computing the [NaturalExprValueComparators] prior to - * evaluation and leaving the [PartiqlPhysical.SortSpec]'s [PartiqlPhysical.Expr] to be evaluated later. - */ - private suspend fun compileSortSpecsAsync(specs: List): List = specs.map { spec -> - val comp = when (spec.orderingSpec ?: PartiqlPhysical.OrderingSpec.Asc()) { - is PartiqlPhysical.OrderingSpec.Asc -> - when (spec.nullsSpec) { - is PartiqlPhysical.NullsSpec.NullsFirst -> NaturalExprValueComparators.NULLS_FIRST_ASC - is PartiqlPhysical.NullsSpec.NullsLast -> NaturalExprValueComparators.NULLS_LAST_ASC - null -> NaturalExprValueComparators.NULLS_LAST_ASC - } - - is PartiqlPhysical.OrderingSpec.Desc -> - when (spec.nullsSpec) { - is PartiqlPhysical.NullsSpec.NullsFirst -> NaturalExprValueComparators.NULLS_FIRST_DESC - is PartiqlPhysical.NullsSpec.NullsLast -> NaturalExprValueComparators.NULLS_LAST_DESC - null -> NaturalExprValueComparators.NULLS_FIRST_DESC - } - } - val value = exprConverter.convert(spec.expr).toValueExpr(spec.expr.metas.sourceLocationMeta) - CompiledSortKeyAsync(comp, value) - } - - @OptIn(ExperimentalWindowFunctions::class) - override suspend fun convertWindow(node: PartiqlPhysical.Bexpr.Window): RelationThunkEnvAsync { - val source = this.convert(node.source) - - val windowPartitionList = node.windowSpecification.partitionBy - - val windowSortSpecList = node.windowSpecification.orderBy - - val compiledPartitionBy = windowPartitionList?.exprs?.map { - exprConverter.convert(it).toValueExpr(it.metas.sourceLocationMeta) - } ?: emptyList() - - val compiledOrderBy = windowSortSpecList?.sortSpecs?.let { compileSortSpecsAsync(it) } ?: emptyList() - - val compiledWindowFunctions = node.windowExpressionList.map { windowExpression -> - CompiledWindowFunctionAsync( - createBuiltinWindowFunctionAsync(windowExpression.funcName.text), - windowExpression.args.map { exprConverter.convert(it).toValueExpr(it.metas.sourceLocationMeta) }, - windowExpression.decl - ) - } - - // locate operator factory - val factory = findOperatorFactory(RelationalOperatorKind.WINDOW, node.i.name.text) - - // create operator implementation - val bindingsExpr = factory.create({ state -> source(state) }, compiledPartitionBy, compiledOrderBy, compiledWindowFunctions) - // wrap in thunk - return bindingsExpr.toRelationThunk(node.metas) - } -} - -internal fun PartiqlPhysical.Expr.isLitTrue() = - this is PartiqlPhysical.Expr.Lit && this.value is BoolElement && this.value.booleanValue diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompiler.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompiler.kt deleted file mode 100644 index 9511060c8b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompiler.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.partiql.lang.eval.physical - -import org.partiql.lang.domains.PartiqlPhysical - -/** - * Simple API that defines a method to convert a [PartiqlPhysical.Expr] to a [PhysicalPlanThunk]. - * - * Intended to prevent [PhysicalBexprToThunkConverter] from having to take a direct dependency on - * [org.partiql.lang.eval.EvaluatingCompiler]. - */ -internal interface PhysicalPlanCompiler { - fun convert(expr: PartiqlPhysical.Expr): PhysicalPlanThunk -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerAsync.kt deleted file mode 100644 index 25efe3f114..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerAsync.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.partiql.lang.eval.physical - -import org.partiql.lang.domains.PartiqlPhysical - -/** - * Simple API that defines a method to convert a [PartiqlPhysical.Expr] to a [PhysicalPlanThunkAsync]. - * - * Intended to prevent [PhysicalBexprToThunkConverterAsync] from having to take a direct dependency on - * [org.partiql.lang.eval.EvaluatingCompiler]. - */ -internal interface PhysicalPlanCompilerAsync { - suspend fun convert(expr: PartiqlPhysical.Expr): PhysicalPlanThunkAsync -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerAsyncImpl.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerAsyncImpl.kt deleted file mode 100644 index 89430245e7..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerAsyncImpl.kt +++ /dev/null @@ -1,1899 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical - -import com.amazon.ion.IonString -import com.amazon.ion.IonValue -import com.amazon.ion.Timestamp -import com.amazon.ion.system.IonSystemBuilder -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.emptyMetaContainer -import com.amazon.ionelement.api.toIonValue -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.flow.withIndex -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.IsOrderedMeta -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.ast.sourceLocation -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.domains.staticType -import org.partiql.lang.domains.toBindingCase -import org.partiql.lang.eval.AnyOfCastTable -import org.partiql.lang.eval.ArityMismatchException -import org.partiql.lang.eval.BaseExprValue -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.CastFunc -import org.partiql.lang.eval.DEFAULT_COMPARATOR -import org.partiql.lang.eval.ErrorDetails -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueBagOp -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.Expression -import org.partiql.lang.eval.ExpressionAsync -import org.partiql.lang.eval.FunctionNotFoundException -import org.partiql.lang.eval.Named -import org.partiql.lang.eval.PartiQLResult -import org.partiql.lang.eval.ProjectionIterationBehavior -import org.partiql.lang.eval.StructOrdering -import org.partiql.lang.eval.ThunkValueAsync -import org.partiql.lang.eval.TypedOpBehavior -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure -import org.partiql.lang.eval.call -import org.partiql.lang.eval.cast -import org.partiql.lang.eval.compareTo -import org.partiql.lang.eval.createErrorSignaler -import org.partiql.lang.eval.createThunkFactoryAsync -import org.partiql.lang.eval.distinct -import org.partiql.lang.eval.err -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.errorIf -import org.partiql.lang.eval.exprEquals -import org.partiql.lang.eval.fillErrorContext -import org.partiql.lang.eval.impl.FunctionManager -import org.partiql.lang.eval.isNotUnknown -import org.partiql.lang.eval.isUnknown -import org.partiql.lang.eval.like.parsePattern -import org.partiql.lang.eval.longValue -import org.partiql.lang.eval.namedValue -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.rangeOver -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.sourceLocationMeta -import org.partiql.lang.eval.stringValue -import org.partiql.lang.eval.syntheticColumnName -import org.partiql.lang.eval.time.Time -import org.partiql.lang.eval.timestampValue -import org.partiql.lang.eval.unnamedValue -import org.partiql.lang.planner.EvaluatorOptions -import org.partiql.lang.types.StaticTypeUtils.getRuntimeType -import org.partiql.lang.types.StaticTypeUtils.isInstance -import org.partiql.lang.types.StaticTypeUtils.staticTypeFromExprValue -import org.partiql.lang.types.TypedOpParameter -import org.partiql.lang.types.UnknownArguments -import org.partiql.lang.types.toTypedOpParameter -import org.partiql.lang.util.checkThreadInterrupted -import org.partiql.lang.util.codePointSequence -import org.partiql.lang.util.div -import org.partiql.lang.util.exprValue -import org.partiql.lang.util.isZero -import org.partiql.lang.util.minus -import org.partiql.lang.util.plus -import org.partiql.lang.util.rem -import org.partiql.lang.util.stringValue -import org.partiql.lang.util.times -import org.partiql.lang.util.toIntExact -import org.partiql.lang.util.totalMinutes -import org.partiql.lang.util.unaryMinus -import org.partiql.types.AnyOfType -import org.partiql.types.AnyType -import org.partiql.types.IntType -import org.partiql.types.SingleType -import org.partiql.types.StaticType -import org.partiql.types.UnsupportedTypeCheckException -import java.util.LinkedList -import java.util.TreeSet -import java.util.regex.Pattern - -/** - * A basic "compiler" that converts an instance of [PartiqlPhysical.Expr] to an [Expression]. - * - * This is a modified copy of the legacy `EvaluatingCompiler` class, which is now legacy. - * The primary differences between this class an `EvaluatingCompiler` are: - * - * - All references to `PartiqlPhysical` are replaced with `PartiqlPhysical`. - * - `EvaluatingCompiler` compiles "monolithic" SFW queries--this class compiles relational - * operators (in concert with [PhysicalBexprToThunkConverter]). - * - * This implementation produces a "compiled" form consisting of context-threaded - * code in the form of a tree of [PhysicalPlanThunkAsync]s. An overview of this technique can be found - * [here][1]. - * - * **Note:** *threaded* in this context is used in how the code gets *threaded* together for - * interpretation and **not** the concurrency primitive. That is to say this code is NOT thread - * safe. - * - * [1]: https://www.complang.tuwien.ac.at/anton/lvas/sem06w/fest.pdf - */ -internal class PhysicalPlanCompilerAsyncImpl( - private val functions: List, - private val customTypedOpParameters: Map, - private val procedures: Map, - private val evaluatorOptions: EvaluatorOptions = EvaluatorOptions.standard(), - private val bexperConverter: PhysicalBexprToThunkConverterAsync, -) : PhysicalPlanCompilerAsync { - @Deprecated("Use constructor with List instead", level = DeprecationLevel.WARNING) - constructor( - functions: Map, - customTypedOpParameters: Map, - procedures: Map, - evaluatorOptions: EvaluatorOptions = EvaluatorOptions.standard(), - bexperConverter: PhysicalBexprToThunkConverterAsync - ) : this( - functions = functions.values.toList(), - customTypedOpParameters = customTypedOpParameters, - procedures = procedures, - evaluatorOptions = evaluatorOptions, - bexperConverter = bexperConverter - ) - - // TODO: remove this once we migrate from `IonValue` to `IonElement`. - private val ion = IonSystemBuilder.standard().build() - - private val errorSignaler = evaluatorOptions.typingMode.createErrorSignaler() - private val thunkFactory = evaluatorOptions.typingMode.createThunkFactoryAsync(evaluatorOptions.thunkOptions) - - private val functionManager = FunctionManager(functions) - - private fun Boolean.exprValue(): ExprValue = ExprValue.newBoolean(this) - private fun String.exprValue(): ExprValue = ExprValue.newString(this) - - /** - * Compiles a [PartiqlPhysical.Statement] tree to an [ExpressionAsync]. - * - * Checks [Thread.interrupted] before every expression and sub-expression is compiled - * and throws [InterruptedException] if [Thread.interrupted] it has been set in the - * hope that long-running compilations may be aborted by the caller. - */ - suspend fun compile(plan: PartiqlPhysical.Plan): ExpressionAsync { - val thunk = compileAstStatement(plan.stmt) - - return object : ExpressionAsync { - override suspend fun eval(session: EvaluationSession): PartiQLResult { - val env = EvaluatorState( - session = session, - registers = Array(plan.locals.size) { ExprValue.missingValue } - ) - val value = thunk(env) - return PartiQLResult.Value(value = value) - } - } - } - - /** - * Compiles a [PartiqlPhysical.Expr] tree to an [ExpressionAsync]. - * - * Checks [Thread.interrupted] before every expression and sub-expression is compiled - * and throws [InterruptedException] if [Thread.interrupted] it has been set in the - * hope that long-running compilations may be aborted by the caller. - */ - internal suspend fun compile(expr: PartiqlPhysical.Expr, localsSize: Int): ExpressionAsync { - val thunk = compileAstExpr(expr) - - return object : ExpressionAsync { - override suspend fun eval(session: EvaluationSession): PartiQLResult { - val env = EvaluatorState( - session = session, - registers = Array(localsSize) { ExprValue.missingValue } - ) - val value = thunk(env) - return PartiQLResult.Value(value = value) - } - } - } - - override suspend fun convert(expr: PartiqlPhysical.Expr): PhysicalPlanThunkAsync = this.compileAstExpr(expr) - - /** - * Compiles the specified [PartiqlPhysical.Statement] into a [PhysicalPlanThunkAsync]. - * - * This function will [InterruptedException] if [Thread.interrupted] has been set. - */ - private suspend fun compileAstStatement(ast: PartiqlPhysical.Statement): PhysicalPlanThunkAsync { - return when (ast) { - is PartiqlPhysical.Statement.Query -> compileAstExpr(ast.expr) - is PartiqlPhysical.Statement.Exec -> compileExec(ast) - is PartiqlPhysical.Statement.Dml, - is PartiqlPhysical.Statement.Explain -> { - val value = ExprValue.newBoolean(true) - thunkFactory.thunkEnvAsync(emptyMetaContainer()) { value } - } - } - } - - private suspend fun compileAstExpr(expr: PartiqlPhysical.Expr): PhysicalPlanThunkAsync { - checkThreadInterrupted() - val metas = expr.metas - - return when (expr) { - is PartiqlPhysical.Expr.Lit -> compileLit(expr, metas) - is PartiqlPhysical.Expr.Missing -> compileMissing(metas) - is PartiqlPhysical.Expr.LocalId -> compileLocalId(expr, metas) - is PartiqlPhysical.Expr.GlobalId -> compileGlobalId(expr) - is PartiqlPhysical.Expr.SimpleCase -> compileSimpleCase(expr, metas) - is PartiqlPhysical.Expr.SearchedCase -> compileSearchedCase(expr, metas) - is PartiqlPhysical.Expr.Path -> compilePath(expr, metas) - is PartiqlPhysical.Expr.Struct -> compileStruct(expr) - is PartiqlPhysical.Expr.Parameter -> compileParameter(expr, metas) - is PartiqlPhysical.Expr.Date -> compileDate(expr, metas) - is PartiqlPhysical.Expr.LitTime -> compileLitTime(expr, metas) - - // arithmetic operations - is PartiqlPhysical.Expr.Plus -> compilePlus(expr, metas) - is PartiqlPhysical.Expr.Times -> compileTimes(expr, metas) - is PartiqlPhysical.Expr.Minus -> compileMinus(expr, metas) - is PartiqlPhysical.Expr.Divide -> compileDivide(expr, metas) - is PartiqlPhysical.Expr.Modulo -> compileModulo(expr, metas) - is PartiqlPhysical.Expr.BitwiseAnd -> compileBitwiseAnd(expr, metas) - - // comparison operators - is PartiqlPhysical.Expr.And -> compileAnd(expr, metas) - is PartiqlPhysical.Expr.Between -> compileBetween(expr, metas) - is PartiqlPhysical.Expr.Eq -> compileEq(expr, metas) - is PartiqlPhysical.Expr.Gt -> compileGt(expr, metas) - is PartiqlPhysical.Expr.Gte -> compileGte(expr, metas) - is PartiqlPhysical.Expr.Lt -> compileLt(expr, metas) - is PartiqlPhysical.Expr.Lte -> compileLte(expr, metas) - is PartiqlPhysical.Expr.Like -> compileLike(expr, metas) - is PartiqlPhysical.Expr.InCollection -> compileIn(expr, metas) - - // logical operators - is PartiqlPhysical.Expr.Ne -> compileNe(expr, metas) - is PartiqlPhysical.Expr.Or -> compileOr(expr, metas) - - // unary - is PartiqlPhysical.Expr.Not -> compileNot(expr, metas) - is PartiqlPhysical.Expr.Pos -> compilePos(expr, metas) - is PartiqlPhysical.Expr.Neg -> compileNeg(expr, metas) - - // other operators - is PartiqlPhysical.Expr.Concat -> compileConcat(expr, metas) - is PartiqlPhysical.Expr.Call -> compileCall(expr, metas) - is PartiqlPhysical.Expr.NullIf -> compileNullIf(expr, metas) - is PartiqlPhysical.Expr.Coalesce -> compileCoalesce(expr, metas) - - // "typed" operators (RHS is a data type and not an expression) - is PartiqlPhysical.Expr.Cast -> compileCast(expr, metas) - is PartiqlPhysical.Expr.IsType -> compileIs(expr, metas) - is PartiqlPhysical.Expr.CanCast -> compileCanCast(expr, metas) - is PartiqlPhysical.Expr.CanLosslessCast -> compileCanLosslessCast(expr, metas) - - // sequence constructors - is PartiqlPhysical.Expr.List -> compileSeq(ExprValueType.LIST, expr.values, metas) - is PartiqlPhysical.Expr.Sexp -> compileSeq(ExprValueType.SEXP, expr.values, metas) - is PartiqlPhysical.Expr.Bag -> compileSeq(ExprValueType.BAG, expr.values, metas) - - // bag operators - is PartiqlPhysical.Expr.BagOp -> compileBagOp(expr, metas) - is PartiqlPhysical.Expr.BindingsToValues -> compileBindingsToValues(expr) - is PartiqlPhysical.Expr.Pivot -> compilePivot(expr, metas) - is PartiqlPhysical.Expr.GraphMatch -> TODO("Physical compilation of GraphMatch expression") - is PartiqlPhysical.Expr.Timestamp -> TODO() - } - } - - private suspend fun compileBindingsToValues(expr: PartiqlPhysical.Expr.BindingsToValues): PhysicalPlanThunkAsync { - val mapThunk = compileAstExpr(expr.exp) - val bexprThunk: RelationThunkEnvAsync = bexperConverter.convert(expr.query) - - val relationType = when (expr.metas.containsKey(IsOrderedMeta.tag)) { - true -> RelationType.LIST - false -> RelationType.BAG - } - - return thunkFactory.thunkEnvAsync(expr.metas) { env -> - // we create a snapshot for currentRegister to use during the evaluation - // this is to avoid issue when iterator planner result - val currentRegister = env.registers.clone() - val elements: Flow = flow { - env.load(currentRegister) - val relItr = bexprThunk(env) - while (relItr.nextRow()) { - emit(mapThunk(env)) - } - } - when (relationType) { - RelationType.LIST -> ExprValue.newList(elements.toList()) - RelationType.BAG -> ExprValue.newBag(elements.toList()) - } - } - } - - private suspend fun compileAstExprs(args: List) = args.map { compileAstExpr(it) } - - private suspend fun compileNullIf(expr: PartiqlPhysical.Expr.NullIf, metas: MetaContainer): PhysicalPlanThunkAsync { - val expr1Thunk = compileAstExpr(expr.expr1) - val expr2Thunk = compileAstExpr(expr.expr2) - - // Note: NULLIF does not propagate the unknown values and .exprEquals provides the correct semantics. - return thunkFactory.thunkEnvAsync(metas) { env -> - val expr1Value = expr1Thunk(env) - val expr2Value = expr2Thunk(env) - when { - expr1Value.exprEquals(expr2Value) -> ExprValue.nullValue - else -> expr1Value - } - } - } - - private suspend fun compileCoalesce(expr: PartiqlPhysical.Expr.Coalesce, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.args) - - return thunkFactory.thunkEnvAsync(metas) { env -> - var nullFound = false - var knownValue: ExprValue? = null - for (thunk in argThunks) { - val argValue = thunk(env) - if (argValue.isNotUnknown()) { - knownValue = argValue - // No need to execute remaining thunks to save computation as first non-unknown value is found - break - } - if (argValue.type == ExprValueType.NULL) { - nullFound = true - } - } - when (knownValue) { - null -> when { - evaluatorOptions.typingMode == TypingMode.PERMISSIVE && !nullFound -> ExprValue.missingValue - else -> ExprValue.nullValue - } - else -> knownValue - } - } - } - - /** - * Returns a function that accepts an [ExprValue] as an argument and returns true it is `NULL`, `MISSING`, or - * within the range specified by [range]. - */ - private fun integerValueValidator( - range: LongRange - ): (ExprValue) -> Boolean = { value -> - when (value.type) { - ExprValueType.NULL, ExprValueType.MISSING -> true - ExprValueType.INT -> { - val longValue: Long = value.scalar.numberValue()?.toLong() - ?: error( - "ExprValue.numberValue() must not be `NULL` when its type is INT." + - "This indicates that the ExprValue instance has a bug." - ) - - // PRO-TIP: make sure to use the `Long` primitive type here with `.contains` otherwise - // Kotlin will use the version of `.contains` that treats [range] as a collection, and it will - // be very slow! - range.contains(longValue) - } - else -> error( - "The expression's static type was supposed to be INT but instead it was ${value.type}" + - "This may indicate the presence of a bug in the type inferencer." - ) - } - } - - /** - * For operators which could return integer type, check integer overflow in case of [TypingMode.PERMISSIVE]. - */ - private suspend fun checkIntegerOverflow(computeThunk: PhysicalPlanThunkAsync, metas: MetaContainer): PhysicalPlanThunkAsync = - when (val staticTypes = metas.staticType?.type?.getTypes()) { - // No staticType, can't validate integer size. - null -> computeThunk - else -> { - when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> { - // integer size constraints have not been tested under [TypingMode.LEGACY] because the - // [StaticTypeInferenceVisitorTransform] doesn't support being used with legacy mode yet. - // throw an exception in case we encounter this untested scenario. This might work fine, but I - // wouldn't bet on it. - val hasConstrainedInteger = staticTypes.any { - it is IntType && it.rangeConstraint != IntType.IntRangeConstraint.UNCONSTRAINED - } - if (hasConstrainedInteger) { - TODO("Legacy mode doesn't support integer size constraints yet.") - } else { - computeThunk - } - } - TypingMode.PERMISSIVE -> { - val biggestIntegerType = staticTypes.filterIsInstance().maxByOrNull { - it.rangeConstraint.numBytes - } - when (biggestIntegerType) { - is IntType -> { - val validator = integerValueValidator(biggestIntegerType.rangeConstraint.validRange) - - thunkFactory.thunkEnvAsync(metas) { env -> - val naryResult = computeThunk(env) - errorSignaler.errorIf( - !validator(naryResult), - ErrorCode.EVALUATOR_INTEGER_OVERFLOW, - { ErrorDetails(metas, "Integer overflow", errorContextFrom(metas)) }, - { naryResult } - ) - } - } - // If there is no IntType StaticType, can't validate the integer size either. - null -> computeThunk - else -> computeThunk - } - } - } - } - } - - private suspend fun compilePlus(expr: PartiqlPhysical.Expr.Plus, metas: MetaContainer): PhysicalPlanThunkAsync { - if (expr.operands.size < 2) { - error("Internal Error: PartiqlPhysical.Expr.Plus must have at least 2 arguments") - } - - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.numberValue() + rValue.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private suspend fun compileMinus(expr: PartiqlPhysical.Expr.Minus, metas: MetaContainer): PhysicalPlanThunkAsync { - if (expr.operands.size < 2) { - error("Internal Error: PartiqlPhysical.Expr.Minus must have at least 2 arguments") - } - - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.numberValue() - rValue.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private suspend fun compilePos(expr: PartiqlPhysical.Expr.Pos, metas: MetaContainer): PhysicalPlanThunkAsync { - val exprThunk = compileAstExpr(expr.expr) - - val computeThunk = thunkFactory.thunkEnvOperands(metas, exprThunk) { _, value -> - // Invoking .numberValue() here makes this essentially just a type check - value.numberValue() - // Original value is returned unmodified. - value - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private suspend fun compileNeg(expr: PartiqlPhysical.Expr.Neg, metas: MetaContainer): PhysicalPlanThunkAsync { - val exprThunk = compileAstExpr(expr.expr) - - val computeThunk = thunkFactory.thunkEnvOperands(metas, exprThunk) { _, value -> - (-value.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private suspend fun compileTimes(expr: PartiqlPhysical.Expr.Times, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.numberValue() * rValue.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private suspend fun compileDivide(expr: PartiqlPhysical.Expr.Divide, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - val denominator = rValue.numberValue() - - errorSignaler.errorIf( - denominator.isZero(), - ErrorCode.EVALUATOR_DIVIDE_BY_ZERO, - { ErrorDetails(metas, "/ by zero") } - ) { - try { - (lValue.numberValue() / denominator).exprValue() - } catch (e: ArithmeticException) { - // Setting the internal flag as true as it is not clear what - // ArithmeticException may be thrown by the above - throw EvaluationException( - cause = e, - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = true - ) - } - } - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private suspend fun compileModulo(expr: PartiqlPhysical.Expr.Modulo, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - val denominator = rValue.numberValue() - if (denominator.isZero()) { - err("% by zero", ErrorCode.EVALUATOR_MODULO_BY_ZERO, errorContextFrom(metas), internal = false) - } - - (lValue.numberValue() % denominator).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private suspend fun compileBitwiseAnd(expr: PartiqlPhysical.Expr.BitwiseAnd, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.longValue() and rValue.longValue()).exprValue() - } - } - - private suspend fun compileEq(expr: PartiqlPhysical.Expr.Eq, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> - (lValue.exprEquals(rValue)) - } - } - - private suspend fun compileNe(expr: PartiqlPhysical.Expr.Ne, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - ((!lValue.exprEquals(rValue)).exprValue()) - } - } - - private suspend fun compileLt(expr: PartiqlPhysical.Expr.Lt, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue < rValue } - } - - private suspend fun compileLte(expr: PartiqlPhysical.Expr.Lte, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue <= rValue } - } - - private suspend fun compileGt(expr: PartiqlPhysical.Expr.Gt, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue > rValue } - } - - private suspend fun compileGte(expr: PartiqlPhysical.Expr.Gte, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue >= rValue } - } - - private suspend fun compileBetween(expr: PartiqlPhysical.Expr.Between, metas: MetaContainer): PhysicalPlanThunkAsync { - val valueThunk = compileAstExpr(expr.value) - val fromThunk = compileAstExpr(expr.from) - val toThunk = compileAstExpr(expr.to) - - return thunkFactory.thunkEnvOperands(metas, valueThunk, fromThunk, toThunk) { _, v, f, t -> - (v >= f && v <= t).exprValue() - } - } - - /** - * `IN` can *almost* be thought of has being syntactic sugar for the `OR` operator. - * - * `a IN (b, c, d)` is equivalent to `a = b OR a = c OR a = d`. On deep inspection, there - * are important implications to this regarding propagation of unknown values. Specifically, the - * presence of any unknown in `b`, `c`, or `d` will result in unknown propagation iif `a` does not - * equal `b`, `c`, or `d`. i.e.: - * - * - `1 in (null, 2, 3)` -> `null` - * - `2 in (null, 2, 3)` -> `true` - * - `2 in (1, 2, 3)` -> `true` - * - `0 in (1, 2, 4)` -> `false` - * - * `IN` is varies from the `OR` operator in that this behavior holds true when other types of expressions are - * used on the right side of `IN` such as sub-queries and variables whose value is that of a list or bag. - */ - private suspend fun compileIn(expr: PartiqlPhysical.Expr.InCollection, metas: MetaContainer): PhysicalPlanThunkAsync { - val args = expr.operands - val leftThunk = compileAstExpr(args[0]) - val rightOp = args[1] - - fun isOptimizedCase(values: List): Boolean = values.all { it is PartiqlPhysical.Expr.Lit && !it.value.isNull } - - suspend fun optimizedCase(values: List): PhysicalPlanThunkAsync { - // Put all the literals in the sequence into a pre-computed map to be checked later by the thunk. - // If the left-hand value is one of these we can short-circuit with a result of TRUE. - // This is the fastest possible case and allows for hundreds of literal values (or more) in the - // sequence without a huge performance penalty. - // NOTE: we cannot use a [HashSet<>] here because [ExprValue] does not implement [Object.hashCode] or - // [Object.equals]. - val precomputedLiteralsMap = values - .filterIsInstance() - .mapTo(TreeSet(DEFAULT_COMPARATOR)) { - ExprValue.of( - it.value.toIonValue(ion) - ) - } - - // the compiled thunk simply checks if the left side is contained on the right side. - // thunkEnvOperands takes care of unknown propagation for the left side; for the right, - // this unknown propagation does not apply since we've eliminated the possibility of unknowns above. - return thunkFactory.thunkEnvOperands(metas, leftThunk) { _, leftValue -> - precomputedLiteralsMap.contains(leftValue).exprValue() - } - } - - return when { - // We can significantly optimize this if rightArg is a sequence constructor which is composed of entirely - // of non-null literal values. - rightOp is PartiqlPhysical.Expr.List && isOptimizedCase(rightOp.values) -> optimizedCase(rightOp.values) - rightOp is PartiqlPhysical.Expr.Bag && isOptimizedCase(rightOp.values) -> optimizedCase(rightOp.values) - rightOp is PartiqlPhysical.Expr.Sexp && isOptimizedCase(rightOp.values) -> optimizedCase(rightOp.values) - // The unoptimized case... - else -> { - val rightThunk = compileAstExpr(rightOp) - - // Legacy mode: - // Returns FALSE when the right side of IN is not a sequence - // Returns NULL if the right side is MISSING or any value on the right side is MISSING - // Permissive mode: - // Returns MISSING when the right side of IN is not a sequence - // Returns MISSING if the right side is MISSING or any value on the right side is MISSING - val (propagateMissingAs, propagateNotASeqAs) = when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> ExprValue.nullValue to ExprValue.newBoolean(false) - TypingMode.PERMISSIVE -> ExprValue.missingValue to ExprValue.missingValue - } - - // Note that standard unknown propagation applies to the left and right operands. Both [TypingMode]s - // are handled by [ThunkFactory.thunkEnvOperands] and that additional rules for unknown propagation are - // implemented within the thunk for the values within the sequence on the right side of IN. - thunkFactory.thunkEnvOperands(metas, leftThunk, rightThunk) { _, leftValue, rightValue -> - var nullSeen = false - var missingSeen = false - - when { - rightValue.type == ExprValueType.MISSING -> propagateMissingAs - !rightValue.type.isSequence -> propagateNotASeqAs - else -> { - rightValue.forEach { - when (it.type) { - ExprValueType.NULL -> nullSeen = true - ExprValueType.MISSING -> missingSeen = true - // short-circuit to TRUE on the first matching value - else -> if (it.exprEquals(leftValue)) { - return@thunkEnvOperands ExprValue.newBoolean(true) - } - } - } - // If we make it here then there was no match. Propagate MISSING, NULL or return false. - // Note that if both MISSING and NULL was encountered, MISSING takes precedence. - when { - missingSeen -> propagateMissingAs - nullSeen -> ExprValue.nullValue - else -> ExprValue.newBoolean(false) - } - } - } - } - } - } - } - - private suspend fun compileNot(expr: PartiqlPhysical.Expr.Not, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunk = compileAstExpr(expr.expr) - - return thunkFactory.thunkEnvOperands(metas, argThunk) { _, value -> - (!value.booleanValue()).exprValue() - } - } - - private suspend fun compileAnd(expr: PartiqlPhysical.Expr.And, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - // can't use the null propagation supplied by [ThunkFactory.thunkEnv] here because AND short-circuits on - // false values and *NOT* on NULL or MISSING - return when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> thunkFactory.thunkEnvAsync(metas) thunk@{ env -> - var hasUnknowns = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - when { - currValue.isUnknown() -> hasUnknowns = true - // Short circuit only if we encounter a known false value. - !currValue.booleanValue() -> return@thunk ExprValue.newBoolean(false) - } - } - - when (hasUnknowns) { - true -> ExprValue.nullValue - false -> ExprValue.newBoolean(true) - } - } - TypingMode.PERMISSIVE -> thunkFactory.thunkEnvAsync(metas) thunk@{ env -> - var hasNull = false - var hasMissing = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - when (currValue.type) { - // Short circuit only if we encounter a known false value. - ExprValueType.BOOL -> if (!currValue.booleanValue()) return@thunk ExprValue.newBoolean(false) - ExprValueType.NULL -> hasNull = true - // type mismatch, return missing - else -> hasMissing = true - } - } - - when { - hasMissing -> ExprValue.missingValue - hasNull -> ExprValue.nullValue - else -> ExprValue.newBoolean(true) - } - } - } - } - - private suspend fun compileOr(expr: PartiqlPhysical.Expr.Or, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - // can't use the null propagation supplied by [ThunkFactory.thunkEnv] here because OR short-circuits on - // true values and *NOT* on NULL or MISSING - return when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> - thunkFactory.thunkEnvAsync(metas) thunk@{ env -> - var hasUnknowns = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - // How null-propagation works for OR is rather weird according to the SQL-92 spec. - // Nulls are propagated like other expressions only when none of the terms are TRUE. - // If any one of them is TRUE, then the entire expression evaluates to TRUE, i.e.: - // NULL OR TRUE -> TRUE - // NULL OR FALSE -> NULL - // (strange but true) - when { - currValue.isUnknown() -> hasUnknowns = true - currValue.booleanValue() -> return@thunk ExprValue.newBoolean(true) - } - } - - when (hasUnknowns) { - true -> ExprValue.nullValue - false -> ExprValue.newBoolean(false) - } - } - TypingMode.PERMISSIVE -> thunkFactory.thunkEnvAsync(metas) thunk@{ env -> - var hasNull = false - var hasMissing = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - when (currValue.type) { - // Short circuit only if we encounter a known true value. - ExprValueType.BOOL -> if (currValue.booleanValue()) return@thunk ExprValue.newBoolean(true) - ExprValueType.NULL -> hasNull = true - else -> hasMissing = true // type mismatch, return missing. - } - } - - when { - hasMissing -> ExprValue.missingValue - hasNull -> ExprValue.nullValue - else -> ExprValue.newBoolean(false) - } - } - } - } - - private suspend fun compileConcat(expr: PartiqlPhysical.Expr.Concat, metas: MetaContainer): PhysicalPlanThunkAsync { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - val lType = lValue.type - val rType = rValue.type - - if (lType.isText && rType.isText) { - // null/missing propagation is handled before getting here - (lValue.stringValue() + rValue.stringValue()).exprValue() - } else { - err( - "Wrong argument type for ||", - ErrorCode.EVALUATOR_CONCAT_FAILED_DUE_TO_INCOMPATIBLE_TYPE, - errorContextFrom(metas).also { - it[Property.ACTUAL_ARGUMENT_TYPES] = listOf(lType, rType).toString() - }, - internal = false - ) - } - } - } - - private suspend fun compileCall(expr: PartiqlPhysical.Expr.Call, metas: MetaContainer): PhysicalPlanThunkAsync { - val funcArgThunks = compileAstExprs(expr.args) - val arity = funcArgThunks.size - val name = expr.funcName.text - return thunkFactory.thunkEnvAsync(metas) { env -> - val args = funcArgThunks.map { thunk -> thunk(env) } - val argTypes = args.map { staticTypeFromExprValue(it) } - try { - val func = functionManager.get(name = name, arity = arity, args = argTypes) - val computeThunk = when (func.signature.unknownArguments) { - UnknownArguments.PROPAGATE -> thunkFactory.thunkEnvOperands(metas, funcArgThunks) { env, _ -> - func.call(env.session, args) - } - UnknownArguments.PASS_THRU -> thunkFactory.thunkEnvAsync(metas) { env -> - func.call(env.session, args) - } - } - checkIntegerOverflow(computeThunk, metas)(env) - } catch (e: FunctionNotFoundException) { - err( - "No such function: $name", - ErrorCode.EVALUATOR_NO_SUCH_FUNCTION, - errorContextFrom(metas).also { - it[Property.FUNCTION_NAME] = name - }, - internal = false - ) - } catch (e: ArityMismatchException) { - val (minArity, maxArity) = e.arity - val errorContext = errorContextFrom(metas).also { - it[Property.FUNCTION_NAME] = name - it[Property.EXPECTED_ARITY_MIN] = minArity - it[Property.EXPECTED_ARITY_MAX] = maxArity - it[Property.ACTUAL_ARITY] = arity - } - err( - "No function found with matching arity: $name", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - errorContext, - internal = false - ) - } - } - } - - private suspend fun compileLit(expr: PartiqlPhysical.Expr.Lit, metas: MetaContainer): PhysicalPlanThunkAsync { - val value = ExprValue.of(expr.value.toIonValue(ion)) - - return thunkFactory.thunkEnvAsync(metas) { value } - } - - private suspend fun compileMissing(metas: MetaContainer): PhysicalPlanThunkAsync = - thunkFactory.thunkEnvAsync(metas) { ExprValue.missingValue } - - private suspend fun compileGlobalId(expr: PartiqlPhysical.Expr.GlobalId): PhysicalPlanThunkAsync { - // TODO: we really should consider using something other than `Bindings` for global variables - // with the physical plan evaluator because `Bindings.get()` accepts a `BindingName` instance - // which contains the `case` property which is always set to `SENSITIVE` and is therefore redundant. - val bindingName = BindingName(expr.uniqueId.text, BindingCase.SENSITIVE) - return thunkFactory.thunkEnvAsync(expr.metas) { env -> - env.session.globals[bindingName] ?: throwUndefinedVariableException(bindingName, expr.metas) - } - } - - @Suppress("UNUSED_PARAMETER") - private suspend fun compileLocalId(expr: PartiqlPhysical.Expr.LocalId, metas: MetaContainer): PhysicalPlanThunkAsync { - val localIndex = expr.index.value.toIntExact() - return thunkFactory.thunkEnvAsync(metas) { env -> - env.registers[localIndex] - } - } - - private fun compileParameter(expr: PartiqlPhysical.Expr.Parameter, metas: MetaContainer): PhysicalPlanThunkAsync { - val ordinal = expr.index.value.toInt() - val index = ordinal - 1 - - return { env -> - val params = env.session.parameters - if (params.size <= index) { - throw EvaluationException( - "Unbound parameter for ordinal: $ordinal", - ErrorCode.EVALUATOR_UNBOUND_PARAMETER, - errorContextFrom(metas).also { - it[Property.EXPECTED_PARAMETER_ORDINAL] = ordinal - it[Property.BOUND_PARAMETER_COUNT] = params.size - }, - internal = false - ) - } - params[index] - } - } - - /** - * Returns a lambda that implements the `IS` operator type check according to the current - * [TypedOpBehavior]. - */ - private fun makeIsCheck( - staticType: SingleType, - typedOpParameter: TypedOpParameter, - metas: MetaContainer - ): (ExprValue) -> Boolean { - return when (evaluatorOptions.typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> { expValue: ExprValue -> - staticType.allTypes.any { - val matchesStaticType = try { - isInstance(expValue, it) - } catch (e: UnsupportedTypeCheckException) { - err( - e.message!!, - ErrorCode.UNIMPLEMENTED_FEATURE, - errorContextFrom(metas), - internal = true - ) - } - - when { - !matchesStaticType -> false - else -> when (val validator = typedOpParameter.validationThunk) { - null -> true - else -> validator(expValue) - } - } - } - } - } - } - - private suspend fun compileIs(expr: PartiqlPhysical.Expr.IsType, metas: MetaContainer): PhysicalPlanThunkAsync { - val expThunk = compileAstExpr(expr.value) - val typedOpParameter = expr.type.toTypedOpParameter(customTypedOpParameters) - if (typedOpParameter.staticType is AnyType) { - return thunkFactory.thunkEnvAsync(metas) { ExprValue.newBoolean(true) } - } - if (evaluatorOptions.typedOpBehavior == TypedOpBehavior.HONOR_PARAMETERS && expr.type is PartiqlPhysical.Type.FloatType && (expr.type as PartiqlPhysical.Type.FloatType).precision != null) { - err( - "FLOAT precision parameter is unsupported", - ErrorCode.SEMANTIC_FLOAT_PRECISION_UNSUPPORTED, - errorContextFrom(expr.type.metas), - internal = false - ) - } - - val typeMatchFunc = when (val staticType = typedOpParameter.staticType) { - is SingleType -> makeIsCheck(staticType, typedOpParameter, metas) - is AnyOfType -> staticType.types.map { childType -> - when (childType) { - is SingleType -> makeIsCheck(childType, typedOpParameter, metas) - else -> err( - "Union type cannot have ANY or nested AnyOf type for IS", - ErrorCode.SEMANTIC_UNION_TYPE_INVALID, - errorContextFrom(metas), - internal = true - ) - } - }.let { typeMatchFuncs -> - { expValue: ExprValue -> typeMatchFuncs.any { func -> func(expValue) } } - } - is AnyType -> throw IllegalStateException("Unexpected ANY type in IS compilation") - } - - return thunkFactory.thunkEnvAsync(metas) { env -> - val expValue = expThunk(env) - typeMatchFunc(expValue).exprValue() - } - } - - private suspend fun compileCastHelper(value: PartiqlPhysical.Expr, asType: PartiqlPhysical.Type, metas: MetaContainer): PhysicalPlanThunkAsync { - val expThunk = compileAstExpr(value) - val typedOpParameter = asType.toTypedOpParameter(customTypedOpParameters) - if (typedOpParameter.staticType is AnyType) { - return expThunk - } - if (evaluatorOptions.typedOpBehavior == TypedOpBehavior.HONOR_PARAMETERS && asType is PartiqlPhysical.Type.FloatType && asType.precision != null) { - err( - "FLOAT precision parameter is unsupported", - ErrorCode.SEMANTIC_FLOAT_PRECISION_UNSUPPORTED, - errorContextFrom(asType.metas), - internal = false - ) - } - - fun typeOpValidate( - value: ExprValue, - castOutput: ExprValue, - typeName: String, - locationMeta: SourceLocationMeta? - ) { - if (typedOpParameter.validationThunk?.let { it(castOutput) } == false) { - val errorContext = PropertyValueMap().also { - it[Property.CAST_FROM] = value.type.toString() - it[Property.CAST_TO] = typeName - } - - locationMeta?.let { fillErrorContext(errorContext, it) } - - throw EvaluationException( - "Validation failure for $asType", - ErrorCode.EVALUATOR_CAST_FAILED, - errorContext, - internal = false - ) - } - } - - fun singleTypeCastFunc(singleType: SingleType): CastFunc { - val locationMeta = metas.sourceLocationMeta - return { value -> - val castOutput = value.cast( - singleType, - evaluatorOptions.typedOpBehavior, - locationMeta, - evaluatorOptions.defaultTimezoneOffset - ) - typeOpValidate(value, castOutput, getRuntimeType(singleType).toString(), locationMeta) - castOutput - } - } - - fun compileSingleTypeCast(singleType: SingleType): PhysicalPlanThunkAsync { - val castFunc = singleTypeCastFunc(singleType) - // We do not use thunkFactory here because we want to explicitly avoid - // the optional evaluation-time type check for CAN_CAST below. - // Can cast needs that returns false if an - // exception is thrown during a normal cast operation. - return { env -> - val valueToCast = expThunk(env) - castFunc(valueToCast) - } - } - - fun compileCast(type: StaticType): PhysicalPlanThunkAsync = when (type) { - is SingleType -> compileSingleTypeCast(type) - is AnyOfType -> { - val locationMeta = metas.sourceLocationMeta - val castTable = AnyOfCastTable(type, metas, ::singleTypeCastFunc); - - // We do not use thunkFactory here because we want to explicitly avoid - // the optional evaluation-time type check for CAN_CAST below. - // note that this would interfere with the error handling for can_cast that returns false if an - // exception is thrown during a normal cast operation. - { env -> - val sourceValue = expThunk(env) - castTable.cast(sourceValue).also { - // TODO put the right type name here - typeOpValidate(sourceValue, it, "", locationMeta) - } - } - } - is AnyType -> throw IllegalStateException("Unreachable code") - } - - return compileCast(typedOpParameter.staticType) - } - - private suspend fun compileCast(expr: PartiqlPhysical.Expr.Cast, metas: MetaContainer): PhysicalPlanThunkAsync = - thunkFactory.thunkEnvAsync(metas, compileCastHelper(expr.value, expr.asType, metas)) - - private suspend fun compileCanCast(expr: PartiqlPhysical.Expr.CanCast, metas: MetaContainer): PhysicalPlanThunkAsync { - val typedOpParameter = expr.asType.toTypedOpParameter(customTypedOpParameters) - if (typedOpParameter.staticType is AnyType) { - return thunkFactory.thunkEnvAsync(metas) { ExprValue.newBoolean(true) } - } - - val expThunk = compileAstExpr(expr.value) - - // TODO consider making this more efficient by not directly delegating to CAST - // TODO consider also making the operand not double evaluated (e.g. having expThunk memoize) - val castThunkEnv = compileCastHelper(expr.value, expr.asType, expr.metas) - return thunkFactory.thunkEnvAsync(metas) { env -> - val sourceValue = expThunk(env) - try { - when { - // NULL/MISSING can cast to anything as themselves - sourceValue.isUnknown() -> ExprValue.newBoolean(true) - else -> { - val castedValue = castThunkEnv(env) - when { - // NULL/MISSING from cast is a permissive way to signal failure - castedValue.isUnknown() -> ExprValue.newBoolean(false) - else -> ExprValue.newBoolean(true) - } - } - } - } catch (e: EvaluationException) { - if (e.internal) { - throw e - } - ExprValue.newBoolean(false) - } - } - } - - private suspend fun compileCanLosslessCast(expr: PartiqlPhysical.Expr.CanLosslessCast, metas: MetaContainer): PhysicalPlanThunkAsync { - val typedOpParameter = expr.asType.toTypedOpParameter(customTypedOpParameters) - if (typedOpParameter.staticType is AnyType) { - return thunkFactory.thunkEnvAsync(metas) { ExprValue.newBoolean(true) } - } - - val expThunk = compileAstExpr(expr.value) - - // TODO consider making this more efficient by not directly delegating to CAST - val castThunkEnv = compileCastHelper(expr.value, expr.asType, expr.metas) - return thunkFactory.thunkEnvAsync(metas) { env -> - val sourceValue = expThunk(env) - val sourceType = staticTypeFromExprValue(sourceValue) - - suspend fun roundTrip(): ExprValue { - val castedValue = castThunkEnv(env) - - val locationMeta = metas.sourceLocationMeta - fun castFunc(singleType: SingleType) = - { value: ExprValue -> - value.cast( - singleType, - evaluatorOptions.typedOpBehavior, - locationMeta, - evaluatorOptions.defaultTimezoneOffset - ) - } - - val roundTripped = when (sourceType) { - is SingleType -> castFunc(sourceType)(castedValue) - is AnyOfType -> { - val castTable = AnyOfCastTable(sourceType, metas, ::castFunc) - castTable.cast(sourceValue) - } - // Should not be possible - is AnyType -> throw IllegalStateException("ANY type is not configured correctly in compiler") - } - - val lossless = sourceValue.exprEquals(roundTripped) - return ExprValue.newBoolean(lossless) - } - - try { - when (sourceValue.type) { - // NULL can cast to anything as itself - ExprValueType.NULL -> ExprValue.newBoolean(true) - - // Short-circuit timestamp -> date roundtrip if precision isn't [Timestamp.Precision.DAY] or - // [Timestamp.Precision.MONTH] or [Timestamp.Precision.YEAR] - ExprValueType.TIMESTAMP -> when (typedOpParameter.staticType) { - StaticType.DATE -> when (sourceValue.timestampValue().precision) { - Timestamp.Precision.DAY, Timestamp.Precision.MONTH, Timestamp.Precision.YEAR -> roundTrip() - else -> ExprValue.newBoolean(false) - } - StaticType.TIME -> ExprValue.newBoolean(false) - else -> roundTrip() - } - - // For all other cases, attempt a round-trip of the value through the source and dest types - else -> roundTrip() - } - } catch (e: EvaluationException) { - if (e.internal) { - throw e - } - ExprValue.newBoolean(false) - } - } - } - - private suspend fun compileSimpleCase(expr: PartiqlPhysical.Expr.SimpleCase, metas: MetaContainer): PhysicalPlanThunkAsync { - val valueThunk = compileAstExpr(expr.expr) - val branchThunks = expr.cases.pairs.map { Pair(compileAstExpr(it.first), compileAstExpr(it.second)) } - val elseThunk = when (val default = expr.default) { - null -> thunkFactory.thunkEnvAsync(metas) { ExprValue.nullValue } - else -> compileAstExpr(default) - } - - return thunkFactory.thunkEnvAsync(metas) thunk@{ env -> - val caseValue = valueThunk(env) - // if the case value is unknown then we can short-circuit to the elseThunk directly - when { - caseValue.isUnknown() -> elseThunk(env) - else -> { - branchThunks.forEach { bt -> - val branchValue = bt.first(env) - // Just skip any branch values that are unknown, which we consider the same as false here. - when { - branchValue.isUnknown() -> { /* intentionally blank */ - } - else -> { - if (caseValue.exprEquals(branchValue)) { - return@thunk bt.second(env) - } - } - } - } - } - } - elseThunk(env) - } - } - - private suspend fun compileSearchedCase(expr: PartiqlPhysical.Expr.SearchedCase, metas: MetaContainer): PhysicalPlanThunkAsync { - val branchThunks = expr.cases.pairs.map { compileAstExpr(it.first) to compileAstExpr(it.second) } - val elseThunk = when (val default = expr.default) { - null -> thunkFactory.thunkEnvAsync(metas) { ExprValue.nullValue } - else -> compileAstExpr(default) - } - - return when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> thunkFactory.thunkEnvAsync(metas) thunk@{ env -> - branchThunks.forEach { bt -> - val conditionValue = bt.first(env) - // Any unknown value is considered the same as false. - // Note that .booleanValue() here will throw an EvaluationException if - // the data type is not boolean. - // TODO: .booleanValue does not have access to metas, so the EvaluationException is reported to be - // at the line & column of the CASE statement, not the predicate, unfortunately. - if (conditionValue.isNotUnknown() && conditionValue.booleanValue()) { - return@thunk bt.second(env) - } - } - elseThunk(env) - } - // Permissive mode propagates data type mismatches as MISSING, which is - // equivalent to false for searched CASE predicates. To simplify this, - // all we really need to do is consider any non-boolean result from the - // predicate to be false. - TypingMode.PERMISSIVE -> thunkFactory.thunkEnvAsync(metas) thunk@{ env -> - branchThunks.forEach { bt -> - val conditionValue = bt.first(env) - if (conditionValue.type == ExprValueType.BOOL && conditionValue.booleanValue()) { - return@thunk bt.second(env) - } - } - elseThunk(env) - } - } - } - - private suspend fun compileStruct(expr: PartiqlPhysical.Expr.Struct): PhysicalPlanThunkAsync { - val structParts = compileStructParts(expr.parts) - - val ordering = if (expr.parts.none { it is PartiqlPhysical.StructPart.StructFields }) - StructOrdering.ORDERED - else - StructOrdering.UNORDERED - - return thunkFactory.thunkEnvAsync(expr.metas) { env -> - val columns = mutableListOf() - for (element in structParts) { - when (element) { - is CompiledStructPartAsync.Field -> { - val fieldName = element.nameThunk(env) - when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> - if (!fieldName.type.isText) { - err( - "Found struct field key to be of type ${fieldName.type}", - ErrorCode.EVALUATOR_NON_TEXT_STRUCT_FIELD_KEY, - errorContextFrom(expr.metas.sourceLocationMeta).also { pvm -> - pvm[Property.ACTUAL_TYPE] = fieldName.type.toString() - }, - internal = false - ) - } - TypingMode.PERMISSIVE -> - if (!fieldName.type.isText) { - continue - } - } - val fieldValue = element.valueThunk(env) - columns.add(fieldValue.namedValue(fieldName)) - } - is CompiledStructPartAsync.StructMerge -> { - for (projThunk in element.thunks) { - val value = projThunk(env) - if (value.type == ExprValueType.MISSING) continue - - val children = value.asSequence() - if (!children.any() || value.type.isSequence) { - val name = syntheticColumnName(columns.size).exprValue() - columns.add(value.namedValue(name)) - } else { - val valuesToProject = - when (evaluatorOptions.projectionIteration) { - ProjectionIterationBehavior.FILTER_MISSING -> { - value.filter { it.type != ExprValueType.MISSING } - } - ProjectionIterationBehavior.UNFILTERED -> value - } - for (childValue in valuesToProject) { - val namedFacet = childValue.asFacet(Named::class.java) - val name = namedFacet?.name - ?: syntheticColumnName(columns.size).exprValue() - columns.add(childValue.namedValue(name)) - } - } - } - } - } - } - createStructExprValue(columns.asSequence(), ordering) - } - } - - private suspend fun compileStructParts(projectItems: List): List = - projectItems.map { - when (it) { - is PartiqlPhysical.StructPart.StructField -> { - val fieldThunk = compileAstExpr(it.fieldName) - val valueThunk = compileAstExpr(it.value) - CompiledStructPartAsync.Field(fieldThunk, valueThunk) - } - is PartiqlPhysical.StructPart.StructFields -> { - CompiledStructPartAsync.StructMerge(listOf(compileAstExpr(it.partExpr))) - } - } - } - - private suspend fun compileSeq(seqType: ExprValueType, itemExprs: List, metas: MetaContainer): PhysicalPlanThunkAsync { - require(seqType.isSequence) { "seqType must be a sequence!" } - - val itemThunks = compileAstExprs(itemExprs) - - val makeItemThunkSequence = when (seqType) { - ExprValueType.BAG -> { env: EvaluatorState -> - itemThunks.asFlow().map { itemThunk -> - // call to unnamedValue() makes sure we don't expose any underlying value name/ordinal - itemThunk(env).unnamedValue() - } - } - else -> { env: EvaluatorState -> - itemThunks.asFlow().withIndex().map { indexedVal -> - indexedVal.value(env).namedValue(indexedVal.index.exprValue()) - } - } - } - - return thunkFactory.thunkEnvAsync(metas) { env -> - when (seqType) { - ExprValueType.BAG -> ExprValue.newBag(makeItemThunkSequence(env).toList()) - ExprValueType.LIST -> ExprValue.newList(makeItemThunkSequence(env).toList()) - ExprValueType.SEXP -> ExprValue.newSexp(makeItemThunkSequence(env).toList()) - else -> error("sequence type required") - } - } - } - - private suspend fun compilePath(expr: PartiqlPhysical.Expr.Path, metas: MetaContainer): PhysicalPlanThunkAsync { - val rootThunk = compileAstExpr(expr.root) - val remainingComponents = LinkedList() - - expr.steps.forEach { remainingComponents.addLast(it) } - - val componentThunk = compilePathComponents(remainingComponents, metas) - - return thunkFactory.thunkEnvAsync(metas) { env -> - val rootValue = rootThunk(env) - componentThunk(env, rootValue) - } - } - - private suspend fun compilePathComponents( - remainingComponents: LinkedList, - pathMetas: MetaContainer - ): PhysicalPlanThunkValueAsync { - - val componentThunks = ArrayList>() - - while (!remainingComponents.isEmpty()) { - val pathComponent = remainingComponents.removeFirst() - val componentMetas = pathComponent.metas - componentThunks.add( - when (pathComponent) { - is PartiqlPhysical.PathStep.PathExpr -> { - val indexExpr = pathComponent.index - val caseSensitivity = pathComponent.case - when { - // If indexExpr is a literal string, there is no need to evaluate it--just compile a - // thunk that directly returns a bound value - indexExpr is PartiqlPhysical.Expr.Lit && indexExpr.value.toIonValue(ion) is IonString -> { - val lookupName = BindingName( - indexExpr.value.toIonValue(ion).stringValue()!!, - caseSensitivity.toBindingCase() - ) - thunkFactory.thunkEnvValue(componentMetas) { _, componentValue -> - componentValue.bindings[lookupName] ?: ExprValue.missingValue - } - } - else -> { - val indexThunk = compileAstExpr(indexExpr) - thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val indexValue = indexThunk(env) - when { - indexValue.type == ExprValueType.INT -> { - componentValue.ordinalBindings[indexValue.numberValue().toInt()] - } - indexValue.type.isText -> { - val lookupName = - BindingName(indexValue.stringValue(), caseSensitivity.toBindingCase()) - componentValue.bindings[lookupName] - } - else -> { - when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> err( - "Cannot convert index to int/string: $indexValue", - ErrorCode.EVALUATOR_INVALID_CONVERSION, - errorContextFrom(componentMetas), - internal = false - ) - TypingMode.PERMISSIVE -> ExprValue.missingValue - } - } - } ?: ExprValue.missingValue - } - } - } - } - is PartiqlPhysical.PathStep.PathUnpivot -> { - when { - !remainingComponents.isEmpty() -> { - val tempThunk = compilePathComponents(remainingComponents, pathMetas) - thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val mapped = componentValue.unpivot() - .flatMap { tempThunk(env, it).rangeOver() } - .asSequence() - ExprValue.newBag(mapped) - } - } - else -> - thunkFactory.thunkEnvValue(componentMetas) { _, componentValue -> - ExprValue.newBag(componentValue.unpivot().asSequence()) - } - } - } - // this is for `path[*].component` - is PartiqlPhysical.PathStep.PathWildcard -> { - when { - !remainingComponents.isEmpty() -> { - val hasMoreWildCards = - remainingComponents.filterIsInstance().any() - val tempThunk = compilePathComponents(remainingComponents, pathMetas) - - when { - !hasMoreWildCards -> thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val mapped = componentValue - .rangeOver() - .map { tempThunk(env, it) } - .asSequence() - - ExprValue.newBag(mapped) - } - else -> thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val mapped = componentValue - .rangeOver() - .flatMap { - val tempValue = tempThunk(env, it) - tempValue - } - .asSequence() - - ExprValue.newBag(mapped) - } - } - } - else -> { - thunkFactory.thunkEnvValue(componentMetas) { _, componentValue -> - val mapped = componentValue.rangeOver().asSequence() - ExprValue.newBag(mapped) - } - } - } - } - } - ) - } - return when (componentThunks.size) { - 1 -> componentThunks.first() - else -> thunkFactory.thunkEnvValue(pathMetas) { env, rootValue -> - componentThunks.fold(rootValue) { componentValue, componentThunk -> - componentThunk(env, componentValue) - } - } - } - } - - /** - * Given an AST node that represents a `LIKE` predicate, return an ExprThunk that evaluates a `LIKE` predicate. - * - * Three cases - * - * 1. All arguments are literals, then compile and run the pattern - * 1. Search pattern and escape pattern are literals, compile the pattern. Running the pattern deferred to evaluation time. - * 1. Pattern or escape (or both) are *not* literals, compile and running of pattern deferred to evaluation time. - * - * ``` - * LIKE [ESCAPE ] - * ``` - * - * @return a thunk that when provided with an environment evaluates the `LIKE` predicate - */ - private suspend fun compileLike(expr: PartiqlPhysical.Expr.Like, metas: MetaContainer): PhysicalPlanThunkAsync { - val valueExpr = expr.value - val patternExpr = expr.pattern - val escapeExpr = expr.escape - - val patternLocationMeta = patternExpr.metas.sourceLocation - val escapeLocationMeta = escapeExpr?.metas?.sourceLocation - - // This is so that null short-circuits can be supported. - fun getRegexPattern(pattern: ExprValue, escape: ExprValue?): (() -> Pattern)? { - val patternArgs = listOfNotNull(pattern, escape) - when { - patternArgs.any { it.type.isUnknown } -> return null - patternArgs.any { !it.type.isText } -> return { - err( - "LIKE expression must be given non-null strings as input", - ErrorCode.EVALUATOR_LIKE_INVALID_INPUTS, - errorContextFrom(metas).also { - it[Property.LIKE_PATTERN] = pattern.toString() - if (escape != null) it[Property.LIKE_ESCAPE] = escape.toString() - }, - internal = false - ) - } - else -> { - val (patternString: String, escapeChar: Int?) = - checkPattern(pattern.stringValue(), patternLocationMeta, escape?.stringValue(), escapeLocationMeta) - val likeRegexPattern = when { - patternString.isEmpty() -> Pattern.compile("") - else -> parsePattern(patternString, escapeChar) - } - return { likeRegexPattern } - } - } - } - - fun matchRegexPattern(value: ExprValue, likePattern: (() -> Pattern)?): ExprValue { - return when { - likePattern == null || value.type.isUnknown -> ExprValue.nullValue - !value.type.isText -> err( - "LIKE expression must be given non-null strings as input", - ErrorCode.EVALUATOR_LIKE_INVALID_INPUTS, - errorContextFrom(metas).also { - it[Property.LIKE_VALUE] = value.toString() - }, - internal = false - ) - else -> ExprValue.newBoolean(likePattern().matcher(value.stringValue()).matches()) - } - } - - val valueThunk = compileAstExpr(valueExpr) - - // If the pattern and escape expressions are literals then we can compile the pattern now and - // re-use it with every execution. Otherwise, we must re-compile the pattern every time. - return when { - patternExpr is PartiqlPhysical.Expr.Lit && (escapeExpr == null || escapeExpr is PartiqlPhysical.Expr.Lit) -> { - val patternParts = getRegexPattern( - ExprValue.of(patternExpr.value.toIonValue(ion)), - (escapeExpr as? PartiqlPhysical.Expr.Lit)?.value?.toIonValue(ion) - ?.let { ExprValue.of(it) } - ) - - // If valueExpr is also a literal then we can evaluate this at compile time and return a constant. - if (valueExpr is PartiqlPhysical.Expr.Lit) { - val resultValue = matchRegexPattern( - ExprValue.of(valueExpr.value.toIonValue(ion)), - patternParts - ) - return thunkFactory.thunkEnvAsync(metas) { resultValue } - } else { - thunkFactory.thunkEnvOperands(metas, valueThunk) { _, value -> - matchRegexPattern(value, patternParts) - } - } - } - else -> { - val patternThunk = compileAstExpr(patternExpr) - when (escapeExpr) { - null -> { - // thunk that re-compiles the DFA every evaluation without a custom escape sequence - thunkFactory.thunkEnvOperands(metas, valueThunk, patternThunk) { _, value, pattern -> - val pps = getRegexPattern(pattern, null) - matchRegexPattern(value, pps) - } - } - else -> { - // thunk that re-compiles the pattern every evaluation but *with* a custom escape sequence - val escapeThunk = compileAstExpr(escapeExpr) - thunkFactory.thunkEnvOperands( - metas, - valueThunk, - patternThunk, - escapeThunk - ) { _, value, pattern, escape -> - val pps = getRegexPattern(pattern, escape) - matchRegexPattern(value, pps) - } - } - } - } - } - } - - /** - * Given the pattern and optional escape character in a `LIKE` predicate as [IonValue]s - * check their validity based on the SQL92 spec and return a triple that contains in order - * - * - the search pattern as a string - * - the escape character, possibly `null` - * - the length of the search pattern. The length of the search pattern is either - * - the length of the string representing the search pattern when no escape character is used - * - the length of the string representing the search pattern without counting uses of the escape character - * when an escape character is used - * - * A search pattern is valid when - * 1. pattern is not null - * 1. pattern contains characters where `_` means any 1 character and `%` means any string of length 0 or more - * 1. if the escape character is specified then pattern can be deterministically partitioned into character groups where - * 1. A length 1 character group consists of any character other than the ESCAPE character - * 1. A length 2 character group consists of the ESCAPE character followed by either `_` or `%` or the ESCAPE character itself - * - * @param pattern search pattern - * @param escape optional escape character provided in the `LIKE` predicate - * - * @return a triple that contains in order the search pattern as a [String], optionally the code point for the escape character if one was provided - * and the size of the search pattern excluding uses of the escape character - */ - private fun checkPattern( - pattern: String, - patternLocationMeta: SourceLocationMeta?, - escape: String?, - escapeLocationMeta: SourceLocationMeta? - ): Pair { - - escape?.let { - val escapeCharString = checkEscapeChar(escape, escapeLocationMeta) - val escapeCharCodePoint = escapeCharString.codePointAt(0) // escape is a string of length 1 - val validEscapedChars = setOf('_'.code, '%'.code, escapeCharCodePoint) - val iter = pattern.codePointSequence().iterator() - - while (iter.hasNext()) { - val current = iter.next() - if (current == escapeCharCodePoint && (!iter.hasNext() || !validEscapedChars.contains(iter.next()))) { - err( - "Invalid escape sequence : $pattern", - ErrorCode.EVALUATOR_LIKE_PATTERN_INVALID_ESCAPE_SEQUENCE, - errorContextFrom(patternLocationMeta).apply { - set(Property.LIKE_PATTERN, pattern) - set(Property.LIKE_ESCAPE, escapeCharString) - }, - internal = false - ) - } - } - return Pair(pattern, escapeCharCodePoint) - } - return Pair(pattern, null) - } - - /** - * Given an [IonValue] to be used as the escape character in a `LIKE` predicate check that it is - * a valid character based on the SQL Spec. - * - * - * A value is a valid escape when - * 1. it is 1 character long, and, - * 1. Cannot be null (SQL92 spec marks this cases as *unknown*) - * - * @param escape value provided as an escape character for a `LIKE` predicate - * - * @return the escape character as a [String] or throws an exception when the input is invalid - */ - private fun checkEscapeChar(escape: String, locationMeta: SourceLocationMeta?): String { - when (escape) { - "" -> { - err( - "Cannot use empty character as ESCAPE character in a LIKE predicate: $escape", - ErrorCode.EVALUATOR_LIKE_PATTERN_INVALID_ESCAPE_SEQUENCE, - errorContextFrom(locationMeta), - internal = false - ) - } - else -> { - if (escape.trim().length != 1) { - err( - "Escape character must have size 1 : $escape", - ErrorCode.EVALUATOR_LIKE_PATTERN_INVALID_ESCAPE_SEQUENCE, - errorContextFrom(locationMeta), - internal = false - ) - } - } - } - return escape - } - - private suspend fun compileExec(node: PartiqlPhysical.Statement.Exec): PhysicalPlanThunkAsync { - val metas = node.metas - val procedureName = node.procedureName.text - val procedure = procedures[procedureName] ?: err( - "No such stored procedure: $procedureName", - ErrorCode.EVALUATOR_NO_SUCH_PROCEDURE, - errorContextFrom(metas).also { - it[Property.PROCEDURE_NAME] = procedureName - }, - internal = false - ) - - val args = node.args - // Check arity - if (args.size !in procedure.signature.arity) { - val errorContext = errorContextFrom(metas).also { - it[Property.EXPECTED_ARITY_MIN] = procedure.signature.arity.first - it[Property.EXPECTED_ARITY_MAX] = procedure.signature.arity.last - } - - val message = when { - procedure.signature.arity.first == 1 && procedure.signature.arity.last == 1 -> - "${procedure.signature.name} takes a single argument, received: ${args.size}" - procedure.signature.arity.first == procedure.signature.arity.last -> - "${procedure.signature.name} takes exactly ${procedure.signature.arity.first} arguments, received: ${args.size}" - else -> - "${procedure.signature.name} takes between ${procedure.signature.arity.first} and " + - "${procedure.signature.arity.last} arguments, received: ${args.size}" - } - - throw EvaluationException( - message, - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_PROCEDURE_CALL, - errorContext, - internal = false - ) - } - - // Compile the procedure's arguments - val argThunks = compileAstExprs(args) - - return thunkFactory.thunkEnvAsync(metas) { env -> - val procedureArgValues = argThunks.map { it(env) } - procedure.call(env.session, procedureArgValues) - } - } - - private suspend fun compileDate(expr: PartiqlPhysical.Expr.Date, metas: MetaContainer): PhysicalPlanThunkAsync = - thunkFactory.thunkEnvAsync(metas) { - ExprValue.newDate( - expr.year.value.toInt(), - expr.month.value.toInt(), - expr.day.value.toInt() - ) - } - - private suspend fun compileLitTime(expr: PartiqlPhysical.Expr.LitTime, metas: MetaContainer): PhysicalPlanThunkAsync = - thunkFactory.thunkEnvAsync(metas) { - // Add the default time zone if the type "TIME WITH TIME ZONE" does not have an explicitly specified time zone. - ExprValue.newTime( - Time.of( - expr.value.hour.value.toInt(), - expr.value.minute.value.toInt(), - expr.value.second.value.toInt(), - expr.value.nano.value.toInt(), - expr.value.precision.value.toInt(), - if (expr.value.withTimeZone.value && expr.value.tzMinutes == null) evaluatorOptions.defaultTimezoneOffset.totalMinutes else expr.value.tzMinutes?.value?.toInt() - ) - ) - } - - private suspend fun compileBagOp(node: PartiqlPhysical.Expr.BagOp, metas: MetaContainer): PhysicalPlanThunkAsync { - val lhs = compileAstExpr(node.operands[0]) - val rhs = compileAstExpr(node.operands[1]) - val op = ExprValueBagOp.create(node.op, metas) - return thunkFactory.thunkEnvAsync(metas) { env -> - val l = lhs(env) - val r = rhs(env) - val result = when (node.quantifier) { - is PartiqlPhysical.SetQuantifier.All -> op.eval(l, r) - is PartiqlPhysical.SetQuantifier.Distinct -> op.eval(l, r).distinct() - } - ExprValue.newBag(result) - } - } - - private suspend fun compilePivot(expr: PartiqlPhysical.Expr.Pivot, metas: MetaContainer): PhysicalPlanThunkAsync { - val inputBExpr: RelationThunkEnvAsync = bexperConverter.convert(expr.input) - // The names are intentionally flipped for clarity; consider fixing this in the AST - val valueExpr = compileAstExpr(expr.key) - val keyExpr = compileAstExpr(expr.value) - return thunkFactory.thunkEnvAsync(metas) { env -> - val attributes: Flow = flow { - val relation = inputBExpr(env) - while (relation.nextRow()) { - val key = keyExpr.invoke(env) - if (key.type.isText) { - val value = valueExpr.invoke(env) - emit(value.namedValue(key)) - } - } - } - ExprValue.newStruct(attributes.toList(), StructOrdering.UNORDERED) - } - } - - /** A special wrapper for `UNPIVOT` values as a BAG. */ - private class UnpivotedExprValue(private val values: Iterable) : BaseExprValue() { - override val type = ExprValueType.BAG - override fun iterator() = values.iterator() - } - - /** Unpivots a `struct`, and synthesizes a synthetic singleton `struct` for other [ExprValue]. */ - internal fun ExprValue.unpivot(): ExprValue = when { - // special case for our special UNPIVOT value to avoid double wrapping - this is UnpivotedExprValue -> this - // Wrap into a pseudo-BAG - type == ExprValueType.STRUCT || type == ExprValueType.MISSING -> UnpivotedExprValue(this) - // for non-struct, this wraps any value into a BAG with a synthetic name - else -> UnpivotedExprValue( - listOf( - this.namedValue(ExprValue.newString(syntheticColumnName(0))) - ) - ) - } - - private fun createStructExprValue(seq: Sequence, ordering: StructOrdering) = - ExprValue.newStruct( - when (evaluatorOptions.projectionIteration) { - ProjectionIterationBehavior.FILTER_MISSING -> seq.filter { it.type != ExprValueType.MISSING } - ProjectionIterationBehavior.UNFILTERED -> seq - }, - ordering - ) -} - -/** - * Represents an element in a select list that is to be projected into the final result. - * i.e. an expression, or a (project_all) node. - */ -private sealed class CompiledStructPartAsync { - - /** - * Represents a single compiled expression to be projected into the final result. - * Given `SELECT a + b as value FROM foo`: - * - `name` is "value" - * - `thunk` is compiled expression, i.e. `a + b` - */ - class Field(val nameThunk: PhysicalPlanThunkAsync, val valueThunk: PhysicalPlanThunkAsync) : CompiledStructPartAsync() - - /** - * Represents a wildcard ((path_project_all) node) expression to be projected into the final result. - * This covers two cases. For `SELECT foo.* FROM foo`, `exprThunks` contains a single compiled expression - * `foo`. - * - * For `SELECT * FROM foo, bar, bat`, `exprThunks` would contain a compiled expression for each of `foo`, `bar` and - * `bat`. - */ - class StructMerge(val thunks: List) : CompiledStructPartAsync() -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt deleted file mode 100644 index 87c50e35f1..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt +++ /dev/null @@ -1,1937 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical - -import com.amazon.ion.IonString -import com.amazon.ion.IonValue -import com.amazon.ion.Timestamp -import com.amazon.ion.system.IonSystemBuilder -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.emptyMetaContainer -import com.amazon.ionelement.api.toIonValue -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.IsOrderedMeta -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.ast.UNKNOWN_SOURCE_LOCATION -import org.partiql.lang.ast.sourceLocation -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.domains.staticType -import org.partiql.lang.domains.toBindingCase -import org.partiql.lang.eval.BaseExprValue -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.CoverageStructure -import org.partiql.lang.eval.DEFAULT_COMPARATOR -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueBagOp -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.Expression -import org.partiql.lang.eval.Named -import org.partiql.lang.eval.PartiQLResult -import org.partiql.lang.eval.ProjectionIterationBehavior -import org.partiql.lang.eval.StructOrdering -import org.partiql.lang.eval.TypedOpBehavior -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure -import org.partiql.lang.eval.call -import org.partiql.lang.eval.cast -import org.partiql.lang.eval.compareTo -import org.partiql.lang.eval.distinct -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.exprEquals -import org.partiql.lang.eval.fillErrorContext -import org.partiql.lang.eval.internal.AnyOfCastTable -import org.partiql.lang.eval.internal.CastFunc -import org.partiql.lang.eval.internal.ErrorDetails -import org.partiql.lang.eval.internal.FunctionManager -import org.partiql.lang.eval.internal.FunctionNotFoundException -import org.partiql.lang.eval.internal.ThunkValue -import org.partiql.lang.eval.internal.createErrorSignaler -import org.partiql.lang.eval.internal.createThunkFactory -import org.partiql.lang.eval.internal.err -import org.partiql.lang.eval.internal.errorIf -import org.partiql.lang.eval.internal.ext.checkThreadInterrupted -import org.partiql.lang.eval.internal.ext.exprValue -import org.partiql.lang.eval.internal.ext.isNotUnknown -import org.partiql.lang.eval.internal.ext.isUnknown -import org.partiql.lang.eval.internal.ext.isZero -import org.partiql.lang.eval.internal.ext.longValue -import org.partiql.lang.eval.internal.ext.toTypedOpParameter -import org.partiql.lang.eval.internal.ext.totalMinutes -import org.partiql.lang.eval.internal.parsePattern -import org.partiql.lang.eval.namedValue -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.rangeOver -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.stringValue -import org.partiql.lang.eval.syntheticColumnName -import org.partiql.lang.eval.time.Time -import org.partiql.lang.eval.timestampValue -import org.partiql.lang.eval.unnamedValue -import org.partiql.lang.planner.EvaluatorOptions -import org.partiql.lang.types.StaticTypeUtils.getRuntimeType -import org.partiql.lang.types.StaticTypeUtils.isInstance -import org.partiql.lang.types.StaticTypeUtils.staticTypeFromExprValue -import org.partiql.lang.types.TypedOpParameter -import org.partiql.lang.types.UnknownArguments -import org.partiql.lang.util.codePointSequence -import org.partiql.lang.util.div -import org.partiql.lang.util.minus -import org.partiql.lang.util.plus -import org.partiql.lang.util.rem -import org.partiql.lang.util.stringValue -import org.partiql.lang.util.times -import org.partiql.lang.util.toIntExact -import org.partiql.lang.util.unaryMinus -import org.partiql.types.AnyOfType -import org.partiql.types.AnyType -import org.partiql.types.IntType -import org.partiql.types.SingleType -import org.partiql.types.StaticType -import org.partiql.types.UnsupportedTypeCheckException -import java.util.LinkedList -import java.util.TreeSet -import java.util.regex.Pattern - -/** - * A basic "compiler" that converts an instance of [PartiqlPhysical.Expr] to an [Expression]. - * - * This is a modified copy of the legacy `EvaluatingCompiler` class, which is now legacy. - * The primary differences between this class an `EvaluatingCompiler` are: - * - * - All references to `PartiqlPhysical` are replaced with `PartiqlPhysical`. - * - `EvaluatingCompiler` compiles "monolithic" SFW queries--this class compiles relational - * operators (in concert with [PhysicalBexprToThunkConverter]). - * - * This implementation produces a "compiled" form consisting of context-threaded - * code in the form of a tree of [PhysicalPlanThunk]s. An overview of this technique can be found - * [here][1]. - * - * **Note:** *threaded* in this context is used in how the code gets *threaded* together for - * interpretation and **not** the concurrency primitive. That is to say this code is NOT thread - * safe. - * - * [1]: https://www.complang.tuwien.ac.at/anton/lvas/sem06w/fest.pdf - */ -internal class PhysicalPlanCompilerImpl( - private val functions: List, - private val customTypedOpParameters: Map, - private val procedures: Map, - private val evaluatorOptions: EvaluatorOptions = EvaluatorOptions.standard(), - private val bexperConverter: PhysicalBexprToThunkConverter, -) : PhysicalPlanCompiler { - @Deprecated("Use constructor with List instead", level = DeprecationLevel.WARNING) - constructor( - functions: Map, - customTypedOpParameters: Map, - procedures: Map, - evaluatorOptions: EvaluatorOptions = EvaluatorOptions.standard(), - bexperConverter: PhysicalBexprToThunkConverter, - ) : this( - functions = functions.values.toList(), - customTypedOpParameters = customTypedOpParameters, - procedures = procedures, - evaluatorOptions = evaluatorOptions, - bexperConverter = bexperConverter - ) - - // TODO: remove this once we migrate from `IonValue` to `IonElement`. - private val ion = IonSystemBuilder.standard().build() - - private val errorSignaler = evaluatorOptions.typingMode.createErrorSignaler() - private val thunkFactory = - evaluatorOptions.typingMode.createThunkFactory(evaluatorOptions.thunkOptions) - - private val functionManager = FunctionManager(functions) - - private fun Boolean.exprValue(): ExprValue = ExprValue.newBoolean(this) - private fun String.exprValue(): ExprValue = ExprValue.newString(this) - - /** - * Compiles a [PartiqlPhysical.Statement] tree to an [Expression]. - * - * Checks [Thread.interrupted] before every expression and sub-expression is compiled - * and throws [InterruptedException] if [Thread.interrupted] it has been set in the - * hope that long-running compilations may be aborted by the caller. - */ - fun compile(plan: PartiqlPhysical.Plan): Expression { - val thunk = compileAstStatement(plan.stmt) - - return object : Expression { - override val coverageStructure: CoverageStructure? = null - - override fun eval(session: EvaluationSession): ExprValue { - val env = EvaluatorState( - session = session, - registers = Array(plan.locals.size) { ExprValue.missingValue } - ) - return thunk(env) - } - - override fun evaluate(session: EvaluationSession): PartiQLResult { - val env = EvaluatorState( - session = session, - registers = Array(plan.locals.size) { ExprValue.missingValue } - ) - val value = thunk(env) - return PartiQLResult.Value(value = value) - } - } - } - - /** - * Compiles a [PartiqlPhysical.Expr] tree to an [Expression]. - * - * Checks [Thread.interrupted] before every expression and sub-expression is compiled - * and throws [InterruptedException] if [Thread.interrupted] it has been set in the - * hope that long-running compilations may be aborted by the caller. - */ - internal fun compile(expr: PartiqlPhysical.Expr, localsSize: Int): Expression { - val thunk = compileAstExpr(expr) - - return object : Expression { - override val coverageStructure: CoverageStructure? = null - - override fun eval(session: EvaluationSession): ExprValue { - val env = EvaluatorState( - session = session, - registers = Array(localsSize) { ExprValue.missingValue } - ) - return thunk(env) - } - - override fun evaluate(session: EvaluationSession): PartiQLResult { - val env = EvaluatorState( - session = session, - registers = Array(localsSize) { ExprValue.missingValue } - ) - val value = thunk(env) - return PartiQLResult.Value(value = value) - } - } - } - - override fun convert(expr: PartiqlPhysical.Expr): PhysicalPlanThunk = this.compileAstExpr(expr) - - /** - * Compiles the specified [PartiqlPhysical.Statement] into a [PhysicalPlanThunk]. - * - * This function will [InterruptedException] if [Thread.interrupted] has been set. - */ - private fun compileAstStatement(ast: PartiqlPhysical.Statement): PhysicalPlanThunk { - return when (ast) { - is PartiqlPhysical.Statement.Query -> compileAstExpr(ast.expr) - is PartiqlPhysical.Statement.Exec -> compileExec(ast) - is PartiqlPhysical.Statement.Dml, - is PartiqlPhysical.Statement.Explain, - -> { - val value = ExprValue.newBoolean(true) - thunkFactory.thunkEnv(emptyMetaContainer()) { value } - } - } - } - - private fun compileAstExpr(expr: PartiqlPhysical.Expr): PhysicalPlanThunk { - checkThreadInterrupted() - val metas = expr.metas - - return when (expr) { - is PartiqlPhysical.Expr.Lit -> compileLit(expr, metas) - is PartiqlPhysical.Expr.Missing -> compileMissing(metas) - is PartiqlPhysical.Expr.LocalId -> compileLocalId(expr, metas) - is PartiqlPhysical.Expr.GlobalId -> compileGlobalId(expr) - is PartiqlPhysical.Expr.SimpleCase -> compileSimpleCase(expr, metas) - is PartiqlPhysical.Expr.SearchedCase -> compileSearchedCase(expr, metas) - is PartiqlPhysical.Expr.Path -> compilePath(expr, metas) - is PartiqlPhysical.Expr.Struct -> compileStruct(expr) - is PartiqlPhysical.Expr.Parameter -> compileParameter(expr, metas) - is PartiqlPhysical.Expr.Date -> compileDate(expr, metas) - is PartiqlPhysical.Expr.LitTime -> compileLitTime(expr, metas) - - // arithmetic operations - is PartiqlPhysical.Expr.Plus -> compilePlus(expr, metas) - is PartiqlPhysical.Expr.Times -> compileTimes(expr, metas) - is PartiqlPhysical.Expr.Minus -> compileMinus(expr, metas) - is PartiqlPhysical.Expr.Divide -> compileDivide(expr, metas) - is PartiqlPhysical.Expr.Modulo -> compileModulo(expr, metas) - is PartiqlPhysical.Expr.BitwiseAnd -> compileBitwiseAnd(expr, metas) - - // comparison operators - is PartiqlPhysical.Expr.And -> compileAnd(expr, metas) - is PartiqlPhysical.Expr.Between -> compileBetween(expr, metas) - is PartiqlPhysical.Expr.Eq -> compileEq(expr, metas) - is PartiqlPhysical.Expr.Gt -> compileGt(expr, metas) - is PartiqlPhysical.Expr.Gte -> compileGte(expr, metas) - is PartiqlPhysical.Expr.Lt -> compileLt(expr, metas) - is PartiqlPhysical.Expr.Lte -> compileLte(expr, metas) - is PartiqlPhysical.Expr.Like -> compileLike(expr, metas) - is PartiqlPhysical.Expr.InCollection -> compileIn(expr, metas) - - // logical operators - is PartiqlPhysical.Expr.Ne -> compileNe(expr, metas) - is PartiqlPhysical.Expr.Or -> compileOr(expr, metas) - - // unary - is PartiqlPhysical.Expr.Not -> compileNot(expr, metas) - is PartiqlPhysical.Expr.Pos -> compilePos(expr, metas) - is PartiqlPhysical.Expr.Neg -> compileNeg(expr, metas) - - // other operators - is PartiqlPhysical.Expr.Concat -> compileConcat(expr, metas) - is PartiqlPhysical.Expr.Call -> compileCall(expr, metas) - is PartiqlPhysical.Expr.NullIf -> compileNullIf(expr, metas) - is PartiqlPhysical.Expr.Coalesce -> compileCoalesce(expr, metas) - - // "typed" operators (RHS is a data type and not an expression) - is PartiqlPhysical.Expr.Cast -> compileCast(expr, metas) - is PartiqlPhysical.Expr.IsType -> compileIs(expr, metas) - is PartiqlPhysical.Expr.CanCast -> compileCanCast(expr, metas) - is PartiqlPhysical.Expr.CanLosslessCast -> compileCanLosslessCast(expr, metas) - - // sequence constructors - is PartiqlPhysical.Expr.List -> compileSeq(ExprValueType.LIST, expr.values, metas) - is PartiqlPhysical.Expr.Sexp -> compileSeq(ExprValueType.SEXP, expr.values, metas) - is PartiqlPhysical.Expr.Bag -> compileSeq(ExprValueType.BAG, expr.values, metas) - - // bag operators - is PartiqlPhysical.Expr.BagOp -> compileBagOp(expr, metas) - is PartiqlPhysical.Expr.BindingsToValues -> compileBindingsToValues(expr) - is PartiqlPhysical.Expr.Pivot -> compilePivot(expr, metas) - is PartiqlPhysical.Expr.GraphMatch -> TODO("Physical compilation of GraphMatch expression") - is PartiqlPhysical.Expr.Timestamp -> TODO() - } - } - - private fun compileBindingsToValues(expr: PartiqlPhysical.Expr.BindingsToValues): PhysicalPlanThunk { - val mapThunk = compileAstExpr(expr.exp) - val bexprThunk: RelationThunkEnv = bexperConverter.convert(expr.query) - - val relationType = when (expr.metas.containsKey(IsOrderedMeta.tag)) { - true -> RelationType.LIST - false -> RelationType.BAG - } - - return thunkFactory.thunkEnv(expr.metas) { env -> - // we create a snapshot for currentRegister to use during the evaluation - // this is to avoid issue when iterator planner result - val currentRegister = env.registers.clone() - val elements = sequence { - env.load(currentRegister) - val relItr = bexprThunk(env) - while (relItr.nextRow()) { - yield(mapThunk(env)) - } - } - when (relationType) { - RelationType.LIST -> ExprValue.newList(elements) - RelationType.BAG -> ExprValue.newBag(elements) - } - } - } - - private fun compileAstExprs(args: List) = args.map { compileAstExpr(it) } - - private fun compileNullIf(expr: PartiqlPhysical.Expr.NullIf, metas: MetaContainer): PhysicalPlanThunk { - val expr1Thunk = compileAstExpr(expr.expr1) - val expr2Thunk = compileAstExpr(expr.expr2) - - // Note: NULLIF does not propagate the unknown values and .exprEquals provides the correct semantics. - return thunkFactory.thunkEnv(metas) { env -> - val expr1Value = expr1Thunk(env) - val expr2Value = expr2Thunk(env) - when { - expr1Value.exprEquals(expr2Value) -> ExprValue.nullValue - else -> expr1Value - } - } - } - - private fun compileCoalesce(expr: PartiqlPhysical.Expr.Coalesce, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.args) - - return thunkFactory.thunkEnv(metas) { env -> - var nullFound = false - var knownValue: ExprValue? = null - for (thunk in argThunks) { - val argValue = thunk(env) - if (argValue.isNotUnknown()) { - knownValue = argValue - // No need to execute remaining thunks to save computation as first non-unknown value is found - break - } - if (argValue.type == ExprValueType.NULL) { - nullFound = true - } - } - when (knownValue) { - null -> when { - evaluatorOptions.typingMode == TypingMode.PERMISSIVE && !nullFound -> ExprValue.missingValue - else -> ExprValue.nullValue - } - else -> knownValue - } - } - } - - /** - * Returns a function that accepts an [ExprValue] as an argument and returns true it is `NULL`, `MISSING`, or - * within the range specified by [range]. - */ - private fun integerValueValidator( - range: LongRange, - ): (ExprValue) -> Boolean = { value -> - when (value.type) { - ExprValueType.NULL, ExprValueType.MISSING -> true - ExprValueType.INT -> { - val longValue: Long = value.scalar.numberValue()?.toLong() - ?: error( - "ExprValue.numberValue() must not be `NULL` when its type is INT." + - "This indicates that the ExprValue instance has a bug." - ) - - // PRO-TIP: make sure to use the `Long` primitive type here with `.contains` otherwise - // Kotlin will use the version of `.contains` that treats [range] as a collection, and it will - // be very slow! - range.contains(longValue) - } - else -> error( - "The expression's static type was supposed to be INT but instead it was ${value.type}" + - "This may indicate the presence of a bug in the type inferencer." - ) - } - } - - /** - * For operators which could return integer type, check integer overflow in case of [TypingMode.PERMISSIVE]. - */ - private fun checkIntegerOverflow(computeThunk: PhysicalPlanThunk, metas: MetaContainer): PhysicalPlanThunk = - when (val staticTypes = metas.staticType?.type?.getTypes()) { - // No staticType, can't validate integer size. - null -> computeThunk - else -> { - when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> { - // integer size constraints have not been tested under [TypingMode.LEGACY] because the - // [StaticTypeInferenceVisitorTransform] doesn't support being used with legacy mode yet. - // throw an exception in case we encounter this untested scenario. This might work fine, but I - // wouldn't bet on it. - val hasConstrainedInteger = staticTypes.any { - it is IntType && it.rangeConstraint != IntType.IntRangeConstraint.UNCONSTRAINED - } - if (hasConstrainedInteger) { - TODO("Legacy mode doesn't support integer size constraints yet.") - } else { - computeThunk - } - } - TypingMode.PERMISSIVE -> { - val biggestIntegerType = staticTypes.filterIsInstance().maxByOrNull { - it.rangeConstraint.numBytes - } - when (biggestIntegerType) { - is IntType -> { - val validator = integerValueValidator(biggestIntegerType.rangeConstraint.validRange) - - thunkFactory.thunkEnv(metas) { env -> - val naryResult = computeThunk(env) - errorSignaler.errorIf( - !validator(naryResult), - ErrorCode.EVALUATOR_INTEGER_OVERFLOW, - { ErrorDetails(metas, "Integer overflow", errorContextFrom(metas)) }, - { naryResult } - ) - } - } - // If there is no IntType StaticType, can't validate the integer size either. - null -> computeThunk - else -> computeThunk - } - } - } - } - } - - private fun compilePlus(expr: PartiqlPhysical.Expr.Plus, metas: MetaContainer): PhysicalPlanThunk { - if (expr.operands.size < 2) { - error("Internal Error: PartiqlPhysical.Expr.Plus must have at least 2 arguments") - } - - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.numberValue() + rValue.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileMinus(expr: PartiqlPhysical.Expr.Minus, metas: MetaContainer): PhysicalPlanThunk { - if (expr.operands.size < 2) { - error("Internal Error: PartiqlPhysical.Expr.Minus must have at least 2 arguments") - } - - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.numberValue() - rValue.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compilePos(expr: PartiqlPhysical.Expr.Pos, metas: MetaContainer): PhysicalPlanThunk { - val exprThunk = compileAstExpr(expr.expr) - - val computeThunk = thunkFactory.thunkEnvOperands(metas, exprThunk) { _, value -> - // Invoking .numberValue() here makes this essentially just a type check - value.numberValue() - // Original value is returned unmodified. - value - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileNeg(expr: PartiqlPhysical.Expr.Neg, metas: MetaContainer): PhysicalPlanThunk { - val exprThunk = compileAstExpr(expr.expr) - - val computeThunk = thunkFactory.thunkEnvOperands(metas, exprThunk) { _, value -> - (-value.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileTimes(expr: PartiqlPhysical.Expr.Times, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.numberValue() * rValue.numberValue()).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileDivide(expr: PartiqlPhysical.Expr.Divide, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - val denominator = rValue.numberValue() - - errorSignaler.errorIf( - denominator.isZero(), - ErrorCode.EVALUATOR_DIVIDE_BY_ZERO, - { ErrorDetails(metas, "/ by zero") } - ) { - try { - (lValue.numberValue() / denominator).exprValue() - } catch (e: ArithmeticException) { - // Setting the internal flag as true as it is not clear what - // ArithmeticException may be thrown by the above - throw EvaluationException( - cause = e, - errorCode = ErrorCode.EVALUATOR_ARITHMETIC_EXCEPTION, - internal = true - ) - } - } - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileModulo(expr: PartiqlPhysical.Expr.Modulo, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - val computeThunk = thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - val denominator = rValue.numberValue() - if (denominator.isZero()) { - err("% by zero", ErrorCode.EVALUATOR_MODULO_BY_ZERO, errorContextFrom(metas), internal = false) - } - - (lValue.numberValue() % denominator).exprValue() - } - - return checkIntegerOverflow(computeThunk, metas) - } - - private fun compileBitwiseAnd(expr: PartiqlPhysical.Expr.BitwiseAnd, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - (lValue.longValue() and rValue.longValue()).exprValue() - } - } - - private fun compileEq(expr: PartiqlPhysical.Expr.Eq, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> - (lValue.exprEquals(rValue)) - } - } - - private fun compileNe(expr: PartiqlPhysical.Expr.Ne, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - ((!lValue.exprEquals(rValue)).exprValue()) - } - } - - private fun compileLt(expr: PartiqlPhysical.Expr.Lt, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue < rValue } - } - - private fun compileLte(expr: PartiqlPhysical.Expr.Lte, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue <= rValue } - } - - private fun compileGt(expr: PartiqlPhysical.Expr.Gt, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue > rValue } - } - - private fun compileGte(expr: PartiqlPhysical.Expr.Gte, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkAndMap(metas, argThunks) { lValue, rValue -> lValue >= rValue } - } - - private fun compileBetween(expr: PartiqlPhysical.Expr.Between, metas: MetaContainer): PhysicalPlanThunk { - val valueThunk = compileAstExpr(expr.value) - val fromThunk = compileAstExpr(expr.from) - val toThunk = compileAstExpr(expr.to) - - return thunkFactory.thunkEnvOperands(metas, valueThunk, fromThunk, toThunk) { _, v, f, t -> - (v >= f && v <= t).exprValue() - } - } - - /** - * `IN` can *almost* be thought of has being syntactic sugar for the `OR` operator. - * - * `a IN (b, c, d)` is equivalent to `a = b OR a = c OR a = d`. On deep inspection, there - * are important implications to this regarding propagation of unknown values. Specifically, the - * presence of any unknown in `b`, `c`, or `d` will result in unknown propagation iif `a` does not - * equal `b`, `c`, or `d`. i.e.: - * - * - `1 in (null, 2, 3)` -> `null` - * - `2 in (null, 2, 3)` -> `true` - * - `2 in (1, 2, 3)` -> `true` - * - `0 in (1, 2, 4)` -> `false` - * - * `IN` is varies from the `OR` operator in that this behavior holds true when other types of expressions are - * used on the right side of `IN` such as sub-queries and variables whose value is that of a list or bag. - */ - private fun compileIn(expr: PartiqlPhysical.Expr.InCollection, metas: MetaContainer): PhysicalPlanThunk { - val args = expr.operands - val leftThunk = compileAstExpr(args[0]) - val rightOp = args[1] - - fun isOptimizedCase(values: List): Boolean = - values.all { it is PartiqlPhysical.Expr.Lit && !it.value.isNull } - - fun optimizedCase(values: List): PhysicalPlanThunk { - // Put all the literals in the sequence into a pre-computed map to be checked later by the thunk. - // If the left-hand value is one of these we can short-circuit with a result of TRUE. - // This is the fastest possible case and allows for hundreds of literal values (or more) in the - // sequence without a huge performance penalty. - // NOTE: we cannot use a [HashSet<>] here because [ExprValue] does not implement [Object.hashCode] or - // [Object.equals]. - val precomputedLiteralsMap = values - .filterIsInstance() - .mapTo(TreeSet(DEFAULT_COMPARATOR)) { - ExprValue.of( - it.value.toIonValue(ion) - ) - } - - // the compiled thunk simply checks if the left side is contained on the right side. - // thunkEnvOperands takes care of unknown propagation for the left side; for the right, - // this unknown propagation does not apply since we've eliminated the possibility of unknowns above. - return thunkFactory.thunkEnvOperands(metas, leftThunk) { _, leftValue -> - precomputedLiteralsMap.contains(leftValue).exprValue() - } - } - - return when { - // We can significantly optimize this if rightArg is a sequence constructor which is composed of entirely - // of non-null literal values. - rightOp is PartiqlPhysical.Expr.List && isOptimizedCase(rightOp.values) -> optimizedCase(rightOp.values) - rightOp is PartiqlPhysical.Expr.Bag && isOptimizedCase(rightOp.values) -> optimizedCase(rightOp.values) - rightOp is PartiqlPhysical.Expr.Sexp && isOptimizedCase(rightOp.values) -> optimizedCase(rightOp.values) - // The unoptimized case... - else -> { - val rightThunk = compileAstExpr(rightOp) - - // Legacy mode: - // Returns FALSE when the right side of IN is not a sequence - // Returns NULL if the right side is MISSING or any value on the right side is MISSING - // Permissive mode: - // Returns MISSING when the right side of IN is not a sequence - // Returns MISSING if the right side is MISSING or any value on the right side is MISSING - val (propagateMissingAs, propagateNotASeqAs) = when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> ExprValue.nullValue to ExprValue.newBoolean(false) - TypingMode.PERMISSIVE -> ExprValue.missingValue to ExprValue.missingValue - } - - // Note that standard unknown propagation applies to the left and right operands. Both [TypingMode]s - // are handled by [ThunkFactory.thunkEnvOperands] and that additional rules for unknown propagation are - // implemented within the thunk for the values within the sequence on the right side of IN. - thunkFactory.thunkEnvOperands(metas, leftThunk, rightThunk) { _, leftValue, rightValue -> - var nullSeen = false - var missingSeen = false - - when { - rightValue.type == ExprValueType.MISSING -> propagateMissingAs - !rightValue.type.isSequence -> propagateNotASeqAs - else -> { - rightValue.forEach { - when (it.type) { - ExprValueType.NULL -> nullSeen = true - ExprValueType.MISSING -> missingSeen = true - // short-circuit to TRUE on the first matching value - else -> if (it.exprEquals(leftValue)) { - return@thunkEnvOperands ExprValue.newBoolean(true) - } - } - } - // If we make it here then there was no match. Propagate MISSING, NULL or return false. - // Note that if both MISSING and NULL was encountered, MISSING takes precedence. - when { - missingSeen -> propagateMissingAs - nullSeen -> ExprValue.nullValue - else -> ExprValue.newBoolean(false) - } - } - } - } - } - } - } - - private fun compileNot(expr: PartiqlPhysical.Expr.Not, metas: MetaContainer): PhysicalPlanThunk { - val argThunk = compileAstExpr(expr.expr) - - return thunkFactory.thunkEnvOperands(metas, argThunk) { _, value -> - (!value.booleanValue()).exprValue() - } - } - - private fun compileAnd(expr: PartiqlPhysical.Expr.And, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - // can't use the null propagation supplied by [ThunkFactory.thunkEnv] here because AND short-circuits on - // false values and *NOT* on NULL or MISSING - return when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> thunkFactory.thunkEnv(metas) thunk@{ env -> - var hasUnknowns = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - when { - currValue.isUnknown() -> hasUnknowns = true - // Short circuit only if we encounter a known false value. - !currValue.booleanValue() -> return@thunk ExprValue.newBoolean(false) - } - } - - when (hasUnknowns) { - true -> ExprValue.nullValue - false -> ExprValue.newBoolean(true) - } - } - TypingMode.PERMISSIVE -> thunkFactory.thunkEnv(metas) thunk@{ env -> - var hasNull = false - var hasMissing = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - when (currValue.type) { - // Short circuit only if we encounter a known false value. - ExprValueType.BOOL -> if (!currValue.booleanValue()) return@thunk ExprValue.newBoolean(false) - ExprValueType.NULL -> hasNull = true - // type mismatch, return missing - else -> hasMissing = true - } - } - - when { - hasMissing -> ExprValue.missingValue - hasNull -> ExprValue.nullValue - else -> ExprValue.newBoolean(true) - } - } - } - } - - private fun compileOr(expr: PartiqlPhysical.Expr.Or, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - // can't use the null propagation supplied by [ThunkFactory.thunkEnv] here because OR short-circuits on - // true values and *NOT* on NULL or MISSING - return when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> - thunkFactory.thunkEnv(metas) thunk@{ env -> - var hasUnknowns = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - // How null-propagation works for OR is rather weird according to the SQL-92 spec. - // Nulls are propagated like other expressions only when none of the terms are TRUE. - // If any one of them is TRUE, then the entire expression evaluates to TRUE, i.e.: - // NULL OR TRUE -> TRUE - // NULL OR FALSE -> NULL - // (strange but true) - when { - currValue.isUnknown() -> hasUnknowns = true - currValue.booleanValue() -> return@thunk ExprValue.newBoolean(true) - } - } - - when (hasUnknowns) { - true -> ExprValue.nullValue - false -> ExprValue.newBoolean(false) - } - } - TypingMode.PERMISSIVE -> thunkFactory.thunkEnv(metas) thunk@{ env -> - var hasNull = false - var hasMissing = false - argThunks.forEach { currThunk -> - val currValue = currThunk(env) - when (currValue.type) { - // Short circuit only if we encounter a known true value. - ExprValueType.BOOL -> if (currValue.booleanValue()) return@thunk ExprValue.newBoolean(true) - ExprValueType.NULL -> hasNull = true - else -> hasMissing = true // type mismatch, return missing. - } - } - - when { - hasMissing -> ExprValue.missingValue - hasNull -> ExprValue.nullValue - else -> ExprValue.newBoolean(false) - } - } - } - } - - private fun compileConcat(expr: PartiqlPhysical.Expr.Concat, metas: MetaContainer): PhysicalPlanThunk { - val argThunks = compileAstExprs(expr.operands) - - return thunkFactory.thunkFold(metas, argThunks) { lValue, rValue -> - val lType = lValue.type - val rType = rValue.type - - if (lType.isText && rType.isText) { - // null/missing propagation is handled before getting here - (lValue.stringValue() + rValue.stringValue()).exprValue() - } else { - err( - "Wrong argument type for ||", - ErrorCode.EVALUATOR_CONCAT_FAILED_DUE_TO_INCOMPATIBLE_TYPE, - errorContextFrom(metas).also { - it[Property.ACTUAL_ARGUMENT_TYPES] = listOf(lType, rType).toString() - }, - internal = false - ) - } - } - } - - private fun compileCall(expr: PartiqlPhysical.Expr.Call, metas: MetaContainer): PhysicalPlanThunk { - val funcArgThunks = compileAstExprs(expr.args) - val arity = funcArgThunks.size - val name = expr.funcName.text - return thunkFactory.thunkEnv(metas) { env -> - val args = funcArgThunks.map { thunk -> thunk(env) } - val argTypes = args.map { staticTypeFromExprValue(it) } - try { - val func = functionManager.get(name = name, arity = arity, args = argTypes) - val computeThunk = when (func.signature.unknownArguments) { - UnknownArguments.PROPAGATE -> thunkFactory.thunkEnvOperands(metas, funcArgThunks) { env, values -> - func.call(env.session, args) - } - UnknownArguments.PASS_THRU -> thunkFactory.thunkEnv(metas) { env -> - func.call(env.session, args) - } - } - checkIntegerOverflow(computeThunk, metas)(env) - } catch (e: FunctionNotFoundException) { - err( - "No such function: $name", - ErrorCode.EVALUATOR_NO_SUCH_FUNCTION, - errorContextFrom(metas).also { - it[Property.FUNCTION_NAME] = name - }, - internal = false - ) - } catch (e: org.partiql.lang.eval.internal.ArityMismatchException) { - val (minArity, maxArity) = e.arity - val errorContext = errorContextFrom(metas).also { - it[Property.FUNCTION_NAME] = name - it[Property.EXPECTED_ARITY_MIN] = minArity - it[Property.EXPECTED_ARITY_MAX] = maxArity - it[Property.ACTUAL_ARITY] = arity - } - err( - "No function found with matching arity: $name", - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNC_CALL, - errorContext, - internal = false - ) - } - } - } - - private fun compileLit(expr: PartiqlPhysical.Expr.Lit, metas: MetaContainer): PhysicalPlanThunk { - val value = ExprValue.of(expr.value.toIonValue(ion)) - - return thunkFactory.thunkEnv(metas) { value } - } - - private fun compileMissing(metas: MetaContainer): PhysicalPlanThunk = - thunkFactory.thunkEnv(metas) { ExprValue.missingValue } - - private fun compileGlobalId(expr: PartiqlPhysical.Expr.GlobalId): PhysicalPlanThunk { - // TODO: we really should consider using something other than `Bindings` for global variables - // with the physical plan evaluator because `Bindings.get()` accepts a `BindingName` instance - // which contains the `case` property which is always set to `SENSITIVE` and is therefore redundant. - val bindingName = BindingName(expr.uniqueId.text, BindingCase.SENSITIVE) - return thunkFactory.thunkEnv(expr.metas) { env -> - env.session.globals[bindingName] ?: throwUndefinedVariableException(bindingName, expr.metas) - } - } - - @Suppress("UNUSED_PARAMETER") - private fun compileLocalId(expr: PartiqlPhysical.Expr.LocalId, metas: MetaContainer): PhysicalPlanThunk { - val localIndex = expr.index.value.toIntExact() - return thunkFactory.thunkEnv(metas) { env -> - env.registers[localIndex] - } - } - - private fun compileParameter(expr: PartiqlPhysical.Expr.Parameter, metas: MetaContainer): PhysicalPlanThunk { - val ordinal = expr.index.value.toInt() - val index = ordinal - 1 - - return { env -> - val params = env.session.parameters - if (params.size <= index) { - throw EvaluationException( - "Unbound parameter for ordinal: $ordinal", - ErrorCode.EVALUATOR_UNBOUND_PARAMETER, - errorContextFrom(metas).also { - it[Property.EXPECTED_PARAMETER_ORDINAL] = ordinal - it[Property.BOUND_PARAMETER_COUNT] = params.size - }, - internal = false - ) - } - params[index] - } - } - - /** - * Returns a lambda that implements the `IS` operator type check according to the current - * [TypedOpBehavior]. - */ - private fun makeIsCheck( - staticType: SingleType, - typedOpParameter: TypedOpParameter, - metas: MetaContainer, - ): (ExprValue) -> Boolean { - return when (evaluatorOptions.typedOpBehavior) { - TypedOpBehavior.HONOR_PARAMETERS -> { expValue: ExprValue -> - staticType.allTypes.any { - val matchesStaticType = try { - isInstance(expValue, it) - } catch (e: UnsupportedTypeCheckException) { - err( - e.message!!, - ErrorCode.UNIMPLEMENTED_FEATURE, - errorContextFrom(metas), - internal = true - ) - } - - when { - !matchesStaticType -> false - else -> when (val validator = typedOpParameter.validationThunk) { - null -> true - else -> validator(expValue) - } - } - } - } - } - } - - private fun compileIs(expr: PartiqlPhysical.Expr.IsType, metas: MetaContainer): PhysicalPlanThunk { - val expThunk = compileAstExpr(expr.value) - val typedOpParameter = expr.type.toTypedOpParameter(customTypedOpParameters) - if (typedOpParameter.staticType is AnyType) { - return thunkFactory.thunkEnv(metas) { ExprValue.newBoolean(true) } - } - if (evaluatorOptions.typedOpBehavior == TypedOpBehavior.HONOR_PARAMETERS && expr.type is PartiqlPhysical.Type.FloatType && (expr.type as PartiqlPhysical.Type.FloatType).precision != null) { - err( - "FLOAT precision parameter is unsupported", - ErrorCode.SEMANTIC_FLOAT_PRECISION_UNSUPPORTED, - errorContextFrom(expr.type.metas), - internal = false - ) - } - - val typeMatchFunc = when (val staticType = typedOpParameter.staticType) { - is SingleType -> makeIsCheck(staticType, typedOpParameter, metas) - is AnyOfType -> staticType.types.map { childType -> - when (childType) { - is SingleType -> makeIsCheck(childType, typedOpParameter, metas) - else -> err( - "Union type cannot have ANY or nested AnyOf type for IS", - ErrorCode.SEMANTIC_UNION_TYPE_INVALID, - errorContextFrom(metas), - internal = true - ) - } - }.let { typeMatchFuncs -> - { expValue: ExprValue -> typeMatchFuncs.any { func -> func(expValue) } } - } - is AnyType -> throw IllegalStateException("Unexpected ANY type in IS compilation") - } - - return thunkFactory.thunkEnv(metas) { env -> - val expValue = expThunk(env) - typeMatchFunc(expValue).exprValue() - } - } - - private fun compileCastHelper( - value: PartiqlPhysical.Expr, - asType: PartiqlPhysical.Type, - metas: MetaContainer, - ): PhysicalPlanThunk { - val expThunk = compileAstExpr(value) - val typedOpParameter = asType.toTypedOpParameter(customTypedOpParameters) - if (typedOpParameter.staticType is AnyType) { - return expThunk - } - if (evaluatorOptions.typedOpBehavior == TypedOpBehavior.HONOR_PARAMETERS && asType is PartiqlPhysical.Type.FloatType && asType.precision != null) { - err( - "FLOAT precision parameter is unsupported", - ErrorCode.SEMANTIC_FLOAT_PRECISION_UNSUPPORTED, - errorContextFrom(asType.metas), - internal = false - ) - } - - fun typeOpValidate( - value: ExprValue, - castOutput: ExprValue, - typeName: String, - locationMeta: SourceLocationMeta?, - ) { - if (typedOpParameter.validationThunk?.let { it(castOutput) } == false) { - val errorContext = PropertyValueMap().also { - it[Property.CAST_FROM] = value.type.toString() - it[Property.CAST_TO] = typeName - } - - locationMeta?.let { fillErrorContext(errorContext, it) } - - throw EvaluationException( - "Validation failure for $asType", - ErrorCode.EVALUATOR_CAST_FAILED, - errorContext, - internal = false - ) - } - } - - fun singleTypeCastFunc(singleType: SingleType): CastFunc { - val locationMeta = metas.sourceLocationMeta - return { value -> - val castOutput = value.cast( - singleType, - evaluatorOptions.typedOpBehavior, - locationMeta, - evaluatorOptions.defaultTimezoneOffset - ) - typeOpValidate(value, castOutput, getRuntimeType(singleType).toString(), locationMeta) - castOutput - } - } - - fun compileSingleTypeCast(singleType: SingleType): PhysicalPlanThunk { - val castFunc = singleTypeCastFunc(singleType) - // We do not use thunkFactory here because we want to explicitly avoid - // the optional evaluation-time type check for CAN_CAST below. - // Can cast needs that returns false if an - // exception is thrown during a normal cast operation. - return { env -> - val valueToCast = expThunk(env) - castFunc(valueToCast) - } - } - - fun compileCast(type: StaticType): PhysicalPlanThunk = when (type) { - is SingleType -> compileSingleTypeCast(type) - is AnyOfType -> { - val locationMeta = metas.sourceLocationMeta - val castTable = AnyOfCastTable(type, metas, ::singleTypeCastFunc); - - // We do not use thunkFactory here because we want to explicitly avoid - // the optional evaluation-time type check for CAN_CAST below. - // note that this would interfere with the error handling for can_cast that returns false if an - // exception is thrown during a normal cast operation. - { env -> - val sourceValue = expThunk(env) - castTable.cast(sourceValue).also { - // TODO put the right type name here - typeOpValidate(sourceValue, it, "", locationMeta) - } - } - } - is AnyType -> throw IllegalStateException("Unreachable code") - } - - return compileCast(typedOpParameter.staticType) - } - - private fun compileCast(expr: PartiqlPhysical.Expr.Cast, metas: MetaContainer): PhysicalPlanThunk = - thunkFactory.thunkEnv(metas, compileCastHelper(expr.value, expr.asType, metas)) - - private fun compileCanCast(expr: PartiqlPhysical.Expr.CanCast, metas: MetaContainer): PhysicalPlanThunk { - val typedOpParameter = expr.asType.toTypedOpParameter(customTypedOpParameters) - if (typedOpParameter.staticType is AnyType) { - return thunkFactory.thunkEnv(metas) { ExprValue.newBoolean(true) } - } - - val expThunk = compileAstExpr(expr.value) - - // TODO consider making this more efficient by not directly delegating to CAST - // TODO consider also making the operand not double evaluated (e.g. having expThunk memoize) - val castThunkEnv = compileCastHelper(expr.value, expr.asType, expr.metas) - return thunkFactory.thunkEnv(metas) { env -> - val sourceValue = expThunk(env) - try { - when { - // NULL/MISSING can cast to anything as themselves - sourceValue.isUnknown() -> ExprValue.newBoolean(true) - else -> { - val castedValue = castThunkEnv(env) - when { - // NULL/MISSING from cast is a permissive way to signal failure - castedValue.isUnknown() -> ExprValue.newBoolean(false) - else -> ExprValue.newBoolean(true) - } - } - } - } catch (e: EvaluationException) { - if (e.internal) { - throw e - } - ExprValue.newBoolean(false) - } - } - } - - private fun compileCanLosslessCast( - expr: PartiqlPhysical.Expr.CanLosslessCast, - metas: MetaContainer, - ): PhysicalPlanThunk { - val typedOpParameter = expr.asType.toTypedOpParameter(customTypedOpParameters) - if (typedOpParameter.staticType is AnyType) { - return thunkFactory.thunkEnv(metas) { ExprValue.newBoolean(true) } - } - - val expThunk = compileAstExpr(expr.value) - - // TODO consider making this more efficient by not directly delegating to CAST - val castThunkEnv = compileCastHelper(expr.value, expr.asType, expr.metas) - return thunkFactory.thunkEnv(metas) { env -> - val sourceValue = expThunk(env) - val sourceType = staticTypeFromExprValue(sourceValue) - - fun roundTrip(): ExprValue { - val castedValue = castThunkEnv(env) - - val locationMeta = metas.sourceLocationMeta - fun castFunc(singleType: SingleType) = - { value: ExprValue -> - value.cast( - singleType, - evaluatorOptions.typedOpBehavior, - locationMeta, - evaluatorOptions.defaultTimezoneOffset - ) - } - - val roundTripped = when (sourceType) { - is SingleType -> castFunc(sourceType)(castedValue) - is AnyOfType -> { - val castTable = AnyOfCastTable(sourceType, metas, ::castFunc) - castTable.cast(sourceValue) - } - // Should not be possible - is AnyType -> throw IllegalStateException("ANY type is not configured correctly in compiler") - } - - val lossless = sourceValue.exprEquals(roundTripped) - return ExprValue.newBoolean(lossless) - } - - try { - when (sourceValue.type) { - // NULL can cast to anything as itself - ExprValueType.NULL -> ExprValue.newBoolean(true) - - // Short-circuit timestamp -> date roundtrip if precision isn't [Timestamp.Precision.DAY] or - // [Timestamp.Precision.MONTH] or [Timestamp.Precision.YEAR] - ExprValueType.TIMESTAMP -> when (typedOpParameter.staticType) { - StaticType.DATE -> when (sourceValue.timestampValue().precision) { - Timestamp.Precision.DAY, Timestamp.Precision.MONTH, Timestamp.Precision.YEAR -> roundTrip() - else -> ExprValue.newBoolean(false) - } - StaticType.TIME -> ExprValue.newBoolean(false) - else -> roundTrip() - } - - // For all other cases, attempt a round-trip of the value through the source and dest types - else -> roundTrip() - } - } catch (e: EvaluationException) { - if (e.internal) { - throw e - } - ExprValue.newBoolean(false) - } - } - } - - private fun compileSimpleCase(expr: PartiqlPhysical.Expr.SimpleCase, metas: MetaContainer): PhysicalPlanThunk { - val valueThunk = compileAstExpr(expr.expr) - val branchThunks = expr.cases.pairs.map { Pair(compileAstExpr(it.first), compileAstExpr(it.second)) } - val elseThunk = when (val default = expr.default) { - null -> thunkFactory.thunkEnv(metas) { ExprValue.nullValue } - else -> compileAstExpr(default) - } - - return thunkFactory.thunkEnv(metas) thunk@{ env -> - val caseValue = valueThunk(env) - // if the case value is unknown then we can short-circuit to the elseThunk directly - when { - caseValue.isUnknown() -> elseThunk(env) - else -> { - branchThunks.forEach { bt -> - val branchValue = bt.first(env) - // Just skip any branch values that are unknown, which we consider the same as false here. - when { - branchValue.isUnknown() -> { /* intentionally blank */ - } - else -> { - if (caseValue.exprEquals(branchValue)) { - return@thunk bt.second(env) - } - } - } - } - } - } - elseThunk(env) - } - } - - private fun compileSearchedCase(expr: PartiqlPhysical.Expr.SearchedCase, metas: MetaContainer): PhysicalPlanThunk { - val branchThunks = expr.cases.pairs.map { compileAstExpr(it.first) to compileAstExpr(it.second) } - val elseThunk = when (val default = expr.default) { - null -> thunkFactory.thunkEnv(metas) { ExprValue.nullValue } - else -> compileAstExpr(default) - } - - return when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> thunkFactory.thunkEnv(metas) thunk@{ env -> - branchThunks.forEach { bt -> - val conditionValue = bt.first(env) - // Any unknown value is considered the same as false. - // Note that .booleanValue() here will throw an EvaluationException if - // the data type is not boolean. - // TODO: .booleanValue does not have access to metas, so the EvaluationException is reported to be - // at the line & column of the CASE statement, not the predicate, unfortunately. - if (conditionValue.isNotUnknown() && conditionValue.booleanValue()) { - return@thunk bt.second(env) - } - } - elseThunk(env) - } - // Permissive mode propagates data type mismatches as MISSING, which is - // equivalent to false for searched CASE predicates. To simplify this, - // all we really need to do is consider any non-boolean result from the - // predicate to be false. - TypingMode.PERMISSIVE -> thunkFactory.thunkEnv(metas) thunk@{ env -> - branchThunks.forEach { bt -> - val conditionValue = bt.first(env) - if (conditionValue.type == ExprValueType.BOOL && conditionValue.booleanValue()) { - return@thunk bt.second(env) - } - } - elseThunk(env) - } - } - } - - private fun compileStruct(expr: PartiqlPhysical.Expr.Struct): PhysicalPlanThunk { - val structParts = compileStructParts(expr.parts) - - val ordering = if (expr.parts.none { it is PartiqlPhysical.StructPart.StructFields }) - StructOrdering.ORDERED - else - StructOrdering.UNORDERED - - return thunkFactory.thunkEnv(expr.metas) { env -> - val columns = mutableListOf() - for (element in structParts) { - when (element) { - is CompiledStructPart.Field -> { - val fieldName = element.nameThunk(env) - when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> - if (!fieldName.type.isText) { - err( - "Found struct field key to be of type ${fieldName.type}", - ErrorCode.EVALUATOR_NON_TEXT_STRUCT_FIELD_KEY, - errorContextFrom(expr.metas.sourceLocationMeta).also { pvm -> - pvm[Property.ACTUAL_TYPE] = fieldName.type.toString() - }, - internal = false - ) - } - TypingMode.PERMISSIVE -> - if (!fieldName.type.isText) { - continue - } - } - val fieldValue = element.valueThunk(env) - columns.add(fieldValue.namedValue(fieldName)) - } - is CompiledStructPart.StructMerge -> { - for (projThunk in element.thunks) { - val value = projThunk(env) - if (value.type == ExprValueType.MISSING) continue - - val children = value.asSequence() - if (!children.any() || value.type.isSequence) { - val name = syntheticColumnName(columns.size).exprValue() - columns.add(value.namedValue(name)) - } else { - val valuesToProject = - when (evaluatorOptions.projectionIteration) { - ProjectionIterationBehavior.FILTER_MISSING -> { - value.filter { it.type != ExprValueType.MISSING } - } - ProjectionIterationBehavior.UNFILTERED -> value - } - for (childValue in valuesToProject) { - val namedFacet = childValue.asFacet(Named::class.java) - val name = namedFacet?.name - ?: syntheticColumnName(columns.size).exprValue() - columns.add(childValue.namedValue(name)) - } - } - } - } - } - } - createStructExprValue(columns.asSequence(), ordering) - } - } - - private fun compileStructParts(projectItems: List): List = - projectItems.map { it -> - when (it) { - is PartiqlPhysical.StructPart.StructField -> { - val fieldThunk = compileAstExpr(it.fieldName) - val valueThunk = compileAstExpr(it.value) - CompiledStructPart.Field(fieldThunk, valueThunk) - } - is PartiqlPhysical.StructPart.StructFields -> { - CompiledStructPart.StructMerge(listOf(compileAstExpr(it.partExpr))) - } - } - } - - private fun compileSeq( - seqType: ExprValueType, - itemExprs: List, - metas: MetaContainer, - ): PhysicalPlanThunk { - require(seqType.isSequence) { "seqType must be a sequence!" } - - val itemThunks = compileAstExprs(itemExprs) - - val makeItemThunkSequence = when (seqType) { - ExprValueType.BAG -> { env: EvaluatorState -> - itemThunks.asSequence().map { itemThunk -> - // call to unnamedValue() makes sure we don't expose any underlying value name/ordinal - itemThunk(env).unnamedValue() - } - } - else -> { env: EvaluatorState -> - itemThunks.asSequence().mapIndexed { i, itemThunk -> itemThunk(env).namedValue(i.exprValue()) } - } - } - - return thunkFactory.thunkEnv(metas) { env -> - when (seqType) { - ExprValueType.BAG -> ExprValue.newBag(makeItemThunkSequence(env)) - ExprValueType.LIST -> ExprValue.newList(makeItemThunkSequence(env)) - ExprValueType.SEXP -> ExprValue.newSexp(makeItemThunkSequence(env)) - else -> error("sequence type required") - } - } - } - - private fun compilePath(expr: PartiqlPhysical.Expr.Path, metas: MetaContainer): PhysicalPlanThunk { - val rootThunk = compileAstExpr(expr.root) - val remainingComponents = LinkedList() - - expr.steps.forEach { remainingComponents.addLast(it) } - - val componentThunk = compilePathComponents(remainingComponents, metas) - - return thunkFactory.thunkEnv(metas) { env -> - val rootValue = rootThunk(env) - componentThunk(env, rootValue) - } - } - - private fun compilePathComponents( - remainingComponents: LinkedList, - pathMetas: MetaContainer, - ): PhysicalPlanThunkValue { - - val componentThunks = ArrayList>() - - while (!remainingComponents.isEmpty()) { - val pathComponent = remainingComponents.removeFirst() - val componentMetas = pathComponent.metas - componentThunks.add( - when (pathComponent) { - is PartiqlPhysical.PathStep.PathExpr -> { - val indexExpr = pathComponent.index - val caseSensitivity = pathComponent.case - when { - // If indexExpr is a literal string, there is no need to evaluate it--just compile a - // thunk that directly returns a bound value - indexExpr is PartiqlPhysical.Expr.Lit && indexExpr.value.toIonValue(ion) is IonString -> { - val lookupName = BindingName( - indexExpr.value.toIonValue(ion).stringValue()!!, - caseSensitivity.toBindingCase() - ) - thunkFactory.thunkEnvValue(componentMetas) { _, componentValue -> - componentValue.bindings[lookupName] ?: ExprValue.missingValue - } - } - else -> { - val indexThunk = compileAstExpr(indexExpr) - thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val indexValue = indexThunk(env) - when { - indexValue.type == ExprValueType.INT -> { - componentValue.ordinalBindings[indexValue.numberValue().toInt()] - } - indexValue.type.isText -> { - val lookupName = - BindingName(indexValue.stringValue(), caseSensitivity.toBindingCase()) - componentValue.bindings[lookupName] - } - else -> { - when (evaluatorOptions.typingMode) { - TypingMode.LEGACY -> err( - "Cannot convert index to int/string: $indexValue", - ErrorCode.EVALUATOR_INVALID_CONVERSION, - errorContextFrom(componentMetas), - internal = false - ) - TypingMode.PERMISSIVE -> ExprValue.missingValue - } - } - } ?: ExprValue.missingValue - } - } - } - } - is PartiqlPhysical.PathStep.PathUnpivot -> { - when { - !remainingComponents.isEmpty() -> { - val tempThunk = compilePathComponents(remainingComponents, pathMetas) - thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val mapped = componentValue.unpivot() - .flatMap { tempThunk(env, it).rangeOver() } - .asSequence() - ExprValue.newBag(mapped) - } - } - else -> - thunkFactory.thunkEnvValue(componentMetas) { _, componentValue -> - ExprValue.newBag(componentValue.unpivot().asSequence()) - } - } - } - // this is for `path[*].component` - is PartiqlPhysical.PathStep.PathWildcard -> { - when { - !remainingComponents.isEmpty() -> { - val hasMoreWildCards = - remainingComponents.filterIsInstance().any() - val tempThunk = compilePathComponents(remainingComponents, pathMetas) - - when { - !hasMoreWildCards -> thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val mapped = componentValue - .rangeOver() - .map { tempThunk(env, it) } - .asSequence() - - ExprValue.newBag(mapped) - } - else -> thunkFactory.thunkEnvValue(componentMetas) { env, componentValue -> - val mapped = componentValue - .rangeOver() - .flatMap { - val tempValue = tempThunk(env, it) - tempValue - } - .asSequence() - - ExprValue.newBag(mapped) - } - } - } - else -> { - thunkFactory.thunkEnvValue(componentMetas) { _, componentValue -> - val mapped = componentValue.rangeOver().asSequence() - ExprValue.newBag(mapped) - } - } - } - } - } - ) - } - return when (componentThunks.size) { - 1 -> componentThunks.first() - else -> thunkFactory.thunkEnvValue(pathMetas) { env, rootValue -> - componentThunks.fold(rootValue) { componentValue, componentThunk -> - componentThunk(env, componentValue) - } - } - } - } - - /** - * Given an AST node that represents a `LIKE` predicate, return an ExprThunk that evaluates a `LIKE` predicate. - * - * Three cases - * - * 1. All arguments are literals, then compile and run the pattern - * 1. Search pattern and escape pattern are literals, compile the pattern. Running the pattern deferred to evaluation time. - * 1. Pattern or escape (or both) are *not* literals, compile and running of pattern deferred to evaluation time. - * - * ``` - * LIKE [ESCAPE ] - * ``` - * - * @return a thunk that when provided with an environment evaluates the `LIKE` predicate - */ - private fun compileLike(expr: PartiqlPhysical.Expr.Like, metas: MetaContainer): PhysicalPlanThunk { - val valueExpr = expr.value - val patternExpr = expr.pattern - val escapeExpr = expr.escape - - val patternLocationMeta = patternExpr.metas.sourceLocation - val escapeLocationMeta = escapeExpr?.metas?.sourceLocation - - // This is so that null short-circuits can be supported. - fun getRegexPattern(pattern: ExprValue, escape: ExprValue?): (() -> Pattern)? { - val patternArgs = listOfNotNull(pattern, escape) - when { - patternArgs.any { it.type.isUnknown } -> return null - patternArgs.any { !it.type.isText } -> return { - err( - "LIKE expression must be given non-null strings as input", - ErrorCode.EVALUATOR_LIKE_INVALID_INPUTS, - errorContextFrom(metas).also { - it[Property.LIKE_PATTERN] = pattern.toString() - if (escape != null) it[Property.LIKE_ESCAPE] = escape.toString() - }, - internal = false - ) - } - else -> { - val (patternString: String, escapeChar: Int?) = - checkPattern( - pattern.stringValue(), - patternLocationMeta, - escape?.stringValue(), - escapeLocationMeta - ) - val likeRegexPattern = when { - patternString.isEmpty() -> Pattern.compile("") - else -> parsePattern(patternString, escapeChar) - } - return { likeRegexPattern } - } - } - } - - fun matchRegexPattern(value: ExprValue, likePattern: (() -> Pattern)?): ExprValue { - return when { - likePattern == null || value.type.isUnknown -> ExprValue.nullValue - !value.type.isText -> err( - "LIKE expression must be given non-null strings as input", - ErrorCode.EVALUATOR_LIKE_INVALID_INPUTS, - errorContextFrom(metas).also { - it[Property.LIKE_VALUE] = value.toString() - }, - internal = false - ) - else -> ExprValue.newBoolean(likePattern().matcher(value.stringValue()).matches()) - } - } - - val valueThunk = compileAstExpr(valueExpr) - - // If the pattern and escape expressions are literals then we can compile the pattern now and - // re-use it with every execution. Otherwise, we must re-compile the pattern every time. - return when { - patternExpr is PartiqlPhysical.Expr.Lit && (escapeExpr == null || escapeExpr is PartiqlPhysical.Expr.Lit) -> { - val patternParts = getRegexPattern( - ExprValue.of(patternExpr.value.toIonValue(ion)), - (escapeExpr as? PartiqlPhysical.Expr.Lit)?.value?.toIonValue(ion) - ?.let { ExprValue.of(it) } - ) - - // If valueExpr is also a literal then we can evaluate this at compile time and return a constant. - if (valueExpr is PartiqlPhysical.Expr.Lit) { - val resultValue = matchRegexPattern( - ExprValue.of(valueExpr.value.toIonValue(ion)), - patternParts - ) - return thunkFactory.thunkEnv(metas) { resultValue } - } else { - thunkFactory.thunkEnvOperands(metas, valueThunk) { _, value -> - matchRegexPattern(value, patternParts) - } - } - } - else -> { - val patternThunk = compileAstExpr(patternExpr) - when (escapeExpr) { - null -> { - // thunk that re-compiles the DFA every evaluation without a custom escape sequence - thunkFactory.thunkEnvOperands(metas, valueThunk, patternThunk) { _, value, pattern -> - val pps = getRegexPattern(pattern, null) - matchRegexPattern(value, pps) - } - } - else -> { - // thunk that re-compiles the pattern every evaluation but *with* a custom escape sequence - val escapeThunk = compileAstExpr(escapeExpr) - thunkFactory.thunkEnvOperands( - metas, - valueThunk, - patternThunk, - escapeThunk - ) { _, value, pattern, escape -> - val pps = getRegexPattern(pattern, escape) - matchRegexPattern(value, pps) - } - } - } - } - } - } - - /** - * Given the pattern and optional escape character in a `LIKE` predicate as [IonValue]s - * check their validity based on the SQL92 spec and return a triple that contains in order - * - * - the search pattern as a string - * - the escape character, possibly `null` - * - the length of the search pattern. The length of the search pattern is either - * - the length of the string representing the search pattern when no escape character is used - * - the length of the string representing the search pattern without counting uses of the escape character - * when an escape character is used - * - * A search pattern is valid when - * 1. pattern is not null - * 1. pattern contains characters where `_` means any 1 character and `%` means any string of length 0 or more - * 1. if the escape character is specified then pattern can be deterministically partitioned into character groups where - * 1. A length 1 character group consists of any character other than the ESCAPE character - * 1. A length 2 character group consists of the ESCAPE character followed by either `_` or `%` or the ESCAPE character itself - * - * @param pattern search pattern - * @param escape optional escape character provided in the `LIKE` predicate - * - * @return a triple that contains in order the search pattern as a [String], optionally the code point for the escape character if one was provided - * and the size of the search pattern excluding uses of the escape character - */ - private fun checkPattern( - pattern: String, - patternLocationMeta: SourceLocationMeta?, - escape: String?, - escapeLocationMeta: SourceLocationMeta?, - ): Pair { - - escape?.let { - val escapeCharString = checkEscapeChar(escape, escapeLocationMeta) - val escapeCharCodePoint = escapeCharString.codePointAt(0) // escape is a string of length 1 - val validEscapedChars = setOf('_'.toInt(), '%'.toInt(), escapeCharCodePoint) - val iter = pattern.codePointSequence().iterator() - - while (iter.hasNext()) { - val current = iter.next() - if (current == escapeCharCodePoint && (!iter.hasNext() || !validEscapedChars.contains(iter.next()))) { - err( - "Invalid escape sequence : $pattern", - ErrorCode.EVALUATOR_LIKE_PATTERN_INVALID_ESCAPE_SEQUENCE, - errorContextFrom(patternLocationMeta).apply { - set(Property.LIKE_PATTERN, pattern) - set(Property.LIKE_ESCAPE, escapeCharString) - }, - internal = false - ) - } - } - return Pair(pattern, escapeCharCodePoint) - } - return Pair(pattern, null) - } - - /** - * Given an [IonValue] to be used as the escape character in a `LIKE` predicate check that it is - * a valid character based on the SQL Spec. - * - * - * A value is a valid escape when - * 1. it is 1 character long, and, - * 1. Cannot be null (SQL92 spec marks this cases as *unknown*) - * - * @param escape value provided as an escape character for a `LIKE` predicate - * - * @return the escape character as a [String] or throws an exception when the input is invalid - */ - private fun checkEscapeChar(escape: String, locationMeta: SourceLocationMeta?): String { - when (escape) { - "" -> { - err( - "Cannot use empty character as ESCAPE character in a LIKE predicate: $escape", - ErrorCode.EVALUATOR_LIKE_PATTERN_INVALID_ESCAPE_SEQUENCE, - errorContextFrom(locationMeta), - internal = false - ) - } - else -> { - if (escape.trim().length != 1) { - err( - "Escape character must have size 1 : $escape", - ErrorCode.EVALUATOR_LIKE_PATTERN_INVALID_ESCAPE_SEQUENCE, - errorContextFrom(locationMeta), - internal = false - ) - } - } - } - return escape - } - - private fun compileExec(node: PartiqlPhysical.Statement.Exec): PhysicalPlanThunk { - val metas = node.metas - val procedureName = node.procedureName.text - val procedure = procedures[procedureName] ?: err( - "No such stored procedure: $procedureName", - ErrorCode.EVALUATOR_NO_SUCH_PROCEDURE, - errorContextFrom(metas).also { - it[Property.PROCEDURE_NAME] = procedureName - }, - internal = false - ) - - val args = node.args - // Check arity - if (args.size !in procedure.signature.arity) { - val errorContext = errorContextFrom(metas).also { - it[Property.EXPECTED_ARITY_MIN] = procedure.signature.arity.first - it[Property.EXPECTED_ARITY_MAX] = procedure.signature.arity.last - } - - val message = when { - procedure.signature.arity.first == 1 && procedure.signature.arity.last == 1 -> - "${procedure.signature.name} takes a single argument, received: ${args.size}" - procedure.signature.arity.first == procedure.signature.arity.last -> - "${procedure.signature.name} takes exactly ${procedure.signature.arity.first} arguments, received: ${args.size}" - else -> - "${procedure.signature.name} takes between ${procedure.signature.arity.first} and " + - "${procedure.signature.arity.last} arguments, received: ${args.size}" - } - - throw EvaluationException( - message, - ErrorCode.EVALUATOR_INCORRECT_NUMBER_OF_ARGUMENTS_TO_PROCEDURE_CALL, - errorContext, - internal = false - ) - } - - // Compile the procedure's arguments - val argThunks = compileAstExprs(args) - - return thunkFactory.thunkEnv(metas) { env -> - val procedureArgValues = argThunks.map { it(env) } - procedure.call(env.session, procedureArgValues) - } - } - - private fun compileDate(expr: PartiqlPhysical.Expr.Date, metas: MetaContainer): PhysicalPlanThunk = - thunkFactory.thunkEnv(metas) { - ExprValue.newDate( - expr.year.value.toInt(), - expr.month.value.toInt(), - expr.day.value.toInt() - ) - } - - private fun compileLitTime(expr: PartiqlPhysical.Expr.LitTime, metas: MetaContainer): PhysicalPlanThunk = - thunkFactory.thunkEnv(metas) { - // Add the default time zone if the type "TIME WITH TIME ZONE" does not have an explicitly specified time zone. - ExprValue.newTime( - Time.of( - expr.value.hour.value.toInt(), - expr.value.minute.value.toInt(), - expr.value.second.value.toInt(), - expr.value.nano.value.toInt(), - expr.value.precision.value.toInt(), - if (expr.value.withTimeZone.value && expr.value.tzMinutes == null) evaluatorOptions.defaultTimezoneOffset.totalMinutes else expr.value.tzMinutes?.value?.toInt() - ) - ) - } - - private fun compileBagOp(node: PartiqlPhysical.Expr.BagOp, metas: MetaContainer): PhysicalPlanThunk { - val lhs = compileAstExpr(node.operands[0]) - val rhs = compileAstExpr(node.operands[1]) - val op = ExprValueBagOp.create(node.op, metas) - return thunkFactory.thunkEnv(metas) { env -> - val l = lhs(env) - val r = rhs(env) - val result = when (node.quantifier) { - is PartiqlPhysical.SetQuantifier.All -> op.eval(l, r) - is PartiqlPhysical.SetQuantifier.Distinct -> op.eval(l, r).distinct() - } - ExprValue.newBag(result) - } - } - - private fun compilePivot(expr: PartiqlPhysical.Expr.Pivot, metas: MetaContainer): PhysicalPlanThunk { - val inputBExpr: RelationThunkEnv = bexperConverter.convert(expr.input) - // The names are intentionally flipped for clarity; consider fixing this in the AST - val valueExpr = compileAstExpr(expr.key) - val keyExpr = compileAstExpr(expr.value) - return thunkFactory.thunkEnv(metas) { env -> - val attributes: Sequence = sequence { - val relation = inputBExpr(env) - while (relation.nextRow()) { - val key = keyExpr.invoke(env) - if (key.type.isText) { - val value = valueExpr.invoke(env) - yield(value.namedValue(key)) - } - } - } - ExprValue.newStruct(attributes, StructOrdering.UNORDERED) - } - } - - /** A special wrapper for `UNPIVOT` values as a BAG. */ - private class UnpivotedExprValue(private val values: Iterable) : BaseExprValue() { - override val type = ExprValueType.BAG - override fun iterator() = values.iterator() - } - - /** Unpivots a `struct`, and synthesizes a synthetic singleton `struct` for other [ExprValue]. */ - internal fun ExprValue.unpivot(): ExprValue = when { - // special case for our special UNPIVOT value to avoid double wrapping - this is UnpivotedExprValue -> this - // Wrap into a pseudo-BAG - type == ExprValueType.STRUCT || type == ExprValueType.MISSING -> UnpivotedExprValue(this) - // for non-struct, this wraps any value into a BAG with a synthetic name - else -> UnpivotedExprValue( - listOf( - this.namedValue(ExprValue.newString(syntheticColumnName(0))) - ) - ) - } - - private fun createStructExprValue(seq: Sequence, ordering: StructOrdering) = - ExprValue.newStruct( - when (evaluatorOptions.projectionIteration) { - ProjectionIterationBehavior.FILTER_MISSING -> seq.filter { it.type != ExprValueType.MISSING } - ProjectionIterationBehavior.UNFILTERED -> seq - }, - ordering - ) -} - -internal val MetaContainer.sourceLocationMeta get() = this[SourceLocationMeta.TAG] as? SourceLocationMeta -internal val MetaContainer.sourceLocationMetaOrUnknown get() = this.sourceLocationMeta ?: UNKNOWN_SOURCE_LOCATION - -internal fun StaticType.getTypes() = when (val flattened = this.flatten()) { - is AnyOfType -> flattened.types - else -> listOf(this) -} - -/** - * Represents an element in a select list that is to be projected into the final result. - * i.e. an expression, or a (project_all) node. - */ -private sealed class CompiledStructPart { - - /** - * Represents a single compiled expression to be projected into the final result. - * Given `SELECT a + b as value FROM foo`: - * - `name` is "value" - * - `thunk` is compiled expression, i.e. `a + b` - */ - class Field(val nameThunk: PhysicalPlanThunk, val valueThunk: PhysicalPlanThunk) : CompiledStructPart() - - /** - * Represents a wildcard ((path_project_all) node) expression to be projected into the final result. - * This covers two cases. For `SELECT foo.* FROM foo`, `exprThunks` contains a single compiled expression - * `foo`. - * - * For `SELECT * FROM foo, bar, bat`, `exprThunks` would contain a compiled expression for each of `foo`, `bar` and - * `bat`. - */ - class StructMerge(val thunks: List) : CompiledStructPart() -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/RelationThunk.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/RelationThunk.kt deleted file mode 100644 index 362292363a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/RelationThunk.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.partiql.lang.eval.physical - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.fillErrorContext -import org.partiql.lang.eval.relation.RelationIterator - -/** A thunk that returns a [RelationIterator], which is the result of evaluating a relational operator. */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("RelationThunkEnvAsync")) -internal typealias RelationThunkEnv = (EvaluatorState) -> RelationIterator - -/** - * Invokes [t] with error handling like is supplied by [ThunkFactory]. - * - * This function is not currently in [ThunkFactory] to avoid complicating it further. If a need arises, it could be - * moved. - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("relationThunkAsync")) -internal inline fun relationThunk(metas: MetaContainer, crossinline t: RelationThunkEnv): RelationThunkEnv { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - return { env: EvaluatorState -> - try { - t(env) - } catch (e: EvaluationException) { - // Only add source location data to the error context if it doesn't already exist - // in [errorContext]. - if (!e.errorContext.hasProperty(Property.LINE_NUMBER)) { - sourceLocationMeta?.let { fillErrorContext(e.errorContext, sourceLocationMeta) } - } - throw e - } catch (e: Exception) { - val message = e.message ?: "" - throw EvaluationException( - "Generic exception, $message", - errorCode = ErrorCode.EVALUATOR_GENERIC_EXCEPTION, - errorContext = errorContextFrom(sourceLocationMeta), - cause = e, - internal = true - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/RelationThunkAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/RelationThunkAsync.kt deleted file mode 100644 index 884e136bff..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/RelationThunkAsync.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.partiql.lang.eval.physical - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.ThunkFactory -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.fillErrorContext -import org.partiql.lang.eval.relation.RelationIterator - -/** A thunk that returns a [RelationIterator], which is the result of evaluating a relational operator. */ -internal typealias RelationThunkEnvAsync = suspend (EvaluatorState) -> RelationIterator - -/** - * Invokes [t] with error handling like is supplied by [ThunkFactory]. - * - * This function is not currently in [ThunkFactory] to avoid complicating it further. If a need arises, it could be - * moved. - */ -internal suspend inline fun relationThunkAsync(metas: MetaContainer, crossinline t: RelationThunkEnvAsync): RelationThunkEnvAsync { - val sourceLocationMeta = metas[SourceLocationMeta.TAG] as? SourceLocationMeta - return { env: EvaluatorState -> - try { - t(env) - } catch (e: EvaluationException) { - // Only add source location data to the error context if it doesn't already exist - // in [errorContext]. - if (!e.errorContext.hasProperty(Property.LINE_NUMBER)) { - sourceLocationMeta?.let { fillErrorContext(e.errorContext, sourceLocationMeta) } - } - throw e - } catch (e: Exception) { - val message = e.message ?: "" - throw EvaluationException( - "Generic exception, $message", - errorCode = ErrorCode.EVALUATOR_GENERIC_EXCEPTION, - errorContext = errorContextFrom(sourceLocationMeta), - cause = e, - internal = true - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/SetVariableFunc.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/SetVariableFunc.kt deleted file mode 100644 index b3fefd34ce..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/SetVariableFunc.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.partiql.lang.eval.physical - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.util.toIntExact - -/** - * Sets the value of a variable which is stored in an [EvaluatorState]. - * - * To obtain an instance of [SetVariableFunc] that sets a specific variable, see [toSetVariableFunc]. This is because - * [EvaluatorState.registers] contains the current value of all variables in an SFW query. As such, allowing direct - * access to the registers is very risky--for example, if the wrong register is set the query will have the incorrect - * result or trigger an evaluation-time exception. - * - * To mitigate this risk, [EvaluatorState.registers] is marked to `internal` to avoid exposing it publicly and - * [SetVariableFunc] is provided to allow custom operator implementation to assign values to only specific variables - * when needed. - * - * Parameters: - * - * - [EvaluatorState] - The object containing the current state of the evaluator and current values of the variables. - * - [ExprValue] - The value to set. - */ -typealias SetVariableFunc = (EvaluatorState, ExprValue) -> Unit - -/** - * Creates a [SetVariableFunc] tied to the variable identified in the [PartiqlPhysical.VarDecl] receiver object that - * can be used to set the value of the variable at evaluation time. This method is considerably safer than direct - * access to the [EvaluatorState.registers] array, which is marked as `internal` to reduce the likelihood of being - * clobbered by a physical operator implementation which was not supplied by this library. - */ -internal fun PartiqlPhysical.VarDecl.toSetVariableFunc(): SetVariableFunc { - val index = this.index.value.toIntExact() - return { state, value -> state.registers[index] = value } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/UndefinedVariableUtil.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/UndefinedVariableUtil.kt deleted file mode 100644 index a7c127f187..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/UndefinedVariableUtil.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.partiql.lang.eval.physical - -import com.amazon.ionelement.api.MetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.util.propertyValueMapOf - -private const val UNBOUND_QUOTED_IDENTIFIER_HINT: String = - "Hint: did you intend to use single quotes (') here instead of double quotes (\")? " + - "Use single quotes (') for string literals and double quotes (\") for quoted identifiers." - -internal fun throwUndefinedVariableException( - bindingName: BindingName, - metas: MetaContainer? -): Nothing { - val (errorCode, hint) = when (bindingName.bindingCase) { - BindingCase.SENSITIVE -> - ErrorCode.EVALUATOR_QUOTED_BINDING_DOES_NOT_EXIST to " $UNBOUND_QUOTED_IDENTIFIER_HINT" - BindingCase.INSENSITIVE -> - ErrorCode.EVALUATOR_BINDING_DOES_NOT_EXIST to "" - } - throw EvaluationException( - message = "No such binding: ${bindingName.name}.$hint", - errorCode = errorCode, - errorContext = (metas?.let { errorContextFrom(metas) } ?: propertyValueMapOf()).also { - it[Property.BINDING_NAME] = bindingName.name - }, - internal = false - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/VariableBinding.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/VariableBinding.kt deleted file mode 100644 index bea2d1d266..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/VariableBinding.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.partiql.lang.eval.physical - -import org.partiql.lang.eval.physical.operators.ValueExpression - -/** - * A compiled variable binding. - * - * @property setFunc The function to be invoked at evaluation-time to set the value of the variable. - * @property expr The function to be invoked at evaluation-time to compute the value of the variable. - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("VariableBindingAsync")) -class VariableBinding( - val setFunc: SetVariableFunc, - val expr: ValueExpression -) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/VariableBindingAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/VariableBindingAsync.kt deleted file mode 100644 index 272629f2a3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/VariableBindingAsync.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.partiql.lang.eval.physical - -import org.partiql.lang.eval.physical.operators.ValueExpressionAsync - -/** - * A compiled variable binding. - * - * @property setFunc The function to be invoked at evaluation-time to set the value of the variable. - * @property expr The function to be invoked at evaluation-time to compute the value of the variable. - */ -class VariableBindingAsync( - val setFunc: SetVariableFunc, - val expr: ValueExpressionAsync -) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/Accumulator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/Accumulator.kt deleted file mode 100644 index de90ac6c96..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/Accumulator.kt +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical.operators - -import org.partiql.errors.ErrorCode -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.NaturalExprValueComparators -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.internal.ExprAggregator -import org.partiql.lang.eval.internal.errNoContext -import org.partiql.lang.eval.internal.ext.bigDecimalOf -import org.partiql.lang.eval.internal.ext.createUniqueExprValueFilter -import org.partiql.lang.eval.internal.ext.exprValue -import org.partiql.lang.eval.internal.ext.isUnknown -import org.partiql.lang.eval.numberValue -import org.partiql.lang.util.div -import org.partiql.lang.util.plus - -internal sealed class Accumulator( - internal open val filter: (ExprValue) -> Boolean -) : ExprAggregator { - companion object { - internal fun create(funcName: String, quantifier: PartiqlPhysical.SetQuantifier): Accumulator { - val filter = when (quantifier) { - is PartiqlPhysical.SetQuantifier.Distinct -> createUniqueExprValueFilter() - is PartiqlPhysical.SetQuantifier.All -> { _: ExprValue -> true } - } - return when (funcName.trim().lowercase()) { - "min" -> AccumulatorMin(filter) - "max" -> AccumulatorMax(filter) - "avg" -> AccumulatorAvg(filter) - "count" -> AccumulatorCount(filter) - "sum" -> AccumulatorSum(filter) - "group_as" -> AccumulatorGroupAs(filter) - "every" -> AccumulatorEvery(filter) - "any" -> AccumulatorAnySome(filter) - "some" -> AccumulatorAnySome(filter) - else -> throw IllegalArgumentException("Unsupported aggregation function: $funcName") - } - } - } - - override fun next(value: ExprValue) { - if (value.isUnknown() || filter.invoke(value).not()) return - nextValue(value) - } - - abstract fun nextValue(value: ExprValue) -} - -internal class AccumulatorSum( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var sum: Number? = null - - override fun nextValue(value: ExprValue) { - checkIsNumberType(funcName = "SUM", value = value) - if (sum == null) sum = 0L - this.sum = value.numberValue() + this.sum!! - } - - override fun compute(): ExprValue { - return sum?.exprValue() ?: ExprValue.nullValue - } -} - -internal class AccumulatorAvg( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var sum: Number = 0.0 - var count: Long = 0L - - override fun nextValue(value: ExprValue) { - checkIsNumberType(funcName = "AVG", value = value) - this.sum += value.numberValue() - this.count += 1L - } - - override fun compute(): ExprValue = when (count) { - 0L -> ExprValue.nullValue - else -> (sum / bigDecimalOf(count)).exprValue() - } -} - -internal class AccumulatorMax( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var max: ExprValue = ExprValue.nullValue - - override fun nextValue(value: ExprValue) { - max = comparisonAccumulator(NaturalExprValueComparators.NULLS_LAST_DESC)(max, value) - } - - override fun compute(): ExprValue = max -} - -internal class AccumulatorMin( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var min: ExprValue = ExprValue.nullValue - - override fun nextValue(value: ExprValue) { - min = comparisonAccumulator(NaturalExprValueComparators.NULLS_LAST_ASC)(min, value) - } - - override fun compute(): ExprValue = min -} - -internal class AccumulatorCount( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - var count: Long = 0L - - override fun nextValue(value: ExprValue) { - this.count += 1L - } - - override fun compute(): ExprValue = count.exprValue() -} - -internal class AccumulatorEvery( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - private var res: ExprValue? = null - override fun nextValue(value: ExprValue) { - checkIsBooleanType("EVERY", value) - res = res?.let { ExprValue.newBoolean(it.booleanValue() && value.booleanValue()) } ?: value - } - - override fun compute(): ExprValue = res ?: ExprValue.nullValue -} - -internal class AccumulatorAnySome( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - private var res: ExprValue? = null - override fun nextValue(value: ExprValue) { - checkIsBooleanType("ANY/SOME", value) - res = res?.let { ExprValue.newBoolean(it.booleanValue() || value.booleanValue()) } ?: value - } - - override fun compute(): ExprValue = res ?: ExprValue.nullValue -} - -internal class AccumulatorGroupAs( - internal override val filter: (ExprValue) -> Boolean -) : Accumulator(filter = filter) { - - val exprValues = mutableListOf() - - override fun nextValue(value: ExprValue) { - exprValues.add(value) - } - - override fun compute(): ExprValue = ExprValue.newBag(exprValues) -} - -private fun comparisonAccumulator(comparator: NaturalExprValueComparators): (ExprValue?, ExprValue) -> ExprValue = - { left, right -> - when { - left == null || comparator.compare(left, right) > 0 -> right - else -> left - } - } - -internal fun checkIsNumberType(funcName: String, value: ExprValue) { - if (!value.type.isNumber) { - errNoContext( - message = "Aggregate function $funcName expects arguments of NUMBER type but the following value was provided: $value, with type of ${value.type}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_AGG_FUNCTION, - internal = false - ) - } -} - -internal fun checkIsBooleanType(funcName: String, value: ExprValue) { - if (value.type != ExprValueType.BOOL) { - errNoContext( - message = "Aggregate function $funcName expects arguments of BOOL type but the following value was provided: $value, with type of ${value.type}", - errorCode = ErrorCode.EVALUATOR_INVALID_ARGUMENTS_FOR_AGG_FUNCTION, - internal = false - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/AggregateOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/AggregateOperatorFactory.kt deleted file mode 100644 index 320aac61c9..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/AggregateOperatorFactory.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.DEFAULT_COMPARATOR -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.SetVariableFunc -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME -import java.util.TreeMap - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Aggregate] operator. - * - * @constructor - * - * @param name - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("AggregateOperatorFactoryAsync")) -public abstract class AggregateOperatorFactory(name: String) : RelationalOperatorFactory { - - public override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.AGGREGATE, name) - - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("AggregateOperatorFactoryAsync.create")) - public abstract fun create( - source: RelationExpression, - strategy: PartiqlPhysical.GroupingStrategy, - keys: List, - functions: List - ): RelationExpression -} - -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("CompiledGroupKeyAsync")) -public class CompiledGroupKey( - val setGroupKeyVal: SetVariableFunc, - val value: ValueExpression, - val variable: PartiqlPhysical.VarDecl -) - -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("CompiledAggregateFunctionAsync")) -public class CompiledAggregateFunction( - val name: String, - val setAggregateVal: SetVariableFunc, - val value: ValueExpression, - val quantifier: PartiqlPhysical.SetQuantifier, -) - -internal object AggregateOperatorFactoryDefault : AggregateOperatorFactory(DEFAULT_IMPL_NAME) { - override fun create( - source: RelationExpression, - strategy: PartiqlPhysical.GroupingStrategy, - keys: List, - functions: List - ): RelationExpression = AggregateOperatorDefault(source, keys, functions) -} - -internal class AggregateOperatorDefault( - val source: RelationExpression, - val keys: List, - val functions: List -) : RelationExpression { - override fun evaluate(state: EvaluatorState): RelationIterator = relation(RelationType.BAG) { - val aggregationMap = TreeMap>(DEFAULT_COMPARATOR) - - val sourceIter = source.evaluate(state) - while (sourceIter.nextRow()) { - - // Initialize the AggregationMap - val evaluatedGroupByKeys = - keys.map { it.value.invoke(state) }.let { ExprValue.newList(it) } - val accumulators = aggregationMap.getOrPut(evaluatedGroupByKeys) { - functions.map { function -> - Accumulator.create(function.name, function.quantifier) - } - } - - // Aggregate Values in Aggregation State - functions.forEachIndexed { index, function -> - val valueToAggregate = function.value(state) - accumulators[index].next(valueToAggregate) - } - } - - // No Aggregations Created - if (keys.isEmpty() && aggregationMap.isEmpty()) { - functions.forEach { function -> - val accumulator = Accumulator.create(function.name, function.quantifier) - function.setAggregateVal(state, accumulator.compute()) - } - yield() - return@relation - } - - // Place Aggregated Values into Result State - aggregationMap.forEach { (exprList, accumulators) -> - exprList.forEachIndexed { index, exprValue -> keys[index].setGroupKeyVal(state, exprValue) } - accumulators.forEachIndexed { index, acc -> functions[index].setAggregateVal(state, acc.compute()) } - yield() - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/AggregateOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/AggregateOperatorFactoryAsync.kt deleted file mode 100644 index 8f4cb2948a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/AggregateOperatorFactoryAsync.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.DEFAULT_COMPARATOR -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.SetVariableFunc -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME -import java.util.TreeMap - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Aggregate] operator. - * - * @constructor - * - * @param name - */ -public abstract class AggregateOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - - public override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.AGGREGATE, name) - - public abstract fun create( - source: RelationExpressionAsync, - strategy: PartiqlPhysical.GroupingStrategy, - keys: List, - functions: List - ): RelationExpressionAsync -} - -public class CompiledGroupKeyAsync( - val setGroupKeyVal: SetVariableFunc, - val value: ValueExpressionAsync, - val variable: PartiqlPhysical.VarDecl -) - -public class CompiledAggregateFunctionAsync( - val name: String, - val setAggregateVal: SetVariableFunc, - val value: ValueExpressionAsync, - val quantifier: PartiqlPhysical.SetQuantifier, -) - -internal object AggregateOperatorFactoryDefaultAsync : AggregateOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - override fun create( - source: RelationExpressionAsync, - strategy: PartiqlPhysical.GroupingStrategy, - keys: List, - functions: List - ): RelationExpressionAsync = AggregateOperatorDefaultAsync(source, keys, functions) -} - -internal class AggregateOperatorDefaultAsync( - val source: RelationExpressionAsync, - val keys: List, - val functions: List -) : RelationExpressionAsync { - override suspend fun evaluate(state: EvaluatorState): RelationIterator = relation(RelationType.BAG) { - val aggregationMap = TreeMap>(DEFAULT_COMPARATOR) - - val sourceIter = source.evaluate(state) - while (sourceIter.nextRow()) { - - // Initialize the AggregationMap - val evaluatedGroupByKeys = - keys.map { it.value.invoke(state) }.let { ExprValue.newList(it) } - val accumulators = aggregationMap.getOrPut(evaluatedGroupByKeys) { - functions.map { function -> - Accumulator.create(function.name, function.quantifier) - } - } - - // Aggregate Values in Aggregation State - functions.forEachIndexed { index, function -> - val valueToAggregate = function.value(state) - accumulators[index].next(valueToAggregate) - } - } - - // No Aggregations Created - if (keys.isEmpty() && aggregationMap.isEmpty()) { - functions.forEach { function -> - val accumulator = Accumulator.create(function.name, function.quantifier) - function.setAggregateVal(state, accumulator.compute()) - } - yield() - return@relation - } - - // Place Aggregated Values into Result State - aggregationMap.forEach { (exprList, accumulators) -> - exprList.forEachIndexed { index, exprValue -> keys[index].setGroupKeyVal(state, exprValue) } - accumulators.forEachIndexed { index, acc -> functions[index].setAggregateVal(state, acc.compute()) } - yield() - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/FilterRelationalOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/FilterRelationalOperatorFactory.kt deleted file mode 100644 index 4a8e6622bc..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/FilterRelationalOperatorFactory.kt +++ /dev/null @@ -1,71 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.internal.ext.isNotUnknown -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Filter] operator. - * - * @constructor - * - * @param name - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("FilterRelationalOperatorFactoryAsync")) -abstract class FilterRelationalOperatorFactory(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.FILTER, name) - - /** - * Creates a [RelationExpression] instance for [PartiqlPhysical.Bexpr.Filter]. - * - * @param impl - * @param predicate - * @param sourceBexpr - * @return - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("FilterRelationalOperatorFactoryAsync.create")) - abstract fun create( - impl: PartiqlPhysical.Impl, - predicate: ValueExpression, - sourceBexpr: RelationExpression - ): RelationExpression -} - -internal object FilterRelationalOperatorFactoryDefault : FilterRelationalOperatorFactory(DEFAULT_IMPL_NAME) { - override fun create( - impl: PartiqlPhysical.Impl, - predicate: ValueExpression, - sourceBexpr: RelationExpression - ) = SelectOperatorDefault( - input = sourceBexpr, - predicate = predicate - ) -} - -internal class SelectOperatorDefault( - val input: RelationExpression, - val predicate: ValueExpression, -) : RelationExpression { - - override fun evaluate(state: EvaluatorState): RelationIterator { - val input = input.evaluate(state) - return relation(RelationType.BAG) { - while (true) { - if (!input.nextRow()) { - break - } else { - val matches = predicate.invoke(state) - if (matches.isNotUnknown() && matches.booleanValue()) { - yield() - } - } - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/FilterRelationalOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/FilterRelationalOperatorFactoryAsync.kt deleted file mode 100644 index 4225db6f76..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/FilterRelationalOperatorFactoryAsync.kt +++ /dev/null @@ -1,69 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.isNotUnknown -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Filter] operator. - * - * @constructor - * - * @param name - */ -abstract class FilterRelationalOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.FILTER, name) - - /** - * Creates a [RelationExpressionAsync] instance for [PartiqlPhysical.Bexpr.Filter]. - * - * @param impl - * @param predicate - * @param sourceBexpr - * @return - */ - abstract fun create( - impl: PartiqlPhysical.Impl, - predicate: ValueExpressionAsync, - sourceBexpr: RelationExpressionAsync - ): RelationExpressionAsync -} - -internal object FilterRelationalOperatorFactoryDefaultAsync : FilterRelationalOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - override fun create( - impl: PartiqlPhysical.Impl, - predicate: ValueExpressionAsync, - sourceBexpr: RelationExpressionAsync - ) = SelectOperatorDefaultAsync( - input = sourceBexpr, - predicate = predicate - ) -} - -internal class SelectOperatorDefaultAsync( - val input: RelationExpressionAsync, - val predicate: ValueExpressionAsync, -) : RelationExpressionAsync { - - override suspend fun evaluate(state: EvaluatorState): RelationIterator { - val input = input.evaluate(state) - return relation(RelationType.BAG) { - while (true) { - if (!input.nextRow()) { - break - } else { - val matches = predicate.invoke(state) - if (matches.isNotUnknown() && matches.booleanValue()) { - yield() - } - } - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/JoinRelationalOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/JoinRelationalOperatorFactory.kt deleted file mode 100644 index 1590e0c89d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/JoinRelationalOperatorFactory.kt +++ /dev/null @@ -1,167 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.internal.ext.isNotUnknown -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Join] operator. - * - * @constructor - * - * @param name - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("JoinRelationalOperatorFactoryAsync")) -abstract class JoinRelationalOperatorFactory(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.JOIN, name) - - /** - * Creates a [RelationExpression] instance for [PartiqlPhysical.Bexpr.Join]. - * - * @param impl static arguments - * @param joinType inner, left, right, outer - * @param leftBexpr left-hand-side of the join - * @param rightBexpr right-hand-side of the join - * @param predicateExpr condition for a theta join - * @param setLeftSideVariablesToNull - * @param setRightSideVariablesToNull - * @return - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("JoinRelationalOperatorFactoryAsync.create")) - abstract fun create( - impl: PartiqlPhysical.Impl, - joinType: PartiqlPhysical.JoinType, - leftBexpr: RelationExpression, - rightBexpr: RelationExpression, - predicateExpr: ValueExpression?, - setLeftSideVariablesToNull: (EvaluatorState) -> Unit, - setRightSideVariablesToNull: (EvaluatorState) -> Unit - ): RelationExpression -} - -internal object JoinRelationalOperatorFactoryDefault : JoinRelationalOperatorFactory(DEFAULT_IMPL_NAME) { - override fun create( - impl: PartiqlPhysical.Impl, - joinType: PartiqlPhysical.JoinType, - leftBexpr: RelationExpression, - rightBexpr: RelationExpression, - predicateExpr: ValueExpression?, - setLeftSideVariablesToNull: (EvaluatorState) -> Unit, - setRightSideVariablesToNull: (EvaluatorState) -> Unit - ): RelationExpression = when (joinType) { - is PartiqlPhysical.JoinType.Inner -> { - InnerJoinOperator( - lhs = leftBexpr, - rhs = rightBexpr, - condition = predicateExpr?.closure() ?: { true } - ) - } - is PartiqlPhysical.JoinType.Left -> { - LeftJoinOperator( - lhs = leftBexpr, - rhs = rightBexpr, - condition = predicateExpr?.closure() ?: { true }, - setRightSideVariablesToNull = setRightSideVariablesToNull - ) - } - is PartiqlPhysical.JoinType.Right -> { - RightJoinOperator( - lhs = leftBexpr, - rhs = rightBexpr, - condition = predicateExpr?.closure() ?: { true }, - setLeftSideVariablesToNull = setLeftSideVariablesToNull - ) - } - is PartiqlPhysical.JoinType.Full -> TODO("Full join") - } - - private fun ValueExpression.closure() = { state: EvaluatorState -> - val v = invoke(state) - v.isNotUnknown() && v.booleanValue() - } -} - -/** - * See specification 5.6 - */ -private class InnerJoinOperator( - private val lhs: RelationExpression, - private val rhs: RelationExpression, - private val condition: (EvaluatorState) -> Boolean -) : RelationExpression { - - override fun evaluate(state: EvaluatorState) = relation(RelationType.BAG) { - val leftItr = lhs.evaluate(state) - while (leftItr.nextRow()) { - val rightItr = rhs.evaluate(state) - while (rightItr.nextRow()) { - if (condition(state)) { - yield() - } - } - } - } -} - -/** - * See specification 5.6 - */ -private class LeftJoinOperator( - private val lhs: RelationExpression, - private val rhs: RelationExpression, - private val condition: (EvaluatorState) -> Boolean, - private val setRightSideVariablesToNull: (EvaluatorState) -> Unit -) : RelationExpression { - - override fun evaluate(state: EvaluatorState) = relation(RelationType.BAG) { - val leftItr = lhs.evaluate(state) - while (leftItr.nextRow()) { - val rightItr = rhs.evaluate(state) - var yieldedSomething = false - while (rightItr.nextRow()) { - if (condition(state)) { - yield() - yieldedSomething = true - } - } - if (!yieldedSomething) { - setRightSideVariablesToNull(state) - yield() - } - } - } -} - -/** - * See specification 5.6 - */ -private class RightJoinOperator( - private val lhs: RelationExpression, - private val rhs: RelationExpression, - private val condition: (EvaluatorState) -> Boolean, - private val setLeftSideVariablesToNull: (EvaluatorState) -> Unit -) : RelationExpression { - - override fun evaluate(state: EvaluatorState) = relation(RelationType.BAG) { - val rightItr = rhs.evaluate(state) - while (rightItr.nextRow()) { - val leftItr = lhs.evaluate(state) - var yieldedSomething = false - while (leftItr.nextRow()) { - if (condition(state)) { - yield() - yieldedSomething = true - } - } - if (!yieldedSomething) { - setLeftSideVariablesToNull(state) - yield() - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/JoinRelationalOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/JoinRelationalOperatorFactoryAsync.kt deleted file mode 100644 index a476e9b91d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/JoinRelationalOperatorFactoryAsync.kt +++ /dev/null @@ -1,165 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.booleanValue -import org.partiql.lang.eval.isNotUnknown -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Join] operator. - * - * @constructor - * - * @param name - */ -abstract class JoinRelationalOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.JOIN, name) - - /** - * Creates a [RelationExpressionAsync] instance for [PartiqlPhysical.Bexpr.Join]. - * - * @param impl static arguments - * @param joinType inner, left, right, outer - * @param leftBexpr left-hand-side of the join - * @param rightBexpr right-hand-side of the join - * @param predicateExpr condition for a theta join - * @param setLeftSideVariablesToNull - * @param setRightSideVariablesToNull - * @return - */ - abstract fun create( - impl: PartiqlPhysical.Impl, - joinType: PartiqlPhysical.JoinType, - leftBexpr: RelationExpressionAsync, - rightBexpr: RelationExpressionAsync, - predicateExpr: ValueExpressionAsync?, - setLeftSideVariablesToNull: (EvaluatorState) -> Unit, - setRightSideVariablesToNull: (EvaluatorState) -> Unit - ): RelationExpressionAsync -} - -internal object JoinRelationalOperatorFactoryDefaultAsync : JoinRelationalOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - override fun create( - impl: PartiqlPhysical.Impl, - joinType: PartiqlPhysical.JoinType, - leftBexpr: RelationExpressionAsync, - rightBexpr: RelationExpressionAsync, - predicateExpr: ValueExpressionAsync?, - setLeftSideVariablesToNull: (EvaluatorState) -> Unit, - setRightSideVariablesToNull: (EvaluatorState) -> Unit - ): RelationExpressionAsync = when (joinType) { - is PartiqlPhysical.JoinType.Inner -> { - InnerJoinOperatorAsync( - lhs = leftBexpr, - rhs = rightBexpr, - condition = predicateExpr?.closure() ?: { true } - ) - } - is PartiqlPhysical.JoinType.Left -> { - LeftJoinOperatorAsync( - lhs = leftBexpr, - rhs = rightBexpr, - condition = predicateExpr?.closure() ?: { true }, - setRightSideVariablesToNull = setRightSideVariablesToNull - ) - } - is PartiqlPhysical.JoinType.Right -> { - RightJoinOperatorAsync( - lhs = leftBexpr, - rhs = rightBexpr, - condition = predicateExpr?.closure() ?: { true }, - setLeftSideVariablesToNull = setLeftSideVariablesToNull - ) - } - is PartiqlPhysical.JoinType.Full -> TODO("Full join") - } - - private fun ValueExpressionAsync.closure(): suspend (EvaluatorState) -> Boolean = { state: EvaluatorState -> - val v = invoke(state) - v.isNotUnknown() && v.booleanValue() - } -} - -/** - * See specification 5.6 - */ -private class InnerJoinOperatorAsync( - private val lhs: RelationExpressionAsync, - private val rhs: RelationExpressionAsync, - private val condition: suspend (EvaluatorState) -> Boolean -) : RelationExpressionAsync { - - override suspend fun evaluate(state: EvaluatorState) = relation(RelationType.BAG) { - val leftItr = lhs.evaluate(state) - while (leftItr.nextRow()) { - val rightItr = rhs.evaluate(state) - while (rightItr.nextRow()) { - if (condition(state)) { - yield() - } - } - } - } -} - -/** - * See specification 5.6 - */ -private class LeftJoinOperatorAsync( - private val lhs: RelationExpressionAsync, - private val rhs: RelationExpressionAsync, - private val condition: suspend (EvaluatorState) -> Boolean, - private val setRightSideVariablesToNull: (EvaluatorState) -> Unit -) : RelationExpressionAsync { - - override suspend fun evaluate(state: EvaluatorState) = relation(RelationType.BAG) { - val leftItr = lhs.evaluate(state) - while (leftItr.nextRow()) { - val rightItr = rhs.evaluate(state) - var yieldedSomething = false - while (rightItr.nextRow()) { - if (condition(state)) { - yield() - yieldedSomething = true - } - } - if (!yieldedSomething) { - setRightSideVariablesToNull(state) - yield() - } - } - } -} - -/** - * See specification 5.6 - */ -private class RightJoinOperatorAsync( - private val lhs: RelationExpressionAsync, - private val rhs: RelationExpressionAsync, - private val condition: suspend (EvaluatorState) -> Boolean, - private val setLeftSideVariablesToNull: (EvaluatorState) -> Unit -) : RelationExpressionAsync { - - override suspend fun evaluate(state: EvaluatorState) = relation(RelationType.BAG) { - val rightItr = rhs.evaluate(state) - while (rightItr.nextRow()) { - val leftItr = lhs.evaluate(state) - var yieldedSomething = false - while (leftItr.nextRow()) { - if (condition(state)) { - yield() - yieldedSomething = true - } - } - if (!yieldedSomething) { - setLeftSideVariablesToNull(state) - yield() - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LetRelationalOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LetRelationalOperatorFactory.kt deleted file mode 100644 index fb7047f51a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LetRelationalOperatorFactory.kt +++ /dev/null @@ -1,66 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.VariableBinding -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Let] operator. - * - * @constructor - * - * @param name - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("LetRelationalOperatorFactoryAsync")) -abstract class LetRelationalOperatorFactory(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.LET, name) - - /** - * Creates a [RelationExpression] instance for [PartiqlPhysical.Bexpr.Let]. - * - * @param impl - * @param sourceBexpr - * @param bindings list of [VariableBinding]s in the `LET` clause - * @return - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("LetRelationalOperatorFactoryAsync.create")) - abstract fun create( - impl: PartiqlPhysical.Impl, - sourceBexpr: RelationExpression, - bindings: List - ): RelationExpression -} - -internal object LetRelationalOperatorFactoryDefault : LetRelationalOperatorFactory(DEFAULT_IMPL_NAME) { - - override fun create( - impl: PartiqlPhysical.Impl, - sourceBexpr: RelationExpression, - bindings: List - ) = LetOperator( - input = sourceBexpr, - bindings = bindings, - ) -} - -internal class LetOperator( - private val input: RelationExpression, - private val bindings: List -) : RelationExpression { - - override fun evaluate(state: EvaluatorState): RelationIterator { - val rows = input.evaluate(state) - return relation(rows.relType) { - while (rows.nextRow()) { - bindings.forEach { - it.setFunc(state, it.expr(state)) - } - yield() - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LetRelationalOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LetRelationalOperatorFactoryAsync.kt deleted file mode 100644 index 21ff9365c5..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LetRelationalOperatorFactoryAsync.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.VariableBindingAsync -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Let] operator. - * - * @constructor - * - * @param name - */ -abstract class LetRelationalOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.LET, name) - - /** - * Creates a [RelationExpressionAsync] instance for [PartiqlPhysical.Bexpr.Let]. - * - * @param impl - * @param sourceBexpr - * @param bindings list of [VariableBindingAsync]s in the `LET` clause - * @return - */ - abstract fun create( - impl: PartiqlPhysical.Impl, - sourceBexpr: RelationExpressionAsync, - bindings: List - ): RelationExpressionAsync -} - -internal object LetRelationalOperatorFactoryDefaultAsync : LetRelationalOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - - override fun create( - impl: PartiqlPhysical.Impl, - sourceBexpr: RelationExpressionAsync, - bindings: List - ) = LetOperatorAsync( - input = sourceBexpr, - bindings = bindings, - ) -} - -internal class LetOperatorAsync( - private val input: RelationExpressionAsync, - private val bindings: List -) : RelationExpressionAsync { - - override suspend fun evaluate(state: EvaluatorState): RelationIterator { - val rows = input.evaluate(state) - return relation(rows.relType) { - while (rows.nextRow()) { - bindings.forEach { - it.setFunc(state, it.expr(state)) - } - yield() - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LimitRelationalOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LimitRelationalOperatorFactory.kt deleted file mode 100644 index 893690f4fa..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LimitRelationalOperatorFactory.kt +++ /dev/null @@ -1,107 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.internal.err -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Limit] operator. - * - * @constructor - * - * @param name - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("LimitRelationalOperatorFactoryAsync")) -abstract class LimitRelationalOperatorFactory(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.LIMIT, name) - - /** - * Creates a [RelationExpression] instance for [PartiqlPhysical.Bexpr.Limit]. - * - * @param impl - * @param rowCountExpr - * @param sourceBexpr - * @return - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("LimitRelationalOperatorFactoryAsync.create")) - abstract fun create( - impl: PartiqlPhysical.Impl, - rowCountExpr: ValueExpression, - sourceBexpr: RelationExpression - ): RelationExpression -} - -internal object LimitRelationalOperatorFactoryDefault : LimitRelationalOperatorFactory(DEFAULT_IMPL_NAME) { - - override fun create( - impl: PartiqlPhysical.Impl, - rowCountExpr: ValueExpression, - sourceBexpr: RelationExpression - ) = LimitOperator( - input = sourceBexpr, - limit = rowCountExpr - ) -} - -internal class LimitOperator( - private val input: RelationExpression, - private val limit: ValueExpression, -) : RelationExpression { - - override fun evaluate(state: EvaluatorState): RelationIterator { - val limit = evalLimitRowCount(limit, state) - val rows = input.evaluate(state) - return relation(rows.relType) { - var rowCount = 0L - while (rowCount++ < limit && rows.nextRow()) { - yield() - } - } - } - - private fun evalLimitRowCount(rowCountExpr: ValueExpression, env: EvaluatorState): Long { - val limitExprValue = rowCountExpr(env) - if (limitExprValue.type != ExprValueType.INT) { - err( - "LIMIT value was not an integer", - ErrorCode.EVALUATOR_NON_INT_LIMIT_VALUE, - errorContextFrom(rowCountExpr.sourceLocation).also { - it[Property.ACTUAL_TYPE] = limitExprValue.type.toString() - }, - internal = false - ) - } - - val originalLimitValue = limitExprValue.numberValue() - val limitValue = originalLimitValue.toLong() - if (originalLimitValue != limitValue as Number) { // Make sure `Number.toLong()` is a lossless transformation - err( - "Integer exceeds Long.MAX_VALUE provided as LIMIT value", - ErrorCode.INTERNAL_ERROR, - errorContextFrom(rowCountExpr.sourceLocation), - internal = true - ) - } - - if (limitValue < 0) { - err( - "negative LIMIT", - ErrorCode.EVALUATOR_NEGATIVE_LIMIT, - errorContextFrom(rowCountExpr.sourceLocation), - internal = false - ) - } - // we can't use the Kotlin's Sequence.take(n) for this since it accepts only an integer. - // this references [Sequence.take(count: Long): Sequence] defined in [org.partiql.util]. - return limitValue - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LimitRelationalOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LimitRelationalOperatorFactoryAsync.kt deleted file mode 100644 index 9442c0fbf1..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/LimitRelationalOperatorFactoryAsync.kt +++ /dev/null @@ -1,105 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.err -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Limit] operator. - * - * @constructor - * - * @param name - */ -abstract class LimitRelationalOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.LIMIT, name) - - /** - * Creates a [RelationExpressionAsync] instance for [PartiqlPhysical.Bexpr.Limit]. - * - * @param impl - * @param rowCountExpr - * @param sourceBexpr - * @return - */ - abstract fun create( - impl: PartiqlPhysical.Impl, - rowCountExpr: ValueExpressionAsync, - sourceBexpr: RelationExpressionAsync - ): RelationExpressionAsync -} - -internal object LimitRelationalOperatorFactoryDefaultAsync : LimitRelationalOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - - override fun create( - impl: PartiqlPhysical.Impl, - rowCountExpr: ValueExpressionAsync, - sourceBexpr: RelationExpressionAsync - ) = LimitOperatorAsync( - input = sourceBexpr, - limit = rowCountExpr - ) -} - -internal class LimitOperatorAsync( - private val input: RelationExpressionAsync, - private val limit: ValueExpressionAsync, -) : RelationExpressionAsync { - - override suspend fun evaluate(state: EvaluatorState): RelationIterator { - val limit = evalLimitRowCount(limit, state) - val rows = input.evaluate(state) - return relation(rows.relType) { - var rowCount = 0L - while (rowCount++ < limit && rows.nextRow()) { - yield() - } - } - } - - private suspend fun evalLimitRowCount(rowCountExpr: ValueExpressionAsync, env: EvaluatorState): Long { - val limitExprValue = rowCountExpr(env) - if (limitExprValue.type != ExprValueType.INT) { - err( - "LIMIT value was not an integer", - ErrorCode.EVALUATOR_NON_INT_LIMIT_VALUE, - errorContextFrom(rowCountExpr.sourceLocation).also { - it[Property.ACTUAL_TYPE] = limitExprValue.type.toString() - }, - internal = false - ) - } - - val originalLimitValue = limitExprValue.numberValue() - val limitValue = originalLimitValue.toLong() - if (originalLimitValue != limitValue as Number) { // Make sure `Number.toLong()` is a lossless transformation - err( - "Integer exceeds Long.MAX_VALUE provided as LIMIT value", - ErrorCode.INTERNAL_ERROR, - errorContextFrom(rowCountExpr.sourceLocation), - internal = true - ) - } - - if (limitValue < 0) { - err( - "negative LIMIT", - ErrorCode.EVALUATOR_NEGATIVE_LIMIT, - errorContextFrom(rowCountExpr.sourceLocation), - internal = false - ) - } - // we can't use the Kotlin's Sequence.take(n) for this since it accepts only an integer. - // this references [Sequence.take(count: Long): Sequence] defined in [org.partiql.util]. - return limitValue - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/OffsetRelationalOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/OffsetRelationalOperatorFactory.kt deleted file mode 100644 index a27fd1f50c..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/OffsetRelationalOperatorFactory.kt +++ /dev/null @@ -1,109 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.internal.err -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Offset] operator. - * - * @constructor - * - * @param name - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("OffsetRelationalOperatorFactoryAsync")) -abstract class OffsetRelationalOperatorFactory(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.OFFSET, name) - - /** - * Creates a [RelationExpression] instance for [PartiqlPhysical.Bexpr.Offset]. - * - * @param impl - * @param rowCountExpr - * @param sourceBexpr - * @return - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("OffsetRelationalOperatorFactoryAsync.create")) - abstract fun create( - impl: PartiqlPhysical.Impl, - rowCountExpr: ValueExpression, - sourceBexpr: RelationExpression - ): RelationExpression -} - -internal object OffsetRelationalOperatorFactoryDefault : OffsetRelationalOperatorFactory(DEFAULT_IMPL_NAME) { - - override fun create( - impl: PartiqlPhysical.Impl, - rowCountExpr: ValueExpression, - sourceBexpr: RelationExpression - ) = OffsetOperator( - input = sourceBexpr, - offset = rowCountExpr, - ) -} - -internal class OffsetOperator( - private val input: RelationExpression, - private val offset: ValueExpression, -) : RelationExpression { - - override fun evaluate(state: EvaluatorState): RelationIterator { - val skipCount: Long = evalOffsetRowCount(offset, state) - val rows = input.evaluate(state) - return relation(rows.relType) { - var rowCount = 0L - while (rowCount++ < skipCount) { - // stop iterating if we run out of rows before we hit the offset. - if (!rows.nextRow()) { - return@relation - } - } - yieldAll(rows) - } - } - - private fun evalOffsetRowCount(rowCountExpr: ValueExpression, state: EvaluatorState): Long { - val offsetExprValue = rowCountExpr(state) - if (offsetExprValue.type != ExprValueType.INT) { - err( - "OFFSET value was not an integer", - ErrorCode.EVALUATOR_NON_INT_OFFSET_VALUE, - errorContextFrom(rowCountExpr.sourceLocation).also { - it[Property.ACTUAL_TYPE] = offsetExprValue.type.toString() - }, - internal = false - ) - } - - val originalOffsetValue = offsetExprValue.numberValue() - val offsetValue = originalOffsetValue.toLong() - if (originalOffsetValue != offsetValue as Number) { // Make sure `Number.toLong()` is a lossless transformation - err( - "Integer exceeds Long.MAX_VALUE provided as OFFSET value", - ErrorCode.INTERNAL_ERROR, - errorContextFrom(rowCountExpr.sourceLocation), - internal = true - ) - } - - if (offsetValue < 0) { - err( - "negative OFFSET", - ErrorCode.EVALUATOR_NEGATIVE_OFFSET, - errorContextFrom(rowCountExpr.sourceLocation), - internal = false - ) - } - return offsetValue - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/OffsetRelationalOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/OffsetRelationalOperatorFactoryAsync.kt deleted file mode 100644 index 3df809d793..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/OffsetRelationalOperatorFactoryAsync.kt +++ /dev/null @@ -1,107 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.err -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Offset] operator. - * - * @constructor - * - * @param name - */ -abstract class OffsetRelationalOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.OFFSET, name) - - /** - * Creates a [RelationExpressionAsync] instance for [PartiqlPhysical.Bexpr.Offset]. - * - * @param impl - * @param rowCountExpr - * @param sourceBexpr - * @return - */ - abstract fun create( - impl: PartiqlPhysical.Impl, - rowCountExpr: ValueExpressionAsync, - sourceBexpr: RelationExpressionAsync - ): RelationExpressionAsync -} - -internal object OffsetRelationalOperatorFactoryDefaultAsync : OffsetRelationalOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - - override fun create( - impl: PartiqlPhysical.Impl, - rowCountExpr: ValueExpressionAsync, - sourceBexpr: RelationExpressionAsync - ) = OffsetOperatorAsync( - input = sourceBexpr, - offset = rowCountExpr, - ) -} - -internal class OffsetOperatorAsync( - private val input: RelationExpressionAsync, - private val offset: ValueExpressionAsync, -) : RelationExpressionAsync { - - override suspend fun evaluate(state: EvaluatorState): RelationIterator { - val skipCount: Long = evalOffsetRowCount(offset, state) - val rows = input.evaluate(state) - return relation(rows.relType) { - var rowCount = 0L - while (rowCount++ < skipCount) { - // stop iterating if we run out of rows before we hit the offset. - if (!rows.nextRow()) { - return@relation - } - } - yieldAll(rows) - } - } - - private suspend fun evalOffsetRowCount(rowCountExpr: ValueExpressionAsync, state: EvaluatorState): Long { - val offsetExprValue = rowCountExpr(state) - if (offsetExprValue.type != ExprValueType.INT) { - err( - "OFFSET value was not an integer", - ErrorCode.EVALUATOR_NON_INT_OFFSET_VALUE, - errorContextFrom(rowCountExpr.sourceLocation).also { - it[Property.ACTUAL_TYPE] = offsetExprValue.type.toString() - }, - internal = false - ) - } - - val originalOffsetValue = offsetExprValue.numberValue() - val offsetValue = originalOffsetValue.toLong() - if (originalOffsetValue != offsetValue as Number) { // Make sure `Number.toLong()` is a lossless transformation - err( - "Integer exceeds Long.MAX_VALUE provided as OFFSET value", - ErrorCode.INTERNAL_ERROR, - errorContextFrom(rowCountExpr.sourceLocation), - internal = true - ) - } - - if (offsetValue < 0) { - err( - "negative OFFSET", - ErrorCode.EVALUATOR_NEGATIVE_OFFSET, - errorContextFrom(rowCountExpr.sourceLocation), - internal = false - ) - } - return offsetValue - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ProjectRelationalOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ProjectRelationalOperatorFactory.kt deleted file mode 100644 index cd9a652f00..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ProjectRelationalOperatorFactory.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.physical.SetVariableFunc - -/** Provides an implementation of the [PartiqlPhysical.Bexpr.Project] operator.*/ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("ProjectRelationalOperatorFactoryAsync")) -abstract class ProjectRelationalOperatorFactory(name: String) : RelationalOperatorFactory { - final override val key: RelationalOperatorFactoryKey = RelationalOperatorFactoryKey(RelationalOperatorKind.PROJECT, name) - - /** Creates a [RelationExpression] instance for [PartiqlPhysical.Bexpr.Project]. */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("ProjectRelationalOperatorFactoryAsync.create")) - abstract fun create( - /** - * Contains any static arguments needed by the operator implementation that were supplied by the planner - * pass which specified the operator implementation. - */ - impl: PartiqlPhysical.Impl, - /** Invoke to set the binding for the current row. */ - setVar: SetVariableFunc, - /** Invoke to obtain evaluation-time arguments. */ - args: List - ): RelationExpression -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ProjectRelationalOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ProjectRelationalOperatorFactoryAsync.kt deleted file mode 100644 index 4268e6d6e0..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ProjectRelationalOperatorFactoryAsync.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.physical.SetVariableFunc - -/** Provides an implementation of the [PartiqlPhysical.Bexpr.Project] operator.*/ -abstract class ProjectRelationalOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - final override val key: RelationalOperatorFactoryKey = RelationalOperatorFactoryKey(RelationalOperatorKind.PROJECT, name) - - /** Creates a [RelationExpressionAsync] instance for [PartiqlPhysical.Bexpr.Project]. */ - abstract fun create( - /** - * Contains any static arguments needed by the operator implementation that were supplied by the planner - * pass which specified the operator implementation. - */ - impl: PartiqlPhysical.Impl, - /** Invoke to set the binding for the current row. */ - setVar: SetVariableFunc, - /** Invoke to obtain evaluation-time arguments. */ - args: List - ): RelationExpressionAsync -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationExpression.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationExpression.kt deleted file mode 100644 index 0f1261d770..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationExpression.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator - -/** - * An implementation of a physical plan relational operator. - * - * PartiQL's relational algebra is based on - * [E.F. Codd's Relational Algebra](https://en.wikipedia.org/wiki/Relational_algebra), but to better support - * semi-structured, schemaless data, our "relations" are actually logical collections of bindings. Still, the term - * "relation" has remained, as well as most other concepts from E.F. Codd's relational algebra. - * - * Like [ValueExpression], this is public API that is supported long term and is intended to avoid exposing - * implementation details such as [org.partiql.lang.eval.physical.RelationThunkEnv]. - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("RelationExpressionAsync")) -fun interface RelationExpression { - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("RelationExpressionAsync.evaluate")) - fun evaluate(state: EvaluatorState): RelationIterator -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationExpressionAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationExpressionAsync.kt deleted file mode 100644 index 61f6f4a795..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationExpressionAsync.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator - -/** - * An implementation of a physical plan relational operator. - * - * PartiQL's relational algebra is based on - * [E.F. Codd's Relational Algebra](https://en.wikipedia.org/wiki/Relational_algebra), but to better support - * semi-structured, schemaless data, our "relations" are actually logical collections of bindings. Still, the term - * "relation" has remained, as well as most other concepts from E.F. Codd's relational algebra. - * - * Like [ValueExpression], this is public API that is supported long term and is intended to avoid exposing - * implementation details such as [org.partiql.lang.eval.physical.RelationThunkEnv]. - */ -fun interface RelationExpressionAsync { - suspend fun evaluate(state: EvaluatorState): RelationIterator -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationalOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationalOperatorFactory.kt deleted file mode 100644 index dd2ce4d424..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationalOperatorFactory.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -/** - * Marker interface with unique [key], which allows all [RelationalOperatorFactory] implementations to exist in a - * `Map`. - * - * Implementations of this interface also define a `create` function, each with a different signature, but always - * returning an instance of [RelationExpression], which is ready to be evaluated as part of query evaluation. Within - * the `create` function, the factory factory may access any values placed in its - * [org.partiql.lang.domains.PartiqlPhysical.Impl.staticArgs], which may be relevant to the operator's implementation - * and perform any compile-time initialization of the [RelationExpression]. - */ -interface RelationalOperatorFactory { - val key: RelationalOperatorFactoryKey -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey.kt deleted file mode 100644 index 749efdd73c..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationalOperatorFactoryKey.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -/** - * A unique identifier for physical operator factories. - * - * Allows all [RelationalOperatorFactory] instances to be stored in a `Map`. - */ -data class RelationalOperatorFactoryKey( - /** The operator implemented by the [RelationalOperatorFactory]. */ - val operator: RelationalOperatorKind, - /** The name of the operator. */ - val name: String -) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationalOperatorKind.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationalOperatorKind.kt deleted file mode 100644 index dd3af23f77..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/RelationalOperatorKind.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -/** - * Indicates which physical relational operator a [RelationalOperatorFactory] can create instances of. - * - * This is part of [RelationalOperatorFactoryKey] and also implies which subclass of [RelationalOperatorFactory] the - * implementation must derive from; e.g. a [RelationalOperatorFactory]s with [PROJECT] in its key must implement - * [ProjectRelationalOperatorFactory], with [FILTER] the factory must implement [FilterRelationalOperatorFactory], and - * so on. - */ -enum class RelationalOperatorKind { - PROJECT, - SCAN, - UNPIVOT, - FILTER, - JOIN, - WINDOW, - OFFSET, - LIMIT, - LET, - SORT, - AGGREGATE -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ScanRelationalOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ScanRelationalOperatorFactory.kt deleted file mode 100644 index 1dfa3e60f8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ScanRelationalOperatorFactory.kt +++ /dev/null @@ -1,84 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.address -import org.partiql.lang.eval.name -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.SetVariableFunc -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.eval.unnamedValue -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Scan] operator. - * - * @constructor - * - * @param name - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("ScanRelationalOperatorFactoryAsync")) -abstract class ScanRelationalOperatorFactory(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.SCAN, name) - - /** - * Creates a [RelationExpression] instance for [PartiqlPhysical.Bexpr.Scan]. - * - * @param impl static arguments - * @param expr invoked to obtain an iterable value - * @param setAsVar AS variable binding - * @param setAtVar AT variable binding, if non-null - * @param setByVar BY variable binding, if non-null - * @return - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("ScanRelationalOperatorFactoryAsync.create")) - abstract fun create( - impl: PartiqlPhysical.Impl, - expr: ValueExpression, - setAsVar: SetVariableFunc, - setAtVar: SetVariableFunc?, - setByVar: SetVariableFunc? - ): RelationExpression -} - -internal object ScanRelationalOperatorFactoryDefault : ScanRelationalOperatorFactory(DEFAULT_IMPL_NAME) { - override fun create( - impl: PartiqlPhysical.Impl, - expr: ValueExpression, - setAsVar: SetVariableFunc, - setAtVar: SetVariableFunc?, - setByVar: SetVariableFunc? - ) = ScanOperator(expr, setAsVar, setAtVar, setByVar) -} - -internal class ScanOperator( - private val expr: ValueExpression, - private val setAsVar: SetVariableFunc, - private val setAtVar: SetVariableFunc?, - private val setByVar: SetVariableFunc? -) : RelationExpression { - - override fun evaluate(state: EvaluatorState): RelationIterator { - val value = expr(state) - val sequence: Sequence = when (value.type) { - ExprValueType.LIST, - ExprValueType.BAG -> value.asSequence() - else -> sequenceOf(value) - } - return relation(RelationType.BAG) { - val rows: Iterator = sequence.iterator() - while (rows.hasNext()) { - val item = rows.next() - // .unnamedValue() removes any ordinal that might exist on item - setAsVar(state, item.unnamedValue()) - setAtVar?.let { it(state, item.name ?: ExprValue.missingValue) } - setByVar?.let { it(state, item.address ?: ExprValue.missingValue) } - yield() - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ScanRelationalOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ScanRelationalOperatorFactoryAsync.kt deleted file mode 100644 index 54b7531c63..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ScanRelationalOperatorFactoryAsync.kt +++ /dev/null @@ -1,82 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.address -import org.partiql.lang.eval.name -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.SetVariableFunc -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.eval.unnamedValue -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * Provides an implementation of the [PartiqlPhysical.Bexpr.Scan] operator. - * - * @constructor - * - * @param name - */ -abstract class ScanRelationalOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - - final override val key = RelationalOperatorFactoryKey(RelationalOperatorKind.SCAN, name) - - /** - * Creates a [RelationExpressionAsync] instance for [PartiqlPhysical.Bexpr.Scan]. - * - * @param impl static arguments - * @param expr invoked to obtain an iterable value - * @param setAsVar AS variable binding - * @param setAtVar AT variable binding, if non-null - * @param setByVar BY variable binding, if non-null - * @return - */ - abstract fun create( - impl: PartiqlPhysical.Impl, - expr: ValueExpressionAsync, - setAsVar: SetVariableFunc, - setAtVar: SetVariableFunc?, - setByVar: SetVariableFunc? - ): RelationExpressionAsync -} - -internal object ScanRelationalOperatorFactoryDefaultAsync : ScanRelationalOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - override fun create( - impl: PartiqlPhysical.Impl, - expr: ValueExpressionAsync, - setAsVar: SetVariableFunc, - setAtVar: SetVariableFunc?, - setByVar: SetVariableFunc? - ) = ScanOperatorAsync(expr, setAsVar, setAtVar, setByVar) -} - -internal class ScanOperatorAsync( - private val expr: ValueExpressionAsync, - private val setAsVar: SetVariableFunc, - private val setAtVar: SetVariableFunc?, - private val setByVar: SetVariableFunc? -) : RelationExpressionAsync { - - override suspend fun evaluate(state: EvaluatorState): RelationIterator { - val value = expr(state) - val sequence: Sequence = when (value.type) { - ExprValueType.LIST, - ExprValueType.BAG -> value.asSequence() - else -> sequenceOf(value) - } - return relation(RelationType.BAG) { - val rows: Iterator = sequence.iterator() - while (rows.hasNext()) { - val item = rows.next() - // .unnamedValue() removes any ordinal that might exist on item - setAsVar(state, item.unnamedValue()) - setAtVar?.let { it(state, item.name ?: ExprValue.missingValue) } - setByVar?.let { it(state, item.address ?: ExprValue.missingValue) } - yield() - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactory.kt deleted file mode 100644 index ec90d26ad0..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactory.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.NaturalExprValueComparators - -/** Provides an implementation of the [PartiqlPhysical.Bexpr.Order] operator.*/ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("SortOperatorFactoryAsync")) -public abstract class SortOperatorFactory(name: String) : RelationalOperatorFactory { - public final override val key: RelationalOperatorFactoryKey = RelationalOperatorFactoryKey(RelationalOperatorKind.SORT, name) - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("SortOperatorFactoryAsync.create")) - public abstract fun create( - sortKeys: List, - sourceRelation: RelationExpression - ): RelationExpression -} - -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("CompiledSortKeyAsync")) -public class CompiledSortKey(val comparator: NaturalExprValueComparators, val value: ValueExpression) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactoryAsync.kt deleted file mode 100644 index 4447939475..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactoryAsync.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.NaturalExprValueComparators - -/** Provides an implementation of the [PartiqlPhysical.Bexpr.Sort] operator.*/ -public abstract class SortOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - public final override val key: RelationalOperatorFactoryKey = RelationalOperatorFactoryKey(RelationalOperatorKind.SORT, name) - public abstract fun create( - sortKeys: List, - sourceRelation: RelationExpressionAsync - ): RelationExpressionAsync -} - -public class CompiledSortKeyAsync(val comparator: NaturalExprValueComparators, val value: ValueExpressionAsync) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactoryDefault.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactoryDefault.kt deleted file mode 100644 index 7befb0d373..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactoryDefault.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical.operators - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.internal.errNoContext -import org.partiql.lang.eval.internal.ext.interruptibleFold -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -internal object SortOperatorFactoryDefault : SortOperatorFactory(DEFAULT_IMPL_NAME) { - override fun create( - sortKeys: List, - sourceRelation: RelationExpression - ): RelationExpression = SortOperatorDefault(sortKeys, sourceRelation) -} - -internal class SortOperatorDefault(private val sortKeys: List, private val sourceRelation: RelationExpression) : RelationExpression { - override fun evaluate(state: EvaluatorState): RelationIterator { - val source = sourceRelation.evaluate(state) - return relation(RelationType.LIST) { - val rows = mutableListOf>() - val comparator = getSortingComparator(sortKeys, state) - - // Consume Input - while (source.nextRow()) { - rows.add(state.registers.clone()) - } - - // Perform Sort - val sortedRows = rows.sortedWith(comparator) - - // Yield Sorted Rows - val iterator = sortedRows.iterator() - while (iterator.hasNext()) { - state.load(iterator.next()) - yield() - } - } - } -} - -/** - * Returns a [Comparator] that compares arrays of registers by using un-evaluated sort keys. It does this by modifying - * the [state] to allow evaluation of the [sortKeys] - */ -internal fun getSortingComparator(sortKeys: List, state: EvaluatorState): Comparator> { - val initial: Comparator>? = null - return sortKeys.interruptibleFold(initial) { intermediate, sortKey -> - if (intermediate == null) { - return@interruptibleFold compareBy, ExprValue>(sortKey.comparator) { row -> - state.load(row) - sortKey.value(state) - } - } - return@interruptibleFold intermediate.thenBy(sortKey.comparator) { row -> - state.load(row) - sortKey.value(state) - } - } ?: errNoContext( - "Order BY comparator cannot be null", - ErrorCode.EVALUATOR_ORDER_BY_NULL_COMPARATOR, - internal = true - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactoryDefaultAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactoryDefaultAsync.kt deleted file mode 100644 index 66b17bafc3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/SortOperatorFactoryDefaultAsync.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical.operators - -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.NaturalExprValueComparators -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -internal object SortOperatorFactoryDefaultAsync : SortOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - override fun create( - sortKeys: List, - sourceRelation: RelationExpressionAsync - ): RelationExpressionAsync = SortOperatorDefaultAsync(sortKeys, sourceRelation) -} - -internal class SortOperatorDefaultAsync(private val sortKeys: List, private val sourceRelation: RelationExpressionAsync) : RelationExpressionAsync { - override suspend fun evaluate(state: EvaluatorState): RelationIterator { - val source = sourceRelation.evaluate(state) - return relation(RelationType.LIST) { - val rows = mutableListOf>() - - // Consume Input - while (source.nextRow()) { - rows.add(state.registers.clone()) - } - - val rowWithValues = rows.map { row -> - state.load(row) - row to sortKeys.map { sk -> - sk.value(state) - } - }.toMutableList() - val comparator = getSortingComparator(sortKeys.map { it.comparator }) - - // Perform Sort - val sortedRows = rowWithValues.sortedWith(comparator) - - // Yield Sorted Rows - val iterator = sortedRows.iterator() - while (iterator.hasNext()) { - state.load(iterator.next().first) - yield() - } - } - } -} - -/** - * Returns a [Comparator] that compares arrays of registers by using un-evaluated sort keys. It does this by modifying - * the [EvaluatorState] to allow evaluation of the [sortKeys]. - */ -internal fun getSortingComparator(sortKeys: List): Comparator, List>> { - return object : Comparator, List>> { - override fun compare( - l: Pair, List>, - r: Pair, List> - ): Int { - val valsToCompare = l.second.zip(r.second) - sortKeys.zip(valsToCompare).map { - val comp = it.first - val cmpResult = comp.compare(it.second.first, it.second.second) - if (cmpResult != 0) { - return cmpResult - } - } - return 0 - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactory.kt deleted file mode 100644 index 5ca8a3bf5f..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactory.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.physical.SetVariableFunc - -/** Provides an implementation of the [PartiqlPhysical.Bexpr.Scan] operator.*/ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("UnpivotOperatorFactoryAsync")) -public abstract class UnpivotOperatorFactory(name: String) : RelationalOperatorFactory { - public final override val key: RelationalOperatorFactoryKey = RelationalOperatorFactoryKey(RelationalOperatorKind.UNPIVOT, name) - - /** Creates a [RelationExpression] instance for [PartiqlPhysical.Bexpr.Scan]. */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("UnpivotOperatorFactoryAsync.create")) - public abstract fun create( - /** Invoke to obtain the value to be iterated over.*/ - expr: ValueExpression, - /** Invoke to set the `AS` variable binding. */ - setAsVar: SetVariableFunc, - /** Invoke to set the `AT` variable binding, if non-null */ - setAtVar: SetVariableFunc?, - /** Invoke to set the `BY` variable binding, if non-null. */ - setByVar: SetVariableFunc? - ): RelationExpression -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryAsync.kt deleted file mode 100644 index 5267e945e5..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryAsync.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.physical.SetVariableFunc - -/** Provides an implementation of the [PartiqlPhysical.Bexpr.Scan] operator.*/ -public abstract class UnpivotOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - public final override val key: RelationalOperatorFactoryKey = RelationalOperatorFactoryKey(RelationalOperatorKind.UNPIVOT, name) - - /** Creates a [RelationExpressionAsync] instance for [PartiqlPhysical.Bexpr.Scan]. */ - public abstract fun create( - /** Invoke to obtain the value to be iterated over.*/ - expr: ValueExpressionAsync, - /** Invoke to set the `AS` variable binding. */ - setAsVar: SetVariableFunc, - /** Invoke to set the `AT` variable binding, if non-null */ - setAtVar: SetVariableFunc?, - /** Invoke to set the `BY` variable binding, if non-null. */ - setByVar: SetVariableFunc? - ): RelationExpressionAsync -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryDefault.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryDefault.kt deleted file mode 100644 index 9fb58dbdf1..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryDefault.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical.operators - -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.address -import org.partiql.lang.eval.name -import org.partiql.lang.eval.namedValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.SetVariableFunc -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.eval.syntheticColumnName -import org.partiql.lang.eval.unnamedValue -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -internal object UnpivotOperatorFactoryDefault : UnpivotOperatorFactory(DEFAULT_IMPL_NAME) { - override fun create( - expr: ValueExpression, - setAsVar: SetVariableFunc, - setAtVar: SetVariableFunc?, - setByVar: SetVariableFunc? - ): RelationExpression = UnpivotOperatorDefault(expr, setAsVar, setAtVar, setByVar) -} - -internal class UnpivotOperatorDefault( - private val expr: ValueExpression, - private val setAsVar: SetVariableFunc, - private val setAtVar: SetVariableFunc?, - private val setByVar: SetVariableFunc? -) : RelationExpression { - override fun evaluate(state: EvaluatorState): RelationIterator { - val originalValue = expr(state) - val unpivot = originalValue.unpivot() - - return relation(RelationType.BAG) { - val iter = unpivot.iterator() - while (iter.hasNext()) { - val item = iter.next() - setAsVar(state, item.unnamedValue()) - setAtVar?.let { it(state, item.name ?: ExprValue.missingValue) } - setByVar?.let { it(state, item.address ?: ExprValue.missingValue) } - yield() - } - } - } - - private fun ExprValue.unpivot(): ExprValue = when (type) { - ExprValueType.STRUCT, ExprValueType.MISSING -> this - else -> ExprValue.newBag( - listOf( - this.namedValue(ExprValue.newString(syntheticColumnName(0))) - ) - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryDefaultAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryDefaultAsync.kt deleted file mode 100644 index 6d768707a9..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/UnpivotOperatorFactoryDefaultAsync.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.physical.operators - -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.address -import org.partiql.lang.eval.name -import org.partiql.lang.eval.namedValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.SetVariableFunc -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.eval.syntheticColumnName -import org.partiql.lang.eval.unnamedValue -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -internal object UnpivotOperatorFactoryDefaultAsync : UnpivotOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - override fun create( - expr: ValueExpressionAsync, - setAsVar: SetVariableFunc, - setAtVar: SetVariableFunc?, - setByVar: SetVariableFunc? - ): RelationExpressionAsync = UnpivotOperatorDefaultAsync(expr, setAsVar, setAtVar, setByVar) -} - -internal class UnpivotOperatorDefaultAsync( - private val expr: ValueExpressionAsync, - private val setAsVar: SetVariableFunc, - private val setAtVar: SetVariableFunc?, - private val setByVar: SetVariableFunc? -) : RelationExpressionAsync { - override suspend fun evaluate(state: EvaluatorState): RelationIterator { - val originalValue = expr(state) - val unpivot = originalValue.unpivot() - - return relation(RelationType.BAG) { - val iter = unpivot.iterator() - while (iter.hasNext()) { - val item = iter.next() - setAsVar(state, item.unnamedValue()) - setAtVar?.let { it(state, item.name ?: ExprValue.missingValue) } - setByVar?.let { it(state, item.address ?: ExprValue.missingValue) } - yield() - } - } - } - - private fun ExprValue.unpivot(): ExprValue = when (type) { - ExprValueType.STRUCT, ExprValueType.MISSING -> this - else -> ExprValue.newBag( - listOf( - this.namedValue(ExprValue.newString(syntheticColumnName(0))) - ) - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ValueExpression.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ValueExpression.kt deleted file mode 100644 index d72da08eef..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ValueExpression.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.physical.EvaluatorState - -/** - * Evaluates a PartiQL expression returning an [ExprValue]. - * - * [RelationExpression] implementations need a mechanism to evaluate such expressions, and said mechanism should - * avoid exposing implementation details (i.e. [org.partiql.lang.eval.physical.PhysicalPlanThunk]) of the evaluator. - * This implementation accomplishes that and is intended as a publicly usable API that is supported long term. - */ -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("ValueExpressionAsync")) -interface ValueExpression { - /** Evaluates the expression. */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("ValueExpressionAsync.invoke")) - operator fun invoke(state: EvaluatorState): ExprValue - - /** Provides the source location (line & column) of the expression, for error reporting purposes. */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("ValueExpressionAsync.sourceLocation")) - val sourceLocation: SourceLocationMeta? -} - -/** Convenience constructor for [ValueExpression]. */ -internal inline fun valueExpression( - sourceLocation: SourceLocationMeta?, - crossinline invoke: (EvaluatorState) -> ExprValue -) = - object : ValueExpression { - override fun invoke(state: EvaluatorState): ExprValue = invoke(state) - override val sourceLocation: SourceLocationMeta? get() = sourceLocation - } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ValueExpressionAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ValueExpressionAsync.kt deleted file mode 100644 index 69093abcc3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/ValueExpressionAsync.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.physical.EvaluatorState - -/** - * Evaluates a PartiQL expression returning an [ExprValue]. - * - * [RelationExpression] implementations need a mechanism to evaluate such expressions, and said mechanism should - * avoid exposing implementation details (i.e. [org.partiql.lang.eval.physical.PhysicalPlanThunk]) of the evaluator. - * This implementation accomplishes that and is intended as a publicly usable API that is supported long term. - */ -interface ValueExpressionAsync { - /** Evaluates the expression. */ - suspend operator fun invoke(state: EvaluatorState): ExprValue - - /** Provides the source location (line & column) of the expression, for error reporting purposes. */ - val sourceLocation: SourceLocationMeta? -} - -/** Convenience constructor for [ValueExpression]. */ -internal inline fun valueExpressionAsync( - sourceLocation: SourceLocationMeta?, - crossinline invoke: suspend (EvaluatorState) -> ExprValue -) = - object : ValueExpressionAsync { - override suspend fun invoke(state: EvaluatorState): ExprValue = invoke(state) - override val sourceLocation: SourceLocationMeta? get() = sourceLocation - } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactory.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactory.kt deleted file mode 100644 index ed1a1acd26..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactory.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.physical.SetVariableFunc -import org.partiql.lang.eval.physical.window.WindowFunction - -@ExperimentalWindowFunctions -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("WindowRelationalOperatorFactoryAsync")) -abstract class WindowRelationalOperatorFactory(name: String) : RelationalOperatorFactory { - - final override val key: RelationalOperatorFactoryKey = RelationalOperatorFactoryKey(RelationalOperatorKind.WINDOW, name) - - /** Creates a [RelationExpression] instance for [PartiqlPhysical.Bexpr.Window]. */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("WindowRelationalOperatorFactoryAsync.create")) - abstract fun create( - source: RelationExpression, - windowPartitionList: List, - windowSortSpecList: List, - compiledWindowFunctions: List - - ): RelationExpression -} - -@ExperimentalWindowFunctions -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("CompiledWindowFunctionAsync")) -class CompiledWindowFunction( - val func: WindowFunction, - val parameters: List, - /** - * This is [PartiqlPhysical.VarDecl] instead of [SetVariableFunc] because we would like to access the index of variable in the register - * when processing rows within the partition. - */ - val windowVarDecl: PartiqlPhysical.VarDecl -) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryAsync.kt deleted file mode 100644 index 7e8c7775a3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryAsync.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.physical.SetVariableFunc -import org.partiql.lang.eval.physical.window.NavigationWindowFunctionAsync - -@ExperimentalWindowFunctions -abstract class WindowRelationalOperatorFactoryAsync(name: String) : RelationalOperatorFactory { - - final override val key: RelationalOperatorFactoryKey = RelationalOperatorFactoryKey(RelationalOperatorKind.WINDOW, name) - - /** Creates a [RelationExpressionAsync] instance for [PartiqlPhysical.Bexpr.Window]. */ - abstract fun create( - source: RelationExpressionAsync, - windowPartitionList: List, - windowSortSpecList: List, - compiledWindowFunctions: List - - ): RelationExpressionAsync -} - -@ExperimentalWindowFunctions -class CompiledWindowFunctionAsync( - val func: NavigationWindowFunctionAsync, - val parameters: List, - /** - * This is [PartiqlPhysical.VarDecl] instead of [SetVariableFunc] because we would like to access the index of variable in the register - * when processing rows within the partition. - */ - val windowVarDecl: PartiqlPhysical.VarDecl -) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryDefault.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryDefault.kt deleted file mode 100644 index efae4242ae..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryDefault.kt +++ /dev/null @@ -1,112 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.NaturalExprValueComparators -import org.partiql.lang.eval.exprEquals -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * This is an experimental implementation of the window operator - * - * The general concept here is to sort the input relation, first by partition keys (if not null) then by sort keys (if not null). - * After sorting, we do a sequence scan to create partition and materialize all the element in the same partition - * - */ -@ExperimentalWindowFunctions -internal object WindowRelationalOperatorFactoryDefault : WindowRelationalOperatorFactory(DEFAULT_IMPL_NAME) { - override fun create( - source: RelationExpression, - windowPartitionList: List, - windowSortSpecList: List, - compiledWindowFunctions: List - ): RelationExpression = WindowOperatorDefault(source, windowPartitionList, windowSortSpecList, compiledWindowFunctions) -} - -@ExperimentalWindowFunctions -internal class WindowOperatorDefault( - private val source: RelationExpression, - private val windowPartitionList: List, - private val windowSortSpecList: List, - private val compiledWindowFunctions: List -) : RelationExpression { - override fun evaluate(state: EvaluatorState): RelationIterator { - // the following corresponding to materialization process - val sourceIter = source.evaluate(state) - val registers = sequence { - while (sourceIter.nextRow()) { - yield(state.registers.clone()) - } - } - - val partitionSortSpec = windowPartitionList.map { - CompiledSortKey(NaturalExprValueComparators.NULLS_FIRST_ASC, it) - } - - val sortKeys = partitionSortSpec + windowSortSpecList - - val sortedRegisters = registers.sortedWith(getSortingComparator(sortKeys, state)) - - // create the partition here - var partition = mutableListOf>>() - - // entire partition - if (windowPartitionList.isEmpty()) { - partition.add(sortedRegisters.toList()) - } - // need to be partitioned - else { - val iter = sortedRegisters.iterator() - var rowInPartition = mutableListOf>() - var previousPartition: ExprValue? = null - while (iter.hasNext()) { - val currentRow = iter.next() - state.load(currentRow) - val currentPartition = ExprValue.newSexp( - windowPartitionList.map { - it.invoke(state) - } - ) - // for the first time, - if (previousPartition == null) { - rowInPartition.add(currentRow) - previousPartition = currentPartition - } else if (previousPartition.exprEquals(currentPartition)) { - rowInPartition.add(currentRow) - } else { - partition.add(rowInPartition.toList()) - rowInPartition.clear() - previousPartition = currentPartition - rowInPartition.add(currentRow) - } - } - // finish up - partition.add(rowInPartition.toList()) - rowInPartition.clear() - } - - return relation(RelationType.BAG) { - partition.forEach { rowsInPartition -> - compiledWindowFunctions.forEach { - val windowFunc = it.func - // set the window function partition to the current partition - windowFunc.reset(rowsInPartition) - } - - rowsInPartition.forEach { - // process current row - compiledWindowFunctions.forEach { compiledWindowFunction -> - compiledWindowFunction.func.processRow(state, compiledWindowFunction.parameters, compiledWindowFunction.windowVarDecl) - } - - // yield the result - yield() - } - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryDefaultAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryDefaultAsync.kt deleted file mode 100644 index 034fcbd18e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/operators/WindowRelationalOperatorFactoryDefaultAsync.kt +++ /dev/null @@ -1,119 +0,0 @@ -package org.partiql.lang.eval.physical.operators - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.NaturalExprValueComparators -import org.partiql.lang.eval.exprEquals -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.relation.RelationIterator -import org.partiql.lang.eval.relation.RelationType -import org.partiql.lang.eval.relation.relation -import org.partiql.lang.planner.transforms.DEFAULT_IMPL_NAME - -/** - * This is an experimental implementation of the window operator - * - * The general concept here is to sort the input relation, first by partition keys (if not null) then by sort keys (if not null). - * After sorting, we do a sequence scan to create partition and materialize all the element in the same partition - * - */ -@ExperimentalWindowFunctions -internal object WindowRelationalOperatorFactoryDefaultAsync : WindowRelationalOperatorFactoryAsync(DEFAULT_IMPL_NAME) { - override fun create( - source: RelationExpressionAsync, - windowPartitionList: List, - windowSortSpecList: List, - compiledWindowFunctions: List - ): RelationExpressionAsync = WindowOperatorDefaultAsync(source, windowPartitionList, windowSortSpecList, compiledWindowFunctions) -} - -@ExperimentalWindowFunctions -internal class WindowOperatorDefaultAsync( - private val source: RelationExpressionAsync, - private val windowPartitionList: List, - private val windowSortSpecList: List, - private val compiledWindowFunctions: List -) : RelationExpressionAsync { - override suspend fun evaluate(state: EvaluatorState): RelationIterator { - // the following corresponding to materialization process - val sourceIter = source.evaluate(state) - val registers = sequence { - while (sourceIter.nextRow()) { - yield(state.registers.clone()) - } - } - - val partitionSortSpec = windowPartitionList.map { - CompiledSortKeyAsync(NaturalExprValueComparators.NULLS_FIRST_ASC, it) - } - - val sortKeys = partitionSortSpec + windowSortSpecList - - val newRegisters = registers.toList().map { row -> - state.load(row) - row to sortKeys.map { sk -> - sk.value(state) - } - }.toMutableList() - - val sortedRegisters = newRegisters.sortedWith(getSortingComparator(sortKeys.map { it.comparator })).map { it.first } - - // create the partition here - val partition = mutableListOf>>() - - // entire partition - if (windowPartitionList.isEmpty()) { - partition.add(sortedRegisters.toList()) - } - // need to be partitioned - else { - val iter = sortedRegisters.iterator() - val rowInPartition = mutableListOf>() - var previousPartition: ExprValue? = null - while (iter.hasNext()) { - val currentRow = iter.next() - state.load(currentRow) - val currentPartition = ExprValue.newSexp( - windowPartitionList.map { - it.invoke(state) - } - ) - // for the first time, - if (previousPartition == null) { - rowInPartition.add(currentRow) - previousPartition = currentPartition - } else if (previousPartition.exprEquals(currentPartition)) { - rowInPartition.add(currentRow) - } else { - partition.add(rowInPartition.toList()) - rowInPartition.clear() - previousPartition = currentPartition - rowInPartition.add(currentRow) - } - } - // finish up - partition.add(rowInPartition.toList()) - rowInPartition.clear() - } - - return relation(RelationType.BAG) { - partition.forEach { rowsInPartition -> - compiledWindowFunctions.forEach { - val windowFunc = it.func - // set the window function partition to the current partition - windowFunc.reset(rowsInPartition) - } - - rowsInPartition.forEach { - // process current row - compiledWindowFunctions.forEach { compiledWindowFunction -> - compiledWindowFunction.func.processRow(state, compiledWindowFunction.parameters, compiledWindowFunction.windowVarDecl) - } - - // yield the result - yield() - } - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/BuiltInWindowFunction.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/BuiltInWindowFunction.kt deleted file mode 100644 index e22b83adcd..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/BuiltInWindowFunction.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.partiql.lang.eval.physical.window - -import org.partiql.annotations.ExperimentalWindowFunctions - -@ExperimentalWindowFunctions -internal fun createBuiltinWindowFunction(name: String) = - when (name) { - "lag" -> Lag() - "lead" -> Lead() - else -> error("Window function $name has not been implemented") - } - -@ExperimentalWindowFunctions -internal fun createBuiltinWindowFunctionAsync(name: String) = - when (name) { - "lag" -> LagAsync() - "lead" -> LeadAsync() - else -> error("Window function $name has not been implemented") - } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/Lag.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/Lag.kt deleted file mode 100644 index 062ed682bc..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/Lag.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.partiql.lang.eval.physical.window - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.operators.ValueExpression - -// TODO: Decide if we should reduce the code duplication by combining lead and lag function -@ExperimentalWindowFunctions -internal class Lag : NavigationWindowFunction() { - override val name = "lag" - - companion object { - const val DEFAULT_OFFSET_VALUE = 1L - } - - override fun processRow(state: EvaluatorState, arguments: List, currentPos: Int): ExprValue { - val (target, offset, default) = when (arguments.size) { - 1 -> listOf(arguments[0], null, null) - 2 -> listOf(arguments[0], arguments[1], null) - 3 -> listOf(arguments[0], arguments[1], arguments[2]) - else -> error("Wrong number of Parameter for Lag Function") - } - - val offsetValue = offset?.let { - val numberValue = it.invoke(state).numberValue().toLong() - if (numberValue >= 0) { - numberValue - } else { - error("offset need to be non-negative integer") - } - } ?: DEFAULT_OFFSET_VALUE - val defaultValue = default?.invoke(state) ?: ExprValue.nullValue - val targetIndex = currentPos - offsetValue - - if (targetIndex >= 0 && targetIndex <= currentPartition.lastIndex) { - val targetRow = currentPartition[targetIndex.toInt()] - state.load(targetRow) - return target!!.invoke(state) - } else { - return defaultValue - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/LagAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/LagAsync.kt deleted file mode 100644 index f31fd1acc4..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/LagAsync.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.partiql.lang.eval.physical.window - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.operators.ValueExpressionAsync - -// TODO: Decide if we should reduce the code duplication by combining lead and lag function -@ExperimentalWindowFunctions -internal class LagAsync : NavigationWindowFunctionAsync() { - override val name = "lag" - - companion object { - const val DEFAULT_OFFSET_VALUE = 1L - } - - override suspend fun processRow(state: EvaluatorState, arguments: List, currentPos: Int): ExprValue { - val (target, offset, default) = when (arguments.size) { - 1 -> listOf(arguments[0], null, null) - 2 -> listOf(arguments[0], arguments[1], null) - 3 -> listOf(arguments[0], arguments[1], arguments[2]) - else -> error("Wrong number of Parameter for Lag Function") - } - - val offsetValue = offset?.let { - val numberValue = it.invoke(state).numberValue().toLong() - if (numberValue >= 0) { - numberValue - } else { - error("offset need to be non-negative integer") - } - } ?: DEFAULT_OFFSET_VALUE - val defaultValue = default?.invoke(state) ?: ExprValue.nullValue - val targetIndex = currentPos - offsetValue - - return if (targetIndex >= 0 && targetIndex <= currentPartition.lastIndex) { - val targetRow = currentPartition[targetIndex.toInt()] - state.load(targetRow) - target!!.invoke(state) - } else { - defaultValue - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/Lead.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/Lead.kt deleted file mode 100644 index bb603943a3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/Lead.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.partiql.lang.eval.physical.window - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.operators.ValueExpression - -// TODO: Decide if we should reduce the code duplication by combining lead and lag function. -@ExperimentalWindowFunctions -internal class Lead : NavigationWindowFunction() { - - override val name = "lead" - - companion object { - const val DEFAULT_OFFSET_VALUE = 1L - } - - override fun processRow(state: EvaluatorState, arguments: List, currentPos: Int): ExprValue { - val (target, offset, default) = when (arguments.size) { - 1 -> listOf(arguments[0], null, null) - 2 -> listOf(arguments[0], arguments[1], null) - 3 -> listOf(arguments[0], arguments[1], arguments[2]) - else -> error("Wrong number of Parameter for Lag Function") - } - - val offsetValue = offset?.let { - val numberValue = it.invoke(state).numberValue().toLong() - if (numberValue >= 0) { - numberValue - } else { - error("offset need to be non-negative integer") - } - } ?: DEFAULT_OFFSET_VALUE - val defaultValue = default?.invoke(state) ?: ExprValue.nullValue - val targetIndex = currentPos + offsetValue - - if (targetIndex <= currentPartition.lastIndex) { - val targetRow = currentPartition[targetIndex.toInt()] - state.load(targetRow) - return target!!.invoke(state) - } else { - return defaultValue - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/LeadAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/LeadAsync.kt deleted file mode 100644 index 6d8464171b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/LeadAsync.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.partiql.lang.eval.physical.window - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.numberValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.operators.ValueExpressionAsync - -// TODO: Decide if we should reduce the code duplication by combining lead and lag function. -@ExperimentalWindowFunctions -internal class LeadAsync : NavigationWindowFunctionAsync() { - - override val name = "lead" - - companion object { - const val DEFAULT_OFFSET_VALUE = 1L - } - - override suspend fun processRow(state: EvaluatorState, arguments: List, currentPos: Int): ExprValue { - val (target, offset, default) = when (arguments.size) { - 1 -> listOf(arguments[0], null, null) - 2 -> listOf(arguments[0], arguments[1], null) - 3 -> listOf(arguments[0], arguments[1], arguments[2]) - else -> error("Wrong number of Parameter for Lag Function") - } - - val offsetValue = offset?.let { - val numberValue = it.invoke(state).numberValue().toLong() - if (numberValue >= 0) { - numberValue - } else { - error("offset need to be non-negative integer") - } - } ?: DEFAULT_OFFSET_VALUE - val defaultValue = default?.invoke(state) ?: ExprValue.nullValue - val targetIndex = currentPos + offsetValue - - return if (targetIndex <= currentPartition.lastIndex) { - val targetRow = currentPartition[targetIndex.toInt()] - state.load(targetRow) - target!!.invoke(state) - } else { - defaultValue - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/NavigationWindowFunction.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/NavigationWindowFunction.kt deleted file mode 100644 index 59d2caa4e7..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/NavigationWindowFunction.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.partiql.lang.eval.physical.window - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.operators.ValueExpression -import org.partiql.lang.eval.physical.toSetVariableFunc - -/** - * This abstract class holds some common logic for navigation window function, i.e., LAG, LEAD - * TODO: When we support FIRST_VALUE, etc, we probably need to modify the process row function, since those function requires frame - */ -@ExperimentalWindowFunctions -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("NavigationWindowFunctionAsync")) -abstract class NavigationWindowFunction() : WindowFunction { - - lateinit var currentPartition: List> - private var currentPos: Int = 0 - - override fun reset(partition: List>) { - currentPartition = partition - currentPos = 0 - } - - override fun processRow( - state: EvaluatorState, - arguments: List, - windowVarDecl: PartiqlPhysical.VarDecl - ) { - state.load(currentPartition[currentPos]) - val value = processRow(state, arguments, currentPos) - // before we declare the window function result, we need to go back to the current row - state.load(currentPartition[currentPos]) - windowVarDecl.toSetVariableFunc().invoke(state, value) - // make sure the change of state is reflected in the partition - // so the result of the current window function won't get removed by the time we process the next window function at the same row level. - currentPartition[currentPos][windowVarDecl.index.value.toInt()] = value - currentPos += 1 - } - - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("NavigationWindowFunctionAsync.processRow")) - abstract fun processRow(state: EvaluatorState, arguments: List, currentPos: Int): ExprValue -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/NavigationWindowFunctionAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/NavigationWindowFunctionAsync.kt deleted file mode 100644 index b60fb2e0d1..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/NavigationWindowFunctionAsync.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.partiql.lang.eval.physical.window - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.operators.ValueExpressionAsync -import org.partiql.lang.eval.physical.toSetVariableFunc - -/** - * This abstract class holds some common logic for navigation window function, i.e., LAG, LEAD - * TODO: When we support FIRST_VALUE, etc, we probably need to modify the process row function, since those function requires frame - */ -@ExperimentalWindowFunctions -abstract class NavigationWindowFunctionAsync : WindowFunctionAsync { - - lateinit var currentPartition: List> - private var currentPos: Int = 0 - - override fun reset(partition: List>) { - currentPartition = partition - currentPos = 0 - } - - override suspend fun processRow( - state: EvaluatorState, - arguments: List, - windowVarDecl: PartiqlPhysical.VarDecl - ) { - state.load(currentPartition[currentPos]) - val value = processRow(state, arguments, currentPos) - // before we declare the window function result, we need to go back to the current row - state.load(currentPartition[currentPos]) - windowVarDecl.toSetVariableFunc().invoke(state, value) - // make sure the change of state is reflected in the partition - // so the result of the current window function won't get removed by the time we process the next window function at the same row level. - currentPartition[currentPos][windowVarDecl.index.value.toInt()] = value - currentPos += 1 - } - - abstract suspend fun processRow(state: EvaluatorState, arguments: List, currentPos: Int): ExprValue -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/WindowFunction.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/WindowFunction.kt deleted file mode 100644 index 71af614f57..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/WindowFunction.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.partiql.lang.eval.physical.window - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.operators.ValueExpression - -@ExperimentalWindowFunctions -@Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("WindowFunctionAsync")) -interface WindowFunction { - - val name: String - - /** - * The reset function should be called before enter a new partition ( including the first one). - * - * For now, a partition is represented by list>. - * We could potentially benefit from further abstraction of partition. - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("WindowFunctionAsync.reset")) - fun reset(partition: List>) - - /** - * Process a row by outputting the result of the window function. - */ - @Deprecated("To be removed in the next major version.", replaceWith = ReplaceWith("WindowFunctionAsync.processRow")) - fun processRow(state: EvaluatorState, arguments: List, windowVarDecl: PartiqlPhysical.VarDecl) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/WindowFunctionAsync.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/WindowFunctionAsync.kt deleted file mode 100644 index 270e3e20eb..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/window/WindowFunctionAsync.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.partiql.lang.eval.physical.window - -import org.partiql.annotations.ExperimentalWindowFunctions -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.physical.EvaluatorState -import org.partiql.lang.eval.physical.operators.ValueExpressionAsync - -@ExperimentalWindowFunctions -interface WindowFunctionAsync { - - val name: String - - /** - * The reset function should be called before enter a new partition ( including the first one). - * - * For now, a partition is represented by list>. - * We could potentially benefit from further abstraction of partition. - */ - fun reset(partition: List>) - - /** - * Process a row by outputting the result of the window function. - */ - suspend fun processRow(state: EvaluatorState, arguments: List, windowVarDecl: PartiqlPhysical.VarDecl) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/relation/Relation.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/relation/Relation.kt deleted file mode 100644 index eced0004ed..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/relation/Relation.kt +++ /dev/null @@ -1,90 +0,0 @@ -package org.partiql.lang.eval.relation - -import kotlin.coroutines.Continuation -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.coroutines.createCoroutine -import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED -import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn -import kotlin.coroutines.resume - -/** - * Builds a [RelationIterator] that yields after every step in evaluating a relational operator. - * - * This is inspired heavily by Kotlin's [sequence] but for [RelationIterator] instead of [Sequence]. - */ -fun relation( - seqType: RelationType, - block: suspend RelationScope.() -> Unit -): RelationIterator { - val iterator = RelationBuilderIterator(seqType, block) - iterator.nextStep = block.createCoroutine(receiver = iterator, completion = iterator) - return iterator -} - -@DslMarker -@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) -annotation class RelationDsl - -/** Defines functions within a block supplied to [relation]. */ -@RelationDsl -interface RelationScope { - /** Suspends the coroutine. Should be called after processing the current row of the relation. */ - suspend fun yield() - - /** Yields once for every row remaining in [relItr]. */ - suspend fun yieldAll(relItr: RelationIterator) -} - -private class RelationBuilderIterator( - override val relType: RelationType, - block: suspend RelationScope.() -> Unit -) : RelationScope, RelationIterator, Continuation { - var yielded = false - - var nextStep: Continuation? = block.createCoroutine(receiver = this, completion = this) - - override suspend fun yield() { - yielded = true - suspendCoroutineUninterceptedOrReturn { c -> - nextStep = c - COROUTINE_SUSPENDED - } - } - - override suspend fun yieldAll(relItr: RelationIterator) { - while (relItr.nextRow()) { - yield() - } - } - - override fun nextRow(): Boolean { - // if nextStep is null it means we've reached the end of the relation, but nextRow() was called again - // for some reason. This probably indicates a bug since we should not in general be attempting to - // read a `RelationIterator` after it has exhausted. - if (nextStep == null) { - error( - "Relation was previously exhausted. " + - "Please don't call nextRow() again after it returns false the first time." - ) - } - val step = nextStep!! - nextStep = null - step.resume(Unit) - - return if (yielded) { - yielded = false - true - } else { - false - } - } - - // Completion continuation implementation - override fun resumeWith(result: Result) { - result.getOrThrow() // just rethrow exception if it is there - } - - override val context: CoroutineContext - get() = EmptyCoroutineContext -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/relation/RelationIterator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/relation/RelationIterator.kt deleted file mode 100644 index 17824d6a04..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/relation/RelationIterator.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.partiql.lang.eval.relation - -import org.partiql.lang.domains.PartiqlPhysical.Expr.BindingsToValues -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.physical.EvaluatorState - -enum class RelationType { BAG, LIST } - -/** - * Represents an iterator that is returned by a relational operator during evaluation. - * - * This is a "faux" iterator in a sense, because it doesn't provide direct access to a current element. - * - * When initially created, the [RelationIterator] is positioned "before" the first element. [nextRow] should be called - * to advance the iterator to the first row. - * - * We do not use [Iterator] for this purpose because it is not a natural fit. There are two reasons: - * - * 1. [Iterator.next] returns the current element, but this isn't actually an iterator over a collection. Instead, - * execution of [nextRow] may have a side effect of populating value(s) in the current [EvaluatorState.registers] - * array. Bridge operators such as [BindingsToValues] are responsible for extracting current values from - * [EvaluatorState.registers] and converting them to the appropriate container [ExprValue]s. In this sense, - * PartiQL's "relations" are actually logical collections of variable bindings. This differs from how the term - * is defined by [E. F. Codd's relational algebra](https://en.wikipedia.org/wiki/Relational_algebra). - * 2. [Iterator.hasNext] would require knowing if additional rows remain after the current row, but in a few cases - * including filters and joins that requires advancing through possibly all remaining rows to see if any remaining row - * matches the predicate. This is awkward to implement and would force eager evaluation of the [Iterator]. - */ -interface RelationIterator { - val relType: RelationType - - /** - * Advances the iterator to the next row. - * - * Returns true to indicate that the next row was found and that [EvaluatorState.registers] have been updated for - * the current row. False if there are no more rows. - */ - fun nextRow(): Boolean -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/time/Time.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/time/Time.kt deleted file mode 100644 index ee35d4b082..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/time/Time.kt +++ /dev/null @@ -1,212 +0,0 @@ -package org.partiql.lang.eval.time - -import com.amazon.ion.IonStruct -import com.amazon.ion.IonSystem -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.TIME_ANNOTATION -import org.partiql.lang.eval.err -import org.partiql.lang.util.getOffsetHHmm -import org.partiql.lang.util.propertyValueMapOf -import java.math.BigDecimal -import java.math.RoundingMode -import java.time.DateTimeException -import java.time.LocalTime -import java.time.OffsetTime -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter -import java.time.temporal.ChronoField -import kotlin.math.min - -// Constants related to the TIME - -internal const val HOURS_PER_DAY = 24 -internal const val MINUTES_PER_HOUR = 60 -internal const val SECONDS_PER_MINUTE = 60 -internal const val SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR -internal const val NANOS_PER_SECOND = 1000000000 -internal const val MAX_PRECISION_FOR_TIME = 9 - -/** - * Wrapper class representing the run time instance of TIME in PartiQL. - * - `TIME [(p)] HH:MM:ss[.ddd][+|-HH:MM]` PartiQL statement creates a run-time instance of this class with [zoneOffset] as null. - * - `TIME WITH TIME ZONE [(p)] HH:MM:ss[.ddd][+|-HH:MM]` PartiQL statement creates a run-time instance of this class with both - * [localTime] and [zoneOffset] defined. - * - * @param localTime Represents the time component irrespective of the [ZoneOffset]. - * @param zoneOffset Represents the time zone of the TIME instance. - * If the [zoneOffset] is null, the [Time] instance has no defined time zone. - * @param precision Represents the number of significant digits in fractional part of the second's value of the TIME instance. - * The fractional part of the second will be rounded to the specified precision. - * For eg, `TIME (3) 23:46:58.1267` will be stored in this instance with [localTime] as `23:46:58.127000000` along with preserving the - * [precision] value as 3. - * Note that the [LocalTime] always stores the fractional part of the second in nanoseconds. - * It is up to the application developers to make use of the preserved [precision] value as need be. - */ -data class Time private constructor(val localTime: LocalTime, val precision: Int, val zoneOffset: ZoneOffset? = null) { - - init { - // Validate that the precision value is between 0 and 9 inclusive. - if (precision < 0 || precision > MAX_PRECISION_FOR_TIME) { - err( - message = "Specified precision for TIME should be a non-negative integer between 0 and 9 inclusive", - errorCode = ErrorCode.EVALUATOR_INVALID_PRECISION_FOR_TIME, - errorContext = propertyValueMapOf(), - internal = false - ) - } - } - - companion object { - - private const val LESS = -1 - private const val MORE = 1 - - /** Returns an instance of [Time] for the given hour, minute, second, precision and tz_minutes. - * @param hour the hour of a day of 24 hours to represent, from 0 to 23 - * @param minute the minute of hour of 60 minutes to represent, from 0 to 59 - * @param second the second of minute of 60 seconds to represent, from 0 to 59 - * @param nano the nano of second to represent, from 0 to 999,999,999. - * @param precision the number of desired significant digits in the fractional part of the second's value. - * If the precision is less than 9, the fractional part of the second will be rounded to the precision and [nano] will store the rounded value. - * For e.g., if [nano] is 126700000 and the [precision] is 3, the [nano] will be rounded to 127000000. - * Note that the [nano] will still store the value in nanoseconds (0's padded after the desired precision digits), - * however the [Time] instance will preserve the [precision] thereby preserving the entire original value. - * The valid values for precision are between 0 and 9 inclusive. - * @param tz_minutes the minutes of the UTC time-zone offset, from -1080 to 1080. - * If [tz_minutes] is null then the timezone offset is not defined. - * @return TimeExprValue - * @throws EvaluationException if the value of any field is out of range - */ - @JvmStatic - @JvmOverloads - fun of(hour: Int, minute: Int, second: Int, nano: Int, precision: Int, tz_minutes: Int? = null): Time { - - // Validates the range of values for all the parameters. This part may throw a DateTimeException - try { - ChronoField.HOUR_OF_DAY.checkValidValue(hour.toLong()) - ChronoField.MINUTE_OF_HOUR.checkValidValue(minute.toLong()) - ChronoField.SECOND_OF_MINUTE.checkValidValue(second.toLong()) - ChronoField.NANO_OF_SECOND.checkValidValue(nano.toLong()) - tz_minutes?.let { - ChronoField.OFFSET_SECONDS.checkValidIntValue((it * SECONDS_PER_MINUTE).toLong()) - } - } catch (dte: DateTimeException) { - throw EvaluationException(dte, ErrorCode.EVALUATOR_TIME_FIELD_OUT_OF_RANGE, propertyValueMapOf(), false) - } - - // Round nanoseconds to the given precision. - val nanoWithPrecision = when (precision) { - MAX_PRECISION_FOR_TIME -> nano - else -> ((nano.toBigDecimal().divide(NANOS_PER_SECOND.toBigDecimal())).setScale(precision, RoundingMode.HALF_EVEN).multiply(NANOS_PER_SECOND.toBigDecimal())).toInt() - } - // If the nanos are added to form up a whole second because of the specified precision, carry over the second all up to the hour - // and use the mod values of all the new fields to fit in the valid range. - val newNano = nanoWithPrecision % NANOS_PER_SECOND - val newSecond = second + (nanoWithPrecision / NANOS_PER_SECOND) - val newMinute = minute + (newSecond / SECONDS_PER_MINUTE) - val newHour = (hour + (newMinute / MINUTES_PER_HOUR)) - // Since all the values are checked for range, this call will not throw a DateTimeException. - val localTime = LocalTime.of( - newHour % HOURS_PER_DAY, - newMinute % MINUTES_PER_HOUR, - newSecond % SECONDS_PER_MINUTE, - newNano - ) - val zoneOffset = tz_minutes?.let { - ZoneOffset.ofTotalSeconds(it * SECONDS_PER_MINUTE) - } - return Time(localTime, precision, zoneOffset) - } - - /** - * Returns an instance of [Time] for the given localTime, precision and zoneOffset. - * Precision is used to round up the fractional part of the second (nano field of localTime). - * The [Time] instance returned has the [LocalTime] rounded up to this precision. - */ - @JvmStatic - @JvmOverloads - fun of(localTime: LocalTime, precision: Int, zoneOffset: ZoneOffset? = null): Time { - return Time.of( - localTime.hour, localTime.minute, localTime.second, localTime.nano, precision, - zoneOffset?.totalSeconds?.div(SECONDS_PER_MINUTE) - ) - } - } - - /** - * Returns the [OffsetTime] representation of this value if a [ZoneOffset] is defined for this, otherwise returns null. - */ - val offsetTime - get(): OffsetTime? = zoneOffset?.let { - OffsetTime.of(localTime, it) - } - - /** - * Returns the TIMEZONE_HOUR for the [zoneOffset] of this instance. - */ - val timezoneHour - get(): Int? = zoneOffset?.totalSeconds?.div(SECONDS_PER_HOUR) - - /** - * Returns the TIMEZONE_HOUR for the [zoneOffset] of this instance. - */ - val timezoneMinute - get(): Int? = (zoneOffset?.totalSeconds?.div(SECONDS_PER_MINUTE))?.rem(SECONDS_PER_MINUTE) - - /** - * Returns the seconds along with the fractional part of the second's value. - */ - val secondsWithFractionalPart: BigDecimal - get() = (localTime.second.toBigDecimal() + localTime.nano.toBigDecimal().divide(NANOS_PER_SECOND.toBigDecimal())) - .setScale(precision, RoundingMode.HALF_EVEN) - - fun toIonValue(ion: IonSystem): IonStruct = - ion.newEmptyStruct().apply { - add("hour", ion.newInt(localTime.hour)) - add("minute", ion.newInt(localTime.minute)) - add("second", ion.newDecimal(secondsWithFractionalPart)) - add("timezone_hour", ion.newInt(timezoneHour)) - add("timezone_minute", ion.newInt(timezoneMinute)) - addTypeAnnotation(TIME_ANNOTATION) - } - - /** - + Generates a formatter pattern at run time depending on the precision value. - * This pattern is subject to change based on the java's [DateTimeFormatter]. [java doc](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) - * Check here if there are issues with the output format pattern. - */ - private fun formatterPattern(): String { - return "HH:mm:ss" + if (precision > 0) "." + "S".repeat(min(9, precision)) else "" - } - - override fun toString(): String = - localTime.format(DateTimeFormatter.ofPattern(formatterPattern())) + - (zoneOffset?.getOffsetHHmm() ?: "") - - /** - * Check if this instance is directly comparable to the other [Time] instance. - * The [Time] instances are directly comparable if [zoneOffset] is defined for both of them - * or it is not defined for both of them. - */ - fun isDirectlyComparableTo(other: Time): Boolean { - return (this.zoneOffset == null && other.zoneOffset == null) || - (this.zoneOffset != null && other.zoneOffset != null) - } - - /** - * Compares the TIME and TIME WITH TIME ZONE values according to the natural order. - * TIME (without time zone) comes before TIME (with time zone) in the natural order comparison. - */ - fun naturalOrderCompareTo(other: Time): Int { - return when { - // When the zone offsets are not null for both the operands, compare OffsetTime i.e. LocalTime with ZoneOffset - this.zoneOffset != null && other.zoneOffset != null -> this.offsetTime!!.compareTo(other.offsetTime) - // When the zone offsets are null for both the operands, compare just LocalTime - this.zoneOffset == null && other.zoneOffset == null -> this.localTime.compareTo(other.localTime) - // When one of the times is `time with time zone` and other is just `time` (without time zone), then they are incomparable. - this.zoneOffset == null && other.zoneOffset != null -> LESS - else -> MORE - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/time/TimeExtensions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/time/TimeExtensions.kt deleted file mode 100644 index c90c5acabb..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/time/TimeExtensions.kt +++ /dev/null @@ -1,62 +0,0 @@ -package org.partiql.lang.util - -import org.partiql.errors.ErrorCode -import org.partiql.lang.eval.time.SECONDS_PER_HOUR -import org.partiql.lang.eval.time.SECONDS_PER_MINUTE -import java.time.ZoneOffset -import kotlin.math.absoluteValue - -// These are used to validate the generic format of the time string. -// The more involved logic such as validating the time is done by LocalTime.parse or OffsetTime.parse -internal val timeWithoutTimeZoneRegex = Regex("\\d\\d:\\d\\d:\\d\\d(\\.\\d*)?") -internal val genericTimeRegex = Regex("\\d\\d:\\d\\d:\\d\\d(\\.\\d*)?([+|-]\\d\\d:\\d\\d)?") - -/** - * Regex pattern to match date strings of the format yyyy-MM-dd - */ -internal val DATE_PATTERN_REGEX = Regex("\\d\\d\\d\\d-\\d\\d-\\d\\d") - -/** - * Returns the string representation of the [ZoneOffset] in HH:mm format. - */ -internal fun ZoneOffset.getOffsetHHmm(): String = - (if (totalSeconds >= 0) "+" else "-") + - hour.absoluteValue.toString().padStart(2, '0') + - ":" + - minute.absoluteValue.toString().padStart(2, '0') - -/** - * Get time zone offset hour - */ -internal val ZoneOffset.hour: Int - get() = totalSeconds / SECONDS_PER_HOUR - -/** - * Get time zone offset minute - */ -internal val ZoneOffset.minute: Int - get() = (totalSeconds / SECONDS_PER_MINUTE) % SECONDS_PER_MINUTE - -/** - * Get time zone offset in total minutes - */ -internal val ZoneOffset.totalMinutes: Int - get() = totalSeconds / SECONDS_PER_MINUTE - -/** - * Calculates the precision of a time string based on the fractional component of the 'HH:MM:SS[.ddd....][+|-HH:MM]' format. - */ -internal fun getPrecisionFromTimeString(timeString: String): Int { - val matcher = genericTimeRegex.toPattern().matcher(timeString) - if (!matcher.find()) { - org.partiql.lang.eval.err( - "Time string does not match the format 'HH:MM:SS[.ddd....][+|-HH:MM]'", - ErrorCode.PARSE_INVALID_TIME_STRING, - propertyValueMapOf(), - false - ) - } - // Note that the [genericTimeRegex] has a group to extract the fractional part of the second. - val fraction = matcher.group(1)?.removePrefix(".") - return fraction?.length ?: 0 -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregateSupportVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregateSupportVisitorTransform.kt deleted file mode 100644 index a9ba72491b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregateSupportVisitorTransform.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.visitors - -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.metaContainerOf -import org.partiql.lang.ast.AggregateCallSiteListMeta -import org.partiql.lang.ast.AggregateRegisterIdMeta -import org.partiql.lang.domains.PartiqlAst - -/** - * Allocates registerIds to all aggregate call-sites, storing the allocated registerId in an instance of - * [AggregateRegisterIdMeta]. - * - * Also collects a list of all aggregate functions used in the `SELECT` of the current query (excluding subqueries) - * and stores them in an instance of [AggregateCallSiteListMeta] on the [PartiqlAst.Expr.Select] instance. - */ - -class AggregateSupportVisitorTransform : VisitorTransformBase() { - private val aggregateCallSites = ArrayList() - - /** - * Nests another [AggregateSupportVisitorTransform] within this transform to avoid operating on - * [aggregateCallSites] of a nested `SELECT`. - */ - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - return AggregateSupportVisitorTransform().transformExprSelectEvaluationOrder(node) - } - - override fun transformExprCallAgg(node: PartiqlAst.Expr.CallAgg): PartiqlAst.Expr { - val transformedCallAgg = PartiqlAst.build { - callAgg_( - setq = node.setq, - funcName = node.funcName, - arg = transformExpr(node.arg), - metas = transformMetas(node.metas) + metaContainerOf(AggregateRegisterIdMeta.TAG to AggregateRegisterIdMeta(aggregateCallSites.size)) - ) - } - aggregateCallSites.add(transformedCallAgg) - return transformedCallAgg - } - - /** - * Applies a new instance of [AggregateSupportVisitorTransform] to [PartiqlAst.Projection.ProjectValue] nodes so - * that a different instance of [aggregateCallSites] is used. - */ - override fun transformProjectionProjectValue_value(node: PartiqlAst.Projection.ProjectValue): PartiqlAst.Expr = - AggregateSupportVisitorTransform().transformExpr(node.value) - - override fun transformExprSelect_metas(node: PartiqlAst.Expr.Select): MetaContainer = - transformMetas(node.metas) + metaContainerOf(AggregateCallSiteListMeta.TAG to AggregateCallSiteListMeta(aggregateCallSites.toList())) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregationVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregationVisitorTransform.kt deleted file mode 100644 index 95354fb258..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregationVisitorTransform.kt +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.visitors - -import com.amazon.ionelement.api.emptyMetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.errors.Problem -import org.partiql.errors.Property -import org.partiql.errors.UNKNOWN_PROBLEM_LOCATION -import org.partiql.lang.ast.passes.SemanticException -import org.partiql.lang.ast.passes.SemanticProblemDetails -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.toBindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.extractColumnAlias -import org.partiql.lang.eval.sourceLocationMeta - -/** - * This VisitorTransform: - * - adds unique aliases to each group key - * - replaces group key references (ids) with their unique name - * - replaces group key references (ids) with the expression that they represent (if within an aggregation function) - * - handles scoping by keeping a context stack - * - converts project star into the aggregation outputs (group keys and group as) - * - throws errors on projection items that reference variables that are not part of the aggregation operator output - * - * This VisitorTransform is specifically used by the planner implementation. - * - * Turns: - * - * ``` - * SELECT - * k AS group_key, - * SUM(k) AS the_sum, - * g AS the_input, - * ( - * SELECT k + SUM(k), SUM(h) - * FROM t2 - * GROUP BY t2.a AS h - * ) AS projection_query - * FROM t1 - * GROUP BY t1.a AS k, t1.b AS h - * GROUP AS g - * HAVING k + SUM(k) > 0 - * ORDER BY - * k + SUM(k) - * AND - * (SELECT k + SUM(k), SUM(h) FROM t2) - * ``` - * - * Into: - * - * ``` - * SELECT - * $__partiql__group_by_0_0 AS group_key, - * SUM(t1.a) AS the_sum, - * "g" AS the_input, - * ( - * SELECT $__partiql__group_by_0_0 + SUM($__partiql__group_by_0_0), SUM($__partiql__group_by_1_0) - * FROM t2 - * GROUP BY t2.a AS $__partiql__group_by_1_0 - * ) AS projection_query - * FROM t1 - * GROUP BY t1.a AS $__partiql__group_by_0_0, t1.b AS $__partiql__group_by_0_0 - * GROUP AS g - * HAVING $__partiql__group_by_0_0 + SUM(t1.a) > 0 - * ORDER BY - * $__partiql__group_by_0_0 + SUM(t1.a) - * AND - * (SELECT $__partiql__group_by_0_0 + SUM($__partiql__group_by_0_0), SUM($__partiql__group_by_0_1) FROM t2) - * ``` - * - */ -internal class AggregationVisitorTransform( - private val contextStack: MutableList = mutableListOf() -) : VisitorTransformBase() { - - private val itemTransform = GroupKeyReferenceTransformer(contextStack) - - companion object { - internal const val GROUP_PREFIX = "\$__partiql__group_by_" - internal const val GROUP_DELIMITER = "_" - internal fun uniqueAlias(level: Int, index: Int) = "$GROUP_PREFIX$level$GROUP_DELIMITER$index" - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - // Every transformExprSelect implicitly adds to the contextStack during the nested transformExprSelect_group. - // Therefore, after transforming the ExprSelect, we need to pop from the contextStack. - return super.transformExprSelectEvaluationOrder(node).also { contextStack.removeLast() } - } - - override fun transformExprSelect_group(node: PartiqlAst.Expr.Select): PartiqlAst.GroupBy? { - // Return with Empty Context if without Group - val containsAggregations = AggregationFinder().containsAggregations(node.project) - if (node.group == null) { - val context = VisitorContext(emptyList(), null, containsAggregations) - contextStack.add(context) - return null - } - - // Add Unique Aliases to Keys and Create GroupKeyInformation - val groupAsAlias = node.group!!.groupAsAlias?.text - val transformedKeys = mutableListOf() - val groupKeyInformation = node.group!!.keyList.keys.mapIndexed { index, key -> - val publicAlias = key.asAlias?.text ?: key.expr.extractColumnAlias(index) - val uniqueAlias = uniqueAlias(this.contextStack.size, index) - val represents = key.expr - val transformedKey = PartiqlAst.build { groupKey(transformExpr(key.expr), uniqueAlias, key.metas) } - transformedKeys.add(transformedKey) - val isPublicAliasUserDefined = key.asAlias != null - GroupKeyInformation(groupKey = key, publicAlias = publicAlias, uniqueAlias = uniqueAlias, represents = represents, isPublicAliasUserDefined = isPublicAliasUserDefined) - } - - // Add to Context Stack and return modified Group Keys - val hasAggregateOperator = containsAggregations || groupKeyInformation.isNotEmpty() || groupAsAlias != null - val ctx = VisitorContext(groupKeyInformation, groupAsAlias, hasAggregateOperator) - contextStack.add(ctx) - return PartiqlAst.build { - groupBy( - strategy = transformGroupBy_strategy(node.group!!), - keyList = groupKeyList(transformedKeys), - groupAsAlias = groupAsAlias, - metas = node.group!!.metas - ) - } - } - - override fun transformExprSelect_having(node: PartiqlAst.Expr.Select): PartiqlAst.Expr? = node.having?.let { having -> - itemTransform.transformExpr(having) - } - - override fun transformSortSpec_expr(node: PartiqlAst.SortSpec) = itemTransform.transformSortSpec_expr(node) - - /** - * Replaces [node] with [PartiqlAst.Projection.ProjectList] IF there are Group Keys and/or a Group Alias - */ - override fun transformProjectionProjectStar(node: PartiqlAst.Projection.ProjectStar): PartiqlAst.Projection { - if (contextStack.last().groupKeys.isNotEmpty() || contextStack.last().groupAsAlias != null) { - return PartiqlAst.build { - val projectionItems = contextStack.last().groupKeys.map { key -> - projectExpr(id(key.uniqueAlias, caseSensitive(), unqualified()), key.publicAlias) - }.toMutableList() - - contextStack.last().groupAsAlias?.let { alias -> - val item = projectExpr(id(alias, caseSensitive(), unqualified()), alias) - projectionItems.add(item) - } - projectList(projectionItems) - } - } - return super.transformProjectionProjectStar(node) - } - - override fun transformProjectionProjectValue(node: PartiqlAst.Projection.ProjectValue): PartiqlAst.Projection = - PartiqlAst.build { - projectValue( - value = itemTransform.transformExpr(node.value), - metas = node.metas - ) - } - - override fun transformProjectionProjectList(node: PartiqlAst.Projection.ProjectList): PartiqlAst.Projection = - PartiqlAst.build { - projectList( - projectItems = node.projectItems.map { item -> - when (item) { - is PartiqlAst.ProjectItem.ProjectExpr -> { - val projectionAlias = item.asAlias ?: throw SemanticException( - err = Problem( - (item.metas.sourceLocationMeta?.toProblemLocation() ?: UNKNOWN_PROBLEM_LOCATION), - details = SemanticProblemDetails.MissingAlias - ) - ) - - projectExpr_( - expr = itemTransform.transformExpr(item.expr), - asAlias = projectionAlias - ) - } - else -> item - } - }, - metas = node.metas - ) - } - - /** - * Recursively searches through a [PartiqlAst.Projection] to find [PartiqlAst.Expr.CallAgg]'s, but does NOT recurse - * into [PartiqlAst.Expr.Select]. Designed to be called directly using [containsAggregations]. - */ - private class AggregationFinder : PartiqlAst.Visitor() { - - var hasAggregations: Boolean = false - - fun containsAggregations(node: PartiqlAst.Projection): Boolean { - this.walkProjection(node) - return this.hasAggregations.also { this.hasAggregations = false } - } - - override fun visitExprCallAgg(node: PartiqlAst.Expr.CallAgg) { - hasAggregations = true - } - - override fun walkExprSelect(node: PartiqlAst.Expr.Select) { return } - } - - /** - * This VisitorTransform: - * 1. transforms group key references (ids) to the unique name given to the group key - * 2. transforms group key references (expressions) to the unique name given to the group key (if no explicit - * user-defined alias is given. - * 3. transforms group key references (ids) to the expression that they group key represents (if the id is within - * a [PartiqlAst.Expr.CallAgg] that is in scope of the current aggregate operator) - * 4. throws exceptions when identifiers are seen within the projection list, having, or order by clauses that do - * not reference Group Keys (when the aggregate operator is defined) - * - * This also handles scoping by using the [ctxStack]. This class is designed to be used directly on the - * [PartiqlAst.Projection], the [PartiqlAst.Expr.Select.having], and the [PartiqlAst.OrderBy]. - */ - private class GroupKeyReferenceTransformer( - private val ctxStack: MutableList, - private val isWithinCallAgg: Boolean = false - ) : VisitorTransformBase() { - - override fun transformExprId(node: PartiqlAst.Expr.Id): PartiqlAst.Expr = when (this.isWithinCallAgg) { - true -> getReplacementForIdInAggregationFunction(node) - false -> getReplacementForIdOutsideOfAggregationFunction(node) - } - - override fun transformExprCallAgg(node: PartiqlAst.Expr.CallAgg): PartiqlAst.Expr = PartiqlAst.build { - val functionArgTransformer = GroupKeyReferenceTransformer(ctxStack, true) - callAgg_( - setq = transformSetQuantifier(node.setq), - funcName = node.funcName, - arg = functionArgTransformer.transformExprCallAgg_arg(node), - metas = transformMetas(node.metas) - ) - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - return AggregationVisitorTransform(ctxStack).transformExprSelect(node) - } - - override fun transformExpr(node: PartiqlAst.Expr): PartiqlAst.Expr { - return getReplacementExpression(node) ?: super.transformExpr(node) - } - - private fun getReplacementExpression(node: PartiqlAst.Expr): PartiqlAst.Expr? { - if (this.isWithinCallAgg) { return null } - - val ctxStackIter = ctxStack.listIterator(ctxStack.size) - while (ctxStackIter.hasPrevious()) { - val ctx = ctxStackIter.previous() - ctx.groupKeys.firstOrNull { key -> - key.isPublicAliasUserDefined.not() && key.represents == node - }?.let { key -> - return PartiqlAst.build { - id(key.uniqueAlias, caseSensitive(), unqualified(), emptyMetaContainer()) - } - } - } - return null - } - - /** - * IDs outside of aggregation functions should always be replaced with the Group Key unique aliases. If no - * replacement is found, we throw an EvaluationException. - */ - private fun getReplacementForIdOutsideOfAggregationFunction(node: PartiqlAst.Expr.Id): PartiqlAst.Expr { - val ctxStackIter = ctxStack.listIterator(ctxStack.size) - while (ctxStackIter.hasPrevious()) { - val ctx = ctxStackIter.previous() - getReplacementInNormalContext(node, ctx)?.let { replacementId -> return replacementId } - } - - when (ctxStack.last().hasLogicalAggregate) { - false -> return node - true -> throw EvaluationException( - "Variable not in GROUP BY or aggregation function: ${node.name.text}", - ErrorCode.EVALUATOR_VARIABLE_NOT_INCLUDED_IN_GROUP_BY, - errorContextFrom(node.metas).also { - it[Property.BINDING_NAME] = node.name.text - }, - internal = false - ) - } - } - - /** - * Called from within a CallAgg -- and therefore, all IDs should be replaced with the current context's - * [GroupKeyInformation.represents]. If not found, search for replacements within "normal" parent contexts. - */ - private fun getReplacementForIdInAggregationFunction(node: PartiqlAst.Expr.Id): PartiqlAst.Expr { - getReplacementInAggregationContext(node, ctxStack.last())?.let { replacement -> return replacement } - - val ctxStackIter = ctxStack.listIterator(ctxStack.size) - while (ctxStackIter.hasPrevious()) { - val ctx = ctxStackIter.previous() - getReplacementInNormalContext(node, ctx)?.let { replacementId -> return replacementId } - } - return node - } - - /** - * Gets replacement ID (the alias of the Group Key or Group Alias (ID)) - */ - private fun getReplacementInNormalContext(node: PartiqlAst.Expr.Id, ctx: VisitorContext): PartiqlAst.Expr.Id? { - val bindingName = BindingName(node.name.text, node.case.toBindingCase()) - val replacementKey = ctx.groupKeys.firstOrNull { key -> - bindingName.isEquivalentTo(key.publicAlias) - }?.let { key -> PartiqlAst.build { id(key.uniqueAlias, caseSensitive(), node.qualifier) } } - - return when { - replacementKey != null -> replacementKey - bindingName.isEquivalentTo(ctx.groupAsAlias) -> PartiqlAst.build { id(node.name.text, caseSensitive(), unqualified()) } - else -> null - } - } - - /** - * Gets replacement Expr (what the Group Key represents, or the Group As Alias) - */ - private fun getReplacementInAggregationContext(node: PartiqlAst.Expr.Id, ctx: VisitorContext): PartiqlAst.Expr? { - val bindingName = BindingName(node.name.text, node.case.toBindingCase()) - val replacementExpression = ctx.groupKeys.firstOrNull { key -> - bindingName.isEquivalentTo(key.publicAlias) - }?.represents - - return when { - replacementExpression != null -> replacementExpression - bindingName.isEquivalentTo(ctx.groupAsAlias) -> PartiqlAst.build { id(node.name.text, caseSensitive(), unqualified()) } - else -> null - } - } - } - - internal data class VisitorContext( - val groupKeys: List, - val groupAsAlias: String?, - val hasLogicalAggregate: Boolean - ) - - internal data class GroupKeyInformation( - val groupKey: PartiqlAst.GroupKey, - val represents: PartiqlAst.Expr, - val publicAlias: String, - val uniqueAlias: String, - val isPublicAliasUserDefined: Boolean - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/FromSourceAliasVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/FromSourceAliasVisitorTransform.kt deleted file mode 100644 index dd3b61b6b1..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/FromSourceAliasVisitorTransform.kt +++ /dev/null @@ -1,61 +0,0 @@ -package org.partiql.lang.eval.visitors - -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.extractSourceLocation -import org.partiql.lang.eval.extractColumnAlias -import org.partiql.pig.runtime.SymbolPrimitive - -/** - * Assigns aliases to any unspecified [PartiqlAst.FromSource.Scan]/[PartiqlAst.FromSource.Unpivot] that does not - * already have one. - * - * For example: `SELECT * FROM foo` gets transformed into `SELECT * from foo as foo`. - * Path expressions: `SELECT * FROM foo.bar.bat` gets transformed into `SELECT * from foo.bar.bat as bat` - * - * If provided with a query that has all of the from source aliases already specified, an exact clone is returned. - */ -class FromSourceAliasVisitorTransform : VisitorTransformBase() { - // When this visitor reaches a top-level FromSource, it transforms it with a fresh instance - // of the helper visitor below. - // (A FromSource is top-level if it does not occur inside another FromSource, which is possible because - // FromSource is recursive, to accommodate complex joins in the AST.) - // Currently, there are two places where a top-level FromSource can occur: in a SELECT and in a DML statement. - - override fun transformExprSelect_from(node: PartiqlAst.Expr.Select): PartiqlAst.FromSource { - val newFrom = super.transformExprSelect_from(node) // this applies the transformation to any eligible subqueries - return InnerFromSourceAliasVisitorTransform().transformFromSource(newFrom) - } - - override fun transformStatementDml_from(node: PartiqlAst.Statement.Dml): PartiqlAst.FromSource? { - val newFrom = super.transformStatementDml_from(node) - return newFrom?.let { InnerFromSourceAliasVisitorTransform().transformFromSource(it) } - } - - /** The helper visitor is responsible for traversing the recursive structure of one top-level FromSource - * and creating aliases in it. - * Only the [transformFromSource] method is intended to be useful as an entry point. - * The visitor is stateful, maintaining a counter for synthetic aliases, - * so a separate instance is needed for each top level FromSource. - */ - private class InnerFromSourceAliasVisitorTransform : VisitorTransformBase() { - private var fromSourceCounter = 0 - - override fun transformFromSourceScan_asAlias(node: PartiqlAst.FromSource.Scan): SymbolPrimitive { - val thisFromSourceIndex = fromSourceCounter++ - return node.asAlias - ?: SymbolPrimitive(node.expr.extractColumnAlias(thisFromSourceIndex), node.extractSourceLocation()) - } - - override fun transformFromSourceUnpivot_asAlias(node: PartiqlAst.FromSource.Unpivot): SymbolPrimitive { - val thisFromSourceIndex = fromSourceCounter++ - return node.asAlias - ?: SymbolPrimitive(node.expr.extractColumnAlias(thisFromSourceIndex), node.extractSourceLocation()) - } - - // Do not traverse into subexpressions of a [FromSource]. - // All relevant subqueries are reached by the host [FromSourceAliasVisitorTransform]. - override fun transformExpr(node: PartiqlAst.Expr): PartiqlAst.Expr { - return node - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/GroupByItemAliasVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/GroupByItemAliasVisitorTransform.kt deleted file mode 100644 index b1c4c4041e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/GroupByItemAliasVisitorTransform.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.visitors - -import com.amazon.ionelement.api.metaContainerOf -import org.partiql.lang.ast.IsSyntheticNameMeta -import org.partiql.lang.ast.UniqueNameMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.eval.extractColumnAlias -import org.partiql.pig.runtime.SymbolPrimitive - -/** - * Pre-calculates [PartiqlAst.GroupBy] aliases, while not changing any that were previously specified, for example: - * - * `SELECT * FROM a GROUP BY a.b AS foo, a.c, 2 + 3` becomes - * `SELECT * FROM a GROUP BY a.b AS foo, a.c AS c, 2 + 3 as _3`. - * - * Also adds [UniqueNameMeta] to [PartiqlAst.GroupBy.keyList]. In order for the generated unique names to be correct, - * this transform must be presented with only the top-most node in a query. - * - * If provided with a query with all of the group by item aliases already specified, an exact clone is returned. - */ -class GroupByItemAliasVisitorTransform(var nestLevel: Int = 0) : VisitorTransformBase() { - - override fun transformGroupBy(node: PartiqlAst.GroupBy): PartiqlAst.GroupBy { - return PartiqlAst.build { - groupBy_( - strategy = node.strategy, - keyList = PartiqlAst.GroupKeyList( - node.keyList.keys.mapIndexed { index, it -> - val aliasText = it.asAlias?.text ?: it.expr.extractColumnAlias(index) - var metas = it.expr.metas + metaContainerOf( - UniqueNameMeta.TAG to UniqueNameMeta("\$__partiql__group_by_${nestLevel}_item_$index") - ) - - if (it.asAlias == null) { - metas = metas + metaContainerOf(IsSyntheticNameMeta.TAG to IsSyntheticNameMeta.instance) - } - val alias = SymbolPrimitive(aliasText, metas) - - groupKey_(transformExpr(it.expr), alias, alias.metas) - }, - node.keyList.metas - ), - groupAsAlias = node.groupAsAlias?.let { transformSymbolPrimitive(it) }, - metas = node.metas - ) - } - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - nestLevel++ - return super.transformExprSelect(node).also { nestLevel-- } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/GroupByPathExpressionVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/GroupByPathExpressionVisitorTransform.kt deleted file mode 100644 index a42ec920f8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/GroupByPathExpressionVisitorTransform.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.visitors - -import com.amazon.ionelement.api.metaContainerOf -import org.partiql.errors.ErrorCode -import org.partiql.lang.ast.IsSyntheticNameMeta -import org.partiql.lang.ast.UniqueNameMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.eval.errNoContext - -/** - * This transform must execute after [GroupByItemAliasVisitorTransform] and [FromSourceAliasVisitorTransform]. - */ -class GroupByPathExpressionVisitorTransform( - parentSubstitutions: Map = mapOf() -) : - SubstitutionVisitorTransform(parentSubstitutions) { - - companion object { - /** - * Determines if [gbi] is an expression of the type that should be replaced elsewhere in the query. - * - * As we support querying nested data, we are not just limited to SQL-92 compatibility (that have - * path expressions with single component, i.e. `foo.bar`) but also replace path expressions that - * have more than one component, i.e. `f.bar.bat`. - */ - fun canBeSubstituted(groupKey: PartiqlAst.GroupKey): Boolean { - val expr = groupKey.expr - val asName = groupKey.asAlias - - // (This is the reason this transform needs to execute after [GroupByItemAliasVisitorTransform].) - return when { - asName == null -> throw IllegalStateException("GroupByItem.asName must be specified for this transform to work") - !asName.metas.containsKey(IsSyntheticNameMeta.TAG) -> - // If this meta is not present it would indicate that the alias was explicitly specified, which is - // not allowed by SQL-92, so ignore. - false - - // Group by expressions other than paths aren't part of SQL-92 so ignore - expr !is PartiqlAst.Expr.Path -> false - else -> true - } - } - - /** - * Collects all of the aliases defined by the specified [FromSource] and its children. - * This is why this transform must occur after [FromSourceAliasVisitorTransform]. - */ - fun collectAliases(fromSource: PartiqlAst.FromSource): List = - when (fromSource) { - is PartiqlAst.FromSource.Scan -> - listOf( - fromSource.asAlias?.text - ?: errNoContext( - "FromSource.asAlias.text must be specified for this transform to work", - errorCode = ErrorCode.SEMANTIC_MISSING_AS_NAME, - internal = true - ) - ) - - is PartiqlAst.FromSource.Join -> - collectAliases(fromSource.left) + collectAliases(fromSource.right) - - is PartiqlAst.FromSource.Unpivot -> - listOfNotNull(fromSource.asAlias?.text, fromSource.atAlias?.text) - } - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - val fromSourceAliases = collectAliases(node.from) - - // These are substitutions for path expressions that do not contain references to shadowed variables - val unshadowedSubstitutions = getSubstitutionsExceptFor(fromSourceAliases) - - // A transformer for the above - val unshadowedTransformer = GroupByPathExpressionVisitorTransform(unshadowedSubstitutions) - - // These are the substitutions originating from the GROUP BY clause of the current [Select] node. - val currentSubstitutions = getSubstitutionsForSelect(node) - - // A transformer for both of the sets of the substitutions defined above. - val currentAndUnshadowedTransformer = GroupByPathExpressionVisitorTransform( - unshadowedSubstitutions + currentSubstitutions - ) - - // Now actually transform the query using the appropriate transformer for each of various clauses of the - // SELECT statement. - - val projection = currentAndUnshadowedTransformer.transformExprSelect_project(node) - - // The scope of the expressions in the FROM clause is the same as that of the parent scope. - val exclude = unshadowedTransformer.transformExprSelect_excludeClause(node) - val from = this.transformExprSelect_from(node) - val fromLet = unshadowedTransformer.transformExprSelect_fromLet(node) - val where = unshadowedTransformer.transformExprSelect_where(node) - val groupBy = unshadowedTransformer.transformExprSelect_group(node) - val having = currentAndUnshadowedTransformer.transformExprSelect_having(node) - val order = currentAndUnshadowedTransformer.transformExprSelect_order(node) - val offset = unshadowedTransformer.transformExprSelect_offset(node) - val limit = unshadowedTransformer.transformExprSelect_limit(node) - val metas = unshadowedTransformer.transformExprSelect_metas(node) - - return PartiqlAst.build { - PartiqlAst.Expr.Select( - setq = node.setq, - project = projection, - excludeClause = exclude, - from = from, - fromLet = fromLet, - where = where, - group = groupBy, - having = having, - order = order, - offset = offset, - limit = limit, - metas = metas - ) - } - } - - private fun getSubstitutionsForSelect(selectExpr: PartiqlAst.Expr.Select): Map { - return (selectExpr.group?.keyList?.keys ?: listOf()) - .asSequence() - .filter { groupKey -> canBeSubstituted(groupKey) } - .map { groupKey -> - val uniqueIdentifierMeta = groupKey.asAlias?.metas?.get(UniqueNameMeta.TAG) as UniqueNameMeta - SubstitutionPair( - groupKey.expr, - PartiqlAst.build { - id( - name = groupKey.asAlias!!.text, - case = caseSensitive(), - qualifier = unqualified(), - metas = groupKey.expr.metas + metaContainerOf(UniqueNameMeta.TAG to uniqueIdentifierMeta) - ) - } - ) - }.associateBy { it.target } - } - - private fun getSubstitutionsExceptFor(fromSourceAliases: List): Map { - return super.substitutions.values.filter { - val targetRootVarRef = (it.target as? PartiqlAst.Expr.Path)?.root as? PartiqlAst.Expr.Id - when (targetRootVarRef) { - null -> true - else -> { - val ignoreCase = targetRootVarRef.case is PartiqlAst.CaseSensitivity.CaseInsensitive - fromSourceAliases.all { alias -> - when (targetRootVarRef) { - null -> true // this branch should never execute but we should handle it if it does. - else -> targetRootVarRef.name.text.compareTo(alias, ignoreCase) != 0 - } - } - } - } - }.associateBy { it.target } - } - - // do not transform CallAgg nodes. - override fun transformExprCallAgg(node: PartiqlAst.Expr.CallAgg): PartiqlAst.Expr = node -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/OrderBySortSpecVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/OrderBySortSpecVisitorTransform.kt deleted file mode 100644 index 71f2686fed..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/OrderBySortSpecVisitorTransform.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.visitors - -import org.partiql.lang.ast.IsTransformedOrderByAliasMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.metaContainerOf -import org.partiql.pig.runtime.SymbolPrimitive - -/** - * A [PartiqlAst.VisitorTransform] to replace the [PartiqlAst.SortSpec] of a [PartiqlAst.OrderBy] with a reference to - * a [PartiqlAst.ProjectItem]'s [PartiqlAst.Expr] if an alias is provided. Also utilizes [IsTransformedOrderByAliasMeta] - * to enforce idempotency (delivering the same result through multiple passes). - * - * Turns: - * - * ```SELECT a + 1 AS b FROM c ORDER BY b``` - * - * Into: - * - * ```SELECT a + 1 AS b FROM c ORDER BY a + 1``` - */ -internal class OrderBySortSpecVisitorTransform : VisitorTransformBase() { - - private val projectionAliases: MutableMap = mutableMapOf() - - /** - * Nests itself to ensure ORDER BYs don't have access to the same [projectionAliases] - */ - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - return OrderBySortSpecVisitorTransform().transformExprSelectEvaluationOrder(node) - } - - /** - * Uses default transform and adds the alias to the [projectionAliases] map - */ - override fun transformProjectItemProjectExpr_asAlias(node: PartiqlAst.ProjectItem.ProjectExpr): SymbolPrimitive? { - val transformedAlias = super.transformProjectItemProjectExpr_asAlias(node) - if (node.asAlias != null) { projectionAliases[node.asAlias!!.text] = node.expr } - return transformedAlias - } - - /** - * Uses the [OrderByAliasSupport] class to transform any encountered IDs in ORDER BY into the appropriate - * expression using the [projectionAliases] while ensuring idempotency via [IsTransformedOrderByAliasMeta] - */ - override fun transformSortSpec_expr(node: PartiqlAst.SortSpec): PartiqlAst.Expr { - val newExpr = when (node.expr.metas.containsKey(IsTransformedOrderByAliasMeta.TAG)) { - true -> super.transformSortSpec_expr(node) - false -> OrderByAliasSupport(projectionAliases).transformSortSpec_expr(node) - } - return newExpr.copy(metas = newExpr.metas + metaContainerOf(IsTransformedOrderByAliasMeta.instance)) - } - - /** - * A [PartiqlAst.VisitorTransform] that converts any found Expr.Id's into what it is mapped to in [aliases]. - */ - private class OrderByAliasSupport(val aliases: Map) : VisitorTransformBase() { - override fun transformExprId(node: PartiqlAst.Expr.Id): PartiqlAst.Expr { - val transformedExpr = super.transformExprId(node) - return when (node.case) { - is PartiqlAst.CaseSensitivity.CaseSensitive -> aliases[node.name.text] ?: transformedExpr - else -> aliases[node.name.text.lowercase()] ?: aliases[node.name.text.toUpperCase()] ?: transformedExpr - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/PartiqlAstSanityValidator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/PartiqlAstSanityValidator.kt deleted file mode 100644 index 32b93819f0..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/PartiqlAstSanityValidator.kt +++ /dev/null @@ -1,172 +0,0 @@ - -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.visitors - -import com.amazon.ionelement.api.IntElement -import com.amazon.ionelement.api.IntElementSize -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.TextElement -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.IsCountStarMeta -import org.partiql.lang.ast.passes.SemanticException -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.addSourceLocation -import org.partiql.lang.eval.CompileOptions -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.TypedOpBehavior -import org.partiql.lang.eval.err -import org.partiql.lang.eval.errorContextFrom -import org.partiql.pig.runtime.LongPrimitive - -/** - * Provides rules for basic AST sanity checks that should be performed before any attempt at further AST processing. - * This is provided as a distinct [PartiqlAst.Visitor] so that all other visitors may assume that the AST at least - * passed the checking performed here. - * - * Any exception thrown by this class should always be considered an indication of a bug in one of the following places: - * - * - [org.partiql.lang.syntax.SqlParser] - * - A visitor transform pass (internal or external) - * - */ -class PartiqlAstSanityValidator : PartiqlAst.Visitor() { - - private var compileOptions = CompileOptions.standard() - - fun validate(statement: PartiqlAst.Statement, compileOptions: CompileOptions = CompileOptions.standard()) { - this.compileOptions = compileOptions - this.walkStatement(statement) - } - - override fun visitExprLit(node: PartiqlAst.Expr.Lit) { - val ionValue = node.value - val metas = node.metas - if (node.value is IntElement && ionValue.integerSize == IntElementSize.BIG_INTEGER) { - throw EvaluationException( - message = "Int overflow or underflow at compile time", - errorCode = ErrorCode.SEMANTIC_LITERAL_INT_OVERFLOW, - errorContext = errorContextFrom(metas), - internal = false - ) - } - } - - private fun validateDecimalOrNumericType(scale: LongPrimitive?, precision: LongPrimitive?, metas: MetaContainer) { - if (scale != null && precision != null && compileOptions.typedOpBehavior == TypedOpBehavior.HONOR_PARAMETERS) { - if (precision.value <= 0L) { - err( - "Precision ${precision.value} should be a positive integer", - errorCode = ErrorCode.SEMANTIC_INVALID_DECIMAL_ARGUMENTS, - errorContext = errorContextFrom(metas), - internal = false - ) - } - if (scale.value !in 0..precision.value) { - err( - "Scale ${scale.value} should be between 0 and precision ${precision.value}", - errorCode = ErrorCode.SEMANTIC_INVALID_DECIMAL_ARGUMENTS, - errorContext = errorContextFrom(metas), - internal = false - ) - } - } - } - - override fun visitTypeDecimalType(node: PartiqlAst.Type.DecimalType) { - validateDecimalOrNumericType(node.scale, node.precision, node.metas) - } - - override fun visitTypeNumericType(node: PartiqlAst.Type.NumericType) { - validateDecimalOrNumericType(node.scale, node.precision, node.metas) - } - - override fun visitExprCallAgg(node: PartiqlAst.Expr.CallAgg) { - val setQuantifier = node.setq - val metas = node.metas - if (setQuantifier is PartiqlAst.SetQuantifier.Distinct && metas.containsKey(IsCountStarMeta.TAG)) { - err( - "COUNT(DISTINCT *) is not supported", - ErrorCode.EVALUATOR_COUNT_DISTINCT_STAR, - errorContextFrom(metas), - internal = false - ) - } - } - - override fun visitExprSelect(node: PartiqlAst.Expr.Select) { - val projection = node.project - val groupBy = node.group - val having = node.having - val metas = node.metas - - if (groupBy != null) { - if (groupBy.strategy is PartiqlAst.GroupingStrategy.GroupPartial) { - err( - "GROUP PARTIAL not supported yet", - ErrorCode.EVALUATOR_FEATURE_NOT_SUPPORTED_YET, - errorContextFrom(metas).also { - it[Property.FEATURE_NAME] = "GROUP PARTIAL" - }, - internal = false - ) - } - - when (projection) { - is PartiqlAst.Projection.ProjectPivot -> { - err( - "PIVOT with GROUP BY not supported yet", - ErrorCode.EVALUATOR_FEATURE_NOT_SUPPORTED_YET, - errorContextFrom(metas).also { - it[Property.FEATURE_NAME] = "PIVOT with GROUP BY" - }, - internal = false - ) - } - is PartiqlAst.Projection.ProjectValue, is PartiqlAst.Projection.ProjectList -> { - // use of group by with SELECT & SELECT VALUE is supported - } - } - } - - if ((groupBy == null || groupBy.keyList.keys.isEmpty()) && having != null) { - throw SemanticException( - "HAVING used without GROUP BY (or grouping expressions)", - ErrorCode.SEMANTIC_HAVING_USED_WITHOUT_GROUP_BY, - PropertyValueMap().addSourceLocation(metas) - ) - } - } - - override fun visitExprStruct(node: PartiqlAst.Expr.Struct) { - node.fields.forEach { field -> - if (field.first is PartiqlAst.Expr.Missing || (field.first is PartiqlAst.Expr.Lit && (field.first as PartiqlAst.Expr.Lit).value !is TextElement)) { - val type = when (val first = field.first) { - is PartiqlAst.Expr.Lit -> first.value.type.toString() - else -> "MISSING" - } - throw SemanticException( - "Found struct field to be of type $type", - ErrorCode.SEMANTIC_NON_TEXT_STRUCT_FIELD_KEY, - PropertyValueMap().addSourceLocation(field.first.metas).also { pvm -> - pvm[Property.ACTUAL_TYPE] = type - } - ) - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/PipelinedVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/PipelinedVisitorTransform.kt deleted file mode 100644 index be340c4a10..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/PipelinedVisitorTransform.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.partiql.lang.eval.visitors - -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.util.interruptibleFold - -/** - * A simple visitor transformer that provides a pipeline of transformers to be executed in sequential order. - * - * @param transformers visitor transforms to be executed - */ -class PipelinedVisitorTransform(vararg transformers: PartiqlAst.VisitorTransform) : PartiqlAst.VisitorTransform() { - private val transformerList = transformers.toList() - - override fun transformStatement(node: PartiqlAst.Statement): PartiqlAst.Statement = - transformerList.interruptibleFold(node) { - intermediateNode, transformer -> - transformer.transformStatement(intermediateNode) - } - - fun appendVisitorTransforms(vararg newVisitorTransforms: PartiqlAst.VisitorTransform) = - PipelinedVisitorTransform(*(transformerList + newVisitorTransforms.toList()).toTypedArray()) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SelectListItemAliasVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SelectListItemAliasVisitorTransform.kt deleted file mode 100644 index 3d8ade4bfe..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SelectListItemAliasVisitorTransform.kt +++ /dev/null @@ -1,66 +0,0 @@ -package org.partiql.lang.eval.visitors - -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.extractSourceLocation -import org.partiql.lang.eval.extractColumnAlias -import org.partiql.pig.runtime.SymbolPrimitive - -/** - * Specifies any previously unspecified select list item aliases. - * - * Turns: - * - * ``` - * SELECT - * foo, - * b.bat, - * baz + 1, - * zoo.* - * FROM bar AS b - * ``` - * - * Into: - * - * ``` - * SELECT - * foo AS foo, - * b.bat AS bat, - * baz + 1 AS _3, - * zoo.* - * FROM bar AS b - * ``` - * - * If provided with a query with all of the select list aliases are already specified, an exact clone is returned. - * - * ``` - */ -class SelectListItemAliasVisitorTransform : VisitorTransformBase() { - - override fun transformProjectionProjectList(node: PartiqlAst.Projection.ProjectList): PartiqlAst.Projection { - return PartiqlAst.build { - projectList( - projectItems = node.projectItems.mapIndexed { idx, it -> - when (it) { - is PartiqlAst.ProjectItem.ProjectExpr -> - when (it.asAlias) { - // Synthesize a column name if one was not specified in the query. - null -> projectExpr_( - expr = transformExpr(it.expr), - asAlias = SymbolPrimitive( - text = it.expr.extractColumnAlias(idx), - metas = node.extractSourceLocation() - ) - ) - else -> projectExpr_( - expr = transformExpr(it.expr), - asAlias = it.asAlias - ) - } - else -> it - } - }, - metas = node.metas - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SelectStarVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SelectStarVisitorTransform.kt deleted file mode 100644 index 312c7db650..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SelectStarVisitorTransform.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.visitors - -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.emptyMetaContainer -import org.partiql.errors.ErrorCode -import org.partiql.lang.ast.IsGroupAttributeReferenceMeta -import org.partiql.lang.ast.UniqueNameMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.metaContainerOf -import org.partiql.lang.eval.errNoContext - -/** Desugars `SELECT *` by, for example, - * transforming `SELECT * FROM A as x, B as y at i` - * to `SELECT x.*, y.*, i FROM A as x, B as y at i` - * and transforming `SELECT * FROM ... GROUP BY E as x, D as y GROUP as g` - * to `SELECT x, y, g FROM ... GROUP BY E as x, D as y GROUP as g` - * - * Requires that [FromSourceAliasVisitorTransform] and [GroupByItemAliasVisitorTransform] - * have already been applied. - */ -class SelectStarVisitorTransform : VisitorTransformBase() { - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - val transformedExpr = super.transformExprSelect(node) as PartiqlAst.Expr.Select - - val projection = transformedExpr.project - - // Check if SELECT * is being used. - if (projection is PartiqlAst.Projection.ProjectStar) { - when (transformedExpr.group) { - null -> { // No group by - val fromSourceAliases = extractAliases(transformedExpr.from) - - val newProjection = - PartiqlAst.build { - projectList( - fromSourceAliases.map { aliases -> - // We are concatenating 3 lists here - listOf(createProjectAll(aliases.asAlias)) + - (aliases.atAlias?.let { listOf(createProjectExpr(it)) } ?: emptyList()) + - (aliases.byAlias?.let { listOf(createProjectExpr(it)) } ?: emptyList()) - }.flatten(), - transformedExpr.metas - ) - } - return transformedExpr.copy(project = newProjection) - } - else -> { // With group by - val selectListItemsFromGroupBy = transformedExpr.group!!.keyList.keys.map { - val asName = it.asAlias - ?: errNoContext( - "GroupByItem has no AS-alias--GroupByItemAliasVisitorTransform must be executed before SelectStarVisitorTransform", - errorCode = ErrorCode.SEMANTIC_MISSING_AS_NAME, - internal = true - ) - - // We need to take the unique name of each grouping field key only because we need to handle - // the case when multiple grouping fields are assigned the same name (which is currently allowed) - val uniqueNameMeta = asName.metas[UniqueNameMeta.TAG] as? UniqueNameMeta? - ?: error("UniqueNameMeta not found--normally, this is added by GroupByItemAliasVisitorTransform") - - val metas = it.metas + metaContainerOf(IsGroupAttributeReferenceMeta.instance) - createProjectExpr(uniqueNameMeta.uniqueName, asName.text, metas) - } - - val groupNameItem = transformedExpr.group!!.groupAsAlias.let { - if (it != null) { - val metas = it.metas + metaContainerOf(IsGroupAttributeReferenceMeta.instance) - listOf(createProjectExpr(it.text, metas = metas)) - } else emptyList() - } - - val newProjection = PartiqlAst.build { projectList(selectListItemsFromGroupBy + groupNameItem, metas = transformMetas(projection.metas)) } - - return transformedExpr.copy(project = newProjection) - } - } - } - return transformedExpr - } - - private fun createProjectAll(name: String) = - PartiqlAst.build { - projectAll(id(name, caseSensitive(), unqualified(), emptyMetaContainer())) - } - - private fun createProjectExpr(variableName: String, asAlias: String = variableName, metas: MetaContainer = emptyMetaContainer()) = - PartiqlAst.build { - projectExpr(id(variableName, caseSensitive(), unqualified(), metas), asAlias) - } - - private class FromSourceAliases(val asAlias: String, val atAlias: String?, val byAlias: String?) - - /** Extracts all the FROM source/unpivot aliases without recursing into any nested queries */ - private fun extractAliases(fromSource: PartiqlAst.FromSource): List { - val aliases = mutableListOf() - val visitor = object : PartiqlAst.Visitor() { - override fun visitFromSourceScan(node: PartiqlAst.FromSource.Scan) { - aliases.add( - FromSourceAliases( - node.asAlias?.text - ?: error("FromSourceAliasVisitorTransform must be executed before SelectStarVisitorTransform"), - node.atAlias?.text, - node.byAlias?.text - ) - ) - } - - override fun visitFromSourceUnpivot(node: PartiqlAst.FromSource.Unpivot) { - aliases.add( - FromSourceAliases( - node.asAlias?.text - ?: error("FromSourceAliasVisitorTransform must be executed before SelectStarVisitorTransform"), - node.atAlias?.text, - node.byAlias?.text - ) - ) - } - - /** We do not want to recurse into the nested select query */ - override fun walkExprSelect(node: PartiqlAst.Expr.Select) { - return - } - } - visitor.walkFromSource(fromSource) - return aliases - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransform.kt deleted file mode 100644 index 77b2aacd68..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransform.kt +++ /dev/null @@ -1,1663 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - */ - -package org.partiql.lang.eval.visitors - -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.StringElement -import com.amazon.ionelement.api.TextElement -import com.amazon.ionelement.api.ionBool -import org.partiql.errors.Problem -import org.partiql.errors.ProblemHandler -import org.partiql.errors.ProblemSeverity -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.ast.StaticTypeMeta -import org.partiql.lang.ast.passes.SemanticException -import org.partiql.lang.ast.passes.SemanticProblemDetails -import org.partiql.lang.ast.passes.inference.cast -import org.partiql.lang.ast.passes.inference.filterNullMissing -import org.partiql.lang.ast.passes.inference.isNullOrMissing -import org.partiql.lang.ast.passes.inference.isNumeric -import org.partiql.lang.ast.passes.inference.isText -import org.partiql.lang.ast.passes.inference.isUnknown -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.staticType -import org.partiql.lang.domains.toBindingCase -import org.partiql.lang.errors.ProblemThrower -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.ExprValueType -import org.partiql.lang.eval.builtins.SCALAR_BUILTINS_DEFAULT -import org.partiql.lang.eval.delegate -import org.partiql.lang.eval.getStartingSourceLocationMeta -import org.partiql.lang.types.FunctionSignature -import org.partiql.lang.types.StaticTypeUtils.areStaticTypesComparable -import org.partiql.lang.types.StaticTypeUtils.getTypeDomain -import org.partiql.lang.types.StaticTypeUtils.isSubTypeOf -import org.partiql.lang.types.StaticTypeUtils.staticTypeFromExprValueType -import org.partiql.lang.types.TypedOpParameter -import org.partiql.lang.types.UnknownArguments -import org.partiql.lang.types.toTypedOpParameter -import org.partiql.lang.util.cartesianProduct -import org.partiql.types.AnyOfType -import org.partiql.types.AnyType -import org.partiql.types.BagType -import org.partiql.types.BoolType -import org.partiql.types.CollectionType -import org.partiql.types.DecimalType -import org.partiql.types.FloatType -import org.partiql.types.IntType -import org.partiql.types.ListType -import org.partiql.types.MissingType -import org.partiql.types.NullType -import org.partiql.types.NumberConstraint -import org.partiql.types.SexpType -import org.partiql.types.SingleType -import org.partiql.types.StaticType -import org.partiql.types.StringType -import org.partiql.types.StructType -import org.partiql.types.SymbolType - -/** - * A [PartiqlAst.VisitorTransform] that annotates nodes with their static type. - * - * Assumes [StaticTypeVisitorTransform] was run before this and all the implicit variables have been resolved. - * - * @param globalBindings The global bindings to the static environment. This is data catalog purely from a lookup - * perspective. - * @param customFunctionSignatures Custom user-defined function signatures that can be called by the query. - * @param customTypedOpParameters Mapping of custom type name to [TypedOpParameter] to be used for typed operators - * (i.e CAST/IS). - * @param problemHandler handles the semantic problems encountered through static type inference. Default handler will - * throw on the first [SemanticException] with [ProblemSeverity.ERROR]. - */ -internal class StaticTypeInferenceVisitorTransform( - globalBindings: Bindings, - customFunctionSignatures: List, - private val customTypedOpParameters: Map, - private val problemHandler: ProblemHandler = ProblemThrower() -) : PartiqlAst.VisitorTransform() { - - /** Used to allow certain binding lookups to occur directly in the global scope. */ - private val globalTypeEnv = wrapBindings(globalBindings, 0) - - /** Captures a [StaticType] and the depth at which it is bound. */ - private data class TypeAndDepth(val type: StaticType, val depth: Int) - - /** Defines the current scope search order--i.e. globals first when in a FROM source, lexical everywhere else. */ - private enum class ScopeSearchOrder { - LEXICAL, - GLOBALS_THEN_LEXICAL - } - - /** The built-in functions + the custom functions. */ - private val allFunctions: Map> = - (SCALAR_BUILTINS_DEFAULT.map { it.signature.name to it.signature } + customFunctionSignatures.map { it.name to it }) - .groupBy({ it.first }, { it.second }) - - /** - * @param parentEnv the enclosing bindings - * @param currentScopeDepth How deeply nested the current scope is. - * - 0 means we are in the global scope - * - 1 is the top-most statement with a `FROM` clause (i.e. select-from-where or DML operation), - * - Values > 1 are for each subsequent level of nested sub-query. - */ - private inner class VisitorTransform( - private val parentEnv: Bindings, - private val currentScopeDepth: Int - ) : VisitorTransformBase() { - - /** Specifies the current scope search order--default is LEXICAL. */ - private var scopeOrder = ScopeSearchOrder.LEXICAL - - private val localsMap = mutableMapOf() - - private var localsOnlyEnv = wrapBindings(Bindings.ofMap(localsMap), currentScopeDepth) - - // because of the mutability of the above reference, we need to encode the lookup as a thunk - private val currentEnv = Bindings.over { localsOnlyEnv[it] }.delegate(parentEnv) - - private fun PartiqlAst.Expr.withStaticType(type: StaticType) = - this.withMeta(StaticTypeMeta.TAG, StaticTypeMeta(type)) as PartiqlAst.Expr - - private fun PartiqlAst.PathStep.withStaticType(type: StaticType) = - this.withMeta(StaticTypeMeta.TAG, StaticTypeMeta(type)) as PartiqlAst.PathStep - - private fun PartiqlAst.Projection.withStaticType(type: StaticType) = - this.withMeta(StaticTypeMeta.TAG, StaticTypeMeta(type)) as PartiqlAst.Projection - - private fun List.getStaticType(): List = map { it.getStaticType() } - - private fun PartiqlAst.Expr.getStaticType(): StaticType = - this.metas.staticType?.type ?: error("No inferred type information found on PartiqlAst: $this") - - private fun MetaContainer.getSourceLocation(): SourceLocationMeta = - this[SourceLocationMeta.TAG] as SourceLocationMeta? ?: error("No source location found on PartiqlAst: $this") - - private fun handleDuplicateAliasesError(sourceLocationMeta: SourceLocationMeta) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocationMeta.toProblemLocation(), - details = SemanticProblemDetails.DuplicateAliasesInSelectListItem - ) - ) - } - - private fun handleNoSuchFunctionError(functionName: String, sourceLocationMeta: SourceLocationMeta) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocationMeta.toProblemLocation(), - details = SemanticProblemDetails.NoSuchFunction(functionName) - ) - ) - } - - private fun handleIncorrectNumberOfArgumentsToFunctionCallError( - functionName: String, - expectedArity: IntRange, - actualArgCount: Int, - sourceLocationMeta: SourceLocationMeta - ) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocationMeta.toProblemLocation(), - details = SemanticProblemDetails.IncorrectNumberOfArgumentsToFunctionCall( - functionName, - expectedArity, - actualArgCount - ) - ) - ) - } - - // TODO: https://github.com/partiql/partiql-lang-kotlin/issues/508 consider not working directly with strings for `op` - private fun handleIncompatibleDataTypesForOpError(actualTypes: List, op: String, sourceLocationMeta: SourceLocationMeta) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocationMeta.toProblemLocation(), - details = SemanticProblemDetails.IncompatibleDatatypesForOp( - actualTypes, - op - ) - ) - ) - } - - private fun handleIncompatibleDataTypeForExprError(expectedType: StaticType, actualType: StaticType, sourceLocationMeta: SourceLocationMeta) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocationMeta.toProblemLocation(), - details = SemanticProblemDetails.IncompatibleDataTypeForExpr(expectedType, actualType) - ) - ) - } - - /** - * If an expression always returns missing, raise a [SemanticProblemDetails.ExpressionAlwaysReturnsMissing] and return true. - * - * If an expression always returns null or union(null, missing), raise a [SemanticProblemDetails.ExpressionAlwaysReturnsNullOrMissing] and return false. - * - * else returns false. - */ - private fun expressionAlwaysReturnsUnknown(types: List, sourceLocationMeta: SourceLocationMeta): Boolean { - if (types.any { type -> type is MissingType }) { - handleExpressionAlwaysReturnsMissingError(sourceLocationMeta) - return true - } - - if (types.any { type -> type is NullType || type == StaticType.NULL_OR_MISSING }) { - handleExpressionAlwaysReturnsMissingOrNullWarning(sourceLocationMeta) - } - return false - } - - private fun handleExpressionAlwaysReturnsUnknown(type: StaticType, sourceLocationMeta: SourceLocationMeta) { - if (type is MissingType) { - handleExpressionAlwaysReturnsMissingError(sourceLocationMeta) - return - } - - if (type is NullType || type == StaticType.NULL_OR_MISSING) { - handleExpressionAlwaysReturnsMissingOrNullWarning(sourceLocationMeta) - } - } - - private fun handleExpressionAlwaysReturnsMissingOrNullWarning(sourceLocationMeta: SourceLocationMeta) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocationMeta.toProblemLocation(), - details = SemanticProblemDetails.ExpressionAlwaysReturnsNullOrMissing - ) - ) - } - - private fun handleExpressionAlwaysReturnsMissingError(sourceLocationMeta: SourceLocationMeta) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocationMeta.toProblemLocation(), - details = SemanticProblemDetails.ExpressionAlwaysReturnsMissing - ) - ) - } - - private fun handleNullOrMissingFunctionArgument(functionName: String, sourceLocationMeta: SourceLocationMeta) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocationMeta.toProblemLocation(), - details = SemanticProblemDetails.NullOrMissingFunctionArgument( - functionName = functionName - ) - ) - ) - } - - private fun handleInvalidArgumentTypeForFunction(functionName: String, expectedType: StaticType, actualType: StaticType, sourceLocationMeta: SourceLocationMeta) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocationMeta.toProblemLocation(), - details = SemanticProblemDetails.InvalidArgumentTypeForFunction( - functionName = functionName, - expectedType = expectedType, - actualType = actualType - ) - ) - ) - } - - private fun addLocal(name: String, type: StaticType) { - val existing = localsOnlyEnv[BindingName(name, BindingCase.INSENSITIVE)] - if (existing != null) { - TODO( - "A variable named '$name' was already defined in this scope. " + - "This wouldn't be the case if StaticTypeVisitorTransform was executed first." - ) - } - localsMap[name] = type - // this requires a new instance because of how [Bindings.ofMap] works - localsOnlyEnv = wrapBindings(Bindings.ofMap(localsMap), currentScopeDepth) - } - - private fun Bindings.lookupBinding(bindingName: BindingName): StaticType? = this[bindingName]?.type - - /** - * Encapsulates variable reference lookup, layering the scoping - * rules from the exclusions given the current state. - * - * Returns an instance of [StaticType] if the binding was found, otherwise returns null. - */ - private fun findBind(bindingName: BindingName, scopeQualifier: PartiqlAst.ScopeQualifier): StaticType? { - // Override the current scope search order if the var is lexically qualified. - val overriddenScopeSearchOrder = when (scopeQualifier) { - is PartiqlAst.ScopeQualifier.LocalsFirst -> ScopeSearchOrder.LEXICAL - is PartiqlAst.ScopeQualifier.Unqualified -> this.scopeOrder - } - val scopes: List> = when (overriddenScopeSearchOrder) { - ScopeSearchOrder.GLOBALS_THEN_LEXICAL -> listOf(globalTypeEnv, currentEnv) - ScopeSearchOrder.LEXICAL -> listOf(currentEnv, globalTypeEnv) - } - - return scopes - .asSequence() - .mapNotNull { it.lookupBinding(bindingName) } - .firstOrNull() - } - - override fun transformExprId(node: PartiqlAst.Expr.Id): PartiqlAst.Expr { - val bindingName = BindingName(node.name.text, node.case.toBindingCase()) - - val foundType = findBind(bindingName, node.qualifier) ?: error( - "No such variable named ${node.name.text}. " + - "This wouldn't be the case if StaticTypeVisitorTransform was executed first." - ) - - return node.withStaticType(foundType) - } - - /** - * Gives [SemanticProblemDetails.IncompatibleDatatypesForOp] error when none of the non-unknown [operandsStaticType]' - * types satisfy [operandTypeValidator]. - * - * If an operand is always missing, the - * [SemanticProblemDetails.ExpressionAlwaysReturnsMissing] error is handled by [ProblemHandler]. - * - * If an operand is always NULL, or unionOf(NULL, MISSING), the - * [SemanticProblemDetails.ExpressionAlwaysReturnsNullOrMissing] **warning** is handled by [ProblemHandler] - * - * Returns true if no **error** is added. - */ - private fun hasValidOperandTypes( - operandsStaticType: List, - operandTypeValidator: (StaticType) -> Boolean, - op: String, - metas: MetaContainer - ): Boolean { - var hasValidOperands = true - - // check for an incompatible operand type - if (operandsStaticType.any { operandStaticType -> !operandStaticType.isUnknown() && operandStaticType.allTypes.none(operandTypeValidator) }) { - handleIncompatibleDataTypesForOpError(operandsStaticType, op, metas.getSourceLocation()) - hasValidOperands = false - } - - // check for an unknown operand type - if (expressionAlwaysReturnsUnknown(operandsStaticType, metas.getSourceLocation())) { - hasValidOperands = false - } - - return hasValidOperands - } - - /** - * Returns true if all of the provided [argsStaticType] are comparable to each other and are not unknown. Otherwise, - * returns false. - * - * If an operand is not comparable to another, the [SemanticProblemDetails.IncompatibleDatatypesForOp] error is - * handled by [ProblemHandler]. - * - * If an operand is always missing, the - * [SemanticProblemDetails.ExpressionAlwaysReturnsMissing] error is handled by [ProblemHandler]. - * - * TODO: consider if collection comparison semantics should be different (e.g. errors over warnings, - * more details in error message): https://github.com/partiql/partiql-lang-kotlin/issues/505 - */ - private fun operandsAreComparable(argsStaticType: List, op: String, metas: MetaContainer): Boolean { - var hasValidOperands = true - - // check for comparability of all operands. currently only adds one data type mismatch error - outerLoop@ for (i in argsStaticType.indices) { - for (j in i + 1 until argsStaticType.size) { - if (!areStaticTypesComparable(argsStaticType[i], argsStaticType[j])) { - handleIncompatibleDataTypesForOpError(argsStaticType, op, metas.getSourceLocation()) - hasValidOperands = false - break@outerLoop - } - } - } - - // check for an unknown operand type - if (expressionAlwaysReturnsUnknown(argsStaticType, metas.getSourceLocation())) { - hasValidOperands = false - } - return hasValidOperands - } - - // Unary Op: NOT, POS, MINUS - override fun transformExprNot(node: PartiqlAst.Expr.Not): PartiqlAst.Expr { - val processedNode = super.transformExprNot(node) as PartiqlAst.Expr.Not - val argStaticType = processedNode.expr.getStaticType() - - return when (hasValidOperandTypes(listOf(argStaticType), { it is BoolType }, "NOT", processedNode.metas)) { - true -> computeReturnTypeForUnary(argStaticType, ::inferNotOp) - false -> StaticType.BOOL // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprPos(node: PartiqlAst.Expr.Pos): PartiqlAst.Expr { - val processedNode = super.transformExprPos(node) as PartiqlAst.Expr.Pos - val argStaticType = processedNode.expr.getStaticType() - - return when (hasValidOperandTypes(listOf(argStaticType), { it.isNumeric() }, "+", processedNode.metas)) { - true -> computeReturnTypeForUnary(argStaticType, ::inferUnaryArithmeticOp) - false -> StaticType.NUMERIC // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprNeg(node: PartiqlAst.Expr.Neg): PartiqlAst.Expr { - val processedNode = super.transformExprNeg(node) as PartiqlAst.Expr.Neg - val argStaticType = processedNode.expr.getStaticType() - - return when (hasValidOperandTypes(listOf(argStaticType), { it.isNumeric() }, "-", processedNode.metas)) { - true -> computeReturnTypeForUnary(argStaticType, ::inferUnaryArithmeticOp) - false -> StaticType.NUMERIC // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - private fun computeReturnTypeForUnary( - argStaticType: StaticType, - unaryOpInferencer: (SingleType) -> SingleType - ): StaticType { - val argSingleTypes = argStaticType.allTypes.map { it as SingleType } - val possibleReturnTypes = argSingleTypes.map { st -> unaryOpInferencer(st) } - - return StaticType.unionOf(possibleReturnTypes.toSet()).flatten() - } - - private fun inferNotOp(type: SingleType): SingleType = when (type) { - // Propagate NULL or MISSING - is NullType -> StaticType.NULL - is MissingType -> StaticType.MISSING - is BoolType -> type - else -> StaticType.MISSING - } - - private fun inferUnaryArithmeticOp(type: SingleType): SingleType = when (type) { - // Propagate NULL or MISSING - is NullType -> StaticType.NULL - is MissingType -> StaticType.MISSING - is DecimalType, is IntType, is FloatType -> type - else -> StaticType.MISSING - } - - // Logical NAry ops: AND, OR - // AND, OR operators are not like other NAry operators, NULL & MISSING don't simply propagate for them. That's - // why we here we deal with them differently - override fun transformExprAnd(node: PartiqlAst.Expr.And): PartiqlAst.Expr { - val processedNode = super.transformExprAnd(node) as PartiqlAst.Expr.And - return inferNaryLogicalOp(processedNode, processedNode.operands.getStaticType(), "AND") - } - - override fun transformExprOr(node: PartiqlAst.Expr.Or): PartiqlAst.Expr { - val processedNode = super.transformExprOr(node) as PartiqlAst.Expr.Or - return inferNaryLogicalOp(processedNode, processedNode.operands.getStaticType(), "OR") - } - - private fun inferNaryLogicalOp(node: PartiqlAst.Expr, argsStaticType: List, opAlias: String): PartiqlAst.Expr = when (hasValidOperandTypes(argsStaticType, { it is BoolType }, opAlias, node.metas)) { - true -> { - val argsSingleTypes = argsStaticType.map { argStaticType -> - argStaticType.allTypes.map { singleType -> singleType as SingleType } - } - val argsSingleTypeCombination = argsSingleTypes.cartesianProduct() - val possibleResultTypes = argsSingleTypeCombination.map { argsSingleType -> - getTypeForNAryLogicalOperations(argsSingleType) - }.toSet() - - StaticType.unionOf(possibleResultTypes).flatten() - } - false -> StaticType.BOOL // continuation type to prevent incompatible types and unknown errors from propagating - }.let { node.withStaticType(it) } - - private fun getTypeForNAryLogicalOperations(args: List): StaticType = when { - // Logical operands need to be of Boolean Type - args.all { it == StaticType.BOOL } -> StaticType.BOOL - // If any of the arguments is boolean, then the return type can be boolean because of short-circuiting - // in logical ops. For e.g. "TRUE OR ANY" returns TRUE. "FALSE AND ANY" returns FALSE. But in the case - // where the other arg is an incompatible type (not an unknown or bool), the result type is MISSING. - args.any { it == StaticType.BOOL } -> when { - // If other argument is missing, then return union(bool, missing) - args.any { it is MissingType } -> AnyOfType(setOf(StaticType.MISSING, StaticType.BOOL)) - // If other argument is null, then return union(bool, null) - args.any { it is NullType } -> AnyOfType(setOf(StaticType.NULL, StaticType.BOOL)) - // If other type is anything other than null or missing, then it is an error case - else -> StaticType.MISSING - } - // If any of the operands is MISSING, return MISSING. MISSING has a precedence over NULL - args.any { it is MissingType } -> StaticType.MISSING - // If any of the operands is NULL, return NULL - args.any { it is NullType } -> StaticType.NULL - else -> StaticType.MISSING - } - - // NAry ops: ADD, SUB, MUL, DIV, MOD, CONCAT, EQ, NE, LT, LTE, GT, GTE - override fun transformExprPlus(node: PartiqlAst.Expr.Plus): PartiqlAst.Expr { - val processedNode = super.transformExprPlus(node) as PartiqlAst.Expr.Plus - val argsStaticType = processedNode.operands.getStaticType() - - return when (hasValidOperandTypes(argsStaticType, { it.isNumeric() }, "+", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferBinaryArithmeticOp) - false -> StaticType.NUMERIC // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprMinus(node: PartiqlAst.Expr.Minus): PartiqlAst.Expr { - val processedNode = super.transformExprMinus(node) as PartiqlAst.Expr.Minus - val argsStaticType = processedNode.operands.getStaticType() - - return when (hasValidOperandTypes(argsStaticType, { it.isNumeric() }, "-", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferBinaryArithmeticOp) - false -> StaticType.NUMERIC // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprTimes(node: PartiqlAst.Expr.Times): PartiqlAst.Expr { - val processedNode = super.transformExprTimes(node) as PartiqlAst.Expr.Times - val argsStaticType = processedNode.operands.getStaticType() - - return when (hasValidOperandTypes(argsStaticType, { it.isNumeric() }, "*", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferBinaryArithmeticOp) - false -> StaticType.NUMERIC // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprDivide(node: PartiqlAst.Expr.Divide): PartiqlAst.Expr { - val processedNode = super.transformExprDivide(node) as PartiqlAst.Expr.Divide - val argsStaticType = processedNode.operands.getStaticType() - - return when (hasValidOperandTypes(argsStaticType, { it.isNumeric() }, "/", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferBinaryArithmeticOp) - false -> StaticType.NUMERIC // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprModulo(node: PartiqlAst.Expr.Modulo): PartiqlAst.Expr { - val processedNode = super.transformExprModulo(node) as PartiqlAst.Expr.Modulo - val argsStaticType = processedNode.operands.getStaticType() - - return when (hasValidOperandTypes(argsStaticType, { it.isNumeric() }, "%", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferBinaryArithmeticOp) - false -> StaticType.NUMERIC // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprConcat(node: PartiqlAst.Expr.Concat): PartiqlAst.Expr { - val processedNode = super.transformExprConcat(node) as PartiqlAst.Expr.Concat - val argsStaticType = processedNode.operands.getStaticType() - - return when (hasValidOperandTypes(argsStaticType, { it.isText() }, "||", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferConcatOp) - false -> StaticType.STRING // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprEq(node: PartiqlAst.Expr.Eq): PartiqlAst.Expr { - val processedNode = super.transformExprEq(node) as PartiqlAst.Expr.Eq - val argsStaticType = processedNode.operands.getStaticType() - - return when (operandsAreComparable(argsStaticType, "=", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferEqNeOp) - false -> StaticType.BOOL // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprNe(node: PartiqlAst.Expr.Ne): PartiqlAst.Expr { - val processedNode = super.transformExprNe(node) as PartiqlAst.Expr.Ne - val argsStaticType = processedNode.operands.getStaticType() - - return when (operandsAreComparable(argsStaticType, "!=", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferEqNeOp) - false -> StaticType.BOOL // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprGt(node: PartiqlAst.Expr.Gt): PartiqlAst.Expr { - val processedNode = super.transformExprGt(node) as PartiqlAst.Expr.Gt - val argsStaticType = processedNode.operands.getStaticType() - - return when (operandsAreComparable(argsStaticType, ">", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferComparatorOp) - false -> StaticType.BOOL // continuation type prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprGte(node: PartiqlAst.Expr.Gte): PartiqlAst.Expr { - val processedNode = super.transformExprGte(node) as PartiqlAst.Expr.Gte - val argsStaticType = processedNode.operands.getStaticType() - - return when (operandsAreComparable(argsStaticType, ">=", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferComparatorOp) - false -> StaticType.BOOL // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprLt(node: PartiqlAst.Expr.Lt): PartiqlAst.Expr { - val processedNode = super.transformExprLt(node) as PartiqlAst.Expr.Lt - val argsStaticType = processedNode.operands.getStaticType() - - return when (operandsAreComparable(argsStaticType, "<", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferComparatorOp) - false -> StaticType.BOOL // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - override fun transformExprLte(node: PartiqlAst.Expr.Lte): PartiqlAst.Expr { - val processedNode = super.transformExprLte(node) as PartiqlAst.Expr.Lte - val argsStaticType = processedNode.operands.getStaticType() - - return when (operandsAreComparable(argsStaticType, "<=", processedNode.metas)) { - true -> computeReturnTypeForNAry(argsStaticType, ::inferComparatorOp) - false -> StaticType.BOOL // continuation type to prevent incompatible types and unknown errors from propagating - }.let { processedNode.withStaticType(it) } - } - - private fun computeReturnTypeForNAry( - argsStaticType: List, - binaryOpInferencer: (SingleType, SingleType) -> SingleType, - ): StaticType = - argsStaticType.reduce { leftStaticType, rightStaticType -> - val leftSingleTypes = leftStaticType.allTypes.map { it as SingleType } - val rightSingleTypes = rightStaticType.allTypes.map { it as SingleType } - val possibleResultTypes: List = - leftSingleTypes.flatMap { leftSingleType -> - rightSingleTypes.map { rightSingleType -> - binaryOpInferencer(leftSingleType, rightSingleType) - } - } - - StaticType.unionOf(possibleResultTypes.toSet()).flatten() - } - - // This could also have been a lookup table of types, however... doing this as a nested `when` allows - // us to not to rely on `.equals` and `.hashcode` implementations of [StaticType], which include metas - // and might introduce unwanted behavior. - private fun inferBinaryArithmeticOp(leftType: SingleType, rightType: SingleType): SingleType = when { - // Propagate missing as missing. Missing has precedence over null - leftType is MissingType || rightType is MissingType -> StaticType.MISSING - leftType is NullType || rightType is NullType -> StaticType.NULL - else -> when (leftType) { - is IntType -> - when (rightType) { - is IntType -> - when { - leftType.rangeConstraint == IntType.IntRangeConstraint.UNCONSTRAINED -> leftType - rightType.rangeConstraint == IntType.IntRangeConstraint.UNCONSTRAINED -> rightType - leftType.rangeConstraint.numBytes > rightType.rangeConstraint.numBytes -> leftType - else -> rightType - } - is FloatType -> StaticType.FLOAT - is DecimalType -> StaticType.DECIMAL // TODO: account for decimal precision - else -> StaticType.MISSING - } - is FloatType -> - when (rightType) { - is IntType -> StaticType.FLOAT - is FloatType -> StaticType.FLOAT - is DecimalType -> StaticType.DECIMAL // TODO: account for decimal precision - else -> StaticType.MISSING - } - is DecimalType -> - when (rightType) { - is IntType -> StaticType.DECIMAL // TODO: account for decimal precision - is FloatType -> StaticType.DECIMAL // TODO: account for decimal precision - is DecimalType -> StaticType.DECIMAL // TODO: account for decimal precision - else -> StaticType.MISSING - } - else -> StaticType.MISSING - } - } - - private fun inferConcatOp(leftType: SingleType, rightType: SingleType): SingleType { - fun checkUnconstrainedText(type: SingleType) = type is SymbolType || type is StringType && type.lengthConstraint is StringType.StringLengthConstraint.Unconstrained - - return when { - // Propagate missing as missing. Missing has precedence over null - leftType is MissingType || rightType is MissingType -> StaticType.MISSING - leftType is NullType || rightType is NullType -> StaticType.NULL - !leftType.isText() || !rightType.isText() -> StaticType.MISSING - checkUnconstrainedText(leftType) || checkUnconstrainedText(rightType) -> StaticType.STRING - else -> { // Constrained string types (char & varchar) - val leftLength = ((leftType as StringType).lengthConstraint as StringType.StringLengthConstraint.Constrained).length - val rightLength = ((rightType as StringType).lengthConstraint as StringType.StringLengthConstraint.Constrained).length - val sum = leftLength.value + rightLength.value - val newConstraint = when { - leftLength is NumberConstraint.UpTo || rightLength is NumberConstraint.UpTo -> NumberConstraint.UpTo(sum) - else -> NumberConstraint.Equals(sum) - } - StringType(StringType.StringLengthConstraint.Constrained(newConstraint)) - } - } - } - - // LT, LTE, GT, GTE - private fun inferComparatorOp(lhs: SingleType, rhs: SingleType): SingleType = when { - // Propagate missing as missing. Missing has precedence over null - lhs is MissingType || rhs is MissingType -> StaticType.MISSING - lhs is NullType || rhs is NullType -> StaticType.NULL - areStaticTypesComparable(lhs, rhs) -> StaticType.BOOL - else -> StaticType.MISSING - } - - // EQ, NE - private fun inferEqNeOp(lhs: SingleType, rhs: SingleType): SingleType = when { - // Propagate missing as missing. Missing has precedence over null - lhs is MissingType || rhs is MissingType -> StaticType.MISSING - lhs.isNullable() || rhs.isNullable() -> StaticType.NULL - else -> StaticType.BOOL - } - - // Other Special NAry ops - // BETWEEN Op - override fun transformExprBetween(node: PartiqlAst.Expr.Between): PartiqlAst.Expr { - val processedNode = super.transformExprBetween(node) as PartiqlAst.Expr.Between - val argTypes = listOf(processedNode.value, processedNode.from, processedNode.to).getStaticType() - if (!operandsAreComparable(argTypes, "BETWEEN", processedNode.metas)) { - return processedNode.withStaticType(StaticType.BOOL) - } - - val argsAllTypes = argTypes.map { it.allTypes } - val possibleReturnTypes: MutableSet = mutableSetOf() - - argsAllTypes.cartesianProduct().forEach { argsChildType -> - val argsSingleType = argsChildType.map { it as SingleType } - when { - // If any one of the operands is null or missing, return NULL - argsSingleType.any { it is NullType || it is MissingType } -> possibleReturnTypes.add(StaticType.NULL) - areStaticTypesComparable(argsSingleType[0], argsSingleType[1]) || areStaticTypesComparable(argsSingleType[0], argsSingleType[2]) -> possibleReturnTypes.add(StaticType.BOOL) - else -> possibleReturnTypes.add(StaticType.MISSING) - } - } - - return processedNode.withStaticType(StaticType.unionOf(possibleReturnTypes).flatten()) - } - - // IN NAry op - override fun transformExprInCollection(node: PartiqlAst.Expr.InCollection): PartiqlAst.Expr { - val processedNode = super.transformExprInCollection(node) as PartiqlAst.Expr.InCollection - val operands = processedNode.operands.getStaticType() - val lhs = operands[0] - val rhs = operands[1] - var errorAdded = false - - // check for an unknown operand type - if (expressionAlwaysReturnsUnknown(operands, processedNode.metas.getSourceLocation())) { - errorAdded = true - } - - // if none of the [rhs] types are [CollectionType]s with comparable element types to [lhs], then data type - // mismatch error - if (!rhs.isUnknown() && rhs.allTypes.none { it is CollectionType && areStaticTypesComparable(it.elementType, lhs) }) { - handleIncompatibleDataTypesForOpError(operands, "IN", processedNode.metas.getSourceLocation()) - errorAdded = true - } - - return when (errorAdded) { - true -> StaticType.BOOL - false -> computeReturnTypeForNAryIn(operands) - }.let { processedNode.withStaticType(it) } - } - - private fun computeReturnTypeForNAryIn(argTypes: List): StaticType { - require(argTypes.size >= 2) { "IN must have at least two args" } - val leftTypes = argTypes.first().allTypes - val rightTypes = argTypes.drop(1).flatMap { it.allTypes } - - val finalTypes = leftTypes - .flatMap { left -> - rightTypes.flatMap { right -> - computeReturnTypeForBinaryIn(left, right).allTypes - } - }.distinct() - - return when (finalTypes.size) { - 1 -> finalTypes.first() - else -> StaticType.unionOf(*finalTypes.toTypedArray()) - } - } - - private fun computeReturnTypeForBinaryIn(left: StaticType, right: StaticType): StaticType = - when (right) { - is NullType -> when (left) { - is MissingType -> StaticType.MISSING - else -> StaticType.NULL - } - is MissingType -> StaticType.MISSING - is CollectionType -> when (left) { - is NullType -> StaticType.NULL - is MissingType -> StaticType.MISSING - else -> { - val rightElemTypes = right.elementType.allTypes - val possibleTypes = mutableSetOf() - if (rightElemTypes.any { it is MissingType }) { - possibleTypes.add(StaticType.MISSING) - } - if (rightElemTypes.any { it is NullType }) { - possibleTypes.add(StaticType.NULL) - } - if (rightElemTypes.any { !it.isNullOrMissing() }) { - possibleTypes.add(StaticType.BOOL) - } - StaticType.unionOf(possibleTypes).flatten() - } - } - else -> when (left) { - is NullType -> StaticType.unionOf(StaticType.NULL, StaticType.MISSING) - else -> StaticType.MISSING - } - } - - // LIKE NAry op - override fun transformExprLike(node: PartiqlAst.Expr.Like): PartiqlAst.Expr { - val processedNode = super.transformExprLike(node) as PartiqlAst.Expr.Like - val args = listOfNotNull(processedNode.value, processedNode.pattern, processedNode.escape) - - if (!hasValidOperandTypes(args.getStaticType(), { it.isText() }, "LIKE", processedNode.metas)) { - return processedNode.withStaticType(StaticType.BOOL) - } - - val argTypes = args.getStaticType() - val argsAllTypes = argTypes.map { it.allTypes } - val possibleReturnTypes: MutableSet = mutableSetOf() - - argsAllTypes.cartesianProduct().forEach { argsChildType -> - val argsSingleType = argsChildType.map { it as SingleType } - when { - // If any one of the operands is missing, return missing - // notice since we short circuit above to handle atomic missing type - // missing type here indicates one of the args is union type and in the union type we have missing - argsSingleType.any() { it is MissingType } -> possibleReturnTypes.add(StaticType.MISSING) - // If any one of the operands is null, return NULL - argsSingleType.any { it is NullType } -> possibleReturnTypes.add(StaticType.NULL) - // Arguments for LIKE need to be text type - argsSingleType.all { it.isText() } -> { - possibleReturnTypes.add(StaticType.BOOL) - // If the optional escape character is provided, it can result in failure even if the type is text (string, in this case) - // This is because the escape character needs to be a single character (string with length 1), - // Even if the escape character is of length 1, escape sequence can be incorrect. - if (processedNode.escape != null) { - possibleReturnTypes.add(StaticType.MISSING) - } - } - else -> possibleReturnTypes.add(StaticType.MISSING) - } - } - - return processedNode.withStaticType(StaticType.unionOf(possibleReturnTypes).flatten()) - } - - // CALL - override fun transformExprCall(node: PartiqlAst.Expr.Call): PartiqlAst.Expr { - val processedNode = super.transformExprCall(node) as PartiqlAst.Expr.Call - - val arguments = processedNode.args - val functionName = processedNode.funcName.text - val arity = arguments.size - val location = processedNode.metas.getSourceLocation() - var signatures = allFunctions[functionName] - if (signatures == null) { - handleNoSuchFunctionError(functionName, location) - return processedNode.withStaticType(StaticType.ANY) - } - val funcsMatchingArity = signatures.filter { it.arity.contains(arity) } - if (funcsMatchingArity.isEmpty()) { - handleIncorrectNumberOfArgumentsToFunctionCallError(functionName, getMinMaxArities(signatures).first..getMinMaxArities(signatures).second, arity, location) - } else { - signatures = funcsMatchingArity - } - - var types = mutableSetOf() - for (sign in signatures) { - when (sign.unknownArguments) { - UnknownArguments.PROPAGATE -> types.add(returnTypeForPropagatingFunction(sign, arguments)) - UnknownArguments.PASS_THRU -> types.add(returnTypeForPassThruFunction(sign, arguments)) - } - } - - return processedNode.withStaticType(StaticType.unionOf(types).flatten()) - } - - fun getMinMaxArities(funcs: List): Pair { - val minArity = funcs.map { it.arity.first }.minOrNull() ?: Int.MAX_VALUE - val maxArity = funcs.map { it.arity.last }.maxOrNull() ?: Int.MIN_VALUE - - return Pair(minArity, maxArity) - } - - // Call agg : "count", "avg", "max", "min", "sum" - override fun transformExprCallAgg(node: PartiqlAst.Expr.CallAgg): PartiqlAst.Expr { - val processedNode = super.transformExprCallAgg(node) as PartiqlAst.Expr.CallAgg - val funcName = processedNode.funcName - // unwrap the type if this is a collectionType - val argType = when (val type = processedNode.arg.getStaticType()) { - is CollectionType -> type.elementType - else -> type - } - val sourceLocation = processedNode.getStartingSourceLocationMeta() - return processedNode.withStaticType(computeReturnTypeForAggFunc(funcName.text, argType, sourceLocation)) - } - - private fun handleInvalidInputTypeForAggFun(sourceLocation: SourceLocationMeta, funcName: String, actualType: StaticType, expectedType: StaticType) { - problemHandler.handleProblem( - Problem( - sourceLocation = sourceLocation.toProblemLocation(), - details = SemanticProblemDetails.InvalidArgumentTypeForFunction( - functionName = funcName, - expectedType = expectedType, - actualType = actualType - ) - ) - ) - } - - private fun computeReturnTypeForAggFunc(funcName: String, elementType: StaticType, sourceLocation: SourceLocationMeta): StaticType { - val elementTypes = elementType.allTypes - - fun List.convertMissingToNull() = toMutableSet().apply { - if (contains(StaticType.MISSING)) { - remove(StaticType.MISSING) - add(StaticType.NULL) - } - } - - fun StaticType.isUnknownOrNumeric() = isUnknown() || isNumeric() - - return when (funcName) { - "count" -> StaticType.INT - // In case that any element is MISSING or there is no element, we should return NULL - "max", "min" -> StaticType.unionOf(elementTypes.convertMissingToNull()) - "sum" -> when { - elementTypes.none { it.isUnknownOrNumeric() } -> { - handleInvalidInputTypeForAggFun(sourceLocation, funcName, elementType, StaticType.unionOf(StaticType.NULL_OR_MISSING, StaticType.NUMERIC).flatten()) - StaticType.unionOf(StaticType.NULL, StaticType.NUMERIC) - } - // If any single type is mismatched, We should add MISSING to the result types set to indicate there is a chance of data mismatch error - elementTypes.any { !it.isUnknownOrNumeric() } -> StaticType.unionOf( - elementTypes.filter { it.isUnknownOrNumeric() }.toMutableSet().apply { add(StaticType.MISSING) } - ) - // In case that any element is MISSING or there is no element, we should return NULL - else -> StaticType.unionOf(elementTypes.convertMissingToNull()) - } - // "avg" returns DECIMAL or NULL - "avg" -> when { - elementTypes.none { it.isUnknownOrNumeric() } -> { - handleInvalidInputTypeForAggFun(sourceLocation, funcName, elementType, StaticType.unionOf(StaticType.NULL_OR_MISSING, StaticType.NUMERIC).flatten()) - StaticType.unionOf(StaticType.NULL, StaticType.DECIMAL) - } - else -> StaticType.unionOf( - mutableSetOf().apply { - if (elementTypes.any { it.isUnknown() }) { add(StaticType.NULL) } - if (elementTypes.any { it.isNumeric() }) { add(StaticType.DECIMAL) } - // If any single type is mismatched, We should add MISSING to the result types set to indicate there is a chance of data mismatch error - if (elementTypes.any { !it.isUnknownOrNumeric() }) { add(StaticType.MISSING) } - } - ) - } - else -> error("Internal Error: Unsupported aggregate function. This probably indicates a parser bug.") - }.flatten() - } - - /** - * Computes return type for functions with [FunctionSignature.unknownArguments] as [UnknownArguments.PASS_THRU] - */ - private fun returnTypeForPassThruFunction(signature: FunctionSignature, arguments: List): StaticType { - return when { - matchesAllArguments(arguments, signature) -> signature.returnType - matchesAtLeastOneArgument(arguments, signature) -> StaticType.unionOf(signature.returnType, StaticType.MISSING) - else -> StaticType.MISSING - } - } - - /** - * Returns true if for every pair (expr, expectedType) in [argsWithExpectedTypes], the expr's [StaticType] is - * not an unknown and has a shared type with expectedType. Returns false otherwise. - * - * If an argument has an unknown type, the [SemanticProblemDetails.NullOrMissingFunctionArgument] error is - * handled by [problemHandler]. If an expr has no shared type with the expectedType, the - * [SemanticProblemDetails.InvalidArgumentTypeForFunction] error is handled by [problemHandler]. - */ - private fun functionHasValidArgTypes(functionName: String, argsWithExpectedTypes: List>): Boolean { - var allArgsValid = true - argsWithExpectedTypes.forEach { (actualExpr, expectedType) -> - val actualType = actualExpr.getStaticType() - - if (actualType.isUnknown()) { - handleNullOrMissingFunctionArgument(functionName, actualExpr.metas.getSourceLocation()) - allArgsValid = false - } else { - val actualNonUnknownType = actualType.filterNullMissing() - if (getTypeDomain(actualNonUnknownType).intersect(getTypeDomain(expectedType)).isEmpty()) { - handleInvalidArgumentTypeForFunction( - functionName = functionName, - expectedType = expectedType, - actualType = actualType, - sourceLocationMeta = actualExpr.metas.getSourceLocation() - ) - allArgsValid = false - } - } - } - return allArgsValid - } - - /** - * Computes return type for functions with [FunctionSignature.unknownArguments] as [UnknownArguments.PROPAGATE] - */ - private fun returnTypeForPropagatingFunction(signature: FunctionSignature, arguments: List): StaticType { - val requiredArgs = arguments.zip(signature.requiredParameters) - val allArgs = requiredArgs - - return if (functionHasValidArgTypes(signature.name, allArgs)) { - val finalReturnTypes = signature.returnType.allTypes + allArgs.flatMap { (actualExpr, expectedType) -> - val actualType = actualExpr.getStaticType() - listOfNotNull( - // if any type is `MISSING`, add `MISSING` to possible return types. - // if the actual type is not a subtype is the expected type, add `MISSING`. In the future, may - // want to give a warning that a data type mismatch could occur - // (https://github.com/partiql/partiql-lang-kotlin/issues/507) - StaticType.MISSING.takeIf { - actualType.allTypes.any { it is MissingType } || !isSubTypeOf(actualType.filterNullMissing(), expectedType) - }, - // if any type is `NULL`, add `NULL` to possible return types - StaticType.NULL.takeIf { actualType.allTypes.any { it is NullType } } - ) - } - AnyOfType(finalReturnTypes.toSet()).flatten() - } else { - // otherwise, has an invalid arg type and errors. continuation type of [FunctionSignature.returnType] - signature.returnType - } - } - - /** - * Function assumes the number of [arguments] passed agrees with the [signature] - * - * Returns true if there's at least one valid overlap between actual and expected - * for all the expected arguments (required, optional, variadic) for the [signature]. - * - * Returns false otherwise. - */ - private fun matchesAtLeastOneArgument(arguments: List, signature: FunctionSignature): Boolean { - val requiredArgumentsMatch = arguments - .zip(signature.requiredParameters) - .all { (actual, expected) -> - getTypeDomain(actual.getStaticType()).intersect(getTypeDomain(expected)).isNotEmpty() - } - return requiredArgumentsMatch - } - - /** - * Function assumes the number of [arguments] passed agrees with the [signature] - * Returns true when all the arguments (required, optional, variadic) are subtypes of the expected arguments for the [signature]. - * Returns false otherwise - */ - private fun matchesAllArguments(arguments: List, signature: FunctionSignature): Boolean { - // Checks if the actual StaticType is subtype of expected StaticType ( filtering the null/missing for PROPAGATING functions - fun isSubType(actual: StaticType, expected: StaticType): Boolean { - val lhs = when (signature.unknownArguments) { - UnknownArguments.PROPAGATE -> when (actual) { - is AnyOfType -> actual.copy( - types = actual.types.filter { - !it.isNullOrMissing() - }.toSet() - ) - else -> actual - } - UnknownArguments.PASS_THRU -> actual - } - return isSubTypeOf(lhs, expected) - } - - val requiredArgumentsMatch = arguments - .zip(signature.requiredParameters) - .all { (actual, expected) -> - val st = actual.getStaticType() - isSubType(st, expected) - } - return requiredArgumentsMatch - } - - override fun transformExprLit(node: PartiqlAst.Expr.Lit): PartiqlAst.Expr { - val literal = super.transformExprLit(node) as PartiqlAst.Expr.Lit - val exprValueType = ExprValueType.fromIonType(literal.value.type.toIonType()) - return literal.withStaticType(staticTypeFromExprValueType(exprValueType)) - } - - override fun transformExprMissing(node: PartiqlAst.Expr.Missing): PartiqlAst.Expr { - val literal = super.transformExprMissing(node) - return literal.withStaticType(StaticType.MISSING) - } - - // Seq => List, Sexp, Bag - private fun transformSeq( - expr: PartiqlAst.Expr, - values: List, - compute: (StaticType) -> StaticType - ): PartiqlAst.Expr { - val valuesTypes = AnyOfType(values.getStaticType().toSet()).flatten() - val inferredType = compute(valuesTypes) - return expr.withStaticType(inferredType) - } - - override fun transformExprList(node: PartiqlAst.Expr.List): PartiqlAst.Expr { - val seq = super.transformExprList(node) as PartiqlAst.Expr.List - return transformSeq(seq, seq.values) { ListType(it) } - } - - override fun transformExprSexp(node: PartiqlAst.Expr.Sexp): PartiqlAst.Expr { - val seq = super.transformExprSexp(node) as PartiqlAst.Expr.Sexp - return transformSeq(seq, seq.values) { SexpType(it) } - } - - override fun transformExprBag(node: PartiqlAst.Expr.Bag): PartiqlAst.Expr { - val seq = super.transformExprBag(node) as PartiqlAst.Expr.Bag - return transformSeq(seq, seq.values) { BagType(it) } - } - - override fun transformExprBagOp(node: PartiqlAst.Expr.BagOp): PartiqlAst.Expr { - val bagOp = super.transformExprBagOp(node) as PartiqlAst.Expr.BagOp - // TODO assert operand compatibility once SQL bag operators are implemented - return bagOp.withStaticType(StaticType.BAG) - } - - override fun transformExprSimpleCase(node: PartiqlAst.Expr.SimpleCase): PartiqlAst.Expr { - val simpleCase = super.transformExprSimpleCase(node) as PartiqlAst.Expr.SimpleCase - val caseValue = simpleCase.expr - val caseValueType = caseValue.getStaticType() - - // handle unknown case value. - if (caseValueType.isUnknown()) { - handleExpressionAlwaysReturnsUnknown(caseValueType, caseValue.getStartingSourceLocationMeta()) - } - - val whenExprs = simpleCase.cases.pairs.map { expr -> expr.first } - whenExprs.forEach { whenExpr -> - val whenExprType = whenExpr.getStaticType() - // handle unknown whenExpr. - if (whenExprType.isUnknown()) { - handleExpressionAlwaysReturnsUnknown(whenExprType, whenExpr.getStartingSourceLocationMeta()) - } - - // if caseValueType is incomparable to whenExprType -> data type mismatch - else if (!areStaticTypesComparable(caseValueType, whenExprType)) { - handleIncompatibleDataTypesForOpError( - actualTypes = listOf(caseValueType, whenExprType), - op = "CASE", - sourceLocationMeta = whenExpr.getStartingSourceLocationMeta() - ) - } - } - - val thenExprs = simpleCase.cases.pairs.map { expr -> expr.second } - - // Inferencer simply keeps all the then/else branch return type - // even though we might have enough information to determine that the branch will never succeed. - // may worth to change the inferencer algorithm to further refine the return type, - // and/or given warning on branches that leads to comparison always fail. - val simpleCaseType = inferCaseWhenBranches(thenExprs, simpleCase.default) - return simpleCase.withStaticType(simpleCaseType) - } - - override fun transformExprSearchedCase(node: PartiqlAst.Expr.SearchedCase): PartiqlAst.Expr { - val searchedCase = super.transformExprSearchedCase(node) as PartiqlAst.Expr.SearchedCase - - val whenExprs = searchedCase.cases.pairs.map { expr -> expr.first } - whenExprs.forEach { whenExpr -> - val whenExprType = whenExpr.getStaticType() - - // check for an unknown whenExpr type - if (whenExprType.isUnknown()) { - handleExpressionAlwaysReturnsUnknown(whenExprType, whenExpr.getStartingSourceLocationMeta()) - } - - // if whenExpr can never be bool -> data type mismatch - else if (whenExprType.allTypes.none { it is BoolType }) { - handleIncompatibleDataTypeForExprError( - expectedType = StaticType.BOOL, - actualType = whenExprType, - sourceLocationMeta = whenExpr.getStartingSourceLocationMeta() - ) - } - } - - val thenExprs = searchedCase.cases.pairs.map { expr -> expr.second } - - // keep all the `THEN` expr types even if the whenExpr could never be bool - val searchedCaseType = inferCaseWhenBranches(thenExprs, searchedCase.default) - return searchedCase.withStaticType(searchedCaseType) - } - - fun inferCaseWhenBranches(thenExprs: List, elseExpr: PartiqlAst.Expr?): StaticType { - val thenExprsTypes = thenExprs.getStaticType() - val elseExprType = when (elseExpr) { - // If there is no ELSE clause in the expression, it possible that - // none of the WHEN clauses succeed and the output of CASE WHEN expression - // ends up being NULL - null -> StaticType.NULL - else -> elseExpr.getStaticType() - } - - if (thenExprsTypes.any { it is AnyType } || elseExprType is AnyType) { - return StaticType.ANY - } - - val possibleTypes = thenExprsTypes + elseExprType - return AnyOfType(possibleTypes.toSet()).flatten() - } - - // PIG ast Types => CanCast, CanLosslessCast, IsType, ExprCast - override fun transformExprCanCast(node: PartiqlAst.Expr.CanCast): PartiqlAst.Expr { - val typed = super.transformExprCanCast(node) as PartiqlAst.Expr.CanCast - return typed.withStaticType(StaticType.BOOL) - } - - override fun transformExprCanLosslessCast(node: PartiqlAst.Expr.CanLosslessCast): PartiqlAst.Expr { - val typed = super.transformExprCanLosslessCast(node) as PartiqlAst.Expr.CanLosslessCast - return typed.withStaticType(StaticType.BOOL) - } - - override fun transformExprIsType(node: PartiqlAst.Expr.IsType): PartiqlAst.Expr { - val typed = super.transformExprIsType(node) as PartiqlAst.Expr.IsType - return typed.withStaticType(StaticType.BOOL) - } - - override fun transformExprCast(node: PartiqlAst.Expr.Cast): PartiqlAst.Expr { - val typed = super.transformExprCast(node) as PartiqlAst.Expr.Cast - val sourceType = typed.value.getStaticType() - val targetType = typed.asType.toTypedOpParameter() - val castOutputType = sourceType.cast(targetType.staticType).let { - if (targetType.validationThunk == null) { - // There is no additional validation for this parameter, return this type as-is - it - } else { - StaticType.unionOf(StaticType.MISSING, it) - } - } - return typed.withStaticType(castOutputType) - } - - override fun transformExprNullIf(node: PartiqlAst.Expr.NullIf): PartiqlAst.Expr { - val nullIf = super.transformExprNullIf(node) as PartiqlAst.Expr.NullIf - - // check for comparability of the two arguments to `NULLIF` - operandsAreComparable(listOf(nullIf.expr1, nullIf.expr2).getStaticType(), "NULLIF", nullIf.metas) - - // output type will be the first argument's types along with `NULL` (even in the case of an error) - val possibleOutputTypes = nullIf.expr1.getStaticType().asNullable() - return nullIf.withStaticType(possibleOutputTypes) - } - - override fun transformExprCoalesce(node: PartiqlAst.Expr.Coalesce): PartiqlAst.Expr { - val coalesce = super.transformExprCoalesce(node) as PartiqlAst.Expr.Coalesce - var allMissing = true - val outputTypes = mutableSetOf() - - for (arg in coalesce.args) { - val staticTypes = arg.getStaticType().allTypes - outputTypes += staticTypes - // If at least one known type is found, remove null and missing from the result - // It means there is at least one type which doesn't contain unknown types. - if (staticTypes.all { type -> !type.isNullOrMissing() }) { - outputTypes.remove(StaticType.MISSING) - outputTypes.remove(StaticType.NULL) - break - } - if (!staticTypes.contains(StaticType.MISSING)) { - allMissing = false - } - } - // If every argument has MISSING as one of it's types, - // then output should contain MISSING and not otherwise. - if (!allMissing) { - outputTypes.remove(StaticType.MISSING) - } - - return coalesce.withStaticType( - when (outputTypes.size) { - 1 -> outputTypes.first() - else -> StaticType.unionOf(outputTypes) - } - ) - } - - override fun transformExprStruct(node: PartiqlAst.Expr.Struct): PartiqlAst.Expr { - val struct = super.transformExprStruct(node) as PartiqlAst.Expr.Struct - val structFields = mutableListOf() - var closedContent = true - struct.fields.forEach { expr -> - val nameExpr = expr.first - val valueExpr = expr.second - when (nameExpr) { - is PartiqlAst.Expr.Lit -> - // A field is only included in the StructType if its key is a text literal - if (nameExpr.value is TextElement) { - structFields.add(StructType.Field(nameExpr.value.textValue, valueExpr.getStaticType())) - } - else -> { - // A field with a non-literal key name is not included. - // If the non-literal could be text, StructType will have open content. - if (nameExpr.getStaticType().allTypes.any { it.isText() }) { - closedContent = false - } - } - } - } - - val hasDuplicateKeys = structFields - .groupingBy { it.key } - .eachCount() - .any { it.value > 1 } - - if (hasDuplicateKeys) { - TODO("Duplicate keys in struct is not yet handled") - } - - return struct.withStaticType(StructType(structFields, contentClosed = closedContent)) - } - - private fun getElementTypeForFromSource(fromSourceType: StaticType): StaticType = - when (fromSourceType) { - is BagType -> fromSourceType.elementType - is ListType -> fromSourceType.elementType - is AnyType -> StaticType.ANY - is AnyOfType -> AnyOfType(fromSourceType.types.map { getElementTypeForFromSource(it) }.toSet()) - // All the other types coerce into a bag of themselves (including null/missing/sexp). - else -> fromSourceType - } - - override fun transformFromSourceScan(node: PartiqlAst.FromSource.Scan): PartiqlAst.FromSource { - val from = super.transformFromSourceScan(node) as PartiqlAst.FromSource.Scan - - val asSymbolicName = node.asAlias - ?: error("fromSourceLet.asName is null. This wouldn't be the case if FromSourceAliasVisitorTransform was executed first.") - - val fromExprType = from.expr.getStaticType() - - val elementType = getElementTypeForFromSource(fromExprType) - - addLocal(asSymbolicName.text, elementType) - - node.atAlias?.let { - val hasLists = getTypeDomain(fromExprType).contains(ExprValueType.LIST) - val hasOnlyLists = hasLists && (getTypeDomain(fromExprType).size == 1) - when { - hasOnlyLists -> { - addLocal(it.text, StaticType.INT) - } - hasLists -> { - addLocal(it.text, StaticType.unionOf(StaticType.INT, StaticType.MISSING)) - } - else -> { - addLocal(it.text, StaticType.MISSING) - } - } - } - - node.byAlias?.let { - TODO("BY variable's inference is not implemented yet.") - } - - return from - } - - /** - * Verifies the given [expr]'s [StaticType] has type [expectedType]. - * If [expr] is always missing, the - * [SemanticProblemDetails.ExpressionAlwaysReturnsMissing] error is handled by [ProblemHandler]. - * - * If [expr] is always NULL, or unionOf(NULL, MISSING), the - * [SemanticProblemDetails.ExpressionAlwaysReturnsNullOrMissing] **warning** is handled by [ProblemHandler] - * - * If [expr]'s [StaticType] could never be [expectedType], an incompatible data types for - * expression error is given. - */ - private fun verifyExpressionType(expr: PartiqlAst.Expr, expectedType: StaticType) { - val exprType = expr.getStaticType() - - if (exprType.isUnknown()) { - handleExpressionAlwaysReturnsUnknown(exprType, expr.getStartingSourceLocationMeta()) - } else if (exprType.allTypes.none { it == expectedType }) { - handleIncompatibleDataTypeForExprError( - expectedType = expectedType, - actualType = exprType, - sourceLocationMeta = expr.getStartingSourceLocationMeta() - ) - } - } - - /** - * Replaces `null` predicates with literal `true` with type [StaticType.BOOL]. - * - * In the PartiqlAst, the non-present, optional join predicate is represented with a `null` value, even - * though the `null` predicate is equivalent to `true`. However, that also causes it to be skipped and not - * assigned a `StaticType`, which is required by [EvaluatorStaticTypeTests]. - * - * If predicate is non-null, checks that its type could be [StaticType.BOOL]. - * - * If predicate is always missing, the - * [SemanticProblemDetails.ExpressionAlwaysReturnsMissing] error is handled by [ProblemHandler]. - * - * If predicate is always NULL, or unionOf(NULL, MISSING), the - * [SemanticProblemDetails.ExpressionAlwaysReturnsNullOrMissing] **warning** is handled by [ProblemHandler]. - * - * If the type is not unknown and could never be [StaticType.BOOL], gives a data type - * mismatch error (incompatible types for expression). - */ - override fun transformFromSourceJoin_predicate(node: PartiqlAst.FromSource.Join): PartiqlAst.Expr? { - return when (val predicate = super.transformFromSourceJoin_predicate(node)) { - null -> PartiqlAst.build { lit(ionBool(true)).withStaticType(StaticType.BOOL) } - else -> { - // verify `JOIN` predicate is bool. If it's unknown, gives appropriate error or warning. If it could - // never be a bool, gives an incompatible data type for expression error - verifyExpressionType(expr = predicate, expectedType = StaticType.BOOL) - - // continuation type (even in the case of an error) is [StaticType.BOOL] - predicate.withStaticType(StaticType.BOOL) - } - } - } - - private fun getUnpivotValueType(fromSourceType: StaticType): StaticType = - when (fromSourceType) { - is StructType -> if (fromSourceType.contentClosed) { - AnyOfType(fromSourceType.fields.map { it.value }.toSet()).flatten() - } else { - // Content is open, so value can be of any type - StaticType.ANY - } - is AnyType -> StaticType.ANY - is AnyOfType -> AnyOfType(fromSourceType.types.map { getUnpivotValueType(it) }.toSet()) - // All the other types coerce into a struct of themselves with synthetic key names - else -> fromSourceType - } - - override fun transformFromSourceUnpivot(node: PartiqlAst.FromSource.Unpivot): PartiqlAst.FromSource { - val from = super.transformFromSourceUnpivot(node) as PartiqlAst.FromSource.Unpivot - - val asSymbolicName = node.asAlias - ?: error("FromSourceUnpivot.asAlias is null. This wouldn't be the case if FromSourceAliasVisitorTransform was executed first.") - - val fromExprType = from.expr.getStaticType() - - val valueType = getUnpivotValueType(fromExprType) - addLocal(asSymbolicName.text, valueType) - - node.atAlias?.let { - val valueHasMissing = getTypeDomain(valueType).contains(ExprValueType.MISSING) - val valueOnlyHasMissing = valueHasMissing && getTypeDomain(valueType).size == 1 - when { - valueOnlyHasMissing -> { - addLocal(it.text, StaticType.MISSING) - } - valueHasMissing -> { - addLocal(it.text, StaticType.STRING.asOptional()) - } - else -> { - addLocal(it.text, StaticType.STRING) - } - } - } - - node.byAlias?.let { - TODO("BY variable's inference is not implemented yet.") - } - - return from - } - - override fun transformExprPath(node: PartiqlAst.Expr.Path): PartiqlAst.Expr { - val path = super.transformExprPath(node) as PartiqlAst.Expr.Path - var currentType = path.root.getStaticType() - val newComponents = path.steps.map { pathComponent -> - currentType = when (pathComponent) { - is PartiqlAst.PathStep.PathExpr -> inferPathComponentExprType(currentType, pathComponent) - is PartiqlAst.PathStep.PathUnpivot -> TODO("PathUnpivot is not implemented yet") - is PartiqlAst.PathStep.PathWildcard -> TODO("PathWildcard is not implemented yet") - } - pathComponent.withStaticType(currentType) - } - return path.copy( - steps = newComponents, - metas = super.transformMetas(node.metas) - ).withStaticType(currentType) - } - - private fun inferPathComponentExprType( - previousComponentType: StaticType, - currentPathComponent: PartiqlAst.PathStep.PathExpr - ): StaticType = - when (previousComponentType) { - is AnyType -> StaticType.ANY - is StructType -> inferStructLookupType(currentPathComponent, previousComponentType.fields.associate { it.key to it.value }, previousComponentType.contentClosed) - is ListType, - is SexpType -> { - val previous = previousComponentType as CollectionType // help Kotlin's type inference to be more specific - if (currentPathComponent.index.getStaticType() is IntType) { - previous.elementType - } else { - StaticType.MISSING - } - } - is AnyOfType -> { - when (previousComponentType.types.size) { - 0 -> throw IllegalStateException("Cannot path on an empty StaticType union") - else -> { - val prevTypes = previousComponentType.allTypes - if (prevTypes.any { it is AnyType }) { - StaticType.ANY - } else { - val staticTypes = prevTypes.map { inferPathComponentExprType(it, currentPathComponent) } - AnyOfType(staticTypes.toSet()).flatten() - } - } - } - } - else -> StaticType.MISSING - } - - private fun inferStructLookupType( - currentPathComponent: PartiqlAst.PathStep.PathExpr, - structFields: Map, - contentClosed: Boolean - ): StaticType = - when (val index = currentPathComponent.index) { - is PartiqlAst.Expr.Lit -> { - if (index.value is StringElement) { - val bindings = Bindings.ofMap(structFields) - val caseSensitivity = currentPathComponent.case - val lookupName = BindingName( - index.value.stringValue, - caseSensitivity.toBindingCase() - ) - bindings[lookupName] ?: if (contentClosed) { - StaticType.MISSING - } else { - StaticType.ANY - } - } else { - // Should this branch result in an error? - StaticType.MISSING - } - } - else -> { - StaticType.MISSING - } - } - - override fun transformLetBinding(node: PartiqlAst.LetBinding): PartiqlAst.LetBinding { - val binding = super.transformLetBinding(node) - addLocal(binding.name.text, binding.expr.getStaticType()) - return binding - } - - override fun transformFromSourceScan_expr(node: PartiqlAst.FromSource.Scan): PartiqlAst.Expr { - this.scopeOrder = ScopeSearchOrder.GLOBALS_THEN_LEXICAL - return transformExpr(node.expr).also { - this.scopeOrder = ScopeSearchOrder.LEXICAL - } - } - - override fun transformFromSourceUnpivot_expr(node: PartiqlAst.FromSource.Unpivot): PartiqlAst.Expr { - this.scopeOrder = ScopeSearchOrder.GLOBALS_THEN_LEXICAL - return transformExpr(node.expr).also { - this.scopeOrder = ScopeSearchOrder.LEXICAL - } - } - - override fun transformProjection(node: PartiqlAst.Projection): PartiqlAst.Projection { - val newProjection = super.transformProjection(node) - val type = when (newProjection) { - is PartiqlAst.Projection.ProjectList -> { - val contentClosed = newProjection.projectItems.filterIsInstance().all { - val exprType = it.expr.getStaticType() as? StructType - ?: TODO("Expected Struct type for PartiqlAst.ProjectItem.ProjectAll expr") - exprType.contentClosed - } - val projectionFields = mutableListOf() - for (item in newProjection.projectItems) { - when (item) { - is PartiqlAst.ProjectItem.ProjectExpr -> { - val projectionAsName = item.asAlias?.text - ?: error("No alias found for projection") - if (projectionFields.find { it.key == projectionAsName } != null) { - // Duplicate select-list-item aliases are not allowed. - // Keeps the static type of the first alias - handleDuplicateAliasesError(item.expr.metas.getSourceLocation()) - } else { - projectionFields.add(StructType.Field(projectionAsName, item.expr.getStaticType())) - } - } - is PartiqlAst.ProjectItem.ProjectAll -> { - val exprType = item.expr.getStaticType() as? StructType - ?: TODO("Expected Struct type for PartiqlAst.ProjectItem.ProjectAll expr") - projectionFields.addAll(exprType.fields) - } - } - } - // TODO: Make the name optional in StaticType - StructType(fields = projectionFields, contentClosed = contentClosed) - } - is PartiqlAst.Projection.ProjectStar -> error( - "Encountered a SelectListItemStar." + - " This wouldn't be the case if SelectStarVisitorTransform ran before this." - ) - is PartiqlAst.Projection.ProjectValue -> newProjection.value.getStaticType() - is PartiqlAst.Projection.ProjectPivot -> StaticType.STRUCT - } - return newProjection.withStaticType(type) - } - - private fun createVisitorTransformForNestedScope(): VisitorTransform { - return VisitorTransform(currentEnv, currentScopeDepth + 1) - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - // calling transformExprSelectEvaluationOrder avoids the infinite recursion that would happen - // if we called transformExprSelect on the nested visitor transform. - val newNode = createVisitorTransformForNestedScope().transformExprSelectEvaluationOrder(node) - as PartiqlAst.Expr.Select - - val projectionType = newNode.project.metas.staticType?.type - ?: error("Select project wasn't assigned a StaticTypeMeta for some reason") - - val selectType = when (newNode.project) { - is PartiqlAst.Projection.ProjectList, - is PartiqlAst.Projection.ProjectValue -> BagType(projectionType) - is PartiqlAst.Projection.ProjectStar -> - error("expected SelectListItemStar transform to be ran before this") - is PartiqlAst.Projection.ProjectPivot -> projectionType - } - - return newNode.withStaticType(selectType) - } - - override fun transformExprSelect_where(node: PartiqlAst.Expr.Select): PartiqlAst.Expr? { - return when (val whereExpr = node.where?.let { transformExpr(it) }) { - null -> whereExpr - else -> { - // verify `WHERE` clause is bool. If it's unknown, gives appropriate warning or error. If it could never - // be a bool, gives an incompatible data type for expression error - verifyExpressionType(expr = whereExpr, expectedType = StaticType.BOOL) - - // continuation type for `WHERE` clause will be [StaticType.BOOL] regardless if there's an error - whereExpr.withStaticType(StaticType.BOOL) - } - } - } - } - - override fun transformStatement(node: PartiqlAst.Statement): PartiqlAst.Statement = - VisitorTransform(wrapBindings(Bindings.empty(), 1), 0) - .transformStatement(node) - - private fun wrapBindings(bindings: Bindings, depth: Int): Bindings { - return Bindings.over { name -> - bindings[name]?.let { bind -> - TypeAndDepth(bind, depth) - } - } - } - - /** Helper to convert [PartiqlAst.Type] in AST to a [TypedOpParameter]. */ - private fun PartiqlAst.Type.toTypedOpParameter(): TypedOpParameter { - return this.toTypedOpParameter(customTypedOpParameters) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeVisitorTransform.kt deleted file mode 100644 index 2ecc2b722e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeVisitorTransform.kt +++ /dev/null @@ -1,507 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - */ - -package org.partiql.lang.eval.visitors - -import com.amazon.ion.IonSystem -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.metaContainerOf -import com.amazon.ionelement.api.toIonElement -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.lang.ast.StaticTypeMeta -import org.partiql.lang.ast.passes.SemanticException -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.addSourceLocation -import org.partiql.lang.domains.extractSourceLocation -import org.partiql.lang.domains.toBindingCase -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.delegate -import org.partiql.lang.util.propertyValueMapOf -import org.partiql.types.StaticType - -/** - * Extra constraints which may be imposed on the type checking. - */ -enum class StaticTypeVisitorTransformConstraints { - - /** - * With this constraint, any VariableReferences in SFW queries must be - * defined within the FROM clause. - * - * This provides for variable resolution which is akin to what users of a - * traditional RDBMS would expect. - */ - PREVENT_GLOBALS_EXCEPT_IN_FROM, - - /** - * With this constraint, all variables from nested queries must resolve to - * lexically scoped variables. - * - * Constraining a user's access to global binds is useful for simplified - * query planning in a DB environment where global binds may be costly to - * fetch or lead to large intermediate results. - */ - PREVENT_GLOBALS_IN_NESTED_QUERIES; -} - -/** - * A [PartiqlAst.VisitorTransform] that annotates nodes with their static types and resolves implicit variables - * explicitly based on the static types. - * - * The validations performed may be enhanced by the passing of additional [StaticTypeVisitorTransformConstraints]. - * - * @param globalEnv The global bindings to the static environment. This is data catalog purely from a lookup - * perspective. - * @param constraints Additional constraints on what variable scoping, or other rules should be followed. - */ -class StaticTypeVisitorTransform( - private val ion: IonSystem, - globalBindings: Bindings, - constraints: Set = setOf() -) : VisitorTransformBase() { - - /** Used to allow certain binding lookups to occur directly in the global scope. */ - private val globalEnv = wrapBindings(globalBindings, 0) - - private val preventGlobalsExceptInFrom = - StaticTypeVisitorTransformConstraints.PREVENT_GLOBALS_EXCEPT_IN_FROM in constraints - - private val preventGlobalsInNestedQueries = - StaticTypeVisitorTransformConstraints.PREVENT_GLOBALS_IN_NESTED_QUERIES in constraints - - /** Captures a [StaticType] and the depth at which it is bound. */ - private data class TypeAndDepth(val type: StaticType, val depth: Int) - - /** Indicates the scope at which a particular bind was found. */ - private enum class BindingScope { - - /** Describes a binding to a variable defined within the same statement. */ - LOCAL, - - /** - * Describes a binding to a variable defined within the overall scope - * of a statement. With nested statements, a variable binding from an - * outer scope would be [LEXICAL], not [LOCAL]. - */ - LEXICAL, - - /** - * A binding to a variable defined in the DB environment, not within - * the user's statement. - */ - GLOBAL; - } - - /** Captures a [StaticType] and what scope it is bound within. */ - private data class TypeAndScope(val type: StaticType, val scope: BindingScope) - - /** Defines the current scope search order--i.e. globals first when in a FROM source, lexical everywhere else. */ - private enum class ScopeSearchOrder { - LEXICAL, - GLOBALS_THEN_LEXICAL - } - - /** - * @param parentEnv the enclosing bindings - * @param currentScopeDepth How deeply nested the current scope is. - * - 0 means we are in the global scope - * - 1 is the top-most statement with a `FROM` clause (i.e. select-from-where or DML operation), - * - Values > 1 are for each subsequent level of nested sub-query. - */ - private inner class VisitorTransform( - private val parentEnv: Bindings, - private val currentScopeDepth: Int - ) : VisitorTransformBase() { - - /** Specifies the current scope search order--default is LEXICAL. */ - private var scopeOrder = ScopeSearchOrder.LEXICAL - - private val localsMap = mutableMapOf() - - // TODO this used to use a wrapper over localsMap, but that API no longer exists, we should figure something - // more reasonable out later for this at some point, but this is good enough for now - private var localsOnlyEnv = wrapBindings(Bindings.ofMap(localsMap), currentScopeDepth) - - // because of the mutability of the above reference, we need to encode the lookup as a thunk - private val currentEnv = Bindings.over { localsOnlyEnv[it] }.delegate(parentEnv) - - private var containsJoin = false - - /** Set to true after any FROM source has been visited.*/ - private var fromVisited = false - - /** - * In short, after the FROM sources have been visited, this is set to the name if-and-only-if there is - * a single from source. Otherwise, it is null. - */ - private var singleFromSourceName: String? = null - - private fun singleFromSourceRef(sourceName: String, metas: MetaContainer): PartiqlAst.Expr.Id { - val sourceType = currentEnv[BindingName(sourceName, BindingCase.SENSITIVE)] - ?: throw IllegalArgumentException("Could not find type for single FROM source variable") - - return PartiqlAst.build { - id( - sourceName, - caseSensitive(), - localsFirst(), - metas + metaContainerOf(StaticTypeMeta.TAG to StaticTypeMeta(sourceType.type)) - ) - } - } - - private fun PartiqlAst.Expr.Id.toPathExpr(): PartiqlAst.PathStep.PathExpr = - PartiqlAst.build { - pathExpr(index = lit(ion.newString(name.text).toIonElement(), this@toPathExpr.extractSourceLocation()), case = case, metas = metas) - } - - private fun errUnboundName(name: String, case: PartiqlAst.CaseSensitivity, metas: MetaContainer): Nothing = - throw SemanticException( - "No such variable named '$name'", - when (case) { - is PartiqlAst.CaseSensitivity.CaseInsensitive -> ErrorCode.SEMANTIC_UNBOUND_BINDING - is PartiqlAst.CaseSensitivity.CaseSensitive -> ErrorCode.SEMANTIC_UNBOUND_QUOTED_BINDING - }, - propertyValueMapOf( - Property.BINDING_NAME to name - ).addSourceLocation(metas) - ) - - private fun errIllegalGlobalVariableAccess(name: String, metas: MetaContainer): Nothing = throw SemanticException( - "Global variable access is illegal in this context", - ErrorCode.SEMANTIC_ILLEGAL_GLOBAL_VARIABLE_ACCESS, - propertyValueMapOf( - Property.BINDING_NAME to name - ).addSourceLocation(metas) - ) - - private fun errAmbiguousName(name: String, metas: MetaContainer): Nothing = throw SemanticException( - "A variable named '$name' was already defined in this scope", - ErrorCode.SEMANTIC_AMBIGUOUS_BINDING, - propertyValueMapOf( - Property.BINDING_NAME to name - ).addSourceLocation(metas) - ) - - private fun errUnimplementedFeature(name: String, metas: MetaContainer? = null): Nothing = throw SemanticException( - "Feature not implemented yet", - ErrorCode.UNIMPLEMENTED_FEATURE, - propertyValueMapOf( - Property.FEATURE_NAME to name - ).also { - if (metas != null) { - it.addSourceLocation(metas) - } - } - ) - - private fun addLocal(name: String, type: StaticType, metas: MetaContainer) { - val existing = localsOnlyEnv[BindingName(name, BindingCase.INSENSITIVE)] - if (existing != null) { - errAmbiguousName(name, metas) - } - localsMap[name] = type - // this requires a new instance because of how [Bindings.ofMap] works - localsOnlyEnv = wrapBindings(Bindings.ofMap(localsMap), currentScopeDepth) - } - - override fun transformExprCallAgg(node: PartiqlAst.Expr.CallAgg): PartiqlAst.Expr { - return PartiqlAst.build { - callAgg_( - // do not transform the funcExpr--as this is a symbolic name in another namespace (AST is over generalized here) - node.setq, - node.funcName, - transformExpr(node.arg), - transformMetas(node.metas) - ) - } - } - - private fun Bindings.lookupBinding(bindingName: BindingName): TypeAndScope? = - when (val match = this[bindingName]) { - null -> null - else -> { - val (type, depth) = match - val scope = when { - depth == 0 -> BindingScope.GLOBAL - depth < currentScopeDepth -> BindingScope.LEXICAL - depth == currentScopeDepth -> BindingScope.LOCAL - else -> error("Unexpected: depth should never be > currentScopeDepth") - } - TypeAndScope(type, scope) - } - } - - /** - * Encapsulates variable reference lookup, layering the scoping - * rules from the exclusions given the current state. - * - * Returns an instance of [TypeAndScope] if the binding was found, otherwise returns null. - */ - private fun findBind(bindingName: BindingName, scopeQualifier: PartiqlAst.ScopeQualifier): TypeAndScope? { - // Override the current scope search order if the var is lexically qualified. - val overridenScopeSearchOrder = when (scopeQualifier) { - is PartiqlAst.ScopeQualifier.LocalsFirst -> ScopeSearchOrder.LEXICAL - is PartiqlAst.ScopeQualifier.Unqualified -> this.scopeOrder - } - val scopes: List> = when (overridenScopeSearchOrder) { - ScopeSearchOrder.GLOBALS_THEN_LEXICAL -> listOf(globalEnv, currentEnv) - ScopeSearchOrder.LEXICAL -> listOf(currentEnv, globalEnv) - } - - return scopes - .asSequence() - .mapNotNull { it.lookupBinding(bindingName) } - .firstOrNull() - } - - /** - * The actual variable resolution occurs in this method--all other parts of the - * [StaticTypeVisitorTransform] support what's happening here. - */ - override fun transformExprId(node: PartiqlAst.Expr.Id): PartiqlAst.Expr { - val bindingName = BindingName(node.name.text, node.case.toBindingCase()) - - val found = findBind(bindingName, node.qualifier) - - val singleBinding = singleFromSourceName // Copy to immutable local variable to enable smart-casting - - // If we didn't find a variable with that name... - if (found == null) { - // ...and there is a single from-source in the current query, then transform it into - // a path expression, i.e. `SELECT foo FROM bar AS b` becomes `SELECT b.foo FROM bar AS b` - return when { - // If there's a single from source... - singleBinding != null -> { - makePathIntoFromSource(singleBinding, node) - } - else -> { - // otherwise there is more than one from source so an undefined variable was referenced. - errUnboundName(node.name.text, node.case, node.metas) - } - } - } - - if (found.scope == BindingScope.GLOBAL) { - when { - // If we found a variable in the global scope but a there is a single - // from source, we should transform to this to path expression anyway and pretend - // we didn't match the global variable. - singleBinding != null -> { - return makePathIntoFromSource(singleBinding, node) - } - preventGlobalsExceptInFrom && fromVisited -> { - errIllegalGlobalVariableAccess(bindingName.name, node.metas) - } - preventGlobalsInNestedQueries && currentScopeDepth > 1 -> { - errIllegalGlobalVariableAccess(bindingName.name, node.metas) - } - } - } - - val newScopeQualifier = when (found.scope) { - BindingScope.LOCAL, BindingScope.LEXICAL -> PartiqlAst.build { localsFirst() } - BindingScope.GLOBAL -> PartiqlAst.build { unqualified() } - } - - return PartiqlAst.build { - id_( - node.name, node.case, newScopeQualifier, - node.metas + metaContainerOf(StaticTypeMeta.TAG to StaticTypeMeta(found.type)) - ) - } - } - - /** - * Changes the specified variable reference to a path expression with the name of the variable as - * its first and only element. - */ - private fun makePathIntoFromSource(fromSourceAlias: String, node: PartiqlAst.Expr.Id): PartiqlAst.Expr.Path { - return PartiqlAst.build { - path( - singleFromSourceRef(fromSourceAlias, node.extractSourceLocation()), - listOf(node.toPathExpr()), - node.extractSourceLocation() - ) - } - } - - override fun transformExprPath(node: PartiqlAst.Expr.Path): PartiqlAst.Expr = - when (node.root) { - is PartiqlAst.Expr.Id -> super.transformExprPath(node).let { - it as PartiqlAst.Expr.Path - when (val root = it.root) { - // we started with a variable, that got turned into a path, normalize it - // SELECT x.y FROM tbl AS t --> SELECT ("t".x).y FROM tbl AS t --> SELECT "t".x.y FROM tbl AS t - is PartiqlAst.Expr.Path -> { - PartiqlAst.build { - path(root.root, root.steps + it.steps, it.metas) - } - } - - // nothing to do--the transform didn't change anything - else -> it - } - } - else -> super.transformExprPath(node) - } - - override fun transformFromSourceScan(node: PartiqlAst.FromSource.Scan): PartiqlAst.FromSource { - // we need to transform the source expression before binding the names to our scope - val from = super.transformFromSourceScan(node) - - node.atAlias?.let { - addLocal(it.text, StaticType.ANY, it.metas) - } - - node.byAlias?.let { - addLocal(it.text, StaticType.ANY, it.metas) - } - - val asSymbolicName = node.asAlias - ?: error( - "fromSourceLet.variables.asName is null. This wouldn't be the case if " + - "FromSourceAliasVisitorTransform was executed first." - ) - - addLocal(asSymbolicName.text, StaticType.ANY, asSymbolicName.metas) - - if (!containsJoin) { - fromVisited = true - if (currentScopeDepth == 1) { - singleFromSourceName = asSymbolicName.text - } - } - return from - } - - override fun transformFromSourceUnpivot(node: PartiqlAst.FromSource.Unpivot): PartiqlAst.FromSource { - // we need to transform the source expression before binding the names to our scope - val from = super.transformFromSourceUnpivot(node) - - node.atAlias?.let { - addLocal(it.text, StaticType.ANY, it.metas) - } - - node.byAlias?.let { - addLocal(it.text, StaticType.ANY, it.metas) - } - - val asSymbolicName = node.asAlias - ?: error( - "fromSourceLet.variables.asName is null. This wouldn't be the case if " + - "FromSourceAliasVisitorTransform was executed first." - ) - - addLocal(asSymbolicName.text, StaticType.ANY, asSymbolicName.metas) - - if (!containsJoin) { - fromVisited = true - if (currentScopeDepth == 1) { - singleFromSourceName = asSymbolicName.text - } - } - return from - } - - override fun transformLetBinding(node: PartiqlAst.LetBinding): PartiqlAst.LetBinding { - val binding = super.transformLetBinding(node) - addLocal(binding.name.text, StaticType.ANY, binding.name.metas) - return binding - } - - override fun transformFromSourceJoin(node: PartiqlAst.FromSource.Join): PartiqlAst.FromSource { - // this happens before FromSourceScan or FromSourceUnpivot gets hit - val outermostJoin = !containsJoin - containsJoin = true - - return super.transformFromSourceJoin(node) - .also { - if (outermostJoin) { - fromVisited = true - singleFromSourceName = null - } - } - } - - override fun transformFromSourceScan_expr(node: PartiqlAst.FromSource.Scan): PartiqlAst.Expr { - this.scopeOrder = ScopeSearchOrder.GLOBALS_THEN_LEXICAL - return transformExpr(node.expr).also { - this.scopeOrder = ScopeSearchOrder.LEXICAL - } - } - - override fun transformFromSourceUnpivot_expr(node: PartiqlAst.FromSource.Unpivot): PartiqlAst.Expr { - this.scopeOrder = ScopeSearchOrder.GLOBALS_THEN_LEXICAL - return transformExpr(node.expr).also { - this.scopeOrder = ScopeSearchOrder.LEXICAL - } - } - - override fun transformGroupBy(node: PartiqlAst.GroupBy): PartiqlAst.GroupBy { - errUnimplementedFeature("GROUP BY") - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - // a SELECT introduces a new scope, we evaluate the each from source - // which is correlated (and thus has visibility from the previous bindings) - return createTransformerForNestedScope().transformExprSelectEvaluationOrder(node) - } - - override fun transformStatementDml(node: PartiqlAst.Statement.Dml): PartiqlAst.Statement { - return createTransformerForNestedScope().transformDataManipulationEvaluationOrder(node) - } - - private fun createTransformerForNestedScope(): VisitorTransform { - return VisitorTransform(currentEnv, currentScopeDepth + 1) - } - - /** - * This function differs from the the overridden function only in that it does not attempt to resolve - * [PartiqlAst.DdlOp.CreateIndex.fields], which would be a problem because they contain [PartiqlAst.Expr.Id]s - * yet the fields/keys are scoped to the table and do not follow traditional lexical scoping rules. This - * indicates that [PartiqlAst.DdlOp.CreateIndex.fields] is incorrectly modeled as a [List<[PartiqlAst.Expr]>]. - */ - override fun transformDdlOpCreateIndex(node: PartiqlAst.DdlOp.CreateIndex): PartiqlAst.DdlOp = - PartiqlAst.build { - createIndex( - node.indexName, - node.fields, - transformMetas(node.metas) - ) - } - - /** - * This function differs from the the overridden function only in that it does not attempt to resolve - * [PartiqlAst.DdlOp.DropIndex.table], which would be a problem because index names are scoped to the table - * and do not follow traditional lexical scoping rules. This is not something the [StaticTypeVisitorTransform] - * is currently plumbed to deal with and also indicates that [PartiqlAst.DdlOp.DropIndex.table] is incorrectly - * modeled as a [PartiqlAst.Expr.Id]. - */ - override fun transformDdlOpDropIndex(node: PartiqlAst.DdlOp.DropIndex): PartiqlAst.DdlOp = - PartiqlAst.build { - dropIndex( - node.table, - node.keys, - transformMetas(node.metas) - ) - } - } - - // Use transformStatement since there's both SFW queries in addition to DDL and DML - override fun transformStatement(node: PartiqlAst.Statement): PartiqlAst.Statement = - VisitorTransform(wrapBindings(Bindings.empty(), 1), 0) - .transformStatement(node) - - private fun wrapBindings(bindings: Bindings, depth: Int): Bindings { - return Bindings.over { name -> - bindings[name]?.let { bind -> - TypeAndDepth(bind, depth) - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubqueryCoercionVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubqueryCoercionVisitorTransform.kt deleted file mode 100644 index e9411c13e8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubqueryCoercionVisitorTransform.kt +++ /dev/null @@ -1,163 +0,0 @@ -package org.partiql.lang.eval.visitors - -import org.partiql.lang.domains.PartiqlAst - -/** Coerce each SQL-style SELECT subquery to single value, if it occurs in a context that expects a single value -- as - * prescribed in SQL for scalar-expecting contexts and as outlined in Chapter 9 of the PartiQL specification. - * The coercion is done by wrapping each eligible SELECT in a call to the COLL_TO_SCALAR built-in. - * The coercion is context-dependent, in the sense that not every SELECT is coerced, - * but only a SELECT subquery in an appropriate context, while its coercion specifics depend on the context as well. */ -/* - * The implementation deals with context-dependency by using the visitor to find possible _contexts_ of possible SELECT - * subqueries (rather than the subqueries themselves) and then inspecting each context to find the subquery - * and coerce it, if eligible. (In this problem, a "context" is an AST node that can contain an eligible subquery.) - */ -class SubqueryCoercionVisitorTransform : VisitorTransformBase() { - - /* When an Expr E is reached during the visitor's traversal, - * - First, perform the visitor's transformation recursively on E's components. - * This will coerce eligible SELECTs deep in each E's subexpression, - * but won't coerce any E's direct subexpression itself, even if it is a SELECT. - * - Then, treat E as a context for a possible SELECT subquery. - * That is, detect each possible subexpression of E and, when the context is right - * and the subexpression is an SQL-style SELECT, coerce it. - * With this, a top-level expression is never coerced, whether it is a SELECT or not. - */ - override fun transformExpr(node: PartiqlAst.Expr): PartiqlAst.Expr { - val n = super.transformExpr(node) - return when (n) { - is PartiqlAst.Expr.Missing -> n - is PartiqlAst.Expr.Lit -> n - is PartiqlAst.Expr.Id -> n - is PartiqlAst.Expr.Parameter -> n - is PartiqlAst.Expr.SessionAttribute -> n - - is PartiqlAst.Expr.Not -> n.copy(expr = coerceToSingle(n.expr)) - is PartiqlAst.Expr.Pos -> n.copy(expr = coerceToSingle(n.expr)) - is PartiqlAst.Expr.Neg -> n.copy(expr = coerceToSingle(n.expr)) - - is PartiqlAst.Expr.Plus -> n.copy(operands = n.operands.map { coerceToSingle(it) }) - is PartiqlAst.Expr.Minus -> n.copy(operands = n.operands.map { coerceToSingle(it) }) - is PartiqlAst.Expr.Times -> n.copy(operands = n.operands.map { coerceToSingle(it) }) - is PartiqlAst.Expr.Divide -> n.copy(operands = n.operands.map { coerceToSingle(it) }) - is PartiqlAst.Expr.Modulo -> n.copy(operands = n.operands.map { coerceToSingle(it) }) - is PartiqlAst.Expr.Concat -> n.copy(operands = n.operands.map { coerceToSingle(it) }) - is PartiqlAst.Expr.And -> n.copy(operands = n.operands.map { coerceToSingle(it) }) - is PartiqlAst.Expr.Or -> n.copy(operands = n.operands.map { coerceToSingle(it) }) - is PartiqlAst.Expr.BitwiseAnd -> n.copy(operands = n.operands.map { coerceToSingle(it) }) - - is PartiqlAst.Expr.Eq -> n.copy(operands = coerceInComparisonOps(n.operands)) - is PartiqlAst.Expr.Ne -> n.copy(operands = coerceInComparisonOps(n.operands)) - is PartiqlAst.Expr.Gt -> n.copy(operands = coerceInComparisonOps(n.operands)) - is PartiqlAst.Expr.Gte -> n.copy(operands = coerceInComparisonOps(n.operands)) - is PartiqlAst.Expr.Lt -> n.copy(operands = coerceInComparisonOps(n.operands)) - is PartiqlAst.Expr.Lte -> n.copy(operands = coerceInComparisonOps(n.operands)) - - is PartiqlAst.Expr.Like -> n.copy(value = coerceToSingle(n.value), pattern = coerceToSingle(n.pattern)) - is PartiqlAst.Expr.Between -> n.copy(value = coerceToSingle(n.value), from = coerceToSingle(n.from), to = coerceToSingle(n.to)) - - is PartiqlAst.Expr.InCollection -> toSingleInInCollection(n) - - is PartiqlAst.Expr.Date -> n - is PartiqlAst.Expr.LitTime -> n - - is PartiqlAst.Expr.GraphMatch -> n.copy(expr = coerceToSingle(n.expr)) - - is PartiqlAst.Expr.Select -> - n.copy( - setq = n.setq, - project = toSingleInProject(n.project), - from = n.from, // per SQL, don't coerce subqueries that are data sources in FROM - fromLet = n.fromLet, // LET is PartiQL-specific, so allow binding to a non-scalar - where = n.where?.let { coerceToSingle(it) }, - group = n.group?.let { toSingleInGroup(it) }, - having = n.having?.let { coerceToSingle(it) }, - order = n.order?.let { toSingleInOrderby(it) }, - limit = n.limit?.let { coerceToSingle(it) }, - offset = n.offset?.let { coerceToSingle(it) } - ) - - // TODO Revisit the following expression forms re coercing or not, see https://github.com/partiql/partiql-spec/issues/42. - // The above expression forms make coercion decisions according to what is specified in Ch 9 of the PartiQL specification - // and they include all cases explicitly mentioned there. - // The cases below, upon literal reading of Ch 9, should fall into it blanket "always coerce" category. - // In some of these, it seems clear that the coercion should not happen: say BagOp (UNION, INTERSECT), - // or PartiQL-specific Bag, List. Others, in particular case expressions and function calls, need a careful look. - // In either case, these need to be addressed in the specification more explicitly. - is PartiqlAst.Expr.IsType -> n - is PartiqlAst.Expr.SimpleCase -> n - is PartiqlAst.Expr.SearchedCase -> n - is PartiqlAst.Expr.Struct -> n - is PartiqlAst.Expr.Bag -> n - is PartiqlAst.Expr.List -> n - is PartiqlAst.Expr.Sexp -> n - is PartiqlAst.Expr.BagOp -> n - - is PartiqlAst.Expr.Path -> n - is PartiqlAst.Expr.Call -> n - is PartiqlAst.Expr.CallAgg -> n - is PartiqlAst.Expr.CallWindow -> n - is PartiqlAst.Expr.Cast -> n - is PartiqlAst.Expr.CanCast -> n - is PartiqlAst.Expr.CanLosslessCast -> n - is PartiqlAst.Expr.NullIf -> n - is PartiqlAst.Expr.Coalesce -> n - is PartiqlAst.Expr.Timestamp -> TODO() - } - } - - /** Whenever the expression is an SQL-style select, wrap it with a singleton coercion. */ - private fun coerceToSingle(e: PartiqlAst.Expr): PartiqlAst.Expr = - when (e) { - is PartiqlAst.Expr.Select -> - if ((e.project is PartiqlAst.Projection.ProjectStar) || (e.project is PartiqlAst.Projection.ProjectList)) { - PartiqlAst.build { call("coll_to_scalar", e) } - } else e - else -> e - } - - private fun coerceToArray(e: PartiqlAst.Expr): PartiqlAst.Expr = - e // TODO: actual to-array coercion, like toSingle above - - /** Coerce the two operands of a comparison operation (<, =, ...), - * considering each as part of the context for the other, as prescribed in SQL. */ - private fun coerceInComparisonOps(operands: List): List { - fun isArrayLiteral(x: PartiqlAst.Expr): Boolean = x is PartiqlAst.Expr.List - val lhs = operands[0] - val rhs = operands[1] - val rest = operands.takeLast(operands.size - 2) // these are useless operands, but the AST carries them, so here we go - return when { - isArrayLiteral(lhs) && isArrayLiteral(rhs) -> operands - isArrayLiteral(lhs) -> listOf(lhs, coerceToArray(rhs)) + rest - isArrayLiteral(rhs) -> listOf(coerceToArray(lhs), rhs) + rest - else -> listOf(coerceToSingle(lhs), coerceToSingle(rhs)) + rest - } - } - - /** Only coerce the element(lhs) side of ` IN ` */ - private fun toSingleInInCollection(n: PartiqlAst.Expr.InCollection): PartiqlAst.Expr.InCollection { - val ops = n.operands - // only the first two operands are meaningful for IN, but the AST supports multiple, so here we go - return n.copy(operands = listOf(coerceToSingle(ops[0]), ops[1]) + ops.takeLast(ops.size - 2)) - } - - private fun toSingleInProject(p: PartiqlAst.Projection): PartiqlAst.Projection = - when (p) { - is PartiqlAst.Projection.ProjectList -> - p.copy( - projectItems = p.projectItems.map { i -> - when (i) { - is PartiqlAst.ProjectItem.ProjectExpr -> i.copy(expr = coerceToSingle(i.expr)) - else -> i - } - } - ) - else -> p - } - - private fun toSingleInGroup(g: PartiqlAst.GroupBy): PartiqlAst.GroupBy = - g.copy(keyList = g.keyList.copy(keys = g.keyList.keys.map { k -> k.copy(expr = coerceToSingle(k.expr)) })) - - private fun toSingleInOrderby(b: PartiqlAst.OrderBy): PartiqlAst.OrderBy = - b.copy(sortSpecs = b.sortSpecs.map { s -> s.copy(expr = coerceToSingle(s.expr)) }) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubstitutionVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubstitutionVisitorTransform.kt deleted file mode 100644 index 650ae1fdbb..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/SubstitutionVisitorTransform.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.eval.visitors - -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.extractSourceLocation - -/** - * Specifies an individual substitution to be performed by [SubstitutionVisitorTransform]. - * - * [target] will be replaced with [replacement]. If the original node has an instance of [SourceLocationMeta], that is - * copied to the replacement as well. - * - * [target] should have its metas stripped as metas will affect the results of the equivalence check. - */ -data class SubstitutionPair(val target: PartiqlAst.Expr, val replacement: PartiqlAst.Expr) - -/** - * Given a [Map] ([substitutions]), replaces every node of the AST that is - * equivalent to a [SubstitutionPair.target] with its corresponding [SubstitutionPair.replacement]. - * - * This class is `open` to allow subclasses to restrict the nodes to which the substitution should occur. - */ -open class SubstitutionVisitorTransform(protected val substitutions: Map) : VisitorTransformBase() { - - /** - * If [node] matches any of the target nodes in [substitutions], replaces the node with the replacement. - * - * If [node] has a [SourceLocationMeta], the replacement is cloned and the [SourceLocationMeta] is copied to the - * clone. - */ - override fun transformExpr(node: PartiqlAst.Expr): PartiqlAst.Expr = - substitutions[node]?.replacement?.copy(metas = node.extractSourceLocation()) - ?: super.transformExpr(node) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/VisitorTransformBase.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/VisitorTransformBase.kt deleted file mode 100644 index 594a26512a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/VisitorTransformBase.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.partiql.lang.eval.visitors - -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.util.checkThreadInterrupted - -/** - * Base-class for visitor transforms that provides additional functions outside of [PartiqlAst.VisitorTransform]. These - * functions are used to avoid infinite recursion when working with nested visitor instances and to change the rewrite - * order to follow the PartiQL evaluation order. - * - * All transforms should derive from this class instead of [PartiqlAst.VisitorTransform] so that they can - * be interrupted if they take a long time to process large ASTs. - */ -abstract class VisitorTransformBase : PartiqlAst.VisitorTransform() { - - override fun transformExpr(node: PartiqlAst.Expr): PartiqlAst.Expr { - checkThreadInterrupted() - return super.transformExpr(node) - } - - /** - * Transforms the [PartiqlAst.Expr.Select] expression following the PartiQL evaluation order. That is: - * - * 1. `FROM` - * 2. `LET` - * 3. `WHERE` - * 4. `GROUP BY` - * 5. `HAVING` - * 6. *projection* - * 7. `OFFSET` - * 8. `LIMIT` - * 9. The metas. - * - * This differs from [transformExprSelect], which executes following the written order of clauses. Also, this - * function can be used to apply a different nested visitor instance to [PartiqlAst.Expr.Select] nodes to avoid - * infinite recursion. - */ - fun transformExprSelectEvaluationOrder(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - val exclude = transformExprSelect_excludeClause(node) - val from = transformExprSelect_from(node) - val fromLet = transformExprSelect_fromLet(node) - val where = transformExprSelect_where(node) - val group = transformExprSelect_group(node) - val having = transformExprSelect_having(node) - val setq = transformExprSelect_setq(node) - val project = transformExprSelect_project(node) - val order = transformExprSelect_order(node) - val offset = transformExprSelect_offset(node) - val limit = transformExprSelect_limit(node) - val metas = transformExprSelect_metas(node) - return PartiqlAst.build { - PartiqlAst.Expr.Select( - setq = setq, - project = project, - excludeClause = exclude, - from = from, - fromLet = fromLet, - where = where, - group = group, - having = having, - order = order, - offset = offset, - limit = limit, - metas = metas - ) - } - } - - /** - * Transforms the [PartiqlAst.Statement.Dml] expression following the PartiQL evaluation order. That is: - * - * 1. `FROM` - * 2. `WHERE` - * 3. The DML operation - * 4. The metas - * - * This differs from [transformStatementDml], which executes following the written order of clauses. Also, this - * function can be used to apply a different nested visitor instances to [PartiqlAst.Statement.Dml] nodes to avoid - * infinite recursion. - */ - fun transformDataManipulationEvaluationOrder(node: PartiqlAst.Statement.Dml): PartiqlAst.Statement { - val from = node.from?.let { transformFromSource(it) } - val where = node.where?.let { transformStatementDml_where(node) } - val dmlOperations = transformDmlOpList(node.operations) - val returning = node.returning?.let { transformReturningExpr(it) } - val metas = transformMetas(node.metas) - - return PartiqlAst.build { - dml( - dmlOperations, - from, - where, - returning, - metas - ) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/VisitorTransforms.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/VisitorTransforms.kt deleted file mode 100644 index 6393ca6301..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/VisitorTransforms.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.partiql.lang.eval.visitors - -import org.partiql.lang.domains.PartiqlAst - -/** AST Normalization Passes. - * - * Returns a [PartiqlAst.VisitorTransform] requiring no external state for the basic functionality of compiling - * PartiQL queries. - * - * Note that this is a function because some of the underlying visitor transforms are stateful. - */ -fun basicVisitorTransforms() = PipelinedVisitorTransform( - // These visitor transforms do not depend on each other and can be executed in any order. - SelectListItemAliasVisitorTransform(), - FromSourceAliasVisitorTransform(), - GroupByItemAliasVisitorTransform(), - AggregateSupportVisitorTransform(), - OrderBySortSpecVisitorTransform(), - - // [GroupByPathExpressionVisitorTransform] and [SelectStarVisitorTransform] require: - // - the synthetic from source aliases added by [FromSourceAliasVisitorTransform] - // - The synthetic group by item aliases added by [GroupByItemAliasVisitorTransform] - GroupByPathExpressionVisitorTransform(), - SelectStarVisitorTransform(), - - SubqueryCoercionVisitorTransform(), -) - -/** A stateless visitor transform that returns the input. */ -@JvmField -internal val IDENTITY_VISITOR_TRANSFORM: PartiqlAst.VisitorTransform = object : VisitorTransformBase() { - override fun transformStatement(node: PartiqlAst.Statement): PartiqlAst.Statement = node -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/ExternalGraphReader.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/graph/ExternalGraphReader.kt deleted file mode 100644 index ae21552b8e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/ExternalGraphReader.kt +++ /dev/null @@ -1,159 +0,0 @@ -package org.partiql.lang.graph - -import com.amazon.ion.IonList -import com.amazon.ion.IonSequence -import com.amazon.ion.IonSexp -import com.amazon.ion.IonString -import com.amazon.ion.IonStruct -import com.amazon.ion.IonSymbol -import com.amazon.ion.IonSystem -import com.amazon.ion.IonValue -import com.amazon.ion.system.IonSystemBuilder -import com.amazon.ionschema.IonSchemaSystemBuilder -import com.amazon.ionschema.Schema -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.util.impl.ResourceAuthority -import java.io.File - -abstract class ExternalGraphException(override val message: String) : RuntimeException(message) -class GraphIonException(message: String) : ExternalGraphException(message) -class GraphValidationException(message: String) : ExternalGraphException(message) -class GraphReadException(message: String) : ExternalGraphException(message) - -internal typealias EdgeTriple = Triple - -/** A validator and reader for external graphs represented in Ion in accordance with the graph.isl schema. - */ -object ExternalGraphReader { - - // Constants for the graph schema and names used inside it. - private const val islSchemaFile = "graph.isl" - private const val islGraph = "Graph" - - private val ion: IonSystem = IonSystemBuilder.standard().build() - private val iss = IonSchemaSystemBuilder.standard() - .addAuthority(ResourceAuthority.getResourceAuthority(ion)) - .withIonSystem(ion) - .build() - private val graphSchema: Schema = iss.loadSchema(islSchemaFile) - private val graphType = graphSchema.getType(islGraph) - ?: error("Definition for type $islGraph not found in ISL schema $islSchemaFile") - - /** Validates an IonValue to be a graph according to the graph.isl ISL schema. */ - fun validate(graphIon: IonValue) { - val violations = graphType.validate(graphIon) - if (!violations.isValid()) - throw GraphValidationException("Ion data did not validate as a graph: \n$violations") - } - - fun validate(graphStr: String) { - val graphIon = ion.singleValue(graphStr) - validate(graphIon) - } - - fun validate(graphFile: File) { - val graphStr = graphFile.readText() - validate(graphStr) - } - - /** Given an IonValue, validates that it is a graph in accordance with graph.isl schema - * and loads it into memory as a SimpleGraph. */ - fun read(graphIon: IonValue): SimpleGraph { - validate(graphIon) - return readGraph(graphIon) - } - fun read(graphStr: String): SimpleGraph { - val graphIon: IonValue? - try { - graphIon = ion.singleValue(graphStr) - } catch (ex: RuntimeException) { - throw GraphIonException("Error while reading Ion for the graph${ex.message?.let { ":\n$it"} ?: "."}") - } - validate(graphIon!!) - return readGraph(graphIon) - } - - /* Entry point into the implementation of graph reading. - * It is assumed that the input Ion value has been validated w.r.t. graph ISL, - * so that defensive error checks are not needed. - */ - private fun readGraph(graphIon: IonValue): SimpleGraph { - val g = graphIon as IonStruct - val nds = g.get("nodes") as IonList - val eds = g.get("edges") as IonList - val nodes = readNodes(nds) - val (directed, undirected) = EdgeReader(nodes).readEdges(eds) - return SimpleGraph(nodes.values.toList(), directed, undirected) - } - - /* Returns a map from node IDs in the source graph to newly-built in-memory nodes. - * This map is used later to recognize node IDs used in edge definitions. - */ - private fun readNodes(nodesList: IonList): Map { - val pairs = nodesList.toList().map { readNode(it as IonStruct) } - val duplicates = pairs.map { it.first }.groupBy { it }.filterValues { it.size > 1 }.keys - if (duplicates.isNotEmpty()) throw GraphReadException( - "Identifiers used for more than one node: ${duplicates.joinToString()}." - ) - return pairs.toMap() - } - - private fun readNode(node: IonStruct): Pair { - val (id, labels, payload) = readCommon(node) - return Pair(id, SimpleGraph.Node(labels, payload)) - } - - /* Can be called on an IonStruct for either a graph node or an edge, - * to extract their components that are structured identically: id, labels, payload. */ - private fun readCommon(node: IonStruct): Triple, ExprValue> { - val id = (node.get("id")!! as IonSymbol).symbolValue().assumeText() - val lbs = node.get("labels") - val labels = - if (lbs == null) { emptySet() } else { (lbs as IonList).toList().map { (it as IonString).stringValue() }.toSet() } - val pld = node.get("payload") - val payload = - if (pld == null) { ExprValue.nullValue } else ExprValue.of(pld) - return Triple(id, labels, payload) - } - - /* EdgeReader class is a scoping trick, to avoid threading the [allNodes] argument through the remaining functions. */ - private class EdgeReader(val allNodes: Map) { - - internal fun readEdges(edgesList: IonList): Pair>, List>> { - val dirs = mutableListOf>() - val undirs = mutableListOf>() - val triples = edgesList.toList().map { readEdge(it as IonStruct) } - triples.forEach { - val (n1, e, n2) = it - when (e) { - is SimpleGraph.EdgeDirected -> dirs += Triple(n1, e, n2) - is SimpleGraph.EdgeUndir -> undirs += Triple(n1, e, n2) - } - } - return Pair(dirs.toList(), undirs.toList()) - } - - internal fun readEdge(edge: IonStruct): EdgeTriple { - val (id, labels, payload) = readCommon(edge) - val ends = edge.get("ends")!! as IonSexp - val n1 = endNode(ends, 0) - val marker = (ends.get(1) as IonSymbol).symbolValue().assumeText() - val n2 = endNode(ends, 2) - return when (marker) { - "--" -> Triple(n1, SimpleGraph.EdgeUndir(labels, payload), n2) - "->" -> Triple(n1, SimpleGraph.EdgeDirected(labels, payload), n2) - "<-" -> Triple(n2, SimpleGraph.EdgeDirected(labels, payload), n1) // flip - else -> throw GraphReadException( - "BUG: At edge $id, directionality marker not recognized: $marker" - ) - } - } - - internal fun endNode(seq: IonSequence, idx: Int): SimpleGraph.Node { - val id = (seq.get(idx) as IonSymbol).symbolValue().assumeText() - return allNodes.getOrElse(id) { - throw GraphReadException("Node id $id is used in an edge, but it was not defined as a node") - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/GpmlTranslator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/graph/GpmlTranslator.kt deleted file mode 100644 index 7c715b0541..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/GpmlTranslator.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.partiql.lang.graph - -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.graph.GpmlTranslator.normalizeElemList - -/** Translate an AST graph pattern into a "plan spec" to be executed by the graph engine. - * Currently, the only non-trivial aspect is making sure (in [normalizeElemList]) that node and edge elements alternate. - * This (as well as the plan specs) is expected to become more sophisticated - * as more graph pattern features are supported (esp. quantifiers and alternation). - */ -object GpmlTranslator { - - /** The main entry point into the translator. */ - fun translateGpmlPattern(gpml: PartiqlAst.GpmlPattern): MatchSpec { - - if (gpml.selector != null) TODO("Evaluation of GPML selectors is not yet supported") - return MatchSpec(gpml.patterns.map { StrideSpec(normalizeElemList(translatePathPat(it))) }) - } - - fun translatePathPat(path: PartiqlAst.GraphMatchPattern): List { - if (path.prefilter != null || path.quantifier != null || path.restrictor != null || path.variable != null) - TODO("Not yet supported in evaluating a GPML path pattern: prefilters, quantifiers, restrictors, binder variables.") - return path.parts.flatMap { translatePartPat(it) } - } - - fun translatePartPat(part: PartiqlAst.GraphMatchPatternPart): List = - when (part) { - is PartiqlAst.GraphMatchPatternPart.Node -> - listOf(translateNodePat(part)) - is PartiqlAst.GraphMatchPatternPart.Edge -> - listOf(translateEdgePat(part)) - is PartiqlAst.GraphMatchPatternPart.Pattern -> - translatePathPat(part.pattern) - } - - fun translateNodePat(node: PartiqlAst.GraphMatchPatternPart.Node): NodeSpec { - if (node.prefilter != null) TODO("Not yet supported in evaluating a GPML node pattern: prefilter.") - return NodeSpec( - binder = node.variable?.text, - label = translateLabels(node.label) - ) - } - - fun translateEdgePat(edge: PartiqlAst.GraphMatchPatternPart.Edge): EdgeSpec { - if (edge.prefilter != null || edge.quantifier != null) - TODO("Not yet supported in evaluating a GPML edge pattern: prefilter, quantifier.") - return EdgeSpec( - binder = edge.variable?.text, - label = translateLabels(edge.label), - dir = translateDirection(edge.direction) - ) - } - - fun translateLabels(labelSpec: PartiqlAst.GraphLabelSpec?): LabelSpec { - return when (labelSpec) { - null -> LabelSpec.Wildcard - is PartiqlAst.GraphLabelSpec.GraphLabelName -> LabelSpec.Name(labelSpec.name.text) - is PartiqlAst.GraphLabelSpec.GraphLabelWildcard -> LabelSpec.Wildcard - else -> TODO("Not yet supported graph label pattern: $labelSpec") - } - } - - fun translateDirection(dir: PartiqlAst.GraphMatchDirection): DirSpec = - when (dir) { - is PartiqlAst.GraphMatchDirection.EdgeLeft -> DirSpec.DirL__ - is PartiqlAst.GraphMatchDirection.EdgeUndirected -> DirSpec.Dir_U_ - is PartiqlAst.GraphMatchDirection.EdgeRight -> DirSpec.Dir__R - is PartiqlAst.GraphMatchDirection.EdgeLeftOrUndirected -> DirSpec.DirLU_ - is PartiqlAst.GraphMatchDirection.EdgeUndirectedOrRight -> DirSpec.Dir_UR - is PartiqlAst.GraphMatchDirection.EdgeLeftOrRight -> DirSpec.DirL_R - is PartiqlAst.GraphMatchDirection.EdgeLeftOrUndirectedOrRight -> DirSpec.DirLUR - } - - /** Make sure there is proper alternation of NodeSpec and EdgeSpec entries, - * by inserting a [NodeSpec] between adjacent [EdgeSpec]s. - * TODO: Deal with adjacent [NodeSpec]s -- by "unification" or prohibit. - */ - fun normalizeElemList(elems: List): List { - val fillerNode = NodeSpec(null, LabelSpec.Wildcard) - val normalized = mutableListOf() - var expectNode = true - for (x in elems) { - if (expectNode) { - when (x) { - is NodeSpec -> { normalized.add(x); expectNode = false } - is EdgeSpec -> { normalized.add(fillerNode); normalized.add(x) } - } - } else { // expectNode == false - when (x) { - is NodeSpec -> TODO("Deal with adjacent nodes in a pattern. Unify? Prohibit?") - is EdgeSpec -> { normalized.add(x); expectNode = true } - } - } - } - if (expectNode) normalized.add(fillerNode) - return normalized.toList() - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/Graph.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/graph/Graph.kt deleted file mode 100644 index ee203423ac..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/Graph.kt +++ /dev/null @@ -1,76 +0,0 @@ -package org.partiql.lang.graph - -import org.partiql.lang.eval.ExprValue - -/** This is an "external" interface to a graph data value, - * providing functionality needed for a pattern-matching processor. - * The intent is to come up with something that can be implemented by different "platforms" - * in different ways. - * There are only minimal assumptions about the underlying implementation of - * graph nodes and edges: they must provide access to labels and payloads - * and the == equality must distinguish and equate them properly. - * In particular, there is no node-edge-node "pointer" navigation. - * The graph's structure is exposed only through the "scan" functions for getting - * adjacent nodes and edges satisfying certain criteria. - * - * TODO: - * - Expand "criteria" beyond label specs, to include predicates ("prefilters"), - * perhaps as call-back lambdas. - * - Instead of returning results as [List]s, consider something lazy/streaming, perhaps [Sequence]. - */ -interface Graph { - - interface Elem { - val labels: Set - val payload: ExprValue - } - - interface Node : Elem - interface Edge : Elem - interface EdgeDirected : Edge - interface EdgeUndir : Edge - - /** Get all the nodes conforming to a label specification. */ - fun scanNodes(spec: LabelSpec): List - - /** Get undirected edges (and their adjacent nodes) whose labels satisfy the given specifications. - * Spec Triple(x, _, y) can be used to compute both patterns (x)~(y) and (y)~(x). - * An undirected edge a---b is matched twice, returning x=a, y=b and x=b, y=a. - */ - fun scanUndir(spec: Triple): List> - - /** Get directed edges (and their adjacent nodes) whose labels satisfy the given specifications, - * when the requested direction *agrees* with the one at which an edge is defined. - * Edge a --> b matches spec Triple(a, _, b), aka a `--)` b or b `(--` a, - * and gets returned as Triple(a, _ , b). */ - fun scanDirectedStraight(spec: Triple): List> - - /** Get directed edges (and their adjacent nodes) whose labels satisfy the given specifications, - * when the requested direction is *opposite* to the one at which an edge is defined. - * Edge a --> b matches spec Triple(b, _, a), aka b `--)` a or a `(--` b, - * and gets returned as Triple(b, _ , a). */ - fun scanDirectedFlipped(spec: Triple): List> - - /** Get directed edges without regard for the direction at which they point. - * Spec Triple(x, _, y) can be used to compute both patterns (x)<->(y) and (y)<->(x). - * A directed edge a --> b is matched twice, returning x=a, y=b and x=b, y=a. - * - * The result of this method can be obtained by combining results of - * [scanDirectedStraight] and [scanDirectedFlipped], - * but [scanDirectedBlunt] is meant to be implemented with one pass over the data. - */ - fun scanDirectedBlunt(spec: Triple): List> -} - -/** Label specifications for selecting graph elements (nodes or edges) - * based on labels at them. - */ -sealed class LabelSpec { - /** A graph element (node or edge) matches when its label set contains [name]. */ - data class Name(val name: String) : LabelSpec() - - /** A graph element matches as long as it has a label. */ - object Wildcard : LabelSpec() - - // TODO: more LabelSpec features: alternation, negation, string patterns, ... -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/GraphEngine.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/graph/GraphEngine.kt deleted file mode 100644 index 7c34cd122c..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/GraphEngine.kt +++ /dev/null @@ -1,206 +0,0 @@ -package org.partiql.lang.graph - -import org.partiql.lang.util.tail - -object GraphEngine { - - /** Compute any [StepSpec] (an elementary node-edge-node path, in any direction), - * by combining the scan methods from [Graph]. - * TODO: Consider making this method as *the* API of Graph, - * as it is, by itself, tirned out to be sufficient, so far as a "window" in a graph - * for the needs of this engine's implementation. - */ - fun Graph.getMatchingSteps(spec: StepSpec): List> { - val (dirSpec, tripleSpec) = spec - val result = - (if (dirSpec.wantUndir) this.scanUndir(tripleSpec) else emptyList()) + - (if (dirSpec.wantLeft && dirSpec.wantRight) this.scanDirectedBlunt(tripleSpec) else emptyList()) + - (if (!dirSpec.wantLeft && dirSpec.wantRight) this.scanDirectedStraight(tripleSpec) else emptyList()) + - (if (dirSpec.wantLeft && !dirSpec.wantRight) this.scanDirectedFlipped(tripleSpec) else emptyList()) - return result - } - - /** The entry point for computing a graph pattern (translated to the [MatchSpec] "plan") - * against a graph. */ - fun evaluate(graph: Graph, matchSpec: MatchSpec): MatchResult { - val strideResults = matchSpec.strides.map { evaluateStride(graph, it) } - return joinStridesOnBinders(strideResults) - } - - private fun evaluateStride(graph: Graph, stride: StrideSpec): StrideResult { - if (stride.elems.size == 1) return evaluateNodeStride(graph, stride) - - val plan = planStride(stride) - check(stride == restoreStrideSpec(plan)) { - "Bad stride plan, not equivalent to the original stride." - } - return evaluatePlan(graph, plan) - } - - // This is for a node-only pattern like (g MATCH (x)). TODO: Something less ugly / more unified with the rest? - private fun evaluateNodeStride(graph: Graph, stride: StrideSpec): StrideResult { - check(stride.elems.size == 1) - return when (val node = stride.elems[0]) { - is EdgeSpec -> error("Bug: evaluateNodeStride should not be called on an EdgeSpec") - is NodeSpec -> StrideResult( - stride, - // toSet contributes to the deduplication step of Section 6.5 in the GPML paper - graph.scanNodes(node.label).toSet().map { Stride(listOf(it)) }.toSet() - ) - } - } - - /** Determine the order of joining the steps in a stride. - * Assumes that [stride] contains a properly alternating list: n, e, n, ..., e, n. - * - * At the moment, this just takes one straightforward order. - * Conceivably, this can become something more sophisticated. - * Would it be more appropriate to do this planing during GPML translation - * (in case there is more information available there for guiding the planning?) - */ - private fun planStride(stride: StrideSpec): StrideTree { - check(stride.elems.size >= 3) - - fun leafFrom3(elems: List): StrideLeaf { - check(elems[0] is NodeSpec) - check(elems[1] is EdgeSpec) - check(elems[2] is NodeSpec) - return StrideLeaf(StrideSpec(elems.take(3))) - } - - fun planRightLeaning(elems: List): StrideTree = - when (elems.size) { - 0, 1, 2 -> error("Bug: planRightLeaning should not be called on a spec shorter than 3 ") - 3 -> leafFrom3(elems) - else -> StrideJoin( // Note: 2nd node (3rd element) participates in both sides of the join - leafFrom3(elems), - planRightLeaning(elems.drop(2)) - ) - } - - return planRightLeaning(stride.elems) - } - - private fun restoreStrideSpec(strideTree: StrideTree): StrideSpec { - fun restore(tree: StrideTree): List = - when (tree) { - is StrideLeaf -> tree.stride.elems - is StrideJoin -> { - val left = restore(tree.left) - val right = restore(tree.right) - check(left.last() is NodeSpec) - check(right.first() is NodeSpec) - check(left.last() == right.first()) - left + right.tail - } - } - return StrideSpec(restore(strideTree)) - } - - /** Perform graph scans and joins, as the [StrideTree] plan specifies. */ - private fun evaluatePlan(graph: Graph, plan: StrideTree): StrideResult { - return when (plan) { - is StrideLeaf -> { - val step = plan.stride.elems - check(step.size == 3, { "A leaf stride in a StrideTree plan must have exactly 3 elements" }) - val lft = step[0] as NodeSpec - val edg = step[1] as EdgeSpec - val rgt = step[2] as NodeSpec - val stepSpec = StepSpec(edg.dir, Triple(lft.label, edg.label, rgt.label)) - val triples = graph.getMatchingSteps(stepSpec) - // if the same variable is used in the step's [NodeSpec]s, it should bind to the same node: - val bindCheck: (Triple) -> Boolean = - if (lft.binder != null && rgt.binder != null && lft.binder == rgt.binder) { - triple -> - triple.first == triple.third - } else { _ -> true } - val prunedTriples = triples.filter { bindCheck(it) } - StrideResult( - plan.stride, - // toSet contributes to the deduplication step of Section 6.5 in the GPML paper - prunedTriples.toSet().map { Stride(listOf(it.first, it.second, it.third)) }.toSet() - ) - } - - is StrideJoin -> { - joinAdjacentStrides( - evaluatePlan(graph, plan.left), - evaluatePlan(graph, plan.right) - ) - } - } - } - - fun joinAdjacentStrides(left: StrideResult, right: StrideResult): StrideResult { - val leftSpec = left.spec.elems - val rightSpec = right.spec.elems - check(leftSpec.last() == rightSpec.first()) - check(rightSpec.first() is NodeSpec) - val joinedSpec = leftSpec + rightSpec.tail - val joinCondition = stridesJoinable(left.spec, right.spec) - - val joined = mutableSetOf() - for (lft in left.result) { - for (rgt in right.result) { - if (joinCondition(lft, rgt)) { - joined.add(Stride(lft.elems + rgt.elems.tail)) - } - } - } - return StrideResult( - StrideSpec(joinedSpec), - joined.toSet() - ) - } - - /** Given two [StrideSpec]s for adjacent strides, - * formulate a predicate for checking whether two adjacent strides are joinable. - * They are joinable if - * - The last element of the left stride is the same as the first element of the right one. - * - For each common binding variable, the two strides hold the same element. - * This assumes that each of the two strides has been properly joined before, - * in that if a variable occurs in the stride multiple times, it already binds to the same element in the stride. - */ - fun stridesJoinable(leftSpec: StrideSpec, rightSpec: StrideSpec): (Stride, Stride) -> Boolean { - // Find variables that are common between the left and right stride specs and record their indexes. - // Note: even though a variable x can be repeated multiple times within a stride spec, - // we only need to note its first occurrence in each -- because of the above assumption. - val joinVars = mutableMapOf>() - for ((lftIdx, lftEltSpec) in leftSpec.elems.withIndex()) { - lftEltSpec.binder?.let { lftVar -> - if (! joinVars.keys.contains(lftVar)) { - val rgtIdx = rightSpec.elems.indexOfFirst { rgtElem -> rgtElem.binder?.let { it == lftVar } ?: false } - if (rgtIdx != -1) - joinVars[lftVar] = lftIdx to rgtIdx - } - } - } - // The index pairs are points where strides being joined must have the same elements. - val joinPoints: List> = joinVars.values + - Pair(leftSpec.elems.lastIndex, 0) // always join on last left and first right - adjacency - - // Now can formulate an index-based join condition on strides: - return { leftStride: Stride, rightStride: Stride -> - /** Relies on [Graph.Elem]s having proper, pointer-based, equality */ - joinPoints.all { (lft, rgt) -> leftStride.elems[lft] == rightStride.elems[rgt] } - } - } - - /** Joins results of stride matches on distinct path patterns of a graph pattern. - * In general, this is a cartesian product, but it is whittled down by the requirement - * that a given binder variable binds to the same graph element in each individual answer. - */ - fun joinStridesOnBinders(strides: List): MatchResult { - return when (strides.size) { - 0 -> { error("Bug: should not call joinStridesOnBinders on a zero-length list of stride results.") } - 1 -> { - val (spec, res) = strides[0] - MatchResult( - listOf(spec), - res.map { listOf(it) } - ) - } - else -> TODO("Later: non-trivial join of strides on binders (when there is two or more strides).") - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/MatchSpec.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/graph/MatchSpec.kt deleted file mode 100644 index dd5b66cb30..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/MatchSpec.kt +++ /dev/null @@ -1,69 +0,0 @@ -package org.partiql.lang.graph - -// Definitions in this file can be seen as versions of GPML patterns that are more convenient to use -// for the computations in [GraphEngine]. -// They may either turn out redundant or prove to be beginnings of something like "plans" for graph queries. -// This is yet to be thought out. - -/** Specification of an edge direction. - * The boolean fields at the enum values capture their semantics in a way that is useful for - * "compiling" them to graph scans. - */ -enum class DirSpec(val wantLeft: Boolean, val wantUndir: Boolean, val wantRight: Boolean) { - DirL__(true, false, false), // <-- - Dir_U_(false, true, false), // ~~~ - Dir__R(false, false, true), // --> - DirLU_(true, true, false), // <~~ - Dir_UR(false, true, true), // ~~> - DirL_R(true, false, true), // <-> - DirLUR(true, true, true), // --- (why isn't it `<~>` ?!) -} - -/** A step in a graph is a triple of adjacent node, edge, node. - * A StepSpec describes a set of steps of interest. */ -data class StepSpec(val dirSpec: DirSpec, val tripleSpec: Triple) - -/** A variable in a graph pattern binding a node or an edge. */ -typealias Variable = String - -sealed class ElemSpec { - abstract val binder: Variable? -} -data class NodeSpec(override val binder: Variable?, val label: LabelSpec) : ElemSpec() -data class EdgeSpec(override val binder: Variable?, val label: LabelSpec, val dir: DirSpec) : ElemSpec() - -/** A stride is a sequence like - * node, edge, node, edge, ..., node - * that is, strictly alternating nodes and edges, starting and ending with a node. - * It is used as an intermediate step in computing path matches. - */ -data class Stride(val elems: List) - -/** Translation of a path pattern into a "plan" for [GraphEngine]. - */ -data class StrideSpec(val elems: List) - -/** The result of matching a [StrideSpec] in a graph. - * Each [Stride] in [result] is one valid match for [spec]. - * Keeping [spec] within the result is for maintaining the association between - * [Variable]s and graph elements in [result] that the variables matched. */ -data class StrideResult(val spec: StrideSpec, val result: Set) - -/** Translation of a graph match pattern -- a collection of path patterns -- - * into a "plan" for [GraphEngine]. */ -data class MatchSpec(val strides: List) { - init { check(strides.size > 0) } -} - -/** The result of matching [specs], a list of stride specs derived from a list of path patterns. - * The result is a "table" where each "column" is headed by a stride spec from [specs] and contains matching strides. - * That is, each row contains strides matching each stride spec. - */ -data class MatchResult(val specs: List, val result: List>) - -/** A [StrideTree] is a plan for computing matches for a stride. - * Joins needed to compute a stride can be performed in different orders; - * a [StrideTree] represents a chosen order. */ -sealed class StrideTree -data class StrideJoin(val left: StrideTree, val right: StrideTree) : StrideTree() -data class StrideLeaf(val stride: StrideSpec) : StrideTree() diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/SimpleGraph.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/graph/SimpleGraph.kt deleted file mode 100644 index cffda268e9..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/graph/SimpleGraph.kt +++ /dev/null @@ -1,88 +0,0 @@ -package org.partiql.lang.graph - -import org.partiql.lang.eval.ExprValue - -internal typealias Labels = Set - -/** A straightforward implementation of in-memory graphs. */ -class SimpleGraph( - val nodes: List, - val directed: List>, - val undir: List> // order of Nodes in the Triple "doesn't matter" -) : Graph { - - // Intentionally, not a data class -- want to use pointer equality - class Node( - override val labels: Labels, - override val payload: ExprValue - ) : Graph.Node - - abstract class Edge : Graph.Edge - - class EdgeDirected( - override val labels: Labels, - override val payload: ExprValue - ) : Edge(), Graph.EdgeDirected - - class EdgeUndir( - override val labels: Labels, - override val payload: ExprValue - ) : Edge(), Graph.EdgeUndir - - companion object { - val empty: Graph = SimpleGraph(emptyList(), emptyList(), emptyList()) - } - - private fun labelsMatchSpec(labels: Set, spec: LabelSpec): Boolean = - when (spec) { - LabelSpec.Wildcard -> true - is LabelSpec.Name -> labels.contains(spec.name) - } - - private fun Graph.Elem.matches(labelSpec: LabelSpec): Boolean = - labelsMatchSpec(this.labels, labelSpec) - - override fun scanNodes(spec: LabelSpec): List { - return nodes.filter { it.matches(spec) } - } - - override fun scanDirectedStraight(spec: Triple): List> { - val (srcSpec, edgeSpec, dstSpec) = spec - return directed.filter { - val (src, edge, dst) = it - src.matches(srcSpec) && edge.matches(edgeSpec) && dst.matches(dstSpec) - } - } - - override fun scanDirectedFlipped(spec: Triple): List> { - val (srcSpec, edgeSpec, dstSpec) = spec - return directed.asSequence().filter { - val (dst, edge, src) = it // data triple flipped, for filtering - src.matches(srcSpec) && edge.matches(edgeSpec) && dst.matches(dstSpec) - }.map { Triple(it.third, it.second, it.first) }.toList() // flipped agan, for the result - } - - private fun getBlunt( - triples: List>, - spec: Triple - ): List> { - val (srcSpec, edgeSpec, dstSpec) = spec - val selected = mutableListOf>() - for (t in triples) { - val (src, edge, dst) = t - if (edge.matches(edgeSpec)) { - if (src.matches(srcSpec) && dst.matches(dstSpec)) - selected.add(t) - if ((src.matches(dstSpec) && dst.matches(srcSpec))) - selected.add(Triple(dst, edge, src)) - } - } - return selected.toList() - } - - override fun scanDirectedBlunt(spec: Triple): List> = - getBlunt(directed, spec) - - override fun scanUndir(spec: Triple): List> = - getBlunt(undir, spec) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/Errors.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/Errors.kt deleted file mode 100644 index ec428096df..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/Errors.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.partiql.lang.planner - -import org.partiql.errors.Problem -import org.partiql.errors.ProblemHandler -import org.partiql.errors.UNKNOWN_PROBLEM_LOCATION -import org.partiql.lang.eval.physical.sourceLocationMeta -import org.partiql.pig.runtime.DomainNode - -/** - * Convenience function that logs [PlanningProblemDetails.UnimplementedFeature] to the receiver [ProblemHandler] - * handler, putting [blame] on the specified [DomainNode] (this is the superclass of all PIG-generated types). - * "Blame" in this case, means that the line & column number in the metas of [blame] become the problem's. - */ -fun ProblemHandler.handleUnimplementedFeature(blame: DomainNode, featureName: String) = - this.handleProblem(createUnimplementedFeatureProblem(blame, featureName)) - -private fun createUnimplementedFeatureProblem(blame: DomainNode, featureName: String) = - Problem( - (blame.metas.sourceLocationMeta?.toProblemLocation() ?: UNKNOWN_PROBLEM_LOCATION), - PlanningProblemDetails.UnimplementedFeature(featureName) - ) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/EvaluatorOptions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/EvaluatorOptions.kt deleted file mode 100644 index b6102455d4..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/EvaluatorOptions.kt +++ /dev/null @@ -1,85 +0,0 @@ -package org.partiql.lang.planner - -import org.partiql.lang.eval.ProjectionIterationBehavior -import org.partiql.lang.eval.ThunkOptions -import org.partiql.lang.eval.TypedOpBehavior -import org.partiql.lang.eval.TypingMode -import java.time.ZoneOffset - -/* - -Differences between CompilerOptions and EvaluatorOptions: - -- There is no EvaluatorOptions equivalent for CompileOptions.visitorTransformMode since the planner always runs some basic - normalization and variable resolution passes *before* the customer can inject their own transforms. -- There is no EvaluatorOptions equivalent for CompileOptions.thunkReturnTypeAssertions since PlannerPipeline does not -support the static type inferencer (yet). -- EvaluatorOptions.allowUndefinedVariables is new. -- EvaluatorOptions has no equivalent for CompileOptions.undefinedVariableBehavior -- this was added for backward -compatibility on behalf of a customer we don't have anymore. Internal bug number is IONSQL-134. - */ - -/** - * Specifies options that effect the behavior of the PartiQL physical plan evaluator. - * - * @param defaultTimezoneOffset Default timezone offset to be used when TIME WITH TIME ZONE does not explicitly - * specify the time zone. Defaults to [ZoneOffset.UTC]. - */ -@Suppress("DataClassPrivateConstructor") -data class EvaluatorOptions private constructor ( - val projectionIteration: ProjectionIterationBehavior = ProjectionIterationBehavior.FILTER_MISSING, - val thunkOptions: ThunkOptions = ThunkOptions.standard(), - val typingMode: TypingMode = TypingMode.LEGACY, - val typedOpBehavior: TypedOpBehavior = TypedOpBehavior.HONOR_PARAMETERS, - val defaultTimezoneOffset: ZoneOffset = ZoneOffset.UTC -) { - companion object { - - /** - * Creates a java style builder that will choose the default values for any unspecified options. - */ - @JvmStatic - fun builder() = Builder() - - /** - * Creates a java style builder that will clone the [EvaluatorOptions] passed to the constructor. - */ - @JvmStatic - fun builder(options: EvaluatorOptions) = Builder(options) - - /** - * Kotlin style builder that will choose the default values for any unspecified options. - */ - fun build(block: Builder.() -> Unit) = Builder().apply(block).build() - - /** - * Kotlin style builder that will clone the [EvaluatorOptions] passed to the constructor. - */ - fun build(options: EvaluatorOptions, block: Builder.() -> Unit) = Builder(options).apply(block).build() - - /** - * Creates a [EvaluatorOptions] instance with the standard values for use by the legacy AST compiler. - */ - @JvmStatic - fun standard() = Builder().build() - } - - /** - * Builds a [EvaluatorOptions] instance. - */ - class Builder(private var options: EvaluatorOptions = EvaluatorOptions()) { - - fun projectionIteration(value: ProjectionIterationBehavior) = set { copy(projectionIteration = value) } - fun typingMode(value: TypingMode) = set { copy(typingMode = value) } - fun typedOpBehavior(value: TypedOpBehavior) = set { copy(typedOpBehavior = value) } - fun thunkOptions(value: ThunkOptions) = set { copy(thunkOptions = value) } - fun defaultTimezoneOffset(value: ZoneOffset) = set { copy(defaultTimezoneOffset = value) } - - private inline fun set(block: EvaluatorOptions.() -> EvaluatorOptions): Builder { - options = block(options) - return this - } - - fun build() = options - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/GlobalVariableResolver.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/GlobalVariableResolver.kt deleted file mode 100644 index fbbddb606a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/GlobalVariableResolver.kt +++ /dev/null @@ -1,63 +0,0 @@ -package org.partiql.lang.planner - -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName - -/** - * Indicates the result of an attempt to resolve a global variable to its supplied unique identifier supplied by - * the application embedding PartiQL. - */ -sealed class GlobalResolutionResult { - /** - * A success case, indicates the [uniqueId] of the match to the [BindingName] in the global scope. - * Typically, this is defined by the storage layer. - * - * In the future, this will likely contain much more than just a unique id. It might include detailed schema - * information about global variables. - */ - data class GlobalVariable(val uniqueId: String) : GlobalResolutionResult() - - /** A failure case, indicates that resolution did not match any variable. */ - object Undefined : GlobalResolutionResult() -} - -/** - * Resolves global variables (usually tables) of the current database. - * - * Global variables are not limited to tables, but may be any PartiQL value assigned by the application embedding - * PartiQL. Most databases associate a UUID or similar unique identifier to a table. The actual type used for the - * unique identifier doesn't matter as long as it can be converted to and from a [String]. The values must be unique - * within the current database. - * - * The term "resolution" in means to look up a global variable's unique identifier, or to indicate that it is not - * defined in the current database. - * - * This interface is meant to be implemented by the application embedding PartiQL and added to the [PlannerPipeline] - * via [PlannerPipeline.Builder.globalVariableResolver]. - */ -fun interface GlobalVariableResolver { - /** - * Implementations try to resolve a variable which is typically a database table to a schema - * using [bindingName]. [bindingName] includes both the name as specified by the query author and a [BindingCase] - * which indicates if query author included double quotes (") which mean the lookup should be case-sensitive. - * - * Implementations of this function must return: - * - * - [GlobalResolutionResult.GlobalVariable] if [bindingName] matches a global variable (typically a database table). - * - [GlobalResolutionResult.Undefined] if no identifier matches [bindingName]. - * - * When determining if a variable name matches a global variable, it is important to consider if the comparison - * should be case-sensitive or case-insensitive. @see [BindingName.bindingCase]. In the event that more than one - * variable matches a case-insensitive [BindingName], the implementation must still select one of them - * without providing an error. (This is consistent with Postres's behavior in this scenario.) - * - * Note that while [GlobalResolutionResult.LocalVariable] exists, it is intentionally marked `internal` and cannot - * be used outside this project. - */ - fun resolveGlobal(bindingName: BindingName): GlobalResolutionResult - - companion object { - - val EMPTY = GlobalVariableResolver { GlobalResolutionResult.Undefined } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlanner.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlanner.kt deleted file mode 100644 index 17daed2468..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlanner.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.planner - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.errors.Problem -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.PartiqlLogical -import org.partiql.lang.domains.PartiqlLogicalResolved -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.TypedOpBehavior - -/** - * [PartiQLPlanner] is responsible for transforming a [PartiqlAst.Statement] representation of a query into an - * equivalent [PartiqlPhysical.Plan] representation of the query. - */ -@ExperimentalPartiQLCompilerPipeline -interface PartiQLPlanner { - - /** - * Transforms the given statement to an equivalent expression tree with each SELECT-FROM-WHERE block - * expanded into its relational algebra form. - * - * If planning succeeds, this returns a [PartiQLPlanner.Result.Success], - * Else this returns a [PartiQLPlanner.Result.Error]. - * - * TODO this error handling pattern is subject to review and change - */ - fun plan(statement: PartiqlAst.Statement): Result - - companion object { - const val PLAN_VERSION = "0.0" - } - - /** - * TODO move Result.Success/Result.Error variant to the PartiQLCompiler - */ - sealed class Result { - - data class Success @JvmOverloads constructor( - val plan: PartiqlPhysical.Plan, - val warnings: List, - val details: PlanningDetails = PlanningDetails() - ) : Result() - - data class Error(val problems: List) : Result() { - override fun toString(): String = problems.joinToString() - } - } - - data class PlanningDetails( - val ast: PartiqlAst.Statement? = null, - val astNormalized: PartiqlAst.Statement? = null, - val logical: PartiqlLogical.Plan? = null, - val logicalResolved: PartiqlLogicalResolved.Plan? = null, - val physical: PartiqlPhysical.Plan? = null, - val physicalTransformed: PartiqlPhysical.Plan? = null - ) - - /** - * Options which control [PartiQLPlanner] behavior. - */ - class Options( - val allowedUndefinedVariables: Boolean = false, - val typedOpBehavior: TypedOpBehavior = TypedOpBehavior.HONOR_PARAMETERS - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlannerBuilder.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlannerBuilder.kt deleted file mode 100644 index 11ea3f0fd8..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlannerBuilder.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.planner - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline - -/** - * Builder class to instantiate a [PartiQLPlanner]. - */ -@ExperimentalPartiQLCompilerPipeline -class PartiQLPlannerBuilder private constructor() { - - private var globalVariableResolver = GlobalVariableResolver.EMPTY - private var physicalPlanPasses: List = emptyList() - private var callback: PlannerEventCallback? = null - private var options = PartiQLPlanner.Options() - - companion object { - - @JvmStatic - fun standard() = PartiQLPlannerBuilder() - } - - fun globalVariableResolver(globalVariableResolver: GlobalVariableResolver) = this.apply { - this.globalVariableResolver = globalVariableResolver - } - - fun physicalPlannerPasses(partiQLPhysicalPassPlanPasses: List) = this.apply { - this.physicalPlanPasses = partiQLPhysicalPassPlanPasses - } - - fun options(options: PartiQLPlanner.Options) = this.apply { - this.options = options - } - - fun callback(callback: PlannerEventCallback) = this.apply { - this.callback = callback - } - - fun build(): PartiQLPlanner = PartiQLPlannerDefault( - globalVariableResolver = globalVariableResolver, - physicalPlanPasses = physicalPlanPasses, - callback = callback, - options = options - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlannerDefault.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlannerDefault.kt deleted file mode 100644 index 88d75d609c..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlannerDefault.kt +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.planner - -import org.partiql.annotations.ExperimentalPartiQLCompilerPipeline -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.PartiqlLogical -import org.partiql.lang.domains.PartiqlLogicalResolved -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.eval.CompileOptions -import org.partiql.lang.eval.TypedOpBehavior -import org.partiql.lang.eval.internal.ProblemCollector -import org.partiql.lang.eval.internal.visitors.AggregationVisitorTransform -import org.partiql.lang.eval.internal.visitors.OrderBySortSpecVisitorTransform -import org.partiql.lang.eval.visitors.FromSourceAliasVisitorTransform -import org.partiql.lang.eval.visitors.PartiqlAstSanityValidator -import org.partiql.lang.eval.visitors.PipelinedVisitorTransform -import org.partiql.lang.eval.visitors.SelectListItemAliasVisitorTransform -import org.partiql.lang.eval.visitors.SelectStarVisitorTransform -import org.partiql.lang.eval.visitors.SubqueryCoercionVisitorTransform -import org.partiql.lang.planner.transforms.AstToLogicalVisitorTransform -import org.partiql.lang.planner.transforms.LogicalResolvedToDefaultPhysicalVisitorTransform -import org.partiql.lang.planner.transforms.LogicalToLogicalResolvedVisitorTransform -import org.partiql.lang.planner.transforms.allocateVariableIds -import org.partiql.lang.planner.validators.PartiqlLogicalResolvedValidator -import org.partiql.lang.planner.validators.PartiqlLogicalValidator -import org.partiql.pig.runtime.asPrimitive - -@ExperimentalPartiQLCompilerPipeline -internal class PartiQLPlannerDefault( - private val globalVariableResolver: GlobalVariableResolver, - private val physicalPlanPasses: List, - private val callback: PlannerEventCallback?, - private val options: PartiQLPlanner.Options, -) : PartiQLPlanner { - - override fun plan(statement: PartiqlAst.Statement): PartiQLPlanner.Result { - - val problemHandler = ProblemCollector() - - // Step 1. Normalize the AST - val normalized = callback.doEvent("normalize_ast", statement) { - statement.normalize(problemHandler) - } - if (problemHandler.hasErrors) { - return PartiQLPlanner.Result.Error(problemHandler.problems) - } - normalized.validate(options.typedOpBehavior) - - // Step 2. AST -> LogicalPlan - val logicalPlan = callback.doEvent("ast_to_logical", normalized) { - normalized.toLogicalPlan(problemHandler) - } - if (problemHandler.hasErrors) { - return PartiQLPlanner.Result.Error(problemHandler.problems) - } - // Validate logical plan - // TODO: if it is an invalid logical plan, do we want to add it to [problemHandler]? - PartiqlLogicalValidator(options.typedOpBehavior).walkPlan(logicalPlan) - - // Step 3. Replace variable references - val resolvedLogicalPlan = callback.doEvent("logical_to_logical_resolved", logicalPlan) { - logicalPlan.toResolvedPlan(problemHandler) - } - if (problemHandler.hasErrors) { - return PartiQLPlanner.Result.Error(problemHandler.problems) - } - PartiqlLogicalResolvedValidator().walkPlan(resolvedLogicalPlan) - - // Step 4. LogicalPlan -> PhysicalPlan - val physicalPlan = callback.doEvent("logical_resolved_to_physical", resolvedLogicalPlan) { - resolvedLogicalPlan.toPhysicalPlan(problemHandler) - } - if (problemHandler.hasErrors) { - return PartiQLPlanner.Result.Error(problemHandler.problems) - } - - // Step 5. Apply additional physical transformations - val plan = physicalPlanPasses.fold(physicalPlan) { plan, pass -> - val result = callback.doEvent("pass_${pass::class.java.simpleName}", plan) { - pass.apply(plan, problemHandler) - } - if (problemHandler.hasErrors) { - return PartiQLPlanner.Result.Error(problemHandler.problems) - } - result - } - - return PartiQLPlanner.Result.Success( - plan = plan, warnings = problemHandler.problems, - details = PartiQLPlanner.PlanningDetails( - ast = statement, - astNormalized = normalized, - logical = logicalPlan, - logicalResolved = resolvedLogicalPlan, - physical = physicalPlan, - physicalTransformed = plan - ) - ) - } - - // --- Internal -------------------------- - - /** - * AST Normalization Passes - */ - @Suppress("UNUSED_PARAMETER") // future work? - private fun PartiqlAst.Statement.normalize(problems: ProblemCollector): PartiqlAst.Statement { - val transform = PipelinedVisitorTransform( - SelectListItemAliasVisitorTransform(), - FromSourceAliasVisitorTransform(), - OrderBySortSpecVisitorTransform(), - AggregationVisitorTransform(), - SelectStarVisitorTransform(), - SubqueryCoercionVisitorTransform(), - ) - return transform.transformStatement(this) - } - - /** - * Performs a validation of the AST. The [PartiqlAstSanityValidator] only requires the - * [TypedOpBehavior] to perform assertions, so we pass this along. - */ - private fun PartiqlAst.Statement.validate(behavior: TypedOpBehavior) { - val validatorCompileOptions = CompileOptions.build { typedOpBehavior(behavior) } - PartiqlAstSanityValidator().validate(this, validatorCompileOptions) - } - - /** - * See [AstToLogicalVisitorTransform] - */ - private fun PartiqlAst.Statement.toLogicalPlan(problems: ProblemCollector): PartiqlLogical.Plan { - val transform = AstToLogicalVisitorTransform(problems) - return PartiqlLogical.Plan( - stmt = transform.transformStatement(this), version = PartiQLPlanner.PLAN_VERSION.asPrimitive() - ) - } - - /** - * See [LogicalToLogicalResolvedVisitorTransform] - */ - private fun PartiqlLogical.Plan.toResolvedPlan(problems: ProblemCollector): PartiqlLogicalResolved.Plan { - val (planWithAllocatedVariables, allLocals) = this.allocateVariableIds() - val transform = LogicalToLogicalResolvedVisitorTransform( - allowUndefinedVariables = options.allowedUndefinedVariables, - problemHandler = problems, - globals = globalVariableResolver, - ) - return transform.transformPlan(planWithAllocatedVariables).copy(locals = allLocals) - } - - /** - * See [LogicalResolvedToDefaultPhysicalVisitorTransform] - */ - private fun PartiqlLogicalResolved.Plan.toPhysicalPlan(problems: ProblemCollector): PartiqlPhysical.Plan { - val transform = LogicalResolvedToDefaultPhysicalVisitorTransform(problems) - return transform.transformPlan(this) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlannerPass.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlannerPass.kt deleted file mode 100644 index 45c2618320..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PartiQLPlannerPass.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.planner - -import org.partiql.errors.ProblemHandler -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.pig.runtime.DomainNode - -/** - * [PartiQLPlannerPass] is a transformation of the plan representation of a PartiQL query. - * - * TODO lower the upper bound of T to `Plan` after https://github.com/partiql/partiql-ir-generator/issues/65 - */ -fun interface PartiQLPlannerPass { - fun apply(plan: T, problemHandler: ProblemHandler): T -} - -fun interface PartiQLPhysicalPass : PartiQLPlannerPass diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PlannerEventCallback.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PlannerEventCallback.kt deleted file mode 100644 index a262ca5089..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PlannerEventCallback.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.partiql.lang.planner - -import java.time.Duration -import java.time.Instant - -/** Called by [PartiQLPlanner] after a phase of query planning has completed. */ -typealias PlannerEventCallback = (PlannerEvent) -> Unit - -/** Information about a planner event. */ -data class PlannerEvent( - /** The name of the event. */ - val eventName: String, - /** The input to the pass, e.g. the SQL query text or instance of the AST or query plan.*/ - val input: Any, - /** The output of the pass, e.g., the AST or rewritten query plan. */ - val output: Any, - /** The duration of the pass. */ - val duration: Duration -) { - override fun toString(): String = - StringBuilder().let { sb -> - println("event: $eventName") - // NOTE: please do not be surprised by the durations shown here if they are from the first - // run in an instance of the JVM. Those durations are 50-100x longer than subsequent runs - // and should improve vastly once the JIT warms up. - sb.appendLine("duration: $duration") - sb.appendLine("input:\n$input") - sb.append("output:\n$output") - sb.toString() - } -} - -/** Convenience function for optionally invoking [PlannerEventCallback] functions. */ -internal inline fun PlannerEventCallback?.doEvent(eventName: String, input: Any, crossinline block: () -> T): T { - if (this == null) return block() - val startTime = Instant.now() - return block().also { output -> - val endTime = Instant.now() - this(PlannerEvent(eventName, input, output, Duration.between(startTime, endTime))) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PlanningProblemDetails.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PlanningProblemDetails.kt deleted file mode 100644 index 27da7d1cbf..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/PlanningProblemDetails.kt +++ /dev/null @@ -1,96 +0,0 @@ -package org.partiql.lang.planner - -import org.partiql.errors.ProblemDetails -import org.partiql.errors.ProblemSeverity - -/** - * Contains detailed information about errors that may occur during query planning. - * - * This information can be used to generate end-user readable error messages and is also easy to assert - * equivalence in unit tests. - */ -sealed class PlanningProblemDetails( - override val severity: ProblemSeverity, - val messageFormatter: () -> String -) : ProblemDetails { - - override fun toString() = message - override val message: String get() = messageFormatter() - - data class ParseError(val parseErrorMessage: String) : - PlanningProblemDetails(ProblemSeverity.ERROR, { parseErrorMessage }) - - data class CompileError(val errorMessage: String) : - PlanningProblemDetails(ProblemSeverity.ERROR, { errorMessage }) - - data class UndefinedVariable(val variableName: String, val caseSensitive: Boolean) : - PlanningProblemDetails( - ProblemSeverity.ERROR, - { - "Undefined variable '$variableName'." + - quotationHint(caseSensitive) - } - ) - - data class UndefinedDmlTarget(val variableName: String, val caseSensitive: Boolean) : - PlanningProblemDetails( - ProblemSeverity.ERROR, - { - "Data manipulation target table '$variableName' is undefined. " + - "Hint: this must be a name in the global scope. " + - quotationHint(caseSensitive) - } - ) - - data class VariablePreviouslyDefined(val variableName: String) : - PlanningProblemDetails( - ProblemSeverity.ERROR, - { "The variable '$variableName' was previously defined." } - ) - - data class UnimplementedFeature(val featureName: String) : - PlanningProblemDetails( - ProblemSeverity.ERROR, - { "The syntax at this location is valid but utilizes unimplemented PartiQL feature '$featureName'" } - ) - - object InvalidDmlTarget : - PlanningProblemDetails( - ProblemSeverity.ERROR, - { "Expression is not a valid DML target. Hint: only table names are allowed here." } - ) - - object InsertValueDisallowed : - PlanningProblemDetails( - ProblemSeverity.ERROR, - { - "Use of `INSERT INTO VALUE ` is not allowed. " + - "Please use the `INSERT INTO
<< >>` form instead." - } - ) - - object InsertValuesDisallowed : - PlanningProblemDetails( - ProblemSeverity.ERROR, - { - "Use of `VALUES (, ...)` with INSERT is not allowed. " + - "Please use the `INSERT INTO
<< , ... >>` form instead." - } - ) - - data class UnresolvedExcludeExprRoot(val root: String) : - PlanningProblemDetails( - ProblemSeverity.ERROR, - { "Exclude expression given an unresolvable root '$root'" } - ) -} - -private fun quotationHint(caseSensitive: Boolean) = - if (caseSensitive) { - // Individuals that are new to SQL often try to use double quotes for string literals. - // Let's help them out a bit. - " Hint: did you intend to use single-quotes (') here? Remember that double-quotes (\") denote " + - "quoted identifiers and single-quotes denote strings." - } else { - "" - } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/QueryPlan.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/QueryPlan.kt deleted file mode 100644 index 98d7af6007..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/QueryPlan.kt +++ /dev/null @@ -1,70 +0,0 @@ -package org.partiql.lang.planner - -import org.partiql.lang.eval.BindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprValue - -/** A query plan that has been compiled and is ready to be evaluated. */ -fun interface QueryPlan { - /** - * Evaluates the query plan with the given Session. - */ - fun eval(session: EvaluationSession): QueryResult -} - -sealed class QueryResult { - /** - * The result of an SFW query, arbitrary expression, or `EXEC` stored procedure call. - */ - class Value(val value: ExprValue) : QueryResult() - - /** - * The result of a INSERT or DELETE statement. (UPDATE is out of scope for now.) - * - * Each instance of a DML command denotes an operation to be performed by the embedding PartiQL application - * to effect the writes specified by a DML operation. - * - * The primary benefit of this class is that it ensures that the [rows] property is evaluated lazily. It also - * provides a cleaner API that is easier to work with for PartiQL embedders. Without this, the user would have to - * consume the [ExprValue] directly and convert it to Ion. Neither - * option is particularly developer friendly, efficient or maintainable. - * - * This is currently only factored to support `INSERT INTO` and `DELETE FROM` as `UPDATE` and `FROM ... UPDATE` is - * out of scope for the current effort. - */ - data class DmlCommand( - /** Identifies the action to take. */ - val action: DmlAction, - /** The unique identifier of the table targed by the DML statement. */ - val targetUniqueId: String, - /** - * The rows to be inserted or deleted. - * - * In the case of delete, the rows must contain at least the fields which comprise the table's primary key. - */ - val rows: Iterable - ) : QueryResult() -} - -/** - * Identifies the action to take. - * TODO This should be represented in the IR grammar - https://github.com/partiql/partiql-lang-kotlin/issues/756 - */ -enum class DmlAction { - INSERT, - DELETE, - REPLACE; - - companion object { - fun safeValueOf(v: String): DmlAction? = try { - valueOf(v.toUpperCase()) - } catch (ex: IllegalArgumentException) { - null - } - } -} - -private operator fun Bindings.get(fieldName: String): ExprValue? = - this[BindingName(fieldName, BindingCase.SENSITIVE)] diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/StaticTypeResolver.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/StaticTypeResolver.kt deleted file mode 100644 index 13d607c631..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/StaticTypeResolver.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.partiql.lang.planner - -import org.partiql.types.StaticType - -/** - * Identifies a global variable's type, given its uniqueId. - * - * A global variable is usually a database table, but it may also be any other PartiQL value. - */ -fun interface StaticTypeResolver { - fun getVariableStaticType(uniqueId: String): StaticType -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstNormalize.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstNormalize.kt deleted file mode 100644 index 029f96754d..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstNormalize.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.partiql.lang.planner.transforms - -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.eval.internal.visitors.AggregationVisitorTransform -import org.partiql.lang.eval.internal.visitors.OrderBySortSpecVisitorTransform -import org.partiql.lang.eval.visitors.FromSourceAliasVisitorTransform -import org.partiql.lang.eval.visitors.PipelinedVisitorTransform -import org.partiql.lang.eval.visitors.SelectListItemAliasVisitorTransform -import org.partiql.lang.eval.visitors.SelectStarVisitorTransform - -/** - * Executes several Visitor Transforms on the AST - */ -fun PartiqlAst.Statement.normalize(): PartiqlAst.Statement { - val transforms = PipelinedVisitorTransform( - SelectListItemAliasVisitorTransform(), - FromSourceAliasVisitorTransform(), - OrderBySortSpecVisitorTransform(), - AggregationVisitorTransform(), - SelectStarVisitorTransform() - ) - return transforms.transformStatement(this) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstToLogicalVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstToLogicalVisitorTransform.kt deleted file mode 100644 index 9a8a626e0b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstToLogicalVisitorTransform.kt +++ /dev/null @@ -1,774 +0,0 @@ -package org.partiql.lang.planner.transforms - -import com.amazon.ionelement.api.emptyMetaContainer -import com.amazon.ionelement.api.ionString -import com.amazon.ionelement.api.ionSymbol -import org.partiql.errors.ErrorCode -import org.partiql.errors.Problem -import org.partiql.errors.ProblemHandler -import org.partiql.errors.UNKNOWN_PROBLEM_LOCATION -import org.partiql.lang.ast.IsOrderedMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.PartiqlAstToPartiqlLogicalVisitorTransform -import org.partiql.lang.domains.PartiqlLogical -import org.partiql.lang.domains.metaContainerOf -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.internal.builtins.CollectionAggregationFunction -import org.partiql.lang.eval.internal.builtins.ExprFunctionCurrentUser -import org.partiql.lang.eval.internal.err -import org.partiql.lang.eval.internal.errorContextFrom -import org.partiql.lang.eval.physical.sourceLocationMetaOrUnknown -import org.partiql.lang.eval.visitors.VisitorTransformBase -import org.partiql.lang.planner.PlanningProblemDetails -import org.partiql.lang.planner.handleUnimplementedFeature -import org.partiql.pig.runtime.SymbolPrimitive -import org.partiql.pig.runtime.toIonElement - -internal fun PartiqlAst.Statement.toLogicalPlan(problemHandler: ProblemHandler): PartiqlLogical.Plan = - PartiqlLogical.build { - plan( - AstToLogicalVisitorTransform(problemHandler).transformStatement(this@toLogicalPlan), - version = PLAN_VERSION_NUMBER - ) - } - -/** - * Transforms an instance of [PartiqlAst.Statement] to [PartiqlLogical.Statement]. This representation of the query - * expresses the intent of the query author in terms of PartiQL's relational algebra instead of its AST. - * - * Performs no semantic checks. - * - * This conversion (and the logical algebra) are early in their lifecycle and so only a limited subset of SFW queries - * are transformable. See `AstToLogicalVisitorTransformTests` to see which queries are transformable. - */ -internal class AstToLogicalVisitorTransform( - val problemHandler: ProblemHandler -) : PartiqlAstToPartiqlLogicalVisitorTransform() { - - internal companion object { - internal const val EXCLUDED: String = "EXCLUDED" - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlLogical.Expr = PartiqlLogical.build { - var algebra: PartiqlLogical.Bexpr = node.from.toBexpr(this@AstToLogicalVisitorTransform, problemHandler) - - algebra = node.fromLet?.let { fromLet -> - let(algebra, fromLet.letBindings.map { transformLetBinding(it) }, fromLet.metas) - } ?: algebra - - algebra = node.where?.let { filter(transformExpr(it), algebra, it.metas) } ?: algebra - - var selectAlgebraPair = transformAggregations(node, algebra) ?: node to algebra - - var select = selectAlgebraPair.first - - algebra = selectAlgebraPair.second - - algebra = select.having?.let { filter(transformExpr(it), algebra, it.metas) } - ?: algebra - - selectAlgebraPair = transformWindowFunctions(select, algebra) ?: (select to algebra) - - select = selectAlgebraPair.first - - algebra = selectAlgebraPair.second - - algebra = select.order?.let { orderBy -> - val sortSpecs = orderBy.sortSpecs.map { sortSpec -> transformSortSpec(sortSpec) } - sort(algebra, sortSpecs, orderBy.metas) - } ?: algebra - - algebra = select.offset?.let { offset(transformExpr(it), algebra, it.metas) } - ?: algebra - - algebra = select.limit?.let { limit(transformExpr(it), algebra, it.metas) } - ?: algebra - - val expr = transformProjection(select, algebra) - when (node.setq) { - is PartiqlAst.SetQuantifier.Distinct -> call("filter_distinct", expr) - else -> expr - } - } - - override fun transformExprSessionAttribute(node: PartiqlAst.Expr.SessionAttribute): PartiqlLogical.Expr.Call = PartiqlLogical.build { - val functionName = when (node.value.text.toUpperCase()) { - EvaluationSession.Constants.CURRENT_USER_KEY -> ExprFunctionCurrentUser.FUNCTION_NAME - else -> err( - "Unsupported session attribute: ${node.value.text}", - errorCode = ErrorCode.SEMANTIC_PROBLEM, - errorContext = errorContextFrom(node.metas), - internal = false - ) - } - call( - funcName = functionName, - args = emptyList() - ) - } - - // This transformation is used for top-level expression transformations and for the SFW clauses prior to the - // `aggregation` relational algebra operator. - override fun transformExprCallAgg(node: PartiqlAst.Expr.CallAgg): PartiqlLogical.Expr = PartiqlLogical.build { - call( - "${CollectionAggregationFunction.PREFIX}${node.funcName.text}", - listOf( - lit(ionString(node.setq.javaClass.simpleName.lowercase())), - transformExpr(node.arg) - ) - ) - } - - /** - * Transforms the input [node] into a pair of two items: - * 1. An AST (where all [PartiqlAst.Expr.CallAgg]'s in the input projection list are replaced with references - * ([PartiqlAst.Expr.Id]) to new [PartiqlLogical.VarDecl]'s) - * 2. A [PartiqlLogical.Bexpr.Aggregate] with the necessary [PartiqlLogical.GroupKey]'s and - * [PartiqlLogical.AggregateFunction]'s (taken from the input [node]). Also, each [PartiqlLogical.GroupKey] and - * [PartiqlLogical.AggregateFunction] creates a [PartiqlLogical.VarDecl] that is used to create the aforementioned - * references (via [PartiqlAst.Expr.Id]) - * - * Transforms a query like: - * ``` - * SELECT newA, MAX(t.b) AS maxB - * FROM t AS t - * GROUP BY t.a AS newA GROUP AS g - * ``` - * * into a logical plan similar to: - * * ``` - * FROM - * - t AS t - * AGGREGATION - * - Group Keys: t.a AS newA - * - Functions: (GROUP_AS AS g), (MAX AS aggregation_function_0) - * PROJECT - * - newA - * - aggregation_function_0 AS maxB - * ``` - */ - private fun transformAggregations( - node: PartiqlAst.Expr.Select, - source: PartiqlLogical.Bexpr - ): Pair? { - val aggregationReplacer = CallAggregationsProjectionReplacer() - val transformedNode = aggregationReplacer.transformExprSelect(node) as PartiqlAst.Expr.Select - - // Check if aggregation is necessary - if (node.group == null && aggregationReplacer.getAggregations().isEmpty()) { - return null - } - - return transformedNode to PartiqlLogical.build { - val groupAsFunction = node.group?.groupAsAlias?.let { convertGroupAsAlias(it, node.from) } - val aggFunctions = aggregationReplacer.getAggregations().map { (name, callAgg) -> convertCallAgg(callAgg, name) } - aggregate( - source = source, - strategy = node.group?.strategy?.let { transformGroupingStrategy(it) } ?: groupFull(), - groupList = groupKeyList(node.group?.keyList?.keys?.map { transformGroupKey(it) } ?: emptyList()), - functionList = aggregateFunctionList(listOfNotNull(groupAsFunction) + aggFunctions), - metas = node.group?.metas ?: emptyMetaContainer() - ) - } - } - - override fun transformGroupKey(node: PartiqlAst.GroupKey): PartiqlLogical.GroupKey { - val thiz = this - return PartiqlLogical.build { - groupKey( - expr = thiz.transformExpr(node.expr), - asVar = varDecl( - name = node.asAlias?.text ?: errAstNotNormalized( - "The group key should have encountered a unique name. This is typically added by the GroupByItemAliasVisitorTransform." - ), - metas = node.asAlias!!.metas - ) - ) - } - } - - private fun convertGroupAsAlias(node: SymbolPrimitive, from: PartiqlAst.FromSource) = PartiqlLogical.build { - val sourceAliases = getSourceAliases(from) - val structFields = sourceAliases.map { alias -> - val aliasText = alias?.text ?: errAstNotNormalized("All FromSources should have aliases") - structField( - lit(aliasText.toIonElement()), - id(aliasText, caseInsensitive(), unqualified()) - ) - } - aggregateFunction( - quantifier = all(), - name = "group_as", - arg = struct(structFields), - asVar = varDecl_(node, node.metas), - metas = node.metas - ) - } - - private fun getSourceAliases(node: PartiqlAst.FromSource): List = when (node) { - is PartiqlAst.FromSource.Scan -> listOf(node.asAlias ?: errAstNotNormalized("Scan should have alias initialized.")) - is PartiqlAst.FromSource.Join -> getSourceAliases(node.left).plus(getSourceAliases(node.right)) - is PartiqlAst.FromSource.Unpivot -> listOf(node.asAlias ?: errAstNotNormalized("Unpivot should have alias initialized.")) - } - - private fun convertCallAgg(node: PartiqlAst.Expr.CallAgg, name: String): PartiqlLogical.AggregateFunction = PartiqlLogical.build { - aggregateFunction( - quantifier = transformSetQuantifier(node.setq), - name = node.funcName.text, - arg = transformExpr(node.arg), - asVar = varDecl(name, node.metas), - metas = node.metas - ) - } - - override fun transformExprCallWindow(node: PartiqlAst.Expr.CallWindow): PartiqlLogical.Expr = - error("Call window node is not transformed (This shall never happend)") - - /** - * Transforms the input [node] into a pair of two items: - * 1. An AST (where all [PartiqlAst.Expr.CallWindow]'s in the input projection list are replaced with references - * ([PartiqlAst.Expr.Id]) to new [PartiqlLogical.VarDecl]'s) - * 2. A modified algebra such that each window function call in the input projection list corresponding to one window operator - * TODO: if multiple window functions are operating on the same window, we can potentially add them in a single window operator to increase performance - * - * Transforms a query like: - * ``` - * SELECT LAG(t.a) OVER (...), LEAD(t.a) OVER (...) - * FROM t AS t - * ``` - * into a logical plan similar to: - * ``` - * bindings_to_values - * struct ... - * window - * window - * scan - * over - * window_expression - * $__partiql_window_function_0 - * LAG - * t.a - * over - * window_expression - * $__partiql_window_function_1 - * LEAD - * t.a - * ``` - */ - private fun transformWindowFunctions(node: PartiqlAst.Expr.Select, algebra: PartiqlLogical.Bexpr): Pair? { - val windowReplacer = CurrentProjectionListWindowFunctionTransform() - - val transformedNode = windowReplacer.transformExprSelect(node) as PartiqlAst.Expr.Select - - val windowExpressions = windowReplacer.getWindowFuncs() - - if (windowExpressions.isEmpty()) { - return null - } - - var modifiedAlgebra = algebra - windowExpressions.forEach { callWindow -> - val callWindowNode = callWindow.second - val windowFuncGeneratedName = callWindow.first - modifiedAlgebra = - PartiqlLogical.build { - window( - modifiedAlgebra, - transformOver(callWindowNode.over), - PartiqlLogical.build { - windowExpression( - varDecl(windowFuncGeneratedName), - callWindowNode.funcName.text, - callWindowNode.args.map { arg -> - transformExpr(arg) - }, - metas = node.project.metas - ) - } - ) - } - } - return transformedNode to modifiedAlgebra - } - - private fun transformProjection(node: PartiqlAst.Expr.Select, algebra: PartiqlLogical.Bexpr): PartiqlLogical.Expr { - val project = node.project - val metas = when (node.order) { - null -> project.metas - else -> project.metas + metaContainerOf(IsOrderedMeta) - } - return PartiqlLogical.build { - when (project) { - is PartiqlAst.Projection.ProjectValue -> { - bindingsToValues( - exp = transformExpr(project.value), - query = algebra, - metas = metas - ) - } - is PartiqlAst.Projection.ProjectList -> { - bindingsToValues( - exp = transformProjectList(project), - query = algebra, - metas = metas - ) - } - is PartiqlAst.Projection.ProjectStar -> { - // `SELECT * FROM bar AS b` is rewritten to `SELECT b.* FROM bar as b` by - // [SelectStarVisitorTransform]. Therefore, there is no need to support `SELECT *` here. - errAstNotNormalized("Expected SELECT * to be removed") - } - is PartiqlAst.Projection.ProjectPivot -> { - pivot( - input = algebra, - key = transformExpr(project.key), - value = transformExpr(project.value), - metas = metas - ) - } - } - } - } - - override fun transformLetBinding(node: PartiqlAst.LetBinding): PartiqlLogical.LetBinding = - PartiqlLogical.build { - letBinding( - transformExpr(node.expr), - varDecl_(node.name, node.name.metas), - node.metas - ) - } - - override fun transformStatementDml(node: PartiqlAst.Statement.Dml): PartiqlLogical.Statement { - require(node.operations.ops.isNotEmpty()) - - // `INSERT` and `DELETE` statements are all that's needed for the current effort--and it just so - // happens that these never utilize more than one DML operation anyway. We don't need to - // support more than one DML operation until we start supporting UPDATE statements. - if (node.operations.ops.size > 1) { - problemHandler.handleUnimplementedFeature(node, "more than one DML operation") - } - - return when (val dmlOp = node.operations.ops.first()) { - is PartiqlAst.DmlOp.Insert -> { - node.from?.let { problemHandler.handleUnimplementedFeature(dmlOp, "UPDATE / INSERT") } - // Check for and block `INSERT INTO VALUES (...)` This is *no* way to support this - // within without the optional comma separated list of columns that precedes `VALUES` since doing so - // requires - // We block this by identifying (bag (list ...) ...) nodes which is how the parser represents the - // VALUES constructor. Since parser uses the same nodes for the alternate syntactic representations - // `<< [ ... ] ... >>` and `BAG(LIST(...), ...)` those get blocked too. This is probably just as well. - if (dmlOp.values is PartiqlAst.Expr.Bag) { - (dmlOp.values as PartiqlAst.Expr.Bag).values.firstOrNull { it is PartiqlAst.Expr.List }?.let { - problemHandler.handleProblem( - Problem( - node.metas.sourceLocationMetaOrUnknown.toProblemLocation(), - PlanningProblemDetails.InsertValuesDisallowed - ) - ) - } - } - - val target = dmlOp.target.toDmlTargetId() - val alias = dmlOp.asAlias?.let { - PartiqlLogical.VarDecl(it) - } ?: PartiqlLogical.VarDecl(target.name) - - val operation = when (val conflictAction = dmlOp.conflictAction) { - null -> PartiqlLogical.DmlOperation.DmlInsert(targetAlias = alias) - is PartiqlAst.ConflictAction.DoReplace -> when (conflictAction.value) { - is PartiqlAst.OnConflictValue.Excluded -> PartiqlLogical.DmlOperation.DmlReplace( - targetAlias = alias, - condition = conflictAction.condition?.let { transformExpr(it) }, - rowAlias = conflictAction.condition?.let { PartiqlLogical.VarDecl(SymbolPrimitive(EXCLUDED, emptyMetaContainer())) } - ) - } - is PartiqlAst.ConflictAction.DoUpdate -> when (conflictAction.value) { - is PartiqlAst.OnConflictValue.Excluded -> PartiqlLogical.DmlOperation.DmlUpdate( - targetAlias = alias, - condition = conflictAction.condition?.let { transformExpr(it) }, - rowAlias = conflictAction.condition?.let { PartiqlLogical.VarDecl(SymbolPrimitive(EXCLUDED, emptyMetaContainer())) } - ) - } - is PartiqlAst.ConflictAction.DoNothing -> TODO("`ON CONFLICT DO NOTHING` is not supported in logical plan yet.") - } - - PartiqlLogical.Statement.Dml( - target = target, - operation = operation, - rows = transformExpr(dmlOp.values), - metas = node.metas - ) - } - // INSERT single row with VALUE is disallowed. (This variation of INSERT might be removed in a future - // release of PartiQL.) - is PartiqlAst.DmlOp.InsertValue -> { - problemHandler.handleProblem( - Problem( - node.metas.sourceLocationMetaOrUnknown.toProblemLocation(), - PlanningProblemDetails.InsertValueDisallowed - ) - ) - INVALID_STATEMENT - } - is PartiqlAst.DmlOp.Delete -> { - if (node.from == null) { - // unfortunately, the AST allows malformations such as this however the parser should - // never actually create an AST for a DELETE statement without a FROM clause. - error("Malformed AST: DELETE without FROM (this should never happen)") - } else { - when (val from = node.from) { - is PartiqlAst.FromSource.Scan -> { - val rowsSource = from.toBexpr(this, problemHandler) as PartiqlLogical.Bexpr.Scan - val predicate = node.where?.let { transformExpr(it) } - val rows = if (predicate == null) { - rowsSource - } else { - PartiqlLogical.build { filter(predicate, rowsSource) } - } - - PartiqlLogical.build { - dml( - target = from.expr.toDmlTargetId(), - operation = dmlDelete(), - // This query returns entire rows which are to be deleted, which is unfortunate - // unavoidable without knowledge of schema. PartiQL embedders may apply a - // pass over the resolved logical (or later) plan that changes this to only - // include the primary keys of the rows to be deleted. - rows = bindingsToValues( - exp = id(rowsSource.asDecl.name.text, caseSensitive(), unqualified()), - query = rows, - ), - metas = node.metas - ) - } - } - else -> { - problemHandler.handleProblem( - Problem( - (from?.metas?.sourceLocationMetaOrUnknown?.toProblemLocation() ?: UNKNOWN_PROBLEM_LOCATION), - PlanningProblemDetails.InvalidDmlTarget - ) - ) - INVALID_STATEMENT - } - } - } - } - is PartiqlAst.DmlOp.Remove -> { - problemHandler.handleProblem( - Problem(dmlOp.metas.sourceLocationMetaOrUnknown.toProblemLocation(), PlanningProblemDetails.UnimplementedFeature("REMOVE")) - ) - INVALID_STATEMENT - } - is PartiqlAst.DmlOp.Set -> { - problemHandler.handleProblem( - Problem(dmlOp.metas.sourceLocationMetaOrUnknown.toProblemLocation(), PlanningProblemDetails.UnimplementedFeature("SET")) - ) - INVALID_STATEMENT - } - } - } - - private fun PartiqlAst.Expr.toDmlTargetId(): PartiqlLogical.Identifier { - val dmlTargetId = when (this) { - is PartiqlAst.Expr.Id -> PartiqlLogical.build { - identifier_(name, transformCaseSensitivity(case), metas) - } - else -> { - problemHandler.handleProblem( - Problem( - metas.sourceLocationMetaOrUnknown.toProblemLocation(), - PlanningProblemDetails.InvalidDmlTarget - ) - ) - INVALID_DML_TARGET_ID - } - } - return dmlTargetId - } - - override fun transformStatementDdl(node: PartiqlAst.Statement.Ddl): PartiqlLogical.Statement { - // It is an open question whether the planner will support DDL statements directly or if they must be handled by - // some other construct. For now, we just submit an error with problem details indicating these statements - // are not implemented. - problemHandler.handleProblem( - Problem( - node.metas.sourceLocationMetaOrUnknown.toProblemLocation(), - PlanningProblemDetails.UnimplementedFeature( - when (node.op) { - is PartiqlAst.DdlOp.CreateIndex -> "CREATE INDEX" - is PartiqlAst.DdlOp.CreateTable -> "CREATE TABLE" - is PartiqlAst.DdlOp.DropIndex -> "DROP INDEX" - is PartiqlAst.DdlOp.DropTable -> "DROP TABLE" - } - ) - ) - ) - return INVALID_STATEMENT - } - - override fun transformExprStruct(node: PartiqlAst.Expr.Struct): PartiqlLogical.Expr = - PartiqlLogical.build { - struct( - node.fields.map { - structField( - transformExpr(it.first), - transformExpr(it.second) - ) - }, - metas = node.metas - ) - } - - private fun transformProjectList(node: PartiqlAst.Projection.ProjectList): PartiqlLogical.Expr = - PartiqlLogical.build { - struct( - List(node.projectItems.size) { idx -> - when (val projectItem = node.projectItems[idx]) { - is PartiqlAst.ProjectItem.ProjectExpr -> - structField( - lit( - projectItem.asAlias?.toIonElement() - ?: errAstNotNormalized("SELECT-list item alias not specified") - ), - transformExpr(projectItem.expr), - ) - is PartiqlAst.ProjectItem.ProjectAll -> { - structFields(transformExpr(projectItem.expr), projectItem.metas) - } - } - } - ) - } -} - -private fun PartiqlAst.FromSource.toBexpr( - toLogicalTransform: AstToLogicalVisitorTransform, - problemHandler: ProblemHandler -) = - FromSourceToBexpr(toLogicalTransform, problemHandler).convert(this) - -private class FromSourceToBexpr( - val toLogicalTransform: AstToLogicalVisitorTransform, - val problemHandler: ProblemHandler -) : PartiqlAst.FromSource.Converter { - - override fun convertScan(node: PartiqlAst.FromSource.Scan): PartiqlLogical.Bexpr { - val asAlias = node.asAlias ?: errAstNotNormalized("Expected as alias to be non-null") - return PartiqlLogical.build { - scan( - toLogicalTransform.transformExpr(node.expr), - varDecl_(asAlias, asAlias.metas), - node.atAlias?.let { varDecl_(it, it.metas) }, - node.byAlias?.let { varDecl_(it, it.metas) }, - node.metas - ) - } - } - - override fun convertUnpivot(node: PartiqlAst.FromSource.Unpivot): PartiqlLogical.Bexpr { - val asAlias = node.asAlias ?: errAstNotNormalized("Expected as alias to be non-null") - return PartiqlLogical.build { - unpivot( - toLogicalTransform.transformExpr(node.expr), - varDecl_(asAlias, asAlias.metas), - node.atAlias?.let { varDecl_(it, it.metas) }, - node.byAlias?.let { varDecl_(it, it.metas) }, - node.metas - ) - } - } - - override fun convertJoin(node: PartiqlAst.FromSource.Join): PartiqlLogical.Bexpr = - PartiqlLogical.build { - join( - joinType = toLogicalTransform.transformJoinType(node.type), - left = convert(node.left), - right = convert(node.right), - predicate = node.predicate?.let { toLogicalTransform.transformExpr(it) }, - node.metas - ) - } -} - -/** - * Given a [PartiqlAst.Expr.Select], transforms all [PartiqlAst.Expr.CallAgg]'s within the projection list to - * [PartiqlAst.Expr.Id]'s that reference the new [PartiqlLogical.VarDecl]. - * Does not recurse into more than 1 [PartiqlAst.Expr.Select]. Designed to be invoked directly on a - * [PartiqlAst.Expr.Select] using [transformExprSelect]. - */ -private class CallAggregationsProjectionReplacer(var level: Int = 0) : VisitorTransformBase() { - val callAggregationVisitorTransform = CallAggregationReplacer() - - override fun transformProjectItemProjectExpr_expr(node: PartiqlAst.ProjectItem.ProjectExpr): PartiqlAst.Expr { - return callAggregationVisitorTransform.transformExpr(node.expr) - } - - override fun transformProjectionProjectValue_value(node: PartiqlAst.Projection.ProjectValue): PartiqlAst.Expr { - return callAggregationVisitorTransform.transformExpr(node.value) - } - - override fun transformExprSelect_having(node: PartiqlAst.Expr.Select): PartiqlAst.Expr? = node.having?.let { having -> - callAggregationVisitorTransform.transformExpr(having) - } - - override fun transformSortSpec_expr(node: PartiqlAst.SortSpec): PartiqlAst.Expr { - return callAggregationVisitorTransform.transformExpr(node.expr) - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - return if (level++ == 0) super.transformExprSelect(node) else node - } - - fun getAggregations() = callAggregationVisitorTransform.aggregations -} - -/** - * Created to be invoked by the [CallAggregationsProjectionReplacer] to transform all encountered [PartiqlAst.Expr.CallAgg]'s to - * [PartiqlAst.Expr.Id]'s. This class is designed to be called directly on [PartiqlAst.Projection]'s and does NOT recurse - * into [PartiqlAst.Expr.Select]'s. This class is designed to be instantiated once per aggregation scope. The class collects - * all unique-per-scope aggregation variable declaration names and exposes it to the calling class. - * - * As an example, this transforms: - * ``` - * SELECT g, h, SUM(t.b) AS sumB - * FROM t - * GROUP BY t.a AS g GROUP AS h - * ``` - * - * into: - * - * ``` - * SELECT g, h, $__partiql_aggregation_0 AS sumB - * FROM t - * GROUP BY t.a AS g GROUP AS h - * ``` - * - */ -private class CallAggregationReplacer() : VisitorTransformBase() { - private var varDeclIncrement = 0 - val aggregations = mutableSetOf>() - - override fun transformExprCallAgg(node: PartiqlAst.Expr.CallAgg): PartiqlAst.Expr { - val name = getAggregationIdName() - aggregations.add(name to node) - return PartiqlAst.build { - id( - name = name, - case = caseInsensitive(), - qualifier = unqualified(), - metas = node.metas - ) - } - } - - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - return node - } - - /** - * Returns unique (per [PartiqlAst.Expr.Select]) strings for each aggregation. - */ - private fun getAggregationIdName(): String = "\$__partiql_aggregation_${varDeclIncrement++}" -} - -/** - * Given a [PartiqlAst.Expr.Select], transforms all [PartiqlAst.Expr.CallWindow]'s within the projection list to - * [PartiqlAst.Expr.Id]'s that reference the new [PartiqlLogical.VarDecl]. - * We only want this to convert the window function in the current projection list without recuse into any sub-query. - * - * For example: - * Consider: - * SELECT - * aWinFunc - * FROM ( - * SELECT - * anotherWinFunc - * FROM - * ... - * ) - * - * The transformation Order is as follows: - * FROM <-- This is the outer FROM - * FROM <- This is the inner FROM - * CurrentProjectionListWindowFunctionTransform (1) - * SELECT <- This is the inner SELECT - * anotherWinFunc <- transformed By CurrentProjectionListWindowFunctionTransform (1) - * Transform Projection - * CurrentProjectionListWindowFunctionTransform (2) - * SELECT <- This is the outer SELECT - * aWindFunc <- transformed By CurrentProjectionListWindowFunctionTransform (2) - * TransformProjection - */ -private class CurrentProjectionListWindowFunctionTransform(var level: Int = 0) : VisitorTransformBase() { - val callWindowFunctionVisitorTransform = CallWindowReplacer() - - override fun transformProjectItemProjectExpr_expr(node: PartiqlAst.ProjectItem.ProjectExpr): PartiqlAst.Expr { - return callWindowFunctionVisitorTransform.transformExpr(node.expr) - } - - override fun transformProjectionProjectValue_value(node: PartiqlAst.Projection.ProjectValue): PartiqlAst.Expr { - return callWindowFunctionVisitorTransform.transformExpr(node.value) - } - - // we don't want to nested in sub-queries, otherwise the inner window function get transformed multiple times. - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr = - if (level == 0) { - level += 1 - super.transformExprSelect(node) - } else { - node - } - - fun getWindowFuncs() = callWindowFunctionVisitorTransform.windowFunctions -} - -/** - * Created to be invoked by the [CurrentProjectionListWindowFunctionTransform] to transform all encountered [PartiqlAst.Expr.CallWindow]'s to - * [PartiqlAst.Expr.Id]'s. This class is designed to be called directly on [PartiqlAst.Projection]'s and does NOT recurse - * into [PartiqlAst.Expr.Select]'s if the projection list contains a Select Node. - */ -private class CallWindowReplacer : VisitorTransformBase() { - private var varDeclIncrement = 0 - val windowFunctions = mutableSetOf>() - override fun transformExprCallWindow(node: PartiqlAst.Expr.CallWindow): PartiqlAst.Expr { - val name = getWindowFuncIdName() - windowFunctions.add(name to node) - return PartiqlAst.build { - id( - name = name, - case = caseInsensitive(), - qualifier = unqualified(), - metas = node.metas - ) - } - } - - // If this function is called, then the projection list contains a Select Node. - // Regardless whether that select node's projection list contains window function - // we do not want to transform it at the moment - override fun transformExprSelect(node: PartiqlAst.Expr.Select): PartiqlAst.Expr { - return node - } - - /** - * Returns unique (per [PartiqlAst.Expr.Select]) strings for each window function. - */ - private fun getWindowFuncIdName(): String = "\$__partiql_window_function_${varDeclIncrement++}" -} - -private val INVALID_STATEMENT = PartiqlLogical.build { - query(lit(ionSymbol("this is a placeholder for an invalid statement - do not run"))) -} - -private val INVALID_BEXPR = PartiqlLogical.build { - scan(lit(ionSymbol("this is a placeholder for an invalid relation - do not run")), varDecl("invalid")) -} - -private val INVALID_EXPR = PartiqlLogical.build { - lit(ionSymbol("this is a placeholder for an invalid expression - do not run")) -} - -private val INVALID_DML_TARGET_ID = PartiqlLogical.build { - identifier("this is a placeholder for an invalid DML target - do not run", caseInsensitive()) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/LogicalResolvedToDefaultPhysicalVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/LogicalResolvedToDefaultPhysicalVisitorTransform.kt deleted file mode 100644 index 88c895680e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/LogicalResolvedToDefaultPhysicalVisitorTransform.kt +++ /dev/null @@ -1,173 +0,0 @@ -package org.partiql.lang.planner.transforms - -import com.amazon.ionelement.api.ionSymbol -import org.partiql.errors.ProblemHandler -import org.partiql.lang.domains.PartiqlLogicalResolved -import org.partiql.lang.domains.PartiqlLogicalResolvedToPartiqlPhysicalVisitorTransform -import org.partiql.lang.domains.PartiqlPhysical - -/** - * Transforms an instance of [PartiqlLogicalResolved.Statement] to [PartiqlPhysical.Statement], - * specifying `(impl default)` for each relational operator. - */ -internal fun PartiqlLogicalResolved.Plan.toDefaultPhysicalPlan(problemHandler: ProblemHandler) = - LogicalResolvedToDefaultPhysicalVisitorTransform(problemHandler).transformPlan(this) - -internal const val DEFAULT_IMPL_NAME = "default" -internal val DEFAULT_IMPL = PartiqlPhysical.build { impl(DEFAULT_IMPL_NAME) } - -internal fun PartiqlPhysical.Builder.structField(name: String, value: String) = - structField(lit(ionSymbol(name)), lit(ionSymbol(value))) - -internal fun PartiqlPhysical.Builder.structField(name: String, value: PartiqlPhysical.Expr) = - structField(lit(ionSymbol(name)), value) - -internal fun structField(name: String, value: PartiqlPhysical.Expr) = - PartiqlPhysical.StructPart.StructField(PartiqlPhysical.Expr.Lit(ionSymbol(name).asAnyElement()), value) - -internal fun structField(name: String, value: String) = PartiqlPhysical.StructPart.StructField( - PartiqlPhysical.Expr.Lit(ionSymbol(name).asAnyElement()), - PartiqlPhysical.Expr.Lit(ionSymbol(value).asAnyElement()) -) - -internal class LogicalResolvedToDefaultPhysicalVisitorTransform( - val problemHandler: ProblemHandler -) : PartiqlLogicalResolvedToPartiqlPhysicalVisitorTransform() { - - /** Copies [PartiqlLogicalResolved.Bexpr.Scan] to [PartiqlPhysical.Bexpr.Scan], adding the default impl. */ - override fun transformBexprScan(node: PartiqlLogicalResolved.Bexpr.Scan): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - scan( - i = DEFAULT_IMPL, - expr = thiz.transformExpr(node.expr), - asDecl = thiz.transformVarDecl(node.asDecl), - atDecl = node.atDecl?.let { thiz.transformVarDecl(it) }, - byDecl = node.byDecl?.let { thiz.transformVarDecl(it) }, - metas = node.metas - ) - } - } - - /** Copies [PartiqlLogicalResolved.Bexpr.Unpivot] to [PartiqlPhysical.Bexpr.Unpivot], adding the default impl. */ - override fun transformBexprUnpivot(node: PartiqlLogicalResolved.Bexpr.Unpivot): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - unpivot( - i = DEFAULT_IMPL, - expr = thiz.transformExpr(node.expr), - asDecl = thiz.transformVarDecl(node.asDecl), - atDecl = node.atDecl?.let { thiz.transformVarDecl(it) }, - byDecl = node.byDecl?.let { thiz.transformVarDecl(it) }, - metas = node.metas - ) - } - } - - /** Copies [PartiqlLogicalResolved.Bexpr.Filter] to [PartiqlPhysical.Bexpr.Filter], adding the default impl. */ - override fun transformBexprFilter(node: PartiqlLogicalResolved.Bexpr.Filter): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - filter( - i = DEFAULT_IMPL, - predicate = thiz.transformExpr(node.predicate), - source = thiz.transformBexpr(node.source), - metas = node.metas - ) - } - } - - override fun transformBexprJoin(node: PartiqlLogicalResolved.Bexpr.Join): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - join( - i = DEFAULT_IMPL, - joinType = thiz.transformJoinType(node.joinType), - left = thiz.transformBexpr(node.left), - right = thiz.transformBexpr(node.right), - predicate = node.predicate?.let { thiz.transformExpr(it) }, - metas = node.metas - ) - } - } - - override fun transformBexprAggregate(node: PartiqlLogicalResolved.Bexpr.Aggregate): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - aggregate( - i = DEFAULT_IMPL, - source = thiz.transformBexpr(node.source), - strategy = thiz.transformGroupingStrategy(node.strategy), - groupList = thiz.transformGroupKeyList(node.groupList), - functionList = thiz.transformAggregateFunctionList(node.functionList), - metas = node.metas - ) - } - } - - // TODO : Remove from experimental once https://github.com/partiql/partiql-docs/issues/31 is resolved and a RFC is approved - override fun transformBexprWindow(node: PartiqlLogicalResolved.Bexpr.Window): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - window( - i = DEFAULT_IMPL, - source = thiz.transformBexpr(node.source), - windowSpecification = thiz.transformOver(node.windowSpecification), - windowExpressionList = node.windowExpressionList.map { - thiz.transformWindowExpression(it) - } - ) - } - } - - override fun transformBexprOffset(node: PartiqlLogicalResolved.Bexpr.Offset): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - offset( - i = DEFAULT_IMPL, - rowCount = thiz.transformExpr(node.rowCount), - source = thiz.transformBexpr(node.source), - metas = node.metas - ) - } - } - - override fun transformBexprSort(node: PartiqlLogicalResolved.Bexpr.Sort): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - sort( - i = DEFAULT_IMPL, - sortSpecs = node.sortSpecs.map { thiz.transformSortSpec(it) }, - source = thiz.transformBexpr(node.source), - metas = node.metas - ) - } - } - - override fun transformBexprLimit(node: PartiqlLogicalResolved.Bexpr.Limit): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - limit( - i = DEFAULT_IMPL, - rowCount = thiz.transformExpr(node.rowCount), - source = thiz.transformBexpr(node.source), - metas = node.metas - ) - } - } - - override fun transformBexprLet(node: PartiqlLogicalResolved.Bexpr.Let): PartiqlPhysical.Bexpr { - val thiz = this - return PartiqlPhysical.build { - let( - i = DEFAULT_IMPL, - source = thiz.transformBexpr(node.source), - bindings = node.bindings.map { transformLetBinding(it) }, - metas = node.metas - ) - } - } - - override fun transformStatementQuery(node: PartiqlLogicalResolved.Statement.Query): PartiqlPhysical.Statement = - PartiqlPhysical.build { query(transformExpr(node.expr), node.metas) } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/LogicalToLogicalResolvedVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/LogicalToLogicalResolvedVisitorTransform.kt deleted file mode 100644 index c5d0d5b545..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/LogicalToLogicalResolvedVisitorTransform.kt +++ /dev/null @@ -1,598 +0,0 @@ -package org.partiql.lang.planner.transforms - -import com.amazon.ionelement.api.ionSymbol -import org.partiql.errors.Problem -import org.partiql.errors.ProblemHandler -import org.partiql.lang.ast.sourceLocation -import org.partiql.lang.domains.PartiqlLogical -import org.partiql.lang.domains.PartiqlLogicalResolved -import org.partiql.lang.domains.PartiqlLogicalToPartiqlLogicalResolvedVisitorTransform -import org.partiql.lang.domains.toBindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.eval.builtins.DYNAMIC_LOOKUP_FUNCTION_NAME -import org.partiql.lang.eval.physical.sourceLocationMetaOrUnknown -import org.partiql.lang.planner.GlobalResolutionResult -import org.partiql.lang.planner.GlobalVariableResolver -import org.partiql.lang.planner.PlanningProblemDetails -import org.partiql.pig.runtime.asPrimitive - -/** - * Resolves all variables by rewriting `(id )` to - * `(id )`) or `(global_id )`, or a `$__dynamic_lookup__` call site (if enabled). - * - * Local variables are resolved independently within this pass, but we rely on [resolver] to resolve global variables. - * - * There are actually two passes here: - * 1. All [PartiqlLogical.VarDecl] nodes are allocated unique indexes (which is stored in a meta). This pass is - * relatively simple. - * 2. Then, during the transform from the `partiql_logical` domain to the `partiql_logical_resolved` domain, we - * determine if the `id` node refers to a global variable, local variable or undefined variable. For global variables, - * the `id` node is replaced with `(global_id )`. For local variables, the original `id` node is - * replaced with a `(id )`), where `` is the index of the corresponding `var_decl`. - * - * When [allowUndefinedVariables] is `false`, the [problemHandler] is notified of any undefined variables. Resolution - * does not stop on the first undefined variable, rather we keep going to provide the end user any additional error - * messaging, unless [ProblemHandler.handleProblem] throws an exception when an error is logged. **If any undefined - * variables are detected, in order to allow traversal to continue, a fake index value (-1) is used in place of a real - * one and the resolved logical plan returned by this function is guaranteed to be invalid.** **Therefore, it is the - * responsibility of callers to check if any problems have been logged with - * [org.partiql.errors.ProblemSeverity.ERROR] and to abort further query planning if so.** - * - * When [allowUndefinedVariables] is `true`, undefined variables are transformed into a dynamic lookup call site, which - * is semantically equivalent to the behavior of the AST evaluator in the same scenario. For example, `name` in the - * query below is undefined: - * - * ```sql - * SELECT name - * FROM foo AS f, bar AS b - * ``` - * Is effectively rewritten to: - * - * ```sql - * SELECT "$__dynamic_lookup__"('name', 'locals_then_globals', 'case_insensitive', f, b) - * FROM foo AS f, bar AS b - * ``` - * - * When `$__dynamic_lookup__` is invoked it will look for the value of `name` in the following locations: (All field - * / variable name comparisons are case-insensitive in this example, although we could have also specified - * `case_sensitive`.) - * - * - The fields of `f` if it is a struct. - * - The fields of `b` if it is a struct. - * - The global scope. - * - * The first value found is returned and the others are ignored. Local variables are searched first - * (`locals_then_globals`) because of the context of the undefined variable. (`name` is not within a `FROM` source.) - * However, to support SQL's FROM-clause semantics this pass specifies `globals_then_locals` when the variable is within - * a `FROM` source, which causes globals to be searched first. - * - * This behavior is backward compatible with the legacy AST evaluator. Furthermore, this rewrite allows us to avoid - * having to support this kind of dynamic lookup within the plan evaluator, thereby reducing its complexity. This - * rewrite can also be disabled entirely by setting [allowUndefinedVariables] to `false`, in which case undefined - * variables to result in a plan-time error instead. - */ -internal fun PartiqlLogical.Plan.toResolvedPlan( - problemHandler: ProblemHandler, - resolver: GlobalVariableResolver, - allowUndefinedVariables: Boolean = false -): PartiqlLogicalResolved.Plan { - // Allocate a unique id for each `VarDecl` - val (planWithAllocatedVariables, allLocals) = this.allocateVariableIds() - - // Transform to `partiql_logical_resolved` while resolving variables. - val resolvedSt = LogicalToLogicalResolvedVisitorTransform(allowUndefinedVariables, problemHandler, resolver) - .transformPlan(planWithAllocatedVariables) - .copy(locals = allLocals) - - return resolvedSt -} - -/** This class's subclasses represent the possible outcomes from an attempt to resolve a variable. */ -private sealed class ResolvedVariable { - - /** - * A success case, indicates the [uniqueId] of the match to the [BindingName] in the global scope. - * Typically, this is defined by the storage layer. - * - * In the future, this will likely contain much more than just a unique id. It might include detailed schema - * information about global variables. - */ - data class Global(val uniqueId: String) : ResolvedVariable() - - /** - * A success case, indicates the [index] of the only possible match to the [BindingName] in a local lexical scope. - * This is `internal` because [index] is an implementation detail that shouldn't be accessible outside of this - * library. - */ - data class LocalVariable(val index: Int) : ResolvedVariable() - - /** A failure case, indicates that resolution did not match any variable. */ - object Undefined : ResolvedVariable() -} - -/** - * Converts the public [GlobalResolutionResult] (which cannot represent local variables) to the private [ResolvedVariable], - * which can represent local variables. - */ -private fun GlobalResolutionResult.toResolvedVariable() = - when (this) { - is GlobalResolutionResult.GlobalVariable -> ResolvedVariable.Global(this.uniqueId) - GlobalResolutionResult.Undefined -> ResolvedVariable.Undefined - } - -/** - * A local scope is a list of variable declarations that are produced by a relational operator and an optional - * reference to a parent scope. This is handled separately from global variables. - * - * This is a [List] of [PartiqlLogical.VarDecl] and not a [Map] or some other more efficient data structure - * because most variable lookups are case-insensitive, which makes storing them in a [Map] and benefiting from it hard. - */ -private data class LocalScope(val varDecls: List) - -internal data class LogicalToLogicalResolvedVisitorTransform( - /** If set to `true`, do not log errors about undefined variables. Rewrite such variables to a `dynamic_id` node. */ - val allowUndefinedVariables: Boolean, - /** Where to send error reports. */ - private val problemHandler: ProblemHandler, - /** If a variable is not found using [inputScope], we will attempt to locate the binding here instead. */ - private val globals: GlobalVariableResolver, - -) : PartiqlLogicalToPartiqlLogicalResolvedVisitorTransform() { - /** The current [LocalScope]. */ - private var inputScope: LocalScope = LocalScope(emptyList()) - - private enum class VariableLookupStrategy { - LOCALS_THEN_GLOBALS, - GLOBALS_THEN_LOCALS - } - - /** - * This is set to [VariableLookupStrategy.GLOBALS_THEN_LOCALS] for the `` in `(scan ...)` nodes and - * [VariableLookupStrategy.LOCALS_THEN_GLOBALS] for everything else. This is we resolve globals first within - * a `FROM`. - */ - private var currentVariableLookupStrategy: VariableLookupStrategy = VariableLookupStrategy.LOCALS_THEN_GLOBALS - - private fun withVariableLookupStrategy(nextVariableLookupStrategy: VariableLookupStrategy, block: () -> T): T { - val lastVariableLookupStrategy = this.currentVariableLookupStrategy - this.currentVariableLookupStrategy = nextVariableLookupStrategy - return block().also { - this.currentVariableLookupStrategy = lastVariableLookupStrategy - } - } - - private fun withInputScope(nextScope: LocalScope, block: () -> T): T { - val lastScope = inputScope - inputScope = nextScope - return block().also { - inputScope = lastScope - } - } - - private fun PartiqlLogical.Expr.Id.asGlobalId(uniqueId: String): PartiqlLogicalResolved.Expr.GlobalId = - PartiqlLogicalResolved.build { - globalId_( - uniqueId = uniqueId.asPrimitive(), - metas = this@asGlobalId.metas - ) - } - - private fun PartiqlLogical.Expr.Id.asLocalId(index: Int): PartiqlLogicalResolved.Expr = - PartiqlLogicalResolved.build { - localId_(index.asPrimitive(), this@asLocalId.metas) - } - - private fun PartiqlLogical.Expr.Id.asErrorId(): PartiqlLogicalResolved.Expr = - PartiqlLogicalResolved.build { - localId_((-1).asPrimitive(), this@asErrorId.metas) - } - - override fun transformPlan(node: PartiqlLogical.Plan): PartiqlLogicalResolved.Plan = - PartiqlLogicalResolved.build { - plan_( - stmt = transformStatement(node.stmt), - version = node.version, - locals = emptyList(), // NOTE: locals will be populated by caller - metas = node.metas - ) - } - - override fun transformBexprScan_expr(node: PartiqlLogical.Bexpr.Scan): PartiqlLogicalResolved.Expr = - withVariableLookupStrategy(VariableLookupStrategy.GLOBALS_THEN_LOCALS) { - super.transformBexprScan_expr(node) - } - - override fun transformBexprJoin_right(node: PartiqlLogical.Bexpr.Join): PartiqlLogicalResolved.Bexpr { - // No need to change the current scope of the node.left. Node.right gets the current scope + - // the left output scope. - val leftOutputScope = getOutputScope(node.left) - val rightInputScope = inputScope.concatenate(leftOutputScope) - return withInputScope(rightInputScope) { - this.transformBexpr(node.right) - } - } - - override fun transformBexprLet(node: PartiqlLogical.Bexpr.Let): PartiqlLogicalResolved.Bexpr { - val thiz = this - return PartiqlLogicalResolved.build { - let( - source = transformBexpr(node.source), - bindings = withInputScope(getOutputScope(node.source)) { - // This "wonderful" (depending on your definition of the term) bit of code performs a fold - // combined with a map... The accumulator is a Pair, - // LocalScope>. - // accumulator.first: the current list of let bindings that have been transformed so far - // accumulator.second: an instance of LocalScope that includes all the variables defined up to - // this point, not including the current let binding. - val initial = emptyList() to thiz.inputScope - val (newBindings: List, _: LocalScope) = - node.bindings.fold(initial) { accumulator, current -> - // Each let binding's expression should be resolved within the scope of the *last* - // let binding (or the current scope if this is the first let binding). - val resolvedValueExpr = withInputScope(accumulator.second) { - thiz.transformExpr(current.value) - } - val nextScope = LocalScope(listOf(current.decl)).concatenate(accumulator.second) - val transformedLetBindings = accumulator.first + PartiqlLogicalResolved.build { - letBinding(resolvedValueExpr, transformVarDecl(current.decl)) - } - transformedLetBindings to nextScope - } - newBindings - } - ) - } - } - - override fun transformBexprWindow_windowSpecification(node: PartiqlLogical.Bexpr.Window): PartiqlLogicalResolved.Over { - val bindings = getOutputScope(node).concatenate(this.inputScope) - return withInputScope(bindings) { - node.windowSpecification.let { - this.transformOver(it) - } - } - } - - override fun transformBexprWindow_windowExpressionList(node: PartiqlLogical.Bexpr.Window): List { - val bindings = getOutputScope(node).concatenate(this.inputScope) - return withInputScope(bindings) { - node.windowExpressionList.map { - this.transformWindowExpression(it) - } - } - } - - // We are currently using bindings_to_values to denote a sub-query, which works for all the use cases we are - // presented with today, as every SELECT statement is replaced with `bindings_to_values at the top level. - override fun transformExprBindingsToValues(node: PartiqlLogical.Expr.BindingsToValues): PartiqlLogicalResolved.Expr = - // If we are in the expr of a scan node, we need to reset the lookup strategy - withVariableLookupStrategy(VariableLookupStrategy.LOCALS_THEN_GLOBALS) { - super.transformExprBindingsToValues(node) - } - - /** - * Grabs the index meta added by [VariableIdAllocator] and stores it as an element in - * [PartiqlLogicalResolved.VarDecl]. - */ - override fun transformVarDecl(node: PartiqlLogical.VarDecl): PartiqlLogicalResolved.VarDecl = - PartiqlLogicalResolved.build { - varDecl(node.indexMeta.toLong()) - } - - /** - * Returns [GlobalResolutionResult.LocalVariable] if [bindingName] refers to a local variable. - * - * Otherwise, returns [GlobalResolutionResult.Undefined]. (Elsewhere, [globals] will be checked next.) - */ - private fun resolveLocalVariable(bindingName: BindingName): ResolvedVariable { - val found = this.inputScope.varDecls.firstOrNull { bindingName.isEquivalentTo(it.name.text) } - return if (found == null) { - ResolvedVariable.Undefined - } else { - ResolvedVariable.LocalVariable(found.indexMeta) - } - } - - /** - * Resolves the logical `(id ...)` node node to a `(local_id ...)`, `(global_id ...)`, or dynamic `(id...)` - * variable. - */ - override fun transformExprId(node: PartiqlLogical.Expr.Id): PartiqlLogicalResolved.Expr { - val bindingName = BindingName(node.name.text, node.case.toBindingCase()) - - val globalResolutionResult = if ( - this.currentVariableLookupStrategy == VariableLookupStrategy.GLOBALS_THEN_LOCALS && - node.qualifier is PartiqlLogical.ScopeQualifier.Unqualified - ) { - // look up variable in globals first, then locals - when (val resolvedVariable = globals.resolveGlobal(bindingName)) { - GlobalResolutionResult.Undefined -> resolveLocalVariable(bindingName) - else -> resolvedVariable.toResolvedVariable() - } - } else { - // look up variable in locals first, then globals. - when (val localResolutionResult = resolveLocalVariable(bindingName)) { - ResolvedVariable.Undefined -> globals.resolveGlobal(bindingName).toResolvedVariable() - else -> localResolutionResult - } - } - return when (globalResolutionResult) { - is ResolvedVariable.Global -> { - node.asGlobalId(globalResolutionResult.uniqueId) - } - is ResolvedVariable.LocalVariable -> { - node.asLocalId(globalResolutionResult.index) - } - ResolvedVariable.Undefined -> { - if (this.allowUndefinedVariables) { - node.asDynamicLookupCallsite( - currentDynamicResolutionCandidates() - .map { - PartiqlLogicalResolved.build { - localId(it.indexMeta.toLong()) - } - } - ) - } else { - node.asErrorId().also { - problemHandler.handleProblem( - Problem( - (node.metas.sourceLocation ?: error("MetaContainer is missing SourceLocationMeta")).toProblemLocation(), - PlanningProblemDetails.UndefinedVariable( - node.name.text, - node.case is PartiqlLogical.CaseSensitivity.CaseSensitive - ) - ) - ) - } - } - } - } - } - - override fun transformStatementDml(node: PartiqlLogical.Statement.Dml): PartiqlLogicalResolved.Statement { - // We only support DML targets that are global variables. - val bindingName = BindingName(node.target.name.text, node.target.case.toBindingCase()) - val tableUniqueId = when (val resolvedVariable = globals.resolveGlobal(bindingName)) { - is GlobalResolutionResult.GlobalVariable -> resolvedVariable.uniqueId - GlobalResolutionResult.Undefined -> { - problemHandler.handleProblem( - Problem( - node.metas.sourceLocationMetaOrUnknown.toProblemLocation(), - PlanningProblemDetails.UndefinedDmlTarget( - node.target.name.text, - node.target.case is PartiqlLogical.CaseSensitivity.CaseSensitive - ) - ) - ) - "undefined DML target: ${node.target.name.text} - do not run" - } - } - return PartiqlLogicalResolved.build { - dml( - uniqueId = tableUniqueId, - operation = transformDmlOperation(node.operation), - rows = transformExpr(node.rows), - metas = node.metas - ) - } - } - - override fun transformDmlOperationDmlInsert(node: PartiqlLogical.DmlOperation.DmlInsert): PartiqlLogicalResolved.DmlOperation { - return withInputScope(this.inputScope.concatenate(node.targetAlias)) { - super.transformDmlOperationDmlInsert(node) - } - } - - override fun transformDmlOperationDmlReplace(node: PartiqlLogical.DmlOperation.DmlReplace): PartiqlLogicalResolved.DmlOperation { - val scopeWithTarget = this.inputScope.concatenate(node.targetAlias) - val inputScope = node.rowAlias?.let { scopeWithTarget.concatenate(it) } ?: scopeWithTarget - return withInputScope(inputScope) { - super.transformDmlOperationDmlReplace(node) - } - } - - override fun transformDmlOperationDmlUpdate(node: PartiqlLogical.DmlOperation.DmlUpdate): PartiqlLogicalResolved.DmlOperation { - val scopeWithTarget = this.inputScope.concatenate(node.targetAlias) - val inputScope = node.rowAlias?.let { scopeWithTarget.concatenate(it) } ?: scopeWithTarget - return withInputScope(inputScope) { - super.transformDmlOperationDmlUpdate(node) - } - } - - /** - * Returns a list of variables accessible from the current scope which contain variables that may contain - * an unqualified variable, in the order that they should be searched. - */ - fun currentDynamicResolutionCandidates(): List = - inputScope.varDecls.filter { it.includeInDynamicResolution } - - override fun transformExprBindingsToValues_exp(node: PartiqlLogical.Expr.BindingsToValues): PartiqlLogicalResolved.Expr { - val bindings = getOutputScope(node.query).concatenate(this.inputScope) - return withInputScope(bindings) { - this.transformExpr(node.exp) - } - } - - override fun transformBexprSort_sortSpecs(node: PartiqlLogical.Bexpr.Sort): List { - val bindings = getOutputScope(node.source).concatenate(this.inputScope) - return withInputScope(bindings) { - node.sortSpecs.map { - this.transformSortSpec(it) - } - } - } - - override fun transformBexprFilter_predicate(node: PartiqlLogical.Bexpr.Filter): PartiqlLogicalResolved.Expr { - val bindings = getOutputScope(node.source) - return withInputScope(bindings) { - this.transformExpr(node.predicate) - } - } - - override fun transformBexprAggregate(node: PartiqlLogical.Bexpr.Aggregate): PartiqlLogicalResolved.Bexpr { - val scope = getOutputScope(node.source).concatenate(this.inputScope) - return PartiqlLogicalResolved.build { - aggregate( - source = transformBexpr(node.source), - strategy = transformBexprAggregate_strategy(node), - groupList = withInputScope(scope) { transformBexprAggregate_groupList(node) }, - functionList = withInputScope(scope) { transformBexprAggregate_functionList(node) }, - metas = transformBexprAggregate_metas(node) - ) - } - } - - override fun transformBexprJoin_predicate(node: PartiqlLogical.Bexpr.Join): PartiqlLogicalResolved.Expr? { - val bindings = getOutputScope(node) - return withInputScope(bindings) { - node.predicate?.let { this.transformExpr(it) } - } - } - - /** - * Rewrites PIVOT with resolved variables of the relevant scope - */ - override fun transformExprPivot(node: PartiqlLogical.Expr.Pivot): PartiqlLogicalResolved.Expr { - val scope = getOutputScope(node.input).concatenate(this.inputScope) - return PartiqlLogicalResolved.build { - pivot( - input = transformBexpr(node.input), - key = withInputScope(scope) { transformExpr(node.key) }, - value = withInputScope(scope) { transformExpr(node.value) }, - metas = transformMetas(node.metas) - ) - } - } - - /** - * This should be called any time we create a [LocalScope] with more than one variable to prevent duplicate - * variable names. When checking for duplication, the letter case of the variable names is not considered. - * - * Example: - * - * ``` - * SELECT * FROM foo AS X AT x - * duplicate variable: ^ - * ``` - */ - private fun checkForDuplicateVariables(varDecls: List) { - val usedVariableNames = hashSetOf() - varDecls.forEach { varDecl -> - val loweredVariableName = varDecl.name.text.lowercase() - if (usedVariableNames.contains(loweredVariableName)) { - this.problemHandler.handleProblem( - Problem( - varDecl.metas.sourceLocation?.toProblemLocation() ?: error("VarDecl was missing source location meta"), - PlanningProblemDetails.VariablePreviouslyDefined(varDecl.name.text) - ) - ) - } - usedVariableNames.add(loweredVariableName) - } - } - - /** - * Computes a [LocalScope] for containing all of the variables that are output from [bexpr]. - */ - private fun getOutputScope(bexpr: PartiqlLogical.Bexpr): LocalScope = - when (bexpr) { - is PartiqlLogical.Bexpr.Filter -> getOutputScope(bexpr.source) - is PartiqlLogical.Bexpr.Limit -> getOutputScope(bexpr.source) - is PartiqlLogical.Bexpr.Offset -> getOutputScope(bexpr.source) - is PartiqlLogical.Bexpr.Sort -> getOutputScope(bexpr.source) - is PartiqlLogical.Bexpr.Aggregate -> { - val keyVariables = bexpr.groupList.keys.map { it.asVar } - val functionVariables = bexpr.functionList.functions.map { it.asVar } - LocalScope(keyVariables + functionVariables) - } - is PartiqlLogical.Bexpr.Scan -> { - LocalScope( - listOfNotNull(bexpr.asDecl.markForDynamicResolution(), bexpr.atDecl, bexpr.byDecl).also { - checkForDuplicateVariables(it) - } - ) - } - is PartiqlLogical.Bexpr.Unpivot -> { - LocalScope( - listOfNotNull(bexpr.asDecl.markForDynamicResolution(), bexpr.atDecl, bexpr.byDecl).also { - checkForDuplicateVariables(it) - } - ) - } - is PartiqlLogical.Bexpr.Join -> { - val (leftBexpr, rightBexpr) = when (bexpr.joinType) { - is PartiqlLogical.JoinType.Full, - is PartiqlLogical.JoinType.Inner, - is PartiqlLogical.JoinType.Left -> bexpr.left to bexpr.right - // right join is same as left join but right and left operands are swapped. - is PartiqlLogical.JoinType.Right -> bexpr.right to bexpr.left - } - val leftScope = getOutputScope(leftBexpr) - val rightScope = getOutputScope(rightBexpr) - // right scope is first to allow RHS variables to "shadow" LHS variables. - rightScope.concatenate(leftScope) - } - is PartiqlLogical.Bexpr.Let -> { - val sourceScope = getOutputScope(bexpr.source) - // Note that .reversed() is important here to ensure that variable shadowing works correctly. - val letVariables = bexpr.bindings.reversed().map { it.decl } - sourceScope.concatenate(letVariables) - } - - is PartiqlLogical.Bexpr.Window -> { - val sourceScope = getOutputScope(bexpr.source) - val windowVariable = bexpr.windowExpressionList.map { it.decl } - sourceScope.concatenate(windowVariable) - } - } - - private fun LocalScope.concatenate(other: LocalScope): LocalScope = - this.concatenate(other.varDecls) - - private fun LocalScope.concatenate(other: List): LocalScope { - val concatenatedScopeVariables = this.varDecls + other - return LocalScope(concatenatedScopeVariables) - } - - private fun LocalScope.concatenate(other: PartiqlLogical.VarDecl): LocalScope { - val concatenatedScopeVariables = this.varDecls + listOf(other) - return LocalScope(concatenatedScopeVariables) - } - - private fun PartiqlLogical.Expr.Id.asDynamicLookupCallsite( - search: List - ): PartiqlLogicalResolved.Expr { - val caseSensitivityString = when (case) { - is PartiqlLogical.CaseSensitivity.CaseInsensitive -> "case_insensitive" - is PartiqlLogical.CaseSensitivity.CaseSensitive -> "case_sensitive" - } - val variableLookupStrategy = when (currentVariableLookupStrategy) { - // If we are not in a FROM source, ignore the scope qualifier - VariableLookupStrategy.LOCALS_THEN_GLOBALS -> VariableLookupStrategy.LOCALS_THEN_GLOBALS - // If we are in a FROM source, allow scope qualifier to override the current variable lookup strategy. - VariableLookupStrategy.GLOBALS_THEN_LOCALS -> when (this.qualifier) { - is PartiqlLogical.ScopeQualifier.LocalsFirst -> VariableLookupStrategy.LOCALS_THEN_GLOBALS - is PartiqlLogical.ScopeQualifier.Unqualified -> VariableLookupStrategy.GLOBALS_THEN_LOCALS - } - }.toString().lowercase() - return PartiqlLogicalResolved.build { - call( - funcName = DYNAMIC_LOOKUP_FUNCTION_NAME, - args = listOf( - lit(name.toIonElement()), - lit(ionSymbol(caseSensitivityString)), - lit(ionSymbol(variableLookupStrategy)), - list(search) - ), - metas = this@asDynamicLookupCallsite.metas - ) - } - } -} - -/** Marks a variable for dynamic resolution--i.e. if undefined, this vardecl will be included in any dynamic_id lookup. */ -private fun PartiqlLogical.VarDecl.markForDynamicResolution() = this.withMeta("\$include_in_dynamic_resolution", Unit) - -/** Returns true of the [VarDecl] has been marked to participate in unqualified field resolution */ -private val PartiqlLogical.VarDecl.includeInDynamicResolution get() = this.metas.containsKey("\$include_in_dynamic_resolution") diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/Util.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/Util.kt deleted file mode 100644 index 8d3023f1a3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/Util.kt +++ /dev/null @@ -1,40 +0,0 @@ - -package org.partiql.lang.planner.transforms - -import com.amazon.ionelement.api.ElementType -import org.partiql.lang.domains.PartiqlPhysical - -/** - * This is the semantic version number of the logical and physical plans supported by this version of PartiQL. This - * deals only with compatibility of trees that have been persisted as s-expressions with their PIG-generated - * classes. The format is: `.`. One or both of these will need to be changed when the following - * events happen: - * - * - Increment `` and set `` to `0` when a change to `partiql.ion` is introduced that will cause the - * persisted s-expressions to fail to load under the new version. Examples include: - * - Making an element non-nullable that was previously nullable. - * - Renaming any type or sum variant. - * - Removing a sum variant. - * - Adding or removing any element of any product type. - * - Changing the data type of any element. - * - Adding a required field to a record type. - * - Increment `` when a change to `partiql.ion` is introduced that will *not* cause the persisted s-expressions - * to fail to load into the PIG-generated classes. Examples include: - * - Adding a new, optional (nullable) field to a record type. - * - Adding a new sum variant. - * - Changing an element that was previously non-nullable nullable. - * - * It would be nice to embed semantic version in the PIG type universe somehow, but this isn't yet implemented, so we - * have to include it here for now. See: https://github.com/partiql/partiql-ir-generator/issues/121 - */ -const val PLAN_VERSION_NUMBER = "0.0" - -internal fun errAstNotNormalized(message: String): Nothing = - error("$message - have the basic visitor transforms been executed first?") - -/** Returns true if the receiver is `(lit true)`. */ -fun PartiqlPhysical.Expr.isLitTrue() = - when (this) { - is PartiqlPhysical.Expr.Lit -> this.value.type == ElementType.BOOL && this.value.booleanValue - else -> false - } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/VariableIdAllocator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/VariableIdAllocator.kt deleted file mode 100644 index de3ffaeb68..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/VariableIdAllocator.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.partiql.lang.planner.transforms - -import org.partiql.lang.domains.PartiqlLogical -import org.partiql.lang.domains.PartiqlLogicalResolved - -/** - * Allocates register indexes for all local variables in the plan. - * - * Returns pair containing a logical plan where all `var_decl`s have a [VARIABLE_ID_META_TAG] meta indicating the - * variable index (which can be utilized later when establishing variable scoping) and list of all local variables - * declared within the plan, which becomes the `locals` sub-node of the `plan` node. - */ -internal fun PartiqlLogical.Plan.allocateVariableIds(): Pair> { - val allLocals = mutableListOf() - val planWithAllocatedVariables = VariableIdAllocator(allLocals).transformPlan(this) - return planWithAllocatedVariables to allLocals.toList() -} - -private const val VARIABLE_ID_META_TAG = "\$variable_id" - -internal val PartiqlLogical.VarDecl.indexMeta - get() = this.metas[VARIABLE_ID_META_TAG] as? Int ?: error("Meta $VARIABLE_ID_META_TAG was not present") - -/** - * Allocates a unique index to every `var_decl` in the logical plan. We use metas for this step to avoid a having - * create another permuted domain. - */ -private class VariableIdAllocator( - val allLocals: MutableList -) : PartiqlLogical.VisitorTransform() { - private var nextVariableId = 0 - - override fun transformVarDecl(node: PartiqlLogical.VarDecl): PartiqlLogical.VarDecl = - node.withMeta(VARIABLE_ID_META_TAG, nextVariableId).also { - - allLocals.add( - PartiqlLogicalResolved.build { - localVariable(node.name.text, nextVariableId.toLong()) - } - ) - - nextVariableId++ - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/ConcatWindowFunction.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/ConcatWindowFunction.kt deleted file mode 100644 index fe2cd0a9cc..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/ConcatWindowFunction.kt +++ /dev/null @@ -1,87 +0,0 @@ -package org.partiql.lang.planner.transforms.optimizations - -import org.partiql.errors.ProblemHandler -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.planner.PartiQLPhysicalPass - -internal fun createConcatWindowFunctionPass(): PartiQLPhysicalPass = - ConcatWindowFunction() - -/** - * Creates an instance of [PartiQLPhysicalPass] that concatenate window functions if they are - * 1) in the same query level (we don't want to concatenate sub-query's window function) - * 2) operate the same window partition - * - * window( - * window( - * scan(...), - * over([some window definition A]), - * windowExpression(varDecl("\$__partiql_window_function_0"), "lag", listOf(id("a"))) - * ), - * over([some window definition A]) - * windowExpression(varDecl("\$__partiql_window_function_1"), "lead", listOf(id("a"))) - * ) - * Will become - * window( - * scan(...), - * over([some window definition A]), - * windowExpression(varDecl("\$__partiql_window_function_0"), "lag", listOf(id("a"))) - * windowExpression(varDecl("\$__partiql_window_function_1"), "lead", listOf(id("a"))) - * ) - */ -private class ConcatWindowFunction : PartiQLPhysicalPass { - override fun apply(plan: PartiqlPhysical.Plan, problemHandler: ProblemHandler): PartiqlPhysical.Plan { - return object : PartiqlPhysical.VisitorTransform() { - override fun transformBexprWindow(node: PartiqlPhysical.Bexpr.Window): PartiqlPhysical.Bexpr { - val rewritten = super.transformBexprWindow(node) as PartiqlPhysical.Bexpr.Window - return rewritten.rewriteWindowExpression() - } - }.transformPlan(plan) - } -} - -private fun PartiqlPhysical.Bexpr.Window.rewriteWindowExpression(): PartiqlPhysical.Bexpr { - val modifiedWindowExpressionList = object : PartiqlPhysical.VisitorTransform() { - - // Only allow to recursive into the window node - override fun transformBexpr(node: PartiqlPhysical.Bexpr) = - when (node) { - is PartiqlPhysical.Bexpr.Window -> super.transformBexpr(node) - else -> node - } - - override fun transformBexprWindow(node: PartiqlPhysical.Bexpr.Window): PartiqlPhysical.Bexpr { - val rewritten = super.transformBexprWindow(node) as PartiqlPhysical.Bexpr.Window - return handleIdenticalOver(rewritten, rewritten.source, rewritten) - } - - private fun handleIdenticalOver( - toCompareNode: PartiqlPhysical.Bexpr.Window, - previousNode: PartiqlPhysical.Bexpr, - currentNode: PartiqlPhysical.Bexpr.Window - ): PartiqlPhysical.Bexpr { - if (previousNode !is PartiqlPhysical.Bexpr.Window) { - return currentNode - } - // check the next level - else if (toCompareNode.windowSpecification != previousNode.windowSpecification) { - return PartiqlPhysical.Bexpr.Window( - i = currentNode.i, - source = handleIdenticalOver(toCompareNode, previousNode.source, previousNode), - windowSpecification = currentNode.windowSpecification, - windowExpressionList = currentNode.windowExpressionList - ) - } else { - // No need to further recurisve - return PartiqlPhysical.Bexpr.Window( - i = previousNode.i, - source = previousNode.source, - windowSpecification = previousNode.windowSpecification, - windowExpressionList = previousNode.windowExpressionList.toMutableList().plus(toCompareNode.windowExpressionList).toList() - ) - } - } - }.transformBexpr(this) - - return modifiedWindowExpressionList -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/FilterScanToKeyLookup.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/FilterScanToKeyLookup.kt deleted file mode 100644 index 6ada83a297..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/FilterScanToKeyLookup.kt +++ /dev/null @@ -1,334 +0,0 @@ -package org.partiql.lang.planner.transforms.optimizations - -import com.amazon.ionelement.api.TextElement -import com.amazon.ionelement.api.ionBool -import com.amazon.ionelement.api.ionSymbol -import org.partiql.errors.ProblemHandler -import org.partiql.lang.compiler.PartiQLCompilerBuilder -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.domains.toBindingCase -import org.partiql.lang.eval.BindingName -import org.partiql.lang.planner.PartiQLPhysicalPass -import org.partiql.lang.planner.StaticTypeResolver -import org.partiql.lang.planner.transforms.DEFAULT_IMPL -import org.partiql.types.BagType -import org.partiql.types.ListType -import org.partiql.types.StructType - -/** - * The "filter scan to key lookup" pass identifies all equality expressions where either side is a primary key - * within the filter's predicate. This class represents the identified key field and the opposing value expression of - * each such equality expression. For example, given the filter predicate: `foo.id = 42`, the [keyFieldName] is `id` - * and the [equivalentValue] is `(lit 42)`. - */ -data class FieldEqualityPredicate(val keyFieldName: String, val equivalentValue: PartiqlPhysical.Expr) - -/** - * If possible, changes a `filter` operator with a nested `scan` into an `project` operator that looks up a single - * record by its primary key. - * - * The implementation of the `project` operator is specified by [customProjectOperatorName], - * which must be supplied separately by the user (see [PartiQLCompilerBuilder.customOperatorFactories]). (More - * details on this operator below). - * - * For example, given a `filter` node with a nested `scan`, such as: - * - * ``` - * (filter (impl ...) - * (impl ...) - * - * (scan (impl ...) (global_id ) (var_decl n)))) - *``` - * - * If `` includes all primary key fields of the table referenced by `` (in the form of - * `x.keyField1 = [and x.keyFieldN = ]...`), then those `=` expressions are replaced with `(lit true)` - * (rendering them inert) and the inner `scan` is replaced with a `project` operator with the - * [customProjectOperatorName]. This may leave some unnecessary `and` expressions and `filter` ``s. These - * are removed later by the passes [RemoveUselessAndsPass] and [RemoveUselessFiltersPass]. - * - * The replacement `project` operator takes the following form: - * - * ``` - * (project - * (impl ) - * (var_decl ) - * ) - *``` - * - * Note that The custom project operator impl must accept a single `` static argument and a single dynamic - * argument, ``, which is the primary key value. The expression used to compute the key is constructed by - * [createKeyValueConstructor] which is a function that is passed the table's [StructType] and a list of - * [FieldEqualityPredicate] instances. - * - * ### Notes - * - * - Although not strictly needed, it is recommended to include the [RemoveUselessAndsPass] and - * [RemoveUselessFiltersPass] **after** this pass to remove any useless ands and filters left behind. - * - * ### Limitations - * - * - Key field references must be fully qualified, (e.g. `someLocalVar.primaryKey = ` and not - * `primaryKey = `. In the future, implicit qualification might be handled by a separate, earlier pass. - * - Key fields must be values at the first level within the row's struct and cannot be nested. e.g. `x.primaryKey` - * is supported but `x.person.ssn` is not. - * - Key field references must be at root node of the `` or nested at any level within a tree of expressions - * consisting only of `and` parents. (This rewrite cannot apply to other expression types without changing the semantic - * intent of the query.) - * - `(eq ...)` expressions not involving exactly 2 operands will fail. The AST modeling supports n operands, - * but supporting > 2 here adds complexity that we may never use, because as of now there is nothing in this entire - * codebase that composes n-ary expressions with more than 2 operands. - */ -fun createFilterScanToKeyLookupPass( - customProjectOperatorName: String, - staticTypeResolver: StaticTypeResolver, - createKeyValueConstructor: (StructType, List) -> PartiqlPhysical.Expr -): PartiQLPhysicalPass = - FilterScanToKeyLookupPass(staticTypeResolver, customProjectOperatorName, createKeyValueConstructor) - -private class FilterScanToKeyLookupPass( - private val staticTypeResolver: StaticTypeResolver, - private val customProjectOperatorName: String, - private val createKeyValueConstructor: (StructType, List) -> PartiqlPhysical.Expr -) : PartiQLPhysicalPass { - override fun apply(plan: PartiqlPhysical.Plan, problemHandler: ProblemHandler): PartiqlPhysical.Plan { - return object : PartiqlPhysical.VisitorTransform() { - override fun transformBexprFilter(node: PartiqlPhysical.Bexpr.Filter): PartiqlPhysical.Bexpr { - // Rewrite children first. - val rewritten = super.transformBexprFilter(node) as PartiqlPhysical.Bexpr.Filter - when (val filterSource = rewritten.source) { - // check if the source of the filter is a full scan - is PartiqlPhysical.Bexpr.Scan -> { - when (val scanExpr = filterSource.expr) { - // check if the scan's expression is a reference to a global variable - is PartiqlPhysical.Expr.GlobalId -> { - // At this point, we've matched a (filter ... (scan ... (global_id ))) - // We know the unique id of the table, and we can use it to get the table's static - // type. - - // Non-table global variables may exist and can be any type. - // Tables however are always bags or lists of structs. - - // Let's ensure that we have a bag or list and get the type of its row. - val rowStaticType = when ( - val globalStaticType = - staticTypeResolver.getVariableStaticType(scanExpr.uniqueId.text) - ) { - is BagType -> globalStaticType.elementType - is ListType -> globalStaticType.elementType - else -> return rewritten // <-- bail out; this optimization does not apply - } - - // If the element type (i.e. type of its rows) of the global variable is not a - // struct, this optimization also does not apply - if (rowStaticType !is StructType) { - return rewritten - } - - // Now that we have static type of the row on hand we can attempt to rewrite the - // filter predicate, replacing `
. = ` with `TRUE`, but - // leaving any other part of the filter predicate unmodified. - val (newPredicate, keyFieldEqualityPredicates) = rewritten.predicate.rewriteFilterPredicate( - filterSource.asDecl.index.value, - rowStaticType.primaryKeyFields - ) // if we didn't succeed in rewriting the filter predicate, there are no primary - // key field references in equality expressions in the filter predicate or not - // all key fields were included and this optimization does not apply. - ?: return rewritten - - // this just a quick sanity check to be more confident in the result of - // .rewriteFilterPredicate(), above if this fails, there is definitely a bug. - require(keyFieldEqualityPredicates.size == rowStaticType.primaryKeyFields.size) - - // Finally, compose a new filter/project to replace the original filter/scan. - return PartiqlPhysical.build { - filter( - DEFAULT_IMPL, - newPredicate, - project( - impl(customProjectOperatorName, listOf(ionSymbol(scanExpr.uniqueId.text))), - filterSource.asDecl, - createKeyValueConstructor(rowStaticType, keyFieldEqualityPredicates) - ) - ) - } - } - else -> return rewritten // didn't match--return the original node unmodified. - } - } - else -> return rewritten // didn't match--return the original node unmodified. - } - } - }.transformPlan(plan) - } -} - -/** - * In the receiving expression, replaces all expressions in the form of: - * - * ``` - * (eq - * (path (local_id ) (path_expr (lit ) )) - * -* ) - * ``` - * - * ... with `(lit true)` if and only if: - * - * - `` is equal to [variableIndexId] and - * - `` contained within [primaryKeyFields] (respecting ``). - * - * If `null` is returned, expressions matching the above were not found for all (or any) primary key fields. Otherwise, - * returns a [Pair] containing: - * - * - The expression predicate with all references to the key fields replaced with `(lit true)`. - * - A list of [FieldEqualityPredicate], which is a list of the referenced key fields and value expressions. - */ -private fun PartiqlPhysical.Expr.rewriteFilterPredicate( - /** - * The index of the variable containing the primary key. We ignore equality expressions that don't reference this - * variable. - */ - variableIndexId: Long, - - /** - * A list of primary key fields. Equals expressions must be found in the filter predicate that match all these - * keys, otherwise, we refuse to rewrite the filter predicate. - */ - primaryKeyFields: List -): Pair>? { - val remainingFilterKeys = primaryKeyFields.toMutableList() - val filterKeyValueExpressions = ArrayList() - - val modifiedPredicate = object : PartiqlPhysical.VisitorTransform() { - - /** - * Only allow recursion into `eq` and `and` expressions. - * - * Other expression types cannot be rewritten without changing the semantic intent of the query. - */ - override fun transformExpr(node: PartiqlPhysical.Expr): PartiqlPhysical.Expr = - when (node) { - is PartiqlPhysical.Expr.And, is PartiqlPhysical.Expr.Eq -> super.transformExpr(node) - else -> node - } - - override fun transformExprEq(node: PartiqlPhysical.Expr.Eq): PartiqlPhysical.Expr { - val rewritten = super.transformExprEq(node) as PartiqlPhysical.Expr.Eq - - // The AST's modeling allows n arguments, but IRL the parser never constructs a node with more than 2 - // operands. If the check below fails, to fix, either: - // - `eq` expressions with > 2 arguments must be re-nested as binary expressions - // - rework the logic below to work with more than 2 operands. - // - // We don't do the latter now because it seems particularly complex and doesn't have value--there's nothing - // that flattens n-ary expressions with > 2 arguments today. - require(rewritten.operands.size == 2) { - "nest n-ary equality expressions to binary before calling this expression" - } - - // handleKeyEqualityPredicate returns true and updates both remainingFilterKeys and - // filterKeyValueExpressions if the first argument is a reference to a primary key field - // i.e. `x.keyField = `. - var matched = handleKeyEqualityPredicate(rewritten.operands[0], rewritten.operands[1]) - - // If we didn't match `x.keyField = `, try the reverse, i.e. ` = x.keyField` - if (!matched) { - matched = handleKeyEqualityPredicate(rewritten.operands[1], rewritten.operands[0]) - } - - return when (matched) { - false -> rewritten - true -> - // If we've found a reference to a key field, we can replace it with (lit true). - // This might make an `and` expression or `(filter ...)` predicate redundant (i.e. `(and (lit true)...)` - // or (filter (lit true) ...)` but we remove those in a later pass. - PartiqlPhysical.build { lit(ionBool(true)) } - } - } - - /** - * If [operand1] is a key field reference of the local variable with matching [variableIndexId], - * and if [operand2] does not contain a reference to [variableIndexId], adds and removes the field from - * [remainingFilterKeys] and adds an entry to [filterKeyValueExpressions]. - * - * Otherwise, `false`. - */ - private fun handleKeyEqualityPredicate( - operand1: PartiqlPhysical.Expr, - operand2: PartiqlPhysical.Expr - ): Boolean { - val fieldReference = operand1.getKeyFieldReference() ?: return false - - // if the located field reference was to the table of interest - if (fieldReference.variableId == variableIndexId) { - // and if the field reference was to one if its key fields (respecting the case-sensitivity requested - // by the query author) - val fieldIndex = remainingFilterKeys.indexOfFirst { fieldReference.fieldBindingName.isEquivalentTo(it) } - - if (fieldIndex >= 0) { - - // lastly, we need to ensure that operand2 doesn't reference the candidate row. operand2 executes - // a context that doesn't have access to the candidate row. - val referencesCandidateRow = object : PartiqlPhysical.VisitorFold() { - override fun visitExprLocalId( - node: PartiqlPhysical.Expr.LocalId, - accumulator: Boolean - ): Boolean = accumulator || node.index.value == variableIndexId - }.walkExpr(operand2, false) - - if (referencesCandidateRow) { - return false - } - - filterKeyValueExpressions.add(FieldEqualityPredicate(remainingFilterKeys[fieldIndex], operand2)) - remainingFilterKeys.removeAt(fieldIndex) - return true - } - } - return false - } - }.transformExpr(this) - - // The rewrite only succeeds if *all* of the key fields have been written out of the predicate. - return if (remainingFilterKeys.any()) { - null - } else { - modifiedPredicate to filterKeyValueExpressions.toList() // <-- .toList() makes the list immutable. - } -} - -private data class FieldReference( - val variableId: Long, - val referencedKey: String, - val case: PartiqlPhysical.CaseSensitivity -) { - val fieldBindingName = BindingName(this.referencedKey, this.case.toBindingCase()) -} - -/** - * If the receiver matches `(path (local_id ) (path_step (lit ) )` and if `` - * is a string, returns `FieldReference(n, field, case_sensitivity)`. Otherwise, returns null. - * - * This makes no determination if the field reference a key or not. - */ -private fun PartiqlPhysical.Expr.getKeyFieldReference(): FieldReference? { - when (this) { - is PartiqlPhysical.Expr.Path -> { - if (this.steps.size != 1) { - return null - } - val fieldStep = this.steps.single() as? PartiqlPhysical.PathStep.PathExpr ?: return null - val fieldStepIndex = fieldStep.index - return when { - fieldStepIndex is PartiqlPhysical.Expr.Lit && fieldStepIndex.value is TextElement -> - when (val root = this.root) { - is PartiqlPhysical.Expr.LocalId -> - FieldReference(root.index.value, fieldStepIndex.value.textValue, fieldStep.case) - else -> null - } - else -> null - } - } - else -> return null - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/RemoveUselessAnds.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/RemoveUselessAnds.kt deleted file mode 100644 index 1d2dedcf00..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/RemoveUselessAnds.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.partiql.lang.planner.transforms.optimizations - -import com.amazon.ionelement.api.ionBool -import org.partiql.errors.ProblemHandler -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.planner.PartiQLPhysicalPass -import org.partiql.lang.planner.transforms.isLitTrue - -/** - * Creates an instance of [PartiQLPhysicalPass] that removes useless "AND" expressions by, i.e.: - * - * - `(and (lit true) (lit true))` -> `(lit true)` - * - `(and (lit true) ))` -> `` - * - `(and (lit true)))` -> `` - * - * If more than one non-lit true remains, the `AND` expression is not useless, but the expression - * is returned with all `(lit true)` operands removed. - * - * Note that in the future, it may be possible to do this with a more general constant folding rewrite but that is - * currently out of scope. - */ -fun createRemoveUselessAndsPass(): PartiQLPhysicalPass = - RemoveUselessAndsPass() - -private class RemoveUselessAndsPass : PartiQLPhysicalPass { - override fun apply(plan: PartiqlPhysical.Plan, problemHandler: ProblemHandler): PartiqlPhysical.Plan = - object : PartiqlPhysical.VisitorTransform() { - override fun transformExprAnd(node: PartiqlPhysical.Expr.And): PartiqlPhysical.Expr { - // Recursing here transforms all child nodes, which is what we really want. - val rewritten = super.transformExprAnd(node) - if (rewritten !is PartiqlPhysical.Expr.And) { - return rewritten - } - - val nonLitTrueOperands = rewritten.operands.filter { !it.isLitTrue() } - return when (nonLitTrueOperands.size) { - // (and (lit true) (lit true)) -> (lit true) - 0 -> PartiqlPhysical.build { lit(ionBool(true)) } - // (and (lit true) )) -> - // (and (lit true))) -> - // etc. - 1 -> nonLitTrueOperands.single() - // For any other combo, simply filter out the non-(lit true) operands - else -> PartiqlPhysical.build { and(nonLitTrueOperands) } - } - } - }.transformPlan(plan) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/RemoveUselessFilters.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/RemoveUselessFilters.kt deleted file mode 100644 index 7f5bf02fc3..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/optimizations/RemoveUselessFilters.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.partiql.lang.planner.transforms.optimizations - -import org.partiql.errors.ProblemHandler -import org.partiql.lang.domains.PartiqlPhysical -import org.partiql.lang.planner.PartiQLPhysicalPass -import org.partiql.lang.planner.transforms.isLitTrue - -/** Creates a pass that removes any `(filter ...)` where the predicate is simply `(lit true)`. */ -fun createRemoveUselessFiltersPass(): PartiQLPhysicalPass = - RemoveUselessFiltersPass() - -private class RemoveUselessFiltersPass : PartiQLPhysicalPass { - override fun apply(plan: PartiqlPhysical.Plan, problemHandler: ProblemHandler): PartiqlPhysical.Plan = - object : PartiqlPhysical.VisitorTransform() { - override fun transformBexprFilter(node: PartiqlPhysical.Bexpr.Filter): PartiqlPhysical.Bexpr { - val rewritten = super.transformBexprFilter(node) as PartiqlPhysical.Bexpr.Filter - return if (node.predicate.isLitTrue()) { - rewritten.source - } else { - rewritten - } - } - }.transformPlan(plan) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/validators/PartiqlLogicalResolvedValidator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/validators/PartiqlLogicalResolvedValidator.kt deleted file mode 100644 index 2c7289c004..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/validators/PartiqlLogicalResolvedValidator.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.partiql.lang.planner.validators - -import org.partiql.errors.ErrorCode -import org.partiql.lang.domains.PartiqlLogicalResolved -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.util.propertyValueMapOf - -/** - * Provides rules for basic AST sanity checks that should be performed before any attempt at further AST processing. - * This is provided as a distinct [PartiqlLogicalResolved.Visitor] so that all other visitors may assume that the AST at least - * passed the checking performed here. - * - * Any exception thrown by this class should always be considered an indication of a bug in one of the following places: - * - [org.partiql.lang.planner.transforms.LogicalToLogicalResolvedVisitorTransform] - */ -class PartiqlLogicalResolvedValidator : PartiqlLogicalResolved.Visitor() { - /** - * Quick validation step to make sure the indexes of any variables make sense. - * It is unlikely that this check will ever fail, but if it does, it likely means there's a bug in - * [org.partiql.lang.planner.transforms.VariableIdAllocator] or that the plan was malformed by other means. - */ - override fun visitPlan(node: PartiqlLogicalResolved.Plan) { - node.locals.forEachIndexed { idx, it -> - if (it.registerIndex.value != idx.toLong()) { - throw EvaluationException( - message = "Variable index must match ordinal position of variable", - errorCode = ErrorCode.INTERNAL_ERROR, - errorContext = propertyValueMapOf(), - internal = true - ) - } - } - super.visitPlan(node) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/validators/PartiqlLogicalValidator.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/validators/PartiqlLogicalValidator.kt deleted file mode 100644 index d618fb0105..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/validators/PartiqlLogicalValidator.kt +++ /dev/null @@ -1,86 +0,0 @@ -package org.partiql.lang.planner.validators - -import com.amazon.ionelement.api.IntElement -import com.amazon.ionelement.api.IntElementSize -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.TextElement -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.passes.SemanticException -import org.partiql.lang.domains.PartiqlLogical -import org.partiql.lang.domains.addSourceLocation -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.TypedOpBehavior -import org.partiql.lang.eval.errorContextFrom -import org.partiql.lang.eval.internal.err -import org.partiql.pig.runtime.LongPrimitive - -/** - * Provides rules for basic AST sanity checks that should be performed before any attempt at further AST processing. - * This is provided as a distinct [PartiqlLogical.Visitor] so that all other visitors may assume that the AST at least - * passed the checking performed here. - * - * Any exception thrown by this class should always be considered an indication of a bug in one of the following places: - * - [org.partiql.lang.planner.transforms.AstToLogicalVisitorTransform] - */ -class PartiqlLogicalValidator(private val typedOpBehavior: TypedOpBehavior) : PartiqlLogical.Visitor() { - override fun visitExprLit(node: PartiqlLogical.Expr.Lit) { - val ionValue = node.value - val metas = node.metas - if (node.value is IntElement && ionValue.integerSize == IntElementSize.BIG_INTEGER) { - throw EvaluationException( - message = "Int overflow or underflow at compile time", - errorCode = ErrorCode.SEMANTIC_LITERAL_INT_OVERFLOW, - errorContext = errorContextFrom(metas), - internal = false - ) - } - } - - private fun validateDecimalOrNumericType(scale: LongPrimitive?, precision: LongPrimitive?, metas: MetaContainer) { - if (scale != null && precision != null && typedOpBehavior == TypedOpBehavior.HONOR_PARAMETERS) { - if (scale.value !in 0..precision.value) { - err( - "Scale ${scale.value} should be between 0 and precision ${precision.value}", - errorCode = ErrorCode.SEMANTIC_INVALID_DECIMAL_ARGUMENTS, - errorContext = errorContextFrom(metas), - internal = false - ) - } - } - } - - override fun visitTypeDecimalType(node: PartiqlLogical.Type.DecimalType) { - validateDecimalOrNumericType(node.scale, node.precision, node.metas) - } - - override fun visitTypeNumericType(node: PartiqlLogical.Type.NumericType) { - validateDecimalOrNumericType(node.scale, node.precision, node.metas) - } - - override fun visitExprStruct(node: PartiqlLogical.Expr.Struct) { - node.parts.forEach { part -> - when (part) { - is PartiqlLogical.StructPart.StructField -> { - if (part.fieldName is PartiqlLogical.Expr.Missing || - (part.fieldName is PartiqlLogical.Expr.Lit && (part.fieldName as PartiqlLogical.Expr.Lit).value !is TextElement) - ) { - val type = when (val fieldName = part.fieldName) { - is PartiqlLogical.Expr.Lit -> fieldName.value.type.toString() - else -> "MISSING" - } - throw SemanticException( - "Found struct part to be of type $type", - ErrorCode.SEMANTIC_NON_TEXT_STRUCT_FIELD_KEY, - PropertyValueMap().addSourceLocation(part.fieldName.metas).also { pvm -> - pvm[Property.ACTUAL_TYPE] = type - } - ) - } - } - is PartiqlLogical.StructPart.StructFields -> { /* intentionally empty */ } - } - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinter.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinter.kt deleted file mode 100644 index cb72c19339..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinter.kt +++ /dev/null @@ -1,773 +0,0 @@ -package org.partiql.lang.prettyprint - -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.syntax.PartiQLParserBuilder -import org.partiql.pig.runtime.SymbolPrimitive - -/** - * This class is used to pretty print PIG AST. - */ -class ASTPrettyPrinter { - /** - * For the given SQL query outputs the corresponding string formatted PartiQL AST representation, e.g: - * Given: - * "SELECT * FROM 1 WHERE a = b GROUP BY c HAVING d = '123' LIMIT 3 OFFSET 4" - * Outputs: - """ - Select - project: * - from: Scan - Lit 1 - where: = - Id a (case_insensitive) (unqualified) - Id b (case_insensitive) (unqualified) - group: Group - strategy: GroupFull - keyList: GroupKeyList - key1: GroupKey - expr: Id c (case_insensitive) (unqualified) - having: = - Id d (case_insensitive) (unqualified) - Lit "123" - limit: Lit 3 - offset: Lit 4 - """ - * @param query An SQL query as string. - * @return formatted string corresponding to the input AST. - */ - fun prettyPrintAST(query: String): String { - val ast = PartiQLParserBuilder().build().parseAstStatement(query) - return prettyPrintAST(ast) - } - - /** - * For the given PartiQL AST Statement, outputs a string formatted query representing the given AST, e.g: - * @param [PartiqlAst.Statement] An SQL query as string. - * @return formatted string corresponding to the input AST. - */ - fun prettyPrintAST(ast: PartiqlAst.Statement): String { - val recursionTree = when (ast) { - is PartiqlAst.Statement.Query -> toRecursionTree(ast.expr) - is PartiqlAst.Statement.Dml -> toRecursionTree(ast) - is PartiqlAst.Statement.Ddl -> toRecursionTree(ast) - is PartiqlAst.Statement.Exec -> toRecursionTree(ast) - is PartiqlAst.Statement.Explain -> toRecursionTree(ast) - } - - return recursionTree.convertToString() - } - - // ******** - // * EXEC * - // ******** - private fun toRecursionTree(node: PartiqlAst.Statement.Exec): RecursionTree = - RecursionTree( - astType = "Exec", - children = listOf( - toRecursionTree(node.procedureName, "procedureName") - ) + node.args.mapIndexed { index, expr -> toRecursionTree(expr, "arg${index + 1}") } - ) - - // ******** - // * EXPLAIN * - // ******** - private fun toRecursionTree(node: PartiqlAst.Statement.Explain): RecursionTree = when (val target = node.target) { - is PartiqlAst.ExplainTarget.Domain -> toRecursionTree(target) - } - - private fun toRecursionTree(node: PartiqlAst.ExplainTarget.Domain): RecursionTree = when (val stmt = node.statement) { - is PartiqlAst.Statement.Query -> toRecursionTree(stmt.expr) - is PartiqlAst.Statement.Dml -> toRecursionTree(stmt) - is PartiqlAst.Statement.Ddl -> toRecursionTree(stmt) - is PartiqlAst.Statement.Exec -> toRecursionTree(stmt) - is PartiqlAst.Statement.Explain -> toRecursionTree(stmt) - } - - // ******* - // * DDL * - // ******* - private fun toRecursionTree(node: PartiqlAst.Statement.Ddl): RecursionTree = - RecursionTree( - astType = "Ddl", - children = listOf( - toRecursionTree(node.op, "op") - ) - ) - - private fun toRecursionTree(node: PartiqlAst.DdlOp, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.DdlOp.CreateIndex -> RecursionTree( - astType = "CreateIndex", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.indexName, "indexName") - ) + node.fields.mapIndexed { index, expr -> toRecursionTree(expr, "field${index + 1}") } - ) - is PartiqlAst.DdlOp.CreateTable -> RecursionTree( - astType = "CreateTable", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.tableName, "tableName") - ) - ) - is PartiqlAst.DdlOp.DropIndex -> RecursionTree( - astType = "DropIndex", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.table, "table"), - toRecursionTree(node.keys, "keys") - ) - ) - is PartiqlAst.DdlOp.DropTable -> RecursionTree( - astType = "DropTable", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.tableName, "tableName") - ) - ) - } - - private fun toRecursionTree(node: PartiqlAst.Identifier, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "Identifier", - value = node.name.text + " " + node.case.toString(), - attrOfParent = attrOfParent - ) - - // ******* - // * DML * - // ******* - private fun toRecursionTree(node: PartiqlAst.Statement.Dml): RecursionTree = - RecursionTree( - astType = "Dml", - children = listOf( - toRecursionTree(node.operations, "operations") - ).let { - if (node.from == null) it else (it.plusElement(toRecursionTree(node.from!!, "from"))) - }.let { - if (node.where == null) it else (it.plusElement(toRecursionTree(node.where!!, "where"))) - }.let { - if (node.returning == null) it else (it.plusElement(toRecursionTree(node.returning!!, "returning"))) - } - ) - - private fun toRecursionTree(node: PartiqlAst.DmlOpList, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "DmlOpList", - attrOfParent = attrOfParent, - children = node.ops.mapIndexed { index, dmlOp -> toRecursionTree(dmlOp, "op${index + 1}") } - ) - - private fun toRecursionTree(node: PartiqlAst.DmlOp, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.DmlOp.Delete -> RecursionTree( - astType = "Delete", - attrOfParent = attrOfParent - ) - is PartiqlAst.DmlOp.Insert -> RecursionTree( - astType = "Insert", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.target, "target"), - toRecursionTree(node.values, "values") - ) - ) - is PartiqlAst.DmlOp.InsertValue -> RecursionTree( - astType = "InsertValue", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.target, "target"), - toRecursionTree(node.value, "value") - ).let { - if (node.index == null) it else (it.plusElement(toRecursionTree(node.index!!, "index"))) - }.let { - if (node.onConflict == null) it else (it.plusElement(toRecursionTree(node.onConflict!!, "onConflict"))) - } - ) - is PartiqlAst.DmlOp.Remove -> RecursionTree( - astType = "Remove", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.target, "target") - ) - ) - is PartiqlAst.DmlOp.Set -> RecursionTree( - astType = "Set", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.assignment, "assignment") - ) - ) - } - - private fun toRecursionTree(node: PartiqlAst.Assignment, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "Assignment", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.target, "target"), - toRecursionTree(node.value, "value") - ) - ) - - private fun toRecursionTree(node: PartiqlAst.OnConflict, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "OnConflict", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.expr, "expr"), - toRecursionTree(node.conflictAction, "conflictAction") - ) - ) - - private fun toRecursionTree(node: PartiqlAst.ConflictAction, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.ConflictAction.DoNothing -> RecursionTree( - astType = "DoNothing", - attrOfParent = attrOfParent - ) - is PartiqlAst.ConflictAction.DoReplace -> TODO("PrettyPrinter doesn't support DO REPLACE yet.") - is PartiqlAst.ConflictAction.DoUpdate -> TODO("PrettyPrinter doesn't support DO UPDATE yet.") - } - - private fun toRecursionTree(node: PartiqlAst.ReturningExpr, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "ReturningExpr", - attrOfParent = attrOfParent, - children = node.elems.mapIndexed { index, returningElem -> toRecursionTree(returningElem, "elem${index + 1}") } - ) - - private fun toRecursionTree(node: PartiqlAst.ReturningElem, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "ReturningElem", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.mapping, "mapping"), - toRecursionTree(node.column, "column") - ) - ) - - private fun toRecursionTree(node: PartiqlAst.ReturningMapping, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.ReturningMapping.AllNew -> RecursionTree( - astType = "AllNew", - attrOfParent = attrOfParent - ) - is PartiqlAst.ReturningMapping.AllOld -> RecursionTree( - astType = "AllOld", - attrOfParent = attrOfParent - ) - is PartiqlAst.ReturningMapping.ModifiedNew -> RecursionTree( - astType = "ModifiedNew", - attrOfParent = attrOfParent - ) - is PartiqlAst.ReturningMapping.ModifiedOld -> RecursionTree( - astType = "ModifiedOld", - attrOfParent = attrOfParent - ) - } - - private fun toRecursionTree(node: PartiqlAst.ColumnComponent, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.ColumnComponent.ReturningColumn -> RecursionTree( - astType = "ReturningColumn", - attrOfParent = attrOfParent - ) - is PartiqlAst.ColumnComponent.ReturningWildcard -> RecursionTree( - astType = "ReturningWildcard", - attrOfParent = attrOfParent - ) - } - - // ********* - // * Query * - // ********* - private fun toRecursionTree(node: PartiqlAst.Expr, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.Expr.Id -> RecursionTree( - astType = "Id", - value = node.name.text + " " + node.case.toString() + " " + node.qualifier.toString(), - attrOfParent = attrOfParent - ) - is PartiqlAst.Expr.Missing -> RecursionTree( - astType = "missing", - attrOfParent = attrOfParent - ) - is PartiqlAst.Expr.Lit -> RecursionTree( - astType = "Lit", - value = node.value.toString(), - attrOfParent = attrOfParent - ) - is PartiqlAst.Expr.Parameter -> RecursionTree( - astType = "Parameter", - value = node.index.value.toString(), - attrOfParent = attrOfParent - ) - is PartiqlAst.Expr.Date -> RecursionTree( - astType = "Date", - value = node.year.value.toString() + "-" + node.month.value.toString() + "-" + node.day.value.toString(), - attrOfParent = attrOfParent - ) - is PartiqlAst.Expr.LitTime -> RecursionTree( - astType = "LitTime", - value = node.value.hour.value.toString() + - ":" + node.value.minute.value.toString() + - ":" + node.value.second.toString() + - "." + node.value.nano.toString() + - ", 'precision': " + node.value.precision.value.toString() + - ", 'timeZone': " + node.value.withTimeZone.toString() + - ", 'tzminute': " + node.value.tzMinutes.toString(), - attrOfParent = attrOfParent, - ) - is PartiqlAst.Expr.Not -> RecursionTree( - astType = "Not", - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.expr)) - ) - is PartiqlAst.Expr.Pos -> RecursionTree( - astType = "+", - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.expr)) - ) - is PartiqlAst.Expr.Neg -> RecursionTree( - astType = "-", - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.expr)) - ) - is PartiqlAst.Expr.Plus -> RecursionTree( - astType = "+", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Minus -> RecursionTree( - astType = "-", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Times -> RecursionTree( - astType = "*", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Divide -> RecursionTree( - astType = "/", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Modulo -> RecursionTree( - astType = "%", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Concat -> RecursionTree( - astType = "||", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.BitwiseAnd -> RecursionTree( - astType = "&", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.And -> RecursionTree( - astType = "And", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Or -> RecursionTree( - astType = "Or", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Eq -> RecursionTree( - astType = "=", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Ne -> RecursionTree( - astType = "!=", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Gt -> RecursionTree( - astType = ">", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Gte -> RecursionTree( - astType = ">=", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Lt -> RecursionTree( - astType = "<", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Lte -> RecursionTree( - astType = "<=", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.InCollection -> RecursionTree( - astType = "In", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.BagOp -> RecursionTree( - astType = node.op.javaClass.simpleName.capitalize(), - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.operands) - ) - is PartiqlAst.Expr.Like -> RecursionTree( - astType = "Like", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.value, "value"), - toRecursionTree(node.pattern, "pattern") - ).let { - if (node.escape == null) it else (it + listOf(toRecursionTree(node.escape!!, "escape"))) - } - ) - is PartiqlAst.Expr.Between -> RecursionTree( - astType = "Between", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.value, "value"), - toRecursionTree(node.from, "from"), - toRecursionTree(node.to, "to") - ) - ) - is PartiqlAst.Expr.SimpleCase -> RecursionTree( - astType = "SimpleCase", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.expr, "expr"), - toRecursionTree(node.cases, "cases") - ).let { - if (node.default == null) it else (it.plusElement(toRecursionTree(node.default!!, "default"))) - } - ) - is PartiqlAst.Expr.SearchedCase -> RecursionTree( - astType = "SearchedCase", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.cases, "cases") - ).let { - if (node.default == null) it else (it.plusElement(toRecursionTree(node.default!!, "default"))) - } - ) - is PartiqlAst.Expr.Struct -> RecursionTree( - astType = "Struct", - attrOfParent = attrOfParent, - children = node.fields.mapIndexed { index, exprPair -> toRecursionTree(exprPair, "field${index + 1}") } - ) - is PartiqlAst.Expr.Bag -> RecursionTree( - astType = "Bag", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.values) - ) - is PartiqlAst.Expr.List -> RecursionTree( - astType = "List", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.values) - ) - is PartiqlAst.Expr.Sexp -> RecursionTree( - astType = "Sexp", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.values) - ) - is PartiqlAst.Expr.Path -> RecursionTree( - astType = "Path", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.root, "root") - ) + node.steps.mapIndexed { index, pathStep -> toRecursionTree(pathStep, "step${index + 1}") } - ) - is PartiqlAst.Expr.Call -> RecursionTree( - astType = "Call", - value = node.funcName.text, - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.args, "arg") - ) - is PartiqlAst.Expr.CallAgg -> RecursionTree( - astType = "CallAgg", - value = node.funcName.text, - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.arg, "arg")) - ) - is PartiqlAst.Expr.IsType -> RecursionTree( - astType = "Is", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.value, "value"), - RecursionTree( - astType = node.type.toString(), - attrOfParent = "type" - ) - ) - ) - is PartiqlAst.Expr.Cast -> RecursionTree( - astType = "Cast", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.value, "value"), - RecursionTree( - astType = node.asType.toString(), - attrOfParent = "asType" - ) - ) - ) - is PartiqlAst.Expr.CanCast -> RecursionTree( - astType = "CanCast", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.value, "value"), - RecursionTree( - astType = node.asType.toString(), - attrOfParent = "asType" - ) - ) - ) - is PartiqlAst.Expr.CanLosslessCast -> RecursionTree( - astType = "CanLosslessCast", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.value, "value"), - RecursionTree( - astType = node.asType.toString(), - attrOfParent = "asType" - ) - ) - ) - is PartiqlAst.Expr.NullIf -> RecursionTree( - astType = "NullIf", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.expr1, "expr1"), - toRecursionTree(node.expr2, "expr2") - ) - ) - is PartiqlAst.Expr.Coalesce -> RecursionTree( - astType = "Coalesce", - attrOfParent = attrOfParent, - children = toRecursionTreeList(node.args, "arg") - ) - is PartiqlAst.Expr.Select -> RecursionTree( - astType = "Select", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.project, "project"), - toRecursionTree(node.from, "from") - ).let { - if (node.fromLet == null) it else (it.plusElement(toRecursionTree(node.fromLet!!, "let"))) - }.let { - if (node.where == null) it else (it.plusElement(toRecursionTree(node.where!!, "where"))) - }.let { - if (node.group == null) it else (it.plusElement(toRecursionTree(node.group!!, "group"))) - }.let { - if (node.having == null) it else (it.plusElement(toRecursionTree(node.having!!, "having"))) - }.let { - if (node.order == null) it else (it.plusElement(toRecursionTree(node.order!!, "order"))) - }.let { - if (node.limit == null) it else (it.plusElement(toRecursionTree(node.limit!!, "limit"))) - }.let { - if (node.offset == null) it else (it.plusElement(toRecursionTree(node.offset!!, "offset"))) - } - ) - is PartiqlAst.Expr.SessionAttribute -> RecursionTree( - astType = "SessionAttribute", - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.value, "value")) - ) - is PartiqlAst.Expr.GraphMatch -> TODO("Unsupported GraphMatch AST node") - is PartiqlAst.Expr.CallWindow -> TODO("PrettyPrinter doesn't support Window Function yet.") - is PartiqlAst.Expr.Timestamp -> TODO() - } - - private fun toRecursionTreeList(nodes: List, attrOfParent: String? = null): List = - nodes.map { toRecursionTree(it, attrOfParent) } - - private fun toRecursionTree(node: PartiqlAst.ExprPair, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "Pair", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.first, "first"), - toRecursionTree(node.second, "second") - ) - ) - - private fun toRecursionTree(node: PartiqlAst.ExprPairList, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "ExprPairList", - attrOfParent = attrOfParent, - children = node.pairs.mapIndexed { index, exprPair -> toRecursionTree(exprPair, "pair${index + 1}") } - ) - - private fun toRecursionTree(node: PartiqlAst.PathStep, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.PathStep.PathExpr -> toRecursionTree(node.index, attrOfParent) - is PartiqlAst.PathStep.PathWildcard -> RecursionTree( - astType = "[*]", - attrOfParent = attrOfParent - ) - is PartiqlAst.PathStep.PathUnpivot -> RecursionTree( - astType = "*", - attrOfParent = attrOfParent - ) - } - - private fun toRecursionTree(node: PartiqlAst.Projection, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.Projection.ProjectStar -> RecursionTree( - astType = "*", - attrOfParent = attrOfParent - ) - is PartiqlAst.Projection.ProjectValue -> RecursionTree( - astType = "ProjectValue", - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.value, "value")) - ) - is PartiqlAst.Projection.ProjectList -> RecursionTree( - astType = "ProjectList", - attrOfParent = attrOfParent, - children = node.projectItems.mapIndexed { index, projectItem -> toRecursionTree(projectItem, "projectItem${index + 1}") } - ) - is PartiqlAst.Projection.ProjectPivot -> RecursionTree( - astType = "ProjectPivot", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.value, "value"), - toRecursionTree(node.key, "key") - ) - ) - } - - private fun toRecursionTree(node: PartiqlAst.ProjectItem, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.ProjectItem.ProjectAll -> RecursionTree( - astType = "ProjectAll", - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.expr, "expr")) - ) - is PartiqlAst.ProjectItem.ProjectExpr -> RecursionTree( - astType = "ProjectExpr", - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.expr, "expr")).let { - if (node.asAlias == null) it else { it.plusElement(toRecursionTree(node.asAlias!!, "as")) } - } - ) - } - - private fun toRecursionTree(node: PartiqlAst.FromSource, attrOfParent: String? = null): RecursionTree = - when (node) { - is PartiqlAst.FromSource.Join -> RecursionTree( - astType = node.type.toString(), - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.left, "left"), - toRecursionTree(node.right, "right") - ).let { - if (node.predicate == null) it else { it.plusElement(toRecursionTree(node.predicate!!, "on")) } - } - ) - is PartiqlAst.FromSource.Scan -> RecursionTree( - astType = "Scan", - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.expr)).let { - if (node.asAlias == null) it else { it.plusElement(toRecursionTree(node.asAlias!!, attrOfParent = "as")) } - }.let { - if (node.atAlias == null) it else { it.plusElement(toRecursionTree(node.atAlias!!, attrOfParent = "at")) } - }.let { - if (node.byAlias == null) it else { it.plusElement(toRecursionTree(node.byAlias!!, attrOfParent = "by")) } - } - ) - is PartiqlAst.FromSource.Unpivot -> RecursionTree( - astType = "Unpivot", - attrOfParent = attrOfParent, - children = listOf(toRecursionTree(node.expr)).let { - if (node.asAlias == null) it else { it.plusElement(toRecursionTree(node.asAlias!!, attrOfParent = "as")) } - }.let { - if (node.atAlias == null) it else { it.plusElement(toRecursionTree(node.atAlias!!, attrOfParent = "at")) } - }.let { - if (node.byAlias == null) it else { it.plusElement(toRecursionTree(node.byAlias!!, attrOfParent = "by")) } - } - ) - } - - private fun toRecursionTree(node: PartiqlAst.Let, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "Let", - attrOfParent = attrOfParent, - children = node.letBindings.mapIndexed { index, letBinding -> toRecursionTree(letBinding, "letBinding${index + 1}") } - ) - - private fun toRecursionTree(node: PartiqlAst.LetBinding, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "LetBinding", - attrOfParent = attrOfParent, - children = listOf( - toRecursionTree(node.expr, "expr"), - toRecursionTree(node.name, "name") - ) - ) - - private fun toRecursionTree(node: PartiqlAst.GroupBy, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "Group", - attrOfParent = attrOfParent, - children = listOf( - RecursionTree( - astType = when (node.strategy) { - is PartiqlAst.GroupingStrategy.GroupFull -> "GroupFull" - is PartiqlAst.GroupingStrategy.GroupPartial -> "GroupPartial" - }, - attrOfParent = "strategy" - ), - RecursionTree( - astType = "GroupKeyList", - attrOfParent = "keyList", - children = node.keyList.keys.mapIndexed { index, groupKey -> - RecursionTree( - astType = "GroupKey", - attrOfParent = "key${index + 1}", - children = listOf( - toRecursionTree(groupKey.expr, "expr") - ).let { - if (groupKey.asAlias == null) it else { it.plusElement(toRecursionTree(groupKey.asAlias!!, "as")) } - } - ) - } - ) - ).let { - if (node.groupAsAlias == null) it else { it.plusElement(toRecursionTree(node.groupAsAlias!!, attrOfParent = "groupAs")) } - } - ) - - private fun toRecursionTree(node: PartiqlAst.OrderBy, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "Order", - attrOfParent = attrOfParent, - children = node.sortSpecs.mapIndexed { index, sortSpec -> - RecursionTree( - astType = "SortSpec", - attrOfParent = "sortSpec${index + 1}", - children = listOf( - toRecursionTree(sortSpec.expr, "expr"), - RecursionTree( - astType = sortSpec.orderingSpec.toString(), - attrOfParent = "orderingSpec" - ) - ) - ) - } - ) - - private fun toRecursionTree(symbol: SymbolPrimitive, attrOfParent: String? = null): RecursionTree = - RecursionTree( - astType = "Symbol", - value = symbol.text, - attrOfParent = attrOfParent - ) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt deleted file mode 100644 index 4e40444a47..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt +++ /dev/null @@ -1,963 +0,0 @@ -package org.partiql.lang.prettyprint - -import org.partiql.lang.ast.IsListParenthesizedMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.syntax.PartiQLParserBuilder -import org.partiql.pig.runtime.toIonElement -import java.time.LocalDate -import java.time.LocalTime -import kotlin.math.abs - -/** - * This class is used to pretty print a query, which first transforms a query into a parsed tree, - * and then transform it back to a pretty string. - * - * The idea is to use a StringBuilder and write a pretty-printed query to it according to the like - * of the parsed tree - */ -class QueryPrettyPrinter { - private val sqlParser = PartiQLParserBuilder.standard().build() - - /** - * For the given SQL query outputs the corresponding string formatted PartiQL AST representation, e.g: - * Given: - * "FROM x WHERE a = b SET k = 5, m = 6 INSERT INTO c VALUE << 1 >> REMOVE a SET l = 3 REMOVE b RETURNING MODIFIED OLD a, ALL NEW *" - * Outputs: - * """ - * FROM x - * WHERE a = b - * SET k = 5, m = 6 - * INSERT INTO c VALUE << 1 >> - * REMOVE a - * SET l = 3 - * REMOVE b - * RETURNING MODIFIED OLD a, ALL NEW * - * """ - * @param query An SQL query as string. - * @return formatted SQL query - */ - fun prettyPrintQuery(query: String): String = - astToPrettyQuery(sqlParser.parseAstStatement(query)) - - /** - * For the given PartiQL AST Statement, outputs a string formatted query corresponding the given AST, e.g: - * @param [PartiqlAst.Statement] An SQL query as string. - * @return formatted SQL query - */ - fun astToPrettyQuery(ast: PartiqlAst.Statement): String { - val sb = StringBuilder() - writeAstNode(ast, sb) - if (sb.lastOrNull() == '\n') { - sb.removeLast(1) - } - - return sb.toString() - } - - private fun writeAstNode(node: PartiqlAst.Statement, sb: StringBuilder) { - when (node) { - is PartiqlAst.Statement.Query -> writeAstNode(node.expr, sb, 0) - is PartiqlAst.Statement.Ddl -> writeAstNode(node, sb) - is PartiqlAst.Statement.Dml -> writeAstNode(node, sb) - is PartiqlAst.Statement.Exec -> writeAstNode(node, sb) - } - } - - // ******** - // * Exec * - // ******** - private fun writeAstNode(node: PartiqlAst.Statement.Exec, sb: StringBuilder) { - sb.append("EXEC ${node.procedureName.text} ") - node.args.forEach { - // Print anything as one line inside EXEC clause - writeAstNodeCheckSubQuery(it, sb, -1) - sb.append(", ") - } - if (node.args.isNotEmpty()) { - sb.removeLast(2) - } - } - - // ******* - // * Ddl * - // ******* - private fun writeAstNode(node: PartiqlAst.Statement.Ddl, sb: StringBuilder) { - when (val op = node.op) { - is PartiqlAst.DdlOp.CreateTable -> writeAstNode(op, sb) - is PartiqlAst.DdlOp.DropTable -> { - sb.append("DROP TABLE ") - writeAstNode(op.tableName, sb) - } - is PartiqlAst.DdlOp.CreateIndex -> { - sb.append("CREATE INDEX ON ") - writeAstNode(op.indexName, sb) - sb.append(" (") - op.fields.forEach { - // Assume fields in CREATE INDEX clause are not SELECT or CASE - writeAstNode(it, sb, 0) - sb.append(", ") - } - sb.removeLast(2).append(')') - } - is PartiqlAst.DdlOp.DropIndex -> { - sb.append("DROP INDEX ") - writeAstNode(op.keys, sb) - sb.append(" ON ") - writeAstNode(op.table, sb) - } - } - } - - private fun writeAstNode(node: PartiqlAst.Identifier, sb: StringBuilder) { - when (node.case) { - is PartiqlAst.CaseSensitivity.CaseSensitive -> sb.append("\"${node.name.text}\"") - is PartiqlAst.CaseSensitivity.CaseInsensitive -> sb.append(node.name.text) - } - } - - private fun writeAstNode(node: PartiqlAst.DdlOp.CreateTable, sb: StringBuilder) { - sb.append("CREATE TABLE ${node.tableName.text}") - node.def?.let { - var separator = "\n\t" - sb.append(" (") - for (n in it.parts) { - sb.append(separator) - when (n) { - is PartiqlAst.TableDefPart.ColumnDeclaration -> writeAstNode(n, sb) - } - separator = ",\n\t" - } - sb.append("\n)") - } - } - - private fun writeAstNode(node: PartiqlAst.TableDefPart.ColumnDeclaration, sb: StringBuilder) { - sb.append("${node.name.text} ") - writeType(node.type, sb) - for (c in node.constraints) { - sb.append(" ") - c.name?.let { sb.append("CONSTRAINT ${it.text} ") } - when (c.def) { - is PartiqlAst.ColumnConstraintDef.ColumnNull -> sb.append("NULL") - is PartiqlAst.ColumnConstraintDef.ColumnNotnull -> sb.append("NOT NULL") - } - } - } - - // ******* - // * Dml * - // ******* - private fun writeAstNode(node: PartiqlAst.Statement.Dml, sb: StringBuilder) { - if (node.operations.ops.first() is PartiqlAst.DmlOp.Delete) { - sb.append("DELETE FROM ") - writeFromSource(node.from!!, sb, 0) - node.where?.let { - sb.append("\nWHERE ") - writeAstNodeCheckSubQuery(it, sb, 0) - } - node.returning?.let { writeReturning(it, sb) } - return - } - - node.from?.let { - sb.append("FROM ") - writeFromSource(it, sb, 0) - } - - node.where?.let { - sb.append("\nWHERE ") - writeAstNodeCheckSubQuery(it, sb, 0) - } - - var previousIsSet = false // Consecutive SET nodes should be transformed into one SET clause - node.operations.ops.forEach { - if (sb.isNotEmpty()) { // If there is no FROM WHERE clause before, we don't need to add a line break - sb.append('\n') - } - previousIsSet = writeDmlOp(it, sb, previousIsSet) - } - - node.returning?.let { writeReturning(it, sb) } - } - - private fun writeDmlOp(dmlOp: PartiqlAst.DmlOp, sb: StringBuilder, previousIsSet: Boolean): Boolean { - when (dmlOp) { - is PartiqlAst.DmlOp.Insert -> { - sb.append("INSERT INTO ") - writeAstNodeCheckSubQuery(dmlOp.target, sb, 0) - sb.append(" VALUES ") - val bag = dmlOp.values as PartiqlAst.Expr.Bag - bag.values.forEach { - val list = it as PartiqlAst.Expr.List - sb.append('(') - list.values.forEach { value -> - writeAstNodeCheckSubQuery(value, sb, 0) - sb.append(", ") - } - sb.removeLast(2) - sb.append("), ") - } - sb.removeLast(2) - } - is PartiqlAst.DmlOp.InsertValue -> { - sb.append("INSERT INTO ") - writeAstNodeCheckSubQuery(dmlOp.target, sb, 0) - sb.append(" VALUE ") - writeAstNodeCheckSubQuery(dmlOp.value, sb, 0) - dmlOp.index?.let { - sb.append(" AT ") - writeAstNodeCheckSubQuery(it, sb, 0) - } - dmlOp.onConflict?.let { - sb.append(" ON CONFLICT WHERE ") - writeAstNodeCheckSubQuery(it.expr, sb, 0) - when (it.conflictAction) { - is PartiqlAst.ConflictAction.DoNothing -> { - sb.append(" DO NOTHING") - } - is PartiqlAst.ConflictAction.DoReplace -> - TODO("PrettyPrinter doesn't support DO REPLACE yet.") - is PartiqlAst.ConflictAction.DoUpdate -> - TODO("PrettyPrinter doesn't support DO UPDATE yet.") - } - } - } - is PartiqlAst.DmlOp.Remove -> { - sb.append("REMOVE ") - writeAstNodeCheckSubQuery(dmlOp.target, sb, 0) - } - is PartiqlAst.DmlOp.Set -> { - when (previousIsSet) { - true -> { - sb.removeLast(1) // Remove the last line breaker - sb.append(", ") - } - false -> sb.append("SET ") - } - writeAstNodeCheckSubQuery(dmlOp.assignment.target, sb, 0) - sb.append(" = ") - writeAstNodeCheckSubQuery(dmlOp.assignment.value, sb, 0) - } - is PartiqlAst.DmlOp.Delete -> error("DELETE clause has different syntax") - } - - return dmlOp is PartiqlAst.DmlOp.Set - } - - private fun writeReturning(returning: PartiqlAst.ReturningExpr, sb: StringBuilder) { - sb.append("\nRETURNING ") - returning.elems.forEach { - when (it.mapping) { - is PartiqlAst.ReturningMapping.ModifiedNew -> sb.append("MODIFIED NEW ") - is PartiqlAst.ReturningMapping.ModifiedOld -> sb.append("MODIFIED OLD ") - is PartiqlAst.ReturningMapping.AllNew -> sb.append("ALL NEW ") - is PartiqlAst.ReturningMapping.AllOld -> sb.append("ALL OLD ") - } - when (val column = it.column) { - is PartiqlAst.ColumnComponent.ReturningWildcard -> sb.append('*') - is PartiqlAst.ColumnComponent.ReturningColumn -> writeAstNode(column.expr, sb, 0) - } - sb.append(", ") - } - sb.removeLast(2) - } - - // ********* - // * Query * - // ********* - /** - * @param node is the PIG AST node - * @param sb is the StringBuilder where we write the pretty query according to the like of the parsed tree - * @param level is an integer which marks how deep in the nested query we are. It increments Only when we step - * into a Case or Select clause. -1 represents no formatting, which transforms the sub-query as a line string - */ - private fun writeAstNode(node: PartiqlAst.Expr, sb: StringBuilder, level: Int) { - return when (node) { - is PartiqlAst.Expr.Missing -> writeAstNode(node, sb) - is PartiqlAst.Expr.Lit -> writeAstNode(node, sb) - is PartiqlAst.Expr.LitTime -> writeAstNode(node, sb) - is PartiqlAst.Expr.Date -> writeAstNode(node, sb) - is PartiqlAst.Expr.Id -> writeAstNode(node, sb) - is PartiqlAst.Expr.Bag -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Sexp -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Struct -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.List -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Parameter -> writeAstNode(node, sb) - is PartiqlAst.Expr.Path -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Call -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.CallAgg -> writeAstNode(node, sb, level) - - is PartiqlAst.Expr.SimpleCase -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.SearchedCase -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Select -> writeAstNode(node, sb, level) - - is PartiqlAst.Expr.Pos -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Neg -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Not -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Between -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Like -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.IsType -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Cast -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.CanCast -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.CanLosslessCast -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.Coalesce -> writeAstNode(node, sb, level) - is PartiqlAst.Expr.NullIf -> writeAstNode(node, sb, level) - - is PartiqlAst.Expr.Concat -> writeNAryOperator("||", node.operands, sb, level) - is PartiqlAst.Expr.Plus -> writeNAryOperator("+", node.operands, sb, level) - is PartiqlAst.Expr.Minus -> writeNAryOperator("-", node.operands, sb, level) - is PartiqlAst.Expr.Times -> writeNAryOperator("*", node.operands, sb, level) - is PartiqlAst.Expr.Divide -> writeNAryOperator("/", node.operands, sb, level) - is PartiqlAst.Expr.Modulo -> writeNAryOperator("%", node.operands, sb, level) - is PartiqlAst.Expr.BitwiseAnd -> writeNAryOperator("&", node.operands, sb, level) - is PartiqlAst.Expr.Eq -> writeNAryOperator("=", node.operands, sb, level) - is PartiqlAst.Expr.Ne -> writeNAryOperator("!=", node.operands, sb, level) - is PartiqlAst.Expr.Gt -> writeNAryOperator(">", node.operands, sb, level) - is PartiqlAst.Expr.Gte -> writeNAryOperator(">=", node.operands, sb, level) - is PartiqlAst.Expr.Lt -> writeNAryOperator("<", node.operands, sb, level) - is PartiqlAst.Expr.Lte -> writeNAryOperator("<=", node.operands, sb, level) - is PartiqlAst.Expr.And -> writeNAryOperator("AND", node.operands, sb, level) - is PartiqlAst.Expr.Or -> writeNAryOperator("OR", node.operands, sb, level) - is PartiqlAst.Expr.InCollection -> writeNAryOperator("IN", node.operands, sb, level) - is PartiqlAst.Expr.BagOp -> { - var name = when (node.op) { - is PartiqlAst.BagOpType.Except -> "EXCEPT" - is PartiqlAst.BagOpType.Intersect -> "INTERSECT" - is PartiqlAst.BagOpType.Union -> "UNION" - is PartiqlAst.BagOpType.OuterExcept -> "OUTER EXCEPT" - is PartiqlAst.BagOpType.OuterIntersect -> "OUTER INTERSECT" - is PartiqlAst.BagOpType.OuterUnion -> "OUTER UNION" - } - if (node.quantifier is PartiqlAst.SetQuantifier.All) { - name += " ALL" - } - writeNAryOperator(name, node.operands, sb, level) - } - is PartiqlAst.Expr.CallWindow -> TODO() - is PartiqlAst.Expr.GraphMatch -> TODO() - is PartiqlAst.Expr.SessionAttribute -> writeSessionAttribute(node, sb) - is PartiqlAst.Expr.Timestamp -> TODO() - } - } - - private fun writeSessionAttribute(node: PartiqlAst.Expr.SessionAttribute, sb: StringBuilder) { - sb.append(node.value.text.uppercase()) - } - - /** - * If the node indicates a sub-query, we surround it with parenthesis and start a new line for it. - */ - private fun writeAstNodeCheckSubQuery(node: PartiqlAst.Expr, sb: StringBuilder, level: Int) { - when (isCaseOrSelect(node)) { - true -> { - val subQueryLevel = getSubQueryLevel(level) - val separator = when (subQueryLevel == -1) { - true -> "" - false -> getSeparator(subQueryLevel) - } - sb.append("($separator") - writeAstNode(node, sb, subQueryLevel) - sb.append(')') - } - false -> writeAstNode(node, sb, level) - } - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.Missing, sb: StringBuilder) { - sb.append("MISSING") - } - - private fun writeAstNode(node: PartiqlAst.Expr.Lit, sb: StringBuilder) { - // Not sure if there is a better way to transform IonElement into a PartiQL value as string - val value = when (node.value.type) { - com.amazon.ionelement.api.ElementType.NULL -> "NULL" - com.amazon.ionelement.api.ElementType.BOOL -> node.value.booleanValue.toString().toUpperCase() - com.amazon.ionelement.api.ElementType.INT -> node.value.longValue.toString() - com.amazon.ionelement.api.ElementType.DECIMAL -> node.value.decimalValue.toString() - com.amazon.ionelement.api.ElementType.FLOAT -> node.value.doubleValue.toString() - com.amazon.ionelement.api.ElementType.STRING -> "'${node.value.stringValue}'" - else -> "`${node.value.toIonElement()}`" - } - - sb.append(value) - } - - private fun writeAstNode(node: PartiqlAst.Expr.Date, sb: StringBuilder) { - val date = LocalDate.of(node.year.value.toInt(), node.month.value.toInt(), node.day.value.toInt()) - sb.append("DATE '$date'") - } - - private fun writeAstNode(node: PartiqlAst.Expr.LitTime, sb: StringBuilder) { - val localTime = LocalTime.of( - node.value.hour.value.toInt(), - node.value.minute.value.toInt(), - node.value.second.value.toInt(), - node.value.nano.value.toInt() - ) - val precision = node.value.precision - val withTimeZone = node.value.withTimeZone - val tzTime = node.value.tzMinutes?.let { - val prefix = when { - (it.value >= 0) -> "+" - else -> "-" - } - val timeValue = abs(it.value.toInt()) - val tzLocalTime = LocalTime.of(timeValue / 60, timeValue % 60) - "$prefix$tzLocalTime" - } ?: "" - - when (withTimeZone.value) { - true -> sb.append("TIME ($precision) WITH TIME ZONE '$localTime$tzTime'") - false -> sb.append("TIME ($precision) '$localTime'") - } - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.Bag, sb: StringBuilder, level: Int) { - sb.append("<< ") - node.values.forEach { - // Print anything as one line inside a bag - writeAstNodeCheckSubQuery(it, sb, -1) - sb.append(", ") - } - if (node.values.isNotEmpty()) { - sb.removeLast(2) - } - sb.append(" >>") - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.Sexp, sb: StringBuilder, level: Int) { - sb.append("sexp(") - node.values.forEach { - // Print anything as one line inside a sexp - writeAstNodeCheckSubQuery(it, sb, -1) - sb.append(", ") - } - if (node.values.isNotEmpty()) { - sb.removeLast(2) - } - sb.append(")") - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.List, sb: StringBuilder, level: Int) { - val (open, close) = when (node.metas.containsKey(IsListParenthesizedMeta.tag)) { - true -> "( " to " )" - else -> "[ " to " ]" - } - sb.append(open) - node.values.forEach { - // Print anything as one line inside a list - writeAstNodeCheckSubQuery(it, sb, -1) - sb.append(", ") - } - if (node.values.isNotEmpty()) { - sb.removeLast(2) - } - sb.append(close) - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.Struct, sb: StringBuilder, level: Int) { - sb.append("{ ") - node.fields.forEach { - // Print anything as one line inside a struct - writeAstNodeCheckSubQuery(it.first, sb, -1) - sb.append(": ") - writeAstNodeCheckSubQuery(it.second, sb, -1) - sb.append(", ") - } - if (node.fields.isNotEmpty()) { - sb.removeLast(2) - } - sb.append(" }") - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.Parameter, sb: StringBuilder) { - sb.append("?") - } - - private fun writeAstNode(node: PartiqlAst.Expr.Id, sb: StringBuilder) { - when (node.case) { - is PartiqlAst.CaseSensitivity.CaseSensitive -> sb.append("\"${node.name.text}\"") - is PartiqlAst.CaseSensitivity.CaseInsensitive -> sb.append(node.name.text) - } - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.Call, sb: StringBuilder, level: Int) { - sb.append("${node.funcName.text}(") - node.args.forEach { arg -> - // Print anything as one line inside a function call - writeAstNodeCheckSubQuery(arg, sb, -1) - sb.append(", ") - } - if (node.args.isNotEmpty()) { - sb.removeLast(2) - } - sb.append(')') - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.CallAgg, sb: StringBuilder, level: Int) { - sb.append("${node.funcName.text}(") - if (node.setq is PartiqlAst.SetQuantifier.Distinct) { - sb.append("DISTINCT ") - } - // Print anything as one line inside aggregate function call - writeAstNodeCheckSubQuery(node.arg, sb, -1) - sb.append(')') - } - - private fun writeAstNode(node: PartiqlAst.Expr.Path, sb: StringBuilder, level: Int) { - when { - isOperator(node.root) || node.root is PartiqlAst.Expr.Path -> { - sb.append('(') - writeAstNode(node.root, sb, level) - sb.append(')') - } - else -> writeAstNode(node.root, sb, level) // Assume a path root is not a SELECT or CASE clause, i.e. people don't write (SELECT a FROM b).c - } - node.steps.forEach { - when (it) { - is PartiqlAst.PathStep.PathExpr -> when (it.case) { - is PartiqlAst.CaseSensitivity.CaseSensitive -> { - // This means the value of the path component is surrounded by square brackets '[' and ']' - // or double-quotes i.e. either a[b] or a."b" - // Here we just transform it to be surrounded by square brackets - sb.append('[') - writeAstNode(it.index, sb, level) // Assume a path component is not a SELECT or CASE clause, i.e. people don't write a[SELECT b FROM c] - sb.append(']') - } - // Case for a.b - is PartiqlAst.CaseSensitivity.CaseInsensitive -> when (val index = it.index) { - is PartiqlAst.Expr.Lit -> { - val value = index.value.stringValue // It must be a string according to behavior of Lexer - sb.append(".$value") - } - else -> throw IllegalArgumentException("PathExpr's attribute 'index' must be PartiqlAst.Expr.Lit when case sensitivity is insensitive") - } - } - is PartiqlAst.PathStep.PathUnpivot -> sb.append(".[*]") - is PartiqlAst.PathStep.PathWildcard -> sb.append(".*") - } - } - } - - private fun writeAstNode(node: PartiqlAst.Expr.SimpleCase, sb: StringBuilder, level: Int) { - val separator = getSeparator(level) - val sqLevel = getSubQueryLevel(level) - sb.append("CASE ") - // Print anything as one line inside a CASE clause - writeAstNodeCheckSubQuery(node.expr, sb, -1) - writeCaseWhenClauses(node.cases.pairs, sb, sqLevel) - writeCaseElseClause(node.default, sb, sqLevel) - sb.append("${separator}END") - } - - private fun writeAstNode(node: PartiqlAst.Expr.SearchedCase, sb: StringBuilder, level: Int) { - val separator = getSeparator(level) - sb.append("CASE") - writeCaseWhenClauses(node.cases.pairs, sb, level + 1) - writeCaseElseClause(node.default, sb, level + 1) - sb.append("${separator}END") - } - - private fun writeCaseWhenClauses(pairs: List, sb: StringBuilder, level: Int) { - val separator = getSeparator(level) - pairs.forEach { pair -> - sb.append("${separator}WHEN ") - writeAstNodeCheckSubQuery(pair.first, sb, -1) - sb.append(" THEN ") - writeAstNodeCheckSubQuery(pair.second, sb, -1) - } - } - - private fun writeCaseElseClause(default: PartiqlAst.Expr?, sb: StringBuilder, level: Int) { - if (default != null) { - val separator = getSeparator(level) - sb.append("${separator}ELSE ") - writeAstNodeCheckSubQuery(default, sb, -1) - } - } - - private fun writeAstNode(node: PartiqlAst.Expr.Select, sb: StringBuilder, level: Int) { - val separator = getSeparator(level) - - // SELECT clause - when (node.project) { - is PartiqlAst.Projection.ProjectPivot -> sb.append("PIVOT ") - else -> when (node.setq) { - is PartiqlAst.SetQuantifier.Distinct -> sb.append("SELECT DISTINCT ") - else -> sb.append("SELECT ") - } - } - writeProjection(node.project, sb, level) - - // FROM clause - sb.append("${separator}FROM ") - writeFromSource(node.from, sb, level) - - // LET clause - node.fromLet?.let { - val sqLevel = getSubQueryLevel(level) - val fromLetSeparator = getSeparator(sqLevel) - sb.append("${fromLetSeparator}LET ") - writeFromLet(it, sb, level) - } - - // WHERE clause - node.where?.let { - sb.append("${separator}WHERE ") - writeAstNodeCheckSubQuery(it, sb, level) - } - - // GROUP clause - node.group?.let { - sb.append("${separator}GROUP ") - writeGroupBy(it, sb, level) - } - - // HAVING clause - node.having?.let { - sb.append("${separator}HAVING ") - writeAstNodeCheckSubQuery(it, sb, level) - } - - // ORDER BY clause - node.order?.let { orderBy -> - sb.append("${separator}ORDER BY ") - orderBy.sortSpecs.forEach { sortSpec -> - writeSortSpec(sortSpec, sb, level) - sb.append(", ") - } - sb.removeLast(2) - } - - // LIMIT clause - node.limit?.let { - sb.append("${separator}LIMIT ") - writeAstNodeCheckSubQuery(it, sb, level) - } - - // OFFSET clause - node.offset?.let { - sb.append("${separator}OFFSET ") - writeAstNodeCheckSubQuery(it, sb, level) - } - } - - private fun writeSortSpec(sortSpec: PartiqlAst.SortSpec, sb: StringBuilder, level: Int) { - writeAstNodeCheckSubQuery(sortSpec.expr, sb, level + 1) - when (sortSpec.orderingSpec) { - is PartiqlAst.OrderingSpec.Asc -> sb.append(" ASC") - is PartiqlAst.OrderingSpec.Desc -> sb.append(" DESC") - } - } - - private fun writeGroupBy(group: PartiqlAst.GroupBy, sb: StringBuilder, level: Int) { - when (group.strategy) { - is PartiqlAst.GroupingStrategy.GroupFull -> sb.append("BY ") - is PartiqlAst.GroupingStrategy.GroupPartial -> sb.append("PARTIAL BY ") - } - group.keyList.keys.forEach { - writeGroupKey(it, sb, level) - sb.append(", ") - } - sb.removeLast(2) - val sqLevel = getSubQueryLevel(level) - val separator = getSeparator(sqLevel) - group.groupAsAlias?.let { sb.append("${separator}GROUP AS ${it.text}") } - } - - private fun writeGroupKey(key: PartiqlAst.GroupKey, sb: StringBuilder, level: Int) { - writeAstNodeCheckSubQuery(key.expr, sb, level) - key.asAlias?.let { sb.append(" AS ${it.text}") } - } - - private fun writeFromLet(fromLet: PartiqlAst.Let, sb: StringBuilder, level: Int) { - fromLet.letBindings.forEach { - writeLetBinding(it, sb, level) - sb.append(", ") - } - sb.removeLast(2) - } - - private fun writeLetBinding(letBinding: PartiqlAst.LetBinding, sb: StringBuilder, level: Int) { - writeAstNodeCheckSubQuery(letBinding.expr, sb, level) - sb.append(" AS ${letBinding.name.text}") - } - - private fun writeFromSource(from: PartiqlAst.FromSource, sb: StringBuilder, level: Int) { - when (from) { - is PartiqlAst.FromSource.Scan -> { - writeAstNodeCheckSubQuery(from.expr, sb, level) - from.asAlias?.let { sb.append(" AS ${it.text}") } - from.atAlias?.let { sb.append(" AT ${it.text}") } - from.byAlias?.let { sb.append(" BY ${it.text}") } - } - is PartiqlAst.FromSource.Join -> when { - (from.type is PartiqlAst.JoinType.Inner && from.predicate == null) -> { - // This means we can use comma to separate JOIN left-hand side and right-hand side - writeFromSource(from.left, sb, level) - sb.append(", ") - writeFromSource(from.right, sb, level) - } - else -> { - val sqLevel = getSubQueryLevel(level) - val separator = getSeparator(sqLevel) - val join = when (from.type) { - is PartiqlAst.JoinType.Inner -> "INNER JOIN" - is PartiqlAst.JoinType.Left -> "LEFT OUTER JOIN" - is PartiqlAst.JoinType.Right -> "RIGHT OUTER JOIN" - is PartiqlAst.JoinType.Full -> "FULL OUTER JOIN" - } - writeFromSource(from.left, sb, level) - sb.append("$separator$join ") - writeFromSource(from.right, sb, level) - from.predicate?.let { - sb.append(" ON ") - writeAstNodeCheckSubQuery(it, sb, level) - } - } - } - is PartiqlAst.FromSource.Unpivot -> { - sb.append("UNPIVOT ") - writeAstNodeCheckSubQuery(from.expr, sb, level) - from.asAlias?.let { sb.append(" AS ${it.text}") } - from.atAlias?.let { sb.append(" AT ${it.text}") } - from.byAlias?.let { sb.append(" BY ${it.text}") } - } - } - } - - private fun writeProjection(project: PartiqlAst.Projection, sb: StringBuilder, level: Int) { - when (project) { - is PartiqlAst.Projection.ProjectStar -> sb.append('*') - is PartiqlAst.Projection.ProjectValue -> { - sb.append("VALUE ") - writeAstNode(project.value, sb, level) - } - is PartiqlAst.Projection.ProjectList -> { - val projectItems = project.projectItems - projectItems.forEach { item -> - writeProjectItem(item, sb, level) - sb.append(", ") - } - sb.removeLast(2) - } - is PartiqlAst.Projection.ProjectPivot -> { - writeAstNodeCheckSubQuery(project.key, sb, level) - sb.append(" AT ") - writeAstNodeCheckSubQuery(project.value, sb, level) - } - } - } - - private fun writeProjectItem(item: PartiqlAst.ProjectItem, sb: StringBuilder, level: Int) { - when (item) { - is PartiqlAst.ProjectItem.ProjectAll -> { - writeAstNodeCheckSubQuery(item.expr, sb, level) - sb.append(".*") - } - is PartiqlAst.ProjectItem.ProjectExpr -> { - writeAstNodeCheckSubQuery(item.expr, sb, level) - item.asAlias?.let { - sb.append(" AS ") - sb.append(it.text) - } - } - } - } - - // The logic here can be improved, so we can remove unnecessary parenthesis in different scenarios. - // i.e. currently, it transforms '1 + 2 + 3' as '(1 + 2) + 3', however, the parenthesis can be removed. - private fun writeAstNodeCheckOp(node: PartiqlAst.Expr, sb: StringBuilder, level: Int) { - when (isOperator(node)) { - true -> { - sb.append('(') - writeAstNode(node, sb, level) - sb.append(')') - } - // Print anything as one line inside an operator - false -> writeAstNodeCheckSubQuery(node, sb, -1) - } - } - - private fun writeAstNode(node: PartiqlAst.Expr.Pos, sb: StringBuilder, level: Int) { - sb.append('+') - writeAstNodeCheckOp(node.expr, sb, level) - } - - private fun writeAstNode(node: PartiqlAst.Expr.Neg, sb: StringBuilder, level: Int) { - sb.append('-') - writeAstNodeCheckOp(node.expr, sb, level) - } - - private fun writeAstNode(node: PartiqlAst.Expr.Not, sb: StringBuilder, level: Int) { - sb.append("NOT ") - writeAstNodeCheckOp(node.expr, sb, level) - } - - private fun writeAstNode(node: PartiqlAst.Expr.Between, sb: StringBuilder, level: Int) { - writeAstNodeCheckOp(node.value, sb, level) - sb.append(" BETWEEN ") - writeAstNodeCheckOp(node.from, sb, level) - sb.append(" AND ") - writeAstNodeCheckOp(node.to, sb, level) - } - - private fun writeAstNode(node: PartiqlAst.Expr.Like, sb: StringBuilder, level: Int) { - writeAstNodeCheckOp(node.value, sb, level) - sb.append(" LIKE ") - writeAstNodeCheckOp(node.pattern, sb, level) - node.escape?.let { - sb.append(" ESCAPE ") - writeAstNodeCheckOp(it, sb, level) - } - } - - private fun writeAstNode(node: PartiqlAst.Expr.IsType, sb: StringBuilder, level: Int) { - writeAstNodeCheckOp(node.value, sb, level) - sb.append(" IS ") - writeType(node.type, sb) - } - - private fun writeType(node: PartiqlAst.Type, sb: StringBuilder) { - when (node) { - is PartiqlAst.Type.NullType -> sb.append("NULL") - is PartiqlAst.Type.AnyType -> sb.append("ANY") - is PartiqlAst.Type.BagType -> sb.append("BAG") - is PartiqlAst.Type.BlobType -> sb.append("BLOB") - is PartiqlAst.Type.BooleanType -> sb.append("BOOLEAN") - is PartiqlAst.Type.CharacterType -> sb.append("CHAR") - is PartiqlAst.Type.CharacterVaryingType -> sb.append("VARCHAR") - is PartiqlAst.Type.ClobType -> sb.append("CLOB") - is PartiqlAst.Type.DateType -> sb.append("DATE") - is PartiqlAst.Type.DecimalType -> sb.append("DECIMAL") - is PartiqlAst.Type.DoublePrecisionType -> sb.append("DOUBLE_PRECISION") - is PartiqlAst.Type.FloatType -> sb.append("FLOAT") - is PartiqlAst.Type.Integer4Type -> sb.append("INT4") - is PartiqlAst.Type.Integer8Type -> sb.append("INT8") - is PartiqlAst.Type.IntegerType -> sb.append("INT") - is PartiqlAst.Type.ListType -> sb.append("LIST") - is PartiqlAst.Type.MissingType -> sb.append("MISSING") - is PartiqlAst.Type.NumericType -> sb.append("NUMERIC") - is PartiqlAst.Type.RealType -> sb.append("REAL") - is PartiqlAst.Type.SexpType -> sb.append("SEXP") - is PartiqlAst.Type.SmallintType -> sb.append("SMALLINT") - is PartiqlAst.Type.StringType -> sb.append("STRING") - is PartiqlAst.Type.StructType -> sb.append("STRUCT") - is PartiqlAst.Type.SymbolType -> sb.append("SYMBOL") - is PartiqlAst.Type.TimeType -> sb.append("TIME") - is PartiqlAst.Type.TimeWithTimeZoneType -> sb.append("TIME WITH TIME ZONE") - is PartiqlAst.Type.TimestampType -> sb.append("TIMESTAMP") - is PartiqlAst.Type.TupleType -> sb.append("TUPLE") - // TODO: Support formatting CustomType - is PartiqlAst.Type.CustomType -> error("CustomType is not supported yet. ") - } - } - - private fun writeAstNode(node: PartiqlAst.Expr.Cast, sb: StringBuilder, level: Int) { - sb.append("CAST (") - writeAstNodeCheckOp(node.value, sb, level) - sb.append(" AS ") - writeType(node.asType, sb) - sb.append(')') - } - - private fun writeAstNode(node: PartiqlAst.Expr.CanCast, sb: StringBuilder, level: Int) { - sb.append("CAN_CAST (") - writeAstNodeCheckOp(node.value, sb, level) - sb.append(" AS ") - writeType(node.asType, sb) - sb.append(')') - } - - private fun writeAstNode(node: PartiqlAst.Expr.CanLosslessCast, sb: StringBuilder, level: Int) { - sb.append("CAN_LOSSLESS_CAST (") - writeAstNodeCheckOp(node.value, sb, level) - sb.append(" AS ") - writeType(node.asType, sb) - sb.append(')') - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.Coalesce, sb: StringBuilder, level: Int) { - sb.append("COALESCE(") - node.args.forEach { arg -> - // Write anything as one line as COALESCE arguments - writeAstNodeCheckSubQuery(arg, sb, -1) - sb.append(", ") - } - if (node.args.isNotEmpty()) { - sb.removeLast(2) - } - sb.append(')') - } - - @Suppress("UNUSED_PARAMETER") - private fun writeAstNode(node: PartiqlAst.Expr.NullIf, sb: StringBuilder, level: Int) { - // Write anything as one line as COALESCE arguments - sb.append("NULLIF(") - writeAstNodeCheckSubQuery(node.expr1, sb, -1) - sb.append(", ") - writeAstNodeCheckSubQuery(node.expr2, sb, -1) - sb.append(')') - } - - private fun writeNAryOperator(operatorName: String, operands: List, sb: StringBuilder, level: Int) { - if (operands.size < 2) { - throw IllegalStateException("Internal Error: NAry operator $operatorName must have at least 2 operands") - } - operands.forEach { - writeAstNodeCheckOp(it, sb, level) - sb.append(" $operatorName ") - } - sb.removeLast(operatorName.length + 2) - } - - private fun isCaseOrSelect(node: PartiqlAst.Expr): Boolean = - when (node) { - is PartiqlAst.Expr.SimpleCase, is PartiqlAst.Expr.SearchedCase, is PartiqlAst.Expr.Select -> true - else -> false - } - - private fun isOperator(node: PartiqlAst.Expr): Boolean = - when (node) { - is PartiqlAst.Expr.And, is PartiqlAst.Expr.Between, is PartiqlAst.Expr.CanCast, - is PartiqlAst.Expr.CanLosslessCast, is PartiqlAst.Expr.Cast, is PartiqlAst.Expr.Concat, - is PartiqlAst.Expr.Divide, is PartiqlAst.Expr.Eq, is PartiqlAst.Expr.BagOp, - is PartiqlAst.Expr.Gt, is PartiqlAst.Expr.Gte, is PartiqlAst.Expr.InCollection, - is PartiqlAst.Expr.IsType, is PartiqlAst.Expr.Like, - is PartiqlAst.Expr.Lt, is PartiqlAst.Expr.Lte, is PartiqlAst.Expr.Minus, - is PartiqlAst.Expr.Modulo, is PartiqlAst.Expr.Ne, is PartiqlAst.Expr.Neg, - is PartiqlAst.Expr.Not, is PartiqlAst.Expr.Or, is PartiqlAst.Expr.Plus, - is PartiqlAst.Expr.Pos, is PartiqlAst.Expr.Times -> true - else -> false - } - - // We need to add a line breaker and indent only for CASE and SELECT clauses. - // If level is -1, this indicates there is no need for formatting - private fun getSeparator(level: Int) = - when (level == -1) { - true -> " " - false -> "\n${"\t".repeat(level)}" - } - - private fun getSubQueryLevel(level: Int) = - when (level == -1) { - true -> -1 - false -> level + 1 - } - - private fun StringBuilder.removeLast(n: Int): StringBuilder { - for (i in 1..n) { - deleteCharAt(length - 1) - } - return this - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/RecursionTree.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/RecursionTree.kt deleted file mode 100644 index a4abddba4b..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/RecursionTree.kt +++ /dev/null @@ -1,51 +0,0 @@ -package org.partiql.lang.prettyprint - -import org.partiql.lang.domains.PartiqlAst - -/** - * PIG AST is not a recursive data structure, thus it is not easy to transform it directly to a pretty printed string. - * So we need to first transform it into RecursionTree, which is a recursive tree structure (it has a list of children which are also RecursionTree), - * then we can recursively pretty print the RecursionTree as we want. - * - * @param astType is a string of the PIG AST node type - * @param value is the value in case node type is [PartiqlAst.Expr.Lit] - * @param attrOfParent is a string which represents which attribute it belongs to its parent - * @param children is a list of child RecursionTree - * - * Take the [PartiqlAst.Expr.Eq] node in the WHERE clause in `SELECT a FROM b WHERE c = d` as example. - * [astType] is '=', [value] is null, [attrOfParent] is 'where', [children] is a list of [PartiqlAst.Expr.Id] c and d. - */ -class RecursionTree( - private val astType: String, - private val value: String? = null, - private val attrOfParent: String? = null, - private val children: List? = null -) { - fun convertToString(): String { - val result = StringBuilder() - recurseToResult(0, result) - return result.toString().dropLast(1) // Drop last line separator \n - } - - private fun recurseToResult(indent: Int, result: StringBuilder) { - val prefix = when (attrOfParent) { - null -> "" - else -> "$attrOfParent: " - } - - val displayedValue = when (value) { - null -> "" - else -> " $value" - } - - result.append("\t".repeat(indent)) - .append(prefix) - .append(astType) - .append(displayedValue) - .append('\n') - - children?.forEach { - it.recurseToResult(indent + 1, result) - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/Exceptions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/Exceptions.kt deleted file mode 100644 index 7562954dc0..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/Exceptions.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.syntax - -import org.partiql.errors.ErrorCode -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.SqlException - -/** Root exception type for syntactic problems. */ -open class SyntaxException( - message: String = "", - errorCode: ErrorCode, - errorContext: PropertyValueMap, - cause: Throwable? = null -) : - SqlException(message, errorCode, errorContext, cause) - -/** Error in the Lexer. */ -open class LexerException( - message: String = "", - errorCode: ErrorCode, - errorContext: PropertyValueMap, - cause: Throwable? = null -) : - SyntaxException(message, errorCode, errorContext, cause) - -/** Error in the parser. */ -open class ParserException( - message: String = "", - errorCode: ErrorCode, - errorContext: PropertyValueMap = PropertyValueMap(), - cause: Throwable? = null -) : - SyntaxException(message, errorCode, errorContext, cause) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/Parser.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/Parser.kt deleted file mode 100644 index c81558d72f..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/Parser.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.syntax - -import org.partiql.lang.domains.PartiqlAst - -/** - * Parses a PartiQL Statement into an AST. - * - * Implementations must be thread-safe. - */ -interface Parser { - fun parseAstStatement(source: String): PartiqlAst.Statement -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/PartiQLParserBuilder.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/PartiQLParserBuilder.kt deleted file mode 100644 index ff6a0b1993..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/PartiQLParserBuilder.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.syntax - -import org.partiql.lang.syntax.impl.PartiQLPigParser -import org.partiql.lang.syntax.impl.PartiQLShimParser -import org.partiql.lang.types.CustomType - -/** - * A builder class to instantiate a [Parser]. - * - * Example usages: - * - * ``` - * val parser = PartiQLParserBuilder.standard().build() - * val parser = PartiQLParserBuilder.standard().customTypes(types).build() - * ``` - */ -class PartiQLParserBuilder { - - private var constructor: (customTypes: List) -> Parser = ::PartiQLPigParser - - companion object { - - @JvmStatic - fun standard(): PartiQLParserBuilder { - return PartiQLParserBuilder() - } - - @JvmStatic - fun experimental(): PartiQLParserBuilder { - val builder = PartiQLParserBuilder() - builder.constructor = { _ -> - // currently don't pass custom types - val delegate = org.partiql.parser.PartiQLParser.default() - PartiQLShimParser(delegate) - } - return builder - } - } - - private var customTypes: List = emptyList() - - fun customTypes(types: List): PartiQLParserBuilder = this.apply { - this.customTypes = types - } - - fun build(): Parser { - return constructor(this.customTypes) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/DateTimePart.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/DateTimePart.kt deleted file mode 100644 index c0c80de80e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/DateTimePart.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.syntax.impl - -internal enum class DateTimePart { - YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, TIMEZONE_HOUR, TIMEZONE_MINUTE; - - companion object { - fun safeValueOf(value: String): DateTimePart? = try { - valueOf(value.toUpperCase().trim()) - } catch (_: IllegalArgumentException) { - null - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigParser.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigParser.kt deleted file mode 100644 index 4e17a4ad1e..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigParser.kt +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.syntax.impl - -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.SqlException -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.syntax.LexerException -import org.partiql.lang.syntax.Parser -import org.partiql.lang.syntax.ParserException -import org.partiql.lang.types.CustomType -import org.partiql.lang.util.checkThreadInterrupted -import org.partiql.lang.util.getAntlrDisplayString -import org.partiql.lang.util.getIonValue -import org.partiql.parser.internal.antlr.PartiQLParser -import org.partiql.parser.internal.antlr.PartiQLTokens -import org.partiql.parser.thirdparty.antlr.v4.runtime.BailErrorStrategy -import org.partiql.parser.thirdparty.antlr.v4.runtime.BaseErrorListener -import org.partiql.parser.thirdparty.antlr.v4.runtime.CharStreams -import org.partiql.parser.thirdparty.antlr.v4.runtime.CommonTokenStream -import org.partiql.parser.thirdparty.antlr.v4.runtime.ParserRuleContext -import org.partiql.parser.thirdparty.antlr.v4.runtime.RecognitionException -import org.partiql.parser.thirdparty.antlr.v4.runtime.Recognizer -import org.partiql.parser.thirdparty.antlr.v4.runtime.Token -import org.partiql.parser.thirdparty.antlr.v4.runtime.TokenSource -import org.partiql.parser.thirdparty.antlr.v4.runtime.TokenStream -import org.partiql.parser.thirdparty.antlr.v4.runtime.atn.PredictionMode -import org.partiql.parser.thirdparty.antlr.v4.runtime.misc.ParseCancellationException -import org.partiql.parser.thirdparty.antlr.v4.runtime.tree.ParseTree -import java.io.InputStream -import java.nio.channels.ClosedByInterruptException -import java.nio.charset.StandardCharsets - -/** - * Extends [Parser] to provide a mechanism to parse an input query string. It internally uses ANTLR's generated parser, - * [PartiQLParser] to create an ANTLR [ParseTree] from the input query. Then, it uses the configured [PartiQLPigVisitor] - * to convert the [ParseTree] into a [PartiqlAst.Statement]. - */ -internal class PartiQLPigParser(val customTypes: List = listOf()) : Parser { - - @Throws(ParserException::class, InterruptedException::class) - override fun parseAstStatement(source: String): PartiqlAst.Statement { - try { - return parseQuery(source) - } catch (throwable: Throwable) { - when (throwable) { - is ParserException -> throw throwable - is SqlException -> throw ParserException( - "Intercepted PartiQL exception.", - throwable.errorCode, - cause = throwable, - errorContext = throwable.errorContext - ) - is StackOverflowError -> { - val msg = "Input query too large. This error typically occurs when there are several nested " + - "expressions/predicates and can usually be fixed by simplifying expressions." - throw ParserException(msg, ErrorCode.PARSE_FAILED_STACK_OVERFLOW, cause = throwable) - } - is InterruptedException -> throw throwable - else -> throw ParserException("Unhandled exception.", ErrorCode.INTERNAL_ERROR, cause = throwable) - } - } - } - - /** - * To reduce latency costs, the [PartiQLPigParser] attempts to use [PredictionMode.SLL] and falls back to - * [PredictionMode.LL] if a [ParseCancellationException] is thrown by the [BailErrorStrategy]. See [createParserSLL] - * and [createParserLL] for more information. - */ - private fun parseQuery(input: String) = try { - parseQuery(input) { createParserSLL(it) } - } catch (ex: ParseCancellationException) { - parseQuery(input) { createParserLL(it) } - } - - /** - * Parses an input string [input] using whichever parser [parserInit] creates. - */ - internal fun parseQuery(input: String, parserInit: (TokenStream) -> InterruptibleParser): PartiqlAst.Statement { - val queryStream = createInputStream(input) - val tokenStream = createTokenStream(queryStream) - val parser = parserInit(tokenStream) - val tree = parser.root() - val visitor = PartiQLPigVisitor(tokenStream, customTypes, tokenStream.parameterIndexes) - return visitor.visit(tree) as PartiqlAst.Statement - } - - private fun createInputStream(input: String) = input.byteInputStream(StandardCharsets.UTF_8) - - internal fun createTokenStream(queryStream: InputStream): CountingTokenStream { - val inputStream = try { - CharStreams.fromStream(queryStream) - } catch (ex: ClosedByInterruptException) { - throw InterruptedException() - } - val handler = TokenizeErrorListener() - val lexer = PartiQLTokens(inputStream) - lexer.removeErrorListeners() - lexer.addErrorListener(handler) - return CountingTokenStream(lexer) - } - - /** - * Creates a [PartiQLParser] that uses [PredictionMode.SLL] and the [BailErrorStrategy]. The [PartiQLParser], - * upon seeing a syntax error, will throw a [ParseCancellationException] due to the [PartiQLParser.getErrorHandler] - * being a [BailErrorStrategy]. The purpose of this is to throw syntax errors as quickly as possible once encountered. - * As noted by the [PredictionMode.SLL] documentation, to guarantee results, it is useful to follow-up a failed parse - * by parsing with [PredictionMode.LL] -- see [createParserLL] for more information. - * See the JavaDocs for [PredictionMode.SLL] and [BailErrorStrategy] for more information. - */ - internal fun createParserSLL(stream: TokenStream): InterruptibleParser { - val parser = InterruptibleParser(stream) - parser.reset() - parser.interpreter.predictionMode = PredictionMode.SLL - parser.removeErrorListeners() - parser.errorHandler = BailErrorStrategy() - return parser - } - - /** - * Creates a [PartiQLParser] that uses [PredictionMode.LL]. This method is capable of parsing all valid inputs - * for a grammar, but is slower than [PredictionMode.SLL]. Upon seeing a syntax error, this parser throws a - * [ParserException]. - */ - internal fun createParserLL(stream: TokenStream): InterruptibleParser { - val parser = InterruptibleParser(stream) - parser.reset() - parser.interpreter.predictionMode = PredictionMode.LL - parser.removeErrorListeners() - parser.addErrorListener(ParseErrorListener()) - return parser - } - - /** - * Catches Lexical errors (unidentified tokens) and throws a [LexerException] - */ - private class TokenizeErrorListener : BaseErrorListener() { - @Throws(LexerException::class) - override fun syntaxError( - recognizer: Recognizer<*, *>?, - offendingSymbol: Any?, - line: Int, - charPositionInLine: Int, - msg: String, - e: RecognitionException? - ) { - val propertyValues = PropertyValueMap() - propertyValues[Property.LINE_NUMBER] = line.toLong() - propertyValues[Property.COLUMN_NUMBER] = charPositionInLine.toLong() + 1 - propertyValues[Property.TOKEN_STRING] = msg - throw LexerException(message = msg, errorCode = ErrorCode.LEXER_INVALID_TOKEN, errorContext = propertyValues, cause = e) - } - } - - /** - * Catches Parser errors (malformed syntax) and throws a [ParserException] - */ - private class ParseErrorListener : BaseErrorListener() { - - @Throws(ParserException::class) - override fun syntaxError( - recognizer: Recognizer<*, *>?, - offendingSymbol: Any, - line: Int, - charPositionInLine: Int, - msg: String, - e: RecognitionException? - ) { - if (offendingSymbol !is Token) { throw IllegalArgumentException("Offending symbol is not a Token.") } - val propertyValues = PropertyValueMap() - propertyValues[Property.LINE_NUMBER] = line.toLong() - propertyValues[Property.COLUMN_NUMBER] = charPositionInLine.toLong() + 1 - propertyValues[Property.TOKEN_DESCRIPTION] = offendingSymbol.type.getAntlrDisplayString() - propertyValues[Property.TOKEN_VALUE] = getIonValue(offendingSymbol) - throw ParserException(message = msg, errorCode = ErrorCode.PARSE_UNEXPECTED_TOKEN, errorContext = propertyValues, cause = e) - } - } - - /** - * A wrapped [PartiQLParser] to allow thread interruption during parse. - */ - internal class InterruptibleParser(input: TokenStream) : PartiQLParser(input) { - override fun enterRule(localctx: ParserRuleContext?, state: Int, ruleIndex: Int) { - checkThreadInterrupted() - super.enterRule(localctx, state, ruleIndex) - } - } - - /** - * This token stream creates [parameterIndexes], which is a map, where the keys represent the - * indexes of all [PartiQLTokens.QUESTION_MARK]'s and the values represent their relative index amongst all other - * [PartiQLTokens.QUESTION_MARK]'s. - */ - internal open class CountingTokenStream(tokenSource: TokenSource) : CommonTokenStream(tokenSource) { - // TODO: Research use-case of parameters and implementation -- see https://github.com/partiql/partiql-docs/issues/23 - val parameterIndexes = mutableMapOf() - private var parametersFound = 0 - override fun LT(k: Int): Token? { - val token = super.LT(k) - token?.let { - if (it.type == PartiQLTokens.QUESTION_MARK && parameterIndexes.containsKey(token.tokenIndex).not()) { - parameterIndexes[token.tokenIndex] = ++parametersFound - } - } - return token - } - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt deleted file mode 100644 index b6730cc50a..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt +++ /dev/null @@ -1,2208 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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.partiql.lang.syntax.impl - -import com.amazon.ion.Decimal -import com.amazon.ionelement.api.DecimalElement -import com.amazon.ionelement.api.FloatElement -import com.amazon.ionelement.api.IntElement -import com.amazon.ionelement.api.IntElementSize -import com.amazon.ionelement.api.IonElement -import com.amazon.ionelement.api.IonElementException -import com.amazon.ionelement.api.MetaContainer -import com.amazon.ionelement.api.StringElement -import com.amazon.ionelement.api.SymbolElement -import com.amazon.ionelement.api.emptyMetaContainer -import com.amazon.ionelement.api.ionBool -import com.amazon.ionelement.api.ionDecimal -import com.amazon.ionelement.api.ionFloat -import com.amazon.ionelement.api.ionInt -import com.amazon.ionelement.api.ionNull -import com.amazon.ionelement.api.ionString -import com.amazon.ionelement.api.ionSymbol -import com.amazon.ionelement.api.loadSingleElement -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.IsCountStarMeta -import org.partiql.lang.ast.IsImplictJoinMeta -import org.partiql.lang.ast.IsIonLiteralMeta -import org.partiql.lang.ast.IsListParenthesizedMeta -import org.partiql.lang.ast.IsPathIndexMeta -import org.partiql.lang.ast.IsValuesExprMeta -import org.partiql.lang.ast.LegacyLogicalNotMeta -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.metaContainerOf -import org.partiql.lang.eval.EvaluationException -import org.partiql.lang.eval.time.MAX_PRECISION_FOR_TIME -import org.partiql.lang.syntax.ParserException -import org.partiql.lang.syntax.util.DateTimeUtils -import org.partiql.lang.types.CustomType -import org.partiql.lang.util.DATE_PATTERN_REGEX -import org.partiql.lang.util.bigDecimalOf -import org.partiql.lang.util.checkThreadInterrupted -import org.partiql.lang.util.error -import org.partiql.lang.util.getPrecisionFromTimeString -import org.partiql.lang.util.unaryMinus -import org.partiql.parser.internal.antlr.PartiQLParser -import org.partiql.parser.internal.antlr.PartiQLParserBaseVisitor -import org.partiql.parser.internal.antlr.PartiQLTokens -import org.partiql.parser.thirdparty.antlr.v4.runtime.CommonTokenStream -import org.partiql.parser.thirdparty.antlr.v4.runtime.ParserRuleContext -import org.partiql.parser.thirdparty.antlr.v4.runtime.Token -import org.partiql.parser.thirdparty.antlr.v4.runtime.tree.TerminalNode -import org.partiql.pig.runtime.SymbolPrimitive -import org.partiql.value.datetime.DateTimeException -import org.partiql.value.datetime.TimeZone -import java.math.BigInteger -import java.time.LocalDate -import java.time.LocalTime -import java.time.OffsetTime -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeParseException - -/** - * Extends ANTLR's generated [PartiQLParserBaseVisitor] to visit an ANTLR ParseTree and convert it into a PartiQL AST. This - * class uses the [PartiqlAst.PartiqlAstNode] to represent all nodes within the new AST. - * - * When the grammar in PartiQL.g4 is extended with a new rule, one needs to override corresponding visitor methods - * in this class, in order to extend the transformation from an ANTLR parse tree into a [PartqlAst] tree. - * (Trivial implementations of these methods are generated into [PartiQLParserBaseVisitor].) - * - * For a rule of the form - * ``` - * Aaa - * : B1 ... Bn ; - * ``` - * it generates the `visitAaa(ctx: PartiQLParser.AaaContext ctx)` method, - * while for a rule of the form - * ``` - * Aaa - * : B1 ... Bn # A1 - * | C1 ... Cm # A2 - * ; - * ``` - * it generates methods `visitA1(ctx: PartiQLParser.A1Context ctx)` and `visitA2(ctx: PartiQLParser.A2Context ctx)`, - * but not `visitAaa`. - * The context objects `ctx` provide access to the terminals and non-terminals (`Bi`, `Cj`) necessary for - * implementing the methods suitably. - * - * Conversely, when implementing the visitor for another rule that *references* `Aaa`, - * - The visitor for a rule of the 1st form can be recursively invoked as `visitAaa(ctx.Aaa)`, - * which usually returns an AST node of the desired type. - * - For the rule of the 2nd form, as there is no `visitAaa`, one has to invoke `AbstractParseTreeVisitor.visit()` - * and then cast the result to the expected AST type, doing something like - * ``` - * visit(ctx.Aaa) as PartiqlAst.Aaa - * ``` - * This delegates to `accept()` which, at run time, invokes the appropriate visitor (`visitA1` or `visitA2`). - * However, any static guarantee is lost (in principle, `accept` can dispatch to any visitor of any rule - * in the grammar), hence the need for the cast. - * - * Note: A rule of an intermediate form between the above two is allowed: when there are multiple alternative clauses, - * but no labels on the clauses. In this case, it generates `visitAaa` whose context object `ctx` provides access - * to the combined set of non-terminals of the rule's clauses -- which are then visible at nullable types. - * There could be clever ways of exploiting this, to avoid the dispatch via `visit()`. - */ -internal class PartiQLPigVisitor( - private val tokens: CommonTokenStream, - val customTypes: List = listOf(), - private val parameterIndexes: Map = mapOf(), -) : - PartiQLParserBaseVisitor() { - - companion object { - internal val TRIM_SPECIFICATION_KEYWORDS = setOf("both", "leading", "trailing") - } - - private val customKeywords = customTypes.map { it.name.lowercase() } - - private val customTypeAliases = - customTypes.map { customType -> - customType.aliases.map { alias -> - Pair(alias.lowercase(), customType.name.lowercase()) - } - }.flatten().toMap() - - /** - * - * TOP LEVEL - * - */ - - override fun visitQueryDql(ctx: PartiQLParser.QueryDqlContext) = visitDql(ctx.dql()) - - override fun visitQueryDml(ctx: PartiQLParser.QueryDmlContext): PartiqlAst.PartiqlAstNode = visit(ctx.dml()) - - override fun visitExprTermCurrentUser(ctx: PartiQLParser.ExprTermCurrentUserContext): PartiqlAst.Expr.SessionAttribute { - val metas = ctx.CURRENT_USER().getSourceMetaContainer() - return PartiqlAst.Expr.SessionAttribute( - value = SymbolPrimitive(ctx.CURRENT_USER().text.toLowerCase(), metas), - metas = metas - ) - } - - override fun visitRoot(ctx: PartiQLParser.RootContext) = when (ctx.EXPLAIN()) { - null -> visit(ctx.statement()) as PartiqlAst.Statement - else -> PartiqlAst.build { - var type: String? = null - var format: String? = null - val metas = ctx.EXPLAIN().getSourceMetaContainer() - ctx.explainOption().forEach { option -> - val parameter = try { - ExplainParameters.valueOf(option.param.text.toUpperCase()) - } catch (ex: IllegalArgumentException) { - throw option.param.error("Unknown EXPLAIN parameter.", ErrorCode.PARSE_UNEXPECTED_TOKEN, cause = ex) - } - when (parameter) { - ExplainParameters.TYPE -> { - type = parameter.getCompliantString(type, option.value) - } - - ExplainParameters.FORMAT -> { - format = parameter.getCompliantString(format, option.value) - } - } - } - explain( - target = domain( - statement = visit(ctx.statement()) as PartiqlAst.Statement, - type = type, - format = format, - metas = metas - ), - metas = metas - ) - } - } - - /** - * - * COMMON USAGES - * - */ - - override fun visitAsIdent(ctx: PartiQLParser.AsIdentContext) = visitSymbolPrimitive(ctx.symbolPrimitive()) - - override fun visitAtIdent(ctx: PartiQLParser.AtIdentContext) = visitSymbolPrimitive(ctx.symbolPrimitive()) - - override fun visitByIdent(ctx: PartiQLParser.ByIdentContext) = visitSymbolPrimitive(ctx.symbolPrimitive()) - - private fun visitSymbolPrimitive(ctx: PartiQLParser.SymbolPrimitiveContext): PartiqlAst.Expr.Id = - when (ctx) { - is PartiQLParser.IdentifierQuotedContext -> visitIdentifierQuoted(ctx) - is PartiQLParser.IdentifierUnquotedContext -> visitIdentifierUnquoted(ctx) - else -> throw ParserException("Invalid symbol reference.", ErrorCode.PARSE_INVALID_QUERY) - } - - override fun visitIdentifierQuoted(ctx: PartiQLParser.IdentifierQuotedContext): PartiqlAst.Expr.Id = PartiqlAst.build { - id( - ctx.IDENTIFIER_QUOTED().getStringValue(), - caseSensitive(), - unqualified(), - ctx.IDENTIFIER_QUOTED().getSourceMetaContainer() - ) - } - - override fun visitIdentifierUnquoted(ctx: PartiQLParser.IdentifierUnquotedContext): PartiqlAst.Expr.Id = PartiqlAst.build { - id( - ctx.text, - caseInsensitive(), - unqualified(), - ctx.IDENTIFIER().getSourceMetaContainer() - ) - } - - /** - * - * DATA DEFINITION LANGUAGE (DDL) - * - */ - - override fun visitQueryDdl(ctx: PartiQLParser.QueryDdlContext) = PartiqlAst.build { - val op = visitDdl(ctx.ddl()) as PartiqlAst.DdlOp - ddl(op, op.metas) - } - - override fun visitDropTable(ctx: PartiQLParser.DropTableContext) = PartiqlAst.build { - val id = if (ctx.qualifiedName().qualifier.isEmpty()) { - visitSymbolPrimitive(ctx.qualifiedName().name) - } else { - throw ParserException("PIG Parser does not support qualified name as table name", ErrorCode.PARSE_UNEXPECTED_TOKEN) - } - dropTable(id.toIdentifier(), ctx.DROP().getSourceMetaContainer()) - } - - override fun visitDropIndex(ctx: PartiQLParser.DropIndexContext) = PartiqlAst.build { - val id = visitSymbolPrimitive(ctx.target) - val key = visitSymbolPrimitive(ctx.on) - dropIndex(key.toIdentifier(), id.toIdentifier(), ctx.DROP().getSourceMetaContainer()) - } - - override fun visitCreateTable(ctx: PartiQLParser.CreateTableContext) = PartiqlAst.build { - val name = if (ctx.qualifiedName().qualifier.isEmpty()) { - visitSymbolPrimitive(ctx.qualifiedName().name).name - } else { - throw ParserException("PIG Parser does not support qualified name as table name", ErrorCode.PARSE_UNEXPECTED_TOKEN) - } - val def = ctx.tableDef()?.let { visitTableDef(it) } - createTable_(name, def, ctx.CREATE().getSourceMetaContainer()) - } - - override fun visitCreateIndex(ctx: PartiQLParser.CreateIndexContext) = PartiqlAst.build { - val id = visitSymbolPrimitive(ctx.symbolPrimitive()) - val fields = ctx.pathSimple().map { path -> visitPathSimple(path) } - createIndex(id.toIdentifier(), fields, ctx.CREATE().getSourceMetaContainer()) - } - - override fun visitTableDef(ctx: PartiQLParser.TableDefContext) = PartiqlAst.build { - val parts = ctx.tableDefPart().map { visit(it) as PartiqlAst.TableDefPart } - tableDef(parts) - } - - override fun visitColumnDeclaration(ctx: PartiQLParser.ColumnDeclarationContext) = PartiqlAst.build { - val name = visitSymbolPrimitive(ctx.columnName().symbolPrimitive()).name.text - val type = visit(ctx.type()) as PartiqlAst.Type - val constrs = ctx.columnConstraint().map { visitColumnConstraint(it) } - columnDeclaration(name, type, constrs) - } - - override fun visitColumnConstraint(ctx: PartiQLParser.ColumnConstraintContext) = PartiqlAst.build { - val name = ctx.columnConstraintName()?.let { visitSymbolPrimitive(it.symbolPrimitive()).name.text } - val def = visit(ctx.columnConstraintDef()) as PartiqlAst.ColumnConstraintDef - columnConstraint(name, def) - } - - override fun visitColConstrNotNull(ctx: PartiQLParser.ColConstrNotNullContext) = PartiqlAst.build { - columnNotnull() - } - - override fun visitColConstrNull(ctx: PartiQLParser.ColConstrNullContext) = PartiqlAst.build { - columnNull() - } - - /** - * - * EXECUTE - * - */ - - override fun visitQueryExec(ctx: PartiQLParser.QueryExecContext) = visitExecCommand(ctx.execCommand()) - - override fun visitExecCommand(ctx: PartiQLParser.ExecCommandContext) = PartiqlAst.build { - val name = visitExpr(ctx.name).getStringValue(ctx.name.getStart()) - val args = ctx.args.map { visitExpr(it) } - exec_( - SymbolPrimitive(name.lowercase(), emptyMetaContainer()), - args, - ctx.name.getStart().getSourceMetaContainer() - ) - } - - /** - * - * DATA MANIPULATION LANGUAGE (DML) - * - */ - - override fun visitDmlBaseWrapper(ctx: PartiQLParser.DmlBaseWrapperContext) = PartiqlAst.build { - val sourceContext = when { - ctx.updateClause() != null -> ctx.updateClause() - ctx.fromClause() != null -> ctx.fromClause() - else -> throw ParserException("Unable to deduce from source in DML", ErrorCode.PARSE_INVALID_QUERY) - } - val from = sourceContext?.let { visit(it) as PartiqlAst.FromSource } - val where = ctx.whereClause()?.let { visitWhereClause(it) } - val returning = ctx.returningClause()?.let { visitReturningClause(it) } - val operations = ctx.dmlBaseCommand().map { command -> getCommandList(visit(command)) }.flatten() - dml(dmlOpList(operations, operations[0].metas), from, where, returning, metas = operations[0].metas) - } - - override fun visitDmlBase(ctx: PartiQLParser.DmlBaseContext) = PartiqlAst.build { - val commands = getCommandList(visit(ctx.dmlBaseCommand())) - dml(dmlOpList(commands, commands[0].metas), metas = commands[0].metas) - } - - private fun getCommandList(command: PartiqlAst.PartiqlAstNode): List { - return when (command) { - is PartiqlAst.DmlOpList -> command.ops - is PartiqlAst.DmlOp -> listOf(command) - else -> throw ParserException("Unable to grab DML operation.", ErrorCode.PARSE_INVALID_QUERY) - } - } - - override fun visitRemoveCommand(ctx: PartiQLParser.RemoveCommandContext) = PartiqlAst.build { - val target = visitPathSimple(ctx.pathSimple()) - remove(target, ctx.REMOVE().getSourceMetaContainer()) - } - - override fun visitDeleteCommand(ctx: PartiQLParser.DeleteCommandContext) = PartiqlAst.build { - val from = visit(ctx.fromClauseSimple()) as PartiqlAst.FromSource - val where = ctx.whereClause()?.let { visitWhereClause(it) } - val returning = ctx.returningClause()?.let { visitReturningClause(it) } - dml( - dmlOpList(delete(ctx.DELETE().getSourceMetaContainer()), metas = ctx.DELETE().getSourceMetaContainer()), - from, - where, - returning, - ctx.DELETE().getSourceMetaContainer() - ) - } - - override fun visitInsertStatementLegacy(ctx: PartiQLParser.InsertStatementLegacyContext) = PartiqlAst.build { - val metas = ctx.INSERT().getSourceMetaContainer() - val target = visitPathSimple(ctx.pathSimple()) - val index = ctx.pos?.let { visitExpr(it) } - val onConflict = ctx.onConflictLegacy()?.let { visitOnConflictLegacy(it) } - insertValue(target, visitExpr(ctx.value), index, onConflict, metas) - } - - override fun visitInsertStatement(ctx: PartiQLParser.InsertStatementContext) = PartiqlAst.build { - insert( - target = visitSymbolPrimitive(ctx.symbolPrimitive()), - asAlias = ctx.asIdent()?.let { visitAsIdent(it).name.text }, - values = visitExpr(ctx.value), - conflictAction = ctx.onConflict()?.let { visitOnConflict(it) }, - metas = ctx.INSERT().getSourceMetaContainer() - ) - } - - override fun visitReplaceCommand(ctx: PartiQLParser.ReplaceCommandContext) = PartiqlAst.build { - insert( - target = visitSymbolPrimitive(ctx.symbolPrimitive()), - asAlias = ctx.asIdent()?.let { visitAsIdent(it).name.text }, - values = visitExpr(ctx.value), - conflictAction = doReplace(excluded()), - metas = ctx.REPLACE().getSourceMetaContainer() - ) - } - - // Based on https://github.com/partiql/partiql-docs/blob/main/RFCs/0011-partiql-insert.md - override fun visitUpsertCommand(ctx: PartiQLParser.UpsertCommandContext) = PartiqlAst.build { - insert( - target = visitSymbolPrimitive(ctx.symbolPrimitive()), - asAlias = ctx.asIdent()?.let { visitAsIdent(it).name.text }, - values = visitExpr(ctx.value), - conflictAction = doUpdate(excluded()), - metas = ctx.UPSERT().getSourceMetaContainer() - ) - } - - // Based on https://github.com/partiql/partiql-docs/blob/main/RFCs/0011-partiql-insert.md - override fun visitInsertCommandReturning(ctx: PartiQLParser.InsertCommandReturningContext) = PartiqlAst.build { - val metas = ctx.INSERT().getSourceMetaContainer() - val target = visitPathSimple(ctx.pathSimple()) - val index = ctx.pos?.let { visitExpr(it) } - val onConflictLegacy = ctx.onConflictLegacy()?.let { visitOnConflictLegacy(it) } - val returning = ctx.returningClause()?.let { visitReturningClause(it) } - dml( - dmlOpList( - insertValue( - target, - visitExpr(ctx.value), - index = index, - onConflict = onConflictLegacy, - ctx.INSERT().getSourceMetaContainer() - ), - metas = metas - ), - returning = returning, - metas = metas - ) - } - - override fun visitReturningClause(ctx: PartiQLParser.ReturningClauseContext) = PartiqlAst.build { - val elements = ctx.returningColumn().map { visit(it) as PartiqlAst.ReturningElem } - returningExpr(elements, ctx.RETURNING().getSourceMetaContainer()) - } - - private fun getReturningMapping(status: Token, age: Token) = PartiqlAst.build { - when { - status.type == PartiQLParser.MODIFIED && age.type == PartiQLParser.NEW -> modifiedNew() - status.type == PartiQLParser.MODIFIED && age.type == PartiQLParser.OLD -> modifiedOld() - status.type == PartiQLParser.ALL && age.type == PartiQLParser.NEW -> allNew() - status.type == PartiQLParser.ALL && age.type == PartiQLParser.OLD -> allOld() - else -> throw status.err("Unable to get return mapping.", ErrorCode.PARSE_UNEXPECTED_TOKEN) - } - } - - override fun visitReturningColumn(ctx: PartiQLParser.ReturningColumnContext) = PartiqlAst.build { - val column = when (ctx.ASTERISK()) { - null -> returningColumn(visitExpr(ctx.expr())) - else -> returningWildcard() - } - returningElem(getReturningMapping(ctx.status, ctx.age), column) - } - - override fun visitOnConflict(ctx: PartiQLParser.OnConflictContext) = PartiqlAst.build { - visitConflictAction(ctx.conflictAction()) - } - - override fun visitOnConflictLegacy(ctx: PartiQLParser.OnConflictLegacyContext) = PartiqlAst.build { - onConflict( - expr = visitExpr(ctx.expr()), - conflictAction = doNothing(), - metas = ctx.ON().getSourceMetaContainer() - ) - } - - override fun visitConflictAction(ctx: PartiQLParser.ConflictActionContext) = PartiqlAst.build { - when { - ctx.NOTHING() != null -> doNothing() - ctx.REPLACE() != null -> visitDoReplace(ctx.doReplace()) - ctx.UPDATE() != null -> visitDoUpdate(ctx.doUpdate()) - else -> TODO("ON CONFLICT only supports `DO REPLACE` and `DO NOTHING` actions at the moment.") - } - } - - override fun visitDoReplace(ctx: PartiQLParser.DoReplaceContext) = PartiqlAst.build { - val value = when { - ctx.EXCLUDED() != null -> excluded() - else -> TODO("DO REPLACE doesn't support values other than `EXCLUDED` yet.") - } - val condition = ctx.condition?.let { visitExpr(it) } - doReplace(value, condition) - } - - override fun visitDoUpdate(ctx: PartiQLParser.DoUpdateContext) = PartiqlAst.build { - val value = when { - ctx.EXCLUDED() != null -> excluded() - else -> TODO("DO UPDATE doesn't support values other than `EXCLUDED` yet.") - } - val condition = ctx.condition?.let { visitExpr(it) } - doUpdate(value, condition) - } - - override fun visitPathSimple(ctx: PartiQLParser.PathSimpleContext) = PartiqlAst.build { - val root = visitSymbolPrimitive(ctx.symbolPrimitive()) - if (ctx.pathSimpleSteps().isEmpty()) return@build root - val steps = ctx.pathSimpleSteps().map { visit(it) as PartiqlAst.PathStep } - path(root, steps, root.metas) - } - - override fun visitPathSimpleLiteral(ctx: PartiQLParser.PathSimpleLiteralContext) = PartiqlAst.build { - pathExpr(visit(ctx.literal()) as PartiqlAst.Expr, caseSensitive()) - } - - override fun visitPathSimpleSymbol(ctx: PartiQLParser.PathSimpleSymbolContext) = PartiqlAst.build { - pathExpr(visitSymbolPrimitive(ctx.symbolPrimitive()), caseSensitive()) - } - - override fun visitPathSimpleDotSymbol(ctx: PartiQLParser.PathSimpleDotSymbolContext) = - getSymbolPathExpr(ctx.symbolPrimitive()) - - override fun visitSetCommand(ctx: PartiQLParser.SetCommandContext) = PartiqlAst.build { - val assignments = ctx.setAssignment().map { visitSetAssignment(it) } - val newSets = assignments.map { assignment -> assignment.copy(metas = ctx.SET().getSourceMetaContainer()) } - dmlOpList(newSets, ctx.SET().getSourceMetaContainer()) - } - - override fun visitSetAssignment(ctx: PartiQLParser.SetAssignmentContext) = PartiqlAst.build { - set(assignment(visitPathSimple(ctx.pathSimple()), visitExpr(ctx.expr()))) - } - - override fun visitUpdateClause(ctx: PartiQLParser.UpdateClauseContext) = - visit(ctx.tableBaseReference()) as PartiqlAst.FromSource - - /** - * - * DATA QUERY LANGUAGE (DQL) - * - */ - - override fun visitDql(ctx: PartiQLParser.DqlContext) = PartiqlAst.build { - val query = visitExpr(ctx.expr()) - query(query, query.metas) - } - - override fun visitQueryBase(ctx: PartiQLParser.QueryBaseContext) = - visit(ctx.exprSelect()) as PartiqlAst.Expr - - override fun visitSfwQuery(ctx: PartiQLParser.SfwQueryContext) = PartiqlAst.build { - val projection = visit(ctx.select) as PartiqlAst.Projection - val strategy = getSetQuantifierStrategy(ctx.select) - val exclude = ctx.exclude?.let { visitExcludeClause(it) } - val from = visitFromClause(ctx.from) - val order = ctx.order?.let { visitOrderByClause(it) } - val group = ctx.group?.let { visitGroupClause(it) } - val limit = ctx.limit?.let { visitLimitClause(it) } - val offset = ctx.offset?.let { visitOffsetByClause(it) } - val where = ctx.where?.let { visitWhereClauseSelect(it) } - val having = ctx.having?.let { visitHavingClause(it) } - val let = ctx.let?.let { visitLetClause(it) } - val metas = ctx.selectClause().getMetas() - select( - project = projection, - excludeClause = exclude, - from = from, - setq = strategy, - order = order, - group = group, - limit = limit, - offset = offset, - where = where, - having = having, - fromLet = let, - metas = metas - ) - } - - /** - * - * SELECT & PROJECTIONS - * - */ - - override fun visitSetQuantifierStrategy(ctx: PartiQLParser.SetQuantifierStrategyContext?): PartiqlAst.SetQuantifier? = - when { - ctx == null -> null - ctx.DISTINCT() != null -> PartiqlAst.SetQuantifier.Distinct() - ctx.ALL() != null -> PartiqlAst.SetQuantifier.All() - else -> null - } - - override fun visitSelectAll(ctx: PartiQLParser.SelectAllContext) = PartiqlAst.build { - projectStar(ctx.ASTERISK().getSourceMetaContainer()) - } - - override fun visitSelectItems(ctx: PartiQLParser.SelectItemsContext) = - PartiqlAst.build { - val projections = ctx.projectionItems().projectionItem().map { visitProjectionItem(it) } - projectList(projections, ctx.SELECT().getSourceMetaContainer()) - } - - override fun visitSelectPivot(ctx: PartiQLParser.SelectPivotContext) = PartiqlAst.build { - projectPivot(visitExpr(ctx.at), visitExpr(ctx.pivot)) - } - - override fun visitSelectValue(ctx: PartiQLParser.SelectValueContext) = PartiqlAst.build { - projectValue(visitExpr(ctx.expr())) - } - - override fun visitProjectionItem(ctx: PartiQLParser.ProjectionItemContext) = PartiqlAst.build { - val expr = visitExpr(ctx.expr()) - val alias = ctx.symbolPrimitive()?.let { visitSymbolPrimitive(it).name } - if (expr is PartiqlAst.Expr.Path) convertPathToProjectionItem(expr, alias) - else projectExpr_(expr, asAlias = alias, expr.metas) - } - - /** - * - * SIMPLE CLAUSES - * - */ - - override fun visitLimitClause(ctx: PartiQLParser.LimitClauseContext): PartiqlAst.Expr = - visit(ctx.arg) as PartiqlAst.Expr - - override fun visitExpr(ctx: PartiQLParser.ExprContext): PartiqlAst.Expr { - checkThreadInterrupted() - return visit(ctx.exprBagOp()) as PartiqlAst.Expr - } - - override fun visitOffsetByClause(ctx: PartiQLParser.OffsetByClauseContext) = - visit(ctx.arg) as PartiqlAst.Expr - - override fun visitWhereClause(ctx: PartiQLParser.WhereClauseContext) = visitExpr(ctx.arg) - - override fun visitWhereClauseSelect(ctx: PartiQLParser.WhereClauseSelectContext) = - visit(ctx.arg) as PartiqlAst.Expr - - override fun visitHavingClause(ctx: PartiQLParser.HavingClauseContext) = - visit(ctx.arg) as PartiqlAst.Expr - - /** - * - * LET CLAUSE - * - */ - - override fun visitLetClause(ctx: PartiQLParser.LetClauseContext) = PartiqlAst.build { - val letBindings = ctx.letBinding().map { visitLetBinding(it) } - let(letBindings) - } - - override fun visitLetBinding(ctx: PartiQLParser.LetBindingContext) = PartiqlAst.build { - val expr = visitExpr(ctx.expr()) - val metas = ctx.symbolPrimitive().getSourceMetaContainer() - letBinding_(expr, convertSymbolPrimitive(ctx.symbolPrimitive())!!, metas) - } - - /** - * EXCLUDE CLAUSE - * - */ - override fun visitExcludeClause(ctx: PartiQLParser.ExcludeClauseContext) = PartiqlAst.build { - val excludeExprs = ctx.excludeExpr().map { expr -> - visitExcludeExpr(expr) - } - excludeOp(excludeExprs) - } - - override fun visitExcludeExpr(ctx: PartiQLParser.ExcludeExprContext) = PartiqlAst.build { - val root = visitSymbolPrimitive(ctx.symbolPrimitive()).toIdentifier() - val steps = ctx.excludeExprSteps().map { visit(it) as PartiqlAst.ExcludeStep } - excludeExpr(root, steps) - } - - override fun visitExcludeExprTupleAttr(ctx: PartiQLParser.ExcludeExprTupleAttrContext) = PartiqlAst.build { - val attr = ctx.symbolPrimitive().getString() - val caseSensitivity = when (ctx.symbolPrimitive()) { - is PartiQLParser.IdentifierQuotedContext -> caseSensitive() - is PartiQLParser.IdentifierUnquotedContext -> caseInsensitive() - else -> throw ParserException("Invalid symbol reference.", ErrorCode.PARSE_INVALID_QUERY) - } - excludeTupleAttr(identifier(attr, caseSensitivity)) - } - - override fun visitExcludeExprCollectionIndex(ctx: PartiQLParser.ExcludeExprCollectionIndexContext) = - PartiqlAst.build { - val index = ctx.index.text.toInteger().toLong() - excludeCollectionIndex(index) - } - - override fun visitExcludeExprCollectionAttr(ctx: PartiQLParser.ExcludeExprCollectionAttrContext) = - PartiqlAst.build { - val attr = ctx.attr.getStringValue() - excludeTupleAttr(identifier(attr, caseSensitive())) - } - - override fun visitExcludeExprCollectionWildcard(ctx: PartiQLParser.ExcludeExprCollectionWildcardContext) = - PartiqlAst.build { - excludeCollectionWildcard() - } - - override fun visitExcludeExprTupleWildcard(ctx: PartiQLParser.ExcludeExprTupleWildcardContext) = PartiqlAst.build { - excludeTupleWildcard() - } - - /** - * - * ORDER BY CLAUSE - * - */ - - override fun visitOrderByClause(ctx: PartiQLParser.OrderByClauseContext) = PartiqlAst.build { - val sortSpecs = ctx.orderSortSpec().map { visitOrderSortSpec(it) } - val metas = ctx.ORDER().getSourceMetaContainer() - orderBy(sortSpecs, metas) - } - - override fun visitOrderSortSpec(ctx: PartiQLParser.OrderSortSpecContext) = PartiqlAst.build { - val expr = visitExpr(ctx.expr()) - val orderSpec = when { - ctx.dir == null -> null - ctx.dir.type == PartiQLParser.ASC -> asc() - ctx.dir.type == PartiQLParser.DESC -> desc() - else -> throw ctx.dir.err("Invalid query syntax", ErrorCode.PARSE_INVALID_QUERY) - } - val nullSpec = when { - ctx.nulls == null -> null - ctx.nulls.type == PartiQLParser.FIRST -> nullsFirst() - ctx.nulls.type == PartiQLParser.LAST -> nullsLast() - else -> throw ctx.dir.err("Invalid query syntax", ErrorCode.PARSE_INVALID_QUERY) - } - sortSpec(expr, orderingSpec = orderSpec, nullsSpec = nullSpec) - } - - /** - * - * GROUP BY CLAUSE - * - */ - - override fun visitGroupClause(ctx: PartiQLParser.GroupClauseContext) = PartiqlAst.build { - val strategy = if (ctx.PARTIAL() != null) groupPartial() else groupFull() - val keys = ctx.groupKey().map { visitGroupKey(it) } - val keyList = groupKeyList(keys) - val alias = ctx.groupAlias()?.let { visitGroupAlias(it).toPigSymbolPrimitive() } - groupBy_(strategy, keyList = keyList, groupAsAlias = alias, ctx.GROUP().getSourceMetaContainer()) - } - - override fun visitGroupAlias(ctx: PartiQLParser.GroupAliasContext) = visitSymbolPrimitive(ctx.symbolPrimitive()) - - /** - * Returns a GROUP BY key - * TODO: Support ordinal case. Also, the conditional defining the exception is odd. 1 + 1 is allowed, but 2 is not. - * This is to match the functionality of SqlParser, but this should likely be adjusted. - */ - override fun visitGroupKey(ctx: PartiQLParser.GroupKeyContext) = PartiqlAst.build { - val expr = visit(ctx.key) as PartiqlAst.Expr - val possibleLiteral = when (expr) { - is PartiqlAst.Expr.Pos -> expr.expr - is PartiqlAst.Expr.Neg -> expr.expr - else -> expr - } - if ( - (possibleLiteral is PartiqlAst.Expr.Lit && possibleLiteral.value != ionNull()) || - possibleLiteral is PartiqlAst.Expr.LitTime || possibleLiteral is PartiqlAst.Expr.Date - ) { - throw ctx.key.getStart().err( - "Literals (including ordinals) not supported in GROUP BY", - ErrorCode.PARSE_UNSUPPORTED_LITERALS_GROUPBY - ) - } - val alias = ctx.symbolPrimitive()?.let { visitSymbolPrimitive(it).toPigSymbolPrimitive() } - groupKey_(expr, asAlias = alias, expr.metas) - } - - /** - * - * BAG OPERATIONS - * - */ - - override fun visitBagOp(ctx: PartiQLParser.BagOpContext) = PartiqlAst.build { - val lhs = visit(ctx.lhs) as PartiqlAst.Expr - val rhs = visit(ctx.rhs) as PartiqlAst.Expr - val setq = when { - ctx.ALL() != null -> all() - ctx.DISTINCT() != null -> distinct() - else -> distinct() - } - val outer = ctx.OUTER() != null - val (op, metas) = when (ctx.op.type) { - PartiQLParser.UNION -> if (outer) { - outerUnion() to ctx.UNION().getSourceMetaContainer() - } else { - union() to ctx.UNION().getSourceMetaContainer() - } - PartiQLParser.INTERSECT -> if (outer) { - outerIntersect() to ctx.OUTER().getSourceMetaContainer() - } else { - intersect() to ctx.INTERSECT().getSourceMetaContainer() - } - PartiQLParser.EXCEPT -> if (outer) { - outerExcept() to ctx.OUTER().getSourceMetaContainer() - } else { - except() to ctx.EXCEPT().getSourceMetaContainer() - } - else -> error("Unsupported bag op token ${ctx.op}") - } - bagOp(op, setq, listOf(lhs, rhs), metas) - } - - /** - * - * GRAPH PATTERN MANIPULATION LANGUAGE (GPML) - * - */ - - override fun visitGpmlPattern(ctx: PartiQLParser.GpmlPatternContext) = PartiqlAst.build { - val selector = ctx.matchSelector()?.let { visit(it) as PartiqlAst.GraphMatchSelector } - val pattern = visitMatchPattern(ctx.matchPattern()) - gpmlPattern(selector, listOf(pattern)) - } - - override fun visitGpmlPatternList(ctx: PartiQLParser.GpmlPatternListContext) = PartiqlAst.build { - val selector = ctx.matchSelector()?.let { visit(it) as PartiqlAst.GraphMatchSelector } - val patterns = ctx.matchPattern().map { pattern -> visitMatchPattern(pattern) } - gpmlPattern(selector, patterns) - } - - override fun visitMatchPattern(ctx: PartiQLParser.MatchPatternContext) = PartiqlAst.build { - val parts = ctx.graphPart().map { visit(it) as PartiqlAst.GraphMatchPatternPart } - val restrictor = ctx.restrictor?.let { visitPatternRestrictor(it) } - val variable = ctx.variable?.let { visitPatternPathVariable(it).name } - graphMatchPattern_(parts = parts, restrictor = restrictor, variable = variable) - } - - override fun visitPatternPathVariable(ctx: PartiQLParser.PatternPathVariableContext) = - visitSymbolPrimitive(ctx.symbolPrimitive()) - - override fun visitSelectorBasic(ctx: PartiQLParser.SelectorBasicContext) = PartiqlAst.build { - val metas = ctx.mod.getSourceMetaContainer() - when (ctx.mod.type) { - PartiQLParser.ANY -> selectorAnyShortest(metas) - PartiQLParser.ALL -> selectorAllShortest(metas) - else -> throw ParserException("Unsupported match selector.", ErrorCode.PARSE_INVALID_QUERY) - } - } - - override fun visitSelectorAny(ctx: PartiQLParser.SelectorAnyContext) = PartiqlAst.build { - val metas = ctx.ANY().getSourceMetaContainer() - when (ctx.k) { - null -> selectorAny(metas) - else -> selectorAnyK(ctx.k.text.toLong(), metas) - } - } - - override fun visitSelectorShortest(ctx: PartiQLParser.SelectorShortestContext) = PartiqlAst.build { - val k = ctx.k.text.toLong() - val metas = ctx.k.getSourceMetaContainer() - when (ctx.GROUP()) { - null -> selectorShortestK(k, metas) - else -> selectorShortestKGroup(k, metas) - } - } - - override fun visitLabelSpecOr(ctx: PartiQLParser.LabelSpecOrContext): PartiqlAst.GraphLabelSpec = - PartiqlAst.build { - val lhs = visit(ctx.labelSpec()) as PartiqlAst.GraphLabelSpec - val rhs = visit(ctx.labelTerm()) as PartiqlAst.GraphLabelSpec - graphLabelDisj(lhs, rhs, ctx.VERTBAR().getSourceMetaContainer()) - } - - override fun visitLabelTermAnd(ctx: PartiQLParser.LabelTermAndContext): PartiqlAst.GraphLabelSpec = - PartiqlAst.build { - val lhs = visit(ctx.labelTerm()) as PartiqlAst.GraphLabelSpec - val rhs = visit(ctx.labelFactor()) as PartiqlAst.GraphLabelSpec - graphLabelConj(lhs, rhs, ctx.AMPERSAND().getSourceMetaContainer()) - } - - override fun visitLabelFactorNot(ctx: PartiQLParser.LabelFactorNotContext) = PartiqlAst.build { - val arg = visit(ctx.labelPrimary()) as PartiqlAst.GraphLabelSpec - graphLabelNegation(arg, ctx.BANG().getSourceMetaContainer()) - } - - override fun visitLabelPrimaryName(ctx: PartiQLParser.LabelPrimaryNameContext) = PartiqlAst.build { - val x = visitSymbolPrimitive(ctx.symbolPrimitive()) - graphLabelName_(x.name, x.metas) - } - - override fun visitLabelPrimaryWild(ctx: PartiQLParser.LabelPrimaryWildContext) = PartiqlAst.build { - graphLabelWildcard(ctx.PERCENT().getSourceMetaContainer()) - } - - override fun visitLabelPrimaryParen(ctx: PartiQLParser.LabelPrimaryParenContext) = - visit(ctx.labelSpec()) as PartiqlAst.GraphLabelSpec - - override fun visitPattern(ctx: PartiQLParser.PatternContext) = PartiqlAst.build { - val restrictor = ctx.restrictor?.let { visitPatternRestrictor(it) } - val variable = ctx.variable?.let { visitPatternPathVariable(it).name } - val prefilter = ctx.where?.let { visitWhereClause(it) } - val quantifier = ctx.quantifier?.let { visitPatternQuantifier(it) } - val parts = ctx.graphPart().map { visit(it) as PartiqlAst.GraphMatchPatternPart } - pattern( - graphMatchPattern_( - parts = parts, - variable = variable, - restrictor = restrictor, - quantifier = quantifier, - prefilter = prefilter - ) - ) - } - - override fun visitEdgeAbbreviated(ctx: PartiQLParser.EdgeAbbreviatedContext) = PartiqlAst.build { - val direction = visitEdgeAbbrev(ctx.edgeAbbrev()) - val quantifier = ctx.quantifier?.let { visitPatternQuantifier(it) } - edge(direction = direction, quantifier = quantifier) - } - - override fun visitEdgeWithSpec(ctx: PartiQLParser.EdgeWithSpecContext) = PartiqlAst.build { - val quantifier = ctx.quantifier?.let { visitPatternQuantifier(it) } - val edge = ctx.edgeWSpec()?.let { visit(it) as PartiqlAst.GraphMatchPatternPart.Edge } - edge!!.copy(quantifier = quantifier) - } - - override fun visitEdgeSpec(ctx: PartiQLParser.EdgeSpecContext) = PartiqlAst.build { - val placeholderDirection = edgeRight() - val variable = ctx.symbolPrimitive()?.let { visitSymbolPrimitive(it).name } - val prefilter = ctx.whereClause()?.let { visitWhereClause(it) } - val label = ctx.labelSpec()?.let { visit(it) as PartiqlAst.GraphLabelSpec } - edge_(direction = placeholderDirection, variable = variable, prefilter = prefilter, label = label) - } - - override fun visitEdgeSpecLeft(ctx: PartiQLParser.EdgeSpecLeftContext) = PartiqlAst.build { - val edge = visitEdgeSpec(ctx.edgeSpec()) - edge.copy(direction = edgeLeft()) - } - - override fun visitEdgeSpecRight(ctx: PartiQLParser.EdgeSpecRightContext) = PartiqlAst.build { - val edge = visitEdgeSpec(ctx.edgeSpec()) - edge.copy(direction = edgeRight()) - } - - override fun visitEdgeSpecBidirectional(ctx: PartiQLParser.EdgeSpecBidirectionalContext) = PartiqlAst.build { - val edge = visitEdgeSpec(ctx.edgeSpec()) - edge.copy(direction = edgeLeftOrRight()) - } - - override fun visitEdgeSpecUndirectedBidirectional(ctx: PartiQLParser.EdgeSpecUndirectedBidirectionalContext) = - PartiqlAst.build { - val edge = visitEdgeSpec(ctx.edgeSpec()) - edge.copy(direction = edgeLeftOrUndirectedOrRight()) - } - - override fun visitEdgeSpecUndirected(ctx: PartiQLParser.EdgeSpecUndirectedContext) = PartiqlAst.build { - val edge = visitEdgeSpec(ctx.edgeSpec()) - edge.copy(direction = edgeUndirected()) - } - - override fun visitEdgeSpecUndirectedLeft(ctx: PartiQLParser.EdgeSpecUndirectedLeftContext) = PartiqlAst.build { - val edge = visitEdgeSpec(ctx.edgeSpec()) - edge.copy(direction = edgeLeftOrUndirected()) - } - - override fun visitEdgeSpecUndirectedRight(ctx: PartiQLParser.EdgeSpecUndirectedRightContext) = PartiqlAst.build { - val edge = visitEdgeSpec(ctx.edgeSpec()) - edge.copy(direction = edgeUndirectedOrRight()) - } - - override fun visitEdgeAbbrev(ctx: PartiQLParser.EdgeAbbrevContext) = PartiqlAst.build { - when { - ctx.TILDE() != null && ctx.ANGLE_RIGHT() != null -> edgeUndirectedOrRight() - ctx.TILDE() != null && ctx.ANGLE_LEFT() != null -> edgeLeftOrUndirected() - ctx.TILDE() != null -> edgeUndirected() - ctx.MINUS() != null && ctx.ANGLE_LEFT() != null && ctx.ANGLE_RIGHT() != null -> edgeLeftOrRight() - ctx.MINUS() != null && ctx.ANGLE_LEFT() != null -> edgeLeft() - ctx.MINUS() != null && ctx.ANGLE_RIGHT() != null -> edgeRight() - ctx.MINUS() != null -> edgeLeftOrUndirectedOrRight() - else -> throw ParserException("Unsupported edge type", ErrorCode.PARSE_INVALID_QUERY) - } - } - - override fun visitPatternQuantifier(ctx: PartiQLParser.PatternQuantifierContext) = PartiqlAst.build { - when { - ctx.quant == null -> graphMatchQuantifier(ctx.lower.text.toLong(), ctx.upper?.text?.toLong()) - ctx.quant.type == PartiQLParser.PLUS -> graphMatchQuantifier(1L) - ctx.quant.type == PartiQLParser.ASTERISK -> graphMatchQuantifier(0L) - else -> throw ParserException("Unsupported quantifier", ErrorCode.PARSE_INVALID_QUERY) - } - } - - override fun visitNode(ctx: PartiQLParser.NodeContext) = PartiqlAst.build { - val variable = ctx.symbolPrimitive()?.let { visitSymbolPrimitive(it).name } - val prefilter = ctx.whereClause()?.let { visitWhereClause(it) } - val label = ctx.labelSpec()?.let { visit(it) as PartiqlAst.GraphLabelSpec } - node_(variable = variable, prefilter = prefilter, label = label) - } - - override fun visitPatternRestrictor(ctx: PartiQLParser.PatternRestrictorContext) = PartiqlAst.build { - val metas = ctx.restrictor.getSourceMetaContainer() - when (ctx.restrictor.text.lowercase()) { - "trail" -> restrictorTrail(metas) - "acyclic" -> restrictorAcyclic(metas) - "simple" -> restrictorSimple(metas) - else -> throw ParserException("Unrecognized pattern restrictor", ErrorCode.PARSE_INVALID_QUERY) - } - } - - /** - * - * TABLE REFERENCES & JOINS & FROM CLAUSE - * - */ - - override fun visitFromClause(ctx: PartiQLParser.FromClauseContext) = - visit(ctx.tableReference()) as PartiqlAst.FromSource - - override fun visitTableBaseRefClauses(ctx: PartiQLParser.TableBaseRefClausesContext) = PartiqlAst.build { - val expr = visit(ctx.source) as PartiqlAst.Expr - scan_( - expr, - asAlias = ctx.asIdent()?.let { visitAsIdent(it).toPigSymbolPrimitive() }, - atAlias = ctx.atIdent()?.let { visitAtIdent(it).toPigSymbolPrimitive() }, - byAlias = ctx.byIdent()?.let { visitByIdent(it).toPigSymbolPrimitive() }, - metas = expr.metas - ) - } - - override fun visitTableBaseRefMatch(ctx: PartiQLParser.TableBaseRefMatchContext) = PartiqlAst.build { - val expr = visit(ctx.source) as PartiqlAst.Expr - scan_( - expr, - asAlias = ctx.asIdent()?.let { visitAsIdent(it).toPigSymbolPrimitive() }, - atAlias = ctx.atIdent()?.let { visitAtIdent(it).toPigSymbolPrimitive() }, - byAlias = ctx.byIdent()?.let { visitByIdent(it).toPigSymbolPrimitive() }, - metas = expr.metas - ) - } - - override fun visitFromClauseSimpleExplicit(ctx: PartiQLParser.FromClauseSimpleExplicitContext) = PartiqlAst.build { - val expr = visitPathSimple(ctx.pathSimple()) - scan_( - expr, - asAlias = ctx.asIdent()?.let { visitAsIdent(it).toPigSymbolPrimitive() }, - atAlias = ctx.atIdent()?.let { visitAtIdent(it).toPigSymbolPrimitive() }, - byAlias = ctx.byIdent()?.let { visitByIdent(it).toPigSymbolPrimitive() }, - metas = expr.metas - ) - } - - override fun visitTableUnpivot(ctx: PartiQLParser.TableUnpivotContext) = PartiqlAst.build { - val expr = visitExpr(ctx.expr()) - val metas = ctx.UNPIVOT().getSourceMetaContainer() - unpivot_( - expr, - asAlias = ctx.asIdent()?.let { visitAsIdent(it).toPigSymbolPrimitive() }, - atAlias = ctx.atIdent()?.let { visitAtIdent(it).toPigSymbolPrimitive() }, - byAlias = ctx.byIdent()?.let { visitByIdent(it).toPigSymbolPrimitive() }, - metas - ) - } - - override fun visitTableCrossJoin(ctx: PartiQLParser.TableCrossJoinContext) = PartiqlAst.build { - val lhs = visit(ctx.lhs) as PartiqlAst.FromSource - val joinType = visitJoinType(ctx.joinType()) - val rhs = visit(ctx.rhs) as PartiqlAst.FromSource - val metas = metaContainerOf(IsImplictJoinMeta.instance) + joinType.metas - join(joinType, lhs, rhs, metas = metas) - } - - override fun visitTableQualifiedJoin(ctx: PartiQLParser.TableQualifiedJoinContext) = PartiqlAst.build { - val lhs = visit(ctx.lhs) as PartiqlAst.FromSource - val joinType = visitJoinType(ctx.joinType()) - val rhs = visit(ctx.rhs) as PartiqlAst.FromSource - val condition = ctx.joinSpec()?.let { visitJoinSpec(it) } - join(joinType, lhs, rhs, condition, metas = joinType.metas) - } - - override fun visitTableBaseRefSymbol(ctx: PartiQLParser.TableBaseRefSymbolContext) = PartiqlAst.build { - val expr = visit(ctx.source) as PartiqlAst.Expr - val name = ctx.symbolPrimitive()?.let { visitSymbolPrimitive(it).toPigSymbolPrimitive() } - scan_(expr, name, metas = expr.metas) - } - - override fun visitFromClauseSimpleImplicit(ctx: PartiQLParser.FromClauseSimpleImplicitContext) = PartiqlAst.build { - val path = visitPathSimple(ctx.pathSimple()) - val name = ctx.symbolPrimitive()?.let { visitSymbolPrimitive(it).name } - scan_(path, name, metas = path.metas) - } - - override fun visitTableWrapped(ctx: PartiQLParser.TableWrappedContext): PartiqlAst.PartiqlAstNode = - visit(ctx.tableReference()) - - override fun visitJoinSpec(ctx: PartiQLParser.JoinSpecContext) = visitExpr(ctx.expr()) - - override fun visitJoinType(ctx: PartiQLParser.JoinTypeContext?) = PartiqlAst.build { - if (ctx == null) return@build inner() - val metas = ctx.mod.getSourceMetaContainer() - when (ctx.mod.type) { - PartiQLParser.LEFT -> left(metas) - PartiQLParser.RIGHT -> right(metas) - PartiQLParser.INNER -> inner(metas) - PartiQLParser.FULL -> full(metas) - PartiQLParser.OUTER -> full(metas) - else -> inner(metas) - } - } - - override fun visitJoinRhsTableJoined(ctx: PartiQLParser.JoinRhsTableJoinedContext) = - visit(ctx.tableReference()) as PartiqlAst.FromSource - - /** - * SIMPLE EXPRESSIONS - */ - - override fun visitOr(ctx: PartiQLParser.OrContext) = visitBinaryOperation(ctx.lhs, ctx.rhs, listOf(ctx.OR().symbol), null) - - override fun visitAnd(ctx: PartiQLParser.AndContext) = visitBinaryOperation(ctx.lhs, ctx.rhs, listOf(ctx.op), null) - - override fun visitNot(ctx: PartiQLParser.NotContext) = visitUnaryOperation(ctx.rhs, ctx.op, null) - - private fun emptyListIfNull(ctx: ParserRuleContext?) = if (ctx == null) { - emptyList() - } else { - listOf(ctx.start) - } - - override fun visitMathOp00(ctx: PartiQLParser.MathOp00Context): PartiqlAst.PartiqlAstNode = - visitBinaryOperation(ctx.lhs, ctx.rhs, emptyListIfNull(ctx.op), ctx.parent) - - override fun visitMathOp01(ctx: PartiQLParser.MathOp01Context) = - visitUnaryOperation(ctx.rhs, ctx.op?.start, ctx.parent) - - override fun visitMathOp02(ctx: PartiQLParser.MathOp02Context): PartiqlAst.PartiqlAstNode = - visitBinaryOperation(ctx.lhs, ctx.rhs, listOf(ctx.op), ctx.parent) - - override fun visitMathOp03(ctx: PartiQLParser.MathOp03Context): PartiqlAst.PartiqlAstNode = - visitBinaryOperation(ctx.lhs, ctx.rhs, listOf(ctx.op), ctx.parent) - - override fun visitValueExpr(ctx: PartiQLParser.ValueExprContext) = - visitUnaryOperation(ctx.rhs, ctx.sign, ctx.parent) - - /** - * - * PREDICATES - * - */ - - override fun visitPredicateComparison(ctx: PartiQLParser.PredicateComparisonContext) = - visitBinaryOperation(ctx.lhs, ctx.rhs, ctx.op.children.map { (it as TerminalNode).symbol }) - - /** - * Note: This predicate can take a wrapped expression on the RHS, and it will wrap it in a LIST. However, if the - * expression is a SELECT or VALUES expression, it will NOT wrap it in a list. This is per SqlParser. - */ - override fun visitPredicateIn(ctx: PartiQLParser.PredicateInContext) = PartiqlAst.build { - // Wrap Expression with LIST unless SELECT / VALUES - val rhs = if (ctx.expr() != null) { - val possibleRhs = visitExpr(ctx.expr()) - if (possibleRhs is PartiqlAst.Expr.Select || possibleRhs.metas.containsKey(IsValuesExprMeta.TAG)) - possibleRhs - else list(possibleRhs, metas = possibleRhs.metas + metaContainerOf(IsListParenthesizedMeta)) - } else { - visit(ctx.rhs) as PartiqlAst.Expr - } - val lhs = visit(ctx.lhs) as PartiqlAst.Expr - val args = listOf(lhs, rhs) - val inCollection = inCollection(args, ctx.IN().getSourceMetaContainer()) - if (ctx.NOT() == null) return@build inCollection - not(inCollection, ctx.NOT().getSourceMetaContainer() + metaContainerOf(LegacyLogicalNotMeta.instance)) - } - - override fun visitPredicateIs(ctx: PartiQLParser.PredicateIsContext) = PartiqlAst.build { - val lhs = visit(ctx.lhs) as PartiqlAst.Expr - val rhs = visit(ctx.type()) as PartiqlAst.Type - val isType = isType(lhs, rhs, ctx.IS().getSourceMetaContainer()) - if (ctx.NOT() == null) return@build isType - not(isType, ctx.NOT().getSourceMetaContainer() + metaContainerOf(LegacyLogicalNotMeta.instance)) - } - - override fun visitPredicateBetween(ctx: PartiQLParser.PredicateBetweenContext) = PartiqlAst.build { - val args = listOf(ctx.lhs, ctx.lower, ctx.upper).map { visit(it) as PartiqlAst.Expr } - val between = between(args[0], args[1], args[2], ctx.BETWEEN().getSourceMetaContainer()) - if (ctx.NOT() == null) return@build between - not(between, ctx.NOT().getSourceMetaContainer() + metaContainerOf(LegacyLogicalNotMeta.instance)) - } - - override fun visitPredicateLike(ctx: PartiQLParser.PredicateLikeContext) = PartiqlAst.build { - val args = listOf(ctx.lhs, ctx.rhs).map { visit(it) as PartiqlAst.Expr } - val escape = ctx.escape?.let { visitExpr(it) } - val like = like(args[0], args[1], escape, ctx.LIKE().getSourceMetaContainer()) - if (ctx.NOT() == null) return@build like - not(like, metas = ctx.NOT().getSourceMetaContainer() + metaContainerOf(LegacyLogicalNotMeta.instance)) - } - - /** - * - * PRIMARY EXPRESSIONS - * - */ - - override fun visitExprTermWrappedQuery(ctx: PartiQLParser.ExprTermWrappedQueryContext) = - visitExpr(ctx.expr()) - - override fun visitVariableIdentifier(ctx: PartiQLParser.VariableIdentifierContext): PartiqlAst.PartiqlAstNode = - PartiqlAst.build { - val metas = ctx.ident.getSourceMetaContainer() - val qualifier = if (ctx.qualifier == null) unqualified() else localsFirst() - val sensitivity = if (ctx.ident.type == PartiQLParser.IDENTIFIER) caseInsensitive() else caseSensitive() - id(ctx.ident.getStringValue(), sensitivity, qualifier, metas) - } - - override fun visitVariableKeyword(ctx: PartiQLParser.VariableKeywordContext): PartiqlAst.PartiqlAstNode = - PartiqlAst.build { - val keyword = ctx.nonReserved().start.text - val metas = ctx.start.getSourceMetaContainer() - val qualifier = ctx.qualifier?.let { localsFirst() } ?: unqualified() - id(keyword, caseInsensitive(), qualifier, metas) - } - - override fun visitParameter(ctx: PartiQLParser.ParameterContext) = PartiqlAst.build { - val parameterIndex = parameterIndexes[ctx.QUESTION_MARK().symbol.tokenIndex] - ?: throw ParserException("Unable to find index of parameter.", ErrorCode.PARSE_INVALID_QUERY) - parameter(parameterIndex.toLong(), ctx.QUESTION_MARK().getSourceMetaContainer()) - } - - override fun visitSequenceConstructor(ctx: PartiQLParser.SequenceConstructorContext) = PartiqlAst.build { - val expressions = ctx.expr().map { visitExpr(it) } - val metas = ctx.datatype.getSourceMetaContainer() - when (ctx.datatype.type) { - PartiQLParser.LIST -> list(expressions, metas) - PartiQLParser.SEXP -> sexp(expressions, metas) - else -> throw ParserException("Unknown sequence", ErrorCode.PARSE_INVALID_QUERY) - } - } - - override fun visitExprPrimaryPath(ctx: PartiQLParser.ExprPrimaryPathContext) = PartiqlAst.build { - val base = visit(ctx.exprPrimary()) as PartiqlAst.Expr - val steps = ctx.pathStep().map { step -> visit(step) as PartiqlAst.PathStep } - path(base, steps, base.metas) - } - - override fun visitPathStepIndexExpr(ctx: PartiQLParser.PathStepIndexExprContext) = PartiqlAst.build { - val expr = visitExpr(ctx.key) - val metas = expr.metas + metaContainerOf(IsPathIndexMeta.instance) - pathExpr(expr, PartiqlAst.CaseSensitivity.CaseSensitive(), metas) - } - - override fun visitPathStepDotExpr(ctx: PartiQLParser.PathStepDotExprContext) = getSymbolPathExpr(ctx.key) - - override fun visitPathStepIndexAll(ctx: PartiQLParser.PathStepIndexAllContext) = PartiqlAst.build { - pathWildcard(metas = ctx.ASTERISK().getSourceMetaContainer()) - } - - override fun visitPathStepDotAll(ctx: PartiQLParser.PathStepDotAllContext) = PartiqlAst.build { - pathUnpivot() - } - - override fun visitExprGraphMatchMany(ctx: PartiQLParser.ExprGraphMatchManyContext) = PartiqlAst.build { - val graph = visit(ctx.exprPrimary()) as PartiqlAst.Expr - val gpmlPattern = visitGpmlPatternList(ctx.gpmlPatternList()) - graphMatch(graph, gpmlPattern, graph.metas) - } - - override fun visitExprGraphMatchOne(ctx: PartiQLParser.ExprGraphMatchOneContext) = PartiqlAst.build { - val graph = visit(ctx.exprPrimary()) as PartiqlAst.Expr - val gpmlPattern = visitGpmlPattern(ctx.gpmlPattern()) - graphMatch(graph, gpmlPattern, graph.metas) - } - - override fun visitValues(ctx: PartiQLParser.ValuesContext) = PartiqlAst.build { - val rows = ctx.valueRow().map { visitValueRow(it) } - bag(rows, ctx.VALUES().getSourceMetaContainer() + metaContainerOf(IsValuesExprMeta.instance)) - } - - override fun visitValueRow(ctx: PartiQLParser.ValueRowContext) = PartiqlAst.build { - val expressions = ctx.expr().map { visitExpr(it) } - list(expressions, metas = ctx.PAREN_LEFT().getSourceMetaContainer() + metaContainerOf(IsListParenthesizedMeta)) - } - - override fun visitValueList(ctx: PartiQLParser.ValueListContext) = PartiqlAst.build { - val expressions = ctx.expr().map { visitExpr(it) } - list(expressions, metas = ctx.PAREN_LEFT().getSourceMetaContainer() + metaContainerOf(IsListParenthesizedMeta)) - } - - /** - * - * FUNCTIONS - * - */ - - override fun visitNullIf(ctx: PartiQLParser.NullIfContext) = PartiqlAst.build { - val lhs = visitExpr(ctx.expr(0)) - val rhs = visitExpr(ctx.expr(1)) - val metas = ctx.NULLIF().getSourceMetaContainer() - nullIf(lhs, rhs, metas) - } - - override fun visitCoalesce(ctx: PartiQLParser.CoalesceContext) = PartiqlAst.build { - val expressions = ctx.expr().map { visitExpr(it) } - val metas = ctx.COALESCE().getSourceMetaContainer() - coalesce(expressions, metas) - } - - override fun visitCaseExpr(ctx: PartiQLParser.CaseExprContext) = PartiqlAst.build { - val pairs = ctx.whens.indices.map { i -> - exprPair(visitExpr(ctx.whens[i]), visitExpr(ctx.thens[i])) - } - val elseExpr = ctx.else_?.let { visitExpr(it) } - val caseMeta = ctx.CASE().getSourceMetaContainer() - when (ctx.case_) { - null -> searchedCase(exprPairList(pairs), elseExpr, metas = caseMeta) - else -> simpleCase(visitExpr(ctx.case_), exprPairList(pairs), elseExpr, metas = caseMeta) - } - } - - override fun visitCast(ctx: PartiQLParser.CastContext) = PartiqlAst.build { - val expr = visitExpr(ctx.expr()) - val type = visit(ctx.type()) as PartiqlAst.Type - val metas = ctx.CAST().getSourceMetaContainer() - cast(expr, type, metas) - } - - override fun visitCanCast(ctx: PartiQLParser.CanCastContext) = PartiqlAst.build { - val expr = visitExpr(ctx.expr()) - val type = visit(ctx.type()) as PartiqlAst.Type - val metas = ctx.CAN_CAST().getSourceMetaContainer() - canCast(expr, type, metas) - } - - override fun visitCanLosslessCast(ctx: PartiQLParser.CanLosslessCastContext) = PartiqlAst.build { - val expr = visitExpr(ctx.expr()) - val type = visit(ctx.type()) as PartiqlAst.Type - val metas = ctx.CAN_LOSSLESS_CAST().getSourceMetaContainer() - canLosslessCast(expr, type, metas) - } - - override fun visitFunctionCall(ctx: PartiQLParser.FunctionCallContext): PartiqlAst.Expr = PartiqlAst.build { - val nameCtx = ctx.qualifiedName() - val name = if (nameCtx.qualifier.isNotEmpty()) { - error("Legacy AST does not support qualified function names") - } else { - nameCtx.name.getString().lowercase() - } - // COUNT(*) turns into COUNT(1) in legacy PIG AST - if (ctx.ASTERISK() != null) { - if (name == "count") { - return@build callAgg( - all(), - name, - lit(ionInt(1)), - nameCtx.name.getSourceMetaContainer() + metaContainerOf(IsCountStarMeta.instance) - ) - } else { - // Do not permit wildcard for other functions; matches existing behavior - error("Given wildcard as argument for non-COUNT function") - } - } - if (!name.isAggregateCall()) { - // Scalar fn call - val args = ctx.expr().map { visitExpr(it) } - val metas = ctx.start.getSourceMetaContainer() - return@build call(name, args = args, metas = metas) - } - // Aggregate fn call - val strategy = getStrategy(ctx.setQuantifierStrategy(), default = all()) - val args = ctx.expr() - if (args.size != 1) { - error("expect only one argument to aggregate function call") - } - val arg = visitExpr(args.first()) - val metas = nameCtx.name.getSourceMetaContainer() - return@build callAgg(strategy, name, arg, metas) - } - - private fun String.isAggregateCall(): Boolean { - // keep legacy PIG parser behavior + PIG AST the same as before - // since it is legacy, we will keep this hard-coded aggregation logic - return listOf("count", "avg", "sum", "min", "max", "any", "some", "every").contains(this) - } - - override fun visitDateFunction(ctx: PartiQLParser.DateFunctionContext) = PartiqlAst.build { - if (DateTimePart.safeValueOf(ctx.dt.text) == null) { - throw ctx.dt.err("Expected one of: ${DateTimePart.values()}", ErrorCode.PARSE_EXPECTED_DATE_TIME_PART) - } - val datetimePart = lit(ionSymbol(ctx.dt.text)) - val secondaryArgs = ctx.expr().map { visitExpr(it) } - val args = listOf(datetimePart) + secondaryArgs - val metas = ctx.func.getSourceMetaContainer() - call(ctx.func.text.lowercase(), args, metas) - } - - override fun visitSubstring(ctx: PartiQLParser.SubstringContext) = PartiqlAst.build { - val args = ctx.expr().map { visitExpr(it) } - val metas = ctx.SUBSTRING().getSourceMetaContainer() - call(ctx.SUBSTRING().text.lowercase(), args, metas) - } - - override fun visitPosition(ctx: PartiQLParser.PositionContext) = PartiqlAst.build { - val args = ctx.expr().map { visitExpr(it) } - val metas = ctx.POSITION().getSourceMetaContainer() - call(ctx.POSITION().text.lowercase(), args, metas) - } - - override fun visitOverlay(ctx: PartiQLParser.OverlayContext) = PartiqlAst.build { - val args = ctx.expr().map { visitExpr(it) } - val metas = ctx.OVERLAY().getSourceMetaContainer() - call(ctx.OVERLAY().text.lowercase(), args, metas) - } - - override fun visitExtract(ctx: PartiQLParser.ExtractContext) = PartiqlAst.build { - if (DateTimePart.safeValueOf(ctx.IDENTIFIER().text) == null) { - throw ctx.IDENTIFIER() - .err("Expected one of: ${DateTimePart.values()}", ErrorCode.PARSE_EXPECTED_DATE_TIME_PART) - } - val datetimePart = lit(ionSymbol(ctx.IDENTIFIER().text)) - val timeExpr = visitExpr(ctx.rhs) - val args = listOf(datetimePart, timeExpr) - val metas = ctx.EXTRACT().getSourceMetaContainer() - call(ctx.EXTRACT().text.lowercase(), args, metas) - } - - /** - * Note: This implementation is odd because the TRIM function contains keywords that are not keywords outside - * of TRIM. Therefore, TRIM( FROM ) needs to be parsed as below. The needs to be - * an identifier (according to SqlParser), but if the identifier is NOT a trim specification, and the is - * null, we need to make the substring equal to the (and make null). - */ - override fun visitTrimFunction(ctx: PartiQLParser.TrimFunctionContext) = PartiqlAst.build { - val possibleModText = if (ctx.mod != null) ctx.mod.text.lowercase() else null - val isTrimSpec = TRIM_SPECIFICATION_KEYWORDS.contains(possibleModText) - val (modifier, substring) = when { - // if is not null and is null - // then there are two possible cases trim(( BOTH | LEADING | TRAILING ) FROM ) - // or trim( FROM target), i.e., we treat what is recognized by parser as the modifier as - ctx.mod != null && ctx.sub == null -> { - if (isTrimSpec) ctx.mod.toSymbol() to null - else null to id(possibleModText!!, caseInsensitive(), unqualified(), ctx.mod.getSourceMetaContainer()) - } - - ctx.mod == null && ctx.sub != null -> { - null to visitExpr(ctx.sub) - } - - ctx.mod != null && ctx.sub != null -> { - if (isTrimSpec) ctx.mod.toSymbol() to visitExpr(ctx.sub) - // todo we need to decide if it should be an evaluator error or a parser error - else { - val errorContext = PropertyValueMap() - errorContext[Property.TOKEN_STRING] = ctx.mod.text - throw ctx.mod.err( - "'${ctx.mod.text}' is an unknown trim specification, valid values: $TRIM_SPECIFICATION_KEYWORDS", - ErrorCode.PARSE_INVALID_TRIM_SPEC, - errorContext - ) - } - } - - else -> null to null - } - - val target = visitExpr(ctx.target) - val args = listOfNotNull(modifier, substring, target) - val metas = ctx.func.getSourceMetaContainer() - call(ctx.func.text.lowercase(), args, metas) - } - - /** - * - * Window Functions - * TODO: Remove from experimental once https://github.com/partiql/partiql-docs/issues/31 is resolved and a RFC is approved - * - */ - - override fun visitLagLeadFunction(ctx: PartiQLParser.LagLeadFunctionContext) = PartiqlAst.build { - val args = ctx.expr().map { visitExpr(it) } - val over = visitOver(ctx.over()) - // LAG and LEAD will require a Window ORDER BY - if (over.orderBy == null) { - val errorContext = PropertyValueMap() - errorContext[Property.TOKEN_STRING] = ctx.func.text.lowercase() - throw ctx.func.err( - "${ctx.func.text} requires Window ORDER BY", - ErrorCode.PARSE_EXPECTED_WINDOW_ORDER_BY, - errorContext - ) - } - val metas = ctx.func.getSourceMetaContainer() - callWindow(ctx.func.text.lowercase(), over, args, metas) - } - - override fun visitOver(ctx: PartiQLParser.OverContext) = PartiqlAst.build { - val windowPartitionList = - if (ctx.windowPartitionList() != null) visitWindowPartitionList(ctx.windowPartitionList()) else null - val windowSortSpecList = - if (ctx.windowSortSpecList() != null) visitWindowSortSpecList(ctx.windowSortSpecList()) else null - val metas = ctx.OVER().getSourceMetaContainer() - over(windowPartitionList, windowSortSpecList, metas) - } - - override fun visitWindowPartitionList(ctx: PartiQLParser.WindowPartitionListContext) = PartiqlAst.build { - val args = ctx.expr().map { visitExpr(it) } - val metas = ctx.PARTITION().getSourceMetaContainer() - windowPartitionList(args, metas) - } - - override fun visitWindowSortSpecList(ctx: PartiQLParser.WindowSortSpecListContext) = PartiqlAst.build { - val sortSpecList = ctx.orderSortSpec().map { visitOrderSortSpec(it) } - val metas = ctx.ORDER().getSourceMetaContainer() - windowSortSpecList(sortSpecList, metas) - } - - /** - * - * LITERALS - * - */ - - override fun visitBag(ctx: PartiQLParser.BagContext) = PartiqlAst.build { - // Prohibit hidden characters between angle brackets - val startTokenIndex = ctx.start.tokenIndex - val endTokenIndex = ctx.stop.tokenIndex - if (tokens.getHiddenTokensToRight(startTokenIndex, PartiQLTokens.HIDDEN) != null || tokens.getHiddenTokensToLeft(endTokenIndex, PartiQLTokens.HIDDEN) != null) { - throw ParserException("Invalid bag expression", ErrorCode.PARSE_INVALID_QUERY) - } - val exprList = ctx.expr().map { visitExpr(it) } - bag(exprList, ctx.ANGLE_LEFT(0).getSourceMetaContainer()) - } - - override fun visitLiteralDecimal(ctx: PartiQLParser.LiteralDecimalContext) = PartiqlAst.build { - val decimal = try { - ionDecimal(Decimal.valueOf(bigDecimalOf(ctx.LITERAL_DECIMAL().text))) - } catch (e: NumberFormatException) { - val errorContext = PropertyValueMap() - errorContext[Property.TOKEN_STRING] = ctx.LITERAL_DECIMAL().text - throw ctx.LITERAL_DECIMAL().err("Invalid decimal literal", ErrorCode.LEXER_INVALID_LITERAL, errorContext) - } - lit( - decimal, - ctx.LITERAL_DECIMAL().getSourceMetaContainer() - ) - } - - override fun visitArray(ctx: PartiQLParser.ArrayContext) = PartiqlAst.build { - val metas = ctx.BRACKET_LEFT().getSourceMetaContainer() - list(ctx.expr().map { visitExpr(it) }, metas) - } - - override fun visitLiteralNull(ctx: PartiQLParser.LiteralNullContext) = PartiqlAst.build { - lit(ionNull(), ctx.NULL().getSourceMetaContainer()) - } - - override fun visitLiteralMissing(ctx: PartiQLParser.LiteralMissingContext) = PartiqlAst.build { - missing(ctx.MISSING().getSourceMetaContainer()) - } - - override fun visitLiteralTrue(ctx: PartiQLParser.LiteralTrueContext) = PartiqlAst.build { - lit(ionBool(true), ctx.TRUE().getSourceMetaContainer()) - } - - override fun visitLiteralFalse(ctx: PartiQLParser.LiteralFalseContext) = PartiqlAst.build { - lit(ionBool(false), ctx.FALSE().getSourceMetaContainer()) - } - - override fun visitLiteralIon(ctx: PartiQLParser.LiteralIonContext) = PartiqlAst.build { - val ionValue = try { - loadSingleElement(ctx.ION_CLOSURE().getStringValue()) - } catch (e: IonElementException) { - throw ParserException("Unable to parse Ion value.", ErrorCode.PARSE_UNEXPECTED_TOKEN, cause = e) - } - lit( - ionValue, - ctx.ION_CLOSURE().getSourceMetaContainer() + metaContainerOf(IsIonLiteralMeta.instance) - ) - } - - override fun visitLiteralString(ctx: PartiQLParser.LiteralStringContext) = PartiqlAst.build { - lit(ionString(ctx.LITERAL_STRING().getStringValue()), ctx.LITERAL_STRING().getSourceMetaContainer()) - } - - override fun visitLiteralInteger(ctx: PartiQLParser.LiteralIntegerContext): PartiqlAst.Expr.Lit = PartiqlAst.build { - lit(parseToIntElement(ctx.LITERAL_INTEGER().text), ctx.LITERAL_INTEGER().getSourceMetaContainer()) - } - - override fun visitLiteralDate(ctx: PartiQLParser.LiteralDateContext) = PartiqlAst.build { - val dateString = ctx.LITERAL_STRING().getStringValue() - if (DATE_PATTERN_REGEX.matches(dateString).not()) { - throw ctx.LITERAL_STRING() - .err("Expected DATE string to be of the format yyyy-MM-dd", ErrorCode.PARSE_INVALID_DATE_STRING) - } - try { - LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE) - val (year, month, day) = dateString.split("-") - date(year.toLong(), month.toLong(), day.toLong(), ctx.DATE().getSourceMetaContainer()) - } catch (e: DateTimeParseException) { - throw ctx.LITERAL_STRING().err(e.localizedMessage, ErrorCode.PARSE_INVALID_DATE_STRING, cause = e) - } catch (e: IndexOutOfBoundsException) { - throw ctx.LITERAL_STRING().err(e.localizedMessage, ErrorCode.PARSE_INVALID_DATE_STRING, cause = e) - } - } - - override fun visitLiteralTime(ctx: PartiQLParser.LiteralTimeContext) = PartiqlAst.build { - val (timeString, precision) = getTimeStringAndPrecision(ctx.LITERAL_STRING(), ctx.LITERAL_INTEGER()) - when (ctx.WITH()) { - null -> getLocalTime(timeString, false, precision, ctx.LITERAL_STRING(), ctx.TIME(0)) - else -> getOffsetTime(timeString, precision, ctx.LITERAL_STRING(), ctx.TIME(0)) - } - } - - override fun visitLiteralTimestamp(ctx: PartiQLParser.LiteralTimestampContext): PartiqlAst.PartiqlAstNode { - val (timestamp, precision) = getTimestampStringAndPrecision(ctx.LITERAL_STRING(), ctx.LITERAL_INTEGER()) - return when (ctx.WITH()) { - null -> getTimestampDynamic(timestamp, precision, ctx.LITERAL_STRING()) - else -> getTimestampWithTimezone(timestamp, precision, ctx.LITERAL_STRING()) - } - } - - override fun visitTuple(ctx: PartiQLParser.TupleContext) = PartiqlAst.build { - val pairs = ctx.pair().map { visitPair(it) } - val metas = ctx.BRACE_LEFT().getSourceMetaContainer() - struct(pairs, metas) - } - - override fun visitPair(ctx: PartiQLParser.PairContext) = PartiqlAst.build { - val lhs = visitExpr(ctx.lhs) - val rhs = visitExpr(ctx.rhs) - exprPair(lhs, rhs) - } - - /** - * - * TYPES - * - */ - - override fun visitTypeAtomic(ctx: PartiQLParser.TypeAtomicContext) = PartiqlAst.build { - val metas = ctx.datatype.getSourceMetaContainer() - when (ctx.datatype.type) { - PartiQLParser.NULL -> nullType(metas) - PartiQLParser.BOOL -> booleanType(metas) - PartiQLParser.BOOLEAN -> booleanType(metas) - PartiQLParser.SMALLINT -> smallintType(metas) - PartiQLParser.INT2 -> smallintType(metas) - PartiQLParser.INTEGER2 -> smallintType(metas) - PartiQLParser.INT -> integerType(metas) - PartiQLParser.INTEGER -> integerType(metas) - PartiQLParser.INT4 -> integer4Type(metas) - PartiQLParser.INTEGER4 -> integer4Type(metas) - PartiQLParser.INT8 -> integer8Type(metas) - PartiQLParser.INTEGER8 -> integer8Type(metas) - PartiQLParser.BIGINT -> integer8Type(metas) - PartiQLParser.REAL -> realType(metas) - PartiQLParser.DOUBLE -> doublePrecisionType(metas) - PartiQLParser.CHAR -> characterType(metas = metas) - PartiQLParser.CHARACTER -> characterType(metas = metas) - PartiQLParser.MISSING -> missingType(metas) - PartiQLParser.STRING -> stringType(metas) - PartiQLParser.SYMBOL -> symbolType(metas) - PartiQLParser.BLOB -> blobType(metas) - PartiQLParser.CLOB -> clobType(metas) - PartiQLParser.DATE -> dateType(metas) - PartiQLParser.STRUCT -> structType(metas) - PartiQLParser.TUPLE -> tupleType(metas) - PartiQLParser.LIST -> listType(metas) - PartiQLParser.BAG -> bagType(metas) - PartiQLParser.SEXP -> sexpType(metas) - PartiQLParser.ANY -> anyType(metas) - else -> throw ParserException("Unsupported type.", ErrorCode.PARSE_INVALID_QUERY) - } - } - - override fun visitTypeVarChar(ctx: PartiQLParser.TypeVarCharContext) = PartiqlAst.build { - val arg0 = if (ctx.arg0 != null) parseToIntElement(ctx.arg0.text) else null - val metas = ctx.CHARACTER().getSourceMetaContainer() - assertIntegerElement(ctx.arg0, arg0) - characterVaryingType(arg0?.longValue, metas) - } - - override fun visitTypeArgSingle(ctx: PartiQLParser.TypeArgSingleContext) = PartiqlAst.build { - val arg0 = if (ctx.arg0 != null) parseToIntElement(ctx.arg0.text) else null - assertIntegerElement(ctx.arg0, arg0) - val metas = ctx.datatype.getSourceMetaContainer() - when (ctx.datatype.type) { - PartiQLParser.FLOAT -> floatType(arg0?.longValue, metas) - PartiQLParser.CHAR, PartiQLParser.CHARACTER -> characterType(arg0?.longValue, metas) - PartiQLParser.VARCHAR -> characterVaryingType(arg0?.longValue, metas) - else -> throw ParserException("Unknown datatype", ErrorCode.PARSE_UNEXPECTED_TOKEN, PropertyValueMap()) - } - } - - override fun visitTypeArgDouble(ctx: PartiQLParser.TypeArgDoubleContext) = PartiqlAst.build { - val arg0 = if (ctx.arg0 != null) parseToIntElement(ctx.arg0.text) else null - val arg1 = if (ctx.arg1 != null) parseToIntElement(ctx.arg1.text) else null - assertIntegerElement(ctx.arg0, arg0) - assertIntegerElement(ctx.arg1, arg1) - val metas = ctx.datatype.getSourceMetaContainer() - when (ctx.datatype.type) { - PartiQLParser.DECIMAL, PartiQLParser.DEC -> decimalType(arg0?.longValue, arg1?.longValue, metas) - PartiQLParser.NUMERIC -> numericType(arg0?.longValue, arg1?.longValue, metas) - else -> throw ParserException("Unknown datatype", ErrorCode.PARSE_UNEXPECTED_TOKEN, PropertyValueMap()) - } - } - - override fun visitTypeTimeZone(ctx: PartiQLParser.TypeTimeZoneContext) = PartiqlAst.build { - val precision = if (ctx.precision != null) ctx.precision.text.toInteger().toLong() else null - if (precision != null && (precision < 0 || precision > MAX_PRECISION_FOR_TIME)) { - throw ctx.precision.err("Unsupported precision", ErrorCode.PARSE_INVALID_PRECISION_FOR_TIME) - } - val hasTimeZone = ctx.WITH() != null - when (ctx.datatype.type) { - PartiQLParser.TIME -> if (hasTimeZone) timeWithTimeZoneType(precision) else timeType(precision) - PartiQLParser.TIMESTAMP -> if (hasTimeZone) timestampWithTimeZoneType(precision) else timestampType( - precision - ) - - else -> throw ParserException("Unknown datatype", ErrorCode.PARSE_UNEXPECTED_TOKEN, PropertyValueMap()) - } - } - - override fun visitTypeCustom(ctx: PartiQLParser.TypeCustomContext) = PartiqlAst.build { - val metas = ctx.symbolPrimitive().getSourceMetaContainer() - val customName: String = when (val name = ctx.symbolPrimitive().getString().lowercase()) { - in customKeywords -> name - in customTypeAliases.keys -> customTypeAliases.getOrDefault(name, name) - else -> throw ParserException("Invalid custom type name: $name", ErrorCode.PARSE_INVALID_QUERY) - } - customType_(SymbolPrimitive(customName, metas), metas) - } - - /** - * - * HELPER METHODS - * - */ - - private fun TerminalNode?.getSourceMetaContainer(): MetaContainer { - if (this == null) return emptyMetaContainer() - val metas = this.getSourceMetas() - return com.amazon.ionelement.api.metaContainerOf(Pair(metas.tag, metas)) - } - - private fun Token?.getSourceMetaContainer(): MetaContainer { - if (this == null) return emptyMetaContainer() - val metas = this.getSourceMetas() - return com.amazon.ionelement.api.metaContainerOf(Pair(metas.tag, metas)) - } - - private fun List.getSourceMetaContainer(): MetaContainer { - val base = this.firstOrNull() ?: return emptyMetaContainer() - val length = this.fold(0) { acc, token -> - acc + token.stopIndex - token.startIndex + 1 - } - return metaContainerOf(SourceLocationMeta(base.line.toLong(), base.charPositionInLine.toLong() + 1, length.toLong())) - } - - private fun TerminalNode.getSourceMetas(): SourceLocationMeta = this.symbol.getSourceMetas() - - private fun Token.getSourceMetas(): SourceLocationMeta { - val length = this.stopIndex - this.startIndex + 1 - return SourceLocationMeta(this.line.toLong(), this.charPositionInLine.toLong() + 1, length.toLong()) - } - - private fun visitBinaryOperation( - lhs: ParserRuleContext?, - rhs: ParserRuleContext?, - op: List, - parent: ParserRuleContext? = null, - ) = PartiqlAst.build { - if (parent != null) return@build visit(parent) as PartiqlAst.Expr - val args = listOf(lhs!!, rhs!!).map { visit(it) as PartiqlAst.Expr } - val metas = op.getSourceMetaContainer() - val start = op.first().tokenIndex - val stop = op.last().tokenIndex - val tokensInRange = tokens.get(start, stop) - if (tokensInRange.any { it.channel == PartiQLTokens.HIDDEN }) { - throw ParserException("Invalid whitespace or comment in operator", ErrorCode.PARSE_INVALID_QUERY) - } - val stringOp = op.joinToString("") { it.text.lowercase() } - when (stringOp) { - "and" -> and(args, metas) - "or" -> or(args, metas) - "*" -> times(args, metas) - "/" -> divide(args, metas) - "+" -> plus(args, metas) - "-" -> minus(args, metas) - "%" -> modulo(args, metas) - "||" -> concat(args, metas) - "<" -> lt(args, metas) - "<>" -> ne(args, metas) - "<=" -> lte(args, metas) - ">" -> gt(args, metas) - ">=" -> gte(args, metas) - "!=" -> ne(args, metas) - "=" -> eq(args, metas) - "&" -> bitwiseAnd(args, metas) - else -> throw ParserException("Unknown binary operator", ErrorCode.PARSE_INVALID_QUERY) - } - } - - private fun visitUnaryOperation(operand: ParserRuleContext?, op: Token?, parent: ParserRuleContext? = null) = - PartiqlAst.build { - if (parent != null) return@build visit(parent) as PartiqlAst.Expr - val arg = visit(operand!!) as PartiqlAst.Expr - val metas = op.getSourceMetaContainer() - when (op!!.type) { - PartiQLParser.PLUS -> { - when { - arg !is PartiqlAst.Expr.Lit -> pos(arg, metas) - arg.value is IntElement -> arg - arg.value is FloatElement -> arg - arg.value is DecimalElement -> arg - else -> pos(arg, metas) - } - } - - PartiQLParser.MINUS -> { - when { - arg !is PartiqlAst.Expr.Lit -> neg(arg, metas) - arg.value is IntElement -> { - val intValue = when (arg.value.integerSize) { - IntElementSize.LONG -> ionInt(-arg.value.longValue) - IntElementSize.BIG_INTEGER -> when (arg.value.bigIntegerValue) { - Long.MAX_VALUE.toBigInteger() + (1L).toBigInteger() -> ionInt(Long.MIN_VALUE) - else -> ionInt(arg.value.bigIntegerValue * BigInteger.valueOf(-1L)) - } - } - arg.copy(value = intValue.asAnyElement()) - } - - arg.value is FloatElement -> arg.copy(value = ionFloat(-(arg.value.doubleValue)).asAnyElement()) - arg.value is DecimalElement -> arg.copy(value = ionDecimal(-(arg.value.decimalValue)).asAnyElement()) - else -> neg(arg, metas) - } - } - PartiQLParser.NOT -> not(arg, metas) - else -> throw ParserException("Unknown unary operator", ErrorCode.PARSE_INVALID_QUERY) - } - } - - private fun PartiQLParser.SymbolPrimitiveContext.getSourceMetaContainer() = when (this) { - is PartiQLParser.IdentifierQuotedContext -> this.IDENTIFIER_QUOTED().getSourceMetaContainer() - is PartiQLParser.IdentifierUnquotedContext -> this.start.getSourceMetaContainer() - else -> throw ParserException( - "Unable to get identifier's source meta-container.", - ErrorCode.PARSE_INVALID_QUERY - ) - } - - private fun PartiqlAst.Expr.getStringValue(token: Token? = null): String = when (this) { - is PartiqlAst.Expr.Id -> this.name.text.lowercase() - is PartiqlAst.Expr.Lit -> { - when (this.value) { - is SymbolElement -> this.value.symbolValue.lowercase() - is StringElement -> this.value.stringValue.lowercase() - else -> - this.value.stringValueOrNull ?: throw token.err( - "Unable to pass the string value", - ErrorCode.PARSE_UNEXPECTED_TOKEN - ) - } - } - - else -> throw token.err("Unable to get value", ErrorCode.PARSE_UNEXPECTED_TOKEN) - } - - private fun PartiqlAst.Expr.Id.toPigSymbolPrimitive(): SymbolPrimitive = - this.name.copy(metas = this.metas) - - private fun PartiqlAst.Expr.Id.toIdentifier(): PartiqlAst.Identifier { - val name = this.name.text - val case = this.case - return PartiqlAst.build { - identifier(name, case) - } - } - - /** - * With the and nodes of a literal time expression, returns the parsed string and precision. - * TIME ()? (WITH TIME ZONE)? - */ - private fun getTimeStringAndPrecision(stringNode: TerminalNode, integerNode: TerminalNode?): Pair { - val timeString = stringNode.getStringValue() - val precision = when (integerNode) { - null -> try { - getPrecisionFromTimeString(timeString).toLong() - } catch (e: EvaluationException) { - throw stringNode.err( - "Unable to parse precision.", ErrorCode.PARSE_INVALID_TIME_STRING, - cause = e - ) - } - - else -> integerNode.text.toInteger().toLong() - } - if (precision < 0 || precision > MAX_PRECISION_FOR_TIME) { - throw integerNode.err("Precision out of bounds", ErrorCode.PARSE_INVALID_PRECISION_FOR_TIME) - } - return timeString to precision - } - - /** - * Parses a [timeString] using [OffsetTime] and converts to a [PartiqlAst.Expr.LitTime]. If unable to parse, parses - * using [getLocalTime]. - */ - private fun getOffsetTime(timeString: String, precision: Long, stringNode: TerminalNode, timeNode: TerminalNode) = - PartiqlAst.build { - try { - val time: OffsetTime = OffsetTime.parse(timeString) - litTime( - timeValue( - time.hour.toLong(), time.minute.toLong(), time.second.toLong(), time.nano.toLong(), - precision, true, (time.offset.totalSeconds / 60).toLong() - ) - ) - } catch (e: DateTimeParseException) { - getLocalTime(timeString, true, precision, stringNode, timeNode) - } - } - - /** - * Parses a [timeString] using [LocalTime] and converts to a [PartiqlAst.Expr.LitTime] - */ - private fun getLocalTime( - timeString: String, - withTimeZone: Boolean, - precision: Long, - stringNode: TerminalNode, - timeNode: TerminalNode, - ) = PartiqlAst.build { - val time: LocalTime - val formatter = when (withTimeZone) { - false -> DateTimeFormatter.ISO_TIME - else -> DateTimeFormatter.ISO_LOCAL_TIME - } - try { - time = LocalTime.parse(timeString, formatter) - } catch (e: DateTimeParseException) { - throw stringNode.err("Unable to parse time", ErrorCode.PARSE_INVALID_TIME_STRING, cause = e) - } - litTime( - timeValue( - time.hour.toLong(), time.minute.toLong(), time.second.toLong(), - time.nano.toLong(), precision, withTimeZone, null, - stringNode.getSourceMetaContainer() - ), - timeNode.getSourceMetaContainer() - ) - } - - private fun getTimestampStringAndPrecision( - stringNode: TerminalNode, - integerNode: TerminalNode?, - ): Pair { - val timestampString = stringNode.getStringValue() - val precision = when (integerNode) { - null -> return timestampString to null - else -> integerNode.text.toInteger().toLong() - } - if (precision < 0) { - throw integerNode.err("Precision out of bounds", ErrorCode.PARSE_INVALID_PRECISION_FOR_TIMESTAMP) - } - return timestampString to precision - } - - /** - * Parse Timestamp based on the existence of Time zone - */ - private fun getTimestampDynamic( - timestampString: String, - precision: Long?, - node: TerminalNode, - ) = PartiqlAst.build { - val timestamp = - try { - DateTimeUtils.parseTimestamp(timestampString) - } catch (e: DateTimeException) { - throw node.err("Invalid Date Time Literal", ErrorCode.PARSE_INVALID_DATETIME_STRING, cause = e) - } - val timeZone = timestamp.timeZone?.let { getTimeZone(it) } - timestamp( - timestampValue( - timestamp.year.toLong(), - timestamp.month.toLong(), - timestamp.day.toLong(), - timestamp.hour.toLong(), - timestamp.minute.toLong(), - ionDecimal(Decimal.valueOf(timestamp.decimalSecond)), - timeZone, - precision - ) - ) - } - - private fun getTimestampWithTimezone( - timestampString: String, - precision: Long?, - node: TerminalNode, - ) = PartiqlAst.build { - val timestamp = try { - DateTimeUtils.parseTimestamp(timestampString) - } catch (e: DateTimeException) { - throw node.err("Invalid Date Time Literal", ErrorCode.PARSE_INVALID_DATETIME_STRING, cause = e) - } - if (timestamp.timeZone == null) - throw node.err( - "Invalid Date Time Literal, expect Time Zone for Type Timestamp With Time Zone", - ErrorCode.PARSE_INVALID_DATETIME_STRING - ) - val timeZone = timestamp.timeZone?.let { getTimeZone(it) } - timestamp( - timestampValue( - timestamp.year.toLong(), - timestamp.month.toLong(), - timestamp.day.toLong(), - timestamp.hour.toLong(), - timestamp.minute.toLong(), - ionDecimal(Decimal.valueOf(timestamp.decimalSecond)), - timeZone, - precision - ) - ) - } - - private fun getTimeZone(timeZone: TimeZone) = PartiqlAst.build { - when (timeZone) { - TimeZone.UnknownTimeZone -> unknownTimezone() - is TimeZone.UtcOffset -> utcOffset(timeZone.totalOffsetMinutes.toLong()) - } - } - - private fun convertSymbolPrimitive(sym: PartiQLParser.SymbolPrimitiveContext?): SymbolPrimitive? = when (sym) { - null -> null - else -> SymbolPrimitive(sym.getString(), sym.getSourceMetaContainer()) - } - - private fun PartiQLParser.SelectClauseContext.getMetas(): MetaContainer = when (this) { - is PartiQLParser.SelectAllContext -> this.SELECT().getSourceMetaContainer() - is PartiQLParser.SelectItemsContext -> this.SELECT().getSourceMetaContainer() - is PartiQLParser.SelectValueContext -> this.SELECT().getSourceMetaContainer() - is PartiQLParser.SelectPivotContext -> this.PIVOT().getSourceMetaContainer() - else -> throw ParserException("Unknown meta location.", ErrorCode.PARSE_INVALID_QUERY) - } - - /** - * Converts a Path expression into a Projection Item (either ALL or EXPR). Note: A Projection Item only allows a - * subset of a typical Path expressions. See the following examples. - * - * Examples of valid projections are: - * - * ```sql - * SELECT * FROM foo - * SELECT foo.* FROM foo - * SELECT f.* FROM foo as f - * SELECT foo.bar.* FROM foo - * SELECT f.bar.* FROM foo as f - * ``` - * Also validates that the expression is valid for select list context. It does this by making - * sure that expressions looking like the following do not appear: - * - * ```sql - * SELECT foo[*] FROM foo - * SELECT f.*.bar FROM foo as f - * SELECT foo[1].* FROM foo - * SELECT foo.*.bar FROM foo - * ``` - */ - protected fun convertPathToProjectionItem(path: PartiqlAst.Expr.Path, alias: SymbolPrimitive?) = PartiqlAst.build { - val steps = mutableListOf() - var containsIndex = false - path.steps.forEachIndexed { index, step -> - - // Only last step can have a '.*' - if (step is PartiqlAst.PathStep.PathUnpivot && index != path.steps.lastIndex) { - throw ParserException("Projection item cannot unpivot unless at end.", ErrorCode.PARSE_INVALID_QUERY) - } - - // No step can have an indexed wildcard: '[*]' - if (step is PartiqlAst.PathStep.PathWildcard) { - throw ParserException("Projection item cannot index using wildcard.", ErrorCode.PARSE_INVALID_QUERY) - } - - // If the last step is '.*', no indexing is allowed - if (step.metas.containsKey(IsPathIndexMeta.TAG)) { - containsIndex = true - } - - if (step !is PartiqlAst.PathStep.PathUnpivot) { - steps.add(step) - } - } - - if (path.steps.last() is PartiqlAst.PathStep.PathUnpivot && containsIndex) { - throw ParserException("Projection item use wildcard with any indexing.", ErrorCode.PARSE_INVALID_QUERY) - } - - when { - path.steps.last() is PartiqlAst.PathStep.PathUnpivot && steps.isEmpty() -> projectAll(path.root, path.metas) - path.steps.last() is PartiqlAst.PathStep.PathUnpivot -> projectAll( - path(path.root, steps, path.metas), - path.metas - ) - - else -> projectExpr_(path, asAlias = alias, path.metas) - } - } - - private fun TerminalNode.getStringValue(): String = this.symbol.getStringValue() - - private fun Token.getStringValue(): String = when (this.type) { - PartiQLParser.IDENTIFIER -> this.text - PartiQLParser.IDENTIFIER_QUOTED -> this.text.removePrefix("\"").removeSuffix("\"").replace("\"\"", "\"") - PartiQLParser.LITERAL_STRING -> this.text.removePrefix("'").removeSuffix("'").replace("''", "'") - PartiQLParser.ION_CLOSURE -> this.text.removePrefix("`").removeSuffix("`") - else -> throw this.err("Unsupported token for grabbing string value.", ErrorCode.PARSE_INVALID_QUERY) - } - - private fun getStrategy(strategy: PartiQLParser.SetQuantifierStrategyContext?, default: PartiqlAst.SetQuantifier) = - PartiqlAst.build { - when { - strategy == null -> default - strategy.DISTINCT() != null -> distinct() - strategy.ALL() != null -> all() - else -> default - } - } - - private fun getStrategy(strategy: PartiQLParser.SetQuantifierStrategyContext?): PartiqlAst.SetQuantifier? { - return when { - strategy == null -> null - strategy.DISTINCT() != null -> PartiqlAst.build { distinct() } - else -> null - } - } - - private fun getSetQuantifierStrategy(ctx: PartiQLParser.SelectClauseContext): PartiqlAst.SetQuantifier? { - return when (ctx) { - is PartiQLParser.SelectAllContext -> getStrategy(ctx.setQuantifierStrategy()) - is PartiQLParser.SelectItemsContext -> getStrategy(ctx.setQuantifierStrategy()) - is PartiQLParser.SelectValueContext -> getStrategy(ctx.setQuantifierStrategy()) - is PartiQLParser.SelectPivotContext -> null - else -> null - } - } - - private fun PartiQLParser.SymbolPrimitiveContext.getString(): String { - return when (this) { - is PartiQLParser.IdentifierQuotedContext -> this.IDENTIFIER_QUOTED().getStringValue() - is PartiQLParser.IdentifierUnquotedContext -> this.text - else -> throw ParserException("Unable to get symbol's text.", ErrorCode.PARSE_INVALID_QUERY) - } - } - - private fun getSymbolPathExpr(ctx: PartiQLParser.SymbolPrimitiveContext) = PartiqlAst.build { - when (ctx) { - is PartiQLParser.IdentifierQuotedContext -> pathExpr( - lit(ionString(ctx.IDENTIFIER_QUOTED().getStringValue())), caseSensitive(), - metas = ctx.getSourceMetaContainer() - ) - is PartiQLParser.IdentifierUnquotedContext -> pathExpr( - lit(ionString(ctx.text)), caseInsensitive(), - metas = ctx.getSourceMetaContainer() - ) - else -> throw ParserException("Unable to get symbol's text.", ErrorCode.PARSE_INVALID_QUERY) - } - } - - private fun String.toInteger() = BigInteger(this, 10) - - private fun Token.toSymbol(): PartiqlAst.Expr.Lit { - val str = this.text - val metas = this.getSourceMetaContainer() - return PartiqlAst.build { - lit(ionSymbol(str), metas) - } - } - - private fun parseToIntElement(text: String): IntElement = - try { - ionInt(text.toLong()) - } catch (e: NumberFormatException) { - ionInt(text.toBigInteger()) - } - - private fun assertIntegerElement(token: Token?, value: IonElement?) { - if (value == null) - return - if (value !is IntElement) - throw token.err("Expected an integer value.", ErrorCode.PARSE_MALFORMED_PARSE_TREE) - if (value.integerSize == IntElementSize.BIG_INTEGER || value.longValue > Int.MAX_VALUE || value.longValue < Int.MIN_VALUE) - throw token.err( - "Type parameter exceeded maximum value", - ErrorCode.PARSE_TYPE_PARAMETER_EXCEEDED_MAXIMUM_VALUE - ) - } - - private enum class ExplainParameters { - TYPE, - FORMAT; - - fun getCompliantString(target: String?, input: Token): String = when (target) { - null -> input.text!! - else -> throw input.error( - "Cannot set EXPLAIN parameter ${this.name} multiple times.", - ErrorCode.PARSE_UNEXPECTED_TOKEN - ) - } - } - - private fun TerminalNode?.err( - msg: String, - code: ErrorCode, - ctx: PropertyValueMap = PropertyValueMap(), - cause: Throwable? = null, - ) = this.error(msg, code, ctx, cause) - - private fun Token?.err( - msg: String, - code: ErrorCode, - ctx: PropertyValueMap = PropertyValueMap(), - cause: Throwable? = null, - ) = this.error(msg, code, ctx, cause) -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLShimParser.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLShimParser.kt deleted file mode 100644 index c1dccd0a90..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLShimParser.kt +++ /dev/null @@ -1,89 +0,0 @@ -package org.partiql.lang.syntax.impl - -import com.amazon.ion.system.IonSystemBuilder -import com.amazon.ionelement.api.MetaContainer -import org.partiql.ast.helpers.toLegacyAst -import org.partiql.errors.ErrorCode -import org.partiql.errors.Property -import org.partiql.errors.PropertyValueMap -import org.partiql.lang.ast.SourceLocationMeta -import org.partiql.lang.domains.PartiqlAst -import org.partiql.lang.domains.metaContainerOf -import org.partiql.lang.syntax.Parser -import org.partiql.lang.syntax.ParserException -import org.partiql.parser.PartiQLLexerException -import org.partiql.parser.PartiQLParser -import org.partiql.parser.PartiQLParserException -import org.partiql.parser.SourceLocations - -/** - * Implementation of [Parser] which uses a [org.partiql.ast.AstNode] tree, then translates to the legacy interface. - * - * @property delegate - */ -internal class PartiQLShimParser( - private val delegate: PartiQLParser, -) : Parser { - - // required for PropertyValueMap debug information - private val ion = IonSystemBuilder.standard().build() - - override fun parseAstStatement(source: String): PartiqlAst.Statement { - val result = try { - delegate.parse(source) - } catch (ex: PartiQLLexerException) { - throw ex.shim() - } catch (ex: PartiQLParserException) { - throw ex.shim() - } - val statement = try { - val metas = result.locations.toMetas() - result.root.toLegacyAst(metas) - } catch (ex: Exception) { - throw ParserException( - message = ex.message ?: "", - errorCode = ErrorCode.PARSE_INVALID_QUERY, - cause = ex, - ) - } - if (statement !is PartiqlAst.Statement) { - throw ParserException( - message = "Expected statement, got ${statement::class.qualifiedName}", - errorCode = ErrorCode.PARSE_INVALID_QUERY, - ) - } - return statement - } - - /** - * The legacy parser tests assert on ParserExcept, not LexerException. - */ - private fun PartiQLLexerException.shim(): ParserException { - val ctx = PropertyValueMap() - ctx[Property.LINE_NUMBER] = location.line.toLong() - ctx[Property.COLUMN_NUMBER] = location.offset.toLong() - ctx[Property.TOKEN_STRING] = token - ctx[Property.TOKEN_DESCRIPTION] = tokenType - ctx[Property.TOKEN_VALUE] = ion.newSymbol(token) - return ParserException(message, ErrorCode.PARSE_UNEXPECTED_TOKEN, ctx, cause) - } - - private fun PartiQLParserException.shim(): ParserException { - val ctx = PropertyValueMap() - ctx[Property.LINE_NUMBER] = location.line.toLong() - ctx[Property.COLUMN_NUMBER] = location.offset.toLong() - ctx[Property.TOKEN_DESCRIPTION] = tokenType - ctx[Property.TOKEN_VALUE] = ion.newSymbol(token) - return ParserException(message, ErrorCode.PARSE_UNEXPECTED_TOKEN, ctx, cause) - } - - private fun SourceLocations.toMetas(): Map = mapValues { - metaContainerOf( - SourceLocationMeta( - lineNum = it.value.line.toLong(), - charOffset = it.value.offset.toLong(), - length = it.value.lengthLegacy.toLong(), - ) - ) - } -} diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/util/DateTimeUtils.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/util/DateTimeUtils.kt deleted file mode 100644 index ec349d24c0..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/util/DateTimeUtils.kt +++ /dev/null @@ -1,76 +0,0 @@ -package org.partiql.lang.syntax.util - -import org.partiql.value.datetime.Date -import org.partiql.value.datetime.DateTimeException -import org.partiql.value.datetime.DateTimeValue -import org.partiql.value.datetime.Time -import org.partiql.value.datetime.TimeZone -import org.partiql.value.datetime.Timestamp -import java.math.BigDecimal -import java.util.regex.Matcher -import java.util.regex.Pattern - -internal object DateTimeUtils { - private val DATE_PATTERN: Pattern = Pattern.compile("(?\\d{4,})-(?\\d{2,})-(?\\d{2,})") - private val TIME_PATTERN: Pattern = Pattern.compile("(?\\d{2,}):(?\\d{2,}):(?\\d{2,})(?:\\.(?\\d+))?\\s*(?([+-]\\d\\d:\\d\\d)|(?[Zz]))?") - private val SQL_TIMESTAMP_DATE_TIME_DELIMITER = "\\s+".toRegex() - private val RFC8889_TIMESTAMP_DATE_TIME_DELIMITER = "[Tt]".toRegex() - private val TIMESTAMP_PATTERN = "(?$DATE_PATTERN)($SQL_TIMESTAMP_DATE_TIME_DELIMITER|$RFC8889_TIMESTAMP_DATE_TIME_DELIMITER)(?