From 8a37a9a1c8cb668293303f24e8338891de839c74 Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Thu, 7 Dec 2023 14:26:57 -0800 Subject: [PATCH] Adds support for EXCLUDE in the SqlDialect --- CHANGELOG.md | 1 + .../kotlin/org/partiql/ast/sql/SqlDialect.kt | 35 +++++++ .../org/partiql/ast/sql/SqlDialectTest.kt | 93 +++++++++++++++++++ 3 files changed, 129 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d91a4513..1d7b88f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Thank you to all who have contributed! - **EXPERIMENTAL** Evaluation of `EXCLUDE` in the `EvaluatingCompiler` - This is currently marked as experimental until the RFC is approved https://github.com/partiql/partiql-lang/issues/27 - This will be added to the `PhysicalPlanCompiler` in an upcoming release +- **EXPERIMENTAL**: Adds support for EXCLUDE in the default SqlDialect. ### Changed - StaticTypeInferencer and PlanTyper will not raise an error when an expression is inferred to `NULL` or `unionOf(NULL, MISSING)`. In these cases the StaticTypeInferencer and PlanTyper will still raise the Problem Code `ExpressionAlwaysReturnsNullOrMissing` but the severity of the problem has been changed to warning. In the case an expression always returns `MISSING`, problem code `ExpressionAlwaysReturnsMissing` will be raised, which will have problem severity of error. diff --git a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt index de6e6e17f..676f687dc 100644 --- a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt +++ b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt @@ -1,6 +1,7 @@ package org.partiql.ast.sql import org.partiql.ast.AstNode +import org.partiql.ast.Exclude import org.partiql.ast.Expr import org.partiql.ast.From import org.partiql.ast.GroupBy @@ -82,6 +83,38 @@ public abstract class SqlDialect : AstBaseVisitor() { return head concat r(path) } + override fun visitExclude(node: Exclude, head: SqlBlock): SqlBlock { + var h = head + h = h concat " EXCLUDE " + h = h concat list(start = null, end = null) { node.exprs } + return h + } + + override fun visitExcludeExcludeExpr(node: Exclude.ExcludeExpr, head: SqlBlock): SqlBlock { + var h = head + h = h concat visitIdentifierSymbol(node.root, SqlBlock.Nil) + h = h concat list(delimiter = null, start = null, end = null) { node.steps } + return h + } + + override fun visitExcludeStepExcludeCollectionIndex(node: Exclude.Step.ExcludeCollectionIndex, head: SqlBlock): SqlBlock { + return head concat r("[${node.index}]") + } + + override fun visitExcludeStepExcludeTupleWildcard(node: Exclude.Step.ExcludeTupleWildcard, head: SqlBlock): SqlBlock { + return head concat r(".*") + } + + override fun visitExcludeStepExcludeTupleAttr(node: Exclude.Step.ExcludeTupleAttr, head: SqlBlock): SqlBlock { + var h = head concat r(".") + h = h concat visitIdentifierSymbol(node.symbol, SqlBlock.Nil) + return h + } + + override fun visitExcludeStepExcludeCollectionWildcard(node: Exclude.Step.ExcludeCollectionWildcard, head: SqlBlock): SqlBlock { + return head concat r("[*]") + } + // cannot write path step outside the context of a path as we don't want it to reflow override fun visitPathStep(node: Path.Step, head: SqlBlock) = error("path step cannot be written directly") @@ -550,6 +583,8 @@ public abstract class SqlDialect : AstBaseVisitor() { var h = head // SELECT h = visit(node.select, h) + // EXCLUDE + h = node.exclude?.let { visit(it, h) } ?: h // FROM h = visit(node.from, h concat r(" FROM ")) // LET diff --git a/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt b/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt index e1c2f5035..422d88f5c 100644 --- a/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt +++ b/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt @@ -134,6 +134,11 @@ class SqlDialectTest { @Execution(ExecutionMode.CONCURRENT) fun testSelectClause(case: Case) = case.assert() + @ParameterizedTest(name = "EXCLUDE Clause #{index}") + @MethodSource("excludeClauseCases") + @Execution(ExecutionMode.CONCURRENT) + fun testExcludeClause(case: Case) = case.assert() + @ParameterizedTest(name = "FROM Clause #{index}") @MethodSource("fromClauseCases") @Execution(ExecutionMode.CONCURRENT) @@ -1058,6 +1063,94 @@ class SqlDialectTest { }, ) + @JvmStatic + fun excludeClauseCases() = listOf( + expect("SELECT a EXCLUDE t.a FROM T") { + exprSFW { + select = select("a") + from = fromValue { + expr = v("T") + type = From.Value.Type.SCAN + } + exclude = exclude { + exprs += excludeExcludeExpr { + root = id("t", Identifier.CaseSensitivity.INSENSITIVE) + steps += excludeStepExcludeTupleAttr { + symbol = id("a", Identifier.CaseSensitivity.INSENSITIVE) + } + } + } + } + }, + expect("SELECT a EXCLUDE a.b, c.d, e.f, g.h FROM T") { + exprSFW { + select = select("a") + from = fromValue { + expr = v("T") + type = From.Value.Type.SCAN + } + exclude = exclude { + exprs += excludeExcludeExpr { + root = id("a", Identifier.CaseSensitivity.INSENSITIVE) + steps += insensitiveExcludeTupleAttr("b") + } + exprs += excludeExcludeExpr { + root = id("c", Identifier.CaseSensitivity.INSENSITIVE) + steps += insensitiveExcludeTupleAttr("d") + } + exprs += excludeExcludeExpr { + root = id("e", Identifier.CaseSensitivity.INSENSITIVE) + steps += insensitiveExcludeTupleAttr("f") + } + exprs += excludeExcludeExpr { + root = id("g", Identifier.CaseSensitivity.INSENSITIVE) + steps += insensitiveExcludeTupleAttr("h") + } + } + } + }, + expect("SELECT a EXCLUDE t.a.\"b\".*[*].c, \"s\"[0].d.\"e\"[*].f.* FROM T") { + exprSFW { + select = select("a") + from = fromValue { + expr = v("T") + type = From.Value.Type.SCAN + } + exclude = exclude { + exprs += excludeExcludeExpr { + root = id("t", Identifier.CaseSensitivity.INSENSITIVE) + steps += mutableListOf( + insensitiveExcludeTupleAttr("a"), + sensitiveExcludeTupleAttr("b"), + excludeStepExcludeTupleWildcard(), + excludeStepExcludeCollectionWildcard(), + insensitiveExcludeTupleAttr("c"), + ) + } + exprs += excludeExcludeExpr { + root = id("s", Identifier.CaseSensitivity.SENSITIVE) + steps += mutableListOf( + excludeStepExcludeCollectionIndex(0), + insensitiveExcludeTupleAttr("d"), + sensitiveExcludeTupleAttr("e"), + excludeStepExcludeCollectionWildcard(), + insensitiveExcludeTupleAttr("f"), + excludeStepExcludeTupleWildcard(), + ) + } + } + } + }, + ) + + private fun AstBuilder.insensitiveExcludeTupleAttr(str: String) = excludeStepExcludeTupleAttr { + symbol = id(str, Identifier.CaseSensitivity.INSENSITIVE) + } + + private fun AstBuilder.sensitiveExcludeTupleAttr(str: String) = excludeStepExcludeTupleAttr { + symbol = id(str, Identifier.CaseSensitivity.SENSITIVE) + } + @JvmStatic fun fromClauseCases() = listOf( expect("SELECT a FROM T") {