From bde2a3f266b860ba71bf4e5d7137561b54fed407 Mon Sep 17 00:00:00 2001 From: John Ed Quinn <40360967+johnedquinn@users.noreply.github.com> Date: Fri, 30 Dec 2022 11:22:08 -0800 Subject: [PATCH] Replaces CLI to use Pico CLI (#946) * Replaces CLI to use Pico CLI * Repackages CLI * Consolidates CLI commands * Adds ability to handle queries from standard input Co-authored-by: R. C. Howell * Cleans up logic Co-authored-by: R. C. Howell * Adjusts options, script name, and capitalization --- CHANGELOG.md | 2 + buildSrc/src/main/kotlin/partiql.versions.kt | 4 +- docs/tutorials/Command Line Tutorial.md | 91 +++--- docs/tutorials/Tutorial.md | 94 +----- partiql-app/partiql-cli/build.gradle.kts | 2 +- .../partiql-cli/{shell.sh => partiql.sh} | 2 +- .../cli/{PartiQLCommand.kt => Main.kt} | 20 +- .../partiql/{ => cli}/format/DotFormatter.kt | 12 +- .../{ => cli}/format/DotUrlFormatter.kt | 2 +- .../{ => cli}/format/ExplainFormatter.kt | 2 +- .../partiql/{ => cli}/format/NodeFormatter.kt | 2 +- .../partiql/{ => cli}/format/SexpFormatter.kt | 2 +- .../partiql/{ => cli}/format/TreeFormatter.kt | 2 +- .../org/partiql/{ => cli}/format/Utilities.kt | 2 +- .../org/partiql/{ => cli}/format/dot/Dot.kt | 6 +- .../src/main/kotlin/org/partiql/cli/main.kt | 277 ------------------ .../org/partiql/cli/pico/PartiQLCommand.kt | 140 +++++++++ .../cli/pico/PartiQLVersionProvider.kt | 26 ++ .../org/partiql/cli/pico/PipelineOptions.kt | 69 +++++ .../{ => cli}/pipeline/AbstractPipeline.kt | 43 ++- .../kotlin/org/partiql/cli/{ => query}/Cli.kt | 32 +- .../{ => cli}/shell/CompleterDefault.kt | 2 +- .../org/partiql/{ => cli}/shell/Shell.kt | 8 +- .../partiql/{ => cli}/shell/ShellExpander.kt | 2 +- .../{ => cli}/shell/ShellGlobalBinding.kt | 2 +- .../{ => cli}/shell/ShellHighlighter.kt | 2 +- .../partiql/{ => cli}/shell/ShellParser.kt | 2 +- .../org/partiql/cli/{ => utils}/stopwatch.kt | 2 +- .../org/partiql/cli/{ => utils}/streams.kt | 2 +- .../test/kotlin/org/partiql/cli/CliTest.kt | 47 +-- .../kotlin/org/partiql/cli/CliTestUtility.kt | 9 +- .../partiql/cli/functions/WriteFileTest.kt | 2 +- 32 files changed, 420 insertions(+), 492 deletions(-) rename partiql-app/partiql-cli/{shell.sh => partiql.sh} (92%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/{PartiQLCommand.kt => Main.kt} (53%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/format/DotFormatter.kt (97%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/format/DotUrlFormatter.kt (97%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/format/ExplainFormatter.kt (97%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/format/NodeFormatter.kt (95%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/format/SexpFormatter.kt (96%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/format/TreeFormatter.kt (99%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/format/Utilities.kt (96%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/format/dot/Dot.kt (99%) delete mode 100644 partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/main.kt create mode 100644 partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PartiQLCommand.kt create mode 100644 partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PartiQLVersionProvider.kt create mode 100644 partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PipelineOptions.kt rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/pipeline/AbstractPipeline.kt (79%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/{ => query}/Cli.kt (81%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/shell/CompleterDefault.kt (98%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/shell/Shell.kt (98%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/shell/ShellExpander.kt (98%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/shell/ShellGlobalBinding.kt (98%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/shell/ShellHighlighter.kt (99%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/{ => cli}/shell/ShellParser.kt (98%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/{ => utils}/stopwatch.kt (96%) rename partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/{ => utils}/streams.kt (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a81ff1770d..33e5d086c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,8 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Adds ability to pipe queries to the CLI ### Changed +- Updates the CLI to use Pico CLI and modifies several CLI options ### Deprecated diff --git a/buildSrc/src/main/kotlin/partiql.versions.kt b/buildSrc/src/main/kotlin/partiql.versions.kt index 0057324335..441e9bd38b 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/docs/tutorials/Command Line Tutorial.md b/docs/tutorials/Command Line Tutorial.md index c2b9d2b4f9..8ddd38bad3 100644 --- a/docs/tutorials/Command Line Tutorial.md +++ b/docs/tutorials/Command Line Tutorial.md @@ -1,63 +1,52 @@ # PartiQL CLI -``` -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 +## Build and Run the CLI -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 +The following command will build and run the CLI: -To pipe input data in via stdin: - cat /logs/log.ion | partiql --query="SELECT * FROM input_data" --format=ION_BINARY > output.10n - -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 -``` - -## Building the CLI +```shell +# To build and run +./partiql-app/partiql-cli/shell.sh -The root Gradle build also builds the CLI. To build the CLI separately, execute: +# To build (only) +./gradlew :partiql-app:partiql-cli:install -```shell -./gradlew :cli:build +# To Run (only) +./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 +After building the entire project, distributable jars are located in the `cli/build/distributions` directory (relative to the project root). Be sure to include the correct relative path to `gradlew` if you are not in the project root. -## Using the CLI +## CLI Options -The following command will build any dependencies before starting the CLI. +To view all available options, run the CLI with the `--help` option. + +## Non-Interactive (Single Query Execution) + +To execute a single query, run: ```shell -./gradlew :cli:run -q --args="" +./partiql-app/partiql-cli/shell.sh query.partiql ``` -The CLI can be run in two manners, non-interactive and interactive (REPL). +where `query.partiql` contains the PartiQL query to execute. + +Alternatively, you may pipe input into the native command: + +```shell +# 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 +``` -## REPL +## Interactive (Shell) -To start an interactive read, eval, print loop (REPL) execute: +To start an interactive shell, execute: > Note that running directly with Gradle will eat arrow keys and control sequences due to the Gradle daemon. @@ -68,7 +57,7 @@ To start an interactive read, eval, print loop (REPL) execute: You will see a prompt that looks as follows: ```shell -Welcome to the PartiQL REPL! +Welcome to the PartiQL shell! PartiQL> ``` @@ -113,7 +102,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 +138,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 +159,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 +196,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 +398,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 +429,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 +705,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 b429d1b65b..28f4991522 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/partiql-app/partiql-cli/build.gradle.kts b/partiql-app/partiql-cli/build.gradle.kts index d6cb4ef69b..5590015188 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-cli/shell.sh b/partiql-app/partiql-cli/partiql.sh similarity index 92% rename from partiql-app/partiql-cli/shell.sh rename to partiql-app/partiql-cli/partiql.sh index 7ccea35986..ab834658f3 100755 --- a/partiql-app/partiql-cli/shell.sh +++ b/partiql-app/partiql-cli/partiql.sh @@ -17,4 +17,4 @@ cli_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cd "$cli_path" ../../gradlew :partiql-app:partiql-cli:install -../partiql-cli/build/install/partiql-cli/bin/partiql +../partiql-cli/build/install/partiql-cli/bin/partiql "$@" diff --git a/partiql-app/partiql-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 partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/PartiQLCommand.kt rename to partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt index ba5dbca214..9d337c3aa9 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 2a3c5b136f..1d6abf0da6 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 369ed3d572..92b00e6d07 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 81e6705152..de0ec4bf1e 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 1e18490b59..2257b1e8dc 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 87d3a6b89e..b5a6dc3fc8 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 6fdb1eaf36..553b2c9b86 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 8e24ff6c35..c64d432d44 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 d762b8cd88..30facb94c8 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/main.kt b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/main.kt deleted file mode 100644 index 5d5203f955..0000000000 --- a/partiql-app/partiql-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/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 0000000000..97dce22547 --- /dev/null +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PartiQLCommand.kt @@ -0,0 +1,140 @@ +/* + * 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 + + /** + * Run the CLI or Shell (default) + */ + override fun run() { + val command = executionOptions ?: ExecutionOptions() + val shell = shellOptions ?: ShellOptions() + val stdin = System.`in` + when { + command.query != null -> runCli(command, command.query!!.inputStream()) + stdin.available() > 0 -> runCli(command, stdin) + 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) + input.use { + output.use { + Cli(valueFactory, input, exec.inputFormat, output, exec.outputFormat, options.pipeline, options.globalEnvironment, query, exec.wrapIon).run() + } + } + } + + /** + * Runs the interactive shell + */ + private fun runShell(shell: ShellOptions = ShellOptions()) { + val config = Shell.ShellConfiguration(isMonochrome = shell.isMonochrome) + Shell(valueFactory, System.out, options.pipeline, options.globalEnvironment, 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 0000000000..7b29f7b287 --- /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 0000000000..e0d69690cc --- /dev/null +++ b/partiql-app/partiql-cli/src/main/kotlin/org/partiql/cli/pico/PipelineOptions.kt @@ -0,0 +1,69 @@ +/* + * 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 + + private val pipelineOptions = AbstractPipeline.createPipelineOptions( + pipelineType, + typedOpBehavior, + projIterBehavior, + undefinedVarBehavior, + typingMode + ) + + internal val pipeline = AbstractPipeline.create(pipelineOptions) + + internal val globalEnvironment = when (environmentFile) { + null -> Bindings.empty() + else -> getEnvironment(environmentFile!!, pipeline) + } + + 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 + } +} diff --git a/partiql-app/partiql-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 partiql-app/partiql-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 1da6ffad5c..2974da9c27 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 8cd8210925..6c9951f7e6 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 686a3b2c5c..9f7d2399e3 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 11482a866e..cc9eb372b3 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 61f6a97afa..14683b11a1 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 95032d538e..4ce91b5215 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 39fcf8d233..45c54bb27d 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 d92aa180f6..85f3f6b6b4 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 b1f369b79f..cf06640874 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-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 partiql-app/partiql-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 1fc065b1e2..5154a573f8 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTest.kt b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTest.kt index 06bface1df..a3558f7f3a 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTestUtility.kt b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/CliTestUtility.kt index a239071a9f..63005b8243 100644 --- a/partiql-app/partiql-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/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/functions/WriteFileTest.kt b/partiql-app/partiql-cli/src/test/kotlin/org/partiql/cli/functions/WriteFileTest.kt index 5b583beaa0..12e01c9865 100644 --- a/partiql-app/partiql-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