diff --git a/README.md b/README.md index 53f5a5a..5ce0fe4 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ The test data is partitioned into the following directories: Subdirectories: - `pass/parser` - statements that can parse without error - `fail/parser` - statements that give an error when parsed +- `fail/semantic` - statements that give an error at some stage between parsing and evaluation - `pass/eval` - statements that can be evaluated successfully and return the same result as the expected result - `fail/eval` - statements that throw an error during evaluation diff --git a/partiql-lang-kotlin-omitted-tests.md b/partiql-lang-kotlin-omitted-tests.md new file mode 100644 index 0000000..af9a1ff --- /dev/null +++ b/partiql-lang-kotlin-omitted-tests.md @@ -0,0 +1,71 @@ +The following lists out the tests from `partiql-lang-kotlin`'s parse tests that are not part of `partiql-tests`: + +Tests excluded: +- Custom type cast + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserTest.kt#L631-L644 + - [SqlParserCastTests.kt](https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserCastTests.kt) + - [SqlParserCustomTypeCatalogTests.kt](https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserCustomTypeCatalogTests.kt) +- ORDER BY tests testing defaults for sort spec and null spec + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserTest.kt#L1793-L1888 + - dependent on when parsed ast checking is added or up to the implementation to test via correspondence (see comment here https://github.com/partiql/partiql-tests/pull/11/files#r865515909) + - alternatively, this may be better tested in the evaluator +- Many nested NOT regression test + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserTest.kt#L4239-L4271 + - performance test shouldn't be included in parse tests +- LET clause parsing (not part of spec formally yet) + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserTest.kt#L4066-L4128 + - `expectedAsForLet` https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L431-L443 + - `expectedIdentForAliasLet` https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L473-L485 +- `idIsNotStringLiteral` -- logic tested elsewhere +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L768-L780 +- DML, DDL tests (DML, DDL not defined in spec yet) + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserTest.kt#L2129-L3855 + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserTest.kt#L3857-L3939 + - `unexpectedKeywordUpdateInSelectList` - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L132-L142 + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1291-L1457 + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1980-L2662 +- Stored procedure calls (EXEC) not in spec yet + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L2760-L2846 + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserTest.kt#L4179-L4237 +- semicolon semantic tests + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1860-L1886 + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserTest.kt#L4025-L4064 + +Unsure if should be included: +- `expectedCastAsIntArity`, `expectedCastAsRealArity` -- type supplied with parameter; not sure is parse error +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L172-L204 +- `expectedUnsupportedLiteralsGroupBy` -- not part of the PartiQL spec; SQL92 says group by must use a column name +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L417-L429 + - also for `groupByOrdinal`, `groupByOutOfBoundsOrdinal`, `groupByBadOrdinal`, `groupByStringConstantOrdinal` https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L953-L1007 +- `idIsNotGroupMissing` -- type surrounded by parens +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L782-L794 +- `castToVarCharToTooBigLength`, `castToDecimalToTooBigLength_1`, `castToDecimalToTooBigLength_2`, `castToNumericToTooBigLength_1`, `castToNumericToTooBigLength_2` -- not quite a parse error; more so a semantic error +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L220-L288 + - Similarly for `castNegativeArg` + https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L883-L895 +- `offsetBeforeLimit` -- not part of spec; postgresql allows; kotlin impl+mysql+sqlite don't allow; included as test for now +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1221-L1233 +- date_add/date_diff tests - date_add and date_diff aren't part of specs + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserTest.kt#L957-L1036 + - https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1743-L1858 +- `countExpressionStar` +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1966-L1978 +- `*` and path `*` with other select list items +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1903-L1936 +- `callTrimSpecificationMissingFrom` -- PostgreSQL supports; unsure of utility of creating this fail test + - https://github.com/~~partiql~~/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L683-L696 + - More details provided by Josh in this comment on the ambiguity https://github.com/partiql/partiql-tests/pull/11/files#r865505304 +Repeated tests (tests repeated at some other place): +- `expectedExpectedTypeName` +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L78-L90 +- `likeWrongOrderOfArgs`, `likeMissingEscapeValue`, `likeMissingPattern` +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1487-L1527 +- `nullIsNullIonLiteral`, `idIsStringLiteral` +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1585-L1611 +- `selectWithFromAtAndAs` +https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L1627-L1639 + +To be included: +- DATE and TIME tests from [SqlParserDateTimeTests.kt](https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserDateTimeTests.kt) +and [ParserErrorsTest.kt](https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/errors/ParserErrorsTest.kt#L2848-L3099) +- Operator precedence tests from [SqlParserPrecedenceTest.kt](https://github.com/partiql/partiql-lang-kotlin/blob/34625c68dbcbaf7b8ae60df7a4cf65c60b2a3b79/lang/test/org/partiql/lang/syntax/SqlParserPrecedenceTest.kt) \ No newline at end of file diff --git a/partiql-test-data/fail/parser/primitives/call.ion b/partiql-test-data/fail/parser/primitives/call.ion new file mode 100644 index 0000000..d224ba1 --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/call.ion @@ -0,0 +1,58 @@ +substring::[ + sql92::[ + parse::{ + name: "SUBSTRING sql92 syntax missing left paren", + statement: "SELECT SUBSTRING FROM 'asdf' FOR 1) FROM foo", + assert: { + result: ParseError + }, + }, + parse::{ + name: "SUBSTRING sql92 syntax missing FROM or comma", + statement: "SELECT SUBSTRING('str' 1) FROM foo", + assert: { + result: ParseError + }, + }, + parse::{ + name: "SUBSTRING sql92 syntax without length and missing right paren", + statement: "SELECT SUBSTRING('str' FROM 1 FROM foo", + assert: { + result: ParseError + }, + }, + parse::{ + name: "SUBSTRING sql92 syntax with length missing right paren", + statement: "SELECT SUBSTRING('str' FROM 1 FOR 1 FROM foo", + assert: { + result: ParseError + }, + } + ], + partiql::[ + parse::{ + name: "SUBSTRING without length missing right paren", + statement: "SELECT SUBSTRING('str', 1 FROM foo", + assert: { + result: ParseError + }, + }, + parse::{ + name: "SUBSTRING missing right paren", + statement: "SELECT SUBSTRING('str', 1, 1 FROM foo", + assert: { + result: ParseError + }, + } + ] +] + +trim::[ + parse::{ + name: "TRIM no left paren", + statement: "TRIM ' ')", + assert: { + result: ParseError + }, + }, +] diff --git a/partiql-test-data/fail/parser/primitives/case.ion b/partiql-test-data/fail/parser/primitives/case.ion new file mode 100644 index 0000000..b5bf88d --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/case.ion @@ -0,0 +1,31 @@ +parse::{ + name: "CASE expected WHEN clause", + statement: "CASE name ELSE 1 END", + assert: { + result: ParseError + }, +} + +parse::{ + name: "CASE only with END", + statement: "CASE END", + assert: { + result: ParseError + }, +} + +parse::{ + name: "searched CASE no when with ELSE", + statement: "CASE ELSE 1 END", + assert: { + result: ParseError + }, +} + +parse::{ + name: "simple CASE no when with ELSE", + statement: "CASE name ELSE 1 END", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/primitives/cast.ion b/partiql-test-data/fail/parser/primitives/cast.ion new file mode 100644 index 0000000..58dd9ef --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/cast.ion @@ -0,0 +1,15 @@ +parse::{ + name: "CAST expected left paren", + statement: "CAST 5 as integer", + assert: { + result: ParseError + }, +} + +parse::{ + name: "CAST given keyword as type arg", + statement: "CAST(5 AS SELECT)", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/primitives/container-constructors.ion b/partiql-test-data/fail/parser/primitives/container-constructors.ion new file mode 100644 index 0000000..ec7aef6 --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/container-constructors.ion @@ -0,0 +1,7 @@ +parse::{ + name: "VALUES constructor expect left paren", + statement: "VALUES 1,2)", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/primitives/expressions.ion b/partiql-test-data/fail/parser/primitives/expressions.ion new file mode 100644 index 0000000..4e96541 --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/expressions.ion @@ -0,0 +1,31 @@ +parse::{ + name: "missing right paren", + statement: "(1 + 2", + assert: { + result: ParseError + }, +} + +parse::{ + name: "missing operation between literals", + statement: "5 5", + assert: { + result: ParseError + }, +} + +parse::{ + name: "semicolon inside expression", + statement: "(1;)", + assert: { + result: ParseError + }, +} + +parse::{ + name: "VALUE as top-level expression", + statement: "VALUE 1", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/primitives/operators/at-operator.ion b/partiql-test-data/fail/parser/primitives/operators/at-operator.ion new file mode 100644 index 0000000..2975054 --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/operators/at-operator.ion @@ -0,0 +1,23 @@ +parse::{ + name: "at operator without identifier", + statement: "@", + assert: { + result: ParseError + }, +} + +parse::{ + name: "at operator on non identifier", + statement: "@(a)", + assert: { + result: ParseError + }, +} + +parse::{ + name: "double at operator on identifier", + statement: "@ @a", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/primitives/operators/between-operator.ion b/partiql-test-data/fail/parser/primitives/operators/between-operator.ion new file mode 100644 index 0000000..c03522a --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/operators/between-operator.ion @@ -0,0 +1,7 @@ +parse::{ + name: "BETWEEN operator missing AND", + statement: "5 BETWEEN 1 10", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/primitives/operators/is-operator.ion b/partiql-test-data/fail/parser/primitives/operators/is-operator.ion new file mode 100644 index 0000000..8b891a0 --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/operators/is-operator.ion @@ -0,0 +1,23 @@ +parse::{ + name: "IS NOT operator expects type name", + statement: "NULL IS NOT `null`", + assert: { + result: ParseError + }, +} + +parse::{ + name: "IS operator parens around type name", + statement: "a IS (MISSING)", + assert: { + result: ParseError + }, +} + +parse::{ + name: "IS NOT operator parens around type name", + statement: "a IS NOT (MISSING)", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/primitives/operators/like-operator.ion b/partiql-test-data/fail/parser/primitives/operators/like-operator.ion new file mode 100644 index 0000000..909dbb8 --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/operators/like-operator.ion @@ -0,0 +1,47 @@ +parse::{ + name: "LIKE wrong order of args", + statement: "SELECT a, b FROM data WHERE LIKE a b", + assert: { + result: ParseError + }, +} + +parse::{ + name: "LIKE expected expression following ESCAPE", + statement: "SELECT a, b FROM data WHERE a LIKE b ESCAPE", + assert: { + result: ParseError + }, +} + +parse::{ + name: "LIKE missing rhs argument", + statement: "SELECT a, b FROM data WHERE a LIKE", + assert: { + result: ParseError + }, +} + +parse::{ + name: "LIKE col name LIKE col name ESCAPE typo", + statement: "SELECT a, b FROM data WHERE a LIKE b ECSAPE '\\'", + assert: { + result: ParseError + }, +} + +parse::{ + name: "LIKE ESCAPE before LIKE", + statement: "SELECT a, b FROM data WHERE ESCAPE '\\' a LIKE b", + assert: { + result: ParseError + }, +} + +parse::{ + name: "LIKE ESCAPE as second argument", + statement: "SELECT a, b FROM data WHERE a LIKE ESCAPE '\\' b", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/primitives/path-expression.ion b/partiql-test-data/fail/parser/primitives/path-expression.ion new file mode 100644 index 0000000..23c702c --- /dev/null +++ b/partiql-test-data/fail/parser/primitives/path-expression.ion @@ -0,0 +1,15 @@ +parse::{ + name: "invalid path component too many dots", + statement: "x...a", + assert: { + result: ParseError + }, +} + +parse::{ + name: "invalid path component keyword in path", + statement: '''SELECT foo.id, foo.table FROM `[{id: 1, table: "foos"}]` AS foo''', + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/query/pivot.ion b/partiql-test-data/fail/parser/query/pivot.ion new file mode 100644 index 0000000..dc549d9 --- /dev/null +++ b/partiql-test-data/fail/parser/query/pivot.ion @@ -0,0 +1,7 @@ +parse::{ + name: "PIVOT no AT", + statement: "PIVOT v FROM data", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/query/select/joins.ion b/partiql-test-data/fail/parser/query/select/joins.ion new file mode 100644 index 0000000..50cb978 --- /dev/null +++ b/partiql-test-data/fail/parser/query/select/joins.ion @@ -0,0 +1,64 @@ +parse::{ + name: "INNER CROSS JOIN with extraneous ON condition", + statement: "SELECT * FROM foo INNER CROSS JOIN bar ON true", + assert: { + result: ParseError + }, +} + +parse::{ + name: "LEFT CROSS JOIN with extraneous ON condition", + statement: "SELECT * FROM foo LEFT CROSS JOIN bar ON true", + assert: { + result: ParseError + }, +} + +parse::{ + name: "RIGHT CROSS JOIN with extraneous ON condition", + statement: "SELECT * FROM foo RIGHT CROSS JOIN bar ON true", + assert: { + result: ParseError + }, +} + +// From pg 181 of sql92 spec, must be specified for qualified joins +parse::{ + name: "INNER JOIN missing required ON condition", + statement: "SELECT * FROM foo INNER JOIN bar", + assert: { + result: ParseError + }, +} + +parse::{ + name: "LEFT JOIN missing required ON condition", + statement: "SELECT * FROM foo LEFT JOIN bar", + assert: { + result: ParseError + }, +} + +parse::{ + name: "RIGHT JOIN missing required ON condition", + statement: "SELECT * FROM foo RIGHT JOIN bar", + assert: { + result: ParseError + }, +} + +parse::{ + name: "JOIN missing required ON condition", + statement: "SELECT * FROM foo JOIN bar", + assert: { + result: ParseError + }, +} + +parse::{ + name: "paren INNER JOIN missing required ON clause", + statement: "SELECT * FROM foo INNER JOIN (bar INNER JOIN baz ON true)", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/query/select/limit-offset.ion b/partiql-test-data/fail/parser/query/select/limit-offset.ion new file mode 100644 index 0000000..0b87bfd --- /dev/null +++ b/partiql-test-data/fail/parser/query/select/limit-offset.ion @@ -0,0 +1,31 @@ +parse::{ + name: "LIMIT OFFSET before ORDER BY", + statement: "SELECT a FROM tb LIMIT 10 OFFSET 5 ORDER BY b ASC", + assert: { + result: ParseError + }, +} + +parse::{ + name: "LIMIT OFFSET before ORDER BY with NULLS spec", + statement: "SELECT a FROM tb LIMIT 10 OFFSET 5 ORDER BY b ASC NULLS FIRST", + assert: { + result: ParseError + }, +} + +parse::{ + name: "OFFSET missing argument", + statement: "SELECT a FROM tb OFFSET", + assert: { + result: ParseError + }, +} + +parse::{ + name: "OFFSET unexpected keyword as attribute", + statement: "SELECT a FROM tb OFFSET SELECT", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/query/select/order-by.ion b/partiql-test-data/fail/parser/query/select/order-by.ion new file mode 100644 index 0000000..a2f07dd --- /dev/null +++ b/partiql-test-data/fail/parser/query/select/order-by.ion @@ -0,0 +1,127 @@ +parse::{ + name: "ORDER BY missing BY and sort spec", + statement: "SELECT a FROM tb ORDER", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY missing BY", + statement: "SELECT a FROM tb ORDER foo", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY missing sort spec", + statement: "SELECT a FROM tb ORDER BY", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY multiple attributes in sort spec missing comma", + statement: "SELECT a FROM tb ORDER BY foo bar", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY multiple empty parsed comma list", + statement: "SELECT a FROM tb ORDER BY foo, ,", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY missing attribute name", + statement: "SELECT a FROM tb ORDER BY ASC, bar", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY invalid punctuation", + statement: "SELECT a FROM tb ORDER BY ASC; bar", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY multiple order spec", + statement: "SELECT a FROM tb ORDER BY foo ASC DESC", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY unexpected keyword as attribute", + statement: "SELECT a FROM tb ORDER BY SELECT", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY missing NULLS type", + statement: "SELECT a FROM tb ORDER BY a ASC NULLS", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY missing NULLS keyword with FIRST", + statement: "SELECT a FROM tb ORDER BY a ASC FIRST", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY missing NULLS keyword with LAST", + statement: "SELECT a FROM tb ORDER BY a ASC LAST", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY NULLS spec before ORDER BY", + statement: "SELECT a FROM tb NULLS LAST ORDER BY a ASC", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY unexpected NULLS keyword attribute", + statement: "SELECT a FROM tb ORDER BY a NULLS SELECT", + assert: { + result: ParseError + }, +} + +parse::{ + name: "ORDER BY unexpected keyword", + statement: "SELECT a FROM tb ORDER BY a NULLS FIRST SELECT", + assert: { + result: ParseError + }, +} + +parse::{ + name: "OFFSET before LIMIT", + statement: "SELECT a FROM tb OFFSET 5 LIMIT 10", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/query/select/select.ion b/partiql-test-data/fail/parser/query/select/select.ion new file mode 100644 index 0000000..632eb5c --- /dev/null +++ b/partiql-test-data/fail/parser/query/select/select.ion @@ -0,0 +1,71 @@ +parse::{ + name: "SFW without WHERE expression", + statement: "SELECT * FROM foo WHERE", + assert: { + result: ParseError + }, +} + +parse::{ + name: "SELECT without comma between projection items", + statement: "SELECT a data", + assert: { + result: ParseError + }, +} + +parse::{ + name: "AT before AS alias", + statement: "SELECT ord, val FROM table1 AT ord AS val", + assert: { + result: ParseError + }, +} + +parse::{ + name: "SELECT missing projection", + statement: "SELECT FROM table1", + assert: { + result: ParseError + }, +} + +parse::{ + name: "SELECT list with extra comma", + statement: "SELECT a, FROM {'a' : 1}", + assert: { + result: ParseError + }, +} + +parse::{ + name: "SELECT projection with empty parens", + statement: "SELECT () FROM data", + assert: { + result: ParseError + }, +} + +parse::{ + name: "expected identifier for AS alias", + statement: "SELECT a AS true FROM data", + assert: { + result: ParseError + }, +} + +parse::{ + name: "expected identifier for AT alias", + statement: "SELECT a FROM data AT true", + assert: { + result: ParseError + }, +} + +parse::{ + name: "SELECT query missing FROM", + statement: "SELECT a DATA", + assert: { + result: ParseError + }, +} diff --git a/partiql-test-data/fail/parser/select/select-from-where.ion b/partiql-test-data/fail/parser/select/select-from-where.ion deleted file mode 100644 index 20a3d2e..0000000 --- a/partiql-test-data/fail/parser/select/select-from-where.ion +++ /dev/null @@ -1,7 +0,0 @@ -parse::{ - name: "SFW without WHERE expression", - statement: "SELECT * FROM foo WHERE", - assert: { - result: ParseError - }, -} diff --git a/partiql-test-data/fail/semantic/README.md b/partiql-test-data/fail/semantic/README.md new file mode 100644 index 0000000..62ba3a4 --- /dev/null +++ b/partiql-test-data/fail/semantic/README.md @@ -0,0 +1,4 @@ +Tests in this directory should have statements that error at some stage of semantic passes or type-checking. That is, +at some stage between parsing and evaluation. It's up to the PartiQL implementation to decide at what stage tests in +this directory will give an error. For some implementations, these tests may fail in the parse stage if they strictly +implement SQL standard's EBNF, but some implementations may fail at a different stage. \ No newline at end of file diff --git a/partiql-test-data/fail/semantic/primitives/aggregate-function-call.ion b/partiql-test-data/fail/semantic/primitives/aggregate-function-call.ion new file mode 100644 index 0000000..c31fd70 --- /dev/null +++ b/partiql-test-data/fail/semantic/primitives/aggregate-function-call.ion @@ -0,0 +1,127 @@ +semantic::{ + name: "AVG no args", + statement: "AVG()", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "AVG too many args", + statement: "AVG(a, b)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "AVG with star", + statement: "AVG(*)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "MAX no args", + statement: "MAX()", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "MAX too many args", + statement: "MAX(a, b)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "MAX with star", + statement: "MAX(*)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "MIN no args", + statement: "MIN()", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "MIN too many args", + statement: "MIN(a, b)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "MIN with star", + statement: "MIN(*)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "SUM no args", + statement: "SUM()", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "SUM too many args", + statement: "SUM(a, b)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "SUM with star", + statement: "SUM(*)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "COUNT no args", + statement: "COUNT()", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "COUNT too many args", + statement: "COUNT(a, b)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "COUNT DISTINCT star", + statement: "COUNT(DISTINCT *)", + assert: { + result: SemanticError + }, +} + +semantic::{ + name: "COUNT ALL star", + statement: "COUNT(ALL *)", + assert: { + result: SemanticError + }, +} diff --git a/partiql-test-data/fail/semantic/primitives/call.ion b/partiql-test-data/fail/semantic/primitives/call.ion new file mode 100644 index 0000000..6062ac0 --- /dev/null +++ b/partiql-test-data/fail/semantic/primitives/call.ion @@ -0,0 +1,111 @@ +trim::[ + semantic::{ + name: "TRIM too many arguments", + statement: "TRIM(BOTH ' ' FROM 'test' 2)", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "TRIM with BOTH missing FROM", + statement: "TRIM(BOTH 'test')", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "TRIM and remove without FROM", + statement: "trim(both '' 'test')", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "TRIM without string", + statement: "TRIM(FROM)", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "TRIM no args", + statement: "TRIM()", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "TRIM all args but string", + statement: "TRIM(TRAILING '' FROM)", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "TRIM two args no FROM", + statement: "TRIM(' ' ' 1 ')", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "TRIM spec and FROM no string", + statement: "TRIM(TRAILING FROM)", + assert: { + result: SemanticError + }, + }, +] + +extract::[ + semantic::{ + name: "EXTRACT missing FROM", + statement: "EXTRACT(YEAR b)", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "EXTRACT missing FROM with comma", + statement: "EXTRACT(YEAR, b)", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "EXTRACT missing second argument", + statement: "EXTRACT(YEAR from)", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "EXTRACT missing date time part", + statement: "EXTRACT(FROM b)", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "EXTRACT with only second argument", + statement: "EXTRACT(b)", + assert: { + result: SemanticError + }, + }, + semantic::{ + name: "EXTRACT with only date time part", + statement: "EXTRACT(YEAR)", + assert: { + result: SemanticError + }, + }, +] + +semantic::{ + name: "function call not COUNT with star", + statement: "F(*)", + assert: { + result: SemanticError + }, +} diff --git a/partiql-test-data/fail/semantic/primitives/cast.ion b/partiql-test-data/fail/semantic/primitives/cast.ion new file mode 100644 index 0000000..88d36f8 --- /dev/null +++ b/partiql-test-data/fail/semantic/primitives/cast.ion @@ -0,0 +1,7 @@ +semantic::{ + name: "invalid cast type parameter", + statement: "CAST(5 AS VARCHAR(a))", + assert: { + result: SemanticError + }, +} diff --git a/partiql-test-data/fail/semantic/primitives/operator/is-operator.ion b/partiql-test-data/fail/semantic/primitives/operator/is-operator.ion new file mode 100644 index 0000000..a7a47af --- /dev/null +++ b/partiql-test-data/fail/semantic/primitives/operator/is-operator.ion @@ -0,0 +1,7 @@ +semantic::{ + name: "IS operator expects type name", + statement: "NULL IS `null`", + assert: { + result: SemanticError + }, +} diff --git a/partiql-test-data/pass/parser/primitives/aggregate-function-call.ion b/partiql-test-data/pass/parser/primitives/aggregate-function-call.ion index 7e95c3c..d327a04 100644 --- a/partiql-test-data/pass/parser/primitives/aggregate-function-call.ion +++ b/partiql-test-data/pass/parser/primitives/aggregate-function-call.ion @@ -1,6 +1,6 @@ parse::{ - name: "COUNT aggregate function call", - statement: "COUNT(a)", + name: "SUM aggregate function call", + statement: "SUM(a)", assert: [ { result: ParseOk @@ -18,6 +18,26 @@ parse::{ ] } +parse::{ + name: "SUM ALL aggregate function call", + statement: "SUM(ALL a)", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "COUNT aggregate function call", + statement: "COUNT(a)", + assert: [ + { + result: ParseOk + }, + ] +} + parse::{ name: "COUNT star aggregate function call", statement: "COUNT(*)", @@ -28,6 +48,16 @@ parse::{ ] } +parse::{ + name: "COUNT ALL aggregate function call", + statement: "COUNT(ALL a)", + assert: [ + { + result: ParseOk + }, + ] +} + parse::{ name: "COUNT DISTINCT aggregate function call", statement: "COUNT(DISTINCT a)", @@ -37,3 +67,93 @@ parse::{ }, ] } + +parse::{ + name: "AVG aggregate function call", + statement: "AVG(a)", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "AVG DISTINCT aggregate function call", + statement: "AVG(DISTINCT a)", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "AVG ALL aggregate function call", + statement: "AVG(ALL a)", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "MAX DISTINCT aggregate function call", + statement: "MAX(a)", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "MAX DISTINCT aggregate function call", + statement: "MAX(DISTINCT a)", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "MAX ALL aggregate function call", + statement: "MAX(ALL a)", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "MIN DISTINCT aggregate function call", + statement: "MIN(a)", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "MIN DISTINCT aggregate function call", + statement: "MIN(DISTINCT a)", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "MIN ALL aggregate function call", + statement: "MIN(ALL a)", + assert: [ + { + result: ParseOk + }, + ] +} diff --git a/partiql-test-data/pass/parser/primitives/operators/arithmetic-operators.ion b/partiql-test-data/pass/parser/primitives/operators/arithmetic-operators.ion new file mode 100644 index 0000000..c025920 --- /dev/null +++ b/partiql-test-data/pass/parser/primitives/operators/arithmetic-operators.ion @@ -0,0 +1,59 @@ +parse::{ + name: "plus operator", + statement: "a + b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "minus operator", + statement: "a - b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "times operator", + statement: "a * b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "divide operator", + statement: "a / b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "modulo operator", + statement: "a % b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "arithmetic operators chained together", + statement: "a + b - c * d / e % f", + assert: [ + { + result: ParseOk + }, + ] +} \ No newline at end of file diff --git a/partiql-test-data/pass/parser/primitives/operators/comparison-operators.ion b/partiql-test-data/pass/parser/primitives/operators/comparison-operators.ion new file mode 100644 index 0000000..d017d83 --- /dev/null +++ b/partiql-test-data/pass/parser/primitives/operators/comparison-operators.ion @@ -0,0 +1,79 @@ +parse::{ + name: "eq operator", + statement: "a = b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "neq operator", + statement: "a != b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "neq operator other form", + statement: "a <> b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "gt operator", + statement: "a > b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "lt operator", + statement: "a < b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "gte operator", + statement: "a >= b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "lte operator", + statement: "a <= b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "comparison ops with logical ops", + statement: "a = b AND NOT c != d OR e <> f AND g > h OR NOT NOT i < j AND k >= l OR m <= n", + assert: [ + { + result: ParseOk + }, + ] +} diff --git a/partiql-test-data/pass/parser/primitives/operators/logical-operators.ion b/partiql-test-data/pass/parser/primitives/operators/logical-operators.ion new file mode 100644 index 0000000..6cddb74 --- /dev/null +++ b/partiql-test-data/pass/parser/primitives/operators/logical-operators.ion @@ -0,0 +1,39 @@ +parse::{ + name: "unary NOT literal", + statement: "not 1", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "chained logical AND", + statement: "a AND b AND c", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "chained logical OR", + statement: "a OR b OR c", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "chained logical ops", + statement: "NOT NOT a AND b OR c OR NOT d AND NOT NOT NOT e", + assert: [ + { + result: ParseOk + }, + ] +} diff --git a/partiql-test-data/pass/parser/primitives/operators/string-operators.ion b/partiql-test-data/pass/parser/primitives/operators/string-operators.ion new file mode 100644 index 0000000..8d2b77d --- /dev/null +++ b/partiql-test-data/pass/parser/primitives/operators/string-operators.ion @@ -0,0 +1,19 @@ +parse::{ + name: "concat operator", + statement: "a || b", + assert: [ + { + result: ParseOk + }, + ] +} + +parse::{ + name: "chained concat operator", + statement: "a || b || c || d || e", + assert: [ + { + result: ParseOk + }, + ] +} diff --git a/partiql-test-data/pass/parser/primitives/operators/unary-operators.ion b/partiql-test-data/pass/parser/primitives/operators/unary-operators.ion index 4cb256b..5c41d15 100644 --- a/partiql-test-data/pass/parser/primitives/operators/unary-operators.ion +++ b/partiql-test-data/pass/parser/primitives/operators/unary-operators.ion @@ -57,13 +57,3 @@ parse::{ }, ] } - -parse::{ - name: "unary NOT literal", - statement: "not 1", - assert: [ - { - result: ParseOk - }, - ] -} diff --git a/partiql-test-data/pass/parser/query/select/joins.ion b/partiql-test-data/pass/parser/query/select/joins.ion index d76a8bf..d2549ca 100644 --- a/partiql-test-data/pass/parser/query/select/joins.ion +++ b/partiql-test-data/pass/parser/query/select/joins.ion @@ -29,3 +29,129 @@ parse::{ result: ParseOk } } + +parse::{ + name: "SELECT with RIGHT CROSS JOIN", + statement: "SELECT x FROM stuff s RIGHT CROSS JOIN foo f", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT with FULL OUTER JOIN and ON condition", + statement: "SELECT x FROM stuff s FULL OUTER JOIN foo f ON s = f", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT single join parens test", + statement: "SELECT x FROM (A INNER JOIN B ON A = B)", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT single join multi parens", + statement: "SELECT x FROM (((A INNER JOIN B ON A = B)))", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT two joins natural order parens", + statement: "SELECT x FROM (A INNER JOIN B ON A = B) INNER JOIN C ON B = C", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT two joins specified order parens", + statement: "SELECT x FROM A INNER JOIN (B INNER JOIN C ON B = C) ON A = B", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT three joins specified order parens", + statement: "SELECT x FROM A INNER JOIN (B INNER JOIN (C INNER JOIN D ON C = D) ON B = C) ON A = B", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT literal wrapped in parens", + statement: "SELECT x FROM A INNER JOIN (1) ON true", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT subquery wrapped in parens", + statement: "SELECT x FROM A INNER JOIN (SELECT x FROM 1) ON true", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT with multiple JOINS and implicit CROSS JOIN", + statement: "SELECT x FROM a, b CROSS JOIN c LEFT JOIN d ON e RIGHT OUTER CROSS JOIN f OUTER JOIN g ON h", + assert: { + result: ParseOk + }, +} + +parse::{ + name: "SELECT with multiple JOINS and explicit CROSS JOIN", + statement: "SELECT x FROM a INNER CROSS JOIN b CROSS JOIN c LEFT JOIN d ON e RIGHT OUTER CROSS JOIN f OUTER JOIN g ON h", + assert: { + result: ParseOk + }, +} + +'correlated-joins'::[ + parse::{ + name: "SELECT correlated JOIN", + statement: "SELECT a, b FROM stuff s, @s WHERE f(s)", + assert: { + result: ParseOk + } + }, + parse::{ + name: "SELECT correlated with explicit CROSS JOIN", + statement: "SELECT a, b FROM stuff s CROSS JOIN @s WHERE f(s)", + assert: { + result: ParseOk + } + }, + parse::{ + name: "SELECT correlated with explicit LEFT CROSS JOIN", + statement: "SELECT a, b FROM stuff s LEFT CROSS JOIN @s WHERE f(s)", + assert: { + result: ParseOk + } + }, + parse::{ + name: "SELECT correlated with explicit LEFT OUTER JOIN and ON", + statement: "SELECT a, b FROM stuff s LEFT JOIN @s ON f(s)", + assert: { + result: ParseOk + } + }, + parse::{ + name: "SELECT correlated JOIN with explicit INNER JOIN", + statement: "SELECT a, b FROM stuff s INNER CROSS JOIN @s WHERE f(s)", + assert: { + result: ParseOk + } + }, +] diff --git a/partiql-tests-schema-proposal.md b/partiql-tests-schema-proposal.md index da05124..db03dab 100644 --- a/partiql-tests-schema-proposal.md +++ b/partiql-tests-schema-proposal.md @@ -96,6 +96,26 @@ parse::{ --- +### Semantic Tests + +Currently have a set of `fail` tests that error on the provided statement. These tests should error at some stage +between parsing and evaluation. It's up to the implementation to decide at what stage these statements should error. + +For now, composed of the same properties as the `parse` `fail` tests. The only differences are the outer test annotation +(i.e. `semantic`) and the `assert`'s error (i.e. `SemanticError`). + +``` +semantic::{ + name: , + statement: , + assert: { + result: SemanticError + }, +} +``` + +--- + ### Evaluation Tests Tests whether a given PartiQL statement evaluates to the expected result. For now, composed of these properties: