From 8879d67a6c9487a84eb948fbca7f07f2efa44933 Mon Sep 17 00:00:00 2001 From: Elliot Chance Date: Thu, 23 Feb 2023 23:15:25 -0500 Subject: [PATCH] Support SET SCHEMA (#144) Adding the `SET SCHEMA` statement and `CURRENT_SCHEMA` expression. These would normally be trivial, but this was a good opportunity to refactor the schema implementation to not be so hacky and make it much easier to support catalogs in the future. SQL Standard F763 --- cmd/vsql/out.v | 13 +- docs/functions.rst | 315 +++++++++++++++++++++++++++++--------- docs/set-schema.rst | 40 +++++ docs/snippets.rst | 14 +- docs/sql-compliance.rst | 2 +- docs/sql-reference.rst | 1 + examples/show-tables.v | 2 +- generate-grammar.py | 2 +- grammar.bnf | 58 ++++--- tests/create-schema.sql | 7 + tests/create-sequence.sql | 7 +- tests/drop-sequence.sql | 4 +- tests/explain.sql | 12 +- tests/parameters.sql | 2 +- tests/primary-key.sql | 2 +- tests/select-from.sql | 7 + tests/set-schema.sql | 16 ++ tests/update.sql | 9 ++ vsql/alter_sequence.v | 17 +- vsql/ast.v | 285 +++++++++++++++++++++++++++++++--- vsql/bytes.v | 18 +++ vsql/connection.v | 68 ++++++-- vsql/create_schema.v | 2 +- vsql/create_sequence.v | 15 +- vsql/create_table.v | 25 +-- vsql/delete.v | 17 +- vsql/drop_schema.v | 2 +- vsql/drop_sequence.v | 7 +- vsql/drop_table.v | 20 +-- vsql/eval.v | 53 +++---- vsql/expr.v | 83 +++++----- vsql/grammar.v | 247 ++++++++++++++++++++++++++++-- vsql/group.v | 2 +- vsql/header.v | 2 +- vsql/insert.v | 27 +--- vsql/parse.v | 87 ++++++++--- vsql/planner.v | 94 +++++------- vsql/prepare.v | 7 + vsql/row.v | 12 +- vsql/sequence.v | 19 +-- vsql/set_schema.v | 21 +++ vsql/storage.v | 49 ++---- vsql/table.v | 39 ++--- vsql/update.v | 31 ++-- vsql/values.v | 11 +- vsql/walk.v | 26 +--- 46 files changed, 1259 insertions(+), 540 deletions(-) create mode 100644 docs/set-schema.rst create mode 100644 tests/set-schema.sql create mode 100644 vsql/set_schema.v diff --git a/cmd/vsql/out.v b/cmd/vsql/out.v index 4a390a9..a8a53ed 100644 --- a/cmd/vsql/out.v +++ b/cmd/vsql/out.v @@ -28,7 +28,9 @@ fn out_command(cmd cli.Command) ! { // To make the output more deterministic, entities will be ordered by name. // This is not true for the records however, which can potentially come out in // any order. - schemas.sort(a.name > b.name) + schemas.sort_with_compare(fn (a &vsql.Schema, b &vsql.Schema) int { + return compare_strings(a.name, b.name) + }) for _, schema in schemas { // We avoid "CREATE SCHEMA PUBLIC;" because it would break the import. @@ -39,15 +41,18 @@ fn out_command(cmd cli.Command) ! { } mut sequences := db.sequences(schema.name)! - sequences.sort(a.name > b.name) + sequences.sort_with_compare(fn (a &vsql.Sequence, b &vsql.Sequence) int { + return compare_strings(a.name.str(), b.name.str()) + }) for _, sequence in sequences { println('${sequence}\n') } mut tables := db.schema_tables(schema.name) or { return err } - - tables.sort(a.name > b.name) + tables.sort_with_compare(fn (a &vsql.Table, b &vsql.Table) int { + return compare_strings(a.name.str(), b.name.str()) + }) for _, table in tables { println('${table}\n') diff --git a/docs/functions.rst b/docs/functions.rst index 4406882..d4e8c44 100644 --- a/docs/functions.rst +++ b/docs/functions.rst @@ -6,8 +6,12 @@ Functions Aggregate Functions ------------------- -``AVG(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +AVG +^^^ + +.. code-block:: sql + + AVG(DOUBLE PRECISION) DOUBLE PRECISION Returns the average value. If any of the expressions are ``NULL`` the result will also be ``NULL``. @@ -23,8 +27,12 @@ will also be ``NULL``. -- CITY: New York COL1: 8.96666 -- CITY: San Francisco COL1: 6.5 -``COUNT(ANY) INTEGER`` -^^^^^^^^^^^^^^^^^^^^^^ +COUNT +^^^^^ + +.. code-block:: sql + + COUNT(ANY) INTEGER Count the number of non-``NULL`` expressions. @@ -43,8 +51,12 @@ that have a non-``NULL`` ``first_name``. SELECT COUNT(first_name) FROM people; -- 10 -``MAX(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +MAX +^^^ + +.. code-block:: sql + + MAX(DOUBLE PRECISION) DOUBLE PRECISION Returns the maximum value. If any of the expressions are ``NULL`` the result will also be ``NULL``. @@ -60,8 +72,12 @@ will also be ``NULL``. -- CITY: New York COL1: 18.05 -- CITY: San Francisco COL1: 17.5 -``MIN(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +MIN +^^^ + +.. code-block:: sql + + MIN(DOUBLE PRECISION) DOUBLE PRECISION Returns the minimum value. If any of the expressions are ``NULL`` the result will also be ``NULL``. @@ -77,8 +93,12 @@ will also be ``NULL``. -- CITY: New York COL1: 8.05 -- CITY: San Francisco COL1: 7.5 -``SUM(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +SUM +^^^ + +.. code-block:: sql + + SUM(DOUBLE PRECISION) DOUBLE PRECISION Returns the sum (total) of all values. If any of the expressions are ``NULL`` the result will also be ``NULL``. @@ -94,11 +114,43 @@ the result will also be ``NULL``. -- CITY: New York COL1: 196.35 -- CITY: San Francisco COL1: 291.4 +Date & Time +----------- + +CURRENT_DATE +^^^^^^^^^^^^ + +See :doc:`dates-times`. + +CURRENT_TIME +^^^^^^^^^^^^ + +See :doc:`dates-times`. + +CURRENT_TIMESTAMP +^^^^^^^^^^^^^^^^^ + +See :doc:`dates-times`. + +LOCALTIME +^^^^^^^^^ + +See :doc:`dates-times`. + +LOCALTIMESTAMP +^^^^^^^^^^^^^^ + +See :doc:`dates-times`. + Mathematical Functions ---------------------- -``ABS(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +ABS +^^^ + +.. code-block:: sql + + ABS(DOUBLE PRECISION) DOUBLE PRECISION Absolute value. @@ -112,8 +164,12 @@ Absolute value. VALUES ABS(-1.23); -- 1.23 -``ACOS(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +ACOS +^^^^ + +.. code-block:: sql + + ACOS(DOUBLE PRECISION) DOUBLE PRECISION Inverse (arc) cosine. @@ -124,8 +180,12 @@ Inverse (arc) cosine. VALUES ACOS(0.2); -- COL1: 1.369438 -``ASIN(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +ASIN +^^^^ + +.. code-block:: sql + + ASIN(DOUBLE PRECISION) DOUBLE PRECISION Inverse (arc) sine. @@ -136,8 +196,12 @@ Inverse (arc) sine. VALUES ASIN(0.2); -- COL1: 0.201358 -``ATAN(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +ATAN +^^^^ + +.. code-block:: sql + + ATAN(DOUBLE PRECISION) DOUBLE PRECISION Inverse (arc) tangent. @@ -148,8 +212,12 @@ Inverse (arc) tangent. VALUES ATAN(0.2); -- COL1: 0.197396 -``CEIL(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +CEIL +^^^^ + +.. code-block:: sql + + CEIL(DOUBLE PRECISION) DOUBLE PRECISION Round up to the nearest integer. @@ -172,13 +240,21 @@ Round up to the nearest integer. VALUES CEILING(3.7); -- COL1: 4 -``CEILING(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +CEILING +^^^^^^^ + +.. code-block:: sql + + CEILING(DOUBLE PRECISION) DOUBLE PRECISION ``CEILING`` is an alias of ``CEIL``. -``COS(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +COS +^^^ + +.. code-block:: sql + + COS(DOUBLE PRECISION) DOUBLE PRECISION Cosine. @@ -189,8 +265,12 @@ Cosine. VALUES COS(1.2); -- COL1: 0.362358 -``COSH(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +COSH +^^^^ + +.. code-block:: sql + + COSH(DOUBLE PRECISION) DOUBLE PRECISION Hyperbolic cosine. @@ -201,8 +281,12 @@ Hyperbolic cosine. VALUES COSH(1.2); -- COL1: 1.810656 -``EXP(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +EXP +^^^ + +.. code-block:: sql + + EXP(DOUBLE PRECISION) DOUBLE PRECISION Exponential. @@ -213,8 +297,12 @@ Exponential. VALUES EXP(3.7); -- COL1: 40.447304 -``FLOOR(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +FLOOR +^^^^^ + +.. code-block:: sql + + FLOOR(DOUBLE PRECISION) DOUBLE PRECISION Round down to the nearest integer. @@ -234,8 +322,12 @@ Round down to the nearest integer. VALUES FLOOR(-3.3); -- COL1: -4 -``LN(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LN +^^^ + +.. code-block:: sql + + LN(DOUBLE PRECISION) DOUBLE PRECISION Natural logarithm (base e). @@ -246,8 +338,12 @@ Natural logarithm (base e). VALUES LN(13.7); -- COL1: 2.617396 -``LOG10(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LOG10 +^^^^^ + +.. code-block:: sql + + LOG10(DOUBLE PRECISION) DOUBLE PRECISION Logarithm in base 10. @@ -258,8 +354,12 @@ Logarithm in base 10. VALUES LOG10(13.7); -- COL1: 1.136721 -``MOD(DOUBLE PRECISION, DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +MOD +^^^ + +.. code-block:: sql + + MOD(DOUBLE PRECISION, DOUBLE PRECISION) DOUBLE PRECISION Modulus. @@ -273,8 +373,12 @@ Modulus. VALUES MOD(10.7, 0.8); -- COL1: 0.3 -``POWER(DOUBLE PRECISION, DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +POWER +^^^^^ + +.. code-block:: sql + + POWER(DOUBLE PRECISION, DOUBLE PRECISION) DOUBLE PRECISION Power. @@ -285,8 +389,12 @@ Power. VALUES POWER(3.7, 2.5); -- COL1: 26.333241 -``SIN(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +SIN +^^^ + +.. code-block:: sql + + SIN(DOUBLE PRECISION) DOUBLE PRECISION Sine. @@ -297,8 +405,12 @@ Sine. VALUES SIN(1.2); -- COL1: 0.932039 -``SINH(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +SINH +^^^^ + +.. code-block:: sql + + SINH(DOUBLE PRECISION) DOUBLE PRECISION Hyperbolic sine. @@ -309,8 +421,12 @@ Hyperbolic sine. VALUES SINH(1.2); -- COL1: 1.509461 -``SQRT(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +SQRT +^^^^ + +.. code-block:: sql + + SQRT(DOUBLE PRECISION) DOUBLE PRECISION Square root. @@ -321,8 +437,12 @@ Square root. VALUES SQRT(3.7); -- COL1: 1.923538 -``TAN(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TAN +^^^ + +.. code-block:: sql + + TAN(DOUBLE PRECISION) DOUBLE PRECISION Tangent. @@ -333,8 +453,12 @@ Tangent. VALUES TAN(1.2); -- COL1: 2.572152 -``TANH(DOUBLE PRECISION) DOUBLE PRECISION`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TANH +^^^^ + +.. code-block:: sql + + TANH(DOUBLE PRECISION) DOUBLE PRECISION Hyperbolic tangent. @@ -348,8 +472,12 @@ Hyperbolic tangent. String Functions ---------------- -``CHAR_LENGTH(CHARACTER VARYING) INTEGER`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +CHAR_LENGTH +^^^^^^^^^^^ + +.. code-block:: sql + + CHAR_LENGTH(CHARACTER VARYING) INTEGER Returns the character length (multibyte chatracters are counted as a single character). @@ -359,13 +487,21 @@ character). VALUES CHAR_LENGTH('😊£'); -- COL1: 2 -``CHARACTER_LENGTH(CHARACTER VARYING) INTEGER`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +CHARACTER_LENGTH +^^^^^^^^^^^^^^^^ + +.. code-block:: sql + + CHARACTER_LENGTH(CHARACTER VARYING) INTEGER ``CHARACTER_LENGTH`` is an alias of ``CHAR_LENGTH``. -``LOWER(CHARACTER VARYING) CHARACTER VARYING`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LOWER +^^^^^ + +.. code-block:: sql + + LOWER(CHARACTER VARYING) CHARACTER VARYING Returns the input string converted to lower-case. @@ -374,8 +510,12 @@ Returns the input string converted to lower-case. VALUES LOWER('Hello'); -- COL1: hello -``OCTET_LENGTH(CHARACTER VARYING) INTEGER`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +OCTET_LENGTH +^^^^^^^^^^^^ + +.. code-block:: sql + + OCTET_LENGTH(CHARACTER VARYING) INTEGER Returns the byte length (multibyte chatracters are ignored). @@ -384,8 +524,12 @@ Returns the byte length (multibyte chatracters are ignored). VALUES OCTET_LENGTH('😊£'); -- COL1: 6 -``POSITION(CHARACTER VARYING IN CHARACTER VARYING) INTEGER`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +POSITION +^^^^^^^^ + +.. code-block:: sql + + POSITION(CHARACTER VARYING IN CHARACTER VARYING) INTEGER Returns the start of the left most (first) match of one string within another. 1 will be the smallest index on a match and 0 is returned if the substring does @@ -403,19 +547,19 @@ Matching is case-sensitive. VALUES POSITION('xx' IN 'hello Hello'); -- COL1: 0 -SUBSTRING(CHARACTER VARYING FROM INTEGER ...) CHARACTER VARYING -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +SUBSTRING +^^^^^^^^^ ``SUBSTRING`` can be constructed in several forms: .. code-block:: text - SUBSTRING( - value - FROM start_position - [ FOR string_length ] - [ USING { CHARACTERS | OCTETS } ] - ) + SUBSTRING( + value + FROM start_position + [ FOR string_length ] + [ USING { CHARACTERS | OCTETS } ] + ) ``start_position`` starts at 1 for the first character or byte. If ``start_position`` is out of bounds (either before the start or after the end) @@ -445,8 +589,8 @@ be used. VALUES SUBSTRING('Жabڣc' FROM 4 USING OCTETS); -- COL1: bڣc -TRIM() CHARACTER VARYING -^^^^^^^^^^^^^^^^^^^^^^^^ +TRIM +^^^^ ``TRIM`` can be constructed in several forms: @@ -475,8 +619,12 @@ If ``trim_character`` is not provided, a space (`' '`) is used. VALUES TRIM(TRAILING 'a' FROM 'aaababccaa'); -- COL1: aaababcc -``UPPER(CHARACTER VARYING) CHARACTER VARYING`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +UPPER +^^^^^ + +.. code-block:: sql + + UPPER(CHARACTER VARYING) CHARACTER VARYING Returns the input string converted to upper-case. @@ -488,8 +636,12 @@ Returns the input string converted to upper-case. Other Functions --------------- -``COALESCE(VALUE, ...)`` -^^^^^^^^^^^^^^^^^^^^^^^^ +COALESCE +^^^^^^^^ + +.. code-block:: sql + + COALESCE(VALUE, ...) ``COALESCE`` returns the first value that is not ``NULL``. If all values are ``NULL`` then ``NULL`` is also returned. @@ -499,8 +651,23 @@ Other Functions VALUES COALESCE(1, 2); -- COL1: 1 -``NULLIF(X, Y)`` -^^^^^^^^^^^^^^^^ +CURRENT_SCHEMA +^^^^^^^^^^^^^^ + +.. code-block:: sql + + CURRENT_SCHEMA + +``CURRENT_SCHEMA`` reports the current schema. The current schema is where +objects (such as tables, sequences, etc) are located or created. The default +schema is ``PUBLIC``. The current schema can be changed with :doc:`set-schema`. + +NULLIF +^^^^^^ + +.. code-block:: sql + + NULLIF(X, Y) If ``X`` and ``Y`` are equal, ``NULL`` will be returned. Otherwise ``X`` is returned. diff --git a/docs/set-schema.rst b/docs/set-schema.rst new file mode 100644 index 0000000..0960b98 --- /dev/null +++ b/docs/set-schema.rst @@ -0,0 +1,40 @@ +SET SCHEMA +========== + +.. contents:: + +Syntax +------ + +.. code-block:: text + + SET SCHEMA + +``SET SCHEMA`` changes the currently selected schema. The current schema is +where objects (such as tables, sequences, etc) are located or created. + +The default schema is ``PUBLIC`` and is created automatically when the database +file is initialized. + +``CURRENT_SCHEMA`` can be used in expressions to get the currently selected +schema. + +Examples +-------- + +.. code-block:: sql + + CREATE SCHEMA foo; + VALUES CURRENT_SCHEMA; + SET SCHEMA 'FOO'; + VALUES CURRENT_SCHEMA; + -- msg: CREATE SCHEMA 1 + -- COL1: PUBLIC + -- msg: SET SCHEMA 1 + -- COL1: FOO + +See Also +-------- + +- :doc:`create-schema` +- :doc:`drop-schema` diff --git a/docs/snippets.rst b/docs/snippets.rst index c647621..e0ad47b 100644 --- a/docs/snippets.rst +++ b/docs/snippets.rst @@ -122,6 +122,15 @@ you can safely share a single cache over multiple connections and you are encouraged to do so. +.. |v.Identifier| replace:: + Identifier is used to describe a object within a schema (such as a table + name) or a property of an object (like a column name of a table). You should + not instantiate this directly, instead use the appropriate new_*_identifier() + function. + |br| |br| + If you need the fully qualified (canonical) form of an identified you can use + Connection.resolve_schema_identifier(). + .. |v.PreparedStmt| replace:: A prepared statement is compiled and validated, but not executed. It can then be executed with a set of host parameters to be substituted into the @@ -218,8 +227,7 @@ A SEQUENCE definition. .. |v.Sequence.name| replace:: - name is case-sensitive. The name is equivilent to using a deliminated - identifier (with double quotes). + name contains the other parts such as the schema. .. |v.Sequence.str| replace:: str returns the CREATE SEQUENCE definition (including the ';') like: @@ -242,7 +250,7 @@ When the table is virtual it is not persisted to disk. .. |v.Table.name| replace:: - The name of the table is case-sensitive. + The name of the table including the schema. .. |v.Table.primary_key| replace:: If the table has a PRIMARY KEY defined the column (or columns) will be diff --git a/docs/sql-compliance.rst b/docs/sql-compliance.rst index 4d0b87b..f62fcc4 100644 --- a/docs/sql-compliance.rst +++ b/docs/sql-compliance.rst @@ -949,7 +949,7 @@ Optional Features * - ❌ **F762** - **CURRENT_CATALOG** - * - ❌ **F763** + * - ✅ **F763** - **CURRENT_SCHEMA** * - ❌ **F771** diff --git a/docs/sql-reference.rst b/docs/sql-reference.rst index b85c3a2..083b270 100644 --- a/docs/sql-reference.rst +++ b/docs/sql-reference.rst @@ -17,6 +17,7 @@ SQL Reference insert.rst rollback.rst select.rst + set-schema.rst start-transaction.rst update.rst values.rst diff --git a/examples/show-tables.v b/examples/show-tables.v index 9bc95fc..cb7d34e 100644 --- a/examples/show-tables.v +++ b/examples/show-tables.v @@ -19,7 +19,7 @@ fn example() ! { for schema in db.schemas()! { mut table_names := []string{} for table in db.schema_tables(schema.name)! { - table_names << table.name + table_names << table.name.str() } println('${schema.name} has ${table_names}') diff --git a/generate-grammar.py b/generate-grammar.py index 5567e83..04ad60b 100644 --- a/generate-grammar.py +++ b/generate-grammar.py @@ -421,7 +421,7 @@ def var_name(name): return [EarleyValue(node.value.end_column.value)] } '^identifier' { - return [EarleyValue(new_identifier(node.value.end_column.value))] + return [EarleyValue(IdentifierChain{node.value.end_column.value})] } '^string' { return [EarleyValue(new_varchar_value(node.value.end_column.value, 0))] diff --git a/grammar.bnf b/grammar.bnf index 56d2a37..4c1c412 100644 --- a/grammar.bnf +++ b/grammar.bnf @@ -10,6 +10,7 @@ | | + | /* Stmt */ ::= @@ -30,7 +31,7 @@ /* Identifier */ ::= - + -> unqualified_schema_name /* Stmt */ ::= @@ -49,9 +50,9 @@ DROP TABLE -> drop_table_statement
/* Identifier */ ::= - + -> table_name - /* Identifier */ ::= + /* IdentifierChain */ ::= | -> local_or_schema_qualified_name2 @@ -59,19 +60,19 @@ /* Identifier */ ::= - /* Identifier */ ::= + /* IdentifierChain */ ::= - /* Identifier */ ::= + /* IdentifierChain */ ::= - /* Identifier */ ::= + /* IdentifierChain */ ::= # is added on top of the original to # allow non-reserved words to be used as identifiers. As far as I can tell, only # s are restricted. See "9075-2:2016 5.2 #26". - /* Identifier */ ::= + /* IdentifierChain */ ::= | -> string_identifier @@ -357,14 +358,14 @@ | WRITE | ZONE - /* Identifier */ ::= + /* IdentifierChain */ ::= - /* Identifier */ ::= + /* IdentifierChain */ ::= ^identifier
/* CreateTableStmt */ ::= - CREATE TABLE
-> table_definition + CREATE TABLE
-> table_definition
/* []TableElement */ ::=
@@ -388,7 +389,7 @@ -> column_definition2 /* Identifier */ ::= - + -> column_name /* Type */ ::= @@ -682,12 +683,12 @@ ::= "." /* Identifier */ ::= - + -> column_reference - /* Identifier */ ::= + /* IdentifierChain */ ::= - /* Identifier */ ::= + /* IdentifierChain */ ::= | -> identifier_chain1 @@ -844,10 +845,10 @@ /* QualifiedAsteriskExpr */ ::= -> qualified_asterisk - /* Identifier */ ::= + /* IdentifierChain */ ::= - /* Identifier */ ::= + /* IdentifierChain */ ::= /* DerivedColumn */ ::= @@ -1048,7 +1049,7 @@ -> routine_invocation /* Identifier */ ::= - + -> routine_name /* []Expr */ ::= -> empty_exprs @@ -1061,6 +1062,7 @@ /* Expr */ ::= + | CURRENT_SCHEMA -> current_schema /* Expr */ ::= @@ -1184,7 +1186,7 @@ -> correlation2 /* Identifier */ ::= - + -> correlation_name /* []Identifier */ ::= @@ -1418,9 +1420,9 @@ -> sequence_generator_definition_2 /* Identifier */ ::= - + -> sequence_generator_name - /* Identifier */ ::= + /* IdentifierChain */ ::= | -> schema_qualified_name_2 @@ -1508,3 +1510,19 @@ /* Expr */ ::= + + /* Stmt */ ::= + + + /* Stmt */ ::= + + + /* Stmt */ ::= + SET -> set_schema_stmt + + /* Expr */ ::= + SCHEMA -> expr + + /* Expr */ ::= + + | diff --git a/tests/create-schema.sql b/tests/create-schema.sql index cb71093..e6b345b 100644 --- a/tests/create-schema.sql +++ b/tests/create-schema.sql @@ -1,8 +1,13 @@ EXPLAIN CREATE SCHEMA foo; -- error 42601: syntax error: Cannot EXPLAIN CREATE SCHEMA +VALUES CURRENT_SCHEMA; +-- COL1: PUBLIC + CREATE SCHEMA foo; +VALUES CURRENT_SCHEMA; -- msg: CREATE SCHEMA 1 +-- COL1: PUBLIC CREATE SCHEMA foo; CREATE SCHEMA foo; @@ -13,10 +18,12 @@ CREATE SCHEMA foo; CREATE TABLE foo.bar (baz INT); INSERT INTO foo.bar (baz) VALUES (123); SELECT * FROM foo.bar; +VALUES CURRENT_SCHEMA; -- msg: CREATE SCHEMA 1 -- msg: CREATE TABLE 1 -- msg: INSERT 1 -- BAZ: 123 +-- COL1: PUBLIC CREATE SCHEMA absolute; -- msg: CREATE SCHEMA 1 diff --git a/tests/create-sequence.sql b/tests/create-sequence.sql index cc3468a..be58584 100644 --- a/tests/create-sequence.sql +++ b/tests/create-sequence.sql @@ -1,6 +1,11 @@ EXPLAIN CREATE SEQUENCE seq1; -- error 42601: syntax error: Cannot EXPLAIN CREATE SEQUENCE +CREATE SEQUENCE seq1; +EXPLAIN VALUES NEXT VALUE FOR seq1; +-- msg: CREATE SEQUENCE 1 +-- EXPLAIN: VALUES (COL1 INTEGER) = ROW(NEXT VALUE FOR PUBLIC.SEQ1) + CREATE SEQUENCE seq1; VALUES NEXT VALUE FOR seq1; VALUES NEXT VALUE FOR seq1; @@ -24,7 +29,7 @@ CREATE SEQUENCE foo.seq1; -- msg: CREATE SEQUENCE 1 VALUES NEXT VALUE FOR seq1; --- error 42P01: no such sequence: SEQ1 +-- error 42P01: no such sequence: PUBLIC.SEQ1 CREATE SEQUENCE seq1; CREATE SEQUENCE seq2; diff --git a/tests/drop-sequence.sql b/tests/drop-sequence.sql index eb8d1e4..fa8446b 100644 --- a/tests/drop-sequence.sql +++ b/tests/drop-sequence.sql @@ -2,7 +2,7 @@ EXPLAIN DROP SEQUENCE foo; -- error 42601: syntax error: Cannot EXPLAIN DROP SEQUENCE DROP SEQUENCE foo; --- error 42P01: no such sequence: FOO +-- error 42P01: no such sequence: PUBLIC.FOO CREATE SEQUENCE foo; DROP SEQUENCE foo; @@ -14,7 +14,7 @@ DROP SEQUENCE foo; DROP SEQUENCE foo; -- msg: CREATE SEQUENCE 1 -- msg: DROP SEQUENCE 1 --- error 42P01: no such sequence: FOO +-- error 42P01: no such sequence: PUBLIC.FOO CREATE SEQUENCE foo; VALUES NEXT VALUE FOR foo; diff --git a/tests/explain.sql b/tests/explain.sql index 65d6f4e..7b49ad5 100644 --- a/tests/explain.sql +++ b/tests/explain.sql @@ -68,24 +68,24 @@ EXPLAIN INSERT INTO t1 (x) VALUES (0); CREATE TABLE t1 (x INT); EXPLAIN DELETE FROM t1; -- msg: CREATE TABLE 1 --- EXPLAIN: TABLE PUBLIC.T1 (X INTEGER) +-- EXPLAIN: TABLE PUBLIC.T1 (PUBLIC.T1.X INTEGER) CREATE TABLE t1 (x INT); EXPLAIN DELETE FROM t1 WHERE x = 35; -- msg: CREATE TABLE 1 --- EXPLAIN: TABLE PUBLIC.T1 (X INTEGER) --- EXPLAIN: WHERE X = 35 +-- EXPLAIN: TABLE PUBLIC.T1 (PUBLIC.T1.X INTEGER) +-- EXPLAIN: WHERE PUBLIC.T1.X = 35 CREATE TABLE t1 (x INT); EXPLAIN UPDATE t1 SET x = 1; -- msg: CREATE TABLE 1 --- EXPLAIN: TABLE PUBLIC.T1 (X INTEGER) +-- EXPLAIN: TABLE PUBLIC.T1 (PUBLIC.T1.X INTEGER) CREATE TABLE t1 (x INT); EXPLAIN UPDATE t1 SET x = 1 WHERE x = 35; -- msg: CREATE TABLE 1 --- EXPLAIN: TABLE PUBLIC.T1 (X INTEGER) --- EXPLAIN: WHERE X = 35 +-- EXPLAIN: TABLE PUBLIC.T1 (PUBLIC.T1.X INTEGER) +-- EXPLAIN: WHERE PUBLIC.T1.X = 35 CREATE TABLE t1 (x INT, PRIMARY KEY(x)); EXPLAIN SELECT * FROM t1; diff --git a/tests/parameters.sql b/tests/parameters.sql index 755719e..964e2e8 100644 --- a/tests/parameters.sql +++ b/tests/parameters.sql @@ -23,7 +23,7 @@ SELECT * FROM t2; -- X: hello EXPLAIN UPDATE t1 SET x = :foo; --- EXPLAIN: TABLE PUBLIC.T1 (X DOUBLE PRECISION) +-- EXPLAIN: TABLE PUBLIC.T1 (PUBLIC.T1.X DOUBLE PRECISION) UPDATE t1 SET x = :foo; -- error 42P02: parameter does not exist: foo diff --git a/tests/primary-key.sql b/tests/primary-key.sql index 712d0c3..58ad425 100644 --- a/tests/primary-key.sql +++ b/tests/primary-key.sql @@ -20,7 +20,7 @@ SELECT * FROM pk1 WHERE id = 2; -- ID: 2 EXPLAIN UPDATE pk1 SET id = 5 WHERE id = 2; --- EXPLAIN: PRIMARY KEY PUBLIC.PK1 (ID INTEGER) BETWEEN 2 AND 2 +-- EXPLAIN: PRIMARY KEY PUBLIC.PK1 (PUBLIC.PK1.ID INTEGER) BETWEEN 2 AND 2 UPDATE pk1 SET id = 5 WHERE id = 2; SELECT * FROM pk1 WHERE id = 2; diff --git a/tests/select-from.sql b/tests/select-from.sql index 4ab3c08..22c37be 100644 --- a/tests/select-from.sql +++ b/tests/select-from.sql @@ -8,6 +8,13 @@ SELECT * FROM foo; -- msg: INSERT 1 -- X: 1.234 +CREATE TABLE "Foo" ("a" FLOAT); +INSERT INTO "Foo" ("a") VALUES (4.56); +SELECT * FROM "Foo"; +-- msg: CREATE TABLE 1 +-- msg: INSERT 1 +-- a: 4.56 + CREATE TABLE foo (x FLOAT); CREATE TABLE "Foo" ("a" FLOAT); INSERT INTO FOO (x) VALUES (1.234); diff --git a/tests/set-schema.sql b/tests/set-schema.sql new file mode 100644 index 0000000..8144fe5 --- /dev/null +++ b/tests/set-schema.sql @@ -0,0 +1,16 @@ +EXPLAIN SET SCHEMA 'foo'; +-- error 42601: syntax error: Cannot EXPLAIN SET SCHEMA + +CREATE SCHEMA foo; +VALUES CURRENT_SCHEMA; +SET SCHEMA 'FOO'; +VALUES CURRENT_SCHEMA; +-- msg: CREATE SCHEMA 1 +-- COL1: PUBLIC +-- msg: SET SCHEMA 1 +-- COL1: FOO + +SET SCHEMA 'foo'; +VALUES CURRENT_SCHEMA; +-- error 3F000: invalid schema name: foo +-- COL1: PUBLIC diff --git a/tests/update.sql b/tests/update.sql index adac6a8..0359e43 100644 --- a/tests/update.sql +++ b/tests/update.sql @@ -68,6 +68,15 @@ SELECT * FROM foo; -- msg: UPDATE 1 -- BAZ: -516.6 +CREATE TABLE foo (baz FLOAT); +INSERT INTO foo (baz) VALUES (-123); +UPDATE foo SET baz = foo.baz * 4.2; +SELECT * FROM foo; +-- msg: CREATE TABLE 1 +-- msg: INSERT 1 +-- msg: UPDATE 1 +-- BAZ: -516.6 + UPDATE foo.bar SET baz = baz * 4.2; -- error 3F000: invalid schema name: FOO diff --git a/vsql/alter_sequence.v b/vsql/alter_sequence.v index f8d8fa8..911133a 100644 --- a/vsql/alter_sequence.v +++ b/vsql/alter_sequence.v @@ -13,21 +13,8 @@ fn execute_alter_sequence(mut c Connection, stmt AlterSequenceStmt, elapsed_pars c.release_write_connection() } - mut sequence_name := stmt.name.str() - - // TODO(elliotchance): This isn't really ideal. Replace with a proper - // identifier chain when we support that. - if sequence_name.contains('.') { - parts := sequence_name.split('.') - - if parts[0] !in c.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - sequence_name = 'PUBLIC.${sequence_name}' - } - - old_sequence := c.storage.sequence(stmt.name)! + name := c.resolve_schema_identifier(stmt.name)! + old_sequence := c.storage.sequence(name)! mut sequence := old_sequence.copy() for option in stmt.options { diff --git a/vsql/ast.v b/vsql/ast.v index df532c4..80e7dbd 100644 --- a/vsql/ast.v +++ b/vsql/ast.v @@ -17,6 +17,7 @@ type Stmt = AlterSequenceStmt | InsertStmt | QueryExpression | RollbackStmt + | SetSchemaStmt | StartTransactionStmt | UpdateStmt @@ -28,6 +29,7 @@ type Expr = BetweenExpr | CoalesceExpr | CountAllExpr | CurrentDateExpr + | CurrentSchemaExpr | CurrentTimeExpr | CurrentTimestampExpr | Identifier @@ -74,6 +76,9 @@ fn (e Expr) pstr(params map[string]Value) string { CountAllExpr { e.pstr(params) } + CurrentSchemaExpr { + e.str() + } CurrentDateExpr { e.str() } @@ -193,7 +198,7 @@ struct TablePrimary { // CREATE TABLE ... struct CreateTableStmt { - table_name string + table_name Identifier table_elements []TableElement } @@ -210,18 +215,18 @@ fn (s CreateTableStmt) columns() Columns { // DELETE ... struct DeleteStmt { - table_name string + table_name Identifier where Expr } // DROP TABLE ... struct DropTableStmt { - table_name string + table_name Identifier } // INSERT INTO ... struct InsertStmt { - table_name string + table_name Identifier columns []Identifier values []Expr } @@ -236,7 +241,7 @@ struct SelectStmt { // UPDATE ... struct UpdateStmt { - table_name string + table_name Identifier set map[string]Expr where Expr } @@ -259,31 +264,248 @@ fn (e NullExpr) pstr(params map[string]Value) string { return '${e.expr.pstr(params)} IS NULL' } -// Identifier is foo or "Foo" -struct Identifier { - // name is the normalized name. That is, upper case for regular tokens or - // the case is kept for delimited identifiers. - name string - // original is the original token string. - original string +// IdentifierChain wraps a single string that contains the chain of one or more +// identifiers, such as: "Foo".bar."BAZ" +struct IdentifierChain { + identifier string +} + +fn (identifier IdentifierChain) str() string { + return identifier.identifier +} + +// Identifier is used to describe a object within a schema (such as a table +// name) or a property of an object (like a column name of a table). You should +// not instantiate this directly, instead use the appropriate new_*_identifier() +// function. +// +// If you need the fully qualified (canonical) form of an identified you can use +// Connection.resolve_schema_identifier(). +// +// snippet: v.Identifier +pub struct Identifier { + // schema_name is optional. If not provided, it will use CURRENT_SCHEMA. + schema_name string + // entity_name would be the table name, sequence name, etc. Something inside + // of a schema. It is case sensitive. + entity_name string + // sub_entity_name would represent a column name. It is case sensitive. + sub_entity_name string + // custom_id is a way to override the behavior of rendering and storage. This + // is only used for internal identifiers. + custom_id string +} + +// new_table_identifier is the correct way to create a new Identifier that +// represents a table. It can take several forms: +// +// foo => FOO +// "foo" => foo +// schema.Bar => SCHEMA.BAR +// "Schema".bar => Schema.BAR +// "Fully"."Qualified" => Fully.Qualified +// +// It's important to note that when a schema is not provided it will be left +// blank. You will need to use Connection.resolve_schema_identifier() to fill in +// the missing schema. +// +// An error is returned if the identifer is not valid (cannot be parsed). +// +// Even though it's valid to have a '.' in an entity name (ie. "foo.bar"), +// new_table_identifier does not correct parse this yet. +fn new_table_identifier(s string) !Identifier { + return new_identifier2(s) +} + +fn new_function_identifier(s string) !Identifier { + return new_identifier2(s) +} + +fn new_schema_identifier(s string) !Identifier { + return new_identifier1(s) } -fn new_identifier(s string) Identifier { - if s.len > 0 && s[0] == `"` { - return Identifier{ - name: s[1..s.len - 1] - original: s +fn new_identifier1(s string) !Identifier { + parts := split_identifier_parts(s)! + + match parts.len { + 1 { + return Identifier{ + schema_name: parts[0] + } + } + else { + return error('invalid identifier: ${s}') + } + } +} + +fn new_identifier2(s string) !Identifier { + parts := split_identifier_parts(s)! + + match parts.len { + 1 { + return Identifier{ + entity_name: parts[0] + } + } + 2 { + return Identifier{ + schema_name: parts[0] + entity_name: parts[1] + } + } + else { + return error('invalid identifier: ${s}') + } + } +} + +// new_column_identifier is the correct way to create a new Identifier that +// represents a table column. It can take several forms: +// +// col => COL +// "col" => col +// tbl.Bar => TBL.BAR +// "Table".bar => Table.BAR +// schema.tbl.Bar => SCHEMA.TBL.BAR +// "Schema"."Table".bar => Schema.Table.BAR +// "Fully"."Qualified"."Column" => Fully.Qualified.Column +// +// It's important to note that when a schema is not provided it will be left +// blank. You will need to use Connection.resolve_schema_identifier() to fill in +// the missing schema. +// +// An error is returned if the identifer is not valid (cannot be parsed). +// +// Even though it's valid to have a '.' in an entity name (ie. "foo.bar"), +// new_column_identifier does not correct parse this yet. +fn new_column_identifier(s string) !Identifier { + return new_identifier3(s) +} + +fn new_identifier3(s string) !Identifier { + parts := split_identifier_parts(s)! + + match parts.len { + 1 { + return Identifier{ + sub_entity_name: parts[0] + } + } + 2 { + return Identifier{ + entity_name: parts[0] + sub_entity_name: parts[1] + } + } + 3 { + return Identifier{ + schema_name: parts[0] + entity_name: parts[1] + sub_entity_name: parts[2] + } + } + else { + return error('invalid identifier3: ${s} ${parts}') } } +} + +// decode_identifier is only for internal use. It is the opposite of +// Identifier.id(). +fn decode_identifier(s string) Identifier { + parts := split_identifier_parts(s) or { panic('cannot parse identifier: ${s}') } return Identifier{ - name: s.to_upper() - original: s + schema_name: parts[0] + entity_name: parts[1] + } +} + +fn split_identifier_parts(s string) ![]string { + if s == '' { + return error('cannot use empty string for identifier') + } + + mut parts := []string{} + mut s2 := s + for s2 != '' { + if s2[0] == `"` { + s2 = s2[1..] + index := s2.index('"') or { return error('invalid identifier chain: ${s}') } + parts << s2[..index] + s2 = s2[index + 1..] + } else { + mut index := s2.index('.') or { -1 } + if index < 0 { + parts << s2.to_upper() + break + } + if index > 0 { + parts << s2[..index].to_upper() + } + s2 = s2[index + 1..] + } } + + return parts } -fn (e Identifier) str() string { - return e.name +fn requote_identifier(s string) string { + if s.to_upper() == s { + return s + } + + return '"${s}"' +} + +// id is the internal canonical name. How it is represented in memory and +// on-disk. As opposed to str() which is the human readable form. +fn (e Identifier) id() string { + if e.custom_id != '' { + return e.custom_id + } + + mut parts := []string{} + + if e.schema_name != '' { + parts << e.schema_name + } + + if e.entity_name != '' { + parts << e.entity_name + } + + if e.sub_entity_name != '' { + parts << e.sub_entity_name + } + + return parts.join('.') +} + +pub fn (e Identifier) str() string { + if e.custom_id != '' { + return e.custom_id + } + + if e.schema_name != '' { + if e.sub_entity_name == '' { + return '${requote_identifier(e.schema_name)}.${requote_identifier(e.entity_name)}' + } + + return '${requote_identifier(e.schema_name)}.${requote_identifier(e.entity_name)}.${requote_identifier(e.sub_entity_name)}' + } + + if e.entity_name != '' { + if e.sub_entity_name == '' { + return requote_identifier(e.entity_name) + } + + return '${requote_identifier(e.entity_name)}.${requote_identifier(e.sub_entity_name)}' + } + + return requote_identifier(e.sub_entity_name) } struct UnaryExpr { @@ -362,7 +584,7 @@ struct Correlation { } fn (c Correlation) str() string { - if c.name.name == '' { + if c.name.sub_entity_name == '' { return '' } @@ -371,7 +593,7 @@ fn (c Correlation) str() string { if c.columns.len > 0 { mut columns := []string{} for col in c.columns { - columns << col.name + columns << col.sub_entity_name } s += ' (${columns.join(', ')})' @@ -710,3 +932,20 @@ struct NextValueExpr { fn (e NextValueExpr) str() string { return 'NEXT VALUE FOR ${e.name}' } + +// CURRENT_SCHEMA +struct CurrentSchemaExpr { +} + +fn (e CurrentSchemaExpr) str() string { + return 'CURRENT_SCHEMA' +} + +// SET SCHEMA +struct SetSchemaStmt { + schema_name Expr +} + +fn (e SetSchemaStmt) pstr(params map[string]Value) string { + return 'SET SCHEMA ${e.schema_name.pstr(params)}' +} diff --git a/vsql/bytes.v b/vsql/bytes.v index 5935820..987eb75 100644 --- a/vsql/bytes.v +++ b/vsql/bytes.v @@ -22,6 +22,7 @@ mut: write_string1(s string) // write_string4 is safe to use for long strings (up to 2GB). write_string4(s string) + write_identifier(identifier Identifier) write_u8(data u8) // write_u8s does not undergo any endian conversion so it's important not to // use this to write the binary representations of ints, etc. It should only @@ -52,6 +53,7 @@ mut: // read_string4 is the opposite of write_string4. It allows for strings up // to 2GB (number of characters may be less). read_string4() string + read_identifier() Identifier read_u8() u8 // read_u8s does not undergo any endian conversion so it's important not to // use this to write the binary representations of ints, etc. It should only @@ -259,12 +261,20 @@ fn (mut b BytesLittleEndian) write_string1(s string) { b.write_u8s(s.bytes()) } +fn (mut b BytesLittleEndian) write_identifier(identifier Identifier) { + b.write_string1(identifier.id()) +} + fn (mut b BytesLittleEndian) read_string1() string { len := b.read_u8() return b.read_string(len) } +fn (mut b BytesLittleEndian) read_identifier() Identifier { + return decode_identifier(b.read_string1()) +} + fn (mut b BytesLittleEndian) read_string(len int) string { return b.read_u8s(len).bytestr() } @@ -462,12 +472,20 @@ fn (mut b BytesBigEndian) write_string1(s string) { b.write_u8s(s.bytes()) } +fn (mut b BytesBigEndian) write_identifier(identifier Identifier) { + b.write_string1(identifier.id()) +} + fn (mut b BytesBigEndian) read_string1() string { len := b.read_u8() return b.read_string(len) } +fn (mut b BytesBigEndian) read_identifier() Identifier { + return decode_identifier(b.read_string1()) +} + fn (mut b BytesBigEndian) read_string(len int) string { return b.read_u8s(len).bytestr() } diff --git a/vsql/connection.v b/vsql/connection.v index 763c2fa..2c61aa1 100644 --- a/vsql/connection.v +++ b/vsql/connection.v @@ -7,6 +7,8 @@ import os import sync import time +pub const default_schema_name = 'PUBLIC' + // A Connection allows querying and other introspection for a database file. Use // open() or open_database() to create a Connection. // @@ -95,6 +97,7 @@ fn open_connection(path string, options ConnectionOptions) !&Connection { query_cache: options.query_cache options: options storage: new_storage(btree) + current_schema: vsql.default_schema_name } register_builtin_funcs(mut conn)! @@ -235,7 +238,7 @@ fn (c Connection) find_function(func_name string, arg_types []Type) !Func { pub fn (mut c Connection) register_function(prototype string, func fn ([]Value) !Value) ! { // TODO(elliotchance): A rather crude way to decode the prototype... parts := prototype.replace('(', '|').replace(')', '|').split('|') - function_name := new_identifier(parts[0].trim_space()).name + function_name := new_function_identifier(parts[0].trim_space())!.entity_name raw_args := parts[1].split(',') mut arg_types := []Type{} for arg in raw_args { @@ -258,21 +261,9 @@ pub fn (mut c Connection) register_virtual_table(create_table string, data Virtu stmt := parse(tokens)! if stmt is CreateTableStmt { - mut table_name := stmt.table_name - - // TODO(elliotchance): This isn't really ideal. Replace with a proper - // identifier chain when we support that. - if table_name.contains('.') { - parts := table_name.split('.') - - if parts[0] !in c.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - table_name = 'PUBLIC.${table_name}' - } + mut table_name := c.resolve_schema_identifier(stmt.table_name)! - c.virtual_tables[table_name] = VirtualTable{ + c.virtual_tables[table_name.id()] = VirtualTable{ create_table_sql: create_table create_table_stmt: stmt data: data @@ -313,7 +304,7 @@ pub fn (mut c Connection) sequences(schema string) ![]Sequence { mut sequences := []Sequence{} for _, sequence in c.storage.sequences()! { - if sequence.name.starts_with('${schema}.') { + if sequence.name.schema_name == schema { sequences << sequence } } @@ -333,7 +324,7 @@ pub fn (mut c Connection) schema_tables(schema string) ![]Table { mut tables := []Table{} for _, table in c.storage.tables { - if table.name.starts_with('${schema}.') { + if table.name.schema_name == schema { tables << table } } @@ -341,6 +332,49 @@ pub fn (mut c Connection) schema_tables(schema string) ![]Table { return tables } +// resolve_identifier returns a new identifier that would represent the canonical +// (fully qualified) form. +fn (c Connection) resolve_identifier(identifier Identifier) Identifier { + return Identifier{ + custom_id: identifier.custom_id + schema_name: if identifier.schema_name == '' && !identifier.entity_name.starts_with('$') { + c.current_schema + } else { + identifier.schema_name + } + entity_name: identifier.entity_name + sub_entity_name: identifier.sub_entity_name + } +} + +// resolve_schema_identifier returns the fully qualified and validates it. +fn (c Connection) resolve_schema_identifier(identifier Identifier) !Identifier { + ident := c.resolve_identifier(identifier) + + if ident.schema_name !in c.storage.schemas { + return sqlstate_3f000(ident.schema_name) // schema does not exist + } + + return ident +} + +// resolve_table_identifier returns a new identifer that would represent the +// canonical (fully qualified) form of the provided identifier or an error. +fn (c Connection) resolve_table_identifier(identifier Identifier, allow_virtual bool) !Identifier { + ident := c.resolve_schema_identifier(identifier)! + id := ident.id() + + if id in c.storage.tables { + return ident + } + + if allow_virtual && id in c.virtual_tables { + return ident + } + + return sqlstate_42p01('table', id) // table does not exist +} + // ConnectionOptions can modify the behavior of a connection when it is opened. // You should not create the ConnectionOptions instance manually. Instead, use // default_connection_options() as a starting point and modify the attributes. diff --git a/vsql/create_schema.v b/vsql/create_schema.v index 9b17176..8c9d9e8 100644 --- a/vsql/create_schema.v +++ b/vsql/create_schema.v @@ -12,7 +12,7 @@ fn execute_create_schema(mut c Connection, stmt CreateSchemaStmt, elapsed_parse c.release_write_connection() } - schema_name := stmt.schema_name.name + schema_name := stmt.schema_name.schema_name if schema_name in c.storage.schemas { return sqlstate_42p06(schema_name) // duplicate schema diff --git a/vsql/create_sequence.v b/vsql/create_sequence.v index a28ae5a..78fae8f 100644 --- a/vsql/create_sequence.v +++ b/vsql/create_sequence.v @@ -13,20 +13,7 @@ fn execute_create_sequence(mut c Connection, stmt CreateSequenceStmt, elapsed_pa c.release_write_connection() } - mut sequence_name := stmt.name.str() - - // TODO(elliotchance): This isn't really ideal. Replace with a proper - // identifier chain when we support that. - if sequence_name.contains('.') { - parts := sequence_name.split('.') - - if parts[0] !in c.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - sequence_name = 'PUBLIC.${sequence_name}' - } - + mut sequence_name := c.resolve_schema_identifier(stmt.name)! mut increment_by := i64(1) mut has_start_value := false mut start_value := i64(1) diff --git a/vsql/create_table.v b/vsql/create_table.v index 10a7619..f036ab9 100644 --- a/vsql/create_table.v +++ b/vsql/create_table.v @@ -14,22 +14,9 @@ fn execute_create_table(mut c Connection, stmt CreateTableStmt, elapsed_parse ti c.release_write_connection() } - mut table_name := stmt.table_name - - // TODO(elliotchance): This isn't really ideal. Replace with a proper - // identifier chain when we support that. - if table_name.contains('.') { - parts := table_name.split('.') - - if parts[0] !in c.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - table_name = 'PUBLIC.${table_name}' - } - - if table_name in c.storage.tables { - return sqlstate_42p07(table_name) // duplicate table + mut table_name := c.resolve_schema_identifier(stmt.table_name)! + if table_name.id() in c.storage.tables { + return sqlstate_42p07(table_name.id()) // duplicate table } mut columns := []Column{} @@ -55,10 +42,10 @@ fn execute_create_table(mut c Connection, stmt CreateTableStmt, elapsed_parse ti mut found := false for e in stmt.table_elements { if e is Column { - if e.name == column.name { + if e.name == column.sub_entity_name { match e.typ.typ { .is_smallint, .is_integer, .is_bigint { - primary_key << column.name + primary_key << column.sub_entity_name } else { return sqlstate_42601('PRIMARY KEY does not support ${e.typ}') @@ -71,7 +58,7 @@ fn execute_create_table(mut c Connection, stmt CreateTableStmt, elapsed_parse ti } if !found { - return sqlstate_42601('unknown column ${column.name} in PRIMARY KEY') + return sqlstate_42601('unknown column ${column} in PRIMARY KEY') } } } diff --git a/vsql/delete.v b/vsql/delete.v index 8ca8f8a..b0c7341 100644 --- a/vsql/delete.v +++ b/vsql/delete.v @@ -12,20 +12,7 @@ fn execute_delete(mut c Connection, stmt DeleteStmt, params map[string]Value, el c.release_write_connection() } - mut table_name := stmt.table_name - - // TODO(elliotchance): This isn't really ideal. Replace with a proper - // identifier chain when we support that. - if table_name.contains('.') { - parts := table_name.split('.') - - if parts[0] !in c.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - table_name = 'PUBLIC.${table_name}' - } - + mut table_name := c.resolve_table_identifier(stmt.table_name, false)! mut plan := create_plan(stmt, params, mut c)! if explain { @@ -35,7 +22,7 @@ fn execute_delete(mut c Connection, stmt DeleteStmt, params map[string]Value, el mut rows := plan.execute([]Row{})! for mut row in rows { - c.storage.delete_row(table_name, mut row)! + c.storage.delete_row(table_name.id(), mut row)! } return new_result_msg('DELETE ${rows.len}', elapsed_parse, t.elapsed()) diff --git a/vsql/drop_schema.v b/vsql/drop_schema.v index cf2fc79..5ca17cc 100644 --- a/vsql/drop_schema.v +++ b/vsql/drop_schema.v @@ -12,7 +12,7 @@ fn execute_drop_schema(mut c Connection, stmt DropSchemaStmt, elapsed_parse time c.release_write_connection() } - schema_name := stmt.schema_name.name + schema_name := stmt.schema_name.schema_name if schema_name !in c.storage.schemas { return sqlstate_3f000(schema_name) // schema does not exist diff --git a/vsql/drop_sequence.v b/vsql/drop_sequence.v index 17547db..1c2f73d 100644 --- a/vsql/drop_sequence.v +++ b/vsql/drop_sequence.v @@ -12,10 +12,9 @@ fn execute_drop_sequence(mut c Connection, stmt DropSequenceStmt, elapsed_parse c.release_write_connection() } - sequence := c.storage.sequence(stmt.sequence_name)! - - sequence_name := stmt.sequence_name.name - c.storage.delete_sequence(sequence_name, sequence.tid)! + name := c.resolve_schema_identifier(stmt.sequence_name)! + sequence := c.storage.sequence(name)! + c.storage.delete_sequence(name, sequence.tid)! return new_result_msg('DROP SEQUENCE 1', elapsed_parse, t.elapsed()) } diff --git a/vsql/drop_table.v b/vsql/drop_table.v index 6a939b5..61566b4 100644 --- a/vsql/drop_table.v +++ b/vsql/drop_table.v @@ -12,27 +12,11 @@ fn execute_drop_table(mut c Connection, stmt DropTableStmt, elapsed_parse time.D c.release_write_connection() } - mut table_name := stmt.table_name - - // TODO(elliotchance): This isn't really ideal. Replace with a proper - // identifier chain when we support that. - if table_name.contains('.') { - parts := table_name.split('.') - - if parts[0] !in c.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - table_name = 'PUBLIC.${table_name}' - } - - if table_name !in c.storage.tables { - return sqlstate_42p01('table', table_name) // table does not exist - } + table_name := c.resolve_table_identifier(stmt.table_name, false)! // TODO(elliotchance): Also delete rows. See // https://github.com/elliotchance/vsql/issues/65. - c.storage.delete_table(table_name, c.storage.tables[table_name].tid)! + c.storage.delete_table(table_name.id(), c.storage.tables[table_name.id()].tid)! return new_result_msg('DROP TABLE 1', elapsed_parse, t.elapsed()) } diff --git a/vsql/eval.v b/vsql/eval.v index 1293f93..aae71b1 100644 --- a/vsql/eval.v +++ b/vsql/eval.v @@ -22,47 +22,33 @@ fn new_expr_operation(conn &Connection, params map[string]Value, select_list Sel for _, table in tables { columns << table.columns for column in table.columns { - exprs << DerivedColumn{new_identifier('"${table.name}.${column.name}"'), new_identifier('"${column.name}"')} + exprs << DerivedColumn{new_column_identifier('${table.name}."${column.name}"')!, new_column_identifier('"${column.name}"')!} } } } QualifiedAsteriskExpr { - mut table_name := select_list.table_name.name - - // TODO(elliotchance): This isn't really ideal. Replace with a - // proper identifier chain when we support that. - if table_name.contains('.') { - parts := table_name.split('.') - - if parts[0] !in conn.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - table_name = 'PUBLIC.${table_name}' - } - - // panic(table_name) - table := tables[table_name] or { return sqlstate_42p01('table', table_name) } + mut table_name := conn.resolve_table_identifier(select_list.table_name, true)! + table := tables[table_name.id()] or { return sqlstate_42p01('table', table_name.str()) } columns = table.columns for column in table.columns { - exprs << DerivedColumn{new_identifier('"${table.name}.${column.name}"'), new_identifier('"${column.name}"')} + exprs << DerivedColumn{new_column_identifier('"${table.name}"."${column.name}"')!, new_column_identifier('"${column.name}"')!} } } []DerivedColumn { empty_row := new_empty_table_row(tables) for i, column in select_list { mut column_name := 'COL${i + 1}' - if column.as_clause.name != '' { - column_name = column.as_clause.name + if column.as_clause.sub_entity_name != '' { + column_name = column.as_clause.sub_entity_name } else if column.expr is Identifier { - column_name = column.expr.name + column_name = column.expr.sub_entity_name } - expr := resolve_identifiers(column.expr, tables)! + expr := resolve_identifiers(conn, column.expr, tables)! columns << Column{column_name, eval_as_type(conn, empty_row, expr, params)!, false} - exprs << DerivedColumn{expr, new_identifier('"${column_name}"')} + exprs << DerivedColumn{expr, new_column_identifier('"${column_name}"')!} } } } @@ -84,7 +70,7 @@ fn (mut o ExprOperation) execute(rows []Row) ![]Row { for row in rows { mut data := map[string]Value{} for expr in o.exprs { - data[expr.as_clause.name] = eval_as_value(mut o.conn, row, expr.expr, o.params)! + data[expr.as_clause.id()] = eval_as_value(mut o.conn, row, expr.expr, o.params)! } new_rows << new_row(data) } @@ -150,7 +136,7 @@ fn eval_as_type(conn &Connection, data Row, e Expr, params map[string]Value) !Ty return eval_as_type(conn, data, e.left, params) } Identifier { - col := data.data[e.name] or { return sqlstate_42601('unknown column: ${e.name}') } + col := data.data[e.id()] or { return sqlstate_42601('unknown column: ${e}') } return col.typ } @@ -172,7 +158,7 @@ fn eval_as_type(conn &Connection, data Row, e Expr, params map[string]Value) !Ty LocalTimestampExpr { return new_type('TIMESTAMP WITHOUT TIME ZONE', 0) } - SubstringExpr, TrimExpr { + SubstringExpr, TrimExpr, CurrentSchemaExpr { return new_type('CHARACTER VARYING', 0) } UntypedNullExpr { @@ -199,7 +185,7 @@ fn eval_as_value(mut conn Connection, data Row, e Expr, params map[string]Value) return eval_coalesce(mut conn, data, e, params) } CountAllExpr { - return eval_identifier(data, new_identifier('COUNT(*)')) + return eval_identifier(data, new_column_identifier('"COUNT(*)"')!) } Identifier { return eval_identifier(data, e) @@ -244,6 +230,9 @@ fn eval_as_value(mut conn Connection, data Row, e Expr, params map[string]Value) return new_date_value(now.strftime('%Y-%m-%d')) } + CurrentSchemaExpr { + return new_varchar_value(conn.current_schema, 0) + } CurrentTimeExpr { if e.prec > 6 { return sqlstate_42601('${e}: cannot have precision greater than 6') @@ -325,7 +314,10 @@ fn eval_as_bool(mut conn Connection, data Row, e Expr, params map[string]Value) } fn eval_identifier(data Row, e Identifier) !Value { - value := data.data[e.name] or { return sqlstate_42601('unknown column: ${e.name}') } + value := data.data[e.id()] or { + panic(e) + return sqlstate_42601('${e.id()} ${data.data} unknown column: ${e}') + } return value } @@ -341,7 +333,7 @@ fn eval_call(mut conn Connection, data Row, e CallExpr, params map[string]Value) func := conn.find_function(func_name, arg_types)! if func.is_agg { - return eval_identifier(data, new_identifier('"${e.pstr(params)}"')) + return eval_identifier(data, new_function_identifier('"${e.pstr(params)}"')!) } if e.args.len != func.arg_types.len { @@ -395,7 +387,7 @@ fn eval_nullif(mut conn Connection, data Row, e NullIfExpr, params map[string]Va fn eval_coalesce(mut conn Connection, data Row, e CoalesceExpr, params map[string]Value) !Value { // TODO(elliotchance): This is horribly inefficient. - mut typ := SQLType{} + mut typ := SQLType.is_varchar mut first := true for i, expr in e.exprs { typ2 := eval_as_type(conn, data, expr, params)! @@ -605,5 +597,6 @@ fn eval_as_nullable_value(mut conn Connection, typ SQLType, data Row, e Expr, pa if e is UntypedNullExpr { return new_null_value(typ) } + return eval_as_value(mut conn, data, e, params) } diff --git a/vsql/expr.v b/vsql/expr.v index db3af72..5019539 100644 --- a/vsql/expr.v +++ b/vsql/expr.v @@ -40,7 +40,7 @@ fn expr_is_agg(conn &Connection, e Expr, row Row, params map[string]Value) !bool } Identifier, Parameter, Value, NoExpr, RowExpr, QualifiedAsteriskExpr, QueryExpression, CurrentDateExpr, CurrentTimeExpr, CurrentTimestampExpr, LocalTimeExpr, LocalTimestampExpr, - UntypedNullExpr, NextValueExpr { + UntypedNullExpr, NextValueExpr, CurrentSchemaExpr { return false } LikeExpr { @@ -106,11 +106,11 @@ fn nested_agg_unsupported(e Expr) !bool { return sqlstate_42601('nested aggregate functions are not supported: ${e.str()}') } -fn resolve_identifiers_exprs(exprs []Expr, tables map[string]Table) ![]Expr { +fn resolve_identifiers_exprs(conn &Connection, exprs []Expr, tables map[string]Table) ![]Expr { mut new_exprs := []Expr{} for expr in exprs { - new_exprs << resolve_identifiers(expr, tables)! + new_exprs << resolve_identifiers(conn, expr, tables)! } return new_exprs @@ -118,80 +118,85 @@ fn resolve_identifiers_exprs(exprs []Expr, tables map[string]Table) ![]Expr { // resolve_identifiers will resolve the identifiers against their relevant // tables. -fn resolve_identifiers(e Expr, tables map[string]Table) !Expr { +fn resolve_identifiers(conn &Connection, e Expr, tables map[string]Table) !Expr { match e { BinaryExpr { - return BinaryExpr{resolve_identifiers(e.left, tables)!, e.op, resolve_identifiers(e.right, - tables)!} + return BinaryExpr{resolve_identifiers(conn, e.left, tables)!, e.op, resolve_identifiers(conn, + e.right, tables)!} } BetweenExpr { - return BetweenExpr{e.not, e.symmetric, resolve_identifiers(e.expr, tables)!, resolve_identifiers(e.left, - tables)!, resolve_identifiers(e.right, tables)!} + return BetweenExpr{e.not, e.symmetric, resolve_identifiers(conn, e.expr, tables)!, resolve_identifiers(conn, + e.left, tables)!, resolve_identifiers(conn, e.right, tables)!} } CallExpr { - return CallExpr{e.function_name, resolve_identifiers_exprs(e.args, tables)!} + return CallExpr{e.function_name, resolve_identifiers_exprs(conn, e.args, tables)!} } Identifier { - // TODO(elliotchance): This is super hacky. It valid for there to be - // a "." in the deliminated name. - parts := e.name.split('.') - if parts.len == 3 { + if e.custom_id != '' { return e } - if parts.len == 2 { - return new_identifier('PUBLIC.${e.name}') - } - - for _, table in tables { - if (table.column(e.name) or { Column{} }).name == e.name { - return new_identifier('${table.name}.${e}') + // If the table name is not provided we need to find it. + if e.entity_name == '' { + for _, table in tables { + if (table.column(e.sub_entity_name) or { Column{} }).name == e.sub_entity_name { + return conn.resolve_identifier(new_column_identifier('${table.name}.${e.sub_entity_name}')!) + } } } // TODO(elliotchance): Need tests for table qualifier not existing. - return e + return conn.resolve_identifier(e) } LikeExpr { - return LikeExpr{resolve_identifiers(e.left, tables)!, resolve_identifiers(e.right, - tables)!, e.not} + return LikeExpr{resolve_identifiers(conn, e.left, tables)!, resolve_identifiers(conn, + e.right, tables)!, e.not} } NullExpr { - return NullExpr{resolve_identifiers(e.expr, tables)!, e.not} + return NullExpr{resolve_identifiers(conn, e.expr, tables)!, e.not} } NullIfExpr { - return NullIfExpr{resolve_identifiers(e.a, tables)!, resolve_identifiers(e.b, - tables)!} + return NullIfExpr{resolve_identifiers(conn, e.a, tables)!, resolve_identifiers(conn, + e.b, tables)!} } TruthExpr { - return TruthExpr{resolve_identifiers(e.expr, tables)!, e.not, e.value} + return TruthExpr{resolve_identifiers(conn, e.expr, tables)!, e.not, e.value} } CastExpr { - return CastExpr{resolve_identifiers(e.expr, tables)!, e.target} + return CastExpr{resolve_identifiers(conn, e.expr, tables)!, e.target} } CoalesceExpr { - return CoalesceExpr{e.exprs.map(resolve_identifiers(it, tables)!)} + return CoalesceExpr{e.exprs.map(resolve_identifiers(conn, it, tables)!)} } SimilarExpr { - return SimilarExpr{resolve_identifiers(e.left, tables)!, resolve_identifiers(e.right, - tables)!, e.not} + return SimilarExpr{resolve_identifiers(conn, e.left, tables)!, resolve_identifiers(conn, + e.right, tables)!, e.not} } SubstringExpr { - return SubstringExpr{resolve_identifiers(e.value, tables)!, resolve_identifiers(e.from, - tables)!, resolve_identifiers(e.@for, tables)!, e.using} + return SubstringExpr{resolve_identifiers(conn, e.value, tables)!, resolve_identifiers(conn, + e.from, tables)!, resolve_identifiers(conn, e.@for, tables)!, e.using} } UnaryExpr { - return UnaryExpr{e.op, resolve_identifiers(e.expr, tables)!} + return UnaryExpr{e.op, resolve_identifiers(conn, e.expr, tables)!} } - CountAllExpr, Parameter, Value, NoExpr, RowExpr, QueryExpression, QualifiedAsteriskExpr, - CurrentDateExpr, CurrentTimeExpr, CurrentTimestampExpr, LocalTimeExpr, LocalTimestampExpr, - UntypedNullExpr, NextValueExpr { + NextValueExpr { + return NextValueExpr{conn.resolve_identifier(e.name)} + } + RowExpr { + return RowExpr{resolve_identifiers_exprs(conn, e.exprs, tables)!} + } + QualifiedAsteriskExpr { + return QualifiedAsteriskExpr{resolve_identifiers(conn, e.table_name, tables)! as Identifier} + } + CountAllExpr, Parameter, Value, NoExpr, QueryExpression, CurrentDateExpr, CurrentTimeExpr, + CurrentTimestampExpr, LocalTimeExpr, LocalTimestampExpr, UntypedNullExpr, + CurrentSchemaExpr { // These don't have any Expr properties to recurse. return e } TrimExpr { - return TrimExpr{e.specification, resolve_identifiers(e.character, tables)!, resolve_identifiers(e.source, - tables)!} + return TrimExpr{e.specification, resolve_identifiers(conn, e.character, tables)!, resolve_identifiers(conn, + e.source, tables)!} } } } diff --git a/vsql/grammar.v b/vsql/grammar.v index 078438c..0fafeaa 100644 --- a/vsql/grammar.v +++ b/vsql/grammar.v @@ -11,6 +11,7 @@ type EarleyValue = BetweenExpr | DerivedColumn | Expr | Identifier + | IdentifierChain | InsertStmt | LikeExpr | QualifiedAsteriskExpr @@ -376,9 +377,15 @@ fn get_grammar() map[string]EarleyRule { mut rule_column_name_list_ := &EarleyRule{ name: '' } + mut rule_column_name_1_ := &EarleyRule{ + name: '' + } mut rule_column_name_ := &EarleyRule{ name: '' } + mut rule_column_reference_1_ := &EarleyRule{ + name: '' + } mut rule_column_reference_ := &EarleyRule{ name: '' } @@ -493,6 +500,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_contextually_typed_value_specification_ := &EarleyRule{ name: '' } + mut rule_correlation_name_1_ := &EarleyRule{ + name: '' + } mut rule_correlation_name_ := &EarleyRule{ name: '' } @@ -772,6 +782,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_general_set_function_ := &EarleyRule{ name: '' } + mut rule_general_value_specification_2_ := &EarleyRule{ + name: '' + } mut rule_general_value_specification_ := &EarleyRule{ name: '' } @@ -1072,6 +1085,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_preparable_sql_schema_statement_ := &EarleyRule{ name: '' } + mut rule_preparable_sql_session_statement_ := &EarleyRule{ + name: '' + } mut rule_preparable_sql_transaction_statement_ := &EarleyRule{ name: '' } @@ -1168,6 +1184,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_routine_invocation_ := &EarleyRule{ name: '' } + mut rule_routine_name_1_ := &EarleyRule{ + name: '' + } mut rule_routine_name_ := &EarleyRule{ name: '' } @@ -1213,6 +1232,12 @@ fn get_grammar() map[string]EarleyRule { mut rule_schema_definition_ := &EarleyRule{ name: '' } + mut rule_schema_name_characteristic_1_ := &EarleyRule{ + name: '' + } + mut rule_schema_name_characteristic_ := &EarleyRule{ + name: '' + } mut rule_schema_name_clause_ := &EarleyRule{ name: '' } @@ -1297,6 +1322,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_sequence_generator_minvalue_option_ := &EarleyRule{ name: '' } + mut rule_sequence_generator_name_1_ := &EarleyRule{ + name: '' + } mut rule_sequence_generator_name_ := &EarleyRule{ name: '' } @@ -1336,6 +1364,12 @@ fn get_grammar() map[string]EarleyRule { mut rule_set_function_type_ := &EarleyRule{ name: '' } + mut rule_set_schema_statement_1_ := &EarleyRule{ + name: '' + } + mut rule_set_schema_statement_ := &EarleyRule{ + name: '' + } mut rule_set_target_ := &EarleyRule{ name: '' } @@ -1423,6 +1457,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_sql_schema_statement_ := &EarleyRule{ name: '' } + mut rule_sql_session_statement_ := &EarleyRule{ + name: '' + } mut rule_sql_transaction_statement_ := &EarleyRule{ name: '' } @@ -1507,6 +1544,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_table_factor_ := &EarleyRule{ name: '
' } + mut rule_table_name_1_ := &EarleyRule{ + name: '
' + } mut rule_table_name_ := &EarleyRule{ name: '
' } @@ -1657,6 +1697,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_unique_specification_ := &EarleyRule{ name: '' } + mut rule_unqualified_schema_name_1_ := &EarleyRule{ + name: '' + } mut rule_unqualified_schema_name_ := &EarleyRule{ name: '' } @@ -1705,6 +1748,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_value_expression_ := &EarleyRule{ name: '' } + mut rule_value_specification_ := &EarleyRule{ + name: '' + } mut rule_where_clause_1_ := &EarleyRule{ name: '' } @@ -1957,6 +2003,9 @@ fn get_grammar() map[string]EarleyRule { mut rule_current_date := &EarleyRule{ name: 'CURRENT_DATE' } + mut rule_current_schema := &EarleyRule{ + name: 'CURRENT_SCHEMA' + } mut rule_current_time := &EarleyRule{ name: 'CURRENT_TIME' } @@ -4149,18 +4198,30 @@ fn get_grammar() map[string]EarleyRule { }, ]} - rule_column_name_.productions << &EarleyProduction{[ + rule_column_name_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_identifier_ }, ]} - rule_column_reference_.productions << &EarleyProduction{[ + rule_column_name_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_column_name_1_ + }, + ]} + + rule_column_reference_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_basic_identifier_chain_ }, ]} + rule_column_reference_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_column_reference_1_ + }, + ]} + rule_comma_.productions << &EarleyProduction{[ &EarleyRuleOrString{ str: ',' @@ -4550,12 +4611,18 @@ fn get_grammar() map[string]EarleyRule { }, ]} - rule_correlation_name_.productions << &EarleyProduction{[ + rule_correlation_name_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_identifier_ }, ]} + rule_correlation_name_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_correlation_name_1_ + }, + ]} + rule_correlation_or_recognition_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_correlation_name_ @@ -5523,11 +5590,22 @@ fn get_grammar() map[string]EarleyRule { }, ]} + rule_general_value_specification_2_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_current_schema + }, + ]} + rule_general_value_specification_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_host_parameter_specification_ }, ]} + rule_general_value_specification_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_general_value_specification_2_ + }, + ]} rule_greater_than_operator_.productions << &EarleyProduction{[ &EarleyRuleOrString{ @@ -7884,6 +7962,12 @@ fn get_grammar() map[string]EarleyRule { }, ]} + rule_preparable_sql_session_statement_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_sql_session_statement_ + }, + ]} + rule_preparable_sql_transaction_statement_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_sql_transaction_statement_ @@ -7905,6 +7989,11 @@ fn get_grammar() map[string]EarleyRule { rule: rule_preparable_sql_transaction_statement_ }, ]} + rule_preparable_statement_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_preparable_sql_session_statement_ + }, + ]} rule_qualified_asterisk_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ @@ -8218,12 +8307,18 @@ fn get_grammar() map[string]EarleyRule { }, ]} - rule_routine_name_.productions << &EarleyProduction{[ + rule_routine_name_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_qualified_identifier_ }, ]} + rule_routine_name_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_routine_name_1_ + }, + ]} + rule_row_or_rows_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_row @@ -8356,6 +8451,21 @@ fn get_grammar() map[string]EarleyRule { }, ]} + rule_schema_name_characteristic_1_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_schema + }, + &EarleyRuleOrString{ + rule: rule_value_specification_ + }, + ]} + + rule_schema_name_characteristic_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_schema_name_characteristic_1_ + }, + ]} + rule_schema_name_clause_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_schema_name_ @@ -8612,12 +8722,18 @@ fn get_grammar() map[string]EarleyRule { }, ]} - rule_sequence_generator_name_.productions << &EarleyProduction{[ + rule_sequence_generator_name_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_schema_qualified_name_ }, ]} + rule_sequence_generator_name_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_sequence_generator_name_1_ + }, + ]} + rule_sequence_generator_option_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_common_sequence_generator_options_ @@ -8721,6 +8837,21 @@ fn get_grammar() map[string]EarleyRule { }, ]} + rule_set_schema_statement_1_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_set + }, + &EarleyRuleOrString{ + rule: rule_schema_name_characteristic_ + }, + ]} + + rule_set_schema_statement_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_set_schema_statement_1_ + }, + ]} + rule_set_target_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_update_target_ @@ -9022,6 +9153,12 @@ fn get_grammar() map[string]EarleyRule { }, ]} + rule_sql_session_statement_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_set_schema_statement_ + }, + ]} + rule_sql_transaction_statement_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_start_transaction_statement_ @@ -9276,12 +9413,18 @@ fn get_grammar() map[string]EarleyRule { }, ]} - rule_table_name_.productions << &EarleyProduction{[ + rule_table_name_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_local_or_schema_qualified_name_ }, ]} + rule_table_name_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_table_name_1_ + }, + ]} + rule_table_or_query_name_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_table_name_ @@ -9764,12 +9907,18 @@ fn get_grammar() map[string]EarleyRule { }, ]} - rule_unqualified_schema_name_.productions << &EarleyProduction{[ + rule_unqualified_schema_name_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_identifier_ }, ]} + rule_unqualified_schema_name_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_unqualified_schema_name_1_ + }, + ]} + rule_unsigned_integer_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule__integer @@ -9925,6 +10074,17 @@ fn get_grammar() map[string]EarleyRule { }, ]} + rule_value_specification_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_literal_ + }, + ]} + rule_value_specification_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_general_value_specification_ + }, + ]} + rule_where_clause_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_where @@ -10528,6 +10688,13 @@ fn get_grammar() map[string]EarleyRule { }, ]} + rule_current_schema.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + str: 'CURRENT_SCHEMA' + rule: 0 + }, + ]} + rule_current_time.productions << &EarleyProduction{[ &EarleyRuleOrString{ str: 'CURRENT_TIME' @@ -12857,7 +13024,9 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_column_name_list_1_ rules[''] = rule_column_name_list_2_ rules[''] = rule_column_name_list_ + rules[''] = rule_column_name_1_ rules[''] = rule_column_name_ + rules[''] = rule_column_reference_1_ rules[''] = rule_column_reference_ rules[''] = rule_comma_ rules[''] = rule_commit_statement_1_ @@ -12896,6 +13065,7 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_contextually_typed_table_value_constructor_1_ rules[''] = rule_contextually_typed_table_value_constructor_ rules[''] = rule_contextually_typed_value_specification_ + rules[''] = rule_correlation_name_1_ rules[''] = rule_correlation_name_ rules[''] = rule_correlation_or_recognition_1_ rules[''] = rule_correlation_or_recognition_2_ @@ -12989,6 +13159,7 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_general_literal_ rules[''] = rule_general_set_function_1_ rules[''] = rule_general_set_function_ + rules[''] = rule_general_value_specification_2_ rules[''] = rule_general_value_specification_ rules[''] = rule_greater_than_operator_ rules[''] = rule_greater_than_or_equals_operator_ @@ -13089,6 +13260,7 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_predicate_ rules[''] = rule_preparable_sql_data_statement_ rules[''] = rule_preparable_sql_schema_statement_ + rules[''] = rule_preparable_sql_session_statement_ rules[''] = rule_preparable_sql_transaction_statement_ rules[''] = rule_preparable_statement_ rules[''] = rule_qualified_asterisk_1_ @@ -13121,6 +13293,7 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_rollback_statement_ rules[''] = rule_routine_invocation_1_ rules[''] = rule_routine_invocation_ + rules[''] = rule_routine_name_1_ rules[''] = rule_routine_name_ rules[''] = rule_row_or_rows_ rules[''] = rule_row_subquery_ @@ -13136,6 +13309,8 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_row_value_predicand_ rules[''] = rule_schema_definition_1_ rules[''] = rule_schema_definition_ + rules[''] = rule_schema_name_characteristic_1_ + rules[''] = rule_schema_name_characteristic_ rules[''] = rule_schema_name_clause_ rules[''] = rule_schema_name_ rules[''] = rule_schema_qualified_name_2_ @@ -13164,6 +13339,7 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_sequence_generator_minvalue_option_1_ rules[''] = rule_sequence_generator_minvalue_option_2_ rules[''] = rule_sequence_generator_minvalue_option_ + rules[''] = rule_sequence_generator_name_1_ rules[''] = rule_sequence_generator_name_ rules[''] = rule_sequence_generator_option_ rules[''] = rule_sequence_generator_options_ @@ -13177,6 +13353,8 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_set_clause_ rules[''] = rule_set_function_specification_ rules[''] = rule_set_function_type_ + rules[''] = rule_set_schema_statement_1_ + rules[''] = rule_set_schema_statement_ rules[''] = rule_set_target_ rules[''] = rule_sign_ rules[''] = rule_signed_numeric_literal_1_ @@ -13206,6 +13384,7 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_sql_schema_definition_statement_ rules[''] = rule_sql_schema_manipulation_statement_ rules[''] = rule_sql_schema_statement_ + rules[''] = rule_sql_session_statement_ rules[''] = rule_sql_transaction_statement_ rules[''] = rule_square_root_1_ rules[''] = rule_square_root_ @@ -13234,6 +13413,7 @@ fn get_grammar() map[string]EarleyRule { rules['
'] = rule_table_expression_4_ rules['
'] = rule_table_expression_ rules['
'] = rule_table_factor_ + rules['
'] = rule_table_name_1_ rules['
'] = rule_table_name_ rules['
'] = rule_table_or_query_name_ rules['
'] = rule_table_primary_1_ @@ -13284,6 +13464,7 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_unique_constraint_definition_ rules[''] = rule_unique_specification_1_ rules[''] = rule_unique_specification_ + rules[''] = rule_unqualified_schema_name_1_ rules[''] = rule_unqualified_schema_name_ rules[''] = rule_unsigned_integer_ rules[''] = rule_unsigned_literal_ @@ -13300,6 +13481,7 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_value_expression_list_ rules[''] = rule_value_expression_primary_ rules[''] = rule_value_expression_ + rules[''] = rule_value_specification_ rules[''] = rule_where_clause_1_ rules[''] = rule_where_clause_ rules[''] = rule_with_or_without_time_zone_1_ @@ -13384,6 +13566,7 @@ fn get_grammar() map[string]EarleyRule { rules['COUNT'] = rule_count rules['CREATE'] = rule_create rules['CURRENT_DATE'] = rule_current_date + rules['CURRENT_SCHEMA'] = rule_current_schema rules['CURRENT_TIME'] = rule_current_time rules['CURRENT_TIMESTAMP'] = rule_current_timestamp rules['CURSOR_NAME'] = rule_cursor_name @@ -13712,7 +13895,7 @@ fn parse_ast(node &EarleyNode) ![]EarleyValue { return [EarleyValue(node.value.end_column.value)] } '^identifier' { - return [EarleyValue(new_identifier(node.value.end_column.value))] + return [EarleyValue(IdentifierChain{node.value.end_column.value})] } '^string' { return [ @@ -13979,6 +14162,14 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { EarleyValue(parse_column_name_list2(children[0] as []Identifier, children[2] as Identifier)!), ] } + '' { + return [EarleyValue(parse_column_name(children[0] as IdentifierChain)!)] + } + '' { + return [ + EarleyValue(parse_column_reference(children[0] as IdentifierChain)!), + ] + } '' { return [EarleyValue(parse_commit()!)] } @@ -14052,6 +14243,11 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { '' { return [EarleyValue(parse_exprs(children[1] as []Expr)!)] } + '' { + return [ + EarleyValue(parse_correlation_name(children[0] as IdentifierChain)!), + ] + } '' { return [EarleyValue(parse_correlation1(children[0] as Identifier)!)] } @@ -14234,6 +14430,9 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { EarleyValue(parse_general_set_function(children[0] as string, children[2] as Expr)!), ] } + '' { + return [EarleyValue(parse_current_schema()!)] + } '' { return [EarleyValue(parse_exprs(children[2] as []Expr)!)] } @@ -14252,12 +14451,12 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { } '' { return [ - EarleyValue(parse_host_parameter_name(children[1] as Identifier)!), + EarleyValue(parse_host_parameter_name(children[1] as IdentifierChain)!), ] } '' { return [ - EarleyValue(parse_identifier_chain1(children[0] as Identifier, children[2] as Identifier)!), + EarleyValue(parse_identifier_chain1(children[0] as IdentifierChain, children[2] as IdentifierChain)!), ] } '' { @@ -14283,7 +14482,7 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { '' { return [ EarleyValue(parse_local_or_schema_qualified_name2(children[0] as Identifier, - children[2] as Identifier)!), + children[2] as IdentifierChain)!), ] } '' { @@ -14360,7 +14559,7 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { } '' { return [ - EarleyValue(parse_qualified_asterisk(children[0] as Identifier, children[2] as string)!), + EarleyValue(parse_qualified_asterisk(children[0] as IdentifierChain, children[2] as string)!), ] } '' { @@ -14440,6 +14639,9 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { EarleyValue(parse_routine_invocation(children[0] as Identifier, children[1] as []Expr)!), ] } + '' { + return [EarleyValue(parse_routine_name(children[0] as IdentifierChain)!)] + } '' { return [EarleyValue(parse_expr_to_list(children[0] as Expr)!)] } @@ -14459,9 +14661,12 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { '' { return [EarleyValue(parse_schema_definition(children[2] as Identifier)!)] } + '' { + return [EarleyValue(parse_expr(children[1] as Expr)!)] + } '' { return [ - EarleyValue(parse_schema_qualified_name_2(children[0] as Identifier, children[2] as Identifier)!), + EarleyValue(parse_schema_qualified_name_2(children[0] as Identifier, children[2] as IdentifierChain)!), ] } '
' { + return [EarleyValue(parse_table_name(children[0] as IdentifierChain)!)] + } '
' { return [ EarleyValue(parse_table_primary_identifier(children[0] as Identifier)!), @@ -14722,6 +14938,11 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { '' { return [EarleyValue(parse_ignore()!)] } + '' { + return [ + EarleyValue(parse_unqualified_schema_name(children[0] as IdentifierChain)!), + ] + } '' { return [EarleyValue(parse_value_to_expr(children[0] as Value)!)] } diff --git a/vsql/group.v b/vsql/group.v index a9d6bee..4725aa7 100644 --- a/vsql/group.v +++ b/vsql/group.v @@ -33,7 +33,7 @@ fn new_group_operation(select_exprs []DerivedColumn, group_exprs []Expr, params columns << table.column(name[name.len - 1])! } - empty_row := new_empty_row(table.columns, table.name) + empty_row := new_empty_row(table.columns, table.name.str()) for expr in select_exprs { if expr_is_agg(conn, expr.expr, empty_row, params)! { columns << Column{expr.expr.pstr(params), eval_as_type(conn, empty_row, expr.expr, diff --git a/vsql/header.v b/vsql/header.v index f1023c1..b5ae398 100644 --- a/vsql/header.v +++ b/vsql/header.v @@ -7,7 +7,7 @@ import os // This is a rudimentary way to ensure that small changes to storage.v are // compatible as things change so rapidly. Sorry if you had a database in a // previous version, you'll need to recreate it. -const current_version = i8(10) +const current_version = i8(11) // The Header contains important metadata about the database and always occupies // the first page of the database. diff --git a/vsql/insert.v b/vsql/insert.v index 470abc4..33c477b 100644 --- a/vsql/insert.v +++ b/vsql/insert.v @@ -21,31 +21,14 @@ fn execute_insert(mut c Connection, stmt InsertStmt, params map[string]Value, el } mut row := map[string]Value{} + mut table_name := c.resolve_table_identifier(stmt.table_name, false)! - mut table_name := stmt.table_name - - // TODO(elliotchance): This isn't really ideal. Replace with a proper - // identifier chain when we support that. - if table_name.contains('.') { - parts := table_name.split('.') - - if parts[0] !in c.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - table_name = 'PUBLIC.${table_name}' - } - - if table_name !in c.storage.tables { - return sqlstate_42p01('table', table_name) // table not found - } - - table := c.storage.tables[table_name] + table := c.storage.tables[table_name.id()] for i, column in stmt.columns { - column_name := column.name + column_name := column.sub_entity_name table_column := table.column(column_name)! - raw_value := eval_as_nullable_value(mut c, table_column.typ.typ, Row{}, stmt.values[i], - params)! + raw_value := eval_as_nullable_value(mut c, table_column.typ.typ, Row{}, resolve_identifiers(c, + stmt.values[i], c.storage.tables)!, params)! value := cast(c, 'for column ${column_name}', raw_value, table_column.typ)! if value.is_null && table_column.not_null { diff --git a/vsql/parse.v b/vsql/parse.v index 7630900..3f50443 100644 --- a/vsql/parse.v +++ b/vsql/parse.v @@ -25,8 +25,8 @@ fn parse_select_sublist2(column QualifiedAsteriskExpr) !SelectList { return column } -fn parse_qualified_asterisk(column Identifier, _ string) !QualifiedAsteriskExpr { - return QualifiedAsteriskExpr{column} +fn parse_qualified_asterisk(column IdentifierChain, _ string) !QualifiedAsteriskExpr { + return QualifiedAsteriskExpr{new_column_identifier(column.identifier)!} } fn parse_derived_column(expr Expr) !DerivedColumn { @@ -70,7 +70,7 @@ fn parse_qualified_join2(left_table TableReference, join_type string, right_tabl } fn parse_table_definition(table_name Identifier, table_contents_source []TableElement) !Stmt { - return CreateTableStmt{table_name.name, table_contents_source} + return CreateTableStmt{table_name, table_contents_source} } fn parse_table_elements1(table_element TableElement) ![]TableElement { @@ -83,12 +83,16 @@ fn parse_table_elements2(table_elements []TableElement, table_element TableEleme return new_table_elements } +fn parse_column_name(column_name IdentifierChain) !Identifier { + return new_column_identifier(column_name.identifier) +} + fn parse_column_definition1(column_name Identifier, data_type Type) !TableElement { - return Column{column_name.name, data_type, false} + return Column{column_name.sub_entity_name, data_type, false} } fn parse_column_definition2(column_name Identifier, data_type Type, constraint bool) !TableElement { - return Column{column_name.name, data_type, constraint} + return Column{column_name.sub_entity_name, data_type, constraint} } fn parse_bigint() !Type { @@ -132,7 +136,11 @@ fn parse_real() !Type { } fn parse_drop_table_statement(table_name Identifier) !Stmt { - return DropTableStmt{table_name.name} + return DropTableStmt{table_name} +} + +fn parse_set_schema_stmt(schema_name Expr) !Stmt { + return SetSchemaStmt{schema_name} } fn parse_table_element_list(table_elements []TableElement) ![]TableElement { @@ -140,11 +148,14 @@ fn parse_table_element_list(table_elements []TableElement) ![]TableElement { } fn parse_insert_statement(insertion_target Identifier, stmt InsertStmt) !Stmt { - return InsertStmt{insertion_target.name, stmt.columns, stmt.values} + return InsertStmt{insertion_target, stmt.columns, stmt.values} } fn parse_from_constructor(columns []Identifier, values []Expr) !InsertStmt { - return InsertStmt{'', columns, values} + return InsertStmt{ + columns: columns + values: values + } } fn parse_column_name_list1(column_name Identifier) ![]Identifier { @@ -301,11 +312,11 @@ fn parse_mod(a Expr, b Expr) !Expr { } fn parse_delete_statement(table_name Identifier) !Stmt { - return DeleteStmt{table_name.name, NoExpr{}} + return DeleteStmt{table_name, NoExpr{}} } fn parse_delete_statement_where(table_name Identifier, where Expr) !Stmt { - return DeleteStmt{table_name.name, where} + return DeleteStmt{table_name, where} } fn parse_comparison_part(op string, expr Expr) !ComparisonPredicatePart2 { @@ -333,11 +344,11 @@ fn parse_unknown() !Value { } fn parse_update_statement(target_table Identifier, set_clause_list map[string]Expr) !Stmt { - return UpdateStmt{target_table.name, set_clause_list, NoExpr{}} + return UpdateStmt{target_table, set_clause_list, NoExpr{}} } fn parse_update_statement_where(target_table Identifier, set_clause_list map[string]Expr, where Expr) !Stmt { - return UpdateStmt{target_table.name, set_clause_list, where} + return UpdateStmt{target_table, set_clause_list, where} } fn parse_set_clause_append(set_clause_list map[string]Expr, set_clause map[string]Expr) !map[string]Expr { @@ -353,7 +364,7 @@ fn parse_set_clause_append(set_clause_list map[string]Expr, set_clause map[strin fn parse_set_clause(target Identifier, update_source Expr) !map[string]Expr { return { - target.name: update_source + target.str(): update_source } } @@ -454,11 +465,11 @@ fn parse_empty_exprs() ![]Expr { } fn parse_routine_invocation(name Identifier, args []Expr) !Expr { - return CallExpr{name.name, args} + return CallExpr{name.entity_name, args} } -fn parse_host_parameter_name(name Identifier) !Expr { - return Parameter{name.original} +fn parse_host_parameter_name(name IdentifierChain) !Expr { + return Parameter{name.identifier} } fn parse_unique_constraint_definition(columns []Identifier) !TableElement { @@ -648,8 +659,28 @@ fn parse_general_set_function(name string, expr Expr) !Expr { return CallExpr{name, [expr]} } -fn parse_identifier_chain1(a Identifier, b Identifier) !Identifier { - return new_identifier(a.name + '.' + b.name) +fn parse_identifier_chain1(a IdentifierChain, b IdentifierChain) !IdentifierChain { + return IdentifierChain{a.identifier + '.' + b.identifier} +} + +fn parse_column_reference(identifier IdentifierChain) !Identifier { + return new_column_identifier(identifier.identifier) +} + +fn parse_correlation_name(identifier IdentifierChain) !Identifier { + return new_column_identifier(identifier.identifier) +} + +fn parse_sequence_generator_name(identifier IdentifierChain) !Identifier { + return new_table_identifier(identifier.identifier) +} + +fn parse_unqualified_schema_name(identifier IdentifierChain) !Identifier { + return new_schema_identifier(identifier.identifier) +} + +fn parse_table_name(identifier IdentifierChain) !Identifier { + return new_table_identifier(identifier.identifier) } fn parse_joined_table(join QualifiedJoin) !TableReference { @@ -765,8 +796,12 @@ fn parse_schema_definition(schema_name Identifier) !Stmt { return CreateSchemaStmt{schema_name} } -fn parse_local_or_schema_qualified_name2(schema_name Identifier, table_name Identifier) !Identifier { - return new_identifier('${schema_name}.${table_name}') +fn parse_routine_name(identifier IdentifierChain) !Identifier { + return new_function_identifier(identifier.identifier) +} + +fn parse_local_or_schema_qualified_name2(schema_name Identifier, table_name IdentifierChain) !IdentifierChain { + return IdentifierChain{'${schema_name.schema_name}.${table_name}'} } fn parse_drop_schema_statement(schema_name Identifier, behavior string) !Stmt { @@ -829,8 +864,8 @@ fn parse_coalesce(exprs []Expr) !Expr { return CoalesceExpr{exprs} } -fn parse_string_identifier(s string) !Identifier { - return new_identifier(s) +fn parse_string_identifier(s string) !IdentifierChain { + return IdentifierChain{s} } fn parse_basic_sequence_generator_option_1(option SequenceGeneratorIncrementByOption) !SequenceGeneratorOption { @@ -863,8 +898,8 @@ fn parse_sequence_generator_options_2(options []SequenceGeneratorOption, option return new_options } -fn parse_schema_qualified_name_2(schema_name Identifier, identifier Identifier) !Identifier { - return new_identifier('${schema_name}.${identifier}') +fn parse_schema_qualified_name_2(schema_name Identifier, identifier IdentifierChain) !IdentifierChain { + return IdentifierChain{'${schema_name.schema_name}.${identifier}'} } fn parse_sequence_generator_definition_1(generator_name Identifier) !Stmt { @@ -948,3 +983,7 @@ fn parse_alter_sequence_generator_statement(generator_name Identifier, options [ fn parse_alter_sequence_generator_option_1(option SequenceGeneratorRestartOption) !SequenceGeneratorOption { return option } + +fn parse_current_schema() !Expr { + return CurrentSchemaExpr{} +} diff --git a/vsql/planner.v b/vsql/planner.v index 321f357..d99c65c 100644 --- a/vsql/planner.v +++ b/vsql/planner.v @@ -39,7 +39,7 @@ fn create_plan(stmt Stmt, params map[string]Value, mut c Connection) !Plan { fn create_basic_plan(body SimpleTable, offset Expr, params map[string]Value, mut c Connection, allow_virtual bool, correlation Correlation) !(Plan, map[string]Table) { match body { SelectStmt { - return create_select_plan(body, offset, params, mut c, allow_virtual, true) + return create_select_plan(body, offset, params, mut c, allow_virtual) } // VALUES []RowExpr { @@ -53,45 +53,45 @@ fn create_basic_plan(body SimpleTable, offset Expr, params map[string]Value, mut } } -fn create_select_plan(body SelectStmt, offset Expr, params map[string]Value, mut c Connection, allow_virtual bool, is_for_select bool) !(Plan, map[string]Table) { +fn create_select_plan(body SelectStmt, offset Expr, params map[string]Value, mut c Connection, allow_virtual bool) !(Plan, map[string]Table) { from_clause := body.table_expression.from_clause match from_clause { TablePrimary { plan, table := create_select_plan_without_join(body, from_clause, offset, - params, mut c, allow_virtual, is_for_select)! + params, mut c, allow_virtual)! return plan, { - table.name: table + table.name.id(): table } } QualifiedJoin { left_table_clause := from_clause.left_table as TablePrimary left_plan, left_table := create_select_plan_without_join(body, left_table_clause, - offset, params, mut c, allow_virtual, is_for_select)! + offset, params, mut c, allow_virtual)! right_table_clause := from_clause.right_table as TablePrimary right_plan, right_table := create_select_plan_without_join(body, right_table_clause, - offset, params, mut c, allow_virtual, is_for_select)! + offset, params, mut c, allow_virtual)! mut plan := Plan{} plan.subplans['\$1'] = left_plan plan.subplans['\$2'] = right_plan tables := { - left_table.name: left_table - right_table.name: right_table + left_table.name.id(): left_table + right_table.name.id(): right_table } plan.operations << new_join_operation(left_plan.columns(), from_clause.join_type, - right_plan.columns(), resolve_identifiers(from_clause.specification, tables)!, - params, c, plan, c.storage) + right_plan.columns(), resolve_identifiers(c, from_clause.specification, + tables)!, params, c, plan, c.storage) return plan, tables } } } -fn create_select_plan_without_join(body SelectStmt, from_clause TablePrimary, offset Expr, params map[string]Value, mut c Connection, allow_virtual bool, is_for_select bool) !(Plan, Table) { +fn create_select_plan_without_join(body SelectStmt, from_clause TablePrimary, offset Expr, params map[string]Value, mut c Connection, allow_virtual bool) !(Plan, Table) { mut plan := Plan{} mut covered_by_pk := false where := body.table_expression.where_clause @@ -99,68 +99,56 @@ fn create_select_plan_without_join(body SelectStmt, from_clause TablePrimary, of match from_clause.body { Identifier { - mut table_name := from_clause.body.name + mut table_name := c.resolve_table_identifier(from_clause.body, allow_virtual)! + table_name_id := table_name.id() - // TODO(elliotchance): This isn't really ideal. Replace with a proper - // identifier chain when we support that. - if table_name.contains('.') { - parts := table_name.split('.') - - if parts[0] !in c.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - table_name = 'PUBLIC.${table_name}' - } - - if allow_virtual && table_name in c.virtual_tables { - plan.operations << VirtualTableOperation{table_name, c.virtual_tables[table_name]} - table = c.virtual_tables[table_name].table() - } else if table_name in c.storage.tables { - table = c.storage.tables[table_name] + if allow_virtual && table_name_id in c.virtual_tables { + plan.operations << VirtualTableOperation{table_name_id, c.virtual_tables[table_name_id]} + table = c.virtual_tables[table_name_id].table() + } else if table_name_id in c.storage.tables { + table = c.storage.tables[table_name_id] // This is a special case to handle "PRIMARY KEY = INTEGER". if table.primary_key.len > 0 && where is BinaryExpr { left := where.left right := where.right if where.op == '=' && left is Identifier { - if left.name == table.primary_key[0] { + if left.sub_entity_name == table.primary_key[0] { covered_by_pk = true plan.operations << new_primary_key_operation(table, right, - right, params, c, is_for_select) + right, params, c) } } } if !covered_by_pk { - plan.operations << TableOperation{table_name, false, table, params, c, is_for_select, plan.subplans, c.storage} + plan.operations << TableOperation{table_name, false, table, params, c, plan.subplans, c.storage} } if where !is NoExpr && !covered_by_pk { last_operation := plan.operations[plan.operations.len - 1] - mut resolved_where := where - if is_for_select { - resolved_where = resolve_identifiers(where, { - table.name: table - })! - } + mut resolved_where := resolve_identifiers(c, where, { + table.name.id(): table + })! plan.operations << new_where_operation(resolved_where, params, c, last_operation.columns()) } } else { - return sqlstate_42p01('table', table_name) + return sqlstate_42p01('table', table_name.str()) } } QueryExpression { // TODO(elliotchance): Needs to increment. - mut table_name := '\$1' + mut table_name := Identifier{ + custom_id: '\$1' + } - if from_clause.correlation.name.name != '' { - table_name = from_clause.correlation.name.name + if from_clause.correlation.name.sub_entity_name != '' { + table_name = from_clause.correlation.name } subplan := create_query_expression_plan(from_clause.body, params, mut c, from_clause.correlation)! - plan.subplans[table_name] = subplan + plan.subplans[table_name.id()] = subplan // NOTE: This has to be assigned to a variable otherwise the value // is lost. This must be a bug in V. @@ -169,7 +157,7 @@ fn create_select_plan_without_join(body SelectStmt, from_clause TablePrimary, of columns: subplan.columns() } - plan.operations << TableOperation{table_name, true, table, params, c, is_for_select, plan.subplans, c.storage} + plan.operations << TableOperation{table_name, true, table, params, c, plan.subplans, c.storage} } } @@ -180,14 +168,14 @@ fn create_select_plan_without_join(body SelectStmt, from_clause TablePrimary, of match body.exprs { []DerivedColumn { tables := { - table.name: table + table.name.id(): table } - group_exprs := resolve_identifiers_exprs(body.table_expression.group_clause, + group_exprs := resolve_identifiers_exprs(c, body.table_expression.group_clause, tables)! mut select_exprs := []DerivedColumn{} for expr in body.exprs { - select_exprs << DerivedColumn{resolve_identifiers(expr.expr, tables)!, expr.as_clause} + select_exprs << DerivedColumn{resolve_identifiers(c, expr.expr, tables)!, expr.as_clause} } add_group_by_plan(mut &plan, group_exprs, select_exprs, params, mut c, table)! @@ -206,7 +194,7 @@ fn add_group_by_plan(mut plan Plan, group_clause []Expr, select_exprs []DerivedC // GROUP BY for the whole set. mut has_agg := false empty_row := new_empty_table_row({ - table.name: table + table.name.id(): table }) for e in select_exprs { if expr_is_agg(c, e.expr, empty_row, params)! { @@ -241,13 +229,13 @@ fn create_delete_plan(stmt DeleteStmt, params map[string]Value, mut c Connection exprs: AsteriskExpr(true) table_expression: TableExpression{ from_clause: TablePrimary{ - body: new_identifier(stmt.table_name) + body: stmt.table_name } where_clause: stmt.where } } - plan, _ := create_select_plan(select_stmt, NoExpr{}, params, mut c, false, false)! + plan, _ := create_select_plan(select_stmt, NoExpr{}, params, mut c, false)! return plan } @@ -257,13 +245,13 @@ fn create_update_plan(stmt UpdateStmt, params map[string]Value, mut c Connection exprs: AsteriskExpr(true) table_expression: TableExpression{ from_clause: TablePrimary{ - body: new_identifier(stmt.table_name) + body: stmt.table_name } where_clause: stmt.where } } - plan, _ := create_select_plan(select_stmt, NoExpr{}, params, mut c, false, false)! + plan, _ := create_select_plan(select_stmt, NoExpr{}, params, mut c, false)! return plan } @@ -276,7 +264,7 @@ fn create_query_expression_plan(stmt QueryExpression, params map[string]Value, m mut order := []SortSpecification{} for spec in stmt.order { order << SortSpecification{ - expr: resolve_identifiers(spec.expr, tables)! + expr: resolve_identifiers(c, spec.expr, tables)! is_asc: spec.is_asc } } diff --git a/vsql/prepare.v b/vsql/prepare.v index ec26103..f5d4dc4 100644 --- a/vsql/prepare.v +++ b/vsql/prepare.v @@ -119,6 +119,13 @@ fn (mut p PreparedStmt) query_internal(params map[string]Value) !Result { // See transaction.v return execute_rollback(mut p.c, stmt, p.elapsed_parse) } + SetSchemaStmt { + if p.explain { + return sqlstate_42601('Cannot EXPLAIN SET SCHEMA') + } + + return execute_set_schema(mut p.c, stmt, p.elapsed_parse) + } QueryExpression { return execute_select(mut p.c, stmt, all_params, p.elapsed_parse, p.explain) } diff --git a/vsql/row.v b/vsql/row.v index 31521a8..1fc6e2e 100644 --- a/vsql/row.v +++ b/vsql/row.v @@ -109,6 +109,16 @@ pub fn (r Row) get(name string) !Value { } } +fn (r Row) for_storage() Row { + mut new_data := map[string]Value{} + for k, v in r.data { + parts := k.split('.') + new_data[parts[parts.len - 1]] = v + } + + return Row{r.id, r.tid, new_data} +} + // new_empty_row is used internally to generate a row with zero values for all // the types in a Row. This is used for testing expressions without needing the // actual row. @@ -368,7 +378,7 @@ fn (mut r Row) object_key(t Table) ![]u8 { mut key := new_empty_bytes() key.write_u8(`R`) - key.write_u8s(t.name.bytes()) + key.write_u8s(t.name.id().bytes()) // TODO(elliotchance): This is actually not a safe separator to use since // deliminated table names can contain ':' diff --git a/vsql/sequence.v b/vsql/sequence.v index c6e931a..4290ca3 100644 --- a/vsql/sequence.v +++ b/vsql/sequence.v @@ -5,16 +5,15 @@ module vsql // A SEQUENCE definition. // // snippet: v.Sequence -struct Sequence { +pub struct Sequence { mut: // The tid is the transaction ID that created this table. tid int pub mut: - // name is case-sensitive. The name is equivilent to using a deliminated - // identifier (with double quotes). + // name contains the other parts such as the schema. // // snippet: v.Sequence.name - name string + name Identifier // current_value is the current value before it is incremented by // "NEXT VALUE FOR". // @@ -55,9 +54,7 @@ pub mut: // // snippet: v.Sequence.str pub fn (s Sequence) str() string { - name := if s.name.is_upper() { s.name } else { '"${s.name}"' } - - mut parts := ['CREATE SEQUENCE ${name} START WITH ${s.current_value}'] + mut parts := ['CREATE SEQUENCE ${s.name} START WITH ${s.current_value}'] if s.increment_by != 1 { parts << 'INCREMENT BY ${s.increment_by}' @@ -94,7 +91,7 @@ fn (s Sequence) next() !Sequence { mut next_value := s.current_value + s.increment_by if s.has_max_value && next_value > s.max_value { if !s.cycle { - return sqlstate_2200h(s.name) + return sqlstate_2200h(s.name.str()) } next_value = s.reset() @@ -102,7 +99,7 @@ fn (s Sequence) next() !Sequence { if s.has_min_value && next_value < s.min_value { if !s.cycle { - return sqlstate_2200h(s.name) + return sqlstate_2200h(s.name.str()) } next_value = s.reset() @@ -114,7 +111,7 @@ fn (s Sequence) next() !Sequence { fn (s Sequence) bytes() []u8 { mut b := new_empty_bytes() - b.write_string1(s.name) + b.write_string1(s.name.id()) b.write_i64(s.current_value) b.write_i64(s.increment_by) b.write_bool(s.cycle) @@ -127,7 +124,7 @@ fn (s Sequence) bytes() []u8 { fn new_sequence_from_bytes(data []u8, tid int) Sequence { mut b := new_bytes(data) - sequence_name := b.read_string1() + sequence_name := b.read_identifier() current_value := b.read_i64() increment_by := b.read_i64() cycle := b.read_bool() diff --git a/vsql/set_schema.v b/vsql/set_schema.v new file mode 100644 index 0000000..aa0a3ec --- /dev/null +++ b/vsql/set_schema.v @@ -0,0 +1,21 @@ +// set_schema.v contains the implementation for the SET SCHEMA statement. + +module vsql + +import time + +fn execute_set_schema(mut c Connection, stmt SetSchemaStmt, elapsed_parse time.Duration) !Result { + t := start_timer() + + // This does not need to hold a write connection with the file. + + new_schema := eval_as_value(mut c, Row{}, stmt.schema_name, map[string]Value{})!.str() + + if new_schema !in c.storage.schemas { + return sqlstate_3f000(new_schema) // schema does not exist + } + + c.current_schema = new_schema + + return new_result_msg('SET SCHEMA 1', elapsed_parse, t.elapsed()) +} diff --git a/vsql/storage.v b/vsql/storage.v index 0611b4f..42b8084 100644 --- a/vsql/storage.v +++ b/vsql/storage.v @@ -78,7 +78,7 @@ fn (mut f Storage) open(path string) ! { for object in f.btree.new_range_iterator('T'.bytes(), 'U'.bytes()) { if object_is_visible(object.tid, object.xid, f.transaction_id, mut f.header.active_transaction_ids) { table := new_table_from_bytes(object.value, object.tid) - f.tables[table.name] = table + f.tables[table.name.id()] = table } } @@ -112,7 +112,7 @@ fn (mut f Storage) close() ! { f.file.close() } -fn (mut f Storage) create_table(table_name string, columns Columns, primary_key []string) ! { +fn (mut f Storage) create_table(table_name Identifier, columns Columns, primary_key []string) ! { f.isolation_start()! defer { f.isolation_end() or { panic(err) } @@ -120,11 +120,11 @@ fn (mut f Storage) create_table(table_name string, columns Columns, primary_key table := Table{f.transaction_id, table_name, columns, primary_key, false} - obj := new_page_object('T${table_name}'.bytes(), f.transaction_id, 0, table.bytes()) + obj := new_page_object('T${table_name.id()}'.bytes(), f.transaction_id, 0, table.bytes()) page_number := f.btree.add(obj)! f.transaction_pages[page_number] = true - f.tables[table_name] = table + f.tables[table_name.id()] = table f.schema_changed() } @@ -150,7 +150,7 @@ fn (mut f Storage) create_sequence(sequence Sequence) ! { f.isolation_end() or { panic(err) } } - obj := new_page_object('Q${sequence.name}'.bytes(), f.transaction_id, 0, sequence.bytes()) + obj := new_page_object('Q${sequence.name.id()}'.bytes(), f.transaction_id, 0, sequence.bytes()) page_number := f.btree.add(obj)! f.transaction_pages[page_number] = true } @@ -161,19 +161,13 @@ fn (mut f Storage) sequence(name Identifier) !Sequence { f.isolation_end() or { panic(err) } } - // TODO(elliotchance): Fix this when identifier chains are properly supported. - mut canonical_name := name.str() - if !canonical_name.contains('.') { - canonical_name = 'PUBLIC.${canonical_name}' - } - - key := 'Q${canonical_name}'.bytes() + key := 'Q${name.id()}'.bytes() mut sequence := Sequence{} for object in f.read_objects(key, key)! { sequence = new_sequence_from_bytes(object.value, object.tid) } - if sequence.name == '' { + if sequence.name.entity_name == '' { return sqlstate_42p01('sequence', name.str()) // sequence does not exist } @@ -201,13 +195,7 @@ fn (mut f Storage) sequence_next_value(name Identifier) !i64 { } mut sequence := f.sequence(name)! - - // TODO(elliotchance): Fix this when identifier chains are properly supported. mut canonical_name := name.str() - if !canonical_name.contains('.') { - canonical_name = 'PUBLIC.${canonical_name}' - } - next_sequence := sequence.next()! key := 'Q${canonical_name}'.bytes() @@ -230,12 +218,7 @@ fn (mut f Storage) update_sequence(old_sequence Sequence, new_sequence Sequence) f.isolation_end() or { panic(err) } } - // TODO(elliotchance): Fix this when identifier chains are properly supported. mut canonical_name := new_sequence.name.str() - if !canonical_name.contains('.') { - canonical_name = 'PUBLIC.${canonical_name}' - } - key := 'Q${canonical_name}'.bytes() // Important: All other objects have to use the current transaction ID when @@ -280,19 +263,13 @@ fn (mut f Storage) delete_schema(schema_name string, tid int) ! { f.schema_changed() } -fn (mut f Storage) delete_sequence(sequence_name string, tid int) ! { +fn (mut f Storage) delete_sequence(name Identifier, tid int) ! { f.isolation_start()! defer { f.isolation_end() or { panic(err) } } - // TODO(elliotchance): Fix this when identifier chains are properly supported. - mut canonical_name := sequence_name - if !canonical_name.contains('.') { - canonical_name = 'PUBLIC.${canonical_name}' - } - - page_number := f.btree.expire('Q${canonical_name}'.bytes(), tid, f.transaction_id)! + page_number := f.btree.expire('Q${name.id()}'.bytes(), tid, f.transaction_id)! f.transaction_pages[page_number] = true } @@ -330,7 +307,7 @@ fn (mut f Storage) update_row(mut old Row, mut new Row, t Table) ! { } } -fn (mut f Storage) read_rows(table_name string, prefix_table_name bool) ![]Row { +fn (mut f Storage) read_rows(table_name string) ![]Row { f.isolation_start()! defer { f.isolation_end() or { panic(err) } @@ -340,11 +317,7 @@ fn (mut f Storage) read_rows(table_name string, prefix_table_name bool) ![]Row { // ';' = ':' + 1 for object in f.read_objects('R${table_name}:'.bytes(), 'R${table_name};'.bytes())! { - mut prefixed_table_name := '' - if prefix_table_name { - prefixed_table_name = table_name - } - rows << new_row_from_bytes(f.tables[table_name], object.value, object.tid, prefixed_table_name) + rows << new_row_from_bytes(f.tables[table_name], object.value, object.tid, table_name) } return rows diff --git a/vsql/table.v b/vsql/table.v index f915519..2486fe0 100644 --- a/vsql/table.v +++ b/vsql/table.v @@ -52,15 +52,15 @@ fn (c Columns) str() string { // Represents the structure of a table. // // snippet: v.Table -struct Table { +pub struct Table { mut: // The tid is the transaction ID that created this table. tid int pub mut: - // The name of the table is case-sensitive. + // The name of the table including the schema. // // snippet: v.Table.name - name string + name Identifier // The column definitions for the table. // // snippet: v.Table.columns @@ -104,7 +104,7 @@ pub fn (t Table) column(name string) !Column { fn (t Table) bytes() []u8 { mut b := new_empty_bytes() - b.write_string1(t.name) + b.write_identifier(t.name) b.write_u8(u8(t.primary_key.len)) for s in t.primary_key { @@ -125,7 +125,7 @@ fn (t Table) bytes() []u8 { fn new_table_from_bytes(data []u8, tid int) Table { mut b := new_bytes(data) - table_name := b.read_string1() + table_name := b.read_identifier() primary_key_len := b.read_u8() mut primary_key := []string{len: int(primary_key_len)} @@ -164,14 +164,13 @@ pub fn (t Table) str() string { // A TableOperation requires that up to rows in the table be read. The number of // rows read may be limited and/or an offset (rows to skip) provided. struct TableOperation { - table_name string + table_name Identifier // table_is_subplan is true if the table_name should be executed from the // subplans instead of a real table. - table_is_subplan bool - table Table - params map[string]Value - conn &Connection - prefix_table_name bool + table_is_subplan bool + table Table + params map[string]Value + conn &Connection mut: subplans map[string]Plan storage Storage @@ -182,32 +181,28 @@ fn (o TableOperation) str() string { } fn (o TableOperation) columns() Columns { - if o.prefix_table_name { - mut columns := []Column{} - for column in o.table.columns { - columns << Column{'${o.table_name}.${column.name}', column.typ, column.not_null} - } - - return columns + mut columns := []Column{} + for column in o.table.columns { + columns << Column{'${o.table_name.id()}.${column.name}', column.typ, column.not_null} } - return o.table.columns + return columns } fn (mut o TableOperation) execute(_ []Row) ![]Row { mut rows := []Row{} if o.table_is_subplan { - for row in o.subplans[o.table_name].execute([]Row{})! { + for row in o.subplans[o.table_name.id()].execute([]Row{})! { mut data := map[string]Value{} for k, v in row.data { - data['${o.table_name}.${k}'] = v + data['${o.table_name.id()}.${k}'] = v } rows << new_row(data) } } else { - rows = o.storage.read_rows(o.table_name, o.prefix_table_name)! + rows = o.storage.read_rows(o.table_name.id())! } return rows diff --git a/vsql/update.v b/vsql/update.v index eaf8701..38a8cb7 100644 --- a/vsql/update.v +++ b/vsql/update.v @@ -32,22 +32,8 @@ fn execute_update(mut c Connection, stmt UpdateStmt, params map[string]Value, el } mut rows := plan.execute([]Row{})! - - mut table_name := stmt.table_name - - // TODO(elliotchance): This isn't really ideal. Replace with a proper - // identifier chain when we support that. - if table_name.contains('.') { - parts := table_name.split('.') - - if parts[0] !in c.storage.schemas { - return sqlstate_3f000(parts[0]) // scheme does not exist - } - } else { - table_name = 'PUBLIC.${table_name}' - } - - table := c.storage.tables[table_name] + mut table_name := c.resolve_table_identifier(stmt.table_name, false)! + table := c.storage.tables[table_name.id()] mut modify_count := 0 for mut row in rows { @@ -63,7 +49,8 @@ fn execute_update(mut c Connection, stmt UpdateStmt, params map[string]Value, el for column_name, v in stmt.set { table_column := table.column(column_name)! - raw_value := eval_as_nullable_value(mut c, table_column.typ.typ, row, v, params)! + raw_value := eval_as_nullable_value(mut c, table_column.typ.typ, row, resolve_identifiers(c, + v, c.storage.tables)!, params)! if table_column.not_null && raw_value.is_null { return sqlstate_23502('column ${column_name}') @@ -76,17 +63,19 @@ fn execute_update(mut c Connection, stmt UpdateStmt, params map[string]Value, el // TODO(elliotchance): This has the side effect that NULL being // replaced with NULL is true, which is unnecessary, even if the // logic is a bit murky. - cmp, is_null := row.data[column_name].cmp(raw_value)! + column_id := '${table_name}.${column_name}' + cmp, is_null := row.data[column_id].cmp(raw_value)! if is_null || cmp != 0 { did_modify = true - row2.data[column_name] = cast(c, 'for column ${column_name}', raw_value, + row2.data[column_id] = cast(c, 'for column ${column_name}', raw_value, table_column.typ)! } } if did_modify { + // To be able to write the row back we need to clean the column names. modify_count++ - c.storage.update_row(mut row, mut row2, table)! + c.storage.update_row(mut row.for_storage(), mut row2.for_storage(), table)! } } @@ -103,7 +92,7 @@ fn execute_update(mut c Connection, stmt UpdateStmt, params map[string]Value, el for column_name, v in stmt.set { table_column := table.column(column_name)! raw_value := eval_as_nullable_value(mut c, table_column.typ.typ, empty_row, - v, params)! + resolve_identifiers(c, v, c.storage.tables)!, params)! value := cast(c, 'for column ${column_name}', raw_value, table_column.typ)! if table_column.not_null && value.is_null { diff --git a/vsql/values.v b/vsql/values.v index fa1cdf5..ed240c5 100644 --- a/vsql/values.v +++ b/vsql/values.v @@ -26,7 +26,12 @@ fn new_values_operation(rows []RowExpr, offset Expr, correlation Correlation, mu } } - return &ValuesOperation{rows, offset, correlation, params, conn} + mut new_rows := []RowExpr{} + for row in rows { + new_rows << resolve_identifiers(conn, row, conn.storage.tables)! as RowExpr + } + + return &ValuesOperation{new_rows, offset, correlation, params, conn} } fn (o &ValuesOperation) str() string { @@ -44,7 +49,7 @@ fn (o &ValuesOperation) columns() Columns { for i, column in o.correlation.columns { typ := eval_as_type(o.conn, Row{}, o.rows[0].exprs[i], o.params) or { panic(err) } columns << Column{ - name: column.name + name: column.sub_entity_name typ: typ } } @@ -59,7 +64,7 @@ fn (o &ValuesOperation) columns() Columns { for i in 1 .. o.rows[0].exprs.len + 1 { typ := eval_as_type(o.conn, Row{}, o.rows[0].exprs[i - 1], o.params) or { panic(err) } columns << Column{ - name: new_identifier('COL${i}').name + name: 'COL${i}' typ: typ } } diff --git a/vsql/walk.v b/vsql/walk.v index 335aa95..c9e9b53 100644 --- a/vsql/walk.v +++ b/vsql/walk.v @@ -88,16 +88,12 @@ struct PrimaryKeyOperation { lower Expr upper Expr params map[string]Value - // prefix_table_name will add the table name to the column names. This is - // required for SELECT statements, but should be avoided for DML statements - // since the rows are written back to disk. - prefix_table_name bool mut: conn &Connection } -fn new_primary_key_operation(table Table, lower Expr, upper Expr, params map[string]Value, conn &Connection, prefix_table_name bool) &PrimaryKeyOperation { - return &PrimaryKeyOperation{table, lower, upper, params, prefix_table_name, conn} +fn new_primary_key_operation(table Table, lower Expr, upper Expr, params map[string]Value, conn &Connection) &PrimaryKeyOperation { + return &PrimaryKeyOperation{table, lower, upper, params, conn} } fn (o &PrimaryKeyOperation) str() string { @@ -105,17 +101,13 @@ fn (o &PrimaryKeyOperation) str() string { } fn (o &PrimaryKeyOperation) columns() Columns { - if o.prefix_table_name { - mut columns := []Column{} + mut columns := []Column{} - for column in o.table.columns { - columns << Column{'${o.table.name}.${column.name}', column.typ, column.not_null} - } - - return columns + for column in o.table.columns { + columns << Column{'${o.table.name}.${column.name}', column.typ, column.not_null} } - return o.table.columns + return columns } fn (mut o PrimaryKeyOperation) execute(_ []Row) ![]Row { @@ -134,11 +126,7 @@ fn (mut o PrimaryKeyOperation) execute(_ []Row) ![]Row { mut rows := []Row{} for object in o.conn.storage.btree.new_range_iterator(object_key, object_key) { if object_is_visible(object.tid, object.xid, tid, mut transaction_ids) { - mut prefixed_table_name := '' - if o.prefix_table_name { - prefixed_table_name = o.table.name - } - rows << new_row_from_bytes(o.table, object.value, object.tid, prefixed_table_name) + rows << new_row_from_bytes(o.table, object.value, object.tid, o.table.name.str()) } }