From a8e9c1cbba38cebe8f5604da5e2a520578a3fe84 Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Mon, 2 Dec 2024 14:53:15 -0800 Subject: [PATCH] Adds tests for DML statements Adds links to removed DML statements Removes ExprRow in favor of ExprRowValue --- partiql-ast/api/partiql-ast.api | 22 +----- .../main/java/org/partiql/ast/AstVisitor.java | 5 -- .../java/org/partiql/ast/expr/ExprRow.java | 52 -------------- .../src/main/kotlin/org/partiql/ast/Ast.kt | 11 ++- partiql-parser/src/main/antlr/DML.g4 | 10 +-- .../parser/internal/PartiQLParserDefault.kt | 6 +- .../parser/internal/ArgumentsProviderBase.kt | 40 +++++++++++ .../parser/internal/DeleteStatementTests.kt | 41 +++++++++++ .../parser/internal/InsertStatementTests.kt | 71 +++++++++++++++++++ .../parser/internal/ParserTestCaseSimple.kt | 4 ++ .../parser/internal/ReplaceStatementTests.kt | 51 +++++++++++++ .../parser/internal/UpdateStatementTests.kt | 49 +++++++++++++ .../parser/internal/UpsertStatementTests.kt | 51 +++++++++++++ .../internal/transforms/RexConverter.kt | 4 +- 14 files changed, 321 insertions(+), 96 deletions(-) delete mode 100644 partiql-ast/src/main/java/org/partiql/ast/expr/ExprRow.java create mode 100644 partiql-parser/src/test/kotlin/org/partiql/parser/internal/ArgumentsProviderBase.kt create mode 100644 partiql-parser/src/test/kotlin/org/partiql/parser/internal/DeleteStatementTests.kt create mode 100644 partiql-parser/src/test/kotlin/org/partiql/parser/internal/InsertStatementTests.kt create mode 100644 partiql-parser/src/test/kotlin/org/partiql/parser/internal/ReplaceStatementTests.kt create mode 100644 partiql-parser/src/test/kotlin/org/partiql/parser/internal/UpdateStatementTests.kt create mode 100644 partiql-parser/src/test/kotlin/org/partiql/parser/internal/UpsertStatementTests.kt diff --git a/partiql-ast/api/partiql-ast.api b/partiql-ast/api/partiql-ast.api index 4c0692c90..5c93a1503 100644 --- a/partiql-ast/api/partiql-ast.api +++ b/partiql-ast/api/partiql-ast.api @@ -43,8 +43,8 @@ public final class org/partiql/ast/Ast { public static final fun exprPathStepField (Lorg/partiql/ast/Identifier;Lorg/partiql/ast/expr/PathStep;)Lorg/partiql/ast/expr/PathStep$Field; public static final fun exprPosition (Lorg/partiql/ast/expr/Expr;Lorg/partiql/ast/expr/Expr;)Lorg/partiql/ast/expr/ExprPosition; public static final fun exprQuerySet (Lorg/partiql/ast/QueryBody;Lorg/partiql/ast/OrderBy;Lorg/partiql/ast/expr/Expr;Lorg/partiql/ast/expr/Expr;)Lorg/partiql/ast/expr/ExprQuerySet; - public static final fun exprRow (ZLjava/util/List;)Lorg/partiql/ast/expr/ExprRow; public static final fun exprRowValue (Ljava/util/List;)Lorg/partiql/ast/expr/ExprRowValue; + public static final fun exprRowValue (Ljava/util/List;Z)Lorg/partiql/ast/expr/ExprRowValue; public static final fun exprSessionAttribute (Lorg/partiql/ast/expr/SessionAttribute;)Lorg/partiql/ast/expr/ExprSessionAttribute; public static final fun exprStruct (Ljava/util/List;)Lorg/partiql/ast/expr/ExprStruct; public static final fun exprStructField (Lorg/partiql/ast/expr/Expr;Lorg/partiql/ast/expr/Expr;)Lorg/partiql/ast/expr/ExprStruct$Field; @@ -326,7 +326,6 @@ public abstract class org/partiql/ast/AstVisitor { public fun visitExprPath (Lorg/partiql/ast/expr/ExprPath;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprPosition (Lorg/partiql/ast/expr/ExprPosition;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprQuerySet (Lorg/partiql/ast/expr/ExprQuerySet;Ljava/lang/Object;)Ljava/lang/Object; - public fun visitExprRow (Lorg/partiql/ast/expr/ExprRow;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprRowValue (Lorg/partiql/ast/expr/ExprRowValue;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprSessionAttribute (Lorg/partiql/ast/expr/ExprSessionAttribute;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprStruct (Lorg/partiql/ast/expr/ExprStruct;Ljava/lang/Object;)Ljava/lang/Object; @@ -2102,25 +2101,6 @@ public class org/partiql/ast/expr/ExprQuerySet$Builder { public fun toString ()Ljava/lang/String; } -public class org/partiql/ast/expr/ExprRow : org/partiql/ast/expr/Expr { - public field isExplicit Z - public final field values Ljava/util/List; - public fun (ZLjava/util/List;)V - public fun accept (Lorg/partiql/ast/AstVisitor;Ljava/lang/Object;)Ljava/lang/Object; - public static fun builder ()Lorg/partiql/ast/expr/ExprRow$Builder; - protected fun canEqual (Ljava/lang/Object;)Z - public fun children ()Ljava/util/Collection; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I -} - -public class org/partiql/ast/expr/ExprRow$Builder { - public fun build ()Lorg/partiql/ast/expr/ExprRow; - public fun isExplicit (Z)Lorg/partiql/ast/expr/ExprRow$Builder; - public fun toString ()Ljava/lang/String; - public fun values (Ljava/util/List;)Lorg/partiql/ast/expr/ExprRow$Builder; -} - public class org/partiql/ast/expr/ExprRowValue : org/partiql/ast/expr/Expr { public field isExplicit Z public final field values Ljava/util/List; diff --git a/partiql-ast/src/main/java/org/partiql/ast/AstVisitor.java b/partiql-ast/src/main/java/org/partiql/ast/AstVisitor.java index 3f9be29f9..04a9d22e7 100644 --- a/partiql-ast/src/main/java/org/partiql/ast/AstVisitor.java +++ b/partiql-ast/src/main/java/org/partiql/ast/AstVisitor.java @@ -24,7 +24,6 @@ import org.partiql.ast.expr.ExprPath; import org.partiql.ast.expr.ExprPosition; import org.partiql.ast.expr.ExprQuerySet; -import org.partiql.ast.expr.ExprRow; import org.partiql.ast.expr.ExprRowValue; import org.partiql.ast.expr.ExprSessionAttribute; import org.partiql.ast.expr.ExprStruct; @@ -262,10 +261,6 @@ public R visitExprArray(ExprArray node, C ctx) { return defaultVisit(node, ctx); } - public R visitExprRow(ExprRow node, C ctx) { - return defaultVisit(node, ctx); - } - public R visitExprBag(ExprBag node, C ctx) { return defaultVisit(node, ctx); } diff --git a/partiql-ast/src/main/java/org/partiql/ast/expr/ExprRow.java b/partiql-ast/src/main/java/org/partiql/ast/expr/ExprRow.java deleted file mode 100644 index 2e796b1f2..000000000 --- a/partiql-ast/src/main/java/org/partiql/ast/expr/ExprRow.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.partiql.ast.expr; - -import lombok.Builder; -import lombok.EqualsAndHashCode; -import org.jetbrains.annotations.NotNull; -import org.partiql.ast.AstNode; -import org.partiql.ast.AstVisitor; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * This represents SQL:1999's row value constructor. EBNF: - * - * <row value constructor> ::= - * <row value constructor element> - * | [ ROW ] <left paren> <row value constructor element list> <right paren> - * | <row subquery> - * - * This specifically models the second variant where the keyword {@code ROW} is used. - */ -@Builder(builderClassName = "Builder") -@EqualsAndHashCode(callSuper = false) -public class ExprRow extends Expr { - // TODO: Equals and hashcode - - /** - * Specifies whether the ROW keyword explicitly precedes the elements in the textual representation. For example, - * {@code ROW (1, 2, 3)} versus {@code (1, 2, 3)}. In the first example, {@code isExplicit} is true. - */ - public boolean isExplicit; - - @NotNull - public final List values; - - public ExprRow(boolean isExplicit, @NotNull List values) { - this.isExplicit = isExplicit; - this.values = values; - } - - @Override - @NotNull - public Collection children() { - return new ArrayList<>(values); - } - - @Override - public R accept(@NotNull AstVisitor visitor, C ctx) { - return visitor.visitExprRow(this, ctx); - } -} diff --git a/partiql-ast/src/main/kotlin/org/partiql/ast/Ast.kt b/partiql-ast/src/main/kotlin/org/partiql/ast/Ast.kt index 349d4dfd7..ff6939b3a 100644 --- a/partiql-ast/src/main/kotlin/org/partiql/ast/Ast.kt +++ b/partiql-ast/src/main/kotlin/org/partiql/ast/Ast.kt @@ -24,7 +24,6 @@ import org.partiql.ast.expr.ExprParameter import org.partiql.ast.expr.ExprPath import org.partiql.ast.expr.ExprPosition import org.partiql.ast.expr.ExprQuerySet -import org.partiql.ast.expr.ExprRow import org.partiql.ast.expr.ExprRowValue import org.partiql.ast.expr.ExprSessionAttribute import org.partiql.ast.expr.ExprStruct @@ -66,11 +65,6 @@ public object Ast { return ExprArray(values) } - @JvmStatic - public fun exprRow(isExplicit: Boolean, values: List): ExprRow { - return ExprRow(isExplicit, values) - } - @JvmStatic public fun exprBag(values: List): ExprBag { return ExprBag(values) @@ -224,6 +218,11 @@ public object Ast { return ExprRowValue(values) } + @JvmStatic + public fun exprRowValue(values: List, isExplicit: Boolean): ExprRowValue { + return ExprRowValue(isExplicit, values) + } + @JvmStatic public fun exprVariant(value: String, encoding: String): ExprVariant { return ExprVariant(value, encoding) diff --git a/partiql-parser/src/main/antlr/DML.g4 b/partiql-parser/src/main/antlr/DML.g4 index 839ba3b6f..47ef022ad 100644 --- a/partiql-parser/src/main/antlr/DML.g4 +++ b/partiql-parser/src/main/antlr/DML.g4 @@ -10,9 +10,9 @@ options { // // // STATEMENTS -// TODO: What is REMOVE? -// TODO: What is the RETURNING clause? -// TODO: What are the DML statements with the preceding FROM? +// TODO: Determine future of REMOVE DML statement: https://github.com/partiql/partiql-lang-kotlin/issues/1668 +// TODO: Determine future of FROM (INSERT, SET, REMOVE) statements: https://github.com/partiql/partiql-lang-kotlin/issues/1669 +// TODO: Implement the RETURNING clause for INSERT/UPDATE. See https://github.com/partiql/partiql-lang-kotlin/issues/1667 // // @@ -49,15 +49,11 @@ replaceStatement: REPLACE INTO tblName=qualifiedName asIdent? insertSource; insertSource : insertFromSubquery - | insertFromConstructor | insertFromDefault ; insertFromSubquery: insertColumnList? expr; -// TODO -insertFromConstructor: insertColumnList?; - insertFromDefault: DEFAULT VALUES; insertColumnList: PAREN_LEFT names+=symbolPrimitive ( COMMA names+=symbolPrimitive )* PAREN_RIGHT; diff --git a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt index f1d163bcb..5364c8e7e 100644 --- a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt +++ b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt @@ -74,7 +74,7 @@ import org.partiql.ast.Ast.exprPathStepElement import org.partiql.ast.Ast.exprPathStepField import org.partiql.ast.Ast.exprPosition import org.partiql.ast.Ast.exprQuerySet -import org.partiql.ast.Ast.exprRow +import org.partiql.ast.Ast.exprRowValue import org.partiql.ast.Ast.exprSessionAttribute import org.partiql.ast.Ast.exprStruct import org.partiql.ast.Ast.exprStructField @@ -1507,11 +1507,11 @@ internal class PartiQLParserDefault : PartiQLParser { override fun visitRowValueConstructor(ctx: GeneratedParser.RowValueConstructorContext) = translate(ctx) { val isExplicit = ctx.ROW() != null val expressions = visitOrEmpty(ctx.expr()) - exprRow(isExplicit, expressions) + exprRowValue(expressions, isExplicit) } override fun visitTableValueConstructor(ctx: GeneratedParser.TableValueConstructorContext) = translate(ctx) { - val rows = visitOrEmpty(ctx.rowValueExpressionList().expr()) + val rows = visitOrEmpty(ctx.rowValueExpressionList().expr()) exprTable(rows) } diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ArgumentsProviderBase.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ArgumentsProviderBase.kt new file mode 100644 index 000000000..12f51ce23 --- /dev/null +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ArgumentsProviderBase.kt @@ -0,0 +1,40 @@ +/* + * Copyright 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.parser.internal + +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import java.util.stream.Stream + +/** + * Reduces some of the boilerplate associated with the style of parameterized testing frequently + * utilized in this package. + * + * Since JUnit5 requires `@JvmStatic` on its `@MethodSource` argument factory methods, this requires all + * of the argument lists to reside in the companion object of a test class. This can be annoying since it + * forces the test to be separated from its tests cases. + * + * Classes that derive from this class can be defined near the `@ParameterizedTest` functions instead. + */ +abstract class ArgumentsProviderBase : ArgumentsProvider { + + abstract fun getParameters(): List + + @Throws(Exception::class) + override fun provideArguments(extensionContext: ExtensionContext): Stream? { + return getParameters().map { Arguments.of(it) }.stream() + } +} diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/DeleteStatementTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/DeleteStatementTests.kt new file mode 100644 index 000000000..c338233e7 --- /dev/null +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/DeleteStatementTests.kt @@ -0,0 +1,41 @@ +package org.partiql.parser.internal + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource + +class DeleteStatementTests { + + @ParameterizedTest + @ArgumentsSource(SuccessTestCases::class) + fun success(tc: PTestDef) { + tc.assert() + } + + @ParameterizedTest + @ArgumentsSource(FailureTestCases::class) + fun failure(tc: PTestDef) { + tc.assert() + } + + object SuccessTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Simplest test" to "DELETE FROM tbl", + "Simplest deletion with condition" to "DELETE FROM tbl WHERE a = 2", + "Namespaced table name" to "DELETE FROM \"CaT1\".schema1.\"TBL_1\"", + "Namespaced table name with condition" to "DELETE FROM \"CaT1\".schema1.\"TBL_1\" WHERE y < x", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = true) } + return m + } + } + + object FailureTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Lack of table name" to "DELETE FROM WHERE a = 2", + "Lack of condition" to "DELETE FROM tbl WHERE", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = false) } + return m + } + } +} diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/InsertStatementTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/InsertStatementTests.kt new file mode 100644 index 000000000..06ece2a09 --- /dev/null +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/InsertStatementTests.kt @@ -0,0 +1,71 @@ +package org.partiql.parser.internal + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource + +class InsertStatementTests { + + @ParameterizedTest + @ArgumentsSource(SuccessTestCases::class) + fun success(tc: PTestDef) { + tc.assert() + } + + @ParameterizedTest + @ArgumentsSource(FailureTestCases::class) + fun failure(tc: PTestDef) { + tc.assert() + } + + object SuccessTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Simplest insert of a row" to "INSERT INTO tbl VALUES (1, 2, 3)", + "Column definitions" to "INSERT INTO tbl (a, b, c) VALUES (1, 2, 3)", + "Multiple rows" to "INSERT INTO tbl VALUES (1, 2, 3), (4, 5, 6), (7, 8, 8)", + "Multiple non-row values" to "INSERT INTO tbl VALUES 1, 2, 3", + "Using DEFAULT VALUES" to "INSERT INTO tbl DEFAULT VALUES", + "Explicit bag" to "INSERT INTO tbl << 1, 2, 3 >>", + "Explicit array" to "INSERT INTO tbl [ 1, 2, 3 ]", + "Namespaced table name with delimited alias" to "INSERT INTO \"CaT1\".schema1.\"TBL_1\" AS \"myt\" VALUES (1, 2, 3)", + "Namespaced table name with regular alias" to "INSERT INTO \"CaT1\".schema1.\"TBL_1\" AS myt VALUES (1, 2, 3)", + "Do nothing on conflict" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT DO NOTHING", + "Do nothing on conflict with target index" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT (a, b) DO NOTHING", + "Do nothing on conflict with named constraint" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT ON CONSTRAINT my_constraint DO NOTHING", + "Do replace excluded on conflict" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT DO REPLACE EXCLUDED", + "Do replace excluded on conflict with condition" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT DO REPLACE EXCLUDED WHERE a < 2", + "Do replace excluded on conflict with target index" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT (a, b) DO REPLACE EXCLUDED", + "Do replace excluded on conflict with target index with condition" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT (a, b) DO REPLACE EXCLUDED WHERE a < 2", + "Do replace excluded on conflict with named constraint" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT ON CONSTRAINT my_constraint DO REPLACE EXCLUDED", + "Do replace excluded on conflict with named constraint with condition" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT ON CONSTRAINT my_constraint DO REPLACE EXCLUDED WHERE a < 2", + "Do update excluded on conflict" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT DO UPDATE EXCLUDED", + "Do update excluded on conflict with condition" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT DO UPDATE EXCLUDED WHERE a < 2", + "Do update excluded on conflict with target index" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT (a, b) DO UPDATE EXCLUDED", + "Do update excluded on conflict with target index with condition" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT (a, b) DO UPDATE EXCLUDED WHERE a < 2", + "Do update excluded on conflict with named constraint" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT ON CONSTRAINT my_constraint DO UPDATE EXCLUDED", + "Do update excluded on conflict with named constraint with condition" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT ON CONSTRAINT my_constraint DO UPDATE EXCLUDED WHERE a < 2", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = true) } + return m + } + } + + object FailureTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Lack of table name" to "INSERT INTO VALUES (1, 2, 3)", + "Trailing comma for VALUES" to "INSERT INTO tbl VALUES (1, 2, 3), (4, 5, 6),", + "Not DEFAULT VALUES" to "INSERT INTO tbl DEFAULT VALUE", + "Bad column definitions" to "INSERT INTO tbl (a.b, b, c) VALUES (1, 2, 3)", + "Empty column definitions" to "INSERT INTO tbl () VALUES (1, 2, 3)", + "No values" to "INSERT INTO tbl (a, b) VALUES", + "Bad alias" to "INSERT INTO tbl AS alias1.alias2 (a, b) VALUES", + "Do something on conflict" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT DO SOMETHING", + "No parenthesis on conflict with target index" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT a, b DO NOTHING", + "Bad reference for index on conflict with target index" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT (a, b.c) DO NOTHING", + "Do nothing on conflict with no named constraint" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT ON CONSTRAINT DO NOTHING", + "Do update excluded on conflict with condition in wrong spot" to "INSERT INTO tbl << 1, 2, 3 >> ON CONFLICT WHERE a < 2 DO UPDATE EXCLUDED", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = false) } + return m + } + } +} diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ParserTestCaseSimple.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ParserTestCaseSimple.kt index 8005d79de..5843db6c8 100644 --- a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ParserTestCaseSimple.kt +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ParserTestCaseSimple.kt @@ -28,4 +28,8 @@ class ParserTestCaseSimple( } } } + + override fun toString(): String { + return name + } } diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ReplaceStatementTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ReplaceStatementTests.kt new file mode 100644 index 000000000..7092c41cf --- /dev/null +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/ReplaceStatementTests.kt @@ -0,0 +1,51 @@ +package org.partiql.parser.internal + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource + +class ReplaceStatementTests { + + @ParameterizedTest + @ArgumentsSource(SuccessTestCases::class) + fun success(tc: PTestDef) { + tc.assert() + } + + @ParameterizedTest + @ArgumentsSource(FailureTestCases::class) + fun failure(tc: PTestDef) { + tc.assert() + } + + object SuccessTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Simplest test" to "REPLACE INTO tbl VALUES (1, 2, 3)", + "Column definitions" to "REPLACE INTO tbl (a, b, c) VALUES (1, 2, 3)", + "Multiple rows" to "REPLACE INTO tbl VALUES (1, 2, 3), (4, 5, 6), (7, 8, 8)", + "Multiple non-row values" to "REPLACE INTO tbl VALUES 1, 2, 3", + "Using DEFAULT VALUES" to "REPLACE INTO tbl DEFAULT VALUES", + "Explicit bag" to "REPLACE INTO tbl << 1, 2, 3 >>", + "Explicit array" to "REPLACE INTO tbl [ 1, 2, 3 ]", + "Namespaced table name with delimited alias" to "REPLACE INTO \"CaT1\".schema1.\"TBL_1\" AS \"myt\" VALUES (1, 2, 3)", + "Namespaced table name with regular alias" to "REPLACE INTO \"CaT1\".schema1.\"TBL_1\" AS myt VALUES (1, 2, 3)", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = true) } + return m + } + } + + object FailureTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Lack of table name" to "REPLACE INTO VALUES (1, 2, 3)", + "Trailing comma for VALUES" to "REPLACE INTO tbl VALUES (1, 2, 3), (4, 5, 6),", + "Not DEFAULT VALUES" to "REPLACE INTO tbl DEFAULT VALUE", + "Bad column definitions" to "REPLACE INTO tbl (a.b, b, c) VALUES (1, 2, 3)", + "Empty column definitions" to "REPLACE INTO tbl () VALUES (1, 2, 3)", + "No values" to "REPLACE INTO tbl (a, b) VALUES", + "Bad alias" to "REPLACE INTO tbl AS alias1.alias2 (a, b) VALUES", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = false) } + return m + } + } +} diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/UpdateStatementTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/UpdateStatementTests.kt new file mode 100644 index 000000000..15051fbe5 --- /dev/null +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/UpdateStatementTests.kt @@ -0,0 +1,49 @@ +package org.partiql.parser.internal + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource + +class UpdateStatementTests { + + @ParameterizedTest + @ArgumentsSource(SuccessTestCases::class) + fun success(tc: PTestDef) { + tc.assert() + } + + @ParameterizedTest + @ArgumentsSource(FailureTestCases::class) + fun failure(tc: PTestDef) { + tc.assert() + } + + object SuccessTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Simplest test" to "UPDATE tbl SET x = 1", + "With multiple sets" to "UPDATE tbl SET x = 1, y = 2, z = 3", + "With multiple sets and condition" to "UPDATE tbl SET x = 1, y = 2, z = 3 WHERE a < 4", + "Namespaced table name" to "UPDATE \"CaT1\".schema1.\"TBL_1\" SET x = 1, y = 2", + "Namespaced table name with condition" to "UPDATE \"CaT1\".schema1.\"TBL_1\" SET x = 1, y = 2 WHERE y < x", + "Multiple complex steps" to "UPDATE tbl SET x.y.z = 1, x['y'].z[0].\"a\".b = 2", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = true) } + return m + } + } + + object FailureTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Lack of table name" to "UPDATE SET x = 1 WHERE a = 2", + "Lack of condition" to "UPDATE tbl SET x = 1 WHERE", + "Lack of SET" to "UPDATE tbl WHERE a = 2", + "Bad step index" to "UPDATE tbl SET x[1 + 2] = 0", + "Bad step key" to "UPDATE tbl SET x['a' || 'b'] = 0", + "Bad step number where root is not a col ref" to "UPDATE tbl SET 1 = 0", + "Bad step string where root is not a col ref" to "UPDATE tbl SET 'a' = 0", + "Bad step struct where root is not a col ref" to "UPDATE tbl SET {'a': 1}.a = 0", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = false) } + return m + } + } +} diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/UpsertStatementTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/UpsertStatementTests.kt new file mode 100644 index 000000000..b2fdf2a87 --- /dev/null +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/UpsertStatementTests.kt @@ -0,0 +1,51 @@ +package org.partiql.parser.internal + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource + +class UpsertStatementTests { + + @ParameterizedTest + @ArgumentsSource(SuccessTestCases::class) + fun success(tc: PTestDef) { + tc.assert() + } + + @ParameterizedTest + @ArgumentsSource(FailureTestCases::class) + fun failure(tc: PTestDef) { + tc.assert() + } + + object SuccessTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Simplest test" to "UPSERT INTO tbl VALUES (1, 2, 3)", + "Column definitions" to "UPSERT INTO tbl (a, b, c) VALUES (1, 2, 3)", + "Multiple rows" to "UPSERT INTO tbl VALUES (1, 2, 3), (4, 5, 6), (7, 8, 8)", + "Multiple non-row values" to "UPSERT INTO tbl VALUES 1, 2, 3", + "Using DEFAULT VALUES" to "UPSERT INTO tbl DEFAULT VALUES", + "Explicit bag" to "UPSERT INTO tbl << 1, 2, 3 >>", + "Explicit array" to "UPSERT INTO tbl [ 1, 2, 3 ]", + "Namespaced table name with delimited alias" to "UPSERT INTO \"CaT1\".schema1.\"TBL_1\" AS \"myt\" VALUES (1, 2, 3)", + "Namespaced table name with regular alias" to "UPSERT INTO \"CaT1\".schema1.\"TBL_1\" AS myt VALUES (1, 2, 3)", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = true) } + return m + } + } + + object FailureTestCases : ArgumentsProviderBase() { + override fun getParameters(): List { + val m = mapOf( + "Lack of table name" to "UPSERT INTO VALUES (1, 2, 3)", + "Trailing comma for VALUES" to "UPSERT INTO tbl VALUES (1, 2, 3), (4, 5, 6),", + "Not DEFAULT VALUES" to "UPSERT INTO tbl DEFAULT VALUE", + "Bad column definitions" to "UPSERT INTO tbl (a.b, b, c) VALUES (1, 2, 3)", + "Empty column definitions" to "UPSERT INTO tbl () VALUES (1, 2, 3)", + "No values" to "UPSERT INTO tbl (a, b) VALUES", + "Bad alias" to "UPSERT INTO tbl AS alias1.alias2 (a, b) VALUES", + ).entries.map { ParserTestCaseSimple(it.key, it.value, isValid = false) } + return m + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt index 9def73e9d..7edd46e8d 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt @@ -45,7 +45,7 @@ import org.partiql.ast.expr.ExprOverlay import org.partiql.ast.expr.ExprPath import org.partiql.ast.expr.ExprPosition import org.partiql.ast.expr.ExprQuerySet -import org.partiql.ast.expr.ExprRow +import org.partiql.ast.expr.ExprRowValue import org.partiql.ast.expr.ExprSessionAttribute import org.partiql.ast.expr.ExprStruct import org.partiql.ast.expr.ExprSubstring @@ -127,7 +127,7 @@ internal object RexConverter { override fun defaultReturn(node: AstNode, context: Env): Rex = throw IllegalArgumentException("unsupported rex $node") - override fun visitExprRow(node: ExprRow, ctx: Env): Rex { + override fun visitExprRowValue(node: ExprRowValue, ctx: Env): Rex { val values = node.values.map { visitExprCoerce(it, ctx) } val op = rexOpCollection(values) return rex(LIST, op) // TODO: We only do this for legacy reasons. This should return a rexOpRow!