├── NOTICE ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ └── build.yml └── no-response.yml ├── pig-runtime ├── src │ ├── main │ │ └── kotlin │ │ │ └── org │ │ │ └── partiql │ │ │ └── pig │ │ │ └── runtime │ │ │ ├── Experimental.kt │ │ │ ├── MalformedDomainDataException.kt │ │ │ ├── MetaContainingNode.kt │ │ │ ├── DomainNode.kt │ │ │ ├── IonElementTransformerBase.kt │ │ │ ├── DomainVisitorBase.kt │ │ │ ├── SymbolPrimitive.kt │ │ │ ├── LongPrimitive.kt │ │ │ ├── BoolPrimitive.kt │ │ │ ├── DomainVisitorFoldBase.kt │ │ │ ├── PrimitiveUtils.kt │ │ │ ├── DomainVisitorTransformBase.kt │ │ │ ├── IonElementHelpers.kt │ │ │ ├── ErrorHelpers.kt │ │ │ └── IntermediateRecord.kt │ └── test │ │ └── kotlin │ │ └── org │ │ └── partiql │ │ └── pig │ │ └── runtime │ │ ├── LongPrimitiveTests.kt │ │ ├── SymbolPrimitiveTests.kt │ │ ├── ErrorHelpersTests.kt │ │ ├── IonElementTransformerBaseTests.kt │ │ └── IntermediateRecordTests.kt └── build.gradle.kts ├── gradle.properties ├── CODE_OF_CONDUCT.md ├── pig ├── src │ ├── main │ │ ├── kotlin │ │ │ └── org │ │ │ │ └── partiql │ │ │ │ └── pig │ │ │ │ ├── generator │ │ │ │ ├── ion │ │ │ │ │ └── generator.kt │ │ │ │ ├── kotlin │ │ │ │ │ ├── KotlinCrossDomainFreeMarkerGlobals.kt │ │ │ │ │ ├── KotlinDomainFreeMarkerGlobals.kt │ │ │ │ │ ├── generator.kt │ │ │ │ │ └── KTypeDomain.kt │ │ │ │ ├── custom │ │ │ │ │ ├── CustomFreeMarkerGlobals.kt │ │ │ │ │ ├── generator.kt │ │ │ │ │ └── CTypeDomain.kt │ │ │ │ ├── html │ │ │ │ │ └── generator.kt │ │ │ │ ├── IndentDirective.kt │ │ │ │ └── FreeMarkerUtils.kt │ │ │ │ ├── domain │ │ │ │ ├── model │ │ │ │ │ ├── TupleType.kt │ │ │ │ │ ├── Arity.kt │ │ │ │ │ ├── TypeAnnotation.kt │ │ │ │ │ ├── NamedElement.kt │ │ │ │ │ ├── TypeRef.kt │ │ │ │ │ └── TypeUniverse.kt │ │ │ │ ├── Utils.kt │ │ │ │ └── parser │ │ │ │ │ └── ParserErrorContext.kt │ │ │ │ ├── errors │ │ │ │ ├── Exceptions.kt │ │ │ │ └── PigError.kt │ │ │ │ ├── cmdline │ │ │ │ ├── Command.kt │ │ │ │ └── TargetLanguage.kt │ │ │ │ ├── util │ │ │ │ ├── Capitalizers.kt │ │ │ │ └── IonElement.kt │ │ │ │ └── main.kt │ │ └── resources │ │ │ └── org │ │ │ └── partiql │ │ │ └── pig │ │ │ └── templates │ │ │ ├── kotlin-header.ftl │ │ │ ├── kotlin-cross-domain-transform.ftl │ │ │ ├── kotlin-visitor.ftl │ │ │ ├── kotlin-visitor-fold.ftl │ │ │ ├── html.ftl │ │ │ └── kotlin-visitor-transform.ftl │ └── test │ │ └── kotlin │ │ └── org │ │ └── partiql │ │ └── pig │ │ ├── domain │ │ ├── Util.kt │ │ ├── TypeAnnotationParserTests.kt │ │ ├── TypeDomainParserTests.kt │ │ ├── PermuteDomainTests.kt │ │ └── TypeDomainParserErrorsTest.kt │ │ ├── generator │ │ ├── FreeMarkerFunctionsTest.kt │ │ ├── ion │ │ │ └── GenerateIonTest.kt │ │ └── kotlin │ │ │ └── KTypeDomainConverterKtTest.kt │ │ └── util │ │ └── CapitalizerTests.kt └── build.gradle.kts ├── settings.gradle.kts ├── pig-tests ├── build.gradle.kts └── src │ └── test │ ├── kotlin │ └── org │ │ └── partiql │ │ └── pig │ │ └── tests │ │ ├── Scope.kt │ │ ├── ArgumentsProviderBase.kt │ │ ├── CustomMetasTests.kt │ │ ├── SmokeTests.kt │ │ ├── MultiWordDomainNamingTest.kt │ │ ├── VisitorFoldTests.kt │ │ ├── IonElementTransformerErrorTests.kt │ │ ├── SumConverterTests.kt │ │ ├── VisitorTests.kt │ │ ├── CopyTests.kt │ │ └── PermuteTransformSmokeTests.kt │ └── pig │ ├── toy-lang.ion │ └── partiql-basic.ion ├── pig-gradle-plugin └── build.gradle.kts ├── gradlew.bat ├── CONTRIBUTING.md └── README.md /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/partiql/partiql-ir-generator/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/Experimental.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.runtime 2 | 3 | @RequiresOptIn( 4 | message = "This DomainNode is marked as experimental", 5 | level = RequiresOptIn.Level.WARNING 6 | ) 7 | annotation class Experimental 8 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=org.partiql 2 | version=0.6.4-SNAPSHOT 3 | # Required for the maven publish plugin--these properties must be present even if they contain placeholder values. 4 | ossrhUsername=EMPTY 5 | ossrhPassword=EMPTY 6 | 7 | org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=2G 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/ion/generator.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.generator.ion 2 | 3 | import org.partiql.pig.domain.model.TypeDomain 4 | import org.partiql.pig.domain.toIonElement 5 | import java.io.PrintWriter 6 | 7 | /** 8 | * Generate an Ion representation of each type domain and write it to [output] 9 | */ 10 | fun generateIon(domains: List, output: PrintWriter) = 11 | domains.map(TypeDomain::toIonElement).forEach(output::println) 12 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KotlinCrossDomainFreeMarkerGlobals.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.generator.kotlin 2 | 3 | import java.time.OffsetDateTime 4 | 5 | /** 6 | * The properties of [KotlinCrossDomainFreeMarkerGlobals] become global variables for our freemarker template that 7 | * generates the cross-domain visitor transforms. 8 | */ 9 | @Suppress("unused") 10 | class KotlinCrossDomainFreeMarkerGlobals( 11 | val namespace: String, 12 | val transform: KTransform, 13 | val generatedDate: OffsetDateTime 14 | ) 15 | -------------------------------------------------------------------------------- /pig/src/main/resources/org/partiql/pig/templates/kotlin-header.ftl: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was generated by the PartiQL I.R. Generator. 3 | * Do not modify this file. [#-- Unless you're editing the template, of course! --] 4 | */ 5 | @file:Suppress("unused", "MemberVisibilityCanBePrivate", "FunctionName", 6 | "CanBePrimaryConstructorProperty", "UNNECESSARY_SAFE_CALL", 7 | "USELESS_ELVIS", "RemoveRedundantQualifierName", "LocalVariableName") 8 | 9 | package ${namespace} 10 | 11 | import com.amazon.ionelement.api.* 12 | import org.partiql.pig.runtime.* 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "Build and Run Tests" 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | java: [8, 11, 17] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Use Java ${{ matrix.java }} 16 | uses: actions/setup-java@v3 17 | with: 18 | distribution: 'corretto' 19 | java-version: ${{ matrix.java }} 20 | cache: gradle 21 | - name: Build and Test 22 | run: ./gradlew build 23 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 14 5 | # Label requiring a response 6 | responseRequiredLabel: more-information-needed 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because there has been no response 10 | to our request for more information from the original author. With only the 11 | information that is currently in the issue, we don't have enough information 12 | to take action. Please reach out if you have or find the answers we need so 13 | that we can investigate further. 14 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/domain/model/TupleType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain.model 17 | 18 | /** Indicates the type of tuple. */ 19 | enum class TupleType { 20 | PRODUCT, 21 | RECORD, 22 | } 23 | -------------------------------------------------------------------------------- /pig/src/main/resources/org/partiql/pig/templates/kotlin-cross-domain-transform.ftl: -------------------------------------------------------------------------------- 1 | [#--------------------------------------------------------------------------------------------------- 2 | 3 | This file is the entry point template that generates all of the 4 | ToVisitorTransform.generated.kt files. 5 | 6 | -----------------------------------------------------------------------------------------------------] 7 | 8 | [#-- include the #macro definitions that we need below. --] 9 | [#include "kotlin-visitor-transform.ftl"] 10 | 11 | 12 | [#-- emits the standard header for all of our generated files--] 13 | [#include "kotlin-header.ftl"] 14 | 15 | [@visitor_transform_class 16 | "${transform.sourceDomainDifference.kotlinName}To${transform.destDomainKotlinName}VisitorTransform" 17 | transform.sourceDomainDifference 18 | transform.destDomainKotlinName /] 19 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/errors/Exceptions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.errors 17 | 18 | class PigException(val error: PigError, cause: Throwable? = null) : Exception(error.toString(), cause) 19 | 20 | class TypeDomainParseException(message: String) : Exception(message) 21 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | rootProject.name = "PIG" 17 | 18 | pluginManagement { 19 | includeBuild("pig-gradle-plugin") 20 | repositories { 21 | gradlePluginPortal() 22 | } 23 | } 24 | 25 | include( 26 | "pig", 27 | "pig-runtime", 28 | "pig-tests" 29 | ) 30 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/domain/model/Arity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain.model 17 | 18 | /** Base class representing an element's arity. */ 19 | sealed class Arity { 20 | object Required : Arity() 21 | object Optional : Arity() 22 | data class Variadic(val minimumArity: Int) : Arity() 23 | } 24 | -------------------------------------------------------------------------------- /pig-runtime/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | plugins { 17 | id("pig.conventions") 18 | id("org.partiql.pig.gradle.publish") 19 | } 20 | 21 | dependencies { 22 | api("com.amazon.ion:ion-element:1.0.0") 23 | } 24 | 25 | publish { 26 | artifactId = "partiql-ir-generator-runtime" 27 | name = "PartiQL I.R. Generator (a.k.a P.I.G.) Runtime Library" 28 | } 29 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/MalformedDomainDataException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.IonLocation 19 | import com.amazon.ionelement.api.locationToString 20 | 21 | class MalformedDomainDataException(val location: IonLocation?, message: String, cause: Throwable? = null) : 22 | Exception("${locationToString(location)}: $message", cause) 23 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/cmdline/Command.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.cmdline 17 | 18 | import java.io.File 19 | 20 | sealed class Command { 21 | object ShowHelp : Command() 22 | 23 | object ShowVersion : Command() 24 | data class InvalidCommandLineArguments(val message: String) : Command() 25 | data class Generate(val typeUniverseFile: File, val target: TargetLanguage) : Command() 26 | } 27 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/custom/CustomFreeMarkerGlobals.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator.custom 17 | 18 | import java.time.OffsetDateTime 19 | 20 | /** The properties of [CustomFreeMarkerGlobals] become global variables for our freemarker templates. */ 21 | @Suppress("unused") 22 | data class CustomFreeMarkerGlobals( 23 | val domains: List, 24 | val generatedDate: OffsetDateTime 25 | ) 26 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/domain/model/TypeAnnotation.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.domain.model 2 | 3 | /** 4 | * Annotations that are allowed on a UserType definition 5 | * 6 | * Example: 7 | * ``` 8 | * (sum my_sum_type 9 | * (deprecated::first_variant ...) 10 | * (second_variant ...) 11 | * (experimental::third_variant ...) 12 | * ) 13 | * ``` 14 | */ 15 | enum class TypeAnnotation { 16 | DEPRECATED, 17 | EXPERIMENTAL; 18 | 19 | companion object { 20 | 21 | /** 22 | * TODO "safe" valueOf until it's decided what to do with 23 | * - Field identifier used in place of type name (in same sum) 24 | * - Variant identifier used in place of type name (in a different sum) 25 | * 26 | * These two TypeDomainSemanticCheckerTests appear to be asserting on missing(?) functionality 27 | */ 28 | fun of(v: String): TypeAnnotation? = try { 29 | valueOf(v.toUpperCase()) 30 | } catch (ex: IllegalArgumentException) { 31 | null 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pig/src/test/kotlin/org/partiql/pig/domain/Util.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain 17 | 18 | import com.amazon.ionelement.api.IonTextLocation 19 | import org.partiql.pig.errors.ErrorContext 20 | import org.partiql.pig.errors.PigError 21 | 22 | fun makeErr(line: Int, col: Int, errorContext: ErrorContext) = 23 | PigError(IonTextLocation(line.toLong(), col.toLong()), errorContext) 24 | 25 | fun makeErr(errorContext: ErrorContext) = 26 | PigError(null, errorContext) 27 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/MetaContainingNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.MetaContainer 19 | 20 | interface MetaContainingNode { 21 | /** This node's collection of metadata. */ 22 | val metas: MetaContainer 23 | 24 | /** Creates a copy of the current node with the specified [key] and [value] as metadata.*/ 25 | fun withMeta(metaKey: String, metaValue: Any): MetaContainingNode 26 | } 27 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KotlinDomainFreeMarkerGlobals.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator.kotlin 17 | 18 | import java.time.OffsetDateTime 19 | 20 | /** 21 | * The properties of [KotlinDomainFreeMarkerGlobals] become global variables for our freemarker templates that 22 | * generate everything but the cross-domain `VisitorTransform`. 23 | */ 24 | @Suppress("unused") 25 | class KotlinDomainFreeMarkerGlobals( 26 | val namespace: String, 27 | val domain: KTypeDomain, 28 | val generatedDate: OffsetDateTime 29 | ) 30 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/cmdline/TargetLanguage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.cmdline 17 | 18 | import java.io.File 19 | 20 | sealed class TargetLanguage { 21 | abstract val domains: Set? 22 | 23 | data class Kotlin(val namespace: String, val outputDirectory: File, override val domains: Set? = null) : TargetLanguage() 24 | data class Custom(val templateFile: File, val outputFile: File, override val domains: Set? = null) : TargetLanguage() 25 | data class Html(val outputFile: File, override val domains: Set? = null) : TargetLanguage() 26 | data class Ion(val outputFile: File, override val domains: Set? = null) : TargetLanguage() 27 | } 28 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/errors/PigError.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.errors 17 | 18 | import com.amazon.ionelement.api.IonLocation 19 | import com.amazon.ionelement.api.locationToString 20 | 21 | /** 22 | * [ErrorContext] instances provide information about an error message which can later be used 23 | * to produce human readable error message. 24 | * 25 | * [ErrorContext] instances must implement [toString] to produce the human readable text, 26 | * and [equals] 27 | */ 28 | interface ErrorContext { 29 | val message: String 30 | } 31 | 32 | data class PigError(val location: IonLocation?, val context: ErrorContext) { 33 | override fun toString(): String = "${locationToString(location)}: ${context.message}" 34 | } 35 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/util/Capitalizers.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.util 17 | 18 | fun String.snakeToPascalCase(): String = 19 | this.split('_') 20 | .filter { it.isNotEmpty() } 21 | .joinToString(separator = "") { 22 | it.capitalize() 23 | } 24 | 25 | fun String.snakeToCamelCase(): String = 26 | this.split('_') 27 | .filter { it.isNotEmpty() } 28 | .mapIndexed { i, str -> 29 | when (i) { 30 | 0 -> str 31 | else -> str.capitalize() 32 | } 33 | } 34 | .joinToString(separator = "") 35 | 36 | private fun String.capitalize() = 37 | String(toCharArray().apply { this[0] = this[0].toUpperCase() }) 38 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/util/IonElement.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.util 2 | 3 | import com.amazon.ionelement.api.AnyElement 4 | import com.amazon.ionelement.api.IonElementException 5 | import com.amazon.ionelement.api.SeqElement 6 | 7 | /** 8 | * Returns the string representation of the symbol in the first element of this container. 9 | * 10 | * If the first element is not a symbol, throws [IonElementException]. 11 | * If this container has no elements, throws [NoSuchElementException]. 12 | */ 13 | internal val SeqElement.tag: String get() = this.head.symbolValue 14 | 15 | /** 16 | * Returns the first element of this container. 17 | * 18 | * If this container has no elements, throws [NoSuchElementException]. 19 | */ 20 | internal val SeqElement.head: AnyElement 21 | get() = values.first() 22 | 23 | /** 24 | * Returns a sub-list containing all elements of this container except the first. 25 | * 26 | * If this container has no elements, throws [NoSuchElementException]. 27 | */ 28 | internal val SeqElement.tail: List get() = 29 | when (this.size) { 30 | 0 -> throw NoSuchElementException("Cannot get tail of empty container") 31 | else -> this.values.subList(1, this.size) 32 | } 33 | 34 | /** Returns the first element. */ 35 | internal val List.head: AnyElement 36 | get() = this.first() 37 | 38 | /** Returns a copy of the list with the first element removed. */ 39 | internal val List.tail: List get() = this.subList(1, this.size) 40 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/DomainNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.IonElement 19 | import com.amazon.ionelement.api.MetaContainer 20 | 21 | /** All generated domain classes must implement this interface. */ 22 | interface DomainNode : MetaContainingNode { 23 | 24 | /** Creates a copy of the current node with [metas] as the new metas. */ 25 | fun copy(metas: MetaContainer): DomainNode 26 | 27 | /** Converts the current node to an instance of `IonElement`. */ 28 | fun toIonElement(): IonElement 29 | 30 | /** This override narrows the return type of [MetaContainingNode.wtihMeta]. */ 31 | override fun withMeta(metaKey: String, metaValue: Any): DomainNode 32 | 33 | /** Converts the current node to a String. Most nodes should simply call `toIonElement().toString()`. */ 34 | override fun toString(): String 35 | } 36 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/html/generator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator.html 17 | 18 | import freemarker.template.Template 19 | import org.partiql.pig.domain.model.TypeDomain 20 | import org.partiql.pig.generator.createDefaultFreeMarkerConfiguration 21 | import org.partiql.pig.generator.custom.createCustomFreeMarkerGlobals 22 | import org.partiql.pig.generator.setClassLoaderForTemplates 23 | import java.io.PrintWriter 24 | 25 | fun applyHtmlTemplate( 26 | domains: List, 27 | output: PrintWriter 28 | ) { 29 | // Html uses the same model as the custom target 30 | val renderModel = createCustomFreeMarkerGlobals(domains) 31 | 32 | createHtmlTemplate().process(renderModel, output) 33 | } 34 | 35 | private fun createHtmlTemplate(): Template { 36 | val cfg = createDefaultFreeMarkerConfiguration() 37 | cfg.setClassLoaderForTemplates() 38 | return cfg.getTemplate("html.ftl")!! 39 | } 40 | -------------------------------------------------------------------------------- /pig-tests/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | plugins { 17 | id("pig.conventions") 18 | id("org.partiql.pig.pig-gradle-plugin") 19 | id("java-library") 20 | } 21 | 22 | dependencies { 23 | implementation(project(":pig-runtime")) 24 | } 25 | 26 | // remove after pig-example is created 27 | val pigOutputDir = file("./src/main/kotlin/org/partiql/pig/tests/generated/") 28 | 29 | pig { 30 | namespace = "org.partiql.pig.tests.generated" 31 | // remove after pig-example is created 32 | outputDir = pigOutputDir 33 | } 34 | 35 | // remove after pig-example is created 36 | tasks.register("pigClean", Delete::class) { 37 | group = "pig" 38 | description = "deletes all pig generated files" 39 | delete( 40 | fileTree(pigOutputDir).matching { 41 | include("*.generated.kt") 42 | } 43 | ) 44 | } 45 | 46 | configure { 47 | filter { 48 | exclude("**/*.generated.kt") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/custom/generator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator.custom 17 | 18 | import org.partiql.pig.domain.model.TypeDomain 19 | import org.partiql.pig.generator.createDefaultFreeMarkerConfiguration 20 | import java.io.File 21 | import java.io.PrintWriter 22 | import java.time.OffsetDateTime 23 | 24 | fun applyCustomTemplate( 25 | templateFile: File, 26 | domains: List, 27 | output: PrintWriter 28 | ) { 29 | val renderModel = createCustomFreeMarkerGlobals(domains) 30 | 31 | val cfg = createDefaultFreeMarkerConfiguration() 32 | 33 | cfg.setDirectoryForTemplateLoading(templateFile.parentFile) 34 | val template = cfg.getTemplate(templateFile.name)!! 35 | 36 | template.process(renderModel, output) 37 | } 38 | 39 | internal fun createCustomFreeMarkerGlobals(domains: List): CustomFreeMarkerGlobals = 40 | CustomFreeMarkerGlobals( 41 | domains = domains.map { it.toCTypeDomain() }, 42 | generatedDate = OffsetDateTime.now() 43 | ) 44 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/IonElementTransformerBase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.AnyElement 19 | import com.amazon.ionelement.api.IonElementException 20 | import com.amazon.ionelement.api.SexpElement 21 | import com.amazon.ionelement.api.location 22 | 23 | abstract class IonElementTransformerBase { 24 | fun transform(maybeSexp: SexpElement): T = 25 | try { 26 | innerTransform(maybeSexp) 27 | } catch (ex: IonElementException) { 28 | throw MalformedDomainDataException(ex.location, ex.description, ex) 29 | } 30 | 31 | protected inline fun AnyElement.transformExpect(): R { 32 | val domainObject = innerTransform(this.asSexp()) 33 | return (domainObject as? R) ?: errMalformed( 34 | this.metas.location, 35 | "Expected '${R::class.java}' but found '${domainObject.javaClass}'" 36 | ) 37 | } 38 | 39 | protected abstract fun innerTransform(sexp: SexpElement): T 40 | } 41 | -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/Scope.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.tests 17 | 18 | sealed class Scope { 19 | 20 | /** Creates a new scope nested within the current scope. */ 21 | abstract fun nest(name: String): Lexical 22 | 23 | /** 24 | * Recursively searches up the scope graph to find the variable with the specified [name]. 25 | * 26 | * Throws if the variable is undefined. 27 | */ 28 | abstract fun findIndex(id: String): Long 29 | 30 | object Global : Scope() { 31 | override fun nest(name: String): Lexical = Lexical(name, 0, this) 32 | override fun findIndex(id: String): Long = error("Undefined variable '$id'") 33 | 34 | override fun toString(): String = "" 35 | } 36 | 37 | data class Lexical(val name: String, val index: Long, val parent: Scope) : Scope() { 38 | override fun nest(name: String): Lexical = 39 | Lexical(name, index + 1, this) 40 | 41 | override fun findIndex(id: String): Long = 42 | when (name) { 43 | id -> index 44 | else -> parent.findIndex(id) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pig-gradle-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-gradle-plugin") 3 | id("org.jetbrains.kotlin.jvm") version "1.4.0" 4 | id("com.gradle.plugin-publish") version "1.1.0" 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | object Versions { 12 | const val pig = "0.6.3" 13 | const val kotlinTarget = "1.4" 14 | const val javaTarget = "1.8" 15 | } 16 | 17 | // latest maven central release 18 | version = Versions.pig 19 | group = "org.partiql" 20 | 21 | dependencies { 22 | implementation("org.partiql:partiql-ir-generator:${Versions.pig}") 23 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") 24 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") 25 | } 26 | 27 | tasks.test { 28 | useJUnitPlatform() 29 | } 30 | 31 | pluginBundle { 32 | website = "https://github.com/partiql/partiql-ir-generator/wiki" 33 | vcsUrl = "https://github.com/partiql/partiql-ir-generator" 34 | tags = listOf("partiql", "pig", "ir", "partiql-ir-generator") 35 | } 36 | 37 | gradlePlugin { 38 | plugins { 39 | create("pig-gradle-plugin") { 40 | id = "org.partiql.pig.pig-gradle-plugin" 41 | displayName = "PIG Gradle Plugin" 42 | description = "The PIG gradle plugin exposes a Gradle task to generate sources from a PIG type universe" 43 | implementationClass = "org.partiql.pig.gradle.PigPlugin" 44 | } 45 | } 46 | } 47 | 48 | java { 49 | sourceCompatibility = JavaVersion.toVersion(Versions.javaTarget) 50 | targetCompatibility = JavaVersion.toVersion(Versions.javaTarget) 51 | } 52 | 53 | tasks.compileKotlin { 54 | kotlinOptions.jvmTarget = Versions.javaTarget 55 | kotlinOptions.apiVersion = Versions.kotlinTarget 56 | kotlinOptions.languageVersion = Versions.kotlinTarget 57 | } 58 | -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/ArgumentsProviderBase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.tests 17 | 18 | import org.junit.jupiter.api.extension.ExtensionContext 19 | import org.junit.jupiter.params.provider.Arguments 20 | import org.junit.jupiter.params.provider.ArgumentsProvider 21 | import java.util.stream.Stream 22 | 23 | /** 24 | * Reduces some of the boilerplate associated with the style of parameterized testing frequently 25 | * utilized in this package. 26 | * 27 | * Since JUnit5 requires `@JvmStatic` on its `@MethodSource` argument factory methods, this requires all 28 | * of the argument lists to reside in the companion object of a test class. This can be annoying since it 29 | * forces the test to be separated from its tests cases. 30 | * 31 | * Classes that derive from this class can be defined near the `@ParameterizedTest` functions instead. 32 | */ 33 | abstract class ArgumentsProviderBase : ArgumentsProvider { 34 | 35 | abstract fun getParameters(): List 36 | 37 | @Throws(Exception::class) 38 | override fun provideArguments(extensionContext: ExtensionContext): Stream? { 39 | return getParameters().map { Arguments.of(it) }.stream() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/domain/model/NamedElement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain.model 17 | 18 | import com.amazon.ionelement.api.MetaContainer 19 | 20 | /** An element of a product or record. */ 21 | data class NamedElement( 22 | /** The name of the element that should be used in generated code. */ 23 | val identifier: String, 24 | /** The tag used in the s-expression representation, if this is a record element. */ 25 | val tag: String, 26 | /** A reference to the type of this element.*/ 27 | val typeReference: TypeRef, 28 | val metas: MetaContainer 29 | ) { 30 | override fun equals(other: Any?): Boolean { 31 | if (this === other) return true 32 | if (other !is NamedElement) return false 33 | 34 | if (identifier != other.identifier) return false 35 | if (tag != other.tag) return false 36 | if (typeReference != other.typeReference) return false 37 | // Metas intentionally omitted here 38 | 39 | return true 40 | } 41 | 42 | override fun hashCode(): Int { 43 | var result = identifier.hashCode() 44 | result = 31 * result + tag.hashCode() 45 | result = 31 * result + typeReference.hashCode() 46 | // Metas intentionally omitted here 47 | 48 | return result 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pig/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | import java.io.FileOutputStream 16 | import java.util.Properties 17 | 18 | plugins { 19 | id("application") 20 | id("pig.conventions") 21 | id("org.partiql.pig.gradle.publish") 22 | } 23 | 24 | publish { 25 | artifactId = "partiql-ir-generator" 26 | name = "PartiQL I.R. Generator (a.k.a P.I.G.)" 27 | } 28 | 29 | val propertiesDir = "$buildDir/properties" 30 | 31 | dependencies { 32 | implementation("org.freemarker:freemarker:2.3.30") 33 | implementation("net.sf.jopt-simple:jopt-simple:5.0.4") 34 | implementation("com.amazon.ion:ion-element:1.0.0") 35 | } 36 | 37 | application { 38 | mainClass.set("org.partiql.pig.MainKt") 39 | } 40 | 41 | tasks.register("generateProperties") { 42 | doLast { 43 | val propertiesFile = file("$propertiesDir/pig.properties") 44 | propertiesFile.parentFile.mkdirs() 45 | val properties = Properties() 46 | properties.setProperty("version", version.toString()) 47 | val out = FileOutputStream(propertiesFile) 48 | properties.store(out, null) 49 | } 50 | } 51 | 52 | tasks.named("processResources") { 53 | dependsOn("generateProperties") 54 | } 55 | 56 | sourceSets { 57 | main { 58 | output.dir(propertiesDir) 59 | } 60 | } 61 | 62 | tasks.build { 63 | finalizedBy(tasks.installDist) 64 | } 65 | -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/CustomMetasTests.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.tests 2 | 3 | import com.amazon.ionelement.api.MetaContainer 4 | import com.amazon.ionelement.api.metaContainerOf 5 | import org.junit.jupiter.api.Assertions.assertEquals 6 | import org.junit.jupiter.api.Test 7 | import org.partiql.pig.tests.generated.TestDomain 8 | 9 | /** 10 | * Demonstrates and tests a custom builder implementation that allocates unique IDs to each node constructed with it. 11 | */ 12 | class CustomMetasTests { 13 | 14 | /** Custom builder implementation that assigns unique Ids to all nodes. */ 15 | class NodeIdAssigningBuilder : TestDomain.Builder { 16 | private var nextNodeId = 0 17 | override fun newMetaContainer(): MetaContainer = mapOf("nodeId" to nextNodeId++) 18 | } 19 | 20 | /** 21 | * Typically, a builder function such as this should be created which invokes [block] on our custom 22 | * [TestDomain.Builder] instance. 23 | * 24 | * Note that the receiver type is [TestDomain.Builder] and not [NodeIdAssigningBuilder]. Callers should never 25 | * need to be aware of the specific type of [TestDomain.Builder]. 26 | */ 27 | private fun buildTestDomainWithNodeIds(block: TestDomain.Builder.() -> T) = NodeIdAssigningBuilder().block() 28 | 29 | @Test 30 | fun testCustomBuilder() { 31 | val pairPair = buildTestDomainWithNodeIds { 32 | intPairPair( 33 | intPair(42, 43), 34 | intPair(44, 45), 35 | // should still be able to overwrite the metas returned by [NodeIdAssigningBuilder.newMetaContainer], 36 | metas = metaContainerOf("nodeId" to 42) 37 | ) 38 | } 39 | 40 | // Note: nodeIds are allocated in the order the nodes are constructed. 41 | assertEquals(42, pairPair.metas["nodeId"]) 42 | assertEquals(0, pairPair.first.metas["nodeId"]) 43 | assertEquals(1, pairPair.second.metas["nodeId"]) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pig/src/test/kotlin/org/partiql/pig/generator/FreeMarkerFunctionsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator 17 | 18 | import freemarker.cache.StringTemplateLoader 19 | import org.junit.jupiter.api.Test 20 | import java.io.StringWriter 21 | import kotlin.test.assertEquals 22 | 23 | /** 24 | * A couple of tests to ensure that the functions we expose to all freemarker templates 25 | * actually work. These are exposed to templates for all language targets. 26 | */ 27 | class FreeMarkerFunctionsTest { 28 | 29 | private fun applyDummyTemplate(template: String): String { 30 | val cfg = createDefaultFreeMarkerConfiguration() 31 | val loader = StringTemplateLoader() 32 | loader.putTemplate("dummyTemplate", template) 33 | cfg.templateLoader = loader 34 | 35 | val t = cfg.getTemplate("dummyTemplate") 36 | val sw = StringWriter() 37 | t.process(null, sw) 38 | return sw.toString() 39 | } 40 | 41 | @Test 42 | fun snakeToCamelCase() { 43 | // ${'$'} inserts a literal $ in the string 44 | assertEquals("fooBar", applyDummyTemplate("""${'$'}{snakeToCamelCase("foo_bar")}""")) 45 | } 46 | 47 | @Test 48 | fun snakeToPascalCase() { 49 | // ${'$'} inserts a literal $ in the string 50 | assertEquals("FooBar", applyDummyTemplate("""${'$'}{snakeToPascalCase("foo_bar")}""")) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pig-tests/src/test/pig/toy-lang.ion: -------------------------------------------------------------------------------- 1 | 2 | // These type domains are used to help come up with accurate samples for the main `README.md` file at the root 3 | // of this project. They are not used by tests. They are left here to aid maintenance of the `README.md` that 4 | // will be needed when changes to the generated code are made. 5 | 6 | // This is an "AST" for a simple hypothetical language named "Toy". 7 | // Toy has literals, variables, basic arithmetic and functions that accept a single argument. 8 | (define toy_lang 9 | (domain 10 | (sum operator (plus) (minus) (times) (divide) (modulo)) 11 | (sum expr 12 | (lit value::ion) 13 | (variable name::symbol) 14 | (not expr::expr) 15 | (nary op::operator operands::(* expr 0)) 16 | (let name::symbol value::expr body::expr) 17 | (function var_name::symbol body::expr)))) 18 | 19 | // Define another type domain which extends "toy_lang" by removing named variables and adding DeBruijn indices: 20 | (define toy_lang_indexed 21 | (permute_domain toy_lang 22 | (with expr 23 | (exclude variable let) 24 | (include 25 | // We are adding the `index` element here. 26 | (variable name::symbol index::int) 27 | (let name::symbol index::int value::expr body::expr))))) 28 | 29 | 30 | 31 | (transform toy_lang toy_lang_indexed) 32 | (transform toy_lang_indexed toy_lang) 33 | 34 | (define calculator_ast 35 | (domain 36 | (sum operator (plus) (minus) (times) (divide) (modulo)) 37 | (sum expr 38 | (lit value::int) 39 | (binary op::operator left::expr right::expr) 40 | ) 41 | ) 42 | ) 43 | 44 | 45 | (define enhanced_calculator_ast 46 | (domain 47 | (sum expr 48 | (lit value::int) 49 | (binary op::operator left::expr right::expr) 50 | (let name::symbol value::expr body::expr) 51 | (variable name::symbol) 52 | ) 53 | 54 | (sum operator (plus) (minus) (times) (divide) (modulo)) 55 | ) 56 | ) -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/DomainVisitorBase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.AnyElement 19 | import com.amazon.ionelement.api.MetaContainer 20 | 21 | open class DomainVisitorBase { 22 | 23 | protected open fun visitBoolPrimitive(node: BoolPrimitive) { 24 | // default does nothing 25 | } 26 | 27 | protected open fun visitLongPrimitive(node: LongPrimitive) { 28 | // default does nothing 29 | } 30 | 31 | protected open fun visitSymbolPrimitive(node: SymbolPrimitive) { 32 | // default does nothing 33 | } 34 | 35 | protected open fun visitAnyElement(node: AnyElement) { 36 | // default does nothing 37 | } 38 | 39 | protected open fun visitMetas(metas: MetaContainer) { 40 | // default does nothing. 41 | } 42 | 43 | // ///////////////////////////////////////////////////// 44 | 45 | open fun walkBoolPrimitive(node: BoolPrimitive) { 46 | visitBoolPrimitive(node) 47 | walkMetas(node.metas) 48 | } 49 | 50 | open fun walkLongPrimitive(node: LongPrimitive) { 51 | visitLongPrimitive(node) 52 | walkMetas(node.metas) 53 | } 54 | 55 | open fun walkSymbolPrimitive(node: SymbolPrimitive) { 56 | visitSymbolPrimitive(node) 57 | walkMetas(node.metas) 58 | } 59 | 60 | open fun walkAnyElement(node: AnyElement) { 61 | visitAnyElement(node) 62 | walkMetas(node.metas) 63 | } 64 | 65 | open fun walkMetas(metas: MetaContainer) { 66 | visitMetas(metas) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/domain/model/TypeRef.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain.model 17 | 18 | import com.amazon.ionelement.api.IonElement 19 | import com.amazon.ionelement.api.MetaContainer 20 | import com.amazon.ionelement.api.ionInt 21 | import com.amazon.ionelement.api.ionSexpOf 22 | import com.amazon.ionelement.api.ionSymbol 23 | 24 | data class TypeRef(val typeName: String, val arity: Arity, val metas: MetaContainer) { 25 | /** 26 | * Generates an s-expression representation of this [TypeRef]. 27 | * 28 | * This primarily aids in unit testing and is not intended to have an identical structure to PIG's type universe 29 | * syntax. 30 | */ 31 | fun toIonElement(): IonElement = 32 | when (arity) { 33 | Arity.Required -> ionSymbol(typeName) 34 | Arity.Optional -> ionSexpOf(ionSymbol("?"), ionSymbol(typeName)) 35 | is Arity.Variadic -> ionSexpOf(ionSymbol("*"), ionSymbol(typeName), ionInt(arity.minimumArity.toLong())) 36 | } 37 | 38 | override fun equals(other: Any?): Boolean { 39 | if (this === other) return true 40 | if (other !is TypeRef) return false 41 | 42 | if (typeName != other.typeName) return false 43 | if (arity != other.arity) return false 44 | // Metas intentionally omitted here 45 | 46 | return true 47 | } 48 | 49 | override fun hashCode(): Int { 50 | var result = typeName.hashCode() 51 | result = 31 * result + arity.hashCode() 52 | // Metas intentionally omitted here 53 | 54 | return result 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pig/src/test/kotlin/org/partiql/pig/util/CapitalizerTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.util 17 | 18 | import org.junit.jupiter.api.Test 19 | import kotlin.test.assertEquals 20 | 21 | class CapitalizerTests { 22 | 23 | @Test 24 | fun snakeToPascalCase() { 25 | assertEquals("One", "one".snakeToPascalCase()) 26 | assertEquals("One", "_one".snakeToPascalCase()) 27 | assertEquals("One", "one_".snakeToPascalCase()) 28 | assertEquals("One", "_one_".snakeToPascalCase()) 29 | assertEquals("OneTwo", "one_two".snakeToPascalCase()) 30 | assertEquals("OneTwo", "_one_two".snakeToPascalCase()) 31 | assertEquals("OneTwo", "one_two_".snakeToPascalCase()) 32 | assertEquals("OneTwo", "_one_two_".snakeToPascalCase()) 33 | assertEquals("OneTwoThree", "one_two_three".snakeToPascalCase()) 34 | assertEquals("OneTwo", "one__two".snakeToPascalCase()) 35 | } 36 | @Test 37 | fun snakeToCamelCase() { 38 | assertEquals("one", "one".snakeToCamelCase()) 39 | assertEquals("one", "_one".snakeToCamelCase()) 40 | assertEquals("one", "one_".snakeToCamelCase()) 41 | assertEquals("one", "_one_".snakeToCamelCase()) 42 | assertEquals("oneTwo", "one_two".snakeToCamelCase()) 43 | assertEquals("oneTwo", "_one_two".snakeToCamelCase()) 44 | assertEquals("oneTwo", "one_two_".snakeToCamelCase()) 45 | assertEquals("oneTwo", "_one_two_".snakeToCamelCase()) 46 | assertEquals("oneTwoThree", "one_two_three".snakeToCamelCase()) 47 | assertEquals("oneTwo", "one__two".snakeToCamelCase()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/SymbolPrimitive.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.IonElement 19 | import com.amazon.ionelement.api.MetaContainer 20 | import com.amazon.ionelement.api.ionSymbol 21 | import com.amazon.ionelement.api.metaContainerOf 22 | 23 | /** 24 | * Represents a symbol value that is part of a generated type domain. 25 | * 26 | * This is needed to allow such values to have metas. 27 | */ 28 | class SymbolPrimitive(val text: String, override val metas: MetaContainer) : DomainNode { 29 | 30 | /** Creates a copy of the current node with the specified values. */ 31 | fun copy(text: String = this.text, metas: MetaContainer = this.metas): SymbolPrimitive = 32 | SymbolPrimitive(text, metas) 33 | 34 | /** Creates a copy of the current [SymbolPrimitive] with [metas] as the new metas. */ 35 | override fun copy(metas: MetaContainer): SymbolPrimitive = 36 | SymbolPrimitive(text, metas) 37 | 38 | /** Creates a copy of the current [SymbolPrimitive] with the specified additional meta. */ 39 | override fun withMeta(metaKey: String, metaValue: Any): SymbolPrimitive = 40 | SymbolPrimitive(text, metas + metaContainerOf(metaKey to metaValue)) 41 | 42 | /** Creates an [IonElement] representation of the current [SymbolPrimitive]. */ 43 | override fun toIonElement(): IonElement = ionSymbol(text, metas = metas) 44 | 45 | override fun toString(): String = text 46 | 47 | override fun equals(other: Any?): Boolean { 48 | if (this === other) return true 49 | if (javaClass != other?.javaClass) return false 50 | 51 | other as SymbolPrimitive 52 | 53 | if (text != other.text) return false 54 | 55 | return true 56 | } 57 | 58 | override fun hashCode(): Int { 59 | return text.hashCode() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/LongPrimitive.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.IonElement 19 | import com.amazon.ionelement.api.MetaContainer 20 | import com.amazon.ionelement.api.ionInt 21 | import com.amazon.ionelement.api.metaContainerOf 22 | 23 | /** 24 | * Represents a long value that is part of a generated type domain. 25 | * 26 | * This is needed to allow such values to have metas. 27 | */ 28 | class LongPrimitive(val value: Long, override val metas: MetaContainer) : DomainNode { 29 | 30 | /** Creates a copy of the current node with the specified values. */ 31 | fun copy(value: Long = this.value, metas: MetaContainer = this.metas): LongPrimitive = 32 | LongPrimitive(value, metas) 33 | 34 | /** Creates a copy of the current [LongPrimitive] with [metas] as the new metas. */ 35 | override fun copy(metas: MetaContainer): LongPrimitive = 36 | LongPrimitive(value, metas) 37 | 38 | /** Creates a copy of the current [LongPrimitive] with the specified additional meta. */ 39 | override fun withMeta(metaKey: String, metaValue: Any): LongPrimitive = 40 | LongPrimitive(this.value, metas + metaContainerOf(metaKey to metaValue)) 41 | 42 | /** Creates an [IonElement] representation of the current [LongPrimitive]. */ 43 | override fun toIonElement(): IonElement = ionInt(value, metas = metas) 44 | 45 | /** Converts [value] to a string. */ 46 | override fun toString(): String = value.toString() 47 | 48 | override fun equals(other: Any?): Boolean { 49 | if (this === other) return true 50 | if (javaClass != other?.javaClass) return false 51 | 52 | other as LongPrimitive 53 | 54 | if (value != other.value) return false 55 | 56 | return true 57 | } 58 | 59 | override fun hashCode(): Int { 60 | return value.hashCode() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/BoolPrimitive.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.IonElement 19 | import com.amazon.ionelement.api.MetaContainer 20 | import com.amazon.ionelement.api.ionBool 21 | import com.amazon.ionelement.api.metaContainerOf 22 | 23 | /** 24 | * Represents a boolean value that is part of a generated type domain. 25 | * 26 | * This is needed to allow such values to have metas. 27 | */ 28 | class BoolPrimitive(val value: Boolean, override val metas: MetaContainer) : DomainNode { 29 | 30 | /** Creates a copy of the current node with the specified values. */ 31 | fun copy(value: Boolean = this.value, metas: MetaContainer = this.metas): BoolPrimitive = 32 | BoolPrimitive(value, metas) 33 | 34 | /** Creates a copy of the current [BoolPrimitive] with [metas] as the new metas. */ 35 | override fun copy(metas: MetaContainer): BoolPrimitive = 36 | BoolPrimitive(value, metas) 37 | 38 | /** Creates a copy of the current [BoolPrimitive] with the specified additional meta. */ 39 | override fun withMeta(metaKey: String, metaValue: Any): BoolPrimitive = 40 | BoolPrimitive(this.value, metas + metaContainerOf(metaKey to metaValue)) 41 | 42 | /** Creates an [IonElement] representation of the current [BoolPrimitive]. */ 43 | override fun toIonElement(): IonElement = ionBool(value, metas = metas) 44 | 45 | /** Converts [value] to a string. */ 46 | override fun toString(): String = value.toString() 47 | 48 | override fun equals(other: Any?): Boolean { 49 | if (this === other) return true 50 | if (javaClass != other?.javaClass) return false 51 | 52 | other as BoolPrimitive 53 | 54 | if (value != other.value) return false 55 | 56 | return true 57 | } 58 | 59 | override fun hashCode(): Int { 60 | return value.hashCode() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/SmokeTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.tests 17 | 18 | import com.amazon.ionelement.api.ElementType 19 | import com.amazon.ionelement.api.ionInt 20 | import com.amazon.ionelement.api.ionNull 21 | import org.junit.jupiter.api.Assertions.assertEquals 22 | import org.junit.jupiter.api.Test 23 | import org.partiql.pig.tests.generated.PartiqlBasic 24 | import org.partiql.pig.tests.generated.ToyLang 25 | 26 | class SmokeTests { 27 | 28 | @Test 29 | fun toy_lang_test() { 30 | val node = ToyLang.build { 31 | nary(plus(), variable("foo"), lit(ionInt(42)), lit(ionNull(ElementType.STRING))) 32 | } 33 | 34 | val expectedIonElement = node.toIonElement() 35 | 36 | val roundTrippedNode = ToyLang.transform(expectedIonElement) 37 | 38 | val roundTrippedElement = roundTrippedNode.toIonElement() 39 | assertEquals(expectedIonElement, roundTrippedElement) 40 | } 41 | 42 | @Test 43 | fun partiql_basic_test() { 44 | val node = PartiqlBasic.build { 45 | select( 46 | all(), 47 | projectList( 48 | projectExpr( 49 | plus( 50 | lit(ionInt(1)), 51 | lit(ionInt(41)) 52 | ), 53 | "select_alias" 54 | ) 55 | ), 56 | scan( 57 | id("foo", caseSensitive(), unqualified()), 58 | "as_foo", 59 | "at_foo", 60 | "by_foo" 61 | ) 62 | ) 63 | } 64 | 65 | val expectedIiv = node.toIonElement() 66 | 67 | val roundTrippedNode = PartiqlBasic.transform(expectedIiv) 68 | 69 | val roundTrippedIiv = roundTrippedNode.toIonElement() 70 | 71 | assertEquals(expectedIiv, roundTrippedIiv) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/DomainVisitorFoldBase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.AnyElement 19 | import com.amazon.ionelement.api.MetaContainer 20 | 21 | open class DomainVisitorFoldBase { 22 | 23 | protected open fun visitBoolPrimitive(node: BoolPrimitive, accumulator: T): T = 24 | // default does nothing 25 | accumulator 26 | 27 | protected open fun visitLongPrimitive(node: LongPrimitive, accumulator: T): T = 28 | // default does nothing 29 | accumulator 30 | 31 | protected open fun visitSymbolPrimitive(node: SymbolPrimitive, accumulator: T): T = 32 | // default does nothing 33 | accumulator 34 | 35 | protected open fun visitAnyElement(node: AnyElement, accumulator: T): T = 36 | // default does nothing 37 | accumulator 38 | 39 | protected open fun visitMetas(node: MetaContainer, accumulator: T): T = 40 | // default does nothing 41 | accumulator 42 | 43 | // ///////////////////////////////////////////////////// 44 | 45 | open fun walkBoolPrimitive(node: BoolPrimitive, accumulator: T): T { 46 | val intermediate = visitBoolPrimitive(node, accumulator) 47 | return walkMetas(node.metas, intermediate) 48 | } 49 | 50 | open fun walkLongPrimitive(node: LongPrimitive, accumulator: T): T { 51 | val intermediate = visitLongPrimitive(node, accumulator) 52 | return walkMetas(node.metas, intermediate) 53 | } 54 | 55 | open fun walkSymbolPrimitive(node: SymbolPrimitive, accumulator: T): T { 56 | val intermediate = visitSymbolPrimitive(node, accumulator) 57 | return walkMetas(node.metas, intermediate) 58 | } 59 | 60 | open fun walkAnyElement(node: AnyElement, accumulator: T): T { 61 | var intermediate = visitAnyElement(node, accumulator) 62 | return walkMetas(node.metas, intermediate) 63 | } 64 | 65 | open fun walkMetas(node: MetaContainer, accumulator: T): T { 66 | return visitMetas(node, accumulator) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/domain/model/TypeUniverse.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain.model 17 | 18 | import org.partiql.pig.errors.PigException 19 | 20 | data class TypeUniverse(val statements: List) { 21 | 22 | /** 23 | * Returns a list of the original [TypeDomain] instances and the [TypeDomain]s computed from [PermutedDomain] 24 | * instances, performing detailed semantic checking along the way. 25 | * 26 | * @throws [PigException] when the first semantic error is encountered. 27 | */ 28 | fun computeTypeDomains(): List { 29 | if (statements.none()) { 30 | semanticError(null, SemanticErrorContext.EmptyUniverse) 31 | } 32 | 33 | val domains = mutableMapOf() 34 | statements.filter { it !is Transform }.map { stmt -> 35 | val typeDomain = when (stmt) { 36 | is Transform -> error("should not happen") 37 | is TypeDomain -> stmt 38 | is PermutedDomain -> { 39 | // Note that we compute the [TypeDomain] for the [PermutedDomain] *before* semantic checking. 40 | stmt.computePermutation(domains) 41 | } 42 | } 43 | 44 | typeDomain.checkSemantics() 45 | if (domains.putIfAbsent(typeDomain.tag, typeDomain) != null) { 46 | semanticError(typeDomain.metas, SemanticErrorContext.DuplicateTypeDomainName(typeDomain.tag)) 47 | } else { 48 | null 49 | } 50 | } 51 | 52 | statements.filterIsInstance().forEach { 53 | if (!domains.containsKey(it.sourceDomainTag)) { 54 | semanticError(it.metas, SemanticErrorContext.SourceDomainDoesNotExist(it.sourceDomainTag)) 55 | } 56 | if (!domains.containsKey(it.destinationDomainTag)) { 57 | semanticError(it.metas, SemanticErrorContext.DestinationDomainDoesNotExist(it.destinationDomainTag)) 58 | } 59 | } 60 | 61 | return domains.values.toList() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/PrimitiveUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.AnyElement 19 | import com.amazon.ionelement.api.MetaContainer 20 | import com.amazon.ionelement.api.emptyMetaContainer 21 | 22 | fun AnyElement.toBoolPrimitive() = 23 | BoolPrimitive(this.booleanValue, this.metas) 24 | 25 | fun AnyElement.toLongPrimitive() = 26 | LongPrimitive(this.longValue, this.metas) 27 | 28 | fun AnyElement.toSymbolPrimitive() = 29 | SymbolPrimitive(this.symbolValue, this.metas) 30 | 31 | fun Boolean.asPrimitive(metas: MetaContainer = emptyMetaContainer()) = 32 | BoolPrimitive(this, metas) 33 | 34 | fun Long.asPrimitive(metas: MetaContainer = emptyMetaContainer()) = 35 | LongPrimitive(this, metas) 36 | 37 | fun Int.asPrimitive(metas: MetaContainer = emptyMetaContainer()) = 38 | LongPrimitive(this.toLong(), metas) 39 | 40 | fun String.asPrimitive(metas: MetaContainer = emptyMetaContainer()) = 41 | SymbolPrimitive(this, metas) 42 | 43 | /** Converts the specified list of [Long] [values] to a list of [LongPrimitive]. */ 44 | fun listOfPrimitives(vararg values: Long) = values.map { it.asPrimitive() } 45 | 46 | /** Converts the specified list of [Long] [values] to a list of [StringPrimitive]. */ 47 | fun listOfPrimitives(vararg values: String) = values.map { it.asPrimitive() } 48 | 49 | /** 50 | * This function behaves identically to `listOf()`. 51 | * 52 | * This exists to reduce the complexity of the `kotlin.ftl` template. Without it, the template would have to know 53 | * if a call to `listOf()` or `listOfPrimitives()` should be emitted. 54 | */ 55 | fun listOfPrimitives(vararg values: LongPrimitive) = values.toList() 56 | 57 | /** 58 | * This function behaves identically to `listOf()`. 59 | * 60 | * This exists to reduce the complexity of the `kotlin.ftl` template. Without it, the template would have to know 61 | * if a call to `listOf()` or `listOfPrimitives()` should be emitted. 62 | */ 63 | fun listOfPrimitives(vararg values: SymbolPrimitive) = values.toList() 64 | -------------------------------------------------------------------------------- /pig/src/test/kotlin/org/partiql/pig/generator/ion/GenerateIonTest.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.generator.ion 2 | 3 | import com.amazon.ion.system.IonReaderBuilder 4 | import org.junit.jupiter.api.Test 5 | import org.partiql.pig.domain.parser.parseTypeUniverse 6 | import java.io.PrintWriter 7 | import java.io.StringWriter 8 | import kotlin.test.assertEquals 9 | 10 | class GenerateIonTest { 11 | @Test 12 | fun `should generate canonical Ion domains`() { 13 | // Typical type universe (contains domain definitions, permutations, etc.) 14 | val typeUniverseWithExtensions = """ 15 | (define test_domain 16 | (domain 17 | (product pair first::ion second::ion) 18 | (product other_pair first::symbol second::symbol) 19 | (sum thing 20 | (a x::pair) 21 | (b y::symbol) 22 | (c z::int)))) 23 | 24 | (define permuted_domain 25 | (permute_domain test_domain 26 | (exclude pair) 27 | (include 28 | (product pair n::int t::int) 29 | (product new_pair m::symbol n::int)) 30 | (with thing 31 | (exclude a) 32 | (include 33 | (d a::pair) 34 | (e b::symbol))))) 35 | """ 36 | 37 | // The expected type universe should only contain pure domain definitions (i.e. no permutations) 38 | val expectedDomains = """ 39 | (define test_domain 40 | (domain 41 | (product pair first::ion second::ion) 42 | (product other_pair first::symbol second::symbol) 43 | (sum thing 44 | (a x::pair) 45 | (b y::symbol) 46 | (c z::int)))) 47 | 48 | (define permuted_domain 49 | (domain 50 | (product other_pair first::symbol second::symbol) 51 | (sum thing 52 | (b y::symbol) 53 | (c z::int) 54 | (d a::pair) 55 | (e b::symbol)) 56 | (product pair n::int t::int) 57 | (product new_pair m::symbol n::int))) 58 | """ 59 | 60 | val td = IonReaderBuilder.standard().build(typeUniverseWithExtensions).use { parseTypeUniverse(it) } 61 | 62 | val concretes = td.computeTypeDomains() 63 | 64 | val writer = StringWriter() 65 | generateIon(concretes, PrintWriter(writer)) 66 | val output = writer.toString() 67 | 68 | val actual = IonReaderBuilder.standard().build(output).use { parseTypeUniverse(it) } 69 | 70 | val expected = IonReaderBuilder.standard().build(expectedDomains).use { parseTypeUniverse(it) } 71 | 72 | assertEquals(expected, actual, "Computed domains should match") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pig-runtime/src/test/kotlin/org/partiql/pig/runtime/LongPrimitiveTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.emptyMetaContainer 19 | import com.amazon.ionelement.api.ionInt 20 | import com.amazon.ionelement.api.metaContainerOf 21 | import org.junit.jupiter.api.Test 22 | import kotlin.test.assertEquals 23 | import kotlin.test.assertNotEquals 24 | 25 | class LongPrimitiveTests { 26 | @Test 27 | fun copy() { 28 | val l = 42.asPrimitive(metaContainerOf("foo" to 43)) 29 | assertEquals(42, l.value) 30 | assertEquals(metaContainerOf("foo" to 43), l.metas) 31 | 32 | val l2 = l.copy(value = 43) 33 | assertEquals(43, l2.value) 34 | assertEquals(metaContainerOf("foo" to 43), l2.metas) 35 | 36 | val l3 = l.copy(metas = metaContainerOf("foo" to 88)) 37 | assertEquals(42, l3.value) 38 | assertEquals(metaContainerOf("foo" to 88), l3.metas) 39 | } 40 | 41 | @Test 42 | fun withMeta() { 43 | val l = 42.asPrimitive() 44 | assertEquals(42, l.value) 45 | assertEquals(emptyMetaContainer(), l.metas) 46 | 47 | val l2 = l.withMeta("foo", 42) 48 | assertEquals(42, l2.value) 49 | assertEquals(metaContainerOf("foo" to 42), l2.metas) 50 | } 51 | 52 | @Test 53 | fun toIonElement() { 54 | val elem = 42.asPrimitive(metaContainerOf("foo" to 43)).toIonElement() 55 | assertEquals(ionInt(42), elem) 56 | assertEquals(metaContainerOf("foo" to 43), elem.metas) 57 | } 58 | 59 | @Test 60 | fun toStringTest() { 61 | assertEquals("12345", 12345.asPrimitive().toString()) 62 | assertEquals("12345", 12345.asPrimitive(metaContainerOf("foo" to 43)).toString()) 63 | } 64 | 65 | @Test 66 | fun equalsAndHashCode() { 67 | val l1 = 42.asPrimitive() 68 | val l2 = 42.asPrimitive(metaContainerOf("foo" to 43)) 69 | val l3 = 24.asPrimitive() 70 | 71 | // metas should not effect equivalence 72 | assertEquals(l1, l2) 73 | assertEquals(l1.hashCode(), l2.hashCode()) 74 | 75 | assertNotEquals(l1, l3) 76 | assertNotEquals(l2, l3) 77 | // Note: hashCode is *likely* to be different, but it is not guaranteed, so therefore no assertion for that. 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pig-tests/src/test/pig/partiql-basic.ion: -------------------------------------------------------------------------------- 1 | 2 | 3 | // This is an incomplete AST definition for PartiQL to show what a more complex domain definition will look like. 4 | // Other than being incomplete, this differs from the V0 AST: the names/tags are *unique* as outlined in Pig's 5 | // README.md 6 | 7 | (define partiql_basic 8 | (domain 9 | (sum projection 10 | (project_list items::(* project_item 1)) 11 | (project_value value::expr)) 12 | 13 | (sum project_item 14 | (project_all) 15 | (project_expr value::expr as_alias::(? symbol))) 16 | 17 | (sum join_type 18 | (inner) 19 | (left) 20 | (right) 21 | (outer)) 22 | 23 | (sum from_source 24 | (scan expr::expr as_alias::(? symbol) at_alias::(? symbol) by_alias::(? symbol)) 25 | (join type::join_type left::from_source right::from_source predicate::(? expr))) 26 | 27 | (product expr_pair first::expr second::expr) 28 | 29 | (product group_by_item value::expr as_alias::(? symbol)) 30 | 31 | (product group_by_list 32 | items::(* group_by_item 1)) 33 | 34 | (product group_by 35 | items::group_by_list 36 | group_as_alias::(? symbol)) // group as alias 37 | (sum case_sensitivity 38 | (case_sensitive) 39 | (case_insensitive)) 40 | 41 | (sum scope_qualifier 42 | (unqualified) 43 | (qualified)) 44 | 45 | (sum set_quantifier 46 | (all) 47 | (distinct)) 48 | 49 | (sum path_element 50 | (path_expr expr::expr) 51 | (path_wildcard) 52 | (path_unpivot)) 53 | 54 | (sum expr 55 | // Basic Expressions 56 | (lit value::ion) 57 | (id name::symbol case::case_sensitivity scope_qualifier::scope_qualifier) 58 | (parameter index::int) 59 | (not expr::expr) 60 | (plus operands::(* expr 2)) 61 | (minus operands::(* expr 2)) 62 | (times operands::(* expr 2)) 63 | (divide operands::(* expr 2)) 64 | (modulo operands::(* expr 2)) 65 | (concat operands::(* expr 2)) 66 | 67 | (like left::expr right::expr escape::expr) 68 | (between value::expr from::expr to::expr) 69 | (path root::expr elements::(* path_element 1)) 70 | (call name::symbol args::(* expr 1)) 71 | (call_agg name::symbol set_quantifier::set_quantifier arg::expr) 72 | 73 | // Case Statements 74 | (simple_case value::expr branches::(* expr_pair 1)) 75 | (searched_case branches::(* expr_pair 1)) 76 | 77 | // Value Constructors 78 | (struct fields::(* expr_pair 0)) 79 | (bag values::(* expr 0)) 80 | (list values::(* expr 0)) 81 | 82 | (select 83 | (setq (? set_quantifier)) 84 | (project projection) 85 | (from from_source) 86 | (where (? expr)) // where 87 | (group (? group_by)) 88 | (having (? expr)) // having 89 | (limit (? expr)))) // limit 90 | ) // end domain 91 | ) // end define 92 | 93 | 94 | -------------------------------------------------------------------------------- /pig-runtime/src/test/kotlin/org/partiql/pig/runtime/SymbolPrimitiveTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.emptyMetaContainer 19 | import com.amazon.ionelement.api.ionSymbol 20 | import com.amazon.ionelement.api.metaContainerOf 21 | import org.junit.jupiter.api.Test 22 | import kotlin.test.assertEquals 23 | import kotlin.test.assertNotEquals 24 | 25 | class SymbolPrimitiveTests { 26 | @Test 27 | fun copy() { 28 | val s = "bat".asPrimitive(metaContainerOf("foo" to 43)) 29 | assertEquals("bat", s.text) 30 | assertEquals(metaContainerOf("foo" to 43), s.metas) 31 | 32 | val s2 = s.copy(text = "baz") 33 | assertEquals("baz", s2.text) 34 | assertEquals(metaContainerOf("foo" to 43), s2.metas) 35 | 36 | val s3 = s.copy(metas = metaContainerOf("foo" to 88)) 37 | assertEquals("bat", s3.text) 38 | assertEquals(metaContainerOf("foo" to 88), s3.metas) 39 | } 40 | 41 | @Test 42 | fun withMeta() { 43 | val s = "bat".asPrimitive() 44 | assertEquals("bat", s.text) 45 | assertEquals(emptyMetaContainer(), s.metas) 46 | 47 | val s2 = s.withMeta("foo", 42) 48 | assertEquals("bat", s2.text) 49 | assertEquals(metaContainerOf("foo" to 42), s2.metas) 50 | } 51 | 52 | @Test 53 | fun toIonElement() { 54 | val elem = "bat".asPrimitive(metaContainerOf("foo" to 43)).toIonElement() 55 | assertEquals(ionSymbol("bat"), elem) 56 | assertEquals(metaContainerOf("foo" to 43), elem.metas) 57 | } 58 | 59 | @Test 60 | fun toStringTest() { 61 | assertEquals("foobar", "foobar".asPrimitive().toString()) 62 | assertEquals("foobar", "foobar".asPrimitive(metaContainerOf("foo" to 43)).toString()) 63 | } 64 | 65 | @Test 66 | fun equalsAndHashCode() { 67 | val s1 = "bat".asPrimitive() 68 | val s2 = "bat".asPrimitive(metaContainerOf("foo" to 43)) 69 | val s3 = "baz".asPrimitive() 70 | 71 | // metas should not effect equivalence 72 | assertEquals(s1, s2) 73 | assertEquals(s1.hashCode(), s2.hashCode()) 74 | 75 | assertNotEquals(s1, s3) 76 | assertNotEquals(s2, s3) 77 | // Note: hashCode is *likely* to be different, but it is not guaranteed, so therefore no assertion for that. 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /pig/src/main/resources/org/partiql/pig/templates/kotlin-visitor.ftl: -------------------------------------------------------------------------------- 1 | [#ftl output_format="plainText"] 2 | [#-- @ftlvariable name="domain" type="org.partiql.pig.generator.kotlin.KTypeDomain" --] 3 | 4 | [#macro tuple_visitor_walker_body t visitSuffix] 5 | [@indent count=4] 6 | visit${visitSuffix}(node) 7 | [#list t.properties as p] 8 | [#if p.variadic] 9 | node.${p.kotlinName}.map { walk${p.rawTypeName}(it) } 10 | [#elseif p.nullable] 11 | node.${p.kotlinName}?.let { walk${p.rawTypeName}(it) } 12 | [#else] 13 | walk${p.rawTypeName}(node.${p.kotlinName}) 14 | [/#if] 15 | [/#list] 16 | walkMetas(node.metas) 17 | [/@indent] 18 | [/#macro] 19 | 20 | [#macro visitor_class] 21 | open class Visitor : DomainVisitorBase() { 22 | //////////////////////////////////////////////////////////////////////////// 23 | // Visit Functions 24 | //////////////////////////////////////////////////////////////////////////// 25 | 26 | [#list domain.tuples] 27 | ////////////////////////////////////// 28 | // Tuple Types 29 | ////////////////////////////////////// 30 | [#items as t] 31 | open fun visit${t.kotlinName}(node: ${domain.kotlinName}.${t.kotlinName}) { } 32 | [/#items] 33 | [/#list] 34 | [#list domain.sums as s] 35 | ////////////////////////////////////// 36 | // Sum Type: ${s.kotlinName} 37 | ////////////////////////////////////// 38 | protected open fun visit${s.kotlinName}(node: ${domain.kotlinName}.${s.kotlinName}) { } 39 | [#list s.variants as t] 40 | protected open fun visit${s.kotlinName}${t.kotlinName}(node: ${domain.kotlinName}.${s.kotlinName}.${t.kotlinName}) { } 41 | [/#list] 42 | [/#list] 43 | 44 | //////////////////////////////////////////////////////////////////////////// 45 | // Walk Functions 46 | //////////////////////////////////////////////////////////////////////////// 47 | 48 | [#list domain.tuples] 49 | ////////////////////////////////////// 50 | // Tuple Types 51 | ////////////////////////////////////// 52 | [#items as t] 53 | open fun walk${t.kotlinName}(node: ${domain.kotlinName}.${t.kotlinName}) { 54 | [@tuple_visitor_walker_body t t.kotlinName/][#t] 55 | } 56 | [/#items] 57 | [/#list] 58 | [#list domain.sums as s] 59 | ////////////////////////////////////// 60 | // Sum Type: ${s.kotlinName} 61 | ////////////////////////////////////// 62 | open fun walk${s.kotlinName}(node: ${domain.kotlinName}.${s.kotlinName}) { 63 | visit${s.kotlinName}(node) 64 | when(node) { 65 | [#list s.variants as v] 66 | is ${domain.kotlinName}.${s.kotlinName}.${v.kotlinName} -> walk${s.kotlinName}${v.kotlinName}(node) 67 | [/#list] 68 | } 69 | } 70 | 71 | [#list s.variants as t] 72 | open fun walk${s.kotlinName}${t.kotlinName}(node: ${domain.kotlinName}.${s.kotlinName}.${t.kotlinName}) { 73 | [@tuple_visitor_walker_body t "${s.kotlinName}${t.kotlinName}"/] 74 | } 75 | [/#list] 76 | [/#list] 77 | } 78 | [/#macro] -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/MultiWordDomainNamingTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.tests 17 | 18 | import org.junit.jupiter.api.Test 19 | import org.partiql.pig.tests.generated.MultiWordDomain 20 | import kotlin.test.assertEquals 21 | 22 | class MultiWordDomainNamingTest { 23 | 24 | // The main check here is if the conversion from snake_case to PascalCase is happening correctly 25 | // for all of generated identifiers. The way its tested is by the compilation fo this file, 26 | // but we include some assertions anyway just to validate our assumptions further. 27 | @Test 28 | @Suppress("ComplexRedundantLet") 29 | fun compilesCorrectly() { 30 | 31 | // MultiWordDomain products 32 | 33 | MultiWordDomain.build { aaaAaa() } 34 | 35 | MultiWordDomain.build { 36 | aaaAab(dField = 123) 37 | }.let { a: MultiWordDomain.AaaAab -> 38 | assertEquals(123, a.dField?.value) 39 | } 40 | 41 | MultiWordDomain.build { aaaAac(dField = 123, eField = "foo") }.let { a: MultiWordDomain.AaaAac -> 42 | assertEquals(123, a.dField?.value) 43 | assertEquals("foo", a.eField?.text) 44 | } 45 | 46 | MultiWordDomain.build { 47 | aaaAad(dField = listOf(1L, 2L, 3L)) 48 | }.let { a: MultiWordDomain.AaaAad -> 49 | assertEquals(listOf(1L, 2L, 3L), a.dField.map { i -> i.value }) 50 | } 51 | 52 | MultiWordDomain.build { 53 | aaaAae(dField0 = 1, dField1 = 2) 54 | }.let { a: MultiWordDomain.AaaAae -> 55 | assertEquals(listOf(1L, 2L), a.dField.map { i -> i.value }) 56 | } 57 | 58 | // SssTtt variants 59 | MultiWordDomain.build { 60 | lll(uField = 1) 61 | }.let { a: MultiWordDomain.SssTtt.Lll -> 62 | assertEquals(1L, a.uField.value) 63 | } 64 | 65 | MultiWordDomain.build { 66 | mmm(vField = "bleeg") 67 | }.let { a: MultiWordDomain.SssTtt.Mmm -> 68 | assertEquals("bleeg", a.vField.text) 69 | } 70 | 71 | // Record 72 | MultiWordDomain.build { 73 | rrr(aField = 123, bbbField = 456) 74 | }.let { a: MultiWordDomain.Rrr -> 75 | // aField uses the default name which is the same as its tag. 76 | assertEquals(123L, a.aField.value) 77 | // bbbField is the non-default name which is different than the element's tag. 78 | assertEquals(456L, a.bbbField.value) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/DomainVisitorTransformBase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.AnyElement 19 | import com.amazon.ionelement.api.MetaContainer 20 | 21 | /** 22 | * Provides basic a overridable for metas, long and symbol primitives. 23 | * 24 | * This is the super class for domain-specific transformer base-classes. 25 | */ 26 | abstract class DomainVisitorTransformBase { 27 | open fun transformMetas(metas: MetaContainer) = metas 28 | 29 | open fun transformAnyElement(node: AnyElement): AnyElement { 30 | val newMetas = transformMetas(node.metas) 31 | return if (node.metas !== newMetas) { 32 | node.copy(metas = newMetas).asAnyElement() 33 | } else { 34 | node 35 | } 36 | } 37 | 38 | // bool 39 | 40 | open fun transformBoolPrimitive(b: BoolPrimitive): BoolPrimitive { 41 | val newValue = transformBoolPrimitiveValue(b) 42 | val newMetas = transformBoolPrimitiveMetas(b) 43 | return if (b.value != newValue || b.metas !== newMetas) { 44 | BoolPrimitive(newValue, newMetas) 45 | } else { 46 | b 47 | } 48 | } 49 | 50 | open fun transformBoolPrimitiveValue(sym: BoolPrimitive): Boolean = sym.value 51 | 52 | open fun transformBoolPrimitiveMetas(b: BoolPrimitive) = transformMetas(b.metas) 53 | 54 | // long 55 | 56 | open fun transformLongPrimitive(lng: LongPrimitive): LongPrimitive { 57 | val newValue = transformLongPrimitiveValue(lng) 58 | val newMetas = transformLongPrimitiveMetas(lng) 59 | return if (lng.value != newValue || lng.metas !== newMetas) { 60 | LongPrimitive(newValue, newMetas) 61 | } else { 62 | lng 63 | } 64 | } 65 | 66 | open fun transformLongPrimitiveValue(sym: LongPrimitive): Long = sym.value 67 | 68 | open fun transformLongPrimitiveMetas(sym: LongPrimitive) = transformMetas(sym.metas) 69 | 70 | // symbol 71 | 72 | open fun transformSymbolPrimitive(sym: SymbolPrimitive): SymbolPrimitive { 73 | val newText = transformSymbolPrimitiveText(sym) 74 | val newMetas = transformSymbolPrimitiveMetas(sym) 75 | return if (sym.text != newText || sym.metas !== newMetas) { 76 | SymbolPrimitive(newText, newMetas) 77 | } else { 78 | sym 79 | } 80 | } 81 | 82 | open fun transformSymbolPrimitiveText(sym: SymbolPrimitive) = sym.text 83 | 84 | open fun transformSymbolPrimitiveMetas(sym: SymbolPrimitive) = transformMetas(sym.metas) 85 | } 86 | -------------------------------------------------------------------------------- /pig/src/test/kotlin/org/partiql/pig/generator/kotlin/KTypeDomainConverterKtTest.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.generator.kotlin 2 | 3 | import com.amazon.ion.system.IonReaderBuilder 4 | import org.junit.jupiter.api.Test 5 | import org.partiql.pig.domain.parser.parseTypeUniverse 6 | import kotlin.test.assertEquals 7 | import kotlin.test.assertTrue 8 | 9 | class KTypeDomainConverterKtTest { 10 | 11 | private val typeUniverseText = """ 12 | (define test_domain 13 | (domain 14 | (product pair first::ion second::ion) 15 | (product other_pair first::symbol second::symbol) 16 | (sum thing 17 | (a x::pair) 18 | (b y::symbol) 19 | (c z::int)))) 20 | 21 | (define permuted_domain 22 | (permute_domain test_domain 23 | (exclude pair) 24 | (include 25 | (product pair n::int t::int) 26 | (product new_pair m::symbol n::int)) 27 | (with thing 28 | (exclude a) 29 | (include 30 | (d a::pair) 31 | (e b::symbol))))) 32 | 33 | (transform test_domain permuted_domain) 34 | """ 35 | 36 | private val typeUniverse = IonReaderBuilder.standard().build(typeUniverseText).use { parseTypeUniverse(it) } 37 | private val typeDomains = typeUniverse.computeTypeDomains() 38 | 39 | @Test 40 | fun `it should convert all domains to a KTypeUniverse with transforms`() { 41 | val ktu = typeUniverse.convertToKTypeUniverse(typeDomains, typeDomains, null) 42 | 43 | val kTypeDomainNames = ktu.domains.map { it.tag } 44 | assertTrue("test_domain should be present in the kotlin type domains") { "test_domain" in kTypeDomainNames } 45 | assertTrue("permuted_domain should be present in the kotlin type domains") { "permuted_domain" in kTypeDomainNames } 46 | 47 | assertEquals(1, ktu.transforms.size, "The should be 1 transform between the test_domain and the permuted domain") 48 | } 49 | 50 | @Test 51 | fun `it should convert all filtered domains to a KTypeUniverse`() { 52 | val ktu = typeUniverse.convertToKTypeUniverse(typeDomains, typeDomains.filter { it.tag == "test_domain" }, setOf("test_domain")) 53 | 54 | val kTypeDomainNames = ktu.domains.map { it.tag } 55 | assertTrue("test_domain should be present in the kotlin type domains") { "test_domain" in kTypeDomainNames } 56 | 57 | assertEquals(0, ktu.transforms.size, "There are no transforms associated with the test_domain") 58 | } 59 | 60 | @Test 61 | fun `it should convert all filtered domains to a KTypeUniverse with transforms`() { 62 | val ktu = typeUniverse.convertToKTypeUniverse(typeDomains, typeDomains.filter { it.tag == "permuted_domain" }, setOf("permuted_domain")) 63 | 64 | val kTypeDomainNames = ktu.domains.map { it.tag } 65 | assertTrue("permuted_domain should be present in the kotlin type domains") { "permuted_domain" in kTypeDomainNames } 66 | 67 | assertEquals(1, ktu.transforms.size, "There should be a transform associated with the permuted_domain") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/IndentDirective.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator 17 | 18 | import freemarker.core.Environment 19 | import freemarker.template.TemplateDirectiveBody 20 | import freemarker.template.TemplateDirectiveModel 21 | import freemarker.template.TemplateException 22 | import freemarker.template.TemplateModel 23 | import freemarker.template.TemplateModelException 24 | import freemarker.template.TemplateNumberModel 25 | import java.io.IOException 26 | import java.io.StringWriter 27 | 28 | /** 29 | * This was shamelessly copied from https://stackoverflow.com/questions/1235179/simple-way-to-repeat-a-string-in-java 30 | * and converted to Kotlin with IntelliJ. 31 | */ 32 | class IndentDirective : TemplateDirectiveModel { 33 | 34 | @Throws(TemplateException::class, IOException::class) 35 | override fun execute( 36 | environment: Environment, 37 | parameters: Map<*, *>, 38 | templateModels: Array, 39 | body: TemplateDirectiveBody 40 | ) { 41 | var count: Int? = null 42 | val iterator = parameters.entries.iterator() 43 | while (iterator.hasNext()) { 44 | val entry = iterator.next() 45 | val name = entry.key as String 46 | val value = entry.value as TemplateModel 47 | 48 | if (name == COUNT == true) { 49 | if (value is TemplateNumberModel == false) { 50 | throw TemplateModelException("The \"$COUNT\" parameter must be a number") 51 | } 52 | count = value.asNumber.toInt() 53 | if (count < 0) { 54 | throw TemplateModelException("The \"$COUNT\" parameter cannot be negative") 55 | } 56 | } else { 57 | throw TemplateModelException("Unsupported parameter '$name'") 58 | } 59 | } 60 | if (count == null) { 61 | throw TemplateModelException("The required \"$COUNT\" parameteris missing") 62 | } 63 | 64 | val indentation = " ".repeat(count) 65 | val writer = StringWriter() 66 | body.render(writer) 67 | val string = writer.toString() 68 | val lineFeed = "\n" 69 | val containsLineFeed = string.contains(lineFeed) == true 70 | val tokens = string.split(lineFeed.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 71 | for (token in tokens) { 72 | environment.out.write(indentation + token + if (containsLineFeed == true) lineFeed else "") 73 | } 74 | } 75 | 76 | companion object { 77 | 78 | private val COUNT = "count" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /pig-runtime/src/test/kotlin/org/partiql/pig/runtime/ErrorHelpersTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.ElementType 19 | import com.amazon.ionelement.api.ionInt 20 | import com.amazon.ionelement.api.ionNull 21 | import com.amazon.ionelement.api.ionSexpOf 22 | import com.amazon.ionelement.api.ionSymbol 23 | import org.junit.jupiter.api.Assertions.assertDoesNotThrow 24 | import org.junit.jupiter.api.Assertions.assertEquals 25 | import org.junit.jupiter.api.Assertions.assertNull 26 | import org.junit.jupiter.api.Assertions.assertTrue 27 | import org.junit.jupiter.api.Test 28 | import org.junit.jupiter.api.assertThrows 29 | 30 | class ErrorHelpersTests { 31 | // Note that "arity" is the number of elements following the tag. 32 | val someSexp = ionSexpOf(ionSymbol("some_tag"), ionInt(1), ionNull(ElementType.STRING)) // <-- has an arity of 2. 33 | 34 | @Test 35 | fun requireArityOrMalformed() { 36 | 37 | assertThrows { 38 | someSexp.requireArityOrMalformed(1) 39 | }.also { 40 | assertTrue(it.message!!.contains("1..1")) 41 | } 42 | 43 | assertDoesNotThrow { 44 | someSexp.requireArityOrMalformed(2) 45 | } 46 | 47 | assertThrows { 48 | someSexp.requireArityOrMalformed(3) 49 | }.also { 50 | assertTrue(it.message!!.contains("3..3")) 51 | } 52 | } 53 | 54 | @Test 55 | fun getRequired() { 56 | assertEquals(ionInt(1), someSexp.getRequired(0)) 57 | 58 | assertThrows { 59 | someSexp.getRequired(1) 60 | }.also { 61 | assertTrue(it.message!!.contains("A non-null value is required.")) 62 | } 63 | 64 | assertThrows { 65 | someSexp.getRequired(3) 66 | }.also { 67 | assertTrue(it.message!!.contains("index 3")) 68 | } 69 | } 70 | 71 | @Test 72 | fun getRequiredIon() { 73 | assertEquals(ionInt(1), someSexp.getRequiredIon(0)) 74 | assertEquals(ionNull(ElementType.STRING), someSexp.getRequiredIon(1)) 75 | 76 | assertThrows { 77 | someSexp.getRequiredIon(2) 78 | }.also { 79 | assertTrue(it.message!!.contains("index 2")) 80 | } 81 | } 82 | 83 | @Test 84 | fun getOptional() { 85 | assertEquals(ionInt(1), someSexp.getOptional(0)) 86 | assertNull(someSexp.getOptional(1)) 87 | assertThrows { 88 | someSexp.getOptional(2) 89 | }.also { 90 | assertTrue(it.message!!.contains("index 2")) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/VisitorFoldTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.tests 17 | 18 | import com.amazon.ionelement.api.MetaContainer 19 | import org.junit.jupiter.api.Test 20 | import org.partiql.pig.runtime.LongPrimitive 21 | import org.partiql.pig.tests.generated.TestDomain 22 | import kotlin.test.assertEquals 23 | 24 | private const val NUMBER_KEY = "number" 25 | 26 | class VisitorFoldTests { 27 | class DummyVisitor : TestDomain.VisitorFold() { 28 | override fun visitLongPrimitive(node: LongPrimitive, accumulator: Long): Long = accumulator + node.value 29 | override fun visitMetas(node: MetaContainer, accumulator: Long) = 30 | when { 31 | node.containsKey(NUMBER_KEY) -> accumulator + node[NUMBER_KEY] as Int 32 | else -> accumulator 33 | } 34 | } 35 | 36 | // Because the folding walker & visitor are stateless, the same instances may be reused for all tests 37 | private val visitorFold = DummyVisitor() 38 | 39 | @Test 40 | fun visitProducts() { 41 | val node = TestDomain.build { 42 | intPairPair(intPair(1, 2), intPair(3, 4)) 43 | }.withMeta(NUMBER_KEY, 5) 44 | 45 | val result = visitorFold.walkIntPairPair(node, 0) 46 | assertEquals(15, result) 47 | } 48 | 49 | @Test 50 | fun visitRecords() { 51 | val node = TestDomain.build { 52 | domainLevelRecord(someField = 2, anotherField = "hi", optionalField = 3) 53 | }.withMeta("number", 4) 54 | 55 | val result = visitorFold.walkDomainLevelRecord(node, 0) 56 | assertEquals(9, result) 57 | } 58 | 59 | @Test 60 | fun visitSums() { 61 | val node = TestDomain.build { 62 | testSumTriplet( 63 | one(1), 64 | two(2, 3), 65 | three(4, 5, 6) 66 | ).withMeta(NUMBER_KEY, 7) 67 | } 68 | val result = visitorFold.walkTestSumTriplet(node, 0) 69 | assertEquals(28, result) 70 | } 71 | 72 | @Test 73 | fun visitProductsWithVariadicElements() { 74 | // No elements, but one meta 75 | val node1 = TestDomain.build { variadicMin0() }.withMeta(NUMBER_KEY, 1) 76 | val result1 = visitorFold.walkVariadicMin0(node1, 0) 77 | assertEquals(1, result1) 78 | 79 | // One element, and one meta. 80 | val node2 = TestDomain.build { variadicMin0(42) }.withMeta(NUMBER_KEY, 43) 81 | val result2 = visitorFold.walkVariadicMin0(node2, 0) 82 | assertEquals(85, result2) 83 | 84 | // Four elements and one meta. 85 | val node3 = TestDomain.build { variadicMin0(1, 2, 3, 4) }.withMeta(NUMBER_KEY, 5) 86 | val result3 = visitorFold.walkVariadicMin0(node3, 0) 87 | assertEquals(15, result3) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /pig/src/main/resources/org/partiql/pig/templates/kotlin-visitor-fold.ftl: -------------------------------------------------------------------------------- 1 | [#ftl output_format="plainText"] 2 | [#-- @ftlvariable name="universe" type="org.partiql.pig.generator.kotlin.KTypeUniverse" --] 3 | 4 | [#macro tuple_visitor_fold_walker_body t visitSuffix] 5 | [@indent count=4] 6 | var current = accumulator 7 | current = visit${visitSuffix}(node, current) 8 | [#list t.properties as p] 9 | [#if p.variadic] 10 | node.${p.kotlinName}.map { current = walk${p.rawTypeName}(it, current) } 11 | [#elseif p.nullable] 12 | node.${p.kotlinName}?.let { current = walk${p.rawTypeName}(it, current) } 13 | [#else] 14 | current = walk${p.rawTypeName}(node.${p.kotlinName}, current) 15 | [/#if] 16 | [/#list] 17 | current = walkMetas(node.metas, current) 18 | return current 19 | [/@indent] 20 | [/#macro] 21 | 22 | [#macro visitor_fold_class] 23 | open class VisitorFold : DomainVisitorFoldBase() { 24 | //////////////////////////////////////////////////////////////////////////// 25 | // Visit Functions 26 | //////////////////////////////////////////////////////////////////////////// 27 | 28 | [#list domain.tuples] 29 | ////////////////////////////////////// 30 | // Tuple Types 31 | ////////////////////////////////////// 32 | [#items as t] 33 | open protected fun visit${t.kotlinName}(node: ${domain.kotlinName}.${t.kotlinName}, accumulator: T): T = accumulator 34 | [/#items] 35 | [/#list] 36 | [#list domain.sums as s] 37 | ////////////////////////////////////// 38 | // Sum Type: ${s.kotlinName} 39 | ////////////////////////////////////// 40 | open protected fun visit${s.kotlinName}(node: ${domain.kotlinName}.${s.kotlinName}, accumulator: T): T = accumulator 41 | [#list s.variants as t] 42 | open protected fun visit${s.kotlinName}${t.kotlinName}(node: ${domain.kotlinName}.${s.kotlinName}.${t.kotlinName}, accumulator: T): T = accumulator 43 | [/#list] 44 | [/#list] 45 | 46 | //////////////////////////////////////////////////////////////////////////// 47 | // Walk Functions 48 | //////////////////////////////////////////////////////////////////////////// 49 | 50 | [#list domain.tuples] 51 | ////////////////////////////////////// 52 | // Tuple Types 53 | ////////////////////////////////////// 54 | [#items as t] 55 | open fun walk${t.kotlinName}(node: ${domain.kotlinName}.${t.kotlinName}, accumulator: T): T { 56 | [@tuple_visitor_fold_walker_body t t.kotlinName/][#t] 57 | } 58 | 59 | [/#items] 60 | [/#list] 61 | [#list domain.sums as s] 62 | ////////////////////////////////////// 63 | // Sum Type: ${s.kotlinName} 64 | ////////////////////////////////////// 65 | open fun walk${s.kotlinName}(node: ${domain.kotlinName}.${s.kotlinName}, accumulator: T): T { 66 | val current = visit${s.kotlinName}(node, accumulator) 67 | return when(node) { 68 | [#list s.variants as v] 69 | is ${domain.kotlinName}.${s.kotlinName}.${v.kotlinName} -> walk${s.kotlinName}${v.kotlinName}(node, current) 70 | [/#list] 71 | } 72 | } 73 | 74 | [#list s.variants as t] 75 | open fun walk${s.kotlinName}${t.kotlinName}(node: ${domain.kotlinName}.${s.kotlinName}.${t.kotlinName}, accumulator: T): T { 76 | [@tuple_visitor_fold_walker_body t "${s.kotlinName}${t.kotlinName}"/] 77 | } 78 | 79 | [/#list] 80 | [/#list] 81 | } 82 | [/#macro] -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/IonElementTransformerErrorTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.tests 17 | 18 | import com.amazon.ionelement.api.IonElementLoaderOptions 19 | import com.amazon.ionelement.api.createIonElementLoader 20 | import org.junit.jupiter.api.Assertions.assertEquals 21 | import org.junit.jupiter.api.assertThrows 22 | import org.junit.jupiter.params.ParameterizedTest 23 | import org.junit.jupiter.params.provider.MethodSource 24 | import org.partiql.pig.runtime.MalformedDomainDataException 25 | import org.partiql.pig.tests.generated.PartiqlBasic 26 | 27 | class IonElementTransformerErrorTests { 28 | data class ErrorTestCase(val domainText: String, val message: String) 29 | 30 | @ParameterizedTest 31 | @MethodSource("parametersForSerializationErrorTest") 32 | fun serializationErrorTest(tc: ErrorTestCase) { 33 | val element = createIonElementLoader(IonElementLoaderOptions(includeLocationMeta = true)).loadSingleElement(tc.domainText) 34 | val ex = assertThrows { PartiqlBasic.transform(element) } 35 | assertEquals(tc.message, ex.message, "Exception's message must match") 36 | } 37 | 38 | companion object { 39 | @JvmStatic 40 | @Suppress("unused") 41 | fun parametersForSerializationErrorTest() = listOf( 42 | // Empty s-exp 43 | ErrorTestCase( 44 | "()", 45 | "1:1: Cannot get head of empty container" 46 | ), 47 | // Undefined tag 48 | ErrorTestCase( 49 | "(asdfasdf)", 50 | "1:2: Unknown tag 'asdfasdf' for domain 'partiql_basic'" 51 | ), 52 | // Invalid arity (too few) 53 | ErrorTestCase( 54 | "(lit)", 55 | "1:1: 1..1 argument(s) were required to `lit`, but 0 was/were supplied." 56 | ), 57 | // Invalid arity (too many) 58 | ErrorTestCase( 59 | "(lit 1 2)", 60 | "1:1: 1..1 argument(s) were required to `lit`, but 2 was/were supplied." 61 | ), 62 | // Incorrect type of second argument to plus 63 | ErrorTestCase( 64 | "(plus (lit 1) (project_value (lit 1)))", 65 | "1:15: Expected 'class org.partiql.pig.tests.generated.PartiqlBasic${'$'}Expr' but found 'class org.partiql.pig.tests.generated.PartiqlBasic${'$'}Projection${'$'}ProjectValue'" 66 | ), 67 | // Missing record field 68 | ErrorTestCase( 69 | "(select (from (scan (lit foo))))", 70 | "1:1: Required field 'project' was not found within 'select' record" 71 | ), 72 | // Undefined record field 73 | ErrorTestCase( 74 | "(select (extra_field 1) (project (project_value (lit 1))) (from (scan (lit foo) null null null)))", 75 | "1:1: Unexpected field 'extra_field' encountered" 76 | ) 77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/kotlin/generator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator.kotlin 17 | 18 | import freemarker.template.Configuration 19 | import org.partiql.pig.generator.createDefaultFreeMarkerConfiguration 20 | import org.partiql.pig.generator.setClassLoaderForTemplates 21 | import java.io.File 22 | import java.io.PrintWriter 23 | import java.time.OffsetDateTime 24 | 25 | const val KOTLIN_SOURCE_SUFFIX = "generated.kt" 26 | 27 | fun generateKotlinCode( 28 | namespace: String, 29 | kotlinTypeUniverse: KTypeUniverse, 30 | outputDirectory: File 31 | ) { 32 | val freemarkerConfig: Configuration = createDefaultFreeMarkerConfiguration() 33 | 34 | // Allow .getTemplate below and [#include...] directives to look in this .jar's embedded resources. 35 | freemarkerConfig.setClassLoaderForTemplates() 36 | 37 | generateDomainFiles(freemarkerConfig, kotlinTypeUniverse, namespace, outputDirectory) 38 | generateCrossDomainVisitorTransforms(freemarkerConfig, kotlinTypeUniverse.transforms, namespace, outputDirectory) 39 | } 40 | 41 | private fun generateDomainFiles( 42 | freemarkerConfig: Configuration, 43 | kotlinTypeUniverse: KTypeUniverse, 44 | namespace: String, 45 | outputDirectory: File 46 | ) { 47 | // Load the template 48 | val template = freemarkerConfig.getTemplate("kotlin-domain.ftl")!! 49 | 50 | // Apply the kotlin template once for each type domain... this creates one `*.generated.kt` file per domain. 51 | kotlinTypeUniverse.domains.forEach { domain -> 52 | val renderModel = KotlinDomainFreeMarkerGlobals( 53 | namespace = namespace, 54 | domain = domain, 55 | generatedDate = OffsetDateTime.now() 56 | ) 57 | 58 | val outputFile = File(outputDirectory, "${domain.kotlinName}.$KOTLIN_SOURCE_SUFFIX") 59 | 60 | PrintWriter(outputFile).use { printWriter -> 61 | template.process(renderModel, printWriter) 62 | } 63 | } 64 | } 65 | 66 | private fun generateCrossDomainVisitorTransforms( 67 | freemarkerConfig: Configuration, 68 | transforms: List, 69 | namespace: String, 70 | outputDirectory: File 71 | ) { 72 | // Load the template 73 | val template = freemarkerConfig.getTemplate("kotlin-cross-domain-transform.ftl")!! 74 | 75 | transforms.forEach { transform -> 76 | // Apply the kotlin template once for each type domain... this creates one `*.generated.kt` file per domain. 77 | val renderModel = KotlinCrossDomainFreeMarkerGlobals( 78 | namespace = namespace, 79 | transform = transform, 80 | generatedDate = OffsetDateTime.now() 81 | ) 82 | 83 | val outputFile = File( 84 | outputDirectory, 85 | "${transform.sourceDomainDifference.kotlinName}To${transform.destDomainKotlinName}.$KOTLIN_SOURCE_SUFFIX" 86 | ) 87 | 88 | PrintWriter(outputFile).use { printWriter -> 89 | template.process(renderModel, printWriter) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /pig/src/test/kotlin/org/partiql/pig/domain/TypeAnnotationParserTests.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.domain 2 | 3 | import com.amazon.ion.system.IonReaderBuilder 4 | import org.junit.jupiter.api.Test 5 | import org.junit.jupiter.params.ParameterizedTest 6 | import org.junit.jupiter.params.provider.MethodSource 7 | import org.partiql.pig.domain.model.DataType 8 | import org.partiql.pig.domain.model.TypeAnnotation 9 | import org.partiql.pig.domain.parser.parseTypeUniverse 10 | 11 | class TypeAnnotationParserTests { 12 | 13 | @ParameterizedTest 14 | @MethodSource("typeAnnotationCases") 15 | fun typeAnnotationTests(tc: TestCase) { 16 | val universe = "(define foo (domain ${tc.definition}))" 17 | val reader = IonReaderBuilder.standard().build(universe) 18 | val parsed = parseTypeUniverse(reader) 19 | val domains = parsed.computeTypeDomains() 20 | assert(domains.size == 1) 21 | val domain = domains.first() 22 | val types = domain.userTypes 23 | assert(types.size == 1) 24 | val type = types.first() 25 | tc.assertion(type) 26 | } 27 | 28 | @Test 29 | internal fun annotationsCarryOverTest() { 30 | val universe = """ 31 | (define domain_a 32 | (domain 33 | deprecated::(sum sum_keep (a) (b) (c)) 34 | deprecated::(sum sum_exclude (x) (y) (z)) 35 | deprecated::(product product_keep v::int) 36 | deprecated::(product product_exclude u::int) 37 | ) 38 | ) 39 | (define domain_b 40 | (permute_domain domain_a 41 | (exclude sum_exclude product_exclude) 42 | ) 43 | ) 44 | """.trimIndent() 45 | val reader = IonReaderBuilder.standard().build(universe) 46 | val parsed = parseTypeUniverse(reader) 47 | val domains = parsed.computeTypeDomains() 48 | domains.forEach { 49 | // every type should have an annotation 50 | it.userTypes.forEach { t -> 51 | assert(t.annotations.isNotEmpty()) 52 | } 53 | } 54 | } 55 | 56 | companion object { 57 | 58 | data class TestCase( 59 | val definition: String, 60 | val assertion: (type: DataType.UserType) -> Unit, 61 | ) 62 | 63 | @JvmStatic 64 | fun typeAnnotationCases() = listOf( 65 | TestCase("experimental::(product window_partition_list)") { 66 | assert(it.annotations.contains(TypeAnnotation.EXPERIMENTAL)) 67 | }, 68 | TestCase("experimental::(sum ordering_spec (asc) (desc))") { 69 | assert(it.annotations.contains(TypeAnnotation.EXPERIMENTAL)) 70 | }, 71 | TestCase("deprecated::(product window_partition_list)") { 72 | assert(it.annotations.contains(TypeAnnotation.DEPRECATED)) 73 | }, 74 | TestCase("deprecated::(sum ordering_spec (asc) (desc))") { 75 | assert(it.annotations.contains(TypeAnnotation.DEPRECATED)) 76 | }, 77 | TestCase("(sum ordering_spec deprecated::(asc) experimental::(desc) (other))") { 78 | (it as DataType.UserType.Sum).variants.forEach { v -> 79 | when (v.tag) { 80 | "asc" -> assert(v.annotations.contains(TypeAnnotation.DEPRECATED)) 81 | "desc" -> assert(v.annotations.contains(TypeAnnotation.EXPERIMENTAL)) 82 | "other" -> assert(v.annotations.isEmpty()) 83 | } 84 | } 85 | }, 86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/IonElementHelpers.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.AnyElement 19 | import com.amazon.ionelement.api.IonElement 20 | import com.amazon.ionelement.api.IonElementException 21 | import com.amazon.ionelement.api.SeqElement 22 | import com.amazon.ionelement.api.ionInt 23 | import com.amazon.ionelement.api.ionNull 24 | import com.amazon.ionelement.api.ionSymbol 25 | import com.amazon.ionelement.api.location 26 | 27 | /** 28 | * This seemingly useless function reduces complexity of the Kotlin template by providing a consistent way to 29 | * convert (or not) a variable to an [IonElement]. This has the same signature as the other `.toIonElement()` 30 | * extensions in package. 31 | */ 32 | fun IonElement.toIonElement() = this 33 | 34 | /** 35 | * This seemingly useless function reduces complexity of the Kotlin template by providing a consistent way to 36 | * convert (or not) a variable to an [IonElement]. This has the same signature as the other `.toIonElement()` 37 | * extensions in package. 38 | */ 39 | fun String?.toIonElement() = this?.let { ionSymbol(this) } ?: ionNull() 40 | 41 | /** 42 | * This seemingly useless function reduces complexity of the Kotlin template by providing a consistent way to 43 | * convert (or not) a variable to an [IonElement]. This has the same signature as the other `.toIonElement()` 44 | * extensions in package. 45 | */ 46 | fun Long.toIonElement() = ionInt(this) 47 | 48 | /** 49 | * Returns the string representation of the symbol in the first element of this container. 50 | * 51 | * If the first element is not a symbol, throws [IonElementException]. 52 | * If this container has no elements, throws [MalformedDomainDataException]. 53 | */ 54 | val SeqElement.tag: String get() = this.head.symbolValue 55 | 56 | /** 57 | * Returns the first element of this container. 58 | * 59 | * If this container has no elements, throws [MalformedDomainDataException]. 60 | */ 61 | val SeqElement.head: AnyElement 62 | get() = 63 | when (this.size) { 64 | 0 -> errMalformed(this.metas.location, "Cannot get head of empty container") 65 | else -> this.values.first() 66 | } 67 | 68 | /** 69 | * Returns a sub-list containing all elements of this container except the first. 70 | * 71 | * If this container has no elements, throws [MalformedDomainDataException]. 72 | */ 73 | val SeqElement.tail: List 74 | get() = when (this.size) { 75 | 0 -> errMalformed(this.metas.location, "Cannot get tail of empty container") 76 | else -> this.values.subList(1, this.size) 77 | } 78 | 79 | /** Returns the first element. */ 80 | val List.head: AnyElement 81 | get() = this.first() 82 | 83 | /** Returns a copy of the list with the first element removed. */ 84 | val List.tail: List get() = this.subList(1, this.size) 85 | 86 | /** 87 | * Returns a copy of the list with each element as AnyElement 88 | */ 89 | fun List.asAnyElement(): List = this.map { it.asAnyElement() } 90 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/ErrorHelpers.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.AnyElement 19 | import com.amazon.ionelement.api.IonLocation 20 | import com.amazon.ionelement.api.SexpElement 21 | import com.amazon.ionelement.api.location 22 | 23 | fun errMalformed(location: IonLocation?, message: String): Nothing = 24 | throw MalformedDomainDataException(location, message) 25 | 26 | fun errUnexpectedField(location: IonLocation?, fieldName: String): Nothing = 27 | errMalformed(location, "Unexpected field '$fieldName' encountered") 28 | 29 | fun SexpElement.requireArityOrMalformed(arity: Int) = 30 | requireArityOrMalformed(IntRange(arity, arity)) 31 | 32 | fun SexpElement.requireArityOrMalformed(arityRange: IntRange) { 33 | // Note: arity does not include the tag! 34 | val argCount = size - 1 35 | if (argCount !in arityRange) { 36 | errMalformed( 37 | metas.location, 38 | "$arityRange argument(s) were required to `${this.head}`, but $argCount was/were supplied." 39 | ) 40 | } 41 | } 42 | 43 | /** 44 | * Returns a required argument of an s-expression, skipping the 'tag'. 45 | * 46 | * Given `(foo bar bat)`, when `i` is '0', this function will return `bar`. When `i` is `1`, `bat` is returned. 47 | * 48 | * IonNull is used to indicate skipped optional argument. A [MalformedDomainDataException] 49 | * is thrown if IonNull is provided to a required argument. Only special case 50 | * in [getRequiredIon] below. 51 | */ 52 | fun SexpElement.getRequired(i: Int): AnyElement { 53 | argIndexInBoundOrMalformed(i) 54 | val ionElement = this.values[i + 1] 55 | return when { 56 | ionElement.isNull -> errMalformed(this.metas.location, "A non-null value is required.") 57 | else -> ionElement 58 | } 59 | } 60 | 61 | /** 62 | * Returns a required Ion-typed argument of an s-expression, skipping the 'tag'. 63 | * 64 | * Given `(foo bar bat)`, when `i` is '0', this function will return `bar`. When `i` is `1`, `bat` is returned. 65 | * 66 | * This is a special case of [getRequired]. Generally, IonNull is not acceptable 67 | * for required arguments. But when getting a required Ion-type argument, IonNull 68 | * is considered a valid value and returned as it is. 69 | */ 70 | fun SexpElement.getRequiredIon(i: Int): AnyElement { 71 | argIndexInBoundOrMalformed(i) 72 | return this.values[i + 1] 73 | } 74 | 75 | /** 76 | * Returns an optional argument of an s-expression, skipping the 'tag'. 77 | * 78 | * Given `(foo bar bat)`, when `i` is '0', this function will return `bar`. When `i` is `1`, `bat` is returned. 79 | * 80 | * IonNull is used to indicate skipped optional argument. Return null if optional 81 | * argument is IonNull. 82 | */ 83 | fun SexpElement.getOptional(i: Int): AnyElement? { 84 | argIndexInBoundOrMalformed(i) 85 | val ionElement = this.values[i + 1] 86 | return when { 87 | ionElement.isNull -> null 88 | else -> ionElement 89 | } 90 | } 91 | 92 | private fun SexpElement.argIndexInBoundOrMalformed(i: Int) { 93 | if (i + 1 >= this.size) 94 | errMalformed(this.metas.location, "Argument index $i is out of bounds (max=${size - 2})") 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PartiQL IR Generator 2 | 3 | PIG is a compiler framework, domain modeling tool and code generator for tree data structures such as ASTs (Abstract 4 | Syntax Tree), database logical plans, database physical plans, and other intermediate representations. Using PIG, the 5 | developer concisely defines the structure of a tree by specifying named constraints for every node and its attributes. 6 | 7 | PIG is mature, but its API and the API of the generated Kotlin code is under active development and may change. 8 | 9 | [![PIG Generator](https://maven-badges.herokuapp.com/maven-central/org.partiql/partiql-ir-generator/badge.svg?)](https://search.maven.org/artifact/org.partiql/partiql-ir-generator) 10 | [![PIG Runtime](https://maven-badges.herokuapp.com/maven-central/org.partiql/partiql-ir-generator-runtime/badge.svg?)](https://search.maven.org/artifact/org.partiql/partiql-ir-generator-runtime) 11 | [![License](https://img.shields.io/hexpm/l/plug.svg)](https://github.com/partiql/partiql-ir-generator/blob/main/LICENSE) 12 | [![CI Build](https://github.com/partiql/partiql-ir-generator/actions/workflows/build.yml/badge.svg)](https://github.com/partiql/partiql-ir-generator/actions?query=workflow%3A%22Build+and+run+tests%22) 13 | 14 | ## About 15 | 16 | Check out the wiki! — [PartiQL IR Generator Wiki](https://github.com/partiql/partiql-ir-generator/wiki) 17 | 18 | ## Usage 19 | 20 | There are two components of PIG to be aware of 21 | - [PIG Generator](https://search.maven.org/artifact/org.partiql/partiql-ir-generator) 22 | - [PIG Runtime](https://search.maven.org/artifact/org.partiql/partiql-ir-generator-runtime) 23 | 24 | Both are available in [Maven Central](https://search.maven.org/search?q=partiql-ir-generator), and it is recommended that **you use the same version for both of them.** 25 | 26 | ### Gradle 27 | 28 | ```groovy 29 | plugins { 30 | id 'pig-gradle-plugin' 31 | } 32 | 33 | sourceSets { 34 | main { 35 | pig { 36 | // in addition to the default 'src/main/pig' 37 | srcDir 'path/to/type/universes' 38 | } 39 | } 40 | test { 41 | pig { 42 | // in addition to the default 'src/test/pig' 43 | srcDir 'path/to/test/type/universes' 44 | } 45 | } 46 | } 47 | 48 | pig { 49 | target = 'kotlin' // required 50 | namespace = ... // optional 51 | template = ... // optional 52 | outDir = 'build/generated-sources/pig/' // default 53 | } 54 | ``` 55 | 56 | ### Other Build Systems 57 | 58 | If you are not using Gradle, it will be necessary to invoke PIG via the command line. 59 | 60 | At build time and before compilation of your application or library, the following should be executed: 61 | 62 | ``` 63 | pig \ 64 | -u \ 65 | -t kotlin \ 66 | -n \ 67 | -o path/to/package/ 68 | ``` 69 | 70 | - ``: path to the Ion text file containing the type universe 71 | - ``: path to the file for the generated code 72 | - ``: the name used in the `package` statement at the top of the output file 73 | 74 | Execute: `pig --help` for all command-line options. 75 | 76 | ### Obtaining the PIG Executable 77 | 78 | To obtain the `pig` executable: 79 | 80 | - Clone this repository. 81 | - Check out the tag of the [release](https://github.com/partiql/partiql-ir-generator/releases) you wish to utilize, 82 | e.g. `git checkout v0.6.3` 83 | - Execute `./gradlew assemble` 84 | 85 | After the build completes, the `pig` executable and dependencies will be located 86 | in `./pig/build/install/pig/bin/pig`. 87 | 88 | **Finally, make sure that the version of the `partiql-ir-generator-runtime` library that you are using corresponds to 89 | the version of the executable.** 90 | 91 | Verify this with the `--version` command line option of PIG. 92 | 93 | ## License 94 | 95 | This project is licensed under the Apache-2.0 License. 96 | 97 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/custom/CTypeDomain.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator.custom 17 | 18 | import org.partiql.pig.domain.model.Arity 19 | import org.partiql.pig.domain.model.DataType 20 | import org.partiql.pig.domain.model.NamedElement 21 | import org.partiql.pig.domain.model.TupleType 22 | import org.partiql.pig.domain.model.TypeDomain 23 | 24 | // 25 | // Types in this file are exposed to FreeMarker templates at run-time. 26 | // 27 | 28 | data class CTypeDomain( 29 | val name: String, 30 | val tuples: List, 31 | val sums: List 32 | ) 33 | 34 | data class CElement( 35 | val identifier: String, 36 | val tag: String, 37 | val type: String, 38 | val isVariadic: Boolean, 39 | val isOptional: Boolean 40 | ) 41 | 42 | data class CTuple( 43 | val tag: String, 44 | val memberOfType: String?, 45 | val elements: List, 46 | val arity: IntRange, 47 | val tupleType: TupleType, 48 | val annotations: List = emptyList() 49 | ) { 50 | /** All of the elements excluding the variadic element. */ 51 | @Suppress("unused") 52 | val monadicElements = elements.filter { !it.isVariadic } 53 | 54 | /** There may be only one variadic element and if it's present, it's here. */ 55 | @Suppress("MemberVisibilityCanBePrivate") 56 | val variadicElement = elements.singleOrNull { it.isVariadic } 57 | 58 | /** True when there's a variadic element. */ 59 | @Suppress("unused") 60 | val hasVariadicElement = variadicElement != null 61 | } 62 | 63 | data class CSum( 64 | val name: String, 65 | val variants: List 66 | ) 67 | 68 | fun TypeDomain.toCTypeDomain(): CTypeDomain { 69 | val gTuples = mutableListOf() 70 | val gSums = mutableListOf() 71 | 72 | this.userTypes.forEach { 73 | when (it) { 74 | is DataType.UserType.Tuple -> gTuples.add(it.toCTuple(memberOfType = null)) 75 | is DataType.UserType.Sum -> gSums.add(it.toCTuple()) 76 | } 77 | } 78 | 79 | return CTypeDomain( 80 | name = this.tag, 81 | tuples = gTuples, 82 | sums = gSums 83 | ) 84 | } 85 | 86 | private fun DataType.UserType.Tuple.toCTuple(memberOfType: String?) = 87 | CTuple( 88 | tag = this.tag, 89 | memberOfType = memberOfType, 90 | elements = this.namedElements.map { it.toCElement() }, 91 | arity = this.computeArity(), 92 | tupleType = this.tupleType, 93 | annotations = this.annotations.map { it.toString() } 94 | ) 95 | 96 | private fun NamedElement.toCElement(): CElement { 97 | val (isOptional, isVariadic) = when (this.typeReference.arity) { 98 | Arity.Required -> false to false 99 | Arity.Optional -> true to false 100 | is Arity.Variadic -> false to true 101 | } 102 | 103 | return CElement( 104 | identifier = this.identifier, 105 | tag = this.tag, 106 | type = this.typeReference.typeName, 107 | isOptional = isOptional, 108 | isVariadic = isVariadic 109 | ) 110 | } 111 | 112 | private fun DataType.UserType.Sum.toCTuple() = 113 | CSum( 114 | name = this.tag, 115 | variants = this.variants.map { it.toCTuple(this.tag) } 116 | ) 117 | -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/SumConverterTests.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.tests 2 | 3 | import com.amazon.ionelement.api.ionBool 4 | import com.amazon.ionelement.api.ionInt 5 | import org.junit.jupiter.params.ParameterizedTest 6 | import org.junit.jupiter.params.provider.ArgumentsSource 7 | import org.partiql.pig.tests.generated.ToyLang 8 | import kotlin.test.assertEquals 9 | 10 | /** 11 | * Tests sum converters and demonstrates their use to convert ToyLang's AST into a text representation 12 | * that is vaguely similar to OCaml. This example converts from a PIG domain sum type to a string, but these 13 | * conversions can be applied to just about anything. 14 | * 15 | * ToyLang's AST actually has two sum types: expr and operator. 16 | * 17 | * This example also demonstrates how converters for both can be used together. 18 | */ 19 | class SumConverterTests { 20 | 21 | private val toyOperatorConverter = object : ToyLang.Operator.Converter { 22 | override fun convertPlus(node: ToyLang.Operator.Plus): String = "+" 23 | override fun convertMinus(node: ToyLang.Operator.Minus): String = "-" 24 | override fun convertTimes(node: ToyLang.Operator.Times): String = "*" 25 | override fun convertDivide(node: ToyLang.Operator.Divide): String = "/" 26 | override fun convertModulo(node: ToyLang.Operator.Modulo): String = "%" 27 | } 28 | 29 | private val toyExprConverter = object : ToyLang.Expr.Converter { 30 | override fun convertLit(node: ToyLang.Expr.Lit): String = "${node.value}" 31 | override fun convertVariable(node: ToyLang.Expr.Variable): String = node.name.text 32 | override fun convertNot(node: ToyLang.Expr.Not): String = "!${convert(node.expr)}" 33 | override fun convertNary(node: ToyLang.Expr.Nary): String { 34 | // This Converter implementation isn't responsible for converting ToyLang.Operator instances. 35 | // delegate to toyOperatorConverter for that. 36 | val op = toyOperatorConverter.convert(node.op) 37 | return node.operands.joinToString(" $op ") { convert(it) } 38 | } 39 | 40 | override fun convertLet(node: ToyLang.Expr.Let): String = 41 | "let ${node.name.text} = ${convert(node.value)} in ${convert(node.body)}" 42 | 43 | override fun convertFunction(node: ToyLang.Expr.Function): String = 44 | "fun (${node.varName.text}) -> ${convert(node.body)}" 45 | } 46 | 47 | data class TestCase(val ast: ToyLang.Expr, val expectedStringRepresentation: String) 48 | 49 | class Arguments : ArgumentsProviderBase() { 50 | private fun case(expectedStringRepresentation: String, block: ToyLang.Builder.() -> ToyLang.Expr): TestCase = 51 | TestCase(ast = block(ToyLang.BUILDER()), expectedStringRepresentation = expectedStringRepresentation) 52 | 53 | override fun getParameters() = listOf( 54 | case("x") { variable("x") }, 55 | case("21 * 2") { nary(times(), lit(ionInt(21)), lit(ionInt(2))) }, 56 | case("!!false") { not(not(lit(ionBool(false)))) }, 57 | case("let x = 38 in x + 4") { 58 | let( 59 | "x", 60 | lit(ionInt(38)), 61 | nary(plus(), variable("x"), lit(ionInt(4))) 62 | ) 63 | }, 64 | case("let x = 38 in x + 4") { 65 | let( 66 | "x", 67 | lit(ionInt(38)), 68 | nary(plus(), variable("x"), lit(ionInt(4))) 69 | ) 70 | }, 71 | case("fun (n) -> n + 42") { 72 | function("n", nary(plus(), variable("n"), lit(ionInt(42)))) 73 | } 74 | ) 75 | } 76 | 77 | @ParameterizedTest 78 | @ArgumentsSource(Arguments::class) 79 | fun `convert toy AST to ocaml-like string`(tc: TestCase) { 80 | val converted = toyExprConverter.convert(tc.ast) 81 | 82 | assertEquals(tc.expectedStringRepresentation, converted) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /pig/src/main/resources/org/partiql/pig/templates/html.ftl: -------------------------------------------------------------------------------- 1 | [#ftl output_format="XHTML"] 2 | 3 | 4 | 5 | 17 | Type Universe 18 | 19 | 20 | 21 |

22 | Domains 23 |

24 | 25 | [#macro elements_table tuple] 26 | 27 | 28 | 29 | 32 | 35 | 38 | 41 | 44 | 45 | 46 | 47 | [#list tuple.elements as element] 48 | 49 | 52 | 55 | 58 | 61 | 64 | 65 | [/#list] 66 | 67 |
30 | Identifier 31 | 33 | Tag 34 | 36 | Type 37 | 39 | Optional 40 | 42 | Variadic 43 |
50 | ${element.identifier} 51 | 53 | ${element.tag} 54 | 56 | ${element.type} 57 | 59 | ${element.optional?c} 60 | 62 | ${element.variadic?c} 63 |
68 | [/#macro] 69 | 70 | [#macro table_of_tuples tuples] 71 | 72 | 73 | 74 | 77 | 80 | 83 | 84 | 85 | 86 | [#list tuples as tuple] 87 | 88 | 95 | 98 | 101 | 102 | [/#list] 103 | 104 |
75 | Tag 76 | 78 | Tuple Type 79 | 81 | Elements 82 |
89 | ${tuple.tag} 90 |
91 | [#list tuple.annotations as annotation] 92 | ${annotation} 93 | [/#list] 94 |
96 | ${tuple.tupleType} 97 | 99 | [@elements_table tuple/] 100 |
105 | [/#macro] 106 | 107 | [#list domains as domain] 108 |

109 | ${domain.name} 110 |

111 |

112 | Product Types 113 |

114 | [#if domain.tuples?size == 0] 115 | This domain has no product types. 116 | [#else] 117 | [@table_of_tuples domain.tuples/] 118 | [/#if] 119 |

120 | Sum Types 121 |

122 | [#if domain.sums?size == 0] 123 | This domain has no sum types. 124 | [#else] 125 | 126 | 127 | 128 | 131 | 134 | 135 | 136 | 137 | [#list domain.sums as sum] 138 | 139 | 142 | 145 | 146 | [/#list] 147 | 148 |
129 | Tag Name 130 | 132 | Variants 133 |
140 | ${sum.name} 141 | 143 | [@table_of_tuples sum.variants /] 144 |
149 | [/#if] 150 | [/#list] 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/VisitorTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.tests 17 | 18 | import com.amazon.ionelement.api.MetaContainer 19 | import org.junit.jupiter.api.Test 20 | import org.partiql.pig.runtime.LongPrimitive 21 | import org.partiql.pig.runtime.SymbolPrimitive 22 | import org.partiql.pig.tests.generated.TestDomain 23 | import kotlin.test.assertEquals 24 | 25 | private const val NUMBER_KEY = "number" 26 | 27 | class VisitorTests { 28 | 29 | class DummyVisitor : TestDomain.Visitor() { 30 | 31 | // The best way I have found to test this type of visitor is to observe 32 | // these side effects. Normally, if you wanted to extract information 33 | // from a tree you'd use [TestDomain.FoldingVisitor] instead. 34 | 35 | var numberAccumulator: Long = 0 36 | var stringAccumulator: String = "" 37 | 38 | override fun visitLongPrimitive(node: LongPrimitive) { 39 | numberAccumulator += node.value 40 | } 41 | 42 | override fun visitSymbolPrimitive(node: SymbolPrimitive) { 43 | stringAccumulator += node.text 44 | } 45 | 46 | override fun visitMetas(metas: MetaContainer) { 47 | if (metas.containsKey(NUMBER_KEY)) { 48 | numberAccumulator += metas[NUMBER_KEY] as Int 49 | } 50 | } 51 | } 52 | 53 | @Test 54 | fun visitProducts() { 55 | val node = TestDomain.build { 56 | intPairPair(intPair(1, 2), intPair(3, 4)) 57 | }.withMeta(NUMBER_KEY, 5) 58 | 59 | val visitor = DummyVisitor() 60 | visitor.walkIntPairPair(node) 61 | assertEquals(15, visitor.numberAccumulator) 62 | } 63 | 64 | @Test 65 | fun visitRecords() { 66 | val node = TestDomain.build { 67 | domainLevelRecord(someField = 2, anotherField = "hi", optionalField = 3) 68 | }.withMeta("number", 4) 69 | 70 | val visitor = DummyVisitor() 71 | visitor.walkDomainLevelRecord(node) 72 | 73 | assertEquals(9, visitor.numberAccumulator) 74 | assertEquals("hi", visitor.stringAccumulator) 75 | } 76 | 77 | @Test 78 | fun visitSums() { 79 | val node = TestDomain.build { 80 | testSumTriplet( 81 | one(1), 82 | two(2, 3), 83 | three(4, 5, 6) 84 | ).withMeta(NUMBER_KEY, 7) 85 | } 86 | 87 | val visitor = DummyVisitor() 88 | visitor.walkTestSumTriplet(node) 89 | 90 | assertEquals(28, visitor.numberAccumulator) 91 | } 92 | 93 | @Test 94 | fun visitProductsWithVariadicElements() { 95 | // No elements 96 | val node1 = TestDomain.build { variadicMin0() }.withMeta(NUMBER_KEY, 1) 97 | val visitor1 = DummyVisitor() 98 | visitor1.walkVariadicMin0(node1) 99 | assertEquals(1, visitor1.numberAccumulator) 100 | 101 | // One element 102 | val node2 = TestDomain.build { variadicMin0(42) }.withMeta(NUMBER_KEY, 43) 103 | val visitor2 = DummyVisitor() 104 | visitor2.walkVariadicMin0(node2) 105 | assertEquals(85, visitor2.numberAccumulator) 106 | 107 | // Four elements. 108 | val node3 = TestDomain.build { variadicMin0(1, 2, 3, 4) }.withMeta(NUMBER_KEY, 5) 109 | val visitor3 = DummyVisitor() 110 | visitor3.walkVariadicMin0(node3) 111 | assertEquals(15, visitor3.numberAccumulator) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/FreeMarkerUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator 17 | 18 | import freemarker.template.Configuration 19 | import freemarker.template.TemplateExceptionHandler 20 | import freemarker.template.TemplateMethodModelEx 21 | import freemarker.template.TemplateModelException 22 | import org.partiql.pig.generator.kotlin.KotlinDomainFreeMarkerGlobals 23 | import org.partiql.pig.util.snakeToCamelCase 24 | import org.partiql.pig.util.snakeToPascalCase 25 | 26 | /** 27 | * When applying a template for a pre-packaged language target such as Kotlin, we 28 | * call this to load the templates from resources. The `custom` language target 29 | * loads the templates from the file specified on the command-line. 30 | */ 31 | internal fun Configuration.setClassLoaderForTemplates() { 32 | // Specify the source where the template files come from. Here we set a 33 | // the classloader of [TopLevelFreeMarkerGlobals] and set the root package. 34 | this.setClassLoaderForTemplateLoading( 35 | KotlinDomainFreeMarkerGlobals::class.java.classLoader, 36 | "/org/partiql/pig/templates" 37 | ) 38 | } 39 | 40 | /** 41 | * Creates the default Apache FreeMarker [Configuration] object that should be used 42 | * when applying any template. 43 | */ 44 | internal fun createDefaultFreeMarkerConfiguration(): Configuration { 45 | // Create your Configuration instance, and specify if up to what FreeMarker 46 | // version (here 2.3.27) do you want to apply the fixes that are not 100% 47 | // backward-compatible. See the Configuration JavaDoc for details. 48 | val cfg = Configuration(Configuration.VERSION_2_3_27) 49 | 50 | // Set the preferred charset template files are stored in. UTF-8 is 51 | // a good choice in most applications: 52 | cfg.defaultEncoding = "UTF-8" 53 | 54 | // Sets how errors will appear. 55 | // During web page *development* TemplateExceptionHandler.HTML_DEBUG_HANDLER is better. 56 | cfg.templateExceptionHandler = TemplateExceptionHandler.RETHROW_HANDLER 57 | 58 | // Don't log exceptions inside FreeMarker that it will thrown at you anyway: 59 | cfg.logTemplateExceptions = false 60 | 61 | // Wrap unchecked exceptions thrown during template processing into TemplateException-s. 62 | cfg.wrapUncheckedExceptions = true 63 | 64 | cfg.tagSyntax = Configuration.SQUARE_BRACKET_TAG_SYNTAX 65 | 66 | // Exposes the `indent` directive we use to make indenting parts of the result easier. 67 | cfg.setSharedVariable("indent", IndentDirective()) 68 | cfg.setSharedVariable("snakeToCamelCase", SnakeToCamelCaseTemplateMethod()) 69 | cfg.setSharedVariable("snakeToPascalCase", SnakeToPascalCaseTemplateMethod()) 70 | return cfg 71 | } 72 | 73 | class SnakeToPascalCaseTemplateMethod : TemplateMethodModelEx { 74 | override fun exec(arguments: MutableList?): Any { 75 | arguments!! 76 | checkArguments(arguments) 77 | return arguments[0].toString().snakeToPascalCase() 78 | } 79 | } 80 | 81 | class SnakeToCamelCaseTemplateMethod : TemplateMethodModelEx { 82 | override fun exec(arguments: MutableList?): Any { 83 | arguments!! 84 | checkArguments(arguments) 85 | return arguments[0].toString().snakeToCamelCase() 86 | } 87 | } 88 | private fun checkArguments(arguments: MutableList) { 89 | if (arguments.size != 1) { 90 | throw TemplateModelException( 91 | "Incorrect number of arguments, expected 1 but was supplied with ${arguments.size}" 92 | ) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainParserTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain 17 | 18 | import com.amazon.ion.system.IonReaderBuilder 19 | import com.amazon.ionelement.api.IonElementLoaderOptions 20 | import com.amazon.ionelement.api.createIonElementLoader 21 | import com.amazon.ionelement.api.ionSexpOf 22 | import com.amazon.ionelement.api.ionSymbol 23 | import org.junit.jupiter.api.Assertions.assertEquals 24 | import org.junit.jupiter.api.Test 25 | import org.junit.jupiter.api.assertDoesNotThrow 26 | import org.partiql.pig.domain.parser.parseTypeUniverse 27 | 28 | class TypeDomainParserTests { 29 | private val loader = createIonElementLoader(IonElementLoaderOptions(includeLocationMeta = true)) 30 | 31 | @Test 32 | fun testProduct() = runTestCase( 33 | """ 34 | (define test_domain 35 | (domain 36 | (product foo a::string b::(* int 2)) 37 | (product bar a::bat b::(? baz) c::(* blargh 10)))) 38 | """ 39 | ) 40 | @Test 41 | fun testTransform() = runTestCase("(transform domain_a domain_b)") 42 | 43 | @Test 44 | fun testRecord() = runTestCase( 45 | """ 46 | (define test_domain 47 | (domain 48 | (record foo (bat int) (blorg (? int))) 49 | (record bar (bloo int) (blat (* int 1))) 50 | (record bat xx::(x int) yy::(y int)))) 51 | """ 52 | ) 53 | 54 | @Test 55 | fun testSum() = 56 | runTestCase( 57 | """ 58 | (define some_domain 59 | (domain 60 | (sum test_sum 61 | // Product with no elements 62 | (vacuum) 63 | 64 | // Product with one element 65 | (lonely single::int) 66 | 67 | // Product with two elements 68 | (company id::int name::symbol) 69 | 70 | // Record with three elements 71 | (crowd first::int second::symbol third::ion) 72 | ))) 73 | """ 74 | ) 75 | 76 | @Test 77 | fun testPermuteDomain() = runTestCase( 78 | """ 79 | (define permuted_domain 80 | (permute_domain some_domain 81 | (exclude excluded_type) 82 | (include 83 | (product included_product a::int)) 84 | (with altered_sum 85 | (exclude removed_variant) 86 | (include 87 | (added_variant a::int b::int))))) 88 | """ 89 | ) 90 | 91 | private fun runTestCase(tc: String) { 92 | val expected = assertDoesNotThrow("loading the expected type universe") { 93 | loader.loadSingleElement(tc) 94 | } 95 | val parsed = assertDoesNotThrow("parsing type universe") { 96 | IonReaderBuilder.standard().build(tc).use { 97 | parseTypeUniverse(it) 98 | } 99 | } 100 | 101 | assertEquals( 102 | ionSexpOf( 103 | ionSymbol("universe"), 104 | expected 105 | ), 106 | parsed.toIonElement() 107 | ) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/CopyTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.tests 17 | 18 | import com.amazon.ionelement.api.emptyMetaContainer 19 | import com.amazon.ionelement.api.metaContainerOf 20 | import org.junit.jupiter.api.Test 21 | import org.partiql.pig.runtime.LongPrimitive 22 | import org.partiql.pig.runtime.asPrimitive 23 | import org.partiql.pig.tests.generated.TestDomain 24 | import kotlin.test.assertEquals 25 | import kotlin.test.assertNotEquals 26 | import kotlin.test.assertNotSame 27 | 28 | private const val NUMBER_KEY = "number" 29 | 30 | class CopyTests { 31 | private val node = TestDomain.build { intPair(1, 2).withMeta(NUMBER_KEY, 3) } 32 | 33 | @Test 34 | fun `copy empty test`() { 35 | val copiedNode = node.copy() 36 | assertEquals(node, copiedNode) 37 | assertNotSame(node, copiedNode) 38 | } 39 | 40 | @Test 41 | fun `copy with same values test`() { 42 | val copiedNode = node.copy( 43 | first = LongPrimitive(1, emptyMetaContainer()), 44 | second = LongPrimitive(2, emptyMetaContainer()), 45 | metas = metaContainerOf(NUMBER_KEY to 3) 46 | ) 47 | assertEquals(node, copiedNode) 48 | assertNotSame(node, copiedNode) 49 | } 50 | 51 | @Test 52 | fun `copy element test`() { 53 | val copiedNode = node.copy(first = LongPrimitive(0, emptyMetaContainer())) 54 | 55 | assertNotEquals(node.first, copiedNode.first) 56 | assertEquals(node.second, copiedNode.second) 57 | assertEquals(node.metas, copiedNode.metas) 58 | assertNotEquals(node, copiedNode) 59 | assertNotSame(node, copiedNode) 60 | } 61 | 62 | @Test 63 | fun `copy multiple elements test`() { 64 | val copiedNode = node.copy( 65 | first = LongPrimitive(0, emptyMetaContainer()), 66 | second = LongPrimitive(0, emptyMetaContainer()) 67 | ) 68 | 69 | assertNotEquals(node.first, copiedNode.first) 70 | assertNotEquals(node.second, copiedNode.second) 71 | assertEquals(node.metas, copiedNode.metas) 72 | assertNotEquals(node, copiedNode) 73 | assertNotSame(node, copiedNode) 74 | } 75 | 76 | @Test 77 | fun `copy element twice test`() { 78 | val copiedNode = node.copy(first = LongPrimitive(0, emptyMetaContainer())) 79 | assertNotEquals(node, copiedNode) 80 | assertNotSame(node, copiedNode) 81 | 82 | val copiedAgainNode = node.copy(first = LongPrimitive(1, emptyMetaContainer())) 83 | assertEquals(node, copiedAgainNode) 84 | assertNotSame(node, copiedAgainNode) 85 | assertNotSame(copiedNode, copiedAgainNode) 86 | } 87 | 88 | @Test 89 | fun `copy metas - override with multiple parameters`() { 90 | val copiedNode = node.copy(second = 2L.asPrimitive(), metas = metaContainerOf(NUMBER_KEY to 0)) 91 | assertNotEquals(node.metas, copiedNode.metas) 92 | assertEquals(node.first, copiedNode.first) 93 | assertEquals(2L, copiedNode.second.value) 94 | assertEquals(node, copiedNode) 95 | assertNotSame(node, copiedNode) 96 | } 97 | 98 | @Test 99 | fun `copy test - override with single metas parameter`() { 100 | val copiedNode = node.copy(metaContainerOf(NUMBER_KEY to 0)) 101 | assertNotEquals(node.metas, copiedNode.metas) 102 | assertEquals(node.first, copiedNode.first) 103 | assertEquals(node.second, copiedNode.second) 104 | assertEquals(node, copiedNode) 105 | assertNotSame(node, copiedNode) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pig/src/main/resources/org/partiql/pig/templates/kotlin-visitor-transform.ftl: -------------------------------------------------------------------------------- 1 | [#ftl output_format="plainText"] 2 | [#-- @ftlvariable name="universe" type="org.partiql.pig.generator.kotlin.KTypeUniverse" --] 3 | 4 | [#macro transform_fun_body destDomainName t transformFuncName avoidCopyingUnchangedNodes] 5 | [#list t.properties as p] 6 | val new_${p.kotlinName} = transform${transformFuncName}_${p.kotlinName}(node) 7 | [/#list] 8 | val new_metas = transform${transformFuncName}_metas(node) 9 | return [#if avoidCopyingUnchangedNodes]if ( 10 | [#list t.properties as p] 11 | node.${p.kotlinName} !== new_${p.kotlinName} || 12 | [/#list] 13 | node.metas !== new_metas 14 | ) { 15 | ${destDomainName}.${t.constructorName}( 16 | [#list t.properties as p] 17 | ${p.kotlinName} = new_${p.kotlinName}, 18 | [/#list] 19 | metas = new_metas 20 | ) 21 | } else { 22 | node 23 | } 24 | [#else][#t] 25 | ${destDomainName}.${t.constructorName}([#lt] 26 | [#list t.properties as p] 27 | ${p.kotlinName} = new_${p.kotlinName}, 28 | [/#list] 29 | metas = new_metas 30 | ) 31 | [/#if] 32 | [/#macro] 33 | 34 | [#macro transform_property_functions source_domain tuple sumName] 35 | [#assign qualifed_name]${source_domain.kotlinName}.[#if sumName?has_content]${sumName}.[/#if]${tuple.kotlinName}[/#assign] 36 | [#list tuple.properties as p] 37 | open fun transform${sumName}${tuple.kotlinName}_${p.kotlinName}(node: ${qualifed_name}) = 38 | [#if p.variadic] 39 | node.${p.kotlinName}.map { transform${p.rawTypeName}(it) } 40 | [#elseif p.nullable] 41 | node.${p.kotlinName}?.let { transform${p.rawTypeName}(it) } 42 | [#else] 43 | transform${p.rawTypeName}(node.${p.kotlinName}) 44 | [/#if] 45 | [/#list] 46 | open fun transform${sumName}${tuple.kotlinName}_metas(node: ${qualifed_name}) = 47 | transformMetas(node.metas) 48 | 49 | [/#macro] 50 | 51 | [#macro visitor_transform_class class_name source_domain dest_domain_name] 52 | abstract class ${class_name} : DomainVisitorTransformBase() { 53 | [#list source_domain.tuples] 54 | ////////////////////////////////////// 55 | // Tuple Types 56 | ////////////////////////////////////// 57 | [#items as tuple] 58 | [#if !tuple.transformAbstract] 59 | // Tuple ${tuple.kotlinName} 60 | open fun transform${tuple.kotlinName}(node: ${source_domain.kotlinName}.${tuple.kotlinName}): ${dest_domain_name}.${tuple.kotlinName} { 61 | [@transform_fun_body dest_domain_name tuple tuple.kotlinName source_domain.kotlinName == dest_domain_name/] 62 | } 63 | [@transform_property_functions source_domain tuple ""/] 64 | [#else] 65 | abstract fun transform${tuple.kotlinName}(node:${source_domain.kotlinName}.${tuple.kotlinName}): ${dest_domain_name}.${tuple.kotlinName} 66 | [/#if] 67 | [/#items] 68 | [/#list] 69 | [#list source_domain.sums as s] 70 | ////////////////////////////////////// 71 | // Sum Type: ${s.kotlinName} 72 | ////////////////////////////////////// 73 | open fun transform${s.kotlinName}(node: ${source_domain.kotlinName}.${s.kotlinName}): ${dest_domain_name}.${s.kotlinName} = 74 | when(node) { 75 | [#list s.variants as v] 76 | is ${source_domain.kotlinName}.${s.kotlinName}.${v.kotlinName} -> transform${s.kotlinName}${v.kotlinName}(node) 77 | [/#list] 78 | } 79 | [#list s.variants as tuple] 80 | // Variant ${s.kotlinName}${tuple.kotlinName} 81 | [#if !tuple.transformAbstract] 82 | open fun transform${s.kotlinName}${tuple.kotlinName}(node: ${source_domain.kotlinName}.${s.kotlinName}.${tuple.kotlinName}): ${dest_domain_name}.${s.kotlinName} { 83 | [@transform_fun_body dest_domain_name, tuple, "${s.kotlinName}${tuple.kotlinName}" source_domain.kotlinName == dest_domain_name/] 84 | } 85 | [@transform_property_functions source_domain tuple s.kotlinName /] 86 | [#else] 87 | abstract fun transform${s.kotlinName}${tuple.kotlinName}(node: ${source_domain.kotlinName}.${s.kotlinName}.${tuple.kotlinName}): ${dest_domain_name}.${s.kotlinName} 88 | [/#if] 89 | [/#list] 90 | [/#list] 91 | } 92 | [/#macro] 93 | 94 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KTypeDomain.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.generator.kotlin 17 | 18 | /* 19 | Note a big design consideration for the classes in this file is that they are easy to consume by the 20 | Apache FreeMarker template. FreeMarker, like all template languages, is not great at expressing complex 21 | logic so we pre-compute most of the complicated aspects of generating the Kotlin code and populate the 22 | results to an instance of this domain model. This helps keep the template much simpler than it would 23 | otherwise be. 24 | */ 25 | data class KTypeUniverse(val domains: List, val transforms: List) 26 | 27 | /** 28 | * Models the *difference* between a source domain and a destination domain. 29 | */ 30 | data class KTransform( 31 | /** 32 | * This KTypeDomain instance will be derived from the *difference* between the destination 33 | * domain and the source domain. See [TypeUniverse.computeTypeDomains] to learn how this 34 | * difference is computed. 35 | */ 36 | val sourceDomainDifference: KTypeDomain, 37 | val destDomainKotlinName: String 38 | ) 39 | 40 | data class KTypeDomain( 41 | /** The name of the type domain in the generated Kotlin code. .*/ 42 | val kotlinName: String, 43 | /** The name of the type domain as defined in the type universe. */ 44 | val tag: String, 45 | val tuples: List, 46 | val sums: List 47 | ) 48 | 49 | data class KProperty( 50 | /** The name of the property in the generated Kotlin code, in `camelCase`. */ 51 | val kotlinName: String, 52 | /** The name of the property s-exp representation and as defined in the type universe. */ 53 | val tag: String, 54 | /** The qualified Kotlin type name... */ 55 | val kotlinTypeName: String, 56 | val isVariadic: Boolean, 57 | val isNullable: Boolean, 58 | val transformExpr: String, 59 | val rawTypeName: String 60 | ) 61 | 62 | data class KParameter( 63 | val kotlinName: String, 64 | val kotlinType: String, 65 | val defaultValue: String?, 66 | val isVariadic: Boolean 67 | ) 68 | 69 | data class KConstructorArgument( 70 | val kotlinName: String, 71 | val value: String 72 | ) 73 | 74 | data class KBuilderFunction( 75 | val kotlinName: String, 76 | val parameters: List, 77 | val constructorArguments: List 78 | ) 79 | 80 | data class KTuple( 81 | /** The name of the tuple in the Kotlin code. */ 82 | val kotlinName: String, 83 | /** The name of the tuple in the s-exp representation and as defined in the type universe. */ 84 | val tag: String, 85 | /** The name of the constructor class, fully qualified. */ 86 | val constructorName: String, 87 | val superClass: String, 88 | val properties: List, 89 | val arity: IntRange, 90 | val builderFunctions: List, 91 | val isRecord: Boolean, 92 | /** 93 | * Set to true if this [KTuple] is a member of a [KTypeDomain] that represents the differences between 94 | * two type domains and this particular tuple has been removed or is different in the second domain. If 95 | * `true`, its generated visitor transform `transform*` method will be `abstract`. 96 | */ 97 | val isTransformAbstract: Boolean, 98 | val hasVariadicElement: Boolean, 99 | val annotations: List, 100 | ) 101 | 102 | data class KSum( 103 | val kotlinName: String, 104 | val superClass: String, 105 | val variants: List, 106 | /** 107 | * Set to true if this [KSum] is a member of a [KTypeDomain] that represents the differences between 108 | * two type domains and this particular sum has been removed from the second. When this is `true`, its 109 | * generated visitor transform `transform*` method will be `abstract`. 110 | */ 111 | val isTransformAbstract: Boolean, 112 | val annotations: List, 113 | ) 114 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/domain/Utils.kt: -------------------------------------------------------------------------------- 1 | package org.partiql.pig.domain 2 | 3 | import com.amazon.ionelement.api.IonElement 4 | import com.amazon.ionelement.api.ionSexpOf 5 | import com.amazon.ionelement.api.ionSymbol 6 | import org.partiql.pig.domain.model.DataType 7 | import org.partiql.pig.domain.model.NamedElement 8 | import org.partiql.pig.domain.model.PermutedDomain 9 | import org.partiql.pig.domain.model.PermutedSum 10 | import org.partiql.pig.domain.model.Statement 11 | import org.partiql.pig.domain.model.Transform 12 | import org.partiql.pig.domain.model.TupleType 13 | import org.partiql.pig.domain.model.TypeDomain 14 | import org.partiql.pig.domain.model.TypeUniverse 15 | 16 | /* 17 | * The [toIonElement] functions below generate an s-expression representation of a [TypeUniverse]. 18 | */ 19 | 20 | internal fun TypeUniverse.toIonElement(): IonElement = 21 | ionSexpOf( 22 | ionSymbol("universe"), 23 | *statements.map { it.toIonElement() }.toTypedArray() 24 | ) 25 | 26 | internal fun Statement.toIonElement(): IonElement = 27 | when (this) { 28 | is TypeDomain -> 29 | ionSexpOf( 30 | ionSymbol("define"), 31 | ionSymbol(tag), 32 | ionSexpOf( 33 | ionSymbol("domain"), 34 | *userTypes 35 | .filterNot { it.isDifferent } 36 | .map { it.toIonElement(includeTypeTag = true) }.toTypedArray() 37 | ) 38 | ) 39 | is PermutedDomain -> 40 | ionSexpOf( 41 | ionSymbol("define"), 42 | ionSymbol(tag), 43 | ionSexpOf( 44 | listOf( 45 | ionSymbol("permute_domain"), 46 | ionSymbol(permutesDomain), 47 | ionSexpOf( 48 | ionSymbol("exclude"), 49 | *excludedTypes.map { ionSymbol(it) }.toTypedArray() 50 | ), 51 | ionSexpOf( 52 | ionSymbol("include"), 53 | *includedTypes.map { it.toIonElement(includeTypeTag = true) }.toTypedArray() 54 | ) 55 | ) + permutedSums.map { it.toIonElement() } 56 | ) 57 | ) 58 | is Transform -> 59 | ionSexpOf( 60 | ionSymbol("transform"), 61 | ionSymbol(this.sourceDomainTag), 62 | ionSymbol(this.destinationDomainTag) 63 | ) 64 | } 65 | 66 | internal fun PermutedSum.toIonElement(): IonElement = 67 | ionSexpOf( 68 | ionSymbol("with"), 69 | ionSymbol(tag), 70 | ionSexpOf( 71 | ionSymbol("exclude"), 72 | *removedVariants.map { ionSymbol(it) }.toTypedArray() 73 | ), 74 | ionSexpOf( 75 | ionSymbol("include"), 76 | *addedVariants.map { it.toIonElement(includeTypeTag = false) }.toTypedArray() 77 | ) 78 | ) 79 | 80 | internal fun DataType.toIonElement(includeTypeTag: Boolean): IonElement = when (this) { 81 | DataType.Ion -> ionSymbol("ion") 82 | DataType.Bool -> ionSymbol("bool") 83 | DataType.Int -> ionSymbol("int") 84 | DataType.Symbol -> ionSymbol("symbol") 85 | is DataType.UserType.Tuple -> 86 | ionSexpOf( 87 | listOfNotNull( 88 | if (includeTypeTag) ionSymbol(tupleType.toString().toLowerCase()) else null, 89 | ionSymbol(tag), 90 | *namedElements.map { it.toIonElement(this.tupleType) }.toTypedArray() 91 | ) 92 | ).withAnnotations(annotations.map { it.toString().toLowerCase() }) 93 | is DataType.UserType.Sum -> 94 | ionSexpOf( 95 | ionSymbol("sum"), 96 | ionSymbol(tag), 97 | *variants 98 | .filterNot { it.isDifferent } 99 | .map { it.toIonElement(includeTypeTag = false) }.toTypedArray() 100 | ).withAnnotations(annotations.map { it.toString().toLowerCase() }) 101 | } 102 | 103 | internal fun NamedElement.toIonElement(tupleType: TupleType) = 104 | when (tupleType) { 105 | TupleType.PRODUCT -> typeReference.toIonElement().withAnnotations(identifier) 106 | TupleType.RECORD -> 107 | ionSexpOf(ionSymbol(tag), typeReference.toIonElement()).let { 108 | when { 109 | tag != identifier -> it.withAnnotations(identifier) 110 | else -> it 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/domain/parser/ParserErrorContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain.parser 17 | 18 | import com.amazon.ionelement.api.ElementType 19 | import com.amazon.ionelement.api.IonElement 20 | import com.amazon.ionelement.api.IonElementException 21 | import com.amazon.ionelement.api.IonLocation 22 | import com.amazon.ionelement.api.location 23 | import org.partiql.pig.domain.model.TypeAnnotation 24 | import org.partiql.pig.errors.ErrorContext 25 | import org.partiql.pig.errors.PigError 26 | import org.partiql.pig.errors.PigException 27 | 28 | /** 29 | * Variants of [ParserErrorContext] contain details about various parse errors that can be encountered 30 | * during type universe parsing. 31 | * 32 | * They each variant is a data class 33 | */ 34 | sealed class ParserErrorContext(val msgFormatter: () -> String) : ErrorContext { 35 | override val message: String get() = msgFormatter() 36 | 37 | /** Indicates that an [IonElementException] was thrown during parsing of a type universe. */ 38 | data class IonElementError(val ex: IonElementException) : 39 | ParserErrorContext({ ex.message!! }) { 40 | // This is for unit tests... we don't include IonElementException here since it doesn't implement 41 | // equals anyway 42 | override fun equals(other: Any?): Boolean = other is IonElementError 43 | override fun hashCode(): Int = 0 44 | } 45 | 46 | data class UnknownConstructor(val tag: String) : 47 | ParserErrorContext({ "Unknown constructor: '$tag' (expected constructors are 'domain' or 'permute_domain')" }) 48 | 49 | data class InvalidDomainLevelTag(val tag: String) : 50 | ParserErrorContext({ "Invalid domain-level tag: '$tag'" }) 51 | 52 | data class InvalidTopLevelTag(val tag: String) : 53 | ParserErrorContext({ "Invalid top-level tag: '$tag'" }) 54 | 55 | data class InvalidSumLevelTag(val tag: String) : 56 | ParserErrorContext({ "Invalid tag for sum variant: '$tag'" }) 57 | 58 | data class InvalidPermutedDomainTag(val tag: String) : 59 | ParserErrorContext({ "Invalid tag for permute_domain body: '$tag'" }) 60 | 61 | data class InvalidWithSumTag(val tag: String) : 62 | ParserErrorContext({ "Invalid tag for with body: '$tag'" }) 63 | 64 | data class ExpectedTypeReferenceArityTag(val tag: String) : 65 | ParserErrorContext({ "Expected '*' or '?' but found '$tag'" }) 66 | 67 | data class ExpectedSymbolOrSexp(val foundType: ElementType) : 68 | ParserErrorContext({ "Expected a symbol or s-exp but encountered a value of type $foundType" }) 69 | 70 | data class InvalidArity(val expectedCount: Int, val actualCount: Int) : 71 | ParserErrorContext({ "$expectedCount argument(s) were required here, but $actualCount was/were supplied." }) 72 | 73 | data class InvalidArityForTag(val expectedCount: IntRange, val tag: String, val actualCount: Int) : 74 | ParserErrorContext({ "$expectedCount argument(s) were required to '$tag', but $actualCount was/were supplied." }) 75 | 76 | object MissingElementIdentifierAnnotation : 77 | ParserErrorContext({ "Element is missing required name annotation" }) 78 | 79 | object MultipleElementIdentifierAnnotations : 80 | ParserErrorContext({ "Element has multiple name annotations" }) 81 | 82 | object MultipleTypeAnnotations : 83 | ParserErrorContext({ "Only one TypeAnnotation (${TypeAnnotation.values().joinToString()}) is supported" }) 84 | } 85 | 86 | fun parseError(blame: IonLocation?, context: ErrorContext): Nothing = 87 | PigError(blame, context).let { 88 | throw when (context) { 89 | is ParserErrorContext.IonElementError -> { 90 | // Include cause in stack trace for debuggability 91 | PigException(it, context.ex) 92 | } 93 | else -> PigException(it) 94 | } 95 | } 96 | 97 | fun parseError(blame: IonElement, context: ErrorContext): Nothing { 98 | val loc = blame.metas.location 99 | parseError(loc, context) 100 | } 101 | -------------------------------------------------------------------------------- /pig-runtime/src/test/kotlin/org/partiql/pig/runtime/IonElementTransformerBaseTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.IonElement 19 | import com.amazon.ionelement.api.IonElementConstraintException 20 | import com.amazon.ionelement.api.MetaContainer 21 | import com.amazon.ionelement.api.SexpElement 22 | import com.amazon.ionelement.api.emptyMetaContainer 23 | import com.amazon.ionelement.api.ionSexpOf 24 | import com.amazon.ionelement.api.ionSymbol 25 | import org.junit.jupiter.api.Assertions.assertTrue 26 | import org.junit.jupiter.api.Test 27 | import org.junit.jupiter.api.assertThrows 28 | 29 | class IonElementTransformerBaseTests { 30 | 31 | abstract class DummyDomainNode : DomainNode 32 | 33 | data class CorrectDomainNode(val someString: String, override val metas: MetaContainer = emptyMetaContainer()) : DummyDomainNode() { 34 | override fun copy(metas: MetaContainer): DomainNode { 35 | error("does not need to be implemented for tests") 36 | } 37 | 38 | override fun withMeta(metaKey: String, metaValue: Any): DomainNode { 39 | error("does not need to be implemented for tests") 40 | } 41 | override fun toIonElement(): IonElement { 42 | error("does not need to be implemented for tests") 43 | } 44 | } 45 | 46 | data class IncorrectDomainNode(val someString: String) : DummyDomainNode() { 47 | override fun copy(metas: MetaContainer): DomainNode { 48 | error("does not need to be implemented for tests") 49 | } 50 | 51 | override fun withMeta(metaKey: String, metaValue: Any): DomainNode { 52 | error("does not need to be implemented for tests") 53 | } 54 | override val metas: MetaContainer 55 | get() = error("does not need to be implemented for tests") 56 | 57 | override fun toIonElement(): IonElement { 58 | error("does not need to be implemented for tests") 59 | } 60 | } 61 | 62 | class DummyIonElementTransformer : IonElementTransformerBase() { 63 | override fun innerTransform(sexp: SexpElement): DummyDomainNode { 64 | return CorrectDomainNode("foo") 65 | } 66 | 67 | fun expectedDomainNode(): DomainNode = ionSexpOf(ionSymbol("doesntmatter")).asAnyElement().transformExpect() 68 | 69 | fun unexpectedDomainNode() { 70 | // Throws MalformedDomainDataException because [innerTransform] returns an instance of 71 | // [IncorrectDomainType]. 72 | ionSexpOf(ionSymbol("doesntmatter")).asAnyElement().transformExpect() 73 | } 74 | } 75 | 76 | class DummyIonElementTransformerThrowing : IonElementTransformerBase() { 77 | override fun innerTransform(sexp: SexpElement): DummyDomainNode { 78 | // IonElementException constructors have internal visibility, so we force an IonElementConstraintException 79 | // by attempting an impossible conversion from Sexp to Bool. Message will be: 80 | // Expected an element of type BOOL but found an element of type SEXP 81 | sexp.asAnyElement().asBoolean() 82 | TODO("Unreachable!") 83 | } 84 | } 85 | 86 | @Test 87 | fun transformExpect_correctType() { 88 | val xformer = DummyIonElementTransformer() 89 | assertTrue(xformer.expectedDomainNode() is CorrectDomainNode) 90 | } 91 | 92 | @Test 93 | fun transformExpect_incorrectType() { 94 | val xformer = DummyIonElementTransformer() 95 | val ex = assertThrows { xformer.unexpectedDomainNode() } 96 | assertTrue(ex.message!!.contains("IncorrectDomainNode")) 97 | } 98 | 99 | @Test 100 | fun handlesIonElementException() { 101 | val xformer = DummyIonElementTransformerThrowing() 102 | val ex = assertThrows { xformer.transform(ionSexpOf()) } 103 | 104 | assertTrue(ex.cause is IonElementConstraintException) 105 | assertTrue(ex.message!!.contains("Expected an element of type BOOL but found an element of type SEXP")) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pig-tests/src/test/kotlin/org/partiql/pig/tests/PermuteTransformSmokeTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.tests 17 | 18 | import com.amazon.ionelement.api.ionInt 19 | import org.junit.jupiter.api.Test 20 | import org.partiql.pig.runtime.asPrimitive 21 | import org.partiql.pig.tests.generated.ToyLang 22 | import org.partiql.pig.tests.generated.ToyLangIndexed 23 | import org.partiql.pig.tests.generated.ToyLangIndexedToToyLangVisitorTransform 24 | import org.partiql.pig.tests.generated.ToyLangToToyLangIndexedVisitorTransform 25 | import kotlin.test.assertEquals 26 | 27 | class PermuteTransformSmokeTests { 28 | /** 29 | * This is a VisitorTransformToToyLangNameless implementation that transforms variable references from names 30 | * to indexes. 31 | * 32 | * The input is an instance of the [ToyLang] domain model and the result is an instance of the 33 | * [ToyLangNameless] domain. 34 | */ 35 | private class VariableResolverToPermutedDomain( 36 | val scope: Scope = Scope.Global 37 | ) : ToyLangToToyLangIndexedVisitorTransform() { 38 | 39 | /** Any variable reference that resides in the top scope is undefined. */ 40 | override fun transformExprVariable(node: ToyLang.Expr.Variable): ToyLangIndexed.Expr { 41 | val thiz = this 42 | return ToyLangIndexed.build { 43 | variable_(name = node.name, index = thiz.scope.findIndex(node.name.text).asPrimitive()) 44 | } 45 | } 46 | 47 | override fun transformExprLet(node: ToyLang.Expr.Let): ToyLangIndexed.Expr = 48 | ToyLangIndexed.build { 49 | val nestedScope = this@VariableResolverToPermutedDomain.scope.nest(node.name.text) 50 | 51 | let_( 52 | name = node.name, 53 | index = nestedScope.index.asPrimitive(), 54 | value = transformExpr(node.value), 55 | body = VariableResolverToPermutedDomain(scope.nest(node.name.text)).transformExpr(node.body) 56 | ) 57 | } 58 | } 59 | 60 | @Test 61 | fun `demonstrate a simple variable index resolver that transforms to a permuted domain and back again`() { 62 | /* 63 | Equivalent to: 64 | let foo = 42 in 65 | let bar = 48 in 66 | foo + bar 67 | */ 68 | val unindexed = ToyLang.build { 69 | let( 70 | "foo", 71 | lit(ionInt(42)), 72 | let( 73 | "bar", 74 | lit(ionInt(48)), 75 | nary(plus(), variable("foo"), variable("bar")) 76 | ) 77 | ) 78 | } 79 | 80 | val resolver = VariableResolverToPermutedDomain() 81 | val outerLet = resolver.transformExpr(unindexed) as ToyLangIndexed.Expr.Let 82 | 83 | val indexed = ToyLangIndexed.build { 84 | let( 85 | "foo", 86 | 0, 87 | lit(ionInt(42)), 88 | let( 89 | "bar", 90 | 1, 91 | lit(ionInt(48)), 92 | nary(plus(), variable("foo", 0), variable("bar", 1)) 93 | ) 94 | ) 95 | } 96 | assertEquals(indexed, outerLet) 97 | 98 | // We can also go the other direction 99 | 100 | val indexedToUnindexed = object : ToyLangIndexedToToyLangVisitorTransform() { 101 | override fun transformExprVariable(node: ToyLangIndexed.Expr.Variable): ToyLang.Expr = 102 | ToyLang.build { 103 | variable_( 104 | name = node.name, 105 | metas = node.metas 106 | ) 107 | } 108 | 109 | override fun transformExprLet(node: ToyLangIndexed.Expr.Let): ToyLang.Expr = 110 | ToyLang.build { 111 | let_( 112 | name = node.name, 113 | value = transformExpr(node.value), 114 | body = transformExpr(node.body), 115 | metas = node.metas 116 | ) 117 | } 118 | } 119 | 120 | val roundTripped = indexedToUnindexed.transformExpr(indexed) 121 | assertEquals(unindexed, roundTripped) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /pig/src/test/kotlin/org/partiql/pig/domain/PermuteDomainTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain 17 | 18 | import com.amazon.ion.system.IonReaderBuilder 19 | import org.junit.jupiter.api.Assertions.assertEquals 20 | import org.junit.jupiter.api.Assertions.assertTrue 21 | import org.junit.jupiter.api.Test 22 | import org.partiql.pig.domain.model.Arity 23 | import org.partiql.pig.domain.model.DataType 24 | import org.partiql.pig.domain.model.TypeUniverse 25 | import org.partiql.pig.domain.parser.parseTypeUniverse 26 | 27 | class PermuteDomainTests { 28 | /** 29 | * Runs a simple type universe that uses through all processing steps and verifies the result. 30 | * 31 | * Happy path only--no error handling being tested here. 32 | * 33 | * TODO: I believe equality is well implemented now, can this test be simplified? 34 | * Also, equality for type domain objects is not well implemented which is the reason we do all 35 | * the assertions instead of comparing test_domain_ex to a parsed example concrete domain. 36 | */ 37 | @Test 38 | fun permuteSmokeTest() { 39 | val typeUniverseWithExtensions = """ 40 | (define test_domain 41 | (domain 42 | (product pair first::ion second::ion) 43 | (product other_pair first::symbol second::symbol) 44 | (sum thing 45 | (a x::pair) 46 | (b y::symbol) 47 | (c z::int)))) 48 | 49 | (define permuted_domain 50 | (permute_domain test_domain 51 | (exclude pair) 52 | (include 53 | (product pair n::int t::int) 54 | (product new_pair m::symbol n::int)) 55 | (with thing 56 | (exclude a) 57 | (include 58 | (d a::pair) 59 | (e b::symbol))))) 60 | """ 61 | 62 | val td: TypeUniverse = IonReaderBuilder.standard().build(typeUniverseWithExtensions).use { parseTypeUniverse(it) } 63 | 64 | val concretes = td.computeTypeDomains() 65 | 66 | // Fist we perform some basic assertions on the concrete domain 67 | val concreteDomain = concretes.single { it.tag == "test_domain" } 68 | 69 | // In the original domain, the `pair` type consists of two ions 70 | val ionPair = concreteDomain.types.single { it.tag == "pair" } as DataType.UserType.Tuple 71 | assertEquals(2, ionPair.namedElements.size) 72 | assertTrue(ionPair.namedElements.all { it.typeReference.typeName == "ion" && it.typeReference.arity is Arity.Required }) 73 | 74 | // In test.domain the "thing" sum has "a", "b", and "c" variants. 75 | val thing = concreteDomain.types.single { it.tag == "thing" } as DataType.UserType.Sum 76 | assertEquals(3, thing.variants.size) 77 | assertEquals(1, thing.variants.filter { it.tag == "a" }.size) 78 | assertEquals(1, thing.variants.filter { it.tag == "b" }.size) 79 | assertEquals(1, thing.variants.filter { it.tag == "c" }.size) 80 | 81 | // Then we verify the permuted domain 82 | val permutedDomain = concretes.single { it.tag == "permuted_domain" } 83 | // Permute domain still has 3 types of test_domain + 1 more 84 | assertEquals(4, permutedDomain.userTypes.size) 85 | assertTrue(permutedDomain.types.map { it.tag }.containsAll(listOf("pair", "thing", "other_pair", "new_pair"))) 86 | 87 | // In the permuted domain, the 'pair' type consists of two ints' 88 | val intPair = permutedDomain.types.single { it.tag == "pair" } as DataType.UserType.Tuple 89 | assertEquals(2, intPair.namedElements.size) 90 | assertTrue(intPair.namedElements.all { it.typeReference.typeName == "int" && it.typeReference.arity is Arity.Required }) 91 | 92 | // In the permuted domain, the "thing.a" variant has been replaced with "thing.d" and "thing.e" has been added 93 | val exThing = permutedDomain.types.single { it.tag == "thing" } as DataType.UserType.Sum 94 | assertEquals(4, exThing.variants.size) 95 | assertTrue(exThing.variants.none { it.tag == "a" }) 96 | assertEquals(1, exThing.variants.filter { it.tag == "b" }.size) 97 | assertEquals(1, exThing.variants.filter { it.tag == "c" }.size) 98 | assertEquals(1, exThing.variants.filter { it.tag == "d" }.size) 99 | assertEquals(1, exThing.variants.filter { it.tag == "e" }.size) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /pig-runtime/src/main/kotlin/org/partiql/pig/runtime/IntermediateRecord.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.AnyElement 19 | import com.amazon.ionelement.api.IonLocation 20 | import com.amazon.ionelement.api.SexpElement 21 | import com.amazon.ionelement.api.location 22 | 23 | /** 24 | * Contains an "intermediate" representation of a PIG record. 25 | * 26 | * This class was intended only for use by PIG-generated domain serialization code. Human written client code 27 | * should avoid using this class. 28 | * 29 | * Instances are single use only and should be discarded after extracting all field values with [processRequiredField] 30 | * or [processOptionalField]. 31 | */ 32 | class IntermediateRecord( 33 | /** The tag of the record. Used for error reporting purposes only. */ 34 | private val recordTagName: String, 35 | /** The location of the record within the data being transformer. */ 36 | private val location: IonLocation?, 37 | /** The fields and their values. */ 38 | fields: Map> 39 | ) { 40 | private val fieldMap = fields.toMutableMap() 41 | 42 | /** 43 | * If a field of named [fieldName] exists in [fieldMap], removes it from [fieldMap] and passes its value to 44 | * [deserFunc] to perform deserialization. Returns `null` if the field does not exist in [fieldMap]. 45 | */ 46 | fun processOptionalField(fieldName: String, deserFunc: (AnyElement) -> T): T? = 47 | fieldMap.remove(fieldName)?.let { values: List -> 48 | values.requireArityOrMalformed(fieldName, 1) 49 | deserFunc(values.single()) 50 | } 51 | 52 | /** 53 | * Same as [processOptionalField] but throws [MalformedDomainDataException] if the field does not exist 54 | * in [fieldMap].] 55 | */ 56 | fun processRequiredField(fieldName: String, deserFunc: (AnyElement) -> T): T = 57 | processOptionalField(fieldName, deserFunc) 58 | ?: errMalformed(location, "Required field '$fieldName' was not found within '$recordTagName' record") 59 | 60 | /** 61 | * Processes a variadic record field. 62 | * 63 | * Throws [MalformedDomainDataException] if [minArity] is > 0 and the field is not present. 64 | */ 65 | fun processVariadicField(fieldName: String, minArity: Int, deserFunc: (AnyElement) -> T): List { 66 | val foundFieldValues = fieldMap.remove(fieldName) ?: emptyList() 67 | foundFieldValues.requireArityOrMalformed(fieldName, minArity..Int.MAX_VALUE) 68 | return foundFieldValues.map { 69 | deserFunc(it) 70 | } 71 | } 72 | 73 | private fun List.requireArityOrMalformed(fieldName: String, size: Int) = 74 | this@requireArityOrMalformed.requireArityOrMalformed(fieldName, IntRange(size, size)) 75 | 76 | private fun List.requireArityOrMalformed(fieldName: String, arityRange: IntRange) { 77 | if (this.size !in arityRange) { 78 | errMalformed( 79 | location, 80 | "$arityRange values(s) were required to for field '$fieldName' of record '$recordTagName' " + 81 | "but ${this.size} was/were supplied." 82 | ) 83 | } 84 | } 85 | 86 | /** 87 | * After all required an optional fields in a record have been processed by the transformer, this 88 | * function should be invoked to throw a [MalformedDomainDataException] if any unprocessed fields remain in 89 | * [fieldMap]. This would indicate that a mis-named field was present. 90 | */ 91 | fun malformedIfAnyUnprocessedFieldsRemain() { 92 | if (fieldMap.isNotEmpty()) { 93 | errUnexpectedField(location, fieldMap.keys.first()) 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Does part of the work of deserializing records. 100 | * 101 | * Converts the receiver [AnyElement], which must be an expression in the form of: 102 | * 103 | * ``` 104 | * '(' [ '(' ')' ]... ')' 105 | * ``` 106 | * 107 | * To an instance of [IntermediateRecord]. 108 | */ 109 | fun SexpElement.transformToIntermediateRecord(): IntermediateRecord { 110 | val recordTagName = this.head.symbolValue 111 | val recordFields = this.tail 112 | 113 | val fieldMap = recordFields.map { field: AnyElement -> 114 | val fieldSexp = field.asSexp() 115 | fieldSexp.head.symbolValue to fieldSexp.values.tail 116 | }.toMap() 117 | 118 | return IntermediateRecord(recordTagName, this.metas.location, fieldMap) 119 | } 120 | -------------------------------------------------------------------------------- /pig/src/main/kotlin/org/partiql/pig/main.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig 17 | 18 | import com.amazon.ion.system.IonReaderBuilder 19 | import org.partiql.pig.cmdline.Command 20 | import org.partiql.pig.cmdline.CommandLineParser 21 | import org.partiql.pig.cmdline.TargetLanguage 22 | import org.partiql.pig.domain.model.TypeUniverse 23 | import org.partiql.pig.domain.parser.parseTypeUniverse 24 | import org.partiql.pig.errors.PigException 25 | import org.partiql.pig.generator.custom.applyCustomTemplate 26 | import org.partiql.pig.generator.html.applyHtmlTemplate 27 | import org.partiql.pig.generator.ion.generateIon 28 | import org.partiql.pig.generator.kotlin.convertToKTypeUniverse 29 | import org.partiql.pig.generator.kotlin.generateKotlinCode 30 | import java.io.File 31 | import java.io.FileInputStream 32 | import java.io.PrintWriter 33 | import kotlin.system.exitProcess 34 | 35 | fun progress(msg: String) = 36 | println("pig: $msg") 37 | 38 | /** 39 | * Entry point for when pig is being invoked from the command-line. 40 | */ 41 | fun main(args: Array) { 42 | val cmdParser = CommandLineParser() 43 | 44 | when (val command = cmdParser.parse(args)) { 45 | is Command.ShowHelp -> cmdParser.printHelp(System.out) 46 | is Command.ShowVersion -> cmdParser.printVersion(System.out) 47 | is Command.InvalidCommandLineArguments -> { 48 | System.err.println(command.message) 49 | exitProcess(-1) 50 | } 51 | is Command.Generate -> { 52 | try { 53 | generateCode(command) 54 | } catch (e: PigException) { 55 | System.err.println("pig: ${e.error.location}: ${e.error.context.message}\n${e.stackTrace}") 56 | exitProcess(-1) 57 | } 58 | } 59 | } 60 | } 61 | 62 | /** 63 | * Gradle projects such as the PartiQL reference implementation take a dependency on this project 64 | * in their `buildSrc` directory and can then use this entry point to generate code direclty without 65 | * having to `exec` pig as a separate process. 66 | */ 67 | fun generateCode(command: Command.Generate) { 68 | progress("universe file: ${command.typeUniverseFile}") 69 | 70 | progress("parsing the universe...") 71 | val typeUniverse: TypeUniverse = FileInputStream(command.typeUniverseFile).use { inputStream -> 72 | IonReaderBuilder.standard().build(inputStream).use { ionReader -> parseTypeUniverse(ionReader) } 73 | } 74 | 75 | progress("permuting domains...") 76 | 77 | val computedDomains = typeUniverse.computeTypeDomains() 78 | val filteredDomains = command.target.domains?.let { computedDomains.filter { domain -> domain.tag in it } } ?: computedDomains 79 | 80 | when (command.target) { 81 | is TargetLanguage.Kotlin -> { 82 | progress("applying Kotlin pre-processing") 83 | val kotlinTypeUniverse = typeUniverse.convertToKTypeUniverse(computedDomains, filteredDomains, command.target.domains) 84 | prepareOutputDirectory(command.target.outputDirectory) 85 | progress("applying the Kotlin template once for each domain...") 86 | 87 | generateKotlinCode(command.target.namespace, kotlinTypeUniverse, command.target.outputDirectory) 88 | } 89 | is TargetLanguage.Custom -> { 90 | progress("output file : ${command.target.outputFile}") 91 | progress("applying ${command.target.templateFile}") 92 | 93 | PrintWriter(command.target.outputFile).use { printWriter -> 94 | applyCustomTemplate(command.target.templateFile, filteredDomains, printWriter) 95 | } 96 | } 97 | is TargetLanguage.Html -> { 98 | progress("output file : ${command.target.outputFile}") 99 | progress("applying the HTML template") 100 | 101 | PrintWriter(command.target.outputFile).use { printWriter -> 102 | applyHtmlTemplate(filteredDomains, printWriter) 103 | } 104 | } 105 | is TargetLanguage.Ion -> { 106 | progress("output file : ${command.target.outputFile}") 107 | progress("applying the Ion template") 108 | 109 | PrintWriter(command.target.outputFile).use { printWriter -> 110 | generateIon(filteredDomains, printWriter) 111 | } 112 | } 113 | } 114 | 115 | progress("universe generation complete!") 116 | } 117 | 118 | private fun prepareOutputDirectory(dir: File) { 119 | if (dir.exists()) { 120 | if (!dir.isDirectory) { 121 | error("The path specified as the output directory exists but is not a directory.") 122 | } 123 | } else { 124 | dir.mkdirs() 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainParserErrorsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.domain 17 | 18 | import com.amazon.ionelement.api.ElementType 19 | import com.amazon.ionelement.api.IonElementException 20 | import com.amazon.ionelement.api.ionSexpOf 21 | import org.junit.jupiter.api.Assertions.assertEquals 22 | import org.junit.jupiter.api.Assertions.assertNotNull 23 | import org.junit.jupiter.api.assertThrows 24 | import org.junit.jupiter.params.ParameterizedTest 25 | import org.junit.jupiter.params.provider.MethodSource 26 | import org.partiql.pig.domain.parser.ParserErrorContext 27 | import org.partiql.pig.domain.parser.parseTypeUniverse 28 | import org.partiql.pig.errors.PigError 29 | import org.partiql.pig.errors.PigException 30 | 31 | class TypeDomainParserErrorsTest { 32 | 33 | // TODO: aren't there more permute_domain related errors? 34 | 35 | @ParameterizedTest 36 | @MethodSource("parametersForErrorsTest") 37 | fun errorsTest(tc: TestCase) { 38 | val ex = assertThrows { 39 | val oops = parseTypeUniverse(tc.typeUniverseText) 40 | println("this was erroneously parsed: ${oops.toIonElement()}") 41 | } 42 | assertEquals(tc.expectedError, ex.error) 43 | 44 | // This is mainly to improve code coverage but also ensures the 45 | // message formatting function doesn't throw 46 | assertNotNull(tc.expectedError.toString()) 47 | } 48 | 49 | companion object { 50 | data class TestCase(val typeUniverseText: String, val expectedError: PigError) 51 | 52 | @JvmStatic 53 | @Suppress("unused") 54 | fun parametersForErrorsTest() = listOf( 55 | TestCase( 56 | "(", // note: ParserErrorContext.IonElementError.equals doesn't check the exception 57 | // IonElementException constructors are not visible, so we force an exception in order to get an instance. 58 | makeErr(ParserErrorContext.IonElementError((runCatching { ionSexpOf().asAnyElement().asBoolean() }.exceptionOrNull() as IonElementException))) 59 | ), 60 | 61 | TestCase( 62 | "(bad_tag)", 63 | makeErr(1, 2, ParserErrorContext.InvalidTopLevelTag("bad_tag")) 64 | ), 65 | 66 | TestCase( 67 | "(define huh hee hoo)", 68 | makeErr(1, 1, ParserErrorContext.InvalidArityForTag(2..2, "define", 3)) 69 | ), 70 | 71 | TestCase( 72 | "(define huh (invalid_constructor))", 73 | makeErr(1, 14, ParserErrorContext.UnknownConstructor("invalid_constructor")) 74 | ), 75 | 76 | TestCase( 77 | "(define huh (domain (bad_tag)))", 78 | makeErr(1, 22, ParserErrorContext.InvalidDomainLevelTag("bad_tag")) 79 | ), 80 | 81 | TestCase( 82 | "(define huh (domain (product one::huh two::int three::(bad_tag))))", 83 | makeErr(1, 56, ParserErrorContext.ExpectedTypeReferenceArityTag("bad_tag")) 84 | ), 85 | 86 | TestCase( 87 | "(define huh (permute_domain huh (bad_tag)))", 88 | makeErr(1, 33, ParserErrorContext.InvalidPermutedDomainTag("bad_tag")) 89 | ), 90 | 91 | TestCase( 92 | "(define huh (permute_domain huh (with huh (bad_tag))))", 93 | makeErr(1, 43, ParserErrorContext.InvalidWithSumTag("bad_tag")) 94 | ), 95 | 96 | TestCase( 97 | "(define huh (domain (product x::huh y::(? ))))", 98 | makeErr(1, 37, ParserErrorContext.InvalidArityForTag(IntRange(1, 1), "?", 0)) 99 | ), 100 | 101 | TestCase( 102 | "(define huh (domain (product huh x::(? o::one t::two))))", 103 | makeErr(1, 34, ParserErrorContext.InvalidArityForTag(IntRange(1, 1), "?", 2)) 104 | ), 105 | 106 | TestCase( 107 | "(define huh (domain (record foo (name_field_with_too_few_elements))))", 108 | makeErr(1, 33, ParserErrorContext.InvalidArity(2, 1)) 109 | ), 110 | 111 | TestCase( 112 | "(define huh (domain (record foo (name_field_with_too many elements))))", 113 | makeErr(1, 33, ParserErrorContext.InvalidArity(2, 3)) 114 | ), 115 | 116 | TestCase( // Covers first place in parser this can be thrown 117 | "(define huh (domain (product huh x::42)))", 118 | makeErr(1, 34, ParserErrorContext.ExpectedSymbolOrSexp(ElementType.INT)) 119 | ), 120 | 121 | TestCase( // Covers second place in parser this can be thrown 122 | "(define huh (domain (product huh x::int y::42)))", 123 | makeErr(1, 41, ParserErrorContext.ExpectedSymbolOrSexp(ElementType.INT)) 124 | ) 125 | ) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /pig-runtime/src/test/kotlin/org/partiql/pig/runtime/IntermediateRecordTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package org.partiql.pig.runtime 17 | 18 | import com.amazon.ionelement.api.IonElement 19 | import com.amazon.ionelement.api.IonElementLoaderOptions 20 | import com.amazon.ionelement.api.IonTextLocation 21 | import com.amazon.ionelement.api.ionInt 22 | import com.amazon.ionelement.api.loadSingleElement 23 | import org.junit.jupiter.api.Assertions.assertDoesNotThrow 24 | import org.junit.jupiter.api.Assertions.assertEquals 25 | import org.junit.jupiter.api.Assertions.assertTrue 26 | import org.junit.jupiter.api.Test 27 | import org.junit.jupiter.api.assertThrows 28 | import org.junit.jupiter.api.fail 29 | import org.junit.jupiter.params.ParameterizedTest 30 | import org.junit.jupiter.params.provider.MethodSource 31 | 32 | class IntermediateRecordTests { 33 | private val oneOne = IonTextLocation(1, 1) 34 | 35 | @Test 36 | fun happyPath() { 37 | 38 | val someRecord = """ 39 | (some_record 40 | (foo 1) 41 | (bar 2) 42 | (bat 3) 43 | (variadic_foo 4) 44 | (variadic_empty)) 45 | """.trimIndent() 46 | 47 | val ir = createIntermediateRecord(someRecord) 48 | 49 | val foundFields = mutableListOf() 50 | 51 | // Required fields 52 | ir.processRequiredField("foo") { foundFields.add(it) } 53 | ir.processRequiredField("bar") { foundFields.add(it) } 54 | 55 | // Optional field that's present 56 | ir.processOptionalField("bat") { foundFields.add(it) } 57 | 58 | // Optional field that's not present 59 | ir.processOptionalField("baz") { foundFields.add(it) } 60 | 61 | // Variadic field with minimum arity of 1 62 | ir.processVariadicField("variadic_foo", 1) { foundFields.add(it) } 63 | 64 | // Variadic field with minimum arity of 0 that is present with no values 65 | // (should be removed so that the call to malformedIfAnyUnprocessedFieldsRemain does not throw) 66 | ir.processVariadicField("variadic_empty", 0) { foundFields.add(it) } 67 | 68 | // Variadic field with minimum arity of 0 that is not present (should not throw) 69 | ir.processVariadicField("variadic_not_present", 0) { fail("should not get called") } 70 | 71 | assertDoesNotThrow { ir.malformedIfAnyUnprocessedFieldsRemain() } 72 | assertEquals(listOf(ionInt(1), ionInt(2), ionInt(3), ionInt(4)), foundFields) 73 | } 74 | 75 | @ParameterizedTest 76 | @MethodSource("parametersForMalformedTest") 77 | fun malformedTests(tc: MalformedTestCase) { 78 | val ex = assertThrows { 79 | tc.blockThrowingMalformedDomainDataException() 80 | } 81 | tc.messageMustContainStrings.forEach { 82 | assertTrue(ex.message!!.contains(it), "exception message must contain '$it'") 83 | } 84 | assertEquals(oneOne, ex.location) 85 | } 86 | 87 | companion object { 88 | @JvmStatic 89 | @Suppress("UNUSED") 90 | fun parametersForMalformedTest() = listOf( 91 | MalformedTestCase( 92 | "required field missing", 93 | { createIntermediateRecord("(some_record (foo 1))").processRequiredField("bad_field") { fail("should not be called") } }, 94 | listOf("bad_field") 95 | ), 96 | MalformedTestCase( 97 | "required field arity too high", 98 | { createIntermediateRecord("(some_record (foo 1 2))").processRequiredField("foo") { fail("should not be called") } }, 99 | listOf("foo", "1..1") 100 | ), 101 | MalformedTestCase( 102 | "optional field arity too high", 103 | { createIntermediateRecord("(some_record (foo 1 2))").processOptionalField("foo") { fail("should not be called") } }, 104 | listOf("foo", "1..1") 105 | ), 106 | MalformedTestCase( 107 | "variadic arity too low", 108 | { createIntermediateRecord("(some_record (foo 1 2))").processVariadicField("foo", 3) { fail("should not be called") } }, 109 | listOf("foo", "3..2147483647") 110 | ), 111 | MalformedTestCase( 112 | "variadic arity too low", 113 | { createIntermediateRecord("(some_record (foo 1 2))").malformedIfAnyUnprocessedFieldsRemain() }, 114 | listOf("Unexpected", "foo") 115 | ) 116 | ) 117 | 118 | private fun createIntermediateRecord(recordIonText: String): IntermediateRecord = 119 | loadSingleElement(recordIonText, IonElementLoaderOptions(includeLocationMeta = true)) 120 | .asSexp() 121 | .transformToIntermediateRecord() 122 | 123 | data class MalformedTestCase( 124 | val name: String, 125 | val blockThrowingMalformedDomainDataException: () -> Unit, 126 | val messageMustContainStrings: List 127 | ) 128 | } 129 | } 130 | --------------------------------------------------------------------------------