diff --git a/README.md b/README.md index 11d0583997..23f41efb8d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ This project is published to [Maven Central](https://search.maven.org/artifact/o | Group ID | Artifact ID | Recommended Version | |----------|-------------|---------------------| -| `org.partiql` | `partiql-lang-kotlin` | `0.2.0` +| `org.partiql` | `partiql-lang-kotlin` | `0.2.1` For Maven builds, add this to your `pom.xml`: diff --git a/build.gradle b/build.gradle index 13933217cc..d6ad8ee849 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ allprojects { subprojects { group = 'org.partiql' - version = '0.2.0' + version = '0.2.1' } buildDir = new File(rootProject.projectDir, "gradle-build/" + project.name) diff --git a/lang/src/org/partiql/lang/eval/EvaluatingCompiler.kt b/lang/src/org/partiql/lang/eval/EvaluatingCompiler.kt index 93d706fa15..ae77c6dfea 100644 --- a/lang/src/org/partiql/lang/eval/EvaluatingCompiler.kt +++ b/lang/src/org/partiql/lang/eval/EvaluatingCompiler.kt @@ -21,7 +21,6 @@ import org.partiql.lang.ast.passes.* import org.partiql.lang.errors.* import org.partiql.lang.eval.binding.* import org.partiql.lang.syntax.SqlParser -import org.partiql.lang.syntax.* import org.partiql.lang.util.* import java.math.* import java.util.* @@ -1088,7 +1087,13 @@ internal class EvaluatingCompiler( val name = syntheticColumnName(columns.size).exprValue() columns.add(value.namedValue(name)) } else { - for (childValue in value.filter { it.type != ExprValueType.MISSING }) { + val valuesToProject = when(compileOptions.projectionIteration) { + ProjectionIterationBehavior.FILTER_MISSING -> { + value.filter { it.type != ExprValueType.MISSING } + } + ProjectionIterationBehavior.UNFILTERED -> value + } + for (childValue in valuesToProject) { val namedFacet = childValue.asFacet(Named::class.java) val name = namedFacet?.name ?: syntheticColumnName(columns.size).exprValue() diff --git a/lang/test/org/partiql/lang/eval/EvaluatingCompilerTests.kt b/lang/test/org/partiql/lang/eval/EvaluatingCompilerTests.kt index dce7baec25..4114acf0b9 100644 --- a/lang/test/org/partiql/lang/eval/EvaluatingCompilerTests.kt +++ b/lang/test/org/partiql/lang/eval/EvaluatingCompilerTests.kt @@ -14,8 +14,10 @@ package org.partiql.lang.eval +import com.amazon.ion.system.IonSystemBuilder import org.junit.Ignore import org.junit.Test +import org.partiql.lang.CompilerPipeline import org.partiql.lang.syntax.ParserException class EvaluatingCompilerTests : EvaluatorTestBase() { @@ -1491,6 +1493,26 @@ class EvaluatingCompilerTests : EvaluatorTestBase() { assertEquals("100, MISSING, 200", actual.iterator().asSequence().joinToString(separator = ", ")) } + @Test + fun projectionIterationBehaviorUnfiltered_select_list() = + assertEvalExprValue( + source = "select a from <<{'a': MISSING}>>", + expected = "<<{'a': MISSING}>>", + compileOptions = CompileOptions.build { + projectionIteration(ProjectionIterationBehavior.UNFILTERED) + } + ) + + @Test + fun projectionIterationBehaviorUnfiltered_select_star() = + assertEvalExprValue( + source = "select * from <<{'a': MISSING}>>", + expected = "<<{'a': MISSING}>>", + compileOptions = CompileOptions.build { + projectionIteration(ProjectionIterationBehavior.UNFILTERED) + } + ) + @Test fun undefinedQualifiedVariableWithUndefinedVariableBehaviorError() { // Demonstrates that UndefinedVariableBehavior.ERROR does not affect qualified field names. diff --git a/lang/test/org/partiql/lang/eval/EvaluatorTestBase.kt b/lang/test/org/partiql/lang/eval/EvaluatorTestBase.kt index c9d702be1d..2a529f2a33 100644 --- a/lang/test/org/partiql/lang/eval/EvaluatorTestBase.kt +++ b/lang/test/org/partiql/lang/eval/EvaluatorTestBase.kt @@ -136,6 +136,14 @@ abstract class EvaluatorTestBase : TestBase() { assertRewrite(source, originalExprNode) } + protected fun assertExprEquals(expected: ExprValue, actual: ExprValue) { + if(!expected.exprEquals(actual)) { + println("Expected ionValue: ${ConfigurableExprValueFormatter.pretty.format(expected)} ") + println("Actual ionValue : ${ConfigurableExprValueFormatter.pretty.format(actual)} ") + fail("Expected and actual ExprValue instances are not equivalent") + } + } + protected fun assertExprEquals(expected: ExprValue, actual: ExprValue, message: String) { if(!expected.exprEquals(actual)) { println(message) @@ -145,6 +153,31 @@ abstract class EvaluatorTestBase : TestBase() { fail("Expected and actual ExprValue instances are not equivalent") } } + /** + * Evaluates [expected] and [source] and asserts that the resulting [ExprValue]s + * are equivalent using PartiQL's equivalence rules. This differs from `assertEval` + * in that the [ExprValue]s are not converted to Ion before comparison. + * This function should be used for any result involving `BAG` and `MISSING` + * since Ion has no representation for these values. + * + * @param source query source to be tested + * @param expected expected result + * @param session [EvaluationSession] used for evaluation + * @param block function literal with receiver used to plug in custom assertions + */ + protected fun assertEvalExprValue(source: String, + expected: String, + session: EvaluationSession = EvaluationSession.standard(), + compileOptions: CompileOptions = CompileOptions.standard()) { + val parser = SqlParser(ion) + val originalExprNode = parser.parseExprNode(source) + val expectedExprNode = parser.parseExprNode(expected) + + val originalExprValue = eval(originalExprNode, compileOptions, session) + val expectedExprValue = eval(expectedExprNode, compileOptions, session) + assertExprEquals(expectedExprValue, originalExprValue) + } + /**