diff --git a/Tests/LibSQL/TestSqlExpressionParser.cpp b/Tests/LibSQL/TestSqlExpressionParser.cpp index ec3685c0288..e11ff30208d 100644 --- a/Tests/LibSQL/TestSqlExpressionParser.cpp +++ b/Tests/LibSQL/TestSqlExpressionParser.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Tim Flynn + * Copyright (c) 2021, Jan de Visser * * 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) diff --git a/Tests/LibSQL/TestSqlStatementParser.cpp b/Tests/LibSQL/TestSqlStatementParser.cpp index c5ad5f036b1..98c793f936e 100644 --- a/Tests/LibSQL/TestSqlStatementParser.cpp +++ b/Tests/LibSQL/TestSqlStatementParser.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Tim Flynn + * Copyright (c) 2021, Jan de Visser * * 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 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 expected_column_names, Vector 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> expected_update_columns, bool expect_where_clause, bool expect_returning_clause, Vector expected_returned_column_aliases) { auto result = parse(sql); @@ -433,42 +438,42 @@ TEST_CASE(update) } }; - Vector> 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> 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 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 all { { SQL::AST::ResultType::All } }; - Vector from { { {}, "table", {} } }; + Vector 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()); } diff --git a/Userland/Libraries/LibSQL/AST/Lexer.cpp b/Userland/Libraries/LibSQL/AST/Lexer.cpp index 49c16022433..8b4c375302f 100644 --- a/Userland/Libraries/LibSQL/AST/Lexer.cpp +++ b/Userland/Libraries/LibSQL/AST/Lexer.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Tim Flynn + * Copyright (c) 2021, Jan de Visser * * 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(¤t_token); + consume(¤t_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(¤t_token); } } if (!found_two_char_token && !found_one_char_token) { token_type = TokenType::Invalid; - consume(); + consume(¤t_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(¤t_token); if (m_current_char == '.') { - consume(); + consume(¤t_token); while (isdigit(m_current_char)) - consume(); + consume(¤t_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(¤t_token); } while (isdigit(m_current_char)); } } else { do { - consume(); + consume(¤t_token); } while (isdigit(m_current_char)); if (m_current_char == '.') { - consume(); + consume(¤t_token); while (isdigit(m_current_char)) - consume(); + consume(¤t_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(¤t_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(¤t_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(¤t_token); + if (m_current_char == '-' || m_current_char == '+') + consume(¤t_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(¤t_token); + } + return true; +} + +bool Lexer::consume_hexadecimal_number(StringBuilder& current_token) +{ + consume(¤t_token); if (!isxdigit(m_current_char)) return false; while (isxdigit(m_current_char)) - consume(); + consume(¤t_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', '\''); diff --git a/Userland/Libraries/LibSQL/AST/Lexer.h b/Userland/Libraries/LibSQL/AST/Lexer.h index 77abcdec45a..1712ed70160 100644 --- a/Userland/Libraries/LibSQL/AST/Lexer.h +++ b/Userland/Libraries/LibSQL/AST/Lexer.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Tim Flynn + * Copyright (c) 2021, Jan de Visser * * 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; diff --git a/Userland/Libraries/LibSQL/AST/Parser.cpp b/Userland/Libraries/LibSQL/AST/Parser.cpp index ae771c9ae04..ffe6c028c04 100644 --- a/Userland/Libraries/LibSQL/AST/Parser.cpp +++ b/Userland/Libraries/LibSQL/AST/Parser.cpp @@ -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) diff --git a/Userland/Libraries/LibSQL/AST/Parser.h b/Userland/Libraries/LibSQL/AST/Parser.h index 466c2f41507..c1f60c32384 100644 --- a/Userland/Libraries/LibSQL/AST/Parser.h +++ b/Userland/Libraries/LibSQL/AST/Parser.h @@ -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; }; diff --git a/Userland/Libraries/LibSQL/AST/SyntaxHighlighter.cpp b/Userland/Libraries/LibSQL/AST/SyntaxHighlighter.cpp index da5de16a677..d2f595ce141 100644 --- a/Userland/Libraries/LibSQL/AST/SyntaxHighlighter.cpp +++ b/Userland/Libraries/LibSQL/AST/SyntaxHighlighter.cpp @@ -47,22 +47,12 @@ void SyntaxHighlighter::rehighlight(Palette const& palette) Vector 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; } diff --git a/Userland/Libraries/LibSQL/AST/Token.h b/Userland/Libraries/LibSQL/AST/Token.h index e8dc8ff5cb0..5eef6753a9b 100644 --- a/Userland/Libraries/LibSQL/AST/Token.h +++ b/Userland/Libraries/LibSQL/AST/Token.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Tim Flynn + * Copyright (c) 2021, Jan de Visser * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,6 +8,7 @@ #pragma once #include +#include #include 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; }; } diff --git a/Userland/Utilities/sql.cpp b/Userland/Utilities/sql.cpp index 936f20976e9..2f8181afbbd 100644 --- a/Userland/Utilities/sql.cpp +++ b/Userland/Utilities/sql.cpp @@ -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) {