From 024892f1bb3305b30a9ea80cbaab050e12bb2fa7 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Tue, 8 Mar 2022 10:18:51 -0800 Subject: [PATCH] [cli] Add permissive mode evaluation option to CLI/REPL (#545) --- cli/src/org/partiql/cli/Repl.kt | 11 ++++++++++ cli/src/org/partiql/cli/main.kt | 33 ++++++++++++++++++++++------- cli/test/org/partiql/cli/CliTest.kt | 18 ++++++++++++++-- docs/user/CLI.md | 24 +++++++++++++++++++++ 4 files changed, 76 insertions(+), 10 deletions(-) diff --git a/cli/src/org/partiql/cli/Repl.kt b/cli/src/org/partiql/cli/Repl.kt index 1ba4ab4c7a..895fdfadee 100644 --- a/cli/src/org/partiql/cli/Repl.kt +++ b/cli/src/org/partiql/cli/Repl.kt @@ -25,6 +25,7 @@ import org.partiql.lang.eval.ExprValue import org.partiql.lang.eval.ExprValueFactory import org.partiql.lang.eval.MapBindings import org.partiql.lang.eval.StructOrdering +import org.partiql.lang.eval.TypingMode import org.partiql.lang.eval.delegate import org.partiql.lang.syntax.Parser import org.partiql.lang.util.ConfigurableExprValueFormatter @@ -203,6 +204,15 @@ internal class Repl( outputWriter.write("\n") } + private fun printTypingMode() { + val typingModeString = when (compiler.compileOptions.typingMode) { + TypingMode.LEGACY -> "LEGACY" + TypingMode.PERMISSIVE -> "PERMISSIVE" + } + outputWriter.write("Typing mode: $typingModeString") + outputWriter.write("\n") + } + fun retrievePartiQLVersionAndHash(): String { val properties = Properties() properties.load(this.javaClass.getResourceAsStream("/partiql.properties")) @@ -286,6 +296,7 @@ internal class Repl( state = when (state) { ReplState.INIT -> { printWelcomeMessage() + printTypingMode() printVersionNumber() ReplState.READY } diff --git a/cli/src/org/partiql/cli/main.kt b/cli/src/org/partiql/cli/main.kt index af9e447e2a..5161ae5ad6 100644 --- a/cli/src/org/partiql/cli/main.kt +++ b/cli/src/org/partiql/cli/main.kt @@ -26,9 +26,11 @@ import org.partiql.cli.functions.ReadFile import org.partiql.cli.functions.WriteFile import org.partiql.lang.CompilerPipeline import org.partiql.lang.eval.Bindings +import org.partiql.lang.eval.CompileOptions import org.partiql.lang.eval.EvaluationSession import org.partiql.lang.eval.ExprValue import org.partiql.lang.eval.ExprValueFactory +import org.partiql.lang.eval.TypingMode import org.partiql.lang.syntax.SqlParser import java.io.File import java.io.FileInputStream @@ -40,10 +42,6 @@ private val ion = IonSystemBuilder.standard().build() private val valueFactory = ExprValueFactory.standard(ion) private val parser = SqlParser(ion) -private val compilerPipeline = CompilerPipeline.build(ion) { - addFunction(ReadFile(valueFactory)) - addFunction(WriteFile(valueFactory)) -} private val optParser = OptionParser() @@ -84,6 +82,8 @@ private val queryOpt = optParser.acceptsAll(listOf("query", "q"), "PartiQL query .withRequiredArg() .ofType(String::class.java) +private val permissiveModeOpt = optParser.acceptsAll(listOf("permissive", "p"), "runs the query in permissive mode") + private val environmentOpt = optParser.acceptsAll(listOf("environment", "e"), "initial global environment (optional)") .withRequiredArg() .ofType(File::class.java) @@ -114,6 +114,8 @@ private val outputFormatOpt = optParser.acceptsAll(listOf("output-format", "of") * * 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 + * mismatches) * * Non interactive only: * * -q --query: PartiQL query * * -i --input: input file, default STDIN @@ -133,6 +135,21 @@ fun main(args: Array) = try { throw IllegalArgumentException("Non option arguments are not allowed!") } + // compile options + // TODO: add other compile options https://github.com/partiql/partiql-lang-kotlin/issues/544 + val compileOptions = CompileOptions.build { + when (optionSet.has(permissiveModeOpt)) { + true -> typingMode(TypingMode.PERMISSIVE) + false -> typingMode(TypingMode.LEGACY) + } + } + + val compilerPipeline = CompilerPipeline.build(ion) { + addFunction(ReadFile(valueFactory)) + addFunction(WriteFile(valueFactory)) + compileOptions(compileOptions) + } + // common options val environment = when { optionSet.has(environmentOpt) -> { @@ -144,9 +161,9 @@ fun main(args: Array) = try { } if (optionSet.has(queryOpt)) { - runCli(environment, optionSet) + runCli(environment, optionSet, compilerPipeline) } else { - runRepl(environment) + runRepl(environment, compilerPipeline) } } catch (e: OptionException) { System.err.println("${e.message}\n") @@ -157,11 +174,11 @@ fun main(args: Array) = try { exitProcess(1) } -private fun runRepl(environment: Bindings) { +private fun runRepl(environment: Bindings, compilerPipeline: CompilerPipeline) { Repl(valueFactory, System.`in`, System.out, parser, compilerPipeline, environment).run() } -private fun runCli(environment: Bindings, optionSet: OptionSet) { +private fun runCli(environment: Bindings, optionSet: OptionSet, compilerPipeline: CompilerPipeline) { val input = if (optionSet.has(inputFileOpt)) { FileInputStream(optionSet.valueOf(inputFileOpt)) } else { diff --git a/cli/test/org/partiql/cli/CliTest.kt b/cli/test/org/partiql/cli/CliTest.kt index 572c544c59..77cad58807 100644 --- a/cli/test/org/partiql/cli/CliTest.kt +++ b/cli/test/org/partiql/cli/CliTest.kt @@ -24,6 +24,7 @@ import org.partiql.lang.eval.Bindings import org.partiql.lang.eval.EvaluationException import org.partiql.lang.eval.ExprValue import org.partiql.lang.eval.ExprValueFactory +import org.partiql.lang.eval.TypingMode import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream @@ -34,7 +35,6 @@ class CliTest { private val ion = IonSystemBuilder.standard().build() private val valueFactory = ExprValueFactory.standard(ion) private val output = ByteArrayOutputStream() - private val compilerPipeline = CompilerPipeline.standard(ion) private val testFile = File("test.ion") @Before @@ -52,7 +52,8 @@ class CliTest { input: String? = null, bindings: Bindings = Bindings.empty(), outputFormat: OutputFormat = OutputFormat.ION_TEXT, - output: OutputStream = this.output + output: OutputStream = this.output, + compilerPipeline: CompilerPipeline = CompilerPipeline.standard(ion) ) = Cli( valueFactory, @@ -166,4 +167,17 @@ class CliTest { fun withoutInputWithInputDataBindingThrowsException() { makeCli("SELECT * FROM input_data").runAndOutput() } + + @Test + fun runQueryInPermissiveMode() { + val permissiveModeCP = CompilerPipeline.build(ion) { + compileOptions { + typingMode(TypingMode.PERMISSIVE) + } + } + val subject = makeCli("1 + 'foo'", compilerPipeline = permissiveModeCP) + val actual = subject.runAndOutput() + + assertAsIon("\$partiql_missing::null", actual) + } } diff --git a/docs/user/CLI.md b/docs/user/CLI.md index 6df49f15d6..38aa23ae15 100644 --- a/docs/user/CLI.md +++ b/docs/user/CLI.md @@ -26,6 +26,7 @@ Option Description -o, --output output file, requires the query option (default: stdout) --of, --output-format +-p, --permissive run the PartiQL query in PERMISSIVE typing mode -q, --query PartiQL query, triggers non interactive mode ``` @@ -673,3 +674,26 @@ All the available options for customized CSV files are shown as following: 5. Set escape sign (single character only): `'escape':'\'` 6. Set quote sign (single character only): `'quote':'"'` 7. Set delimiter sign (single character only): `'delimiter':','` + +## 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) +typing mode, which will give an evaluation time error in the case of data type mismatches. + +``` +(Running in the default LEGACY typing mode) +PartiQL> 1 + 'foo'; +org.partiql.lang.eval.EvaluationException: ... + ... +``` + +Specifying the `-p` or `-permissive` flag will allow you to run PartiQL queries in [PERMISSIVE](https://github.com/partiql/partiql-lang-kotlin/blob/main/lang/src/org/partiql/lang/eval/CompileOptions.kt#L64-L73) +typing mode, which will return `MISSING` in the case of data type mismatches. + +``` +(Running in PERMISSIVE typing mode) +PartiQL> 1 + 'foo'; +===' +MISSING +--- +OK! +```