LibSQL: Make lexer and parser more standard SQL compliant

SQL was standardized before there was consensus on sane language syntax
constructs had evolved. The language is mostly case-insensitive, with
unquoted text converted to upper case. Identifiers can include lower
case characters and other 'special' characters by enclosing the
identifier with double quotes. A double quote is escaped by doubling it.
Likewise, a single quote in a literal string is escaped by doubling it.

All this means that the strategy used in the lexer, where a token's
value is a StringView 'window' on the source string, does not work,
because the value needs to be massaged before being handed to the
parser. Therefore a token now has a String containing its value. Given
the limited lifetime of a token, this is acceptable overhead.

Not doing this means that for example quote removal and double quote
escaping would need to be done in the parser or in AST node
construction, which would spread lexing basically all over the place.
Which would be suboptimal.

There was some impact on the sql utility and SyntaxHighlighter component
which was addressed by storing the token's end position together with
the start position in order to properly highlight it.

Finally, reviewing the tests for parsing numeric literals revealed an
inconsistency in which tokens we accept or reject: `1a` is accepted but
`1e` is rejected. Related to this is the fate of `0x`. Added a FIXME
reminding us to address this.
This commit is contained in:
Jan de Visser 2021-06-21 11:20:09 -04:00 committed by Andreas Kling
parent 4198f7e1af
commit 5c4890411b
Notes: sideshowbarker 2024-07-18 11:36:44 +09:00
9 changed files with 408 additions and 311 deletions

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -48,6 +49,14 @@ ParseResult parse(StringView sql)
TEST_CASE(numeric_literal)
{
// FIXME Right now the "1a" test fails (meaning the parse succeeds).
// This is obviously inconsistent.
// See the FIXME in lexer.cpp, method consume_exponent() about
// solutions.
// EXPECT(parse("1e").is_error());
// EXPECT(parse("1a").is_error());
// EXPECT(parse("0x").is_error());
auto validate = [](StringView sql, double expected_value) {
auto result = parse(sql);
EXPECT(!result.is_error());
@ -61,7 +70,9 @@ TEST_CASE(numeric_literal)
validate("123", 123);
validate("3.14", 3.14);
validate("0xA", 10);
validate("0xff", 255);
validate("0x100", 256);
validate("1e3", 1000);
}
@ -81,14 +92,16 @@ TEST_CASE(string_literal)
EXPECT_EQ(literal.value(), expected_value);
};
validate("''", "''");
validate("'hello friends'", "'hello friends'");
validate("''", "");
validate("'hello friends'", "hello friends");
validate("'hello ''friends'''", "hello 'friends'");
}
TEST_CASE(blob_literal)
{
EXPECT(parse("x'").is_error());
EXPECT(parse("x'unterminated").is_error());
EXPECT(parse("x'NOTHEX'").is_error());
auto validate = [](StringView sql, StringView expected_value) {
auto result = parse(sql);
@ -101,8 +114,8 @@ TEST_CASE(blob_literal)
EXPECT_EQ(literal.value(), expected_value);
};
validate("x''", "x''");
validate("x'hello friends'", "x'hello friends'");
validate("x''", "");
validate("x'DEADC0DE'", "DEADC0DE");
}
TEST_CASE(null_literal)
@ -120,9 +133,10 @@ TEST_CASE(null_literal)
TEST_CASE(column_name)
{
EXPECT(parse(".column").is_error());
EXPECT(parse("table.").is_error());
EXPECT(parse("schema.table.").is_error());
EXPECT(parse(".column_name").is_error());
EXPECT(parse("table_name.").is_error());
EXPECT(parse("schema_name.table_name.").is_error());
EXPECT(parse("\"unterminated").is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_column) {
auto result = parse(sql);
@ -137,9 +151,11 @@ TEST_CASE(column_name)
EXPECT_EQ(column.column_name(), expected_column);
};
validate("column", {}, {}, "column");
validate("table.column", {}, "table", "column");
validate("schema.table.column", "schema", "table", "column");
validate("column_name", {}, {}, "COLUMN_NAME");
validate("table_name.column_name", {}, "TABLE_NAME", "COLUMN_NAME");
validate("schema_name.table_name.column_name", "SCHEMA_NAME", "TABLE_NAME", "COLUMN_NAME");
validate("\"Column_Name\"", {}, {}, "Column_Name");
validate("\"Column\n_Name\"", {}, {}, "Column\n_Name");
}
TEST_CASE(unary_operator)
@ -258,7 +274,7 @@ TEST_CASE(chained_expression)
validate("(15)", 1);
validate("(15, 16)", 2);
validate("(15, 16, column)", 3);
validate("(15, 16, column_name)", 3);
}
TEST_CASE(cast_expression)
@ -273,6 +289,8 @@ TEST_CASE(cast_expression)
auto validate = [](StringView sql, StringView expected_type_name) {
auto result = parse(sql);
if (result.is_error())
outln("{}: {}", sql, result.error());
EXPECT(!result.is_error());
auto expression = result.release_value();
@ -285,9 +303,12 @@ TEST_CASE(cast_expression)
EXPECT_EQ(type_name->name(), expected_type_name);
};
validate("CAST (15 AS int)", "int");
validate("CAST ('NULL' AS null)", "null");
validate("CAST (15 AS varchar(255))", "varchar");
validate("CAST (15 AS int)", "INT");
// FIXME The syntax in the test below fails on both sqlite3 and psql (PostgreSQL).
// Also fails here because null is interpreted as the NULL keyword and not the
// identifier null (which is not a type)
// validate("CAST ('NULL' AS null)", "null");
validate("CAST (15 AS varchar(255))", "VARCHAR");
}
TEST_CASE(case_expression)
@ -344,16 +365,16 @@ TEST_CASE(exists_expression)
EXPECT(parse("EXISTS (").is_error());
EXPECT(parse("EXISTS (SELECT").is_error());
EXPECT(parse("EXISTS (SELECT)").is_error());
EXPECT(parse("EXISTS (SELECT * FROM table").is_error());
EXPECT(parse("EXISTS (SELECT * FROM table_name").is_error());
EXPECT(parse("NOT EXISTS").is_error());
EXPECT(parse("NOT EXISTS (").is_error());
EXPECT(parse("NOT EXISTS (SELECT").is_error());
EXPECT(parse("NOT EXISTS (SELECT)").is_error());
EXPECT(parse("NOT EXISTS (SELECT * FROM table").is_error());
EXPECT(parse("NOT EXISTS (SELECT * FROM table_name").is_error());
EXPECT(parse("(").is_error());
EXPECT(parse("(SELECT").is_error());
EXPECT(parse("(SELECT)").is_error());
EXPECT(parse("(SELECT * FROM table").is_error());
EXPECT(parse("(SELECT * FROM table_name").is_error());
auto validate = [](StringView sql, bool expected_invert_expression) {
auto result = parse(sql);
@ -366,9 +387,9 @@ TEST_CASE(exists_expression)
EXPECT_EQ(exists.invert_expression(), expected_invert_expression);
};
validate("EXISTS (SELECT * FROM table)", false);
validate("NOT EXISTS (SELECT * FROM table)", true);
validate("(SELECT * FROM table)", false);
validate("EXISTS (SELECT * FROM table_name)", false);
validate("NOT EXISTS (SELECT * FROM table_name)", true);
validate("(SELECT * FROM table_name)", false);
}
TEST_CASE(collate_expression)
@ -389,8 +410,8 @@ TEST_CASE(collate_expression)
EXPECT_EQ(collate.collation_name(), expected_collation_name);
};
validate("15 COLLATE fifteen", "fifteen");
validate("(15, 16) COLLATE chain", "chain");
validate("15 COLLATE fifteen", "FIFTEEN");
validate("(15, 16) COLLATE \"chain\"", "chain");
}
TEST_CASE(is_expression)
@ -525,9 +546,9 @@ TEST_CASE(between_expression)
TEST_CASE(in_table_expression)
{
EXPECT(parse("IN").is_error());
EXPECT(parse("IN table").is_error());
EXPECT(parse("IN table_name").is_error());
EXPECT(parse("NOT IN").is_error());
EXPECT(parse("NOT IN table").is_error());
EXPECT(parse("NOT IN table_name").is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, bool expected_invert_expression) {
auto result = parse(sql);
@ -543,11 +564,11 @@ TEST_CASE(in_table_expression)
EXPECT_EQ(in.invert_expression(), expected_invert_expression);
};
validate("15 IN table", {}, "table", false);
validate("15 IN schema.table", "schema", "table", false);
validate("15 IN table_name", {}, "TABLE_NAME", false);
validate("15 IN schema_name.table_name", "SCHEMA_NAME", "TABLE_NAME", false);
validate("15 NOT IN table", {}, "table", true);
validate("15 NOT IN schema.table", "schema", "table", true);
validate("15 NOT IN table_name", {}, "TABLE_NAME", true);
validate("15 NOT IN schema_name.table_name", "SCHEMA_NAME", "TABLE_NAME", true);
}
TEST_CASE(in_chained_expression)
@ -583,9 +604,9 @@ TEST_CASE(in_chained_expression)
TEST_CASE(in_selection_expression)
{
EXPECT(parse("IN (SELECT)").is_error());
EXPECT(parse("IN (SELECT * FROM table, SELECT * FROM table);").is_error());
EXPECT(parse("IN (SELECT * FROM table_name, SELECT * FROM table_name);").is_error());
EXPECT(parse("NOT IN (SELECT)").is_error());
EXPECT(parse("NOT IN (SELECT * FROM table, SELECT * FROM table);").is_error());
EXPECT(parse("NOT IN (SELECT * FROM table_name, SELECT * FROM table_name);").is_error());
auto validate = [](StringView sql, bool expected_invert_expression) {
auto result = parse(sql);
@ -599,8 +620,8 @@ TEST_CASE(in_selection_expression)
EXPECT_EQ(in.invert_expression(), expected_invert_expression);
};
validate("15 IN (SELECT * FROM table)", false);
validate("15 NOT IN (SELECT * FROM table)", true);
validate("15 IN (SELECT * FROM table_name)", false);
validate("15 NOT IN (SELECT * FROM table_name)", true);
}
TEST_CASE(expression_tree_depth_limit)

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -35,8 +36,6 @@ ParseResult parse(StringView sql)
TEST_CASE(create_table)
{
EXPECT(parse("").is_error());
EXPECT(parse("CREATE").is_error());
EXPECT(parse("CREATE TABLE").is_error());
EXPECT(parse("CREATE TABLE test").is_error());
EXPECT(parse("CREATE TABLE test ()").is_error());
@ -56,8 +55,8 @@ TEST_CASE(create_table)
EXPECT(parse("CREATE TABLE test ( column1 varchar(.abc) )").is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(0x) )").is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(0xzzz) )").is_error());
EXPECT(parse("CREATE TABLE test ( column1 int ) AS SELECT * FROM table;").is_error());
EXPECT(parse("CREATE TABLE test AS SELECT * FROM table ( column1 int ) ;").is_error());
EXPECT(parse("CREATE TABLE test ( column1 int ) AS SELECT * FROM table_name;").is_error());
EXPECT(parse("CREATE TABLE test AS SELECT * FROM table_name ( column1 int ) ;").is_error());
struct Column {
StringView name;
@ -67,6 +66,8 @@ TEST_CASE(create_table)
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, Vector<Column> expected_columns, bool expected_is_temporary = false, bool expected_is_error_if_table_exists = true) {
auto result = parse(sql);
if (result.is_error())
outln("{}: {}", sql, result.error());
EXPECT(!result.is_error());
auto statement = result.release_value();
@ -107,22 +108,26 @@ TEST_CASE(create_table)
}
};
validate("CREATE TABLE test ( column1 );", {}, "test", { { "column1", "BLOB" } });
validate("CREATE TABLE schema.test ( column1 );", "schema", "test", { { "column1", "BLOB" } });
validate("CREATE TEMP TABLE test ( column1 );", {}, "test", { { "column1", "BLOB" } }, true, true);
validate("CREATE TEMPORARY TABLE test ( column1 );", {}, "test", { { "column1", "BLOB" } }, true, true);
validate("CREATE TABLE IF NOT EXISTS test ( column1 );", {}, "test", { { "column1", "BLOB" } }, false, false);
validate("CREATE TABLE test ( column1 );", {}, "TEST", { { "COLUMN1", "BLOB" } });
validate("Create Table test ( column1 );", {}, "TEST", { { "COLUMN1", "BLOB" } });
validate(R"(CREATE TABLE "test" ( "column1" );)", {}, "test", { { "column1", "BLOB" } });
validate(R"(CREATE TABLE "te""st" ( "co""lumn1" );)", {}, "te\"st", { { "co\"lumn1", "BLOB" } });
validate("CREATE TABLE schema_name.test ( column1 );", "SCHEMA_NAME", "TEST", { { "COLUMN1", "BLOB" } });
validate("CREATE TABLE \"schema\".test ( column1 );", "schema", "TEST", { { "COLUMN1", "BLOB" } });
validate("CREATE TEMP TABLE test ( column1 );", {}, "TEST", { { "COLUMN1", "BLOB" } }, true, true);
validate("CREATE TEMPORARY TABLE test ( column1 );", {}, "TEST", { { "COLUMN1", "BLOB" } }, true, true);
validate("CREATE TABLE IF NOT EXISTS test ( column1 );", {}, "TEST", { { "COLUMN1", "BLOB" } }, false, false);
validate("CREATE TABLE test AS SELECT * FROM table;", {}, "test", {});
validate("CREATE TABLE test AS SELECT * FROM table_name;", {}, "TEST", {});
validate("CREATE TABLE test ( column1 int );", {}, "test", { { "column1", "int" } });
validate("CREATE TABLE test ( column1 varchar );", {}, "test", { { "column1", "varchar" } });
validate("CREATE TABLE test ( column1 varchar(255) );", {}, "test", { { "column1", "varchar", { 255 } } });
validate("CREATE TABLE test ( column1 varchar(255, 123) );", {}, "test", { { "column1", "varchar", { 255, 123 } } });
validate("CREATE TABLE test ( column1 varchar(255, -123) );", {}, "test", { { "column1", "varchar", { 255, -123 } } });
validate("CREATE TABLE test ( column1 varchar(0xff) );", {}, "test", { { "column1", "varchar", { 255 } } });
validate("CREATE TABLE test ( column1 varchar(3.14) );", {}, "test", { { "column1", "varchar", { 3.14 } } });
validate("CREATE TABLE test ( column1 varchar(1e3) );", {}, "test", { { "column1", "varchar", { 1000 } } });
validate("CREATE TABLE test ( column1 int );", {}, "TEST", { { "COLUMN1", "INT" } });
validate("CREATE TABLE test ( column1 varchar );", {}, "TEST", { { "COLUMN1", "VARCHAR" } });
validate("CREATE TABLE test ( column1 varchar(255) );", {}, "TEST", { { "COLUMN1", "VARCHAR", { 255 } } });
validate("CREATE TABLE test ( column1 varchar(255, 123) );", {}, "TEST", { { "COLUMN1", "VARCHAR", { 255, 123 } } });
validate("CREATE TABLE test ( column1 varchar(255, -123) );", {}, "TEST", { { "COLUMN1", "VARCHAR", { 255, -123 } } });
validate("CREATE TABLE test ( column1 varchar(0xff) );", {}, "TEST", { { "COLUMN1", "VARCHAR", { 255 } } });
validate("CREATE TABLE test ( column1 varchar(3.14) );", {}, "TEST", { { "COLUMN1", "VARCHAR", { 3.14 } } });
validate("CREATE TABLE test ( column1 varchar(1e3) );", {}, "TEST", { { "COLUMN1", "VARCHAR", { 1000 } } });
}
TEST_CASE(alter_table)
@ -130,15 +135,15 @@ TEST_CASE(alter_table)
// This test case only contains common error cases of the AlterTable subclasses.
EXPECT(parse("ALTER").is_error());
EXPECT(parse("ALTER TABLE").is_error());
EXPECT(parse("ALTER TABLE table").is_error());
EXPECT(parse("ALTER TABLE table;").is_error());
EXPECT(parse("ALTER TABLE table_name").is_error());
EXPECT(parse("ALTER TABLE table_name;").is_error());
}
TEST_CASE(alter_table_rename_table)
{
EXPECT(parse("ALTER TABLE table RENAME").is_error());
EXPECT(parse("ALTER TABLE table RENAME TO").is_error());
EXPECT(parse("ALTER TABLE table RENAME TO new_table").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME TO").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME TO new_table").is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_new_table) {
auto result = parse(sql);
@ -153,20 +158,20 @@ TEST_CASE(alter_table_rename_table)
EXPECT_EQ(alter.new_table_name(), expected_new_table);
};
validate("ALTER TABLE table RENAME TO new_table;", {}, "table", "new_table");
validate("ALTER TABLE schema.table RENAME TO new_table;", "schema", "table", "new_table");
validate("ALTER TABLE table_name RENAME TO new_table;", {}, "TABLE_NAME", "NEW_TABLE");
validate("ALTER TABLE schema_name.table_name RENAME TO new_table;", "SCHEMA_NAME", "TABLE_NAME", "NEW_TABLE");
}
TEST_CASE(alter_table_rename_column)
{
EXPECT(parse("ALTER TABLE table RENAME").is_error());
EXPECT(parse("ALTER TABLE table RENAME COLUMN").is_error());
EXPECT(parse("ALTER TABLE table RENAME COLUMN column").is_error());
EXPECT(parse("ALTER TABLE table RENAME COLUMN column TO").is_error());
EXPECT(parse("ALTER TABLE table RENAME COLUMN column TO new_column").is_error());
EXPECT(parse("ALTER TABLE table RENAME column").is_error());
EXPECT(parse("ALTER TABLE table RENAME column TO").is_error());
EXPECT(parse("ALTER TABLE table RENAME column TO new_column").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME COLUMN").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME COLUMN column_name").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME COLUMN column_name TO").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME COLUMN column_name TO new_column").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME column_name").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME column_name TO").is_error());
EXPECT(parse("ALTER TABLE table_name RENAME column_name TO new_column").is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_column, StringView expected_new_column) {
auto result = parse(sql);
@ -182,17 +187,17 @@ TEST_CASE(alter_table_rename_column)
EXPECT_EQ(alter.new_column_name(), expected_new_column);
};
validate("ALTER TABLE table RENAME column TO new_column;", {}, "table", "column", "new_column");
validate("ALTER TABLE table RENAME COLUMN column TO new_column;", {}, "table", "column", "new_column");
validate("ALTER TABLE schema.table RENAME column TO new_column;", "schema", "table", "column", "new_column");
validate("ALTER TABLE schema.table RENAME COLUMN column TO new_column;", "schema", "table", "column", "new_column");
validate("ALTER TABLE table_name RENAME column_name TO new_column;", {}, "TABLE_NAME", "COLUMN_NAME", "NEW_COLUMN");
validate("ALTER TABLE table_name RENAME COLUMN column_name TO new_column;", {}, "TABLE_NAME", "COLUMN_NAME", "NEW_COLUMN");
validate("ALTER TABLE schema_name.table_name RENAME column_name TO new_column;", "SCHEMA_NAME", "TABLE_NAME", "COLUMN_NAME", "NEW_COLUMN");
validate("ALTER TABLE schema_name.table_name RENAME COLUMN column_name TO new_column;", "SCHEMA_NAME", "TABLE_NAME", "COLUMN_NAME", "NEW_COLUMN");
}
TEST_CASE(alter_table_add_column)
{
EXPECT(parse("ALTER TABLE table ADD").is_error());
EXPECT(parse("ALTER TABLE table ADD COLUMN").is_error());
EXPECT(parse("ALTER TABLE table ADD COLUMN column").is_error());
EXPECT(parse("ALTER TABLE table_name ADD").is_error());
EXPECT(parse("ALTER TABLE table_name ADD COLUMN").is_error());
EXPECT(parse("ALTER TABLE table_name ADD COLUMN column_name").is_error());
struct Column {
StringView name;
@ -227,25 +232,25 @@ TEST_CASE(alter_table_add_column)
}
};
validate("ALTER TABLE test ADD column1;", {}, "test", { "column1", "BLOB" });
validate("ALTER TABLE test ADD column1 int;", {}, "test", { "column1", "int" });
validate("ALTER TABLE test ADD column1 varchar;", {}, "test", { "column1", "varchar" });
validate("ALTER TABLE test ADD column1 varchar(255);", {}, "test", { "column1", "varchar", { 255 } });
validate("ALTER TABLE test ADD column1 varchar(255, 123);", {}, "test", { "column1", "varchar", { 255, 123 } });
validate("ALTER TABLE test ADD column1;", {}, "TEST", { "COLUMN1", "BLOB" });
validate("ALTER TABLE test ADD column1 int;", {}, "TEST", { "COLUMN1", "INT" });
validate("ALTER TABLE test ADD column1 varchar;", {}, "TEST", { "COLUMN1", "VARCHAR" });
validate("ALTER TABLE test ADD column1 varchar(255);", {}, "TEST", { "COLUMN1", "VARCHAR", { 255 } });
validate("ALTER TABLE test ADD column1 varchar(255, 123);", {}, "TEST", { "COLUMN1", "VARCHAR", { 255, 123 } });
validate("ALTER TABLE schema.test ADD COLUMN column1;", "schema", "test", { "column1", "BLOB" });
validate("ALTER TABLE schema.test ADD COLUMN column1 int;", "schema", "test", { "column1", "int" });
validate("ALTER TABLE schema.test ADD COLUMN column1 varchar;", "schema", "test", { "column1", "varchar" });
validate("ALTER TABLE schema.test ADD COLUMN column1 varchar(255);", "schema", "test", { "column1", "varchar", { 255 } });
validate("ALTER TABLE schema.test ADD COLUMN column1 varchar(255, 123);", "schema", "test", { "column1", "varchar", { 255, 123 } });
validate("ALTER TABLE schema_name.test ADD COLUMN column1;", "SCHEMA_NAME", "TEST", { "COLUMN1", "BLOB" });
validate("ALTER TABLE schema_name.test ADD COLUMN column1 int;", "SCHEMA_NAME", "TEST", { "COLUMN1", "INT" });
validate("ALTER TABLE schema_name.test ADD COLUMN column1 varchar;", "SCHEMA_NAME", "TEST", { "COLUMN1", "VARCHAR" });
validate("ALTER TABLE schema_name.test ADD COLUMN column1 varchar(255);", "SCHEMA_NAME", "TEST", { "COLUMN1", "VARCHAR", { 255 } });
validate("ALTER TABLE schema_name.test ADD COLUMN column1 varchar(255, 123);", "SCHEMA_NAME", "TEST", { "COLUMN1", "VARCHAR", { 255, 123 } });
}
TEST_CASE(alter_table_drop_column)
{
EXPECT(parse("ALTER TABLE table DROP").is_error());
EXPECT(parse("ALTER TABLE table DROP COLUMN").is_error());
EXPECT(parse("ALTER TABLE table DROP column").is_error());
EXPECT(parse("ALTER TABLE table DROP COLUMN column").is_error());
EXPECT(parse("ALTER TABLE table_name DROP").is_error());
EXPECT(parse("ALTER TABLE table_name DROP COLUMN").is_error());
EXPECT(parse("ALTER TABLE table_name DROP column_name").is_error());
EXPECT(parse("ALTER TABLE table_name DROP COLUMN column_name").is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_column) {
auto result = parse(sql);
@ -260,10 +265,10 @@ TEST_CASE(alter_table_drop_column)
EXPECT_EQ(alter.column_name(), expected_column);
};
validate("ALTER TABLE table DROP column;", {}, "table", "column");
validate("ALTER TABLE table DROP COLUMN column;", {}, "table", "column");
validate("ALTER TABLE schema.table DROP column;", "schema", "table", "column");
validate("ALTER TABLE schema.table DROP COLUMN column;", "schema", "table", "column");
validate("ALTER TABLE table_name DROP column_name;", {}, "TABLE_NAME", "COLUMN_NAME");
validate("ALTER TABLE table_name DROP COLUMN column_name;", {}, "TABLE_NAME", "COLUMN_NAME");
validate("ALTER TABLE schema_name.table_name DROP column_name;", "SCHEMA_NAME", "TABLE_NAME", "COLUMN_NAME");
validate("ALTER TABLE schema_name.table_name DROP COLUMN column_name;", "SCHEMA_NAME", "TABLE_NAME", "COLUMN_NAME");
}
TEST_CASE(drop_table)
@ -286,25 +291,25 @@ TEST_CASE(drop_table)
EXPECT_EQ(table.is_error_if_table_does_not_exist(), expected_is_error_if_table_does_not_exist);
};
validate("DROP TABLE test;", {}, "test");
validate("DROP TABLE schema.test;", "schema", "test");
validate("DROP TABLE IF EXISTS test;", {}, "test", false);
validate("DROP TABLE test;", {}, "TEST");
validate("DROP TABLE schema_name.test;", "SCHEMA_NAME", "TEST");
validate("DROP TABLE IF EXISTS test;", {}, "TEST", false);
}
TEST_CASE(insert)
{
EXPECT(parse("INSERT").is_error());
EXPECT(parse("INSERT INTO").is_error());
EXPECT(parse("INSERT INTO table").is_error());
EXPECT(parse("INSERT INTO table (column)").is_error());
EXPECT(parse("INSERT INTO table (column, ) DEFAULT VALUES;").is_error());
EXPECT(parse("INSERT INTO table VALUES").is_error());
EXPECT(parse("INSERT INTO table VALUES ();").is_error());
EXPECT(parse("INSERT INTO table VALUES (1)").is_error());
EXPECT(parse("INSERT INTO table SELECT").is_error());
EXPECT(parse("INSERT INTO table SELECT * from table").is_error());
EXPECT(parse("INSERT OR INTO table DEFAULT VALUES;").is_error());
EXPECT(parse("INSERT OR foo INTO table DEFAULT VALUES;").is_error());
EXPECT(parse("INSERT INTO table_name").is_error());
EXPECT(parse("INSERT INTO table_name (column_name)").is_error());
EXPECT(parse("INSERT INTO table_name (column_name, ) DEFAULT VALUES;").is_error());
EXPECT(parse("INSERT INTO table_name VALUES").is_error());
EXPECT(parse("INSERT INTO table_name VALUES ();").is_error());
EXPECT(parse("INSERT INTO table_name VALUES (1)").is_error());
EXPECT(parse("INSERT INTO table_name SELECT").is_error());
EXPECT(parse("INSERT INTO table_name SELECT * from table_name").is_error());
EXPECT(parse("INSERT OR INTO table_name DEFAULT VALUES;").is_error());
EXPECT(parse("INSERT OR foo INTO table_name DEFAULT VALUES;").is_error());
auto validate = [](StringView sql, SQL::AST::ConflictResolution expected_conflict_resolution, StringView expected_schema, StringView expected_table, StringView expected_alias, Vector<StringView> expected_column_names, Vector<size_t> expected_chain_sizes, bool expect_select_statement) {
auto result = parse(sql);
@ -343,48 +348,48 @@ TEST_CASE(insert)
EXPECT_EQ(insert.default_values(), expected_chain_sizes.is_empty() && !expect_select_statement);
};
validate("INSERT OR ABORT INTO table DEFAULT VALUES;", SQL::AST::ConflictResolution::Abort, {}, "table", {}, {}, {}, false);
validate("INSERT OR FAIL INTO table DEFAULT VALUES;", SQL::AST::ConflictResolution::Fail, {}, "table", {}, {}, {}, false);
validate("INSERT OR IGNORE INTO table DEFAULT VALUES;", SQL::AST::ConflictResolution::Ignore, {}, "table", {}, {}, {}, false);
validate("INSERT OR REPLACE INTO table DEFAULT VALUES;", SQL::AST::ConflictResolution::Replace, {}, "table", {}, {}, {}, false);
validate("INSERT OR ROLLBACK INTO table DEFAULT VALUES;", SQL::AST::ConflictResolution::Rollback, {}, "table", {}, {}, {}, false);
validate("INSERT OR ABORT INTO table_name DEFAULT VALUES;", SQL::AST::ConflictResolution::Abort, {}, "TABLE_NAME", {}, {}, {}, false);
validate("INSERT OR FAIL INTO table_name DEFAULT VALUES;", SQL::AST::ConflictResolution::Fail, {}, "TABLE_NAME", {}, {}, {}, false);
validate("INSERT OR IGNORE INTO table_name DEFAULT VALUES;", SQL::AST::ConflictResolution::Ignore, {}, "TABLE_NAME", {}, {}, {}, false);
validate("INSERT OR REPLACE INTO table_name DEFAULT VALUES;", SQL::AST::ConflictResolution::Replace, {}, "TABLE_NAME", {}, {}, {}, false);
validate("INSERT OR ROLLBACK INTO table_name DEFAULT VALUES;", SQL::AST::ConflictResolution::Rollback, {}, "TABLE_NAME", {}, {}, {}, false);
auto resolution = SQL::AST::ConflictResolution::Abort;
validate("INSERT INTO table DEFAULT VALUES;", resolution, {}, "table", {}, {}, {}, false);
validate("INSERT INTO schema.table DEFAULT VALUES;", resolution, "schema", "table", {}, {}, {}, false);
validate("INSERT INTO table AS foo DEFAULT VALUES;", resolution, {}, "table", "foo", {}, {}, false);
validate("INSERT INTO table_name DEFAULT VALUES;", resolution, {}, "TABLE_NAME", {}, {}, {}, false);
validate("INSERT INTO schema_name.table_name DEFAULT VALUES;", resolution, "SCHEMA_NAME", "TABLE_NAME", {}, {}, {}, false);
validate("INSERT INTO table_name AS foo DEFAULT VALUES;", resolution, {}, "TABLE_NAME", "FOO", {}, {}, false);
validate("INSERT INTO table (column) DEFAULT VALUES;", resolution, {}, "table", {}, { "column" }, {}, false);
validate("INSERT INTO table (column1, column2) DEFAULT VALUES;", resolution, {}, "table", {}, { "column1", "column2" }, {}, false);
validate("INSERT INTO table_name (column_name) DEFAULT VALUES;", resolution, {}, "TABLE_NAME", {}, { "COLUMN_NAME" }, {}, false);
validate("INSERT INTO table_name (column1, column2) DEFAULT VALUES;", resolution, {}, "TABLE_NAME", {}, { "COLUMN1", "COLUMN2" }, {}, false);
validate("INSERT INTO table VALUES (1);", resolution, {}, "table", {}, {}, { 1 }, false);
validate("INSERT INTO table VALUES (1, 2);", resolution, {}, "table", {}, {}, { 2 }, false);
validate("INSERT INTO table VALUES (1, 2), (3, 4, 5);", resolution, {}, "table", {}, {}, { 2, 3 }, false);
validate("INSERT INTO table_name VALUES (1);", resolution, {}, "TABLE_NAME", {}, {}, { 1 }, false);
validate("INSERT INTO table_name VALUES (1, 2);", resolution, {}, "TABLE_NAME", {}, {}, { 2 }, false);
validate("INSERT INTO table_name VALUES (1, 2), (3, 4, 5);", resolution, {}, "TABLE_NAME", {}, {}, { 2, 3 }, false);
validate("INSERT INTO table SELECT * FROM table;", resolution, {}, "table", {}, {}, {}, true);
validate("INSERT INTO table_name SELECT * FROM table_name;", resolution, {}, "TABLE_NAME", {}, {}, {}, true);
}
TEST_CASE(update)
{
EXPECT(parse("UPDATE").is_error());
EXPECT(parse("UPDATE table").is_error());
EXPECT(parse("UPDATE table SET").is_error());
EXPECT(parse("UPDATE table SET column").is_error());
EXPECT(parse("UPDATE table SET column=4").is_error());
EXPECT(parse("UPDATE table SET column=4, ;").is_error());
EXPECT(parse("UPDATE table SET (column)=4").is_error());
EXPECT(parse("UPDATE table SET (column)=4, ;").is_error());
EXPECT(parse("UPDATE table SET (column, )=4;").is_error());
EXPECT(parse("UPDATE table SET column=4 FROM").is_error());
EXPECT(parse("UPDATE table SET column=4 FROM table").is_error());
EXPECT(parse("UPDATE table SET column=4 WHERE").is_error());
EXPECT(parse("UPDATE table SET column=4 WHERE 1==1").is_error());
EXPECT(parse("UPDATE table SET column=4 RETURNING").is_error());
EXPECT(parse("UPDATE table SET column=4 RETURNING *").is_error());
EXPECT(parse("UPDATE table SET column=4 RETURNING column").is_error());
EXPECT(parse("UPDATE table SET column=4 RETURNING column AS").is_error());
EXPECT(parse("UPDATE OR table SET column=4;").is_error());
EXPECT(parse("UPDATE OR foo table SET column=4;").is_error());
EXPECT(parse("UPDATE table_name").is_error());
EXPECT(parse("UPDATE table_name SET").is_error());
EXPECT(parse("UPDATE table_name SET column_name").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4, ;").is_error());
EXPECT(parse("UPDATE table_name SET (column_name)=4").is_error());
EXPECT(parse("UPDATE table_name SET (column_name)=4, ;").is_error());
EXPECT(parse("UPDATE table_name SET (column_name, )=4;").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 FROM").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 FROM table_name").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE 1==1").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 RETURNING").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 RETURNING *").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 RETURNING column_name").is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 RETURNING column_name AS").is_error());
EXPECT(parse("UPDATE OR table_name SET column_name=4;").is_error());
EXPECT(parse("UPDATE OR foo table_name SET column_name=4;").is_error());
auto validate = [](StringView sql, SQL::AST::ConflictResolution expected_conflict_resolution, StringView expected_schema, StringView expected_table, StringView expected_alias, Vector<Vector<String>> expected_update_columns, bool expect_where_clause, bool expect_returning_clause, Vector<StringView> expected_returned_column_aliases) {
auto result = parse(sql);
@ -433,42 +438,42 @@ TEST_CASE(update)
}
};
Vector<Vector<String>> update_columns { { "column" } };
validate("UPDATE OR ABORT table SET column=1;", SQL::AST::ConflictResolution::Abort, {}, "table", {}, update_columns, false, false, {});
validate("UPDATE OR FAIL table SET column=1;", SQL::AST::ConflictResolution::Fail, {}, "table", {}, update_columns, false, false, {});
validate("UPDATE OR IGNORE table SET column=1;", SQL::AST::ConflictResolution::Ignore, {}, "table", {}, update_columns, false, false, {});
validate("UPDATE OR REPLACE table SET column=1;", SQL::AST::ConflictResolution::Replace, {}, "table", {}, update_columns, false, false, {});
validate("UPDATE OR ROLLBACK table SET column=1;", SQL::AST::ConflictResolution::Rollback, {}, "table", {}, update_columns, false, false, {});
Vector<Vector<String>> update_columns { { "COLUMN_NAME" } };
validate("UPDATE OR ABORT table_name SET column_name=1;", SQL::AST::ConflictResolution::Abort, {}, "TABLE_NAME", {}, update_columns, false, false, {});
validate("UPDATE OR FAIL table_name SET column_name=1;", SQL::AST::ConflictResolution::Fail, {}, "TABLE_NAME", {}, update_columns, false, false, {});
validate("UPDATE OR IGNORE table_name SET column_name=1;", SQL::AST::ConflictResolution::Ignore, {}, "TABLE_NAME", {}, update_columns, false, false, {});
validate("UPDATE OR REPLACE table_name SET column_name=1;", SQL::AST::ConflictResolution::Replace, {}, "TABLE_NAME", {}, update_columns, false, false, {});
validate("UPDATE OR ROLLBACK table_name SET column_name=1;", SQL::AST::ConflictResolution::Rollback, {}, "TABLE_NAME", {}, update_columns, false, false, {});
auto resolution = SQL::AST::ConflictResolution::Abort;
validate("UPDATE table SET column=1;", resolution, {}, "table", {}, update_columns, false, false, {});
validate("UPDATE schema.table SET column=1;", resolution, "schema", "table", {}, update_columns, false, false, {});
validate("UPDATE table AS foo SET column=1;", resolution, {}, "table", "foo", update_columns, false, false, {});
validate("UPDATE table_name SET column_name=1;", resolution, {}, "TABLE_NAME", {}, update_columns, false, false, {});
validate("UPDATE schema_name.table_name SET column_name=1;", resolution, "SCHEMA_NAME", "TABLE_NAME", {}, update_columns, false, false, {});
validate("UPDATE table_name AS foo SET column_name=1;", resolution, {}, "TABLE_NAME", "FOO", update_columns, false, false, {});
validate("UPDATE table SET column=1;", resolution, {}, "table", {}, { { "column" } }, false, false, {});
validate("UPDATE table SET column1=1, column2=2;", resolution, {}, "table", {}, { { "column1" }, { "column2" } }, false, false, {});
validate("UPDATE table SET (column1, column2)=1, column3=2;", resolution, {}, "table", {}, { { "column1", "column2" }, { "column3" } }, false, false, {});
validate("UPDATE table_name SET column_name=1;", resolution, {}, "TABLE_NAME", {}, { { "COLUMN_NAME" } }, false, false, {});
validate("UPDATE table_name SET column1=1, column2=2;", resolution, {}, "TABLE_NAME", {}, { { "COLUMN1" }, { "COLUMN2" } }, false, false, {});
validate("UPDATE table_name SET (column1, column2)=1, column3=2;", resolution, {}, "TABLE_NAME", {}, { { "COLUMN1", "COLUMN2" }, { "COLUMN3" } }, false, false, {});
validate("UPDATE table SET column=1 WHERE 1==1;", resolution, {}, "table", {}, update_columns, true, false, {});
validate("UPDATE table_name SET column_name=1 WHERE 1==1;", resolution, {}, "TABLE_NAME", {}, update_columns, true, false, {});
validate("UPDATE table SET column=1 RETURNING *;", resolution, {}, "table", {}, update_columns, false, true, {});
validate("UPDATE table SET column=1 RETURNING column;", resolution, {}, "table", {}, update_columns, false, true, { {} });
validate("UPDATE table SET column=1 RETURNING column AS alias;", resolution, {}, "table", {}, update_columns, false, true, { "alias" });
validate("UPDATE table SET column=1 RETURNING column1 AS alias1, column2 AS alias2;", resolution, {}, "table", {}, update_columns, false, true, { "alias1", "alias2" });
validate("UPDATE table_name SET column_name=1 RETURNING *;", resolution, {}, "TABLE_NAME", {}, update_columns, false, true, {});
validate("UPDATE table_name SET column_name=1 RETURNING column_name;", resolution, {}, "TABLE_NAME", {}, update_columns, false, true, { {} });
validate("UPDATE table_name SET column_name=1 RETURNING column_name AS alias;", resolution, {}, "TABLE_NAME", {}, update_columns, false, true, { "ALIAS" });
validate("UPDATE table_name SET column_name=1 RETURNING column1 AS alias1, column2 AS alias2;", resolution, {}, "TABLE_NAME", {}, update_columns, false, true, { "ALIAS1", "ALIAS2" });
}
TEST_CASE(delete_)
{
EXPECT(parse("DELETE").is_error());
EXPECT(parse("DELETE FROM").is_error());
EXPECT(parse("DELETE FROM table").is_error());
EXPECT(parse("DELETE FROM table WHERE").is_error());
EXPECT(parse("DELETE FROM table WHERE 15").is_error());
EXPECT(parse("DELETE FROM table WHERE 15 RETURNING").is_error());
EXPECT(parse("DELETE FROM table WHERE 15 RETURNING *").is_error());
EXPECT(parse("DELETE FROM table WHERE 15 RETURNING column").is_error());
EXPECT(parse("DELETE FROM table WHERE 15 RETURNING column AS;").is_error());
EXPECT(parse("DELETE FROM table WHERE (');").is_error());
EXPECT(parse("DELETE FROM table_name").is_error());
EXPECT(parse("DELETE FROM table_name WHERE").is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15").is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15 RETURNING").is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15 RETURNING *").is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15 RETURNING column_name").is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15 RETURNING column_name AS;").is_error());
EXPECT(parse("DELETE FROM table_name WHERE (');").is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_alias, bool expect_where_clause, bool expect_returning_clause, Vector<StringView> expected_returned_column_aliases) {
auto result = parse(sql);
@ -504,14 +509,14 @@ TEST_CASE(delete_)
}
};
validate("DELETE FROM table;", {}, "table", {}, false, false, {});
validate("DELETE FROM schema.table;", "schema", "table", {}, false, false, {});
validate("DELETE FROM schema.table AS alias;", "schema", "table", "alias", false, false, {});
validate("DELETE FROM table WHERE (1 == 1);", {}, "table", {}, true, false, {});
validate("DELETE FROM table RETURNING *;", {}, "table", {}, false, true, {});
validate("DELETE FROM table RETURNING column;", {}, "table", {}, false, true, { {} });
validate("DELETE FROM table RETURNING column AS alias;", {}, "table", {}, false, true, { "alias" });
validate("DELETE FROM table RETURNING column1 AS alias1, column2 AS alias2;", {}, "table", {}, false, true, { "alias1", "alias2" });
validate("DELETE FROM table_name;", {}, "TABLE_NAME", {}, false, false, {});
validate("DELETE FROM schema_name.table_name;", "SCHEMA_NAME", "TABLE_NAME", {}, false, false, {});
validate("DELETE FROM schema_name.table_name AS alias;", "SCHEMA_NAME", "TABLE_NAME", "ALIAS", false, false, {});
validate("DELETE FROM table_name WHERE (1 == 1);", {}, "TABLE_NAME", {}, true, false, {});
validate("DELETE FROM table_name RETURNING *;", {}, "TABLE_NAME", {}, false, true, {});
validate("DELETE FROM table_name RETURNING column_name;", {}, "TABLE_NAME", {}, false, true, { {} });
validate("DELETE FROM table_name RETURNING column_name AS alias;", {}, "TABLE_NAME", {}, false, true, { "ALIAS" });
validate("DELETE FROM table_name RETURNING column1 AS alias1, column2 AS alias2;", {}, "TABLE_NAME", {}, false, true, { "ALIAS1", "ALIAS2" });
}
TEST_CASE(select)
@ -522,32 +527,32 @@ TEST_CASE(select)
EXPECT(parse("SELECT ALL;").is_error());
EXPECT(parse("SELECT *").is_error());
EXPECT(parse("SELECT * FROM;").is_error());
EXPECT(parse("SELECT table. FROM table;").is_error());
EXPECT(parse("SELECT column AS FROM table;").is_error());
EXPECT(parse("SELECT table_name. FROM table_name;").is_error());
EXPECT(parse("SELECT column_name AS FROM table_name;").is_error());
EXPECT(parse("SELECT * FROM (").is_error());
EXPECT(parse("SELECT * FROM ()").is_error());
EXPECT(parse("SELECT * FROM ();").is_error());
EXPECT(parse("SELECT * FROM (table1)").is_error());
EXPECT(parse("SELECT * FROM (table1, )").is_error());
EXPECT(parse("SELECT * FROM (table1, table2)").is_error());
EXPECT(parse("SELECT * FROM table").is_error());
EXPECT(parse("SELECT * FROM table AS;").is_error());
EXPECT(parse("SELECT * FROM table WHERE;").is_error());
EXPECT(parse("SELECT * FROM table WHERE 1 ==1").is_error());
EXPECT(parse("SELECT * FROM table GROUP;").is_error());
EXPECT(parse("SELECT * FROM table GROUP BY;").is_error());
EXPECT(parse("SELECT * FROM table GROUP BY column").is_error());
EXPECT(parse("SELECT * FROM table ORDER:").is_error());
EXPECT(parse("SELECT * FROM table ORDER BY column").is_error());
EXPECT(parse("SELECT * FROM table ORDER BY column COLLATE:").is_error());
EXPECT(parse("SELECT * FROM table ORDER BY column COLLATE collation").is_error());
EXPECT(parse("SELECT * FROM table ORDER BY column NULLS;").is_error());
EXPECT(parse("SELECT * FROM table ORDER BY column NULLS SECOND;").is_error());
EXPECT(parse("SELECT * FROM table LIMIT;").is_error());
EXPECT(parse("SELECT * FROM table LIMIT 12").is_error());
EXPECT(parse("SELECT * FROM table LIMIT 12 OFFSET;").is_error());
EXPECT(parse("SELECT * FROM table LIMIT 12 OFFSET 15").is_error());
EXPECT(parse("SELECT * FROM table LIMIT 15, 16;").is_error());
EXPECT(parse("SELECT * FROM (table_name1)").is_error());
EXPECT(parse("SELECT * FROM (table_name1, )").is_error());
EXPECT(parse("SELECT * FROM (table_name1, table_name2)").is_error());
EXPECT(parse("SELECT * FROM table_name").is_error());
EXPECT(parse("SELECT * FROM table_name AS;").is_error());
EXPECT(parse("SELECT * FROM table_name WHERE;").is_error());
EXPECT(parse("SELECT * FROM table_name WHERE 1 ==1").is_error());
EXPECT(parse("SELECT * FROM table_name GROUP;").is_error());
EXPECT(parse("SELECT * FROM table_name GROUP BY;").is_error());
EXPECT(parse("SELECT * FROM table_name GROUP BY column_name").is_error());
EXPECT(parse("SELECT * FROM table_name ORDER:").is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name").is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name COLLATE:").is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name COLLATE collation").is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name NULLS;").is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name NULLS SECOND;").is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT;").is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT 12").is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT 12 OFFSET;").is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT 12 OFFSET 15").is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT 15, 16;").is_error());
struct Type {
SQL::AST::ResultType type;
@ -649,51 +654,51 @@ TEST_CASE(select)
};
Vector<Type> all { { SQL::AST::ResultType::All } };
Vector<From> from { { {}, "table", {} } };
Vector<From> from { { {}, "TABLE_NAME", {} } };
validate("SELECT * FROM table;", { { SQL::AST::ResultType::All } }, from, false, 0, false, {}, false, false);
validate("SELECT table.* FROM table;", { { SQL::AST::ResultType::Table, "table" } }, from, false, 0, false, {}, false, false);
validate("SELECT column AS alias FROM table;", { { SQL::AST::ResultType::Expression, "alias" } }, from, false, 0, false, {}, false, false);
validate("SELECT table.column AS alias FROM table;", { { SQL::AST::ResultType::Expression, "alias" } }, from, false, 0, false, {}, false, false);
validate("SELECT schema.table.column AS alias FROM table;", { { SQL::AST::ResultType::Expression, "alias" } }, from, false, 0, false, {}, false, false);
validate("SELECT column AS alias, *, table.* FROM table;", { { SQL::AST::ResultType::Expression, "alias" }, { SQL::AST::ResultType::All }, { SQL::AST::ResultType::Table, "table" } }, from, false, 0, false, {}, false, false);
validate("SELECT * FROM table_name;", { { SQL::AST::ResultType::All } }, from, false, 0, false, {}, false, false);
validate("SELECT table_name.* FROM table_name;", { { SQL::AST::ResultType::Table, "TABLE_NAME" } }, from, false, 0, false, {}, false, false);
validate("SELECT column_name AS alias FROM table_name;", { { SQL::AST::ResultType::Expression, "ALIAS" } }, from, false, 0, false, {}, false, false);
validate("SELECT table_name.column_name AS alias FROM table_name;", { { SQL::AST::ResultType::Expression, "ALIAS" } }, from, false, 0, false, {}, false, false);
validate("SELECT schema_name.table_name.column_name AS alias FROM table_name;", { { SQL::AST::ResultType::Expression, "ALIAS" } }, from, false, 0, false, {}, false, false);
validate("SELECT column_name AS alias, *, table_name.* FROM table_name;", { { SQL::AST::ResultType::Expression, "ALIAS" }, { SQL::AST::ResultType::All }, { SQL::AST::ResultType::Table, "TABLE_NAME" } }, from, false, 0, false, {}, false, false);
validate("SELECT * FROM table;", all, { { {}, "table", {} } }, false, 0, false, {}, false, false);
validate("SELECT * FROM schema.table;", all, { { "schema", "table", {} } }, false, 0, false, {}, false, false);
validate("SELECT * FROM schema.table AS alias;", all, { { "schema", "table", "alias" } }, false, 0, false, {}, false, false);
validate("SELECT * FROM schema.table AS alias, table2, table3 AS table4;", all, { { "schema", "table", "alias" }, { {}, "table2", {} }, { {}, "table3", "table4" } }, false, 0, false, {}, false, false);
validate("SELECT * FROM table_name;", all, { { {}, "TABLE_NAME", {} } }, false, 0, false, {}, false, false);
validate("SELECT * FROM schema_name.table_name;", all, { { "SCHEMA_NAME", "TABLE_NAME", {} } }, false, 0, false, {}, false, false);
validate("SELECT * FROM schema_name.table_name AS alias;", all, { { "SCHEMA_NAME", "TABLE_NAME", "ALIAS" } }, false, 0, false, {}, false, false);
validate("SELECT * FROM schema_name.table_name AS alias, table_name2, table_name3 AS table_name4;", all, { { "SCHEMA_NAME", "TABLE_NAME", "ALIAS" }, { {}, "TABLE_NAME2", {} }, { {}, "TABLE_NAME3", "TABLE_NAME4" } }, false, 0, false, {}, false, false);
validate("SELECT * FROM table WHERE column IS NOT NULL;", all, from, true, 0, false, {}, false, false);
validate("SELECT * FROM table_name WHERE column_name IS NOT NULL;", all, from, true, 0, false, {}, false, false);
validate("SELECT * FROM table GROUP BY column;", all, from, false, 1, false, {}, false, false);
validate("SELECT * FROM table GROUP BY column1, column2, column3;", all, from, false, 3, false, {}, false, false);
validate("SELECT * FROM table GROUP BY column HAVING 'abc';", all, from, false, 1, true, {}, false, false);
validate("SELECT * FROM table_name GROUP BY column_name;", all, from, false, 1, false, {}, false, false);
validate("SELECT * FROM table_name GROUP BY column1, column2, column3;", all, from, false, 3, false, {}, false, false);
validate("SELECT * FROM table_name GROUP BY column_name HAVING 'abc';", all, from, false, 1, true, {}, false, false);
validate("SELECT * FROM table ORDER BY column;", all, from, false, 0, false, { { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::First } }, false, false);
validate("SELECT * FROM table ORDER BY column COLLATE collation;", all, from, false, 0, false, { { "collation", SQL::AST::Order::Ascending, SQL::AST::Nulls::First } }, false, false);
validate("SELECT * FROM table ORDER BY column ASC;", all, from, false, 0, false, { { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::First } }, false, false);
validate("SELECT * FROM table ORDER BY column DESC;", all, from, false, 0, false, { { {}, SQL::AST::Order::Descending, SQL::AST::Nulls::Last } }, false, false);
validate("SELECT * FROM table ORDER BY column ASC NULLS LAST;", all, from, false, 0, false, { { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::Last } }, false, false);
validate("SELECT * FROM table ORDER BY column DESC NULLS FIRST;", all, from, false, 0, false, { { {}, SQL::AST::Order::Descending, SQL::AST::Nulls::First } }, false, false);
validate("SELECT * FROM table ORDER BY column1, column2 DESC, column3 NULLS LAST;", all, from, false, 0, false, { { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::First }, { {}, SQL::AST::Order::Descending, SQL::AST::Nulls::Last }, { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::Last } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name;", all, from, false, 0, false, { { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::First } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name COLLATE collation;", all, from, false, 0, false, { { "COLLATION", SQL::AST::Order::Ascending, SQL::AST::Nulls::First } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name ASC;", all, from, false, 0, false, { { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::First } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name DESC;", all, from, false, 0, false, { { {}, SQL::AST::Order::Descending, SQL::AST::Nulls::Last } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name ASC NULLS LAST;", all, from, false, 0, false, { { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::Last } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name DESC NULLS FIRST;", all, from, false, 0, false, { { {}, SQL::AST::Order::Descending, SQL::AST::Nulls::First } }, false, false);
validate("SELECT * FROM table_name ORDER BY column1, column2 DESC, column3 NULLS LAST;", all, from, false, 0, false, { { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::First }, { {}, SQL::AST::Order::Descending, SQL::AST::Nulls::Last }, { {}, SQL::AST::Order::Ascending, SQL::AST::Nulls::Last } }, false, false);
validate("SELECT * FROM table LIMIT 15;", all, from, false, 0, false, {}, true, false);
validate("SELECT * FROM table LIMIT 15 OFFSET 16;", all, from, false, 0, false, {}, true, true);
validate("SELECT * FROM table_name LIMIT 15;", all, from, false, 0, false, {}, true, false);
validate("SELECT * FROM table_name LIMIT 15 OFFSET 16;", all, from, false, 0, false, {}, true, true);
}
TEST_CASE(common_table_expression)
{
EXPECT(parse("WITH").is_error());
EXPECT(parse("WITH;").is_error());
EXPECT(parse("WITH DELETE FROM table;").is_error());
EXPECT(parse("WITH table DELETE FROM table;").is_error());
EXPECT(parse("WITH table AS DELETE FROM table;").is_error());
EXPECT(parse("WITH RECURSIVE table DELETE FROM table;").is_error());
EXPECT(parse("WITH RECURSIVE table AS DELETE FROM table;").is_error());
EXPECT(parse("WITH DELETE FROM table_name;").is_error());
EXPECT(parse("WITH table_name DELETE FROM table_name;").is_error());
EXPECT(parse("WITH table_name AS DELETE FROM table_name;").is_error());
EXPECT(parse("WITH RECURSIVE table_name DELETE FROM table_name;").is_error());
EXPECT(parse("WITH RECURSIVE table_name AS DELETE FROM table_name;").is_error());
// Below are otherwise valid common-table-expressions, but attached to statements which do not allow them.
EXPECT(parse("WITH table AS (SELECT * AS TABLE) CREATE TABLE test ( column1 );").is_error());
EXPECT(parse("WITH table AS (SELECT * FROM table) DROP TABLE test;").is_error());
EXPECT(parse("WITH table_name AS (SELECT * AS TABLE) CREATE TABLE test ( column1 );").is_error());
EXPECT(parse("WITH table_name AS (SELECT * FROM table_name) DROP TABLE test;").is_error());
struct SelectedTableList {
struct SelectedTable {
@ -733,15 +738,15 @@ TEST_CASE(common_table_expression)
}
};
validate("WITH table AS (SELECT * FROM table) DELETE FROM table;", { false, { { "table" } } });
validate("WITH table (column) AS (SELECT * FROM table) DELETE FROM table;", { false, { { "table", { "column" } } } });
validate("WITH table (column1, column2) AS (SELECT * FROM table) DELETE FROM table;", { false, { { "table", { "column1", "column2" } } } });
validate("WITH RECURSIVE table AS (SELECT * FROM table) DELETE FROM table;", { true, { { "table", {} } } });
validate("WITH table_name AS (SELECT * FROM table_name) DELETE FROM table_name;", { false, { { "TABLE_NAME" } } });
validate("WITH table_name (column_name) AS (SELECT * FROM table_name) DELETE FROM table_name;", { false, { { "TABLE_NAME", { "COLUMN_NAME" } } } });
validate("WITH table_name (column1, column2) AS (SELECT * FROM table_name) DELETE FROM table_name;", { false, { { "TABLE_NAME", { "COLUMN1", "COLUMN2" } } } });
validate("WITH RECURSIVE table_name AS (SELECT * FROM table_name) DELETE FROM table_name;", { true, { { "TABLE_NAME", {} } } });
}
TEST_CASE(nested_subquery_limit)
{
auto subquery = String::formatted("{:(^{}}table{:)^{}}", "", SQL::AST::Limits::maximum_subquery_depth - 1, "", SQL::AST::Limits::maximum_subquery_depth - 1);
auto subquery = String::formatted("{:(^{}}table_name{:)^{}}", "", SQL::AST::Limits::maximum_subquery_depth - 1, "", SQL::AST::Limits::maximum_subquery_depth - 1);
EXPECT(!parse(String::formatted("SELECT * FROM {};", subquery)).is_error());
EXPECT(parse(String::formatted("SELECT * FROM ({});", subquery)).is_error());
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -48,31 +49,36 @@ Token Lexer::next()
{
bool found_invalid_comment = consume_whitespace_and_comments();
size_t value_start = m_position;
size_t value_start_line_number = m_line_number;
size_t value_start_column_number = m_line_column;
auto token_type = TokenType::Invalid;
StringBuilder current_token;
if (is_eof()) {
token_type = found_invalid_comment ? TokenType::Invalid : TokenType::Eof;
} else if (is_numeric_literal_start()) {
token_type = TokenType::NumericLiteral;
if (!consume_numeric_literal())
if (!consume_numeric_literal(current_token))
token_type = TokenType::Invalid;
} else if (is_string_literal_start()) {
token_type = TokenType::StringLiteral;
if (!consume_string_literal())
if (!consume_string_literal(current_token))
token_type = TokenType::Invalid;
} else if (is_quoted_identifier_start()) {
token_type = TokenType::Identifier;
if (!consume_quoted_identifier(current_token))
token_type = TokenType::Invalid;
} else if (is_blob_literal_start()) {
token_type = TokenType::BlobLiteral;
if (!consume_blob_literal())
if (!consume_blob_literal(current_token))
token_type = TokenType::Invalid;
} else if (is_identifier_start()) {
do {
current_token.append((char)toupper(m_current_char));
consume();
} while (is_identifier_middle());
if (auto it = s_keywords.find(m_source.substring_view(value_start - 1, m_position - value_start)); it != s_keywords.end()) {
if (auto it = s_keywords.find(current_token.string_view()); it != s_keywords.end()) {
token_type = it->value;
} else {
token_type = TokenType::Identifier;
@ -83,8 +89,8 @@ Token Lexer::next()
if (auto it = s_two_char_tokens.find(m_source.substring_view(m_position - 1, 2)); it != s_two_char_tokens.end()) {
found_two_char_token = true;
token_type = it->value;
consume();
consume();
consume(&current_token);
consume(&current_token);
}
}
@ -93,30 +99,32 @@ Token Lexer::next()
if (auto it = s_one_char_tokens.find(m_current_char); it != s_one_char_tokens.end()) {
found_one_char_token = true;
token_type = it->value;
consume();
consume(&current_token);
}
}
if (!found_two_char_token && !found_one_char_token) {
token_type = TokenType::Invalid;
consume();
consume(&current_token);
}
}
Token token(token_type, m_source.substring_view(value_start - 1, m_position - value_start), value_start_line_number, value_start_column_number);
Token token(token_type, current_token.build(),
{ value_start_line_number, value_start_column_number },
{ m_line_number, m_line_column });
if constexpr (SQL_DEBUG) {
dbgln("------------------------------");
dbgln("Token: {}", token.name());
dbgln("Value: {}", token.value());
dbgln("Line: {}, Column: {}", token.line_number(), token.line_column());
dbgln("Line: {}, Column: {}", token.start_position().line, token.start_position().column);
dbgln("------------------------------");
}
return token;
}
void Lexer::consume()
void Lexer::consume(StringBuilder* current_token)
{
auto did_reach_eof = [this] {
if (m_position != m_source.length())
@ -128,6 +136,9 @@ void Lexer::consume()
return true;
};
if (current_token)
current_token->append(m_current_char);
if (m_position > m_source.length())
return;
@ -177,91 +188,148 @@ bool Lexer::consume_whitespace_and_comments()
return found_invalid_comment;
}
bool Lexer::consume_numeric_literal()
bool Lexer::consume_numeric_literal(StringBuilder& current_token)
{
// https://sqlite.org/syntax/numeric-literal.html
bool is_valid_numeric_literal = true;
if (m_current_char == '0') {
consume();
consume(&current_token);
if (m_current_char == '.') {
consume();
consume(&current_token);
while (isdigit(m_current_char))
consume();
consume(&current_token);
if (m_current_char == 'e' || m_current_char == 'E')
is_valid_numeric_literal = consume_exponent();
is_valid_numeric_literal = consume_exponent(current_token);
} else if (m_current_char == 'e' || m_current_char == 'E') {
is_valid_numeric_literal = consume_exponent();
is_valid_numeric_literal = consume_exponent(current_token);
} else if (m_current_char == 'x' || m_current_char == 'X') {
is_valid_numeric_literal = consume_hexadecimal_number();
is_valid_numeric_literal = consume_hexadecimal_number(current_token);
} else if (isdigit(m_current_char)) {
do {
consume();
consume(&current_token);
} while (isdigit(m_current_char));
}
} else {
do {
consume();
consume(&current_token);
} while (isdigit(m_current_char));
if (m_current_char == '.') {
consume();
consume(&current_token);
while (isdigit(m_current_char))
consume();
consume(&current_token);
}
if (m_current_char == 'e' || m_current_char == 'E')
is_valid_numeric_literal = consume_exponent();
is_valid_numeric_literal = consume_exponent(current_token);
}
return is_valid_numeric_literal;
}
bool Lexer::consume_string_literal()
bool Lexer::consume_string_literal(StringBuilder& current_token)
{
// https://sqlite.org/lang_expr.html - See "3. Literal Values (Constants)"
bool is_valid_string_literal = true;
// Skip the opening single quote:
consume();
while (!is_eof() && !is_string_literal_end())
consume();
while (!is_eof() && !is_string_literal_end()) {
// If both the current character and the next one are single quotes,
// consume one single quote into the current token, and drop the
// other one on the floor:
if (match('\'', '\''))
consume();
consume(&current_token);
}
if (is_eof())
is_valid_string_literal = false;
// Drop the closing quote on the floor:
consume();
return is_valid_string_literal;
}
bool Lexer::consume_blob_literal()
bool Lexer::consume_quoted_identifier(StringBuilder& current_token)
{
// https://sqlite.org/lang_expr.html - See "3. Literal Values (Constants)"
// I have not found a reference to the syntax for identifiers in the
// SQLite documentation, but PostgreSQL has this:
// https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
bool is_valid_identifier = true;
// Skip the opening double quote:
consume();
return consume_string_literal();
while (!is_eof() && !is_quoted_identifier_end()) {
// If both the current character and the next one are double quotes,
// consume one single quote into the current token, and drop the
// other one on the floor:
if (match('"', '"'))
consume();
consume(&current_token);
}
if (is_eof())
is_valid_identifier = false;
// Drop the closing double quote on the floor:
consume();
return is_valid_identifier;
}
bool Lexer::consume_exponent()
bool Lexer::consume_blob_literal(StringBuilder& current_token)
{
// https://sqlite.org/lang_expr.html - See "3. Literal Values (Constants)"
// Skip starting 'X'/'x' character:
consume();
if (m_current_char == '-' || m_current_char == '+')
consume();
if (!isdigit(m_current_char))
if (!consume_string_literal(current_token))
return false;
while (isdigit(m_current_char)) {
consume();
for (auto ix = 0u; ix < current_token.length(); ix++) {
if (!isxdigit(current_token.string_view()[ix]))
return false;
}
return true;
}
bool Lexer::consume_hexadecimal_number()
bool Lexer::consume_exponent(StringBuilder& current_token)
{
consume();
consume(&current_token);
if (m_current_char == '-' || m_current_char == '+')
consume(&current_token);
if (!isdigit(m_current_char))
return false;
// FIXME This code results in the string "1e" being rejected as a
// malformed numeric literal. We do however accept "1a" which
// is inconsistent. We have to decide what we want to do:
// - Be like `SQLite` and reject both "1a" and "1e" because we
// require a space between the two tokens. This is pretty invasive;
// we would have to decide where all spaces are required and fix
// the lexer accordingly.
// - Be like `PostgreSQL` and accept both "1e" and "1a" as two
// separate tokens, and accept "1e3" as a single token. This would
// would require pushing back the "e" we lexed here, terminate the
// numeric literal, and re-process the "e" as the first char of
// a new token.
while (isdigit(m_current_char)) {
consume(&current_token);
}
return true;
}
bool Lexer::consume_hexadecimal_number(StringBuilder& current_token)
{
consume(&current_token);
if (!isxdigit(m_current_char))
return false;
while (isxdigit(m_current_char))
consume();
consume(&current_token);
return true;
}
@ -299,6 +367,16 @@ bool Lexer::is_string_literal_end() const
return m_current_char == '\'' && !(m_position < m_source.length() && m_source[m_position] == '\'');
}
bool Lexer::is_quoted_identifier_start() const
{
return m_current_char == '"';
}
bool Lexer::is_quoted_identifier_end() const
{
return m_current_char == '"' && !(m_position < m_source.length() && m_source[m_position] == '"');
}
bool Lexer::is_blob_literal_start() const
{
return match('x', '\'') || match('X', '\'');

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -20,14 +21,15 @@ public:
Token next();
private:
void consume();
void consume(StringBuilder* = nullptr);
bool consume_whitespace_and_comments();
bool consume_numeric_literal();
bool consume_string_literal();
bool consume_blob_literal();
bool consume_exponent();
bool consume_hexadecimal_number();
bool consume_numeric_literal(StringBuilder&);
bool consume_string_literal(StringBuilder&);
bool consume_quoted_identifier(StringBuilder&);
bool consume_blob_literal(StringBuilder&);
bool consume_exponent(StringBuilder&);
bool consume_hexadecimal_number(StringBuilder&);
bool match(char a, char b) const;
bool is_identifier_start() const;
@ -35,6 +37,8 @@ private:
bool is_numeric_literal_start() const;
bool is_string_literal_start() const;
bool is_string_literal_end() const;
bool is_quoted_identifier_start() const;
bool is_quoted_identifier_end() const;
bool is_blob_literal_start() const;
bool is_line_comment_start() const;
bool is_block_comment_start() const;

View File

@ -1075,12 +1075,9 @@ void Parser::syntax_error(String message)
m_parser_state.m_errors.append({ move(message), position() });
}
Parser::Position Parser::position() const
SourcePosition Parser::position() const
{
return {
m_parser_state.m_token.line_number(),
m_parser_state.m_token.line_column()
};
return m_parser_state.m_token.start_position();
}
Parser::ParserState::ParserState(Lexer lexer)

View File

@ -21,14 +21,9 @@ constexpr size_t maximum_subquery_depth = 100;
}
class Parser {
struct Position {
size_t line { 0 };
size_t column { 0 };
};
struct Error {
String message;
Position position;
SourcePosition position;
String to_string() const
{
@ -126,7 +121,7 @@ private:
void expected(StringView what);
void syntax_error(String message);
Position position() const;
SourcePosition position() const;
ParserState m_parser_state;
};

View File

@ -47,22 +47,12 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
Vector<GUI::TextDocumentSpan> spans;
auto append_token = [&](StringView str, Token const& token) {
if (str.is_empty())
auto append_token = [&](Token const& token) {
if (token.value().is_empty())
return;
GUI::TextPosition position { token.line_number() - 1, token.line_column() - 1 };
for (char c : str) {
if (c == '\n') {
position.set_line(position.line() + 1);
position.set_column(0);
} else
position.set_column(position.column() + 1);
}
GUI::TextDocumentSpan span;
span.range.set_start({ token.line_number() - 1, token.line_column() - 1 });
span.range.set_end({ position.line(), position.column() });
span.range.set_start({ token.start_position().line - 1, token.start_position().column - 1 });
span.range.set_end({ token.end_position().line - 1, token.end_position().column - 1 });
auto style = style_for_token_type(palette, token.type());
span.attributes.color = style.color;
span.attributes.bold = style.bold;
@ -78,7 +68,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
for (;;) {
auto token = lexer.next();
append_token(token.value(), token);
append_token(token);
if (token.type() == TokenType::Eof)
break;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,6 +8,7 @@
#pragma once
#include <AK/HashMap.h>
#include <AK/String.h>
#include <AK/StringView.h>
namespace SQL::AST {
@ -209,13 +211,18 @@ enum class TokenCategory {
Punctuation,
};
struct SourcePosition {
size_t line { 0 };
size_t column { 0 };
};
class Token {
public:
Token(TokenType type, StringView value, size_t line_number, size_t line_column)
Token(TokenType type, String value, SourcePosition start_position, SourcePosition end_position)
: m_type(type)
, m_value(value)
, m_line_number(line_number)
, m_line_column(line_column)
, m_value(move(value))
, m_start_position(start_position)
, m_end_position(end_position)
{
}
@ -226,17 +233,17 @@ public:
TokenType type() const { return m_type; }
TokenCategory category() const { return category(m_type); }
StringView value() const { return m_value; }
String const& value() const { return m_value; }
double double_value() const;
size_t line_number() const { return m_line_number; }
size_t line_column() const { return m_line_column; }
SourcePosition const& start_position() const { return m_start_position; }
SourcePosition const& end_position() const { return m_end_position; }
private:
TokenType m_type;
StringView m_value;
size_t m_line_number;
size_t m_line_column;
String m_value;
SourcePosition m_start_position;
SourcePosition m_end_position;
};
}

View File

@ -137,7 +137,7 @@ int main()
bool indenters_starting_line = true;
for (SQL::AST::Token token = lexer.next(); token.type() != SQL::AST::TokenType::Eof; token = lexer.next()) {
auto length = token.value().length();
auto start = token.line_column() - 1;
auto start = token.start_position().column - 1;
auto end = start + length;
if (indenters_starting_line) {