diff --git a/CHANGELOG.md b/CHANGELOG.md index bc7196a9b..381fabda5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security +## [0.9.2] - 2023-01-20 + +### Added +- Adds ability to pipe queries to the CLI +- Adds ability to run PartiQL files as executables by adding support for shebangs + +### Changed + +### Deprecated + +### Fixed +- Fixes list/bag ExprValue creation in plan evaluator +- Fixes gradle build issues. + +### Removed + +### Security + ## [0.9.1] - 2023-01-04 ### Added diff --git a/buildSrc/src/main/kotlin/partiql.versions.kt b/buildSrc/src/main/kotlin/partiql.versions.kt index 3c185a166..681e71db9 100644 --- a/buildSrc/src/main/kotlin/partiql.versions.kt +++ b/buildSrc/src/main/kotlin/partiql.versions.kt @@ -34,7 +34,7 @@ object Versions { const val jline = "3.21.0" const val jmh = "0.5.3" const val joda = "2.12.1" - const val jopt = "5.0" + const val picoCli = "4.7.0" const val ktlint = "10.2.1" const val pig = "0.6.1" //---Testing @@ -66,7 +66,7 @@ object Deps { val jansi = "org.fusesource.jansi:jansi:${Versions.jansi}" val jline = "org.jline:jline:${Versions.jline}" val joda = "joda-time:joda-time:${Versions.joda}" - val jopt = "net.sf.jopt-simple:jopt-simple:${Versions.jopt}" + const val picoCli = "info.picocli:picocli:${Versions.picoCli}" val pig = "org.partiql:partiql-ir-generator:${Versions.pig}" val pigRuntime = "org.partiql:partiql-ir-generator-runtime:${Versions.pig}" //---Testing diff --git a/cli/src/main/kotlin/org/partiql/cli/main.kt b/cli/src/main/kotlin/org/partiql/cli/main.kt deleted file mode 100644 index 5d5203f95..000000000 --- a/cli/src/main/kotlin/org/partiql/cli/main.kt +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at: - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific - * language governing permissions and limitations under the License. - */ -@file:JvmName("Main") - -@file:Suppress("DEPRECATION") - -package org.partiql.cli - -import com.amazon.ion.system.IonSystemBuilder -import joptsimple.BuiltinHelpFormatter -import joptsimple.OptionDescriptor -import joptsimple.OptionException -import joptsimple.OptionParser -import joptsimple.OptionSet -import org.partiql.cli.functions.ReadFile -import org.partiql.cli.functions.WriteFile -import org.partiql.extensions.cli.functions.QueryDDB -import org.partiql.lang.eval.Bindings -import org.partiql.lang.eval.EvaluationSession -import org.partiql.lang.eval.ExprFunction -import org.partiql.lang.eval.ExprValue -import org.partiql.lang.eval.ExprValueFactory -import org.partiql.lang.eval.PartiQLResult -import org.partiql.lang.eval.ProjectionIterationBehavior -import org.partiql.lang.eval.TypedOpBehavior -import org.partiql.lang.eval.TypingMode -import org.partiql.lang.eval.UndefinedVariableBehavior -import org.partiql.lang.syntax.PartiQLParserBuilder -import org.partiql.pipeline.AbstractPipeline -import org.partiql.shell.Shell -import org.partiql.shell.Shell.ShellConfiguration -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import kotlin.system.exitProcess - -// TODO how can a user pass the catalog here? -private val ion = IonSystemBuilder.standard().build() -private val valueFactory = ExprValueFactory.standard(ion) - -private val optParser = OptionParser() - -private val formatter = object : BuiltinHelpFormatter(120, 2) { - override fun format(options: MutableMap?): String { - return """PartiQL CLI - |Command line interface for executing PartiQL queries. Can be run in an interactive (REPL) mode or non-interactive. - | - |Examples: - |To run in REPL mode simply execute the executable without any arguments: - | partiql - | - |In non-interactive mode we use Ion as the format for input data which is bound to a global variable - |named "input_data", in the example below /logs/log.ion is bound to "input_data": - | partiql --query="SELECT * FROM input_data" --input=/logs/log.ion - | - |The cli can output using PartiQL syntax or Ion using the --output-format option, e.g. to output binary ion: - | partiql --query="SELECT * FROM input_data" --output-format=ION_BINARY --input=/logs/log.ion - | - |To pipe input data in via stdin: - | cat /logs/log.ion | partiql --query="SELECT * FROM input_data" --format=ION_BINARY > output.10n - | - |${super.format(options)} - """.trimMargin() - } -} - -enum class InputFormat { - PARTIQL, ION -} - -enum class OutputFormat { - ION_TEXT, ION_BINARY, PARTIQL, PARTIQL_PRETTY -} - -enum class Pipeline { - STANDARD, EXPERIMENTAL -} - -// opt parser options - -private val helpOpt = optParser.acceptsAll(listOf("help", "h"), "prints this help") - .forHelp() - -private val queryOpt = optParser.acceptsAll(listOf("query", "q"), "PartiQL query, triggers non interactive mode") - .withRequiredArg() - .ofType(String::class.java) - -private val permissiveModeOpt = optParser.acceptsAll(listOf("permissive", "p"), "runs the query in permissive mode") - -private val typedOpBehaviorOpt = optParser.acceptsAll(listOf("typed-op-behavior", "t"), "indicates how CAST should behave") - .withRequiredArg() - .ofType(TypedOpBehavior::class.java) - .describedAs("(${TypedOpBehavior.values().joinToString("|")})") - .defaultsTo(TypedOpBehavior.HONOR_PARAMETERS) - -private val projectionIterationBehaviorOpt = optParser.acceptsAll(listOf("projection-iter-behavior", "r"), "Controls the behavior of ExprValue.iterator in the projection result") - .withRequiredArg() - .ofType(ProjectionIterationBehavior::class.java) - .describedAs("(${ProjectionIterationBehavior.values().joinToString("|")})") - .defaultsTo(ProjectionIterationBehavior.FILTER_MISSING) - -private val undefinedVariableBehaviorOpt = optParser.acceptsAll(listOf("undefined-variable-behavior", "v"), "Defines the behavior when a non-existent variable is referenced") - .withRequiredArg() - .ofType(UndefinedVariableBehavior::class.java) - .describedAs("(${UndefinedVariableBehavior.values().joinToString("|")})") - .defaultsTo(UndefinedVariableBehavior.ERROR) - -private val environmentOpt = optParser.acceptsAll(listOf("environment", "e"), "initial global environment (optional)") - .withRequiredArg() - .ofType(File::class.java) - -private val inputFileOpt = optParser.acceptsAll(listOf("input", "i"), "input file, requires the query option (default: stdin)") - .availableIf(queryOpt) - .withRequiredArg() - .ofType(File::class.java) - -private val inputFormatOpt = optParser.acceptsAll(listOf("input-format", "if"), "input format, requires the query option") - .availableIf(queryOpt) - .withRequiredArg() - .ofType(InputFormat::class.java) - .describedAs("(${InputFormat.values().joinToString("|")})") - .defaultsTo(InputFormat.ION) - -private val wrapIonOpt = optParser.acceptsAll(listOf("wrap-ion", "w"), "wraps Ion input file values in a bag, requires the input format to be ION, requires the query option") - .availableIf(queryOpt) - -private val monochromeOpt = optParser.acceptsAll(listOf("monochrome", "m"), "removes syntax highlighting for the REPL") - -private val outputFileOpt = optParser.acceptsAll(listOf("output", "o"), "output file, requires the query option (default: stdout)") - .availableIf(queryOpt) - .withRequiredArg() - .ofType(File::class.java) - -private val outputFormatOpt = optParser.acceptsAll(listOf("output-format", "of"), "output format, requires the query option") - .availableIf(queryOpt) - .withRequiredArg() - .ofType(OutputFormat::class.java) - .describedAs("(${OutputFormat.values().joinToString("|")})") - .defaultsTo(OutputFormat.PARTIQL) - -private val pipelineOpt = optParser.acceptsAll(listOf("pipeline"), "pipeline implementation") - .withRequiredArg() - .ofType(Pipeline::class.java) - .describedAs("(${Pipeline.values().joinToString("|")})") - .defaultsTo(Pipeline.STANDARD) - -/** - * Runs PartiQL CLI. - * - * Has two modes: - * * Interactive (default): Starts a REPL - * * Non-interactive: takes in an PartiQL query as a command line input - * - * Options: - * * -e --environment: takes an environment file to load as the initial global environment - * * -p --permissive: run the query in permissive typing mode (returns MISSING rather than error for data type - * * -t --typed-op-behavior: indicates how CAST should behave: (default: HONOR_PARAMETERS) [LEGACY, HONOR_PARAMETERS] - * * -r --projection-iter-behavior: Controls the behavior of ExprValue.iterator in the projection result: (default: FILTER_MISSING) [FILTER_MISSING, UNFILTERED] - * * -v --undefined-variable-behavior: Defines the behavior when a non-existent variable is referenced: (default: ERROR) [ERROR, MISSING] - * mismatches) - * * Interactive only: - * * -m --monochrome: removes syntax highlighting for the REPL - * * Non interactive only: - * * -q --query: PartiQL query - * * -i --input: input file - * * -if --input-format: (default: ION) [ION, PARTIQL] - * * -w --wrap-ion: wraps Ion input file values in a bag, requires the input format to be ION, requires the query option - * * -o --output: output file (default: STDOUT) - * * -of --output-format: (default: PARTIQL) [ION_TEXT, ION_BINARY, PARTIQL, PARTIQL_PRETTY] - */ -fun main(args: Array) = try { - optParser.formatHelpWith(formatter) - - val optionSet = optParser.parse(*args) - if (optionSet.has(helpOpt)) { - optParser.printHelpOn(System.out) - exitProcess(0) // print help and bail - } - - if (optionSet.nonOptionArguments().isNotEmpty()) { - throw IllegalArgumentException("Non option arguments are not allowed!") - } - - // Create Pipeline and Environment - val options = createPipelineOptions(optionSet) - val pipeline = AbstractPipeline.create(options) - val environment = when (optionSet.has(environmentOpt)) { - true -> getEnvironment(optionSet.valueOf(environmentOpt), pipeline) - else -> Bindings.empty() - } - - when (optionSet.has(queryOpt)) { - true -> runCli(environment, optionSet, pipeline) - false -> runShell(environment, optionSet, pipeline) - } -} catch (e: OptionException) { - System.err.println("${e.message}\n") - optParser.printHelpOn(System.err) - exitProcess(1) -} catch (e: Exception) { - e.printStackTrace(System.err) - exitProcess(1) -} - -private fun createPipelineOptions(optionSet: OptionSet): AbstractPipeline.PipelineOptions { - val ion = IonSystemBuilder.standard().build() - val pipeline = optionSet.valueOf(pipelineOpt) - val typedOpBehavior = optionSet.valueOf(typedOpBehaviorOpt) - val projectionIteration = optionSet.valueOf(projectionIterationBehaviorOpt) - val undefinedVariable = optionSet.valueOf(undefinedVariableBehaviorOpt) - val permissiveMode = when (optionSet.has(permissiveModeOpt)) { - true -> TypingMode.PERMISSIVE - false -> TypingMode.LEGACY - } - - val functions: List<(ExprValueFactory) -> ExprFunction> = listOf( - { valueFactory -> ReadFile(valueFactory) }, - { valueFactory -> WriteFile(valueFactory) }, - { valueFactory -> QueryDDB(valueFactory) } - ) - - val parser = PartiQLParserBuilder().ionSystem(ion).build() - return AbstractPipeline.PipelineOptions( - pipeline, - ion, - parser, - typedOpBehavior, - projectionIteration, - undefinedVariable, - permissiveMode, - functions = functions - ) -} - -private fun getEnvironment(environmentFile: File, pipeline: AbstractPipeline): Bindings { - val configSource = environmentFile.readText(charset("UTF-8")) - val config = pipeline.compile(configSource, EvaluationSession.standard()) as PartiQLResult.Value - return config.value.bindings -} - -private fun runShell(environment: Bindings, optionSet: OptionSet, pipeline: AbstractPipeline) { - val config = ShellConfiguration(isMonochrome = optionSet.has(monochromeOpt)) - Shell(valueFactory, System.out, pipeline, environment, config).start() -} - -private fun runCli(environment: Bindings, optionSet: OptionSet, pipeline: AbstractPipeline) { - val input = when (optionSet.has(inputFileOpt)) { - true -> FileInputStream(optionSet.valueOf(inputFileOpt)) - false -> EmptyInputStream() - } - val output = when (optionSet.has(outputFileOpt)) { - true -> FileOutputStream(optionSet.valueOf(outputFileOpt)) - false -> UnclosableOutputStream(System.out) - } - - val inputFormat = optionSet.valueOf(inputFormatOpt) - val outputFormat = optionSet.valueOf(outputFormatOpt) - val wrapIon = optionSet.has(wrapIonOpt) - val query = optionSet.valueOf(queryOpt) - - input.use { - output.use { - Cli(valueFactory, input, inputFormat, output, outputFormat, pipeline, environment, query, wrapIon).run() - } - } -} diff --git a/docs/tutorials/Command Line Tutorial.md b/docs/tutorials/Command Line Tutorial.md index 2b620983f..bd7b412cd 100644 --- a/docs/tutorials/Command Line Tutorial.md +++ b/docs/tutorials/Command Line Tutorial.md @@ -1,74 +1,116 @@ # PartiQL CLI +## Build and Run the CLI + +The following command will build and run the CLI: + +```shell +# To build and run +./partiql-app/partiql-cli/shell.sh + +# To build (only) +./gradlew :partiql-app:partiql-cli:install + +# To Run (only) +./partiql-app/partiql-cli/build/install/partiql-cli/bin/partiql ``` -PartiQL CLI -Command line interface for executing PartiQL queries. Can be run in an interactive (REPL) mode or non-interactive. -Examples: -To run in REPL mode simply execute the executable without any arguments: - partiql +After building the entire project, distributable jars are located in the `cli/build/distributions` directory (relative to the +project root). -In non-interactive mode we use Ion as the format for input data which is bound to a global variable -named "input_data", in the example below /logs/log.ion is bound to "input_data": - partiql --query="SELECT * FROM input_data" --input=/logs/log.ion +Be sure to include the correct relative path to `gradlew` if you are not in the project root. -The cli can output using PartiQL syntax or Ion using the --output-format option, e.g. to output binary ion: - partiql --query="SELECT * FROM input_data" --output-format=ION_BINARY --input=/logs/log.ion +## CLI Options -To pipe input data in via stdin: - cat /logs/log.ion | partiql --query="SELECT * FROM input_data" --format=ION_BINARY > output.10n +To view all available options, run the CLI with the `--help` option. -Option Description ------- ----------- --e, --environment initial global environment (optional) --h, --help prints this help --i, --input input file, requires the query option (optional) --if, --input-format input format, requires the query option (default: ION) [ION, PARTIQL] --w, --wrap-ion wraps Ion input file values in a bag, requires the input format to be ION, requires the query option --m, --monochrome removes syntax highlighting for the REPL --o, --output output file, requires the query option (default: stdout) --of, --output-format output format, requires the query option (default: PARTIQL) [PARTIQL, PARTIQL_PRETTY, ION_TEXT, ION_BINARY] --p, --permissive run the PartiQL query in PERMISSIVE typing mode --q, --query PartiQL query, triggers non interactive mode +## Non-Interactive (Single Query Execution) + +### Using the Script + +To execute a single query, run: + +```shell +./partiql-app/partiql-cli/shell.sh query.partiql ``` -## Building the CLI +where `query.partiql` contains the PartiQL query to execute. + +### Using the `partiql` Command Directly -The root Gradle build also builds the CLI. To build the CLI separately, execute: +Alternatively, you may pipe input into the native command: ```shell -./gradlew :cli:build +# Via `echo` +echo "SELECT * FROM [0, 1, 2]" | ./partiql-app/partiql-cli/build/install/partiql-cli/bin/partiql + +# Via `cat` +echo ~/Desktop/query.partiql | ./partiql-app/partiql-cli/build/install/partiql-cli/bin/partiql ``` -After building, distributable jars are located in the `cli/build/distributions` directory (relative to the -project root). +### Running a PartiQL Executable File (Unix) -Be sure to include the correct relative path to `gradlew` if you are not in the project root. +Users can also create and run executable files containing PartiQL queries. To use this feature, +please add `partiql` (the built executable) to your path: -## Using the CLI +```shell +# file: ~/.bashrc or ~/.zshrc +# desc: Example configuration update of BASH or ZSH shells -The following command will build any dependencies before starting the CLI. +# Example adding gradle-built partiql command. Need to build the executable (see directions above). +PATH_TO_PARTIQL_LANG_KOTLIN="${HOME}/partiql-lang-kotlin" +PATH="$PATH_TO_PARTIQL_LANG_KOTLIN/partiql-app/partiql-cli/build/install/partiql-cli/bin:$PATH" +export PATH +``` +Once you have saved your configurations, remember to source your configuration file. ```shell -./gradlew :cli:run -q --args="" +# For ZSH +source ~/.zshrc + +# For Bash +source ~/.bashrc ``` -The CLI can be run in two manners, non-interactive and interactive (REPL). +Now, with the `partiql` executable on your path, you can write PartiQL files such as the below file (`example.partiql`): +```partiql +#!/usr/bin/env partiql -## REPL +-- file: example.partiql +-- desc: A simple PartiQL query -To start an interactive read, eval, print loop (REPL) execute: +SELECT t.a AS result +FROM << + { 'a': 1 }, + { 'a': 9 }, + { 'a': 4 }, + { 'a': 6 } +>> AS t +WHERE a > 2 +ORDER BY a DESC +``` + +Now, you can convert this file into an executable and run it directly! +```shell +$ chmod +x ./example.partiql +$ ./example.partiql +[{'result': 9}, {'result': 6}, {'result': 4}] +``` + +## Interactive (Shell) + +To start an interactive shell, execute: > Note that running directly with Gradle will eat arrow keys and control sequences due to the Gradle daemon. ```shell -./cli/shell.sh +./partiql-app/partiql-cli/shell.sh ``` You will see a prompt that looks as follows: ```shell -Welcome to the PartiQL REPL! +Welcome to the PartiQL shell! PartiQL> ``` @@ -113,7 +155,7 @@ PartiQL> SELECT id + 4 AS name FROM _; Press control-D to exit the REPL. -### Advanced REPL Features +### Advanced Shell Features To view the AST of a PartiQL statement, type the statement and press enter only *once*, then type `!!` and press enter: @@ -149,7 +191,7 @@ OK! ### Initial Environment -The initial environment for the REPL can be setup with a configuration file, which should be a PartiQL file with a +The initial environment for the Shell can be setup with a configuration file, which should be a PartiQL file with a single `struct` containing the initial *global environment*. For example, a file named `config.env` contains the following: @@ -170,14 +212,12 @@ For example, a file named `config.env` contains the following: ``` The variables `animals` and `types` can both be bound to the execution environment for later access. -To bind the environment file to the execution environment, start the REPL with the following command: +To bind the environment file to the execution environment, start the Shell with the following command: ```shell -$ ./gradlew :cli:run -q --console=plain --args='-e config.env' +$ ./partiql-app/partiql-cli/shell.sh -e config.env ``` -**Note**: Shell expansions such as `~` do not work within the value of the `args` argument. - Or, if you have extracted one of the compressed archives: ```shell @@ -209,7 +249,7 @@ PartiQL> SELECT name, type, is_magic FROM animals, types WHERE type = id >> ``` -To see the current REPL environment you can use `!global_env`, for example for the file above: +To see the current Shell environment you can use `!global_env`, for example for the file above: ```shell PartiQL> !global_env; @@ -411,7 +451,7 @@ PartiQL> SELECT * FROM stores AS s ``` ## Reading/Writing Files -The REPL provides the `read_file` function to stream data from a file. The files need to be placed in the folder `cli`, +The CLI provides the `read_file` function to stream data from a file. The files need to be placed in the folder `cli`, and, if using the default file type (Ion), they must contain only a single Ion value (typically a list). **Note**: Later on, we will introduce reading different file types, but we will first focus on the default (Ion). @@ -442,7 +482,7 @@ PartiQL> SELECT city FROM read_file('data.ion') AS c, `["HI", "NY"]` AS s WHERE >> ``` -The REPL also has the capability to write files with the `write_file` function: +The CLI also has the capability to write files with the `write_file` function: ```shell PartiQL> write_file('out.ion', SELECT * FROM _); @@ -718,7 +758,7 @@ For in-depth documentation on valid DDB PartiQL queries, please reference the of [AWS DynamoDB PartiQL Docs](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.html). ## Permissive Typing Mode -By default, the CLI/REPL runs in [LEGACY](https://github.com/partiql/partiql-lang-kotlin/blob/main/lang/src/org/partiql/lang/eval/CompileOptions.kt#L53-L62) +By default, the CLI runs in [LEGACY](https://github.com/partiql/partiql-lang-kotlin/blob/main/lang/src/main/kotlin/org/partiql/lang/eval/CompileOptions.kt#L62) typing mode, which will give an evaluation time error in the case of data type mismatches. ```shell diff --git a/docs/tutorials/Tutorial.md b/docs/tutorials/Tutorial.md index b429d1b65..28f499152 100644 --- a/docs/tutorials/Tutorial.md +++ b/docs/tutorials/Tutorial.md @@ -1,7 +1,6 @@ # Getting Started -PartiQL provides an interactive shell, or Read Evaluate Print Loop (REPL), -that allows users to write and evaluate PartiQL queries. +PartiQL provides an interactive shell that allows users to write and evaluate PartiQL queries. ## Prerequisites @@ -14,9 +13,9 @@ You can obtain the *latest* version of the Java Runtime from either [Follow the instructions on how to set](https://docs.oracle.com/cd/E19182-01/820-7851/inst_cli_jdk_javahome_t/) `JAVA_HOME` to the path where your Java Runtime is installed. -## Download the PartiQL REPL +## Download the PartiQL CLI -Each release of PartiQL comes with an archive that contains the PartiQL REPL as a +Each release of PartiQL comes with an archive that contains the PartiQL CLI as a zip file. 1. [Download](https://github.com/partiql/partiql-lang-kotlin/releases). @@ -58,20 +57,20 @@ The root folder `partiql-cli` contains a `README.md` file and 3 subfolders 1. Sample query output files with the extension `.output`. These files contain sample output from running the tutorial queries on the appropriate data. + 1. Alternatively, you can use the online [CLI Tutorial](https://github.com/partiql/partiql-lang-kotlin/wiki/Command-Line-Tutorial). -## Running the PartiQL REPL +## Running the PartiQL CLI ### Windows Run (double click on) `partiql.bat`. This should open a command-line -prompt and start the PartiQL REPL which displays: +prompt and start the PartiQL Shell which displays: ```shell -Welcome to the PartiQL REPL! -PartiQL> +Welcome to the PartiQL shell! ``` ### macOS (Mac) and Unix @@ -82,85 +81,12 @@ PartiQL> The folder name will have the PartiQL version as a suffix, i.e., `partiql-cli-0.1.0`. ```shell -Welcome to the PartiQL REPL! -PartiQL> +Welcome to the PartiQL shell! ``` -## Testing the PartiQL REPL - -Let's write a simple query to verify that our PartiQL REPL is working. At the `PartiQL>` prompt type: - -```shell -PartiQL> SELECT * FROM [1,2,3] -``` - -and press `ENTER` *twice*. The output should look similar to: - -```partiql -<< - { - '_1': 1 - }, - { - '_1': 2 - }, - { - '_1': 3 - } ->> -``` - -Congratulations! You successfully installed and run the PartiQL REPL. -The PartiQL REPL is now waiting for more input. - -To exit the PartiQL REPL, press: - -* `Control+D` in macOS or Unix -* `Control+C` on Windows - -or close the terminal/command prompt window. - - -## Loading data from a file - -An easy way to load the necessary data into the REPL -is use the `-e` switch when starting the REPL -and provide the name of a file that contains your data. - -```shell -./bin/partiql -e Tutorial/code/q1.env -``` - -You can then see what is loaded in the REPL's global environment using -the **special** REPL command `!global_env`, i.e., - -```shell -PartiQL> !global_env; -``` -```partiql -{ - 'hr': { - 'employees': << - { - 'id': 3, - 'name': 'Bob Smith', - 'title': NULL - }, - { - 'id': 4, - 'name': 'Susan Smith', - 'title': 'Dev Mgr' - }, - { - 'id': 6, - 'name': 'Jane Smith', - 'title': 'Software Eng 2' - } - >> - } -} -``` +### Command Line Tutorial +To get a deeper understanding of PartiQL, check out the [CLI Tutorial](https://github.com/partiql/partiql-lang-kotlin/wiki/Command-Line-Tutorial). # Introduction diff --git a/gradle.properties b/gradle.properties index ed7df0aa2..09f1bb293 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=org.partiql -version=0.9.1 +version=0.9.2 ossrhUsername=EMPTY ossrhPassword=EMPTY diff --git a/lang/src/main/kotlin/org/partiql/lang/ast/IsOrderedMeta.kt b/lang/src/main/kotlin/org/partiql/lang/ast/IsOrderedMeta.kt new file mode 100644 index 000000000..131d2dd0c --- /dev/null +++ b/lang/src/main/kotlin/org/partiql/lang/ast/IsOrderedMeta.kt @@ -0,0 +1,9 @@ +package org.partiql.lang.ast + +/** + * To reduce any extraneous passes over data, this [Meta] indicates whether the associated BindingsToValues Physical + * expression should be an ordered list or a bag. + */ +object IsOrderedMeta : Meta { + override val tag = "\$is_ordered" +} diff --git a/lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt b/lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt index bc749b6e9..b4bd24eec 100644 --- a/lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt +++ b/lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt @@ -20,6 +20,7 @@ import com.amazon.ion.Timestamp import com.amazon.ionelement.api.MetaContainer import com.amazon.ionelement.api.emptyMetaContainer import com.amazon.ionelement.api.toIonValue +import org.partiql.lang.ast.IsOrderedMeta import org.partiql.lang.ast.SourceLocationMeta import org.partiql.lang.ast.UNKNOWN_SOURCE_LOCATION import org.partiql.lang.ast.sourceLocation @@ -275,34 +276,25 @@ internal class PhysicalPlanCompilerImpl( val mapThunk = compileAstExpr(expr.exp) val bexprThunk: RelationThunkEnv = bexperConverter.convert(expr.query) - fun createOutputSequence(relationType: RelationType?, elements: Sequence) = when (relationType) { - RelationType.LIST -> valueFactory.newList(elements) - RelationType.BAG -> valueFactory.newBag(elements) - null -> throw EvaluationException( - message = "Unable to recover the output Relation Type", - errorCode = ErrorCode.EVALUATOR_GENERIC_EXCEPTION, - internal = false - ) + val relationType = when (expr.metas.containsKey(IsOrderedMeta.tag)) { + true -> RelationType.LIST + false -> RelationType.BAG } return thunkFactory.thunkEnv(expr.metas) { env -> - var relationType: RelationType? = null // we create a snapshot for currentRegister to use during the evaluation // this is to avoid issue when iterator planner result val currentRegister = env.registers.clone() val elements = sequence { env.load(currentRegister) val relItr = bexprThunk(env) - relationType = relItr.relType while (relItr.nextRow()) { yield(mapThunk(env)) } } - - // Trick the compiler here to always initialize `relationType` - when (elements.firstOrNull()) { - null -> createOutputSequence(relationType, emptySequence()) - else -> createOutputSequence(relationType, elements) + when (relationType) { + RelationType.LIST -> valueFactory.newList(elements) + RelationType.BAG -> valueFactory.newBag(elements) } } } diff --git a/lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregationVisitorTransform.kt b/lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregationVisitorTransform.kt index adb82a7c0..99d2ad98a 100644 --- a/lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregationVisitorTransform.kt +++ b/lang/src/main/kotlin/org/partiql/lang/eval/visitors/AggregationVisitorTransform.kt @@ -105,7 +105,7 @@ internal class AggregationVisitorTransform( override fun transformExprSelect_group(node: PartiqlAst.Expr.Select): PartiqlAst.GroupBy? { // Return with Empty Context if without Group - val containsAggregations = AggregationFinder.containsAggregations(node.project) + val containsAggregations = AggregationFinder().containsAggregations(node.project) if (node.group == null) { val context = VisitorContext(emptyList(), null, containsAggregations) contextStack.add(context) @@ -202,7 +202,8 @@ internal class AggregationVisitorTransform( * Recursively searches through a [PartiqlAst.Projection] to find [PartiqlAst.Expr.CallAgg]'s, but does NOT recurse * into [PartiqlAst.Expr.Select]. Designed to be called directly using [containsAggregations]. */ - private object AggregationFinder : PartiqlAst.Visitor() { + private class AggregationFinder : PartiqlAst.Visitor() { + var hasAggregations: Boolean = false fun containsAggregations(node: PartiqlAst.Projection): Boolean { @@ -276,7 +277,7 @@ internal class AggregationVisitorTransform( } /** - * IDs outside of aggregation functions should always be replaced with the Group Key uniue aliases. If no + * IDs outside of aggregation functions should always be replaced with the Group Key unique aliases. If no * replacement is found, we throw an EvaluationException. */ private fun getReplacementForIdOutsideOfAggregationFunction(node: PartiqlAst.Expr.Id): PartiqlAst.Expr { diff --git a/lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstToLogicalVisitorTransform.kt b/lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstToLogicalVisitorTransform.kt index ee2ab1780..b51f33d47 100644 --- a/lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstToLogicalVisitorTransform.kt +++ b/lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstToLogicalVisitorTransform.kt @@ -3,9 +3,11 @@ package org.partiql.lang.planner.transforms import com.amazon.ionelement.api.emptyMetaContainer import com.amazon.ionelement.api.ionString import com.amazon.ionelement.api.ionSymbol +import org.partiql.lang.ast.IsOrderedMeta import org.partiql.lang.domains.PartiqlAst import org.partiql.lang.domains.PartiqlAstToPartiqlLogicalVisitorTransform import org.partiql.lang.domains.PartiqlLogical +import org.partiql.lang.domains.metaContainerOf import org.partiql.lang.errors.Problem import org.partiql.lang.errors.ProblemHandler import org.partiql.lang.eval.builtins.CollectionAggregationFunction @@ -263,20 +265,25 @@ internal class AstToLogicalVisitorTransform( } private fun transformProjection(node: PartiqlAst.Expr.Select, algebra: PartiqlLogical.Bexpr): PartiqlLogical.Expr { + val project = node.project + val metas = when (node.order) { + null -> project.metas + else -> project.metas + metaContainerOf(IsOrderedMeta) + } return PartiqlLogical.build { - when (val project = node.project) { + when (project) { is PartiqlAst.Projection.ProjectValue -> { bindingsToValues( exp = transformExpr(project.value), query = algebra, - metas = project.metas + metas = metas ) } is PartiqlAst.Projection.ProjectList -> { bindingsToValues( exp = transformProjectList(project), query = algebra, - metas = project.metas + metas = metas ) } is PartiqlAst.Projection.ProjectStar -> { @@ -289,7 +296,7 @@ internal class AstToLogicalVisitorTransform( input = algebra, key = transformExpr(project.key), value = transformExpr(project.value), - metas = project.metas + metas = metas ) } } diff --git a/lang/src/test/kotlin/org/partiql/lang/compiler/memorydb/operators/GetByKeyProjectRelationalOperatorFactory.kt b/lang/src/test/kotlin/org/partiql/lang/compiler/memorydb/operators/GetByKeyProjectRelationalOperatorFactory.kt index 2f0f03e7b..2a2778fc6 100644 --- a/lang/src/test/kotlin/org/partiql/lang/compiler/memorydb/operators/GetByKeyProjectRelationalOperatorFactory.kt +++ b/lang/src/test/kotlin/org/partiql/lang/compiler/memorydb/operators/GetByKeyProjectRelationalOperatorFactory.kt @@ -57,11 +57,17 @@ class GetByKeyProjectRelationalOperatorFactory : ProjectRelationalOperatorFactor // Parse the tableId so we don't have to at evaluation-time val tableId = UUID.fromString(impl.staticArgs.single().textValue) + var exhausted = false + // Finally, return a RelationExpression which evaluates the key value expression and returns a // RelationIterator containing a single row corresponding to the key (or no rows if nothing matches) return RelationExpression { state -> // this code runs at evaluation-time. + if (exhausted) { + throw IllegalStateException("Exhausted result set") + } + // Get the current database from the EvaluationSession context. // Please note that the state.session.context map is immutable, therefore it is not possible // for custom operators or functions to put stuff in there. (Hopefully that will reduce the @@ -74,6 +80,8 @@ class GetByKeyProjectRelationalOperatorFactory : ProjectRelationalOperatorFactor // get the record requested. val record = db.getRecordByKey(tableId, keyValue) + exhausted = true + // if the record was not found, return an empty relation: if (record == null) relation(RelationType.BAG) { diff --git a/lang/src/test/kotlin/org/partiql/lang/eval/EvaluatingCompilerCollectionAggregationsTest.kt b/lang/src/test/kotlin/org/partiql/lang/eval/EvaluatingCompilerCollectionAggregationsTest.kt index 9cbee7282..5a8d88fb5 100644 --- a/lang/src/test/kotlin/org/partiql/lang/eval/EvaluatingCompilerCollectionAggregationsTest.kt +++ b/lang/src/test/kotlin/org/partiql/lang/eval/EvaluatingCompilerCollectionAggregationsTest.kt @@ -172,8 +172,8 @@ internal class EvaluatingCompilerCollectionAggregationsTest : EvaluatorTestBase( """, expectedResult = """ << - {'k': [2, 4], 'coll_sum_a': 6, 'coll_sum_inner': <<6>>, 'sum_b': 30}, - {'k': [6, 7], 'coll_sum_a': 13, 'coll_sum_inner': <<13>>, 'sum_b': 20} + {'k': [2, 4], 'coll_sum_a': 6, 'sum_b': 30, 'coll_sum_inner': <<6>>}, + {'k': [6, 7], 'coll_sum_a': 13, 'sum_b': 20, 'coll_sum_inner': <<13>>} >> """ ), diff --git a/cli/README.md b/partiql-app/partiql-cli/README.md similarity index 100% rename from cli/README.md rename to partiql-app/partiql-cli/README.md diff --git a/cli/archive/README b/partiql-app/partiql-cli/archive/README similarity index 100% rename from cli/archive/README rename to partiql-app/partiql-cli/archive/README diff --git a/cli/archive/Tutorial/code/q1.env b/partiql-app/partiql-cli/archive/Tutorial/code/q1.env similarity index 100% rename from cli/archive/Tutorial/code/q1.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q1.env diff --git a/cli/archive/Tutorial/code/q1.output b/partiql-app/partiql-cli/archive/Tutorial/code/q1.output similarity index 100% rename from cli/archive/Tutorial/code/q1.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q1.output diff --git a/cli/archive/Tutorial/code/q1.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q1.sql similarity index 100% rename from cli/archive/Tutorial/code/q1.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q1.sql diff --git a/cli/archive/Tutorial/code/q10.env b/partiql-app/partiql-cli/archive/Tutorial/code/q10.env similarity index 100% rename from cli/archive/Tutorial/code/q10.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q10.env diff --git a/cli/archive/Tutorial/code/q10.output b/partiql-app/partiql-cli/archive/Tutorial/code/q10.output similarity index 100% rename from cli/archive/Tutorial/code/q10.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q10.output diff --git a/cli/archive/Tutorial/code/q10.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q10.sql similarity index 100% rename from cli/archive/Tutorial/code/q10.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q10.sql diff --git a/cli/archive/Tutorial/code/q11.env b/partiql-app/partiql-cli/archive/Tutorial/code/q11.env similarity index 100% rename from cli/archive/Tutorial/code/q11.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q11.env diff --git a/cli/archive/Tutorial/code/q11.output b/partiql-app/partiql-cli/archive/Tutorial/code/q11.output similarity index 100% rename from cli/archive/Tutorial/code/q11.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q11.output diff --git a/cli/archive/Tutorial/code/q11.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q11.sql similarity index 100% rename from cli/archive/Tutorial/code/q11.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q11.sql diff --git a/cli/archive/Tutorial/code/q12.env b/partiql-app/partiql-cli/archive/Tutorial/code/q12.env similarity index 100% rename from cli/archive/Tutorial/code/q12.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q12.env diff --git a/cli/archive/Tutorial/code/q12.output b/partiql-app/partiql-cli/archive/Tutorial/code/q12.output similarity index 100% rename from cli/archive/Tutorial/code/q12.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q12.output diff --git a/cli/archive/Tutorial/code/q12.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q12.sql similarity index 100% rename from cli/archive/Tutorial/code/q12.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q12.sql diff --git a/cli/archive/Tutorial/code/q13.env b/partiql-app/partiql-cli/archive/Tutorial/code/q13.env similarity index 100% rename from cli/archive/Tutorial/code/q13.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q13.env diff --git a/cli/archive/Tutorial/code/q13.output b/partiql-app/partiql-cli/archive/Tutorial/code/q13.output similarity index 100% rename from cli/archive/Tutorial/code/q13.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q13.output diff --git a/cli/archive/Tutorial/code/q13.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q13.sql similarity index 100% rename from cli/archive/Tutorial/code/q13.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q13.sql diff --git a/cli/archive/Tutorial/code/q14.env b/partiql-app/partiql-cli/archive/Tutorial/code/q14.env similarity index 100% rename from cli/archive/Tutorial/code/q14.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q14.env diff --git a/cli/archive/Tutorial/code/q14.output b/partiql-app/partiql-cli/archive/Tutorial/code/q14.output similarity index 100% rename from cli/archive/Tutorial/code/q14.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q14.output diff --git a/cli/archive/Tutorial/code/q14.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q14.sql similarity index 100% rename from cli/archive/Tutorial/code/q14.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q14.sql diff --git a/cli/archive/Tutorial/code/q15.env b/partiql-app/partiql-cli/archive/Tutorial/code/q15.env similarity index 100% rename from cli/archive/Tutorial/code/q15.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q15.env diff --git a/cli/archive/Tutorial/code/q15.output b/partiql-app/partiql-cli/archive/Tutorial/code/q15.output similarity index 100% rename from cli/archive/Tutorial/code/q15.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q15.output diff --git a/cli/archive/Tutorial/code/q15.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q15.sql similarity index 100% rename from cli/archive/Tutorial/code/q15.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q15.sql diff --git a/cli/archive/Tutorial/code/q16.env b/partiql-app/partiql-cli/archive/Tutorial/code/q16.env similarity index 100% rename from cli/archive/Tutorial/code/q16.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q16.env diff --git a/cli/archive/Tutorial/code/q16.output b/partiql-app/partiql-cli/archive/Tutorial/code/q16.output similarity index 100% rename from cli/archive/Tutorial/code/q16.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q16.output diff --git a/cli/archive/Tutorial/code/q16.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q16.sql similarity index 100% rename from cli/archive/Tutorial/code/q16.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q16.sql diff --git a/cli/archive/Tutorial/code/q17.env b/partiql-app/partiql-cli/archive/Tutorial/code/q17.env similarity index 100% rename from cli/archive/Tutorial/code/q17.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q17.env diff --git a/cli/archive/Tutorial/code/q17.output b/partiql-app/partiql-cli/archive/Tutorial/code/q17.output similarity index 100% rename from cli/archive/Tutorial/code/q17.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q17.output diff --git a/cli/archive/Tutorial/code/q17.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q17.sql similarity index 100% rename from cli/archive/Tutorial/code/q17.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q17.sql diff --git a/cli/archive/Tutorial/code/q18.env b/partiql-app/partiql-cli/archive/Tutorial/code/q18.env similarity index 100% rename from cli/archive/Tutorial/code/q18.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q18.env diff --git a/cli/archive/Tutorial/code/q18.output b/partiql-app/partiql-cli/archive/Tutorial/code/q18.output similarity index 100% rename from cli/archive/Tutorial/code/q18.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q18.output diff --git a/cli/archive/Tutorial/code/q18.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q18.sql similarity index 100% rename from cli/archive/Tutorial/code/q18.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q18.sql diff --git a/cli/archive/Tutorial/code/q2.env b/partiql-app/partiql-cli/archive/Tutorial/code/q2.env similarity index 100% rename from cli/archive/Tutorial/code/q2.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q2.env diff --git a/cli/archive/Tutorial/code/q2.output b/partiql-app/partiql-cli/archive/Tutorial/code/q2.output similarity index 100% rename from cli/archive/Tutorial/code/q2.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q2.output diff --git a/cli/archive/Tutorial/code/q2.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q2.sql similarity index 100% rename from cli/archive/Tutorial/code/q2.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q2.sql diff --git a/cli/archive/Tutorial/code/q3.env b/partiql-app/partiql-cli/archive/Tutorial/code/q3.env similarity index 100% rename from cli/archive/Tutorial/code/q3.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q3.env diff --git a/cli/archive/Tutorial/code/q3.output b/partiql-app/partiql-cli/archive/Tutorial/code/q3.output similarity index 100% rename from cli/archive/Tutorial/code/q3.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q3.output diff --git a/cli/archive/Tutorial/code/q3.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q3.sql similarity index 100% rename from cli/archive/Tutorial/code/q3.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q3.sql diff --git a/cli/archive/Tutorial/code/q4.env b/partiql-app/partiql-cli/archive/Tutorial/code/q4.env similarity index 100% rename from cli/archive/Tutorial/code/q4.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q4.env diff --git a/cli/archive/Tutorial/code/q4.output b/partiql-app/partiql-cli/archive/Tutorial/code/q4.output similarity index 100% rename from cli/archive/Tutorial/code/q4.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q4.output diff --git a/cli/archive/Tutorial/code/q4.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q4.sql similarity index 100% rename from cli/archive/Tutorial/code/q4.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q4.sql diff --git a/cli/archive/Tutorial/code/q5.env b/partiql-app/partiql-cli/archive/Tutorial/code/q5.env similarity index 100% rename from cli/archive/Tutorial/code/q5.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q5.env diff --git a/cli/archive/Tutorial/code/q5.output b/partiql-app/partiql-cli/archive/Tutorial/code/q5.output similarity index 100% rename from cli/archive/Tutorial/code/q5.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q5.output diff --git a/cli/archive/Tutorial/code/q5.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q5.sql similarity index 100% rename from cli/archive/Tutorial/code/q5.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q5.sql diff --git a/cli/archive/Tutorial/code/q6.env b/partiql-app/partiql-cli/archive/Tutorial/code/q6.env similarity index 100% rename from cli/archive/Tutorial/code/q6.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q6.env diff --git a/cli/archive/Tutorial/code/q6.output b/partiql-app/partiql-cli/archive/Tutorial/code/q6.output similarity index 100% rename from cli/archive/Tutorial/code/q6.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q6.output diff --git a/cli/archive/Tutorial/code/q6.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q6.sql similarity index 100% rename from cli/archive/Tutorial/code/q6.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q6.sql diff --git a/cli/archive/Tutorial/code/q7.env b/partiql-app/partiql-cli/archive/Tutorial/code/q7.env similarity index 100% rename from cli/archive/Tutorial/code/q7.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q7.env diff --git a/cli/archive/Tutorial/code/q7.output b/partiql-app/partiql-cli/archive/Tutorial/code/q7.output similarity index 100% rename from cli/archive/Tutorial/code/q7.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q7.output diff --git a/cli/archive/Tutorial/code/q7.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q7.sql similarity index 100% rename from cli/archive/Tutorial/code/q7.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q7.sql diff --git a/cli/archive/Tutorial/code/q8.env b/partiql-app/partiql-cli/archive/Tutorial/code/q8.env similarity index 100% rename from cli/archive/Tutorial/code/q8.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q8.env diff --git a/cli/archive/Tutorial/code/q8.out b/partiql-app/partiql-cli/archive/Tutorial/code/q8.out similarity index 100% rename from cli/archive/Tutorial/code/q8.out rename to partiql-app/partiql-cli/archive/Tutorial/code/q8.out diff --git a/cli/archive/Tutorial/code/q8.output b/partiql-app/partiql-cli/archive/Tutorial/code/q8.output similarity index 100% rename from cli/archive/Tutorial/code/q8.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q8.output diff --git a/cli/archive/Tutorial/code/q8.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q8.sql similarity index 100% rename from cli/archive/Tutorial/code/q8.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q8.sql diff --git a/cli/archive/Tutorial/code/q9.env b/partiql-app/partiql-cli/archive/Tutorial/code/q9.env similarity index 100% rename from cli/archive/Tutorial/code/q9.env rename to partiql-app/partiql-cli/archive/Tutorial/code/q9.env diff --git a/cli/archive/Tutorial/code/q9.output b/partiql-app/partiql-cli/archive/Tutorial/code/q9.output similarity index 100% rename from cli/archive/Tutorial/code/q9.output rename to partiql-app/partiql-cli/archive/Tutorial/code/q9.output diff --git a/cli/archive/Tutorial/code/q9.sql b/partiql-app/partiql-cli/archive/Tutorial/code/q9.sql similarity index 100% rename from cli/archive/Tutorial/code/q9.sql rename to partiql-app/partiql-cli/archive/Tutorial/code/q9.sql diff --git a/cli/archive/Tutorial/code/tutorial-all-data.env b/partiql-app/partiql-cli/archive/Tutorial/code/tutorial-all-data.env similarity index 100% rename from cli/archive/Tutorial/code/tutorial-all-data.env rename to partiql-app/partiql-cli/archive/Tutorial/code/tutorial-all-data.env diff --git a/cli/archive/Tutorial/tutorial.html b/partiql-app/partiql-cli/archive/Tutorial/tutorial.html similarity index 100% rename from cli/archive/Tutorial/tutorial.html rename to partiql-app/partiql-cli/archive/Tutorial/tutorial.html diff --git a/cli/archive/Tutorial/tutorial.pdf b/partiql-app/partiql-cli/archive/Tutorial/tutorial.pdf similarity index 100% rename from cli/archive/Tutorial/tutorial.pdf rename to partiql-app/partiql-cli/archive/Tutorial/tutorial.pdf diff --git a/cli/build.gradle.kts b/partiql-app/partiql-cli/build.gradle.kts similarity index 98% rename from cli/build.gradle.kts rename to partiql-app/partiql-cli/build.gradle.kts index d6cb4ef69..559001518 100644 --- a/cli/build.gradle.kts +++ b/partiql-app/partiql-cli/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { implementation(Deps.jansi) implementation(Deps.jline) implementation(Deps.joda) - implementation(Deps.jopt) + implementation(Deps.picoCli) implementation(Deps.kotlinReflect) } diff --git a/cli/shell.sh b/partiql-app/partiql-cli/partiql.sh similarity index 86% rename from cli/shell.sh rename to partiql-app/partiql-cli/partiql.sh index ef4811aae..ab834658f 100755 --- a/cli/shell.sh +++ b/partiql-app/partiql-cli/partiql.sh @@ -16,5 +16,5 @@ cli_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cd "$cli_path" -../gradlew :cli:install -../cli/build/install/partiql-cli/bin/partiql +../../gradlew :partiql-app:partiql-cli:install +../partiql-cli/build/install/partiql-cli/bin/partiql "$@" diff --git a/cli/src/main/kotlin/org/partiql/cli/PartiQLCommand.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt similarity index 53% rename from cli/src/main/kotlin/org/partiql/cli/PartiQLCommand.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt index ba5dbca21..9d337c3aa 100644 --- a/cli/src/main/kotlin/org/partiql/cli/PartiQLCommand.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt @@ -11,9 +11,25 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific * language governing permissions and limitations under the License. */ +@file:JvmName("Main") + +@file:Suppress("DEPRECATION") package org.partiql.cli -interface PartiQLCommand { - fun run() +import com.amazon.ion.system.IonSystemBuilder +import org.partiql.cli.pico.PartiQLCommand +import org.partiql.lang.eval.ExprValueFactory +import picocli.CommandLine +import kotlin.system.exitProcess + +/** + * Runs the PartiQL CLI. + */ +fun main(args: Array) { + val ion = IonSystemBuilder.standard().build() + val valueFactory = ExprValueFactory.standard(ion) + val command = CommandLine(PartiQLCommand(valueFactory)) + val exitCode = command.execute(*args) + exitProcess(exitCode) } diff --git a/cli/src/main/kotlin/org/partiql/format/DotFormatter.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/DotFormatter.kt similarity index 97% rename from cli/src/main/kotlin/org/partiql/format/DotFormatter.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/DotFormatter.kt index 2a3c5b136..1d6abf0da 100644 --- a/cli/src/main/kotlin/org/partiql/format/DotFormatter.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/DotFormatter.kt @@ -12,13 +12,13 @@ * language governing permissions and limitations under the License. */ -package org.partiql.format +package org.partiql.cli.format -import org.partiql.format.dot.DotGraph -import org.partiql.format.dot.DotNodeId -import org.partiql.format.dot.DotNodeShape -import org.partiql.format.dot.DotNodeStmt -import org.partiql.format.dot.digraph +import org.partiql.cli.format.dot.DotGraph +import org.partiql.cli.format.dot.DotNodeId +import org.partiql.cli.format.dot.DotNodeShape +import org.partiql.cli.format.dot.DotNodeStmt +import org.partiql.cli.format.dot.digraph import org.partiql.lang.domains.PartiqlAst import org.partiql.lang.domains.PartiqlLogical import org.partiql.lang.domains.PartiqlLogicalResolved diff --git a/cli/src/main/kotlin/org/partiql/format/DotUrlFormatter.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/DotUrlFormatter.kt similarity index 97% rename from cli/src/main/kotlin/org/partiql/format/DotUrlFormatter.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/DotUrlFormatter.kt index 369ed3d57..92b00e6d0 100644 --- a/cli/src/main/kotlin/org/partiql/format/DotUrlFormatter.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/DotUrlFormatter.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.format +package org.partiql.cli.format import com.google.common.net.PercentEscaper import org.partiql.pig.runtime.DomainNode diff --git a/cli/src/main/kotlin/org/partiql/format/ExplainFormatter.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/ExplainFormatter.kt similarity index 97% rename from cli/src/main/kotlin/org/partiql/format/ExplainFormatter.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/ExplainFormatter.kt index 81e670515..de0ec4bf1 100644 --- a/cli/src/main/kotlin/org/partiql/format/ExplainFormatter.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/ExplainFormatter.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.format +package org.partiql.cli.format import org.partiql.lang.eval.PartiQLResult diff --git a/cli/src/main/kotlin/org/partiql/format/NodeFormatter.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/NodeFormatter.kt similarity index 95% rename from cli/src/main/kotlin/org/partiql/format/NodeFormatter.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/NodeFormatter.kt index 1e18490b5..2257b1e8d 100644 --- a/cli/src/main/kotlin/org/partiql/format/NodeFormatter.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/NodeFormatter.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.format +package org.partiql.cli.format import org.partiql.pig.runtime.DomainNode diff --git a/cli/src/main/kotlin/org/partiql/format/SexpFormatter.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/SexpFormatter.kt similarity index 96% rename from cli/src/main/kotlin/org/partiql/format/SexpFormatter.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/SexpFormatter.kt index 87d3a6b89..b5a6dc3fc 100644 --- a/cli/src/main/kotlin/org/partiql/format/SexpFormatter.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/SexpFormatter.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.format +package org.partiql.cli.format import com.amazon.ion.system.IonTextWriterBuilder import org.partiql.pig.runtime.DomainNode diff --git a/cli/src/main/kotlin/org/partiql/format/TreeFormatter.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/TreeFormatter.kt similarity index 99% rename from cli/src/main/kotlin/org/partiql/format/TreeFormatter.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/TreeFormatter.kt index 6fdb1eaf3..553b2c9b8 100644 --- a/cli/src/main/kotlin/org/partiql/format/TreeFormatter.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/TreeFormatter.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.format +package org.partiql.cli.format import org.partiql.lang.domains.PartiqlLogical import org.partiql.lang.domains.PartiqlLogicalResolved diff --git a/cli/src/main/kotlin/org/partiql/format/Utilities.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/Utilities.kt similarity index 96% rename from cli/src/main/kotlin/org/partiql/format/Utilities.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/Utilities.kt index 8e24ff6c3..c64d432d4 100644 --- a/cli/src/main/kotlin/org/partiql/format/Utilities.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/Utilities.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.format +package org.partiql.cli.format import org.partiql.pig.runtime.DomainNode import kotlin.reflect.full.memberProperties diff --git a/cli/src/main/kotlin/org/partiql/format/dot/Dot.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/dot/Dot.kt similarity index 99% rename from cli/src/main/kotlin/org/partiql/format/dot/Dot.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/dot/Dot.kt index d762b8cd8..30facb94c 100644 --- a/cli/src/main/kotlin/org/partiql/format/dot/Dot.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/format/dot/Dot.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.format.dot +package org.partiql.cli.format.dot import kotlin.properties.ObservableProperty import kotlin.properties.ReadWriteProperty @@ -105,7 +105,7 @@ sealed class DotGraph( /** * I couldn't figure out how to chain edges while also attaching edge attributes - * hence why these return a org.partiql.format.DotEdgeStmt and not the rhs + * hence why these return a org.partiql.cli.format.DotEdgeStmt and not the rhs */ /** @@ -475,7 +475,7 @@ interface DotEntity { } /** - * Calling a org.partiql.format.DotVertex entities that can be the source or target of an edge -- i.e. node ids and subgraphs + * Calling a org.partiql.cli.format.DotVertex entities that can be the source or target of an edge -- i.e. node ids and subgraphs * This affects indentation in Dot generating. */ interface DotVertex { diff --git a/cli/src/main/kotlin/org/partiql/cli/functions/ReadFile.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/functions/ReadFile.kt similarity index 100% rename from cli/src/main/kotlin/org/partiql/cli/functions/ReadFile.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/functions/ReadFile.kt diff --git a/cli/src/main/kotlin/org/partiql/cli/functions/WriteFile.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/functions/WriteFile.kt similarity index 100% rename from cli/src/main/kotlin/org/partiql/cli/functions/WriteFile.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/functions/WriteFile.kt diff --git a/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PartiQLCommand.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PartiQLCommand.kt new file mode 100644 index 000000000..4d55ae972 --- /dev/null +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PartiQLCommand.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at: + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ + +package org.partiql.cli.pico + +import org.partiql.cli.query.Cli +import org.partiql.cli.shell.Shell +import org.partiql.cli.utils.EmptyInputStream +import org.partiql.cli.utils.UnclosableOutputStream +import org.partiql.lang.eval.ExprValueFactory +import picocli.CommandLine +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.InputStream + +@CommandLine.Command( + name = "partiql", + mixinStandardHelpOptions = true, + versionProvider = PartiQLVersionProvider::class, + descriptionHeading = "%n@|bold,underline,yellow The PartiQL CLI|@%n", + description = [ + "%nThe PartiQL CLI allows query execution in two modes: Non-Interactive and Interactive (default).%n", + "@|bold,underline General Options|@%n", + "These options configure both Non-Interactive and Interactive executions.%n" + ], + showDefaultValues = true +) +internal class PartiQLCommand(private val valueFactory: ExprValueFactory) : Runnable { + + @CommandLine.Mixin + internal lateinit var options: PipelineOptions + + @CommandLine.ArgGroup( + exclusive = false, + heading = "%n@|bold,underline Non-Interactive (Single Query Execution)|@%n%n" + + "Specifying any of the below options will trigger Non-Interactive execution. " + + "Also, passing input through standard input will trigger its execution.%n%n" + ) + internal var executionOptions: ExecutionOptions? = null + + @CommandLine.ArgGroup(exclusive = false, heading = "%n@|bold,underline Interactive (Shell) Configurations|@%n%n") + internal var shellOptions: ShellOptions? = null + + internal companion object { + private const val SHEBANG_PREFIX = "#!" + } + + /** + * Run the CLI or Shell (default) + */ + override fun run() { + val command = executionOptions ?: ExecutionOptions() + val shell = shellOptions ?: ShellOptions() + when { + command.query != null -> runCli(command, command.query!!.inputStream()) + System.console() == null -> runCli(command, System.`in`) + else -> runShell(shell) + } + } + + /** + * Runs the CLI + */ + private fun runCli(exec: ExecutionOptions, stream: InputStream) { + val input = when (exec.inputFile) { + null -> EmptyInputStream() + else -> FileInputStream(exec.inputFile!!) + } + val output = when (exec.outputFile) { + null -> UnclosableOutputStream(System.out) + else -> FileOutputStream(exec.outputFile!!) + } + val query = stream.readBytes().toString(Charsets.UTF_8) + val queryLines = query.lines() + val queryWithoutShebang = when (queryLines.firstOrNull()?.startsWith(SHEBANG_PREFIX)) { + false -> query + else -> queryLines.subList(1, queryLines.size).joinToString(System.lineSeparator()) + } + input.use { src -> + output.use { out -> + Cli(valueFactory, src, exec.inputFormat, out, exec.outputFormat, options.pipeline, options.environment, queryWithoutShebang, exec.wrapIon).run() + out.write(System.lineSeparator().toByteArray(Charsets.UTF_8)) + } + } + } + + /** + * Runs the interactive shell + */ + private fun runShell(shell: ShellOptions = ShellOptions()) { + val config = Shell.ShellConfiguration(isMonochrome = shell.isMonochrome) + Shell(valueFactory, System.out, options.pipeline, options.environment, config).start() + } + + /** + * Options specific to single query execution + */ + class ExecutionOptions { + @CommandLine.Option(names = ["-i", "--in"], description = ["The path to the input file"], paramLabel = "FILE") + var inputFile: File? = null + + @CommandLine.Option(names = ["--in-format"], description = ["The input file format: [\${COMPLETION-CANDIDATES}]"], paramLabel = "FORMAT") + var inputFormat: InputFormat = InputFormat.ION + + @CommandLine.Option(names = ["-o", "--out"], description = ["The path to the output file"], paramLabel = "FILE") + var outputFile: File? = null + + @CommandLine.Option(names = ["--out-format"], description = ["The output file format: [\${COMPLETION-CANDIDATES}]"], paramLabel = "FORMAT") + var outputFormat: OutputFormat = OutputFormat.PARTIQL + + @CommandLine.Option(names = ["-w", "--wrap-ion"], description = ["Indicates that the input Ion file contains a sequence of Ion values rather than a single Ion collection"]) + var wrapIon: Boolean = false + + @CommandLine.Parameters(arity = "0..1", index = "0..1", description = ["The filepath of the PartiQL query to execute"], paramLabel = "PARTIQL_FILE") + var query: File? = null + } + + /** + * Options specific to the shell + */ + class ShellOptions { + @CommandLine.Option(names = ["-m", "--monochrome"], description = ["Specifies that syntax highlighting should not be used"]) + var isMonochrome: Boolean = false + } + + enum class InputFormat { + ION, + PARTIQL + } + + enum class OutputFormat { + ION_TEXT, + ION_BINARY, + PARTIQL, + PARTIQL_PRETTY + } +} diff --git a/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PartiQLVersionProvider.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PartiQLVersionProvider.kt new file mode 100644 index 000000000..7b29f7b28 --- /dev/null +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PartiQLVersionProvider.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at: + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ + +package org.partiql.cli.pico + +import picocli.CommandLine +import java.util.Properties + +internal class PartiQLVersionProvider : CommandLine.IVersionProvider { + override fun getVersion(): Array { + val properties = Properties() + properties.load(this.javaClass.getResourceAsStream("/partiql.properties")) + return Array(1) { "PartiQL ${properties.getProperty("version")}-${properties.getProperty("commit")}" } + } +} diff --git a/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PipelineOptions.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PipelineOptions.kt new file mode 100644 index 000000000..ecc0f07bd --- /dev/null +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PipelineOptions.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at: + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ + +package org.partiql.cli.pico + +import org.partiql.cli.pipeline.AbstractPipeline +import org.partiql.lang.eval.Bindings +import org.partiql.lang.eval.EvaluationSession +import org.partiql.lang.eval.ExprValue +import org.partiql.lang.eval.PartiQLResult +import org.partiql.lang.eval.ProjectionIterationBehavior +import org.partiql.lang.eval.TypedOpBehavior +import org.partiql.lang.eval.TypingMode +import org.partiql.lang.eval.UndefinedVariableBehavior +import picocli.CommandLine +import java.io.File + +internal class PipelineOptions { + + @CommandLine.Option( + names = ["-p", "--pipeline"], + description = ["The type of pipeline to use: [\${COMPLETION-CANDIDATES}]"], + paramLabel = "TYPE" + ) + var pipelineType: AbstractPipeline.PipelineType = AbstractPipeline.PipelineType.STANDARD + + @CommandLine.Option( + names = ["-e", "--environment"], + description = ["File containing the global environment"], + paramLabel = "FILE" + ) + var environmentFile: File? = null + + @CommandLine.Option( + names = ["--typing-mode"], + description = ["Specifies the typing mode: [\${COMPLETION-CANDIDATES}]"], + paramLabel = "MODE" + ) + var typingMode: TypingMode = TypingMode.LEGACY + + @CommandLine.Option( + names = ["--typed-op-behavior"], + description = ["Indicates how CAST should behave: [\${COMPLETION-CANDIDATES}]"], + paramLabel = "OPT" + ) + var typedOpBehavior: TypedOpBehavior = TypedOpBehavior.HONOR_PARAMETERS + + @CommandLine.Option( + names = ["--projection-iter-behavior"], + description = ["Controls the behavior of ExprValue.iterator in the projection result: [\${COMPLETION-CANDIDATES}]"], + paramLabel = "OPT" + ) + var projIterBehavior: ProjectionIterationBehavior = ProjectionIterationBehavior.FILTER_MISSING + + @CommandLine.Option( + names = ["-u", "--undefined-variable-behavior"], + description = ["Defines the behavior when a non-existent variable is referenced: [\${COMPLETION-CANDIDATES}]"], + paramLabel = "OPT" + ) + var undefinedVarBehavior: UndefinedVariableBehavior = UndefinedVariableBehavior.ERROR + + internal val pipeline: AbstractPipeline + get() { + val options = AbstractPipeline.createPipelineOptions( + pipelineType, + typedOpBehavior, + projIterBehavior, + undefinedVarBehavior, + typingMode + ) + return AbstractPipeline.create(options) + } + + internal val environment: Bindings + get() { + if (environmentFile == null) return Bindings.empty() + val configSource = environmentFile!!.readText(charset("UTF-8")) + val config = pipeline.compile(configSource, EvaluationSession.standard()) as PartiQLResult.Value + return config.value.bindings + } +} diff --git a/cli/src/main/kotlin/org/partiql/pipeline/AbstractPipeline.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pipeline/AbstractPipeline.kt similarity index 79% rename from cli/src/main/kotlin/org/partiql/pipeline/AbstractPipeline.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pipeline/AbstractPipeline.kt index 1da6ffad5..2974da9c2 100644 --- a/cli/src/main/kotlin/org/partiql/pipeline/AbstractPipeline.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pipeline/AbstractPipeline.kt @@ -12,12 +12,14 @@ * language governing permissions and limitations under the License. */ -package org.partiql.pipeline +package org.partiql.cli.pipeline import com.amazon.ion.IonSystem import com.amazon.ion.system.IonSystemBuilder import org.partiql.annotations.PartiQLExperimental -import org.partiql.cli.Pipeline +import org.partiql.cli.functions.ReadFile +import org.partiql.cli.functions.WriteFile +import org.partiql.extensions.cli.functions.QueryDDB import org.partiql.lang.CompilerPipeline import org.partiql.lang.compiler.PartiQLCompilerBuilder import org.partiql.lang.compiler.PartiQLCompilerPipeline @@ -48,8 +50,8 @@ internal sealed class AbstractPipeline(open val options: PipelineOptions) { companion object { internal fun create(options: PipelineOptions): AbstractPipeline = when (options.pipeline) { - Pipeline.STANDARD -> PipelineStandard(options) - Pipeline.EXPERIMENTAL -> PipelineExperimental(options) + PipelineType.STANDARD -> PipelineStandard(options) + PipelineType.EXPERIMENTAL -> PipelineExperimental(options) } internal fun convertExprValue(value: ExprValue): PartiQLResult { return PartiQLResult.Value(value) @@ -57,10 +59,36 @@ internal sealed class AbstractPipeline(open val options: PipelineOptions) { internal fun standard(): AbstractPipeline { return create(PipelineOptions()) } + + internal fun createPipelineOptions( + pipeline: PipelineType, + typedOpBehavior: TypedOpBehavior, + projectionIteration: ProjectionIterationBehavior, + undefinedVariable: UndefinedVariableBehavior, + permissiveMode: TypingMode + ): PipelineOptions { + val ion = IonSystemBuilder.standard().build() + val functions: List<(ExprValueFactory) -> ExprFunction> = listOf( + { valueFactory -> ReadFile(valueFactory) }, + { valueFactory -> WriteFile(valueFactory) }, + { valueFactory -> QueryDDB(valueFactory) } + ) + val parser = PartiQLParserBuilder().ionSystem(ion).build() + return PipelineOptions( + pipeline, + ion, + parser, + typedOpBehavior, + projectionIteration, + undefinedVariable, + permissiveMode, + functions = functions + ) + } } data class PipelineOptions( - val pipeline: Pipeline = Pipeline.STANDARD, + val pipeline: PipelineType = PipelineType.STANDARD, val ion: IonSystem = IonSystemBuilder.standard().build(), val parser: Parser = PartiQLParserBuilder.standard().build(), val typedOpBehavior: TypedOpBehavior = TypedOpBehavior.HONOR_PARAMETERS, @@ -70,6 +98,11 @@ internal sealed class AbstractPipeline(open val options: PipelineOptions) { val functions: List<(ExprValueFactory) -> ExprFunction> = emptyList() ) + internal enum class PipelineType { + STANDARD, + EXPERIMENTAL + } + /** * Wraps the EvaluatingCompiler */ diff --git a/cli/src/main/kotlin/org/partiql/cli/Cli.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/query/Cli.kt similarity index 81% rename from cli/src/main/kotlin/org/partiql/cli/Cli.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/query/Cli.kt index 8cd821092..6c9951f7e 100644 --- a/cli/src/main/kotlin/org/partiql/cli/Cli.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/query/Cli.kt @@ -12,12 +12,15 @@ * language governing permissions and limitations under the License. */ -package org.partiql.cli +package org.partiql.cli.query import com.amazon.ion.system.IonReaderBuilder import com.amazon.ion.system.IonSystemBuilder import com.amazon.ion.system.IonTextWriterBuilder -import org.partiql.format.ExplainFormatter +import org.partiql.cli.format.ExplainFormatter +import org.partiql.cli.pico.PartiQLCommand +import org.partiql.cli.pipeline.AbstractPipeline +import org.partiql.cli.utils.EmptyInputStream import org.partiql.lang.eval.Bindings import org.partiql.lang.eval.EvaluationSession import org.partiql.lang.eval.ExprValue @@ -26,7 +29,6 @@ import org.partiql.lang.eval.PartiQLResult import org.partiql.lang.eval.delegate import org.partiql.lang.eval.toIonValue import org.partiql.lang.util.ConfigurableExprValueFormatter -import org.partiql.pipeline.AbstractPipeline import java.io.InputStream import java.io.OutputStream import java.io.OutputStreamWriter @@ -34,20 +36,20 @@ import java.io.OutputStreamWriter internal class Cli( private val valueFactory: ExprValueFactory, private val input: InputStream, - private val inputFormat: InputFormat, + private val inputFormat: PartiQLCommand.InputFormat, private val output: OutputStream, - private val outputFormat: OutputFormat, + private val outputFormat: PartiQLCommand.OutputFormat, private val compilerPipeline: AbstractPipeline, private val globals: Bindings, private val query: String, private val wrapIon: Boolean -) : PartiQLCommand { +) { private val ion = IonSystemBuilder.standard().build() init { - if (wrapIon && inputFormat != InputFormat.ION) { - throw IllegalArgumentException("Specifying --wrap-ion requires that the input format be ${InputFormat.ION}.") + if (wrapIon && inputFormat != PartiQLCommand.InputFormat.ION) { + throw IllegalArgumentException("Specifying --wrap-ion requires that the input format be ${PartiQLCommand.InputFormat.ION}.") } } @@ -56,10 +58,10 @@ internal class Cli( .withWriteTopLevelValuesOnNewLines(true) } - override fun run() { + internal fun run() { when (inputFormat) { - InputFormat.ION -> runWithIonInput() - InputFormat.PARTIQL -> runWithPartiQLInput() + PartiQLCommand.InputFormat.ION -> runWithIonInput() + PartiQLCommand.InputFormat.PARTIQL -> runWithPartiQLInput() } } @@ -118,10 +120,10 @@ internal class Cli( private fun outputResult(result: ExprValue) { when (outputFormat) { - OutputFormat.ION_TEXT -> ionTextWriterBuilder.build(output).use { result.toIonValue(ion).writeTo(it) } - OutputFormat.ION_BINARY -> valueFactory.ion.newBinaryWriter(output).use { result.toIonValue(ion).writeTo(it) } - OutputFormat.PARTIQL -> OutputStreamWriter(output).use { it.write(result.toString()) } - OutputFormat.PARTIQL_PRETTY -> OutputStreamWriter(output).use { + PartiQLCommand.OutputFormat.ION_TEXT -> ionTextWriterBuilder.build(output).use { result.toIonValue(ion).writeTo(it) } + PartiQLCommand.OutputFormat.ION_BINARY -> valueFactory.ion.newBinaryWriter(output).use { result.toIonValue(ion).writeTo(it) } + PartiQLCommand.OutputFormat.PARTIQL -> OutputStreamWriter(output).use { it.write(result.toString()) } + PartiQLCommand.OutputFormat.PARTIQL_PRETTY -> OutputStreamWriter(output).use { ConfigurableExprValueFormatter.pretty.formatTo(result, it) } } diff --git a/cli/src/main/kotlin/org/partiql/shell/CompleterDefault.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/CompleterDefault.kt similarity index 98% rename from cli/src/main/kotlin/org/partiql/shell/CompleterDefault.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/CompleterDefault.kt index 686a3b2c5..9f7d2399e 100644 --- a/cli/src/main/kotlin/org/partiql/shell/CompleterDefault.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/CompleterDefault.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.shell +package org.partiql.cli.shell import org.jline.reader.Completer import org.jline.reader.impl.completer.AggregateCompleter diff --git a/cli/src/main/kotlin/org/partiql/shell/Shell.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/Shell.kt similarity index 98% rename from cli/src/main/kotlin/org/partiql/shell/Shell.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/Shell.kt index 11482a866..cc9eb372b 100644 --- a/cli/src/main/kotlin/org/partiql/shell/Shell.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/Shell.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.shell +package org.partiql.cli.shell import com.google.common.base.CharMatcher import com.google.common.util.concurrent.Uninterruptibles @@ -29,7 +29,8 @@ import org.jline.utils.AttributedStringBuilder import org.jline.utils.AttributedStyle import org.jline.utils.InfoCmp import org.joda.time.Duration -import org.partiql.format.ExplainFormatter +import org.partiql.cli.format.ExplainFormatter +import org.partiql.cli.pipeline.AbstractPipeline import org.partiql.lang.eval.Bindings import org.partiql.lang.eval.EvaluationSession import org.partiql.lang.eval.ExprValue @@ -39,7 +40,6 @@ import org.partiql.lang.eval.delegate import org.partiql.lang.syntax.PartiQLParserBuilder import org.partiql.lang.util.ConfigurableExprValueFormatter import org.partiql.lang.util.ExprValueFormatter -import org.partiql.pipeline.AbstractPipeline import java.io.Closeable import java.io.OutputStream import java.io.PrintStream @@ -56,7 +56,7 @@ private const val PROMPT_1 = "PartiQL> " private const val PROMPT_2 = " | " private const val BAR_1 = "===' " private const val BAR_2 = "--- " -private const val WELCOME_MSG = "Welcome to the PartiQL REPL!" +private const val WELCOME_MSG = "Welcome to the PartiQL shell!" private const val HELP = """ !add_to_global_env Adds a value to the global environment diff --git a/cli/src/main/kotlin/org/partiql/shell/ShellExpander.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellExpander.kt similarity index 98% rename from cli/src/main/kotlin/org/partiql/shell/ShellExpander.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellExpander.kt index 61f6a97af..14683b11a 100644 --- a/cli/src/main/kotlin/org/partiql/shell/ShellExpander.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellExpander.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.shell +package org.partiql.cli.shell import org.jline.reader.Expander import org.jline.reader.History diff --git a/cli/src/main/kotlin/org/partiql/shell/ShellGlobalBinding.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellGlobalBinding.kt similarity index 98% rename from cli/src/main/kotlin/org/partiql/shell/ShellGlobalBinding.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellGlobalBinding.kt index 95032d538..4ce91b521 100644 --- a/cli/src/main/kotlin/org/partiql/shell/ShellGlobalBinding.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellGlobalBinding.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.shell +package org.partiql.cli.shell import org.partiql.lang.eval.BindingCase import org.partiql.lang.eval.BindingName diff --git a/cli/src/main/kotlin/org/partiql/shell/ShellHighlighter.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellHighlighter.kt similarity index 99% rename from cli/src/main/kotlin/org/partiql/shell/ShellHighlighter.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellHighlighter.kt index 39fcf8d23..45c54bb27 100644 --- a/cli/src/main/kotlin/org/partiql/shell/ShellHighlighter.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellHighlighter.kt @@ -11,7 +11,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific * language governing permissions and limitations under the License. */ -package org.partiql.shell +package org.partiql.cli.shell import org.antlr.v4.runtime.BaseErrorListener import org.antlr.v4.runtime.CharStreams diff --git a/cli/src/main/kotlin/org/partiql/shell/ShellParser.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellParser.kt similarity index 98% rename from cli/src/main/kotlin/org/partiql/shell/ShellParser.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellParser.kt index d92aa180f..85f3f6b6b 100644 --- a/cli/src/main/kotlin/org/partiql/shell/ShellParser.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/shell/ShellParser.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.shell +package org.partiql.cli.shell import org.jline.reader.EOFError import org.jline.reader.ParsedLine diff --git a/cli/src/main/kotlin/org/partiql/cli/stopwatch.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/utils/stopwatch.kt similarity index 96% rename from cli/src/main/kotlin/org/partiql/cli/stopwatch.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/utils/stopwatch.kt index b1f369b79..cf0664087 100644 --- a/cli/src/main/kotlin/org/partiql/cli/stopwatch.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/utils/stopwatch.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.cli +package org.partiql.cli.utils import java.util.concurrent.TimeUnit diff --git a/cli/src/main/kotlin/org/partiql/cli/streams.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/utils/streams.kt similarity index 97% rename from cli/src/main/kotlin/org/partiql/cli/streams.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/utils/streams.kt index 1fc065b1e..5154a573f 100644 --- a/cli/src/main/kotlin/org/partiql/cli/streams.kt +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/utils/streams.kt @@ -12,7 +12,7 @@ * language governing permissions and limitations under the License. */ -package org.partiql.cli +package org.partiql.cli.utils import java.io.FilterOutputStream import java.io.InputStream diff --git a/cli/src/test/kotlin/org/partiql/cli/CliTest.kt b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTest.kt similarity index 86% rename from cli/src/test/kotlin/org/partiql/cli/CliTest.kt rename to partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTest.kt index 06bface1d..a3558f7f3 100644 --- a/cli/src/test/kotlin/org/partiql/cli/CliTest.kt +++ b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTest.kt @@ -20,6 +20,8 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.partiql.cli.pico.PartiQLCommand +import org.partiql.cli.pipeline.AbstractPipeline import org.partiql.lang.eval.BAG_ANNOTATION import org.partiql.lang.eval.EvaluationException import org.partiql.lang.eval.MISSING_ANNOTATION @@ -27,7 +29,6 @@ import org.partiql.lang.eval.ProjectionIterationBehavior import org.partiql.lang.eval.TypedOpBehavior import org.partiql.lang.eval.TypingMode import org.partiql.lang.eval.UndefinedVariableBehavior -import org.partiql.pipeline.AbstractPipeline import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream @@ -54,8 +55,8 @@ class CliTest { val input = "[{'a': 1}]" val expected = "$BAG_ANNOTATION::[{a: 1}]" - val ionInputResult = makeCliAndGetResult(query, input, inputFormat = InputFormat.ION) - val partiqlInputResult = makeCliAndGetResult(query, input, inputFormat = InputFormat.PARTIQL) + val ionInputResult = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.ION) + val partiqlInputResult = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.PARTIQL) assertAsIon(expected, ionInputResult) assertAsIon(expected, partiqlInputResult) @@ -86,7 +87,7 @@ class CliTest { val query = "SELECT * FROM input_data" val input = "{a:1} {a:2}" assertThrows { - makeCliAndGetResult(query, input, wrapIon = true, inputFormat = InputFormat.PARTIQL) + makeCliAndGetResult(query, input, wrapIon = true, inputFormat = PartiQLCommand.InputFormat.PARTIQL) } } @@ -96,8 +97,8 @@ class CliTest { val input = "[{'a': 1},{'a': 2},{'a': 3}]" val expected = "$BAG_ANNOTATION::[{a: 1},{a: 2},{a: 3}]" - val ionInputResult = makeCliAndGetResult(query, input, inputFormat = InputFormat.ION) - val partiqlInputResult = makeCliAndGetResult(query, input, inputFormat = InputFormat.PARTIQL) + val ionInputResult = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.ION) + val partiqlInputResult = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.PARTIQL) assertAsIon(expected, ionInputResult) assertAsIon(expected, partiqlInputResult) @@ -109,8 +110,8 @@ class CliTest { val input = "[{'a': 1}]" val expected = "$BAG_ANNOTATION::[{a: 1}]" - val ionInputResult = makeCliAndGetResult(query, input, inputFormat = InputFormat.ION) - val partiqlInputResult = makeCliAndGetResult(query, input, inputFormat = InputFormat.PARTIQL) + val ionInputResult = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.ION) + val partiqlInputResult = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.PARTIQL) assertAsIon(expected, ionInputResult) assertAsIon(expected, partiqlInputResult) @@ -126,7 +127,7 @@ class CliTest { val wrappedInputResult = makeCliAndGetResult(query, wrappedInput, bindings = bindings, wrapIon = true) val ionInputResult = makeCliAndGetResult(query, input, bindings = bindings) - val partiqlInputResult = makeCliAndGetResult(query, input, bindings = bindings, inputFormat = InputFormat.PARTIQL) + val partiqlInputResult = makeCliAndGetResult(query, input, bindings = bindings, inputFormat = PartiQLCommand.InputFormat.PARTIQL) assertAsIon(expected, wrappedInputResult) assertAsIon(expected, ionInputResult) @@ -143,7 +144,7 @@ class CliTest { val wrappedInputResult = makeCliAndGetResult(query, wrappedInput, bindings = bindings, wrapIon = true) val ionInputResult = makeCliAndGetResult(query, input, bindings = bindings) - val partiqlInputResult = makeCliAndGetResult(query, input, bindings = bindings, inputFormat = InputFormat.PARTIQL) + val partiqlInputResult = makeCliAndGetResult(query, input, bindings = bindings, inputFormat = PartiQLCommand.InputFormat.PARTIQL) assertAsIon(expected, wrappedInputResult) assertAsIon(expected, ionInputResult) @@ -157,8 +158,8 @@ class CliTest { val wrappedInput = "{a: 1}" val expected = "<<{'a': 1}>>" - val wrappedInputResult = makeCliAndGetResult(query, wrappedInput, wrapIon = true, outputFormat = OutputFormat.PARTIQL) - val ionInputResult = makeCliAndGetResult(query, input, inputFormat = InputFormat.ION, outputFormat = OutputFormat.PARTIQL) + val wrappedInputResult = makeCliAndGetResult(query, wrappedInput, wrapIon = true, outputFormat = PartiQLCommand.OutputFormat.PARTIQL) + val ionInputResult = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.ION, outputFormat = PartiQLCommand.OutputFormat.PARTIQL) assertEquals(expected, wrappedInputResult) assertEquals(expected, ionInputResult) @@ -170,7 +171,7 @@ class CliTest { val input = "[{a: 1, b: 2}]" val expected = "<<\n {\n 'a': 1,\n 'b': 2\n }\n>>" - val actual = makeCliAndGetResult(query, input, inputFormat = InputFormat.ION, outputFormat = OutputFormat.PARTIQL_PRETTY) + val actual = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.ION, outputFormat = PartiQLCommand.OutputFormat.PARTIQL_PRETTY) assertEquals(expected, actual) } @@ -181,7 +182,7 @@ class CliTest { val input = "[{a: 1}, {b: 1}]" val expected = "$BAG_ANNOTATION::[{a:1}\n,{b:1}\n]" - val actual = makeCliAndGetResult(query, input, inputFormat = InputFormat.ION, outputFormat = OutputFormat.ION_TEXT) + val actual = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.ION, outputFormat = PartiQLCommand.OutputFormat.ION_TEXT) assertAsIon(expected, actual) } @@ -191,11 +192,11 @@ class CliTest { val query = "SELECT * FROM input_data" val input = "[{'a': 1}, {'b': 1}]" val expected = "$BAG_ANNOTATION::[{a:1}\n,{b:1}\n]" - makeCliAndGetResult(query, input, inputFormat = InputFormat.ION, outputFormat = OutputFormat.ION_TEXT, output = FileOutputStream(testFile)) + makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.ION, outputFormat = PartiQLCommand.OutputFormat.ION_TEXT, output = FileOutputStream(testFile)) val ionInputResult = testFile!!.bufferedReader().use { it.readText() } assertAsIon(expected, ionInputResult) - makeCliAndGetResult(query, input, inputFormat = InputFormat.PARTIQL, outputFormat = OutputFormat.ION_TEXT, output = FileOutputStream(testFile)) + makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.PARTIQL, outputFormat = PartiQLCommand.OutputFormat.ION_TEXT, output = FileOutputStream(testFile)) val partiqlInputResult = testFile!!.bufferedReader().use { it.readText() } assertAsIon(expected, partiqlInputResult) } @@ -251,7 +252,7 @@ class CliTest { val input = "<<{'a': null, 'b': missing, 'c': 1}>>" val query = "SELECT a, b, c FROM input_data" assertThrows { - makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = InputFormat.PARTIQL) + makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = PartiQLCommand.InputFormat.PARTIQL) } } @@ -260,7 +261,7 @@ class CliTest { val pipeline = AbstractPipeline.create(AbstractPipeline.PipelineOptions(projectionIterationBehavior = ProjectionIterationBehavior.FILTER_MISSING)) val input = "<<{'a': null, 'b': missing, 'c': 1}>>" val query = "SELECT * FROM input_data" - val actual = makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = InputFormat.PARTIQL) + val actual = makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = PartiQLCommand.InputFormat.PARTIQL) assertAsIon("$BAG_ANNOTATION::[{a:null,c:1}]", actual) } @@ -269,7 +270,7 @@ class CliTest { val pipeline = AbstractPipeline.create(AbstractPipeline.PipelineOptions(projectionIterationBehavior = ProjectionIterationBehavior.UNFILTERED)) val input = "<<{'a': null, 'b': missing, 'c': 1}>>" val query = "SELECT a, b, c FROM input_data" - val actual = makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = InputFormat.PARTIQL) + val actual = makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = PartiQLCommand.InputFormat.PARTIQL) assertAsIon("$BAG_ANNOTATION::[{a:null,c:1}]", actual) } @@ -279,7 +280,7 @@ class CliTest { val input = "<<{'a': 1}>>" val query = "SELECT * FROM undefined_variable" assertThrows { - makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = InputFormat.PARTIQL) + makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = PartiQLCommand.InputFormat.PARTIQL) } } @@ -288,7 +289,7 @@ class CliTest { val pipeline = AbstractPipeline.create(AbstractPipeline.PipelineOptions(undefinedVariableBehavior = UndefinedVariableBehavior.MISSING)) val input = "<<{'a': 1}>>" val query = "SELECT * FROM undefined_variable" - val actual = makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = InputFormat.PARTIQL) + val actual = makeCliAndGetResult(query, input, pipeline = pipeline, inputFormat = PartiQLCommand.InputFormat.PARTIQL) assertAsIon("$BAG_ANNOTATION::[{}]", actual) } @@ -298,7 +299,7 @@ class CliTest { val input = "<<{'a': 1}, {'b': 1}>>" val expected = "$BAG_ANNOTATION::[{a:1}\n,{b:1}\n]" - val partiqlInputResult = makeCliAndGetResult(query, input, inputFormat = InputFormat.PARTIQL) + val partiqlInputResult = makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.PARTIQL) assertAsIon(expected, partiqlInputResult) } @@ -307,7 +308,7 @@ class CliTest { val query = "SELECT * FROM input_data" val input = "<<{'a': 1}, {'b': 1}>>" assertThrows { - makeCliAndGetResult(query, input, inputFormat = InputFormat.ION) + makeCliAndGetResult(query, input, inputFormat = PartiQLCommand.InputFormat.ION) } } } diff --git a/cli/src/test/kotlin/org/partiql/cli/CliTestUtility.kt b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTestUtility.kt similarity index 83% rename from cli/src/test/kotlin/org/partiql/cli/CliTestUtility.kt rename to partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTestUtility.kt index a239071a9..63005b824 100644 --- a/cli/src/test/kotlin/org/partiql/cli/CliTestUtility.kt +++ b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTestUtility.kt @@ -3,10 +3,13 @@ package org.partiql.cli import com.amazon.ion.IonSystem import com.amazon.ion.system.IonSystemBuilder import org.junit.jupiter.api.Assertions.assertEquals +import org.partiql.cli.pico.PartiQLCommand +import org.partiql.cli.pipeline.AbstractPipeline +import org.partiql.cli.query.Cli +import org.partiql.cli.utils.EmptyInputStream import org.partiql.lang.eval.Bindings import org.partiql.lang.eval.ExprValue import org.partiql.lang.eval.ExprValueFactory -import org.partiql.pipeline.AbstractPipeline import java.io.ByteArrayOutputStream import java.io.OutputStream @@ -16,9 +19,9 @@ import java.io.OutputStream internal fun makeCliAndGetResult( query: String, input: String? = null, - inputFormat: InputFormat = InputFormat.ION, + inputFormat: PartiQLCommand.InputFormat = PartiQLCommand.InputFormat.ION, bindings: Bindings = Bindings.empty(), - outputFormat: OutputFormat = OutputFormat.ION_TEXT, + outputFormat: PartiQLCommand.OutputFormat = PartiQLCommand.OutputFormat.ION_TEXT, output: OutputStream = ByteArrayOutputStream(), ion: IonSystem = IonSystemBuilder.standard().build(), pipeline: AbstractPipeline = AbstractPipeline.standard(), diff --git a/cli/src/test/kotlin/org/partiql/cli/functions/ReadFileTest.kt b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/functions/ReadFileTest.kt similarity index 100% rename from cli/src/test/kotlin/org/partiql/cli/functions/ReadFileTest.kt rename to partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/functions/ReadFileTest.kt diff --git a/cli/src/test/kotlin/org/partiql/cli/functions/WriteFileTest.kt b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/functions/WriteFileTest.kt similarity index 99% rename from cli/src/test/kotlin/org/partiql/cli/functions/WriteFileTest.kt rename to partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/functions/WriteFileTest.kt index 5b583beaa..12e01c986 100644 --- a/cli/src/test/kotlin/org/partiql/cli/functions/WriteFileTest.kt +++ b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/functions/WriteFileTest.kt @@ -21,11 +21,11 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.partiql.cli.assertAsIon import org.partiql.cli.makeCliAndGetResult +import org.partiql.cli.pipeline.AbstractPipeline import org.partiql.lang.eval.BAG_ANNOTATION import org.partiql.lang.eval.EvaluationSession import org.partiql.lang.eval.ExprValueFactory import org.partiql.lang.eval.toIonValue -import org.partiql.pipeline.AbstractPipeline import java.io.ByteArrayOutputStream import java.io.OutputStream import java.nio.file.Files diff --git a/cli/src/test/resources/junit-platform.properties b/partiql-app/partiql-cli/src/test/resources/junit-platform.properties similarity index 100% rename from cli/src/test/resources/junit-platform.properties rename to partiql-app/partiql-cli/src/test/resources/junit-platform.properties diff --git a/settings.gradle.kts b/settings.gradle.kts index ab0f2a8f5..802e37483 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,7 +16,7 @@ rootProject.name = "partiql" include( "lang", - "cli", + "partiql-app:partiql-cli", "examples", "extensions", "lib:partiql-isl",