Skip to content

Commit

Permalink
Adding support for catalogs (#145)
Browse files Browse the repository at this point in the history
A catalog is the SQL standard term for a database. A catalog contains
schemas, but cannot be directly created with SQL. Instead, a catalog in
vsql means a database file which includes a ":memory:" database.

Loading multiple catalogs allows vsql to work seamlessly across multiple
database files, although there is some limitations with transaction
guarantees when working with multiple catalogs. See documentation for
more details.

Catalogs are virtual, meaning that the catalog name is not encoded into
the database file but created when attaching it to the engine. By
default the catalog name is derived from the file by using the first
part of the file name. For example "/foo/bar.baz.vsql" will be "bar".

SQL Standard F651
SQL Standard F762
  • Loading branch information
elliotchance authored Feb 28, 2023
1 parent 8879d67 commit bd6d3b1
Show file tree
Hide file tree
Showing 74 changed files with 1,138 additions and 509 deletions.
19 changes: 19 additions & 0 deletions cmd/tests/catalogs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh

set -e

VSQL1_FILE="$(mktemp -d)/file1.abc.vsql" || exit 1
VSQL2_FILE="$(mktemp -d)/file2.def.vsql" || exit 1
TXT_FILE="$(mktemp).sql" || exit 1

echo 'CREATE TABLE foo (bar INT);' | $VSQL cli $VSQL1_FILE
echo 'INSERT INTO foo (bar) VALUES (123);' | $VSQL cli $VSQL1_FILE

echo 'CREATE TABLE foo (baz INT);' | $VSQL cli $VSQL2_FILE
echo 'INSERT INTO foo (baz) VALUES (456);' | $VSQL cli $VSQL2_FILE

echo 'SELECT * FROM "file1".public.foo;' | $VSQL cli $VSQL1_FILE $VSQL2_FILE > $TXT_FILE
echo 'SELECT * FROM "file2".public.foo;' | $VSQL cli $VSQL1_FILE $VSQL2_FILE >> $TXT_FILE

grep -R "BAR: 123" $TXT_FILE
grep -R "BAZ: 456" $TXT_FILE
18 changes: 15 additions & 3 deletions cmd/vsql/cli.v
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,26 @@ fn register_cli_command(mut cmd cli.Command) {
fn cli_command(cmd cli.Command) ! {
print_version()

mut db := vsql.open(cmd.args[0]) or { return err }
mut db := vsql.open(cmd.args[0])!

for arg in cmd.args[1..] {
catalog_name := vsql.catalog_name_from_path(arg)
options := vsql.default_connection_options()
db.add_catalog(catalog_name, arg, options)!
}

for {
print('vsql> ')
query := os.get_line()

if query != '' {
start := time.ticks()
result := db.query(query) or { return err }
result := db.query(query)!

mut total_rows := 0
for row in result {
for column in result.columns {
print('${column.name}: ${row.get_string(column.name)} ')
print('${column.name.sub_entity_name}: ${row.get_string(column.name.sub_entity_name)!} ')
}
total_rows++
}
Expand All @@ -42,6 +48,12 @@ fn cli_command(cmd cli.Command) ! {
}

println('${total_rows} ${vsql.pluralize(total_rows, 'row')} (${time.ticks() - start} ms)')
} else {
// This means there is no more input and should only occur when the
// commands are being few through a pipe like:
//
// echo "VALUES CURRENT_CATALOG;" | vsql cli
break
}

println('')
Expand Down
7 changes: 4 additions & 3 deletions cmd/vsql/out.v
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ fn register_out_command(mut cmd cli.Command) {

fn out_command(cmd cli.Command) ! {
mut db := vsql.open(cmd.args[0])!
mut schemas := db.schemas()!
mut catalog := db.catalog()
mut schemas := catalog.schemas()!

// 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
Expand All @@ -40,7 +41,7 @@ fn out_command(cmd cli.Command) ! {
println('${schema}\n')
}

mut sequences := db.sequences(schema.name)!
mut sequences := catalog.sequences(schema.name)!
sequences.sort_with_compare(fn (a &vsql.Sequence, b &vsql.Sequence) int {
return compare_strings(a.name.str(), b.name.str())
})
Expand All @@ -49,7 +50,7 @@ fn out_command(cmd cli.Command) ! {
println('${sequence}\n')
}

mut tables := db.schema_tables(schema.name) or { return err }
mut tables := catalog.schema_tables(schema.name) or { return err }
tables.sort_with_compare(fn (a &vsql.Table, b &vsql.Table) int {
return compare_strings(a.name.str(), b.name.str())
})
Expand Down
13 changes: 13 additions & 0 deletions docs/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,19 @@ COALESCE
VALUES COALESCE(1, 2);
-- COL1: 1
CURRENT_CATALOG
^^^^^^^^^^^^^^^

.. code-block:: sql
CURRENT_CATALOG
``CURRENT_CATALOG`` always reflects the file name, only up to the first ``.``.
So, if the complete file path is ``/tmp/mydb.cool.vsql`` the ``CURRENT_CATALOG``
would be ``mydb``.

When using in-memory databases, the ``CURRENT_CATALOG`` will be ``:memory:``.

CURRENT_SCHEMA
^^^^^^^^^^^^^^

Expand Down
4 changes: 2 additions & 2 deletions docs/snippets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
MINVALUE is optional.

.. |v.Column.name| replace::
name is case-sensitive. The name is equivilent to using a deliminated
identifier (with double quotes).
name resolves to the actual canonical location. If you only need the column
name itself, you can use name.sub_entity_name.

.. |v.Column.not_null| replace::
not_null will be true if ``NOT NULL`` was specified on the column.
Expand Down
4 changes: 2 additions & 2 deletions docs/sql-compliance.rst
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@ Optional Features
* - ❌ **F641**
- **Row and table constructors**

* - **F651**
* - **F651**
- **Catalog name qualifiers**

* - ❌ **F661**
Expand Down Expand Up @@ -946,7 +946,7 @@ Optional Features
* - ❌ **F761**
- **Session management**

* - **F762**
* - **F762**
- **CURRENT_CATALOG**

* - ✅ **F763**
Expand Down
5 changes: 5 additions & 0 deletions docs/sqlstate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ in an active transaction.
COMMIT;
-- error 2D000: invalid transaction termination
``3D000`` invalid catalog name
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

``3D000`` occurs if the catalog does not exist or is otherwise invalid.

``3F000`` invalid schema name
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
7 changes: 7 additions & 0 deletions docs/start-transaction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ wrapped in an implicit transaction. Internally this is important becuase any
statements that may make changes (such as a ``DELETE`` that removes multiple
rows) should seem atomic to all other readers and writers.

Catalogs
--------

Transactions across catalogs are semi-supported. That is, transaction isolation
is not guaranteed across catalogs for all operations. And ``COMMIT`` and
``ROLLBACK`` may independently succeed or fail.

Examples
--------

Expand Down
15 changes: 15 additions & 0 deletions docs/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,21 @@ Connection names can be any single word including numbers for convienience. The
default connection name is named "main" but this should not be used or
referenced in tests to avoid unexpected behavior.

Multiple Catalogs
^^^^^^^^^^^^^^^^^

If a test needs to use more than one catalog, you can use the ``create_catalog``
directive:

.. code-block:: sql
/* create_catalog FOO */
CREATE TABLE foo.public.bar (baz INTEGER);
EXPLAIN SELECT * FROM foo.public.bar;
-- msg: CREATE TABLE 1
-- EXPLAIN: TABLE FOO.PUBLIC.BAR (BAZ INTEGER)
-- EXPLAIN: EXPR (FOO.PUBLIC.BAR.BAZ INTEGER)
Debugging Tests
---------------

Expand Down
9 changes: 5 additions & 4 deletions examples/show-tables.v
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ fn main() {

fn example() ! {
mut db := vsql.open('test.vsql')!
mut catalog := db.catalog()

// The "public" schema is available by default, but we can create more.
db.query('CREATE TABLE foo (x DOUBLE PRECISION)')!
Expand All @@ -16,9 +17,9 @@ fn example() ! {
db.query('CREATE TABLE my_schema.baz (x BOOLEAN)')!

mut actual := []string{}
for schema in db.schemas()! {
for schema in catalog.schemas()! {
mut table_names := []string{}
for table in db.schema_tables(schema.name)! {
for table in catalog.schema_tables(schema.name)! {
table_names << table.name.str()
}

Expand All @@ -27,7 +28,7 @@ fn example() ! {
}

assert actual == [
"PUBLIC has ['PUBLIC.FOO', 'PUBLIC.BAR']",
"MY_SCHEMA has ['MY_SCHEMA.BAZ']",
'PUBLIC has [\'"test".PUBLIC.FOO\', \'"test".PUBLIC.BAR\']',
'MY_SCHEMA has [\'"test".MY_SCHEMA.BAZ\']',
]
}
2 changes: 1 addition & 1 deletion examples/virtual-tables.v
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fn example() ! {
}

assert lines == [
'VIRTUAL TABLE PUBLIC.FOO ("FOO.num" INTEGER, FOO.WORD CHARACTER VARYING(32))',
'VIRTUAL TABLE PUBLIC.FOO ("num" INTEGER, WORD CHARACTER VARYING(32))',
'EXPR ("num" INTEGER, WORD CHARACTER VARYING(32))',
]

Expand Down
14 changes: 13 additions & 1 deletion grammar.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
<schema name>

<schema name> /* Identifier */ ::=
<unqualified schema name>
<catalog name> <period> <unqualified schema name> -> schema_name_1
| <unqualified schema name>

<unqualified schema name> /* Identifier */ ::=
<identifier> -> unqualified_schema_name
Expand Down Expand Up @@ -1062,6 +1063,7 @@

<general value specification> /* Expr */ ::=
<host parameter specification>
| CURRENT_CATALOG -> current_catalog
| CURRENT_SCHEMA -> current_schema

<host parameter specification> /* Expr */ ::=
Expand Down Expand Up @@ -1516,6 +1518,13 @@

<SQL session statement> /* Stmt */ ::=
<set schema statement>
| <set catalog statement>

<set catalog statement> /* Stmt */ ::=
SET <catalog name characteristic> -> set_catalog_stmt

<catalog name characteristic> /* Expr */ ::=
CATALOG <value specification> -> expr

<set schema statement> /* Stmt */ ::=
SET <schema name characteristic> -> set_schema_stmt
Expand All @@ -1526,3 +1535,6 @@
<value specification> /* Expr */ ::=
<literal>
| <general value specification>

<catalog name> /* IdentifierChain */ ::=
<identifier>
4 changes: 2 additions & 2 deletions tests/as.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ CREATE TABLE t1 (x INT);
INSERT INTO t1 (x) VALUES (0);

EXPLAIN SELECT 1 AS bob FROM t1;
-- EXPLAIN: TABLE PUBLIC.T1 (PUBLIC.T1.X INTEGER)
-- EXPLAIN: TABLE ":memory:".PUBLIC.T1 (X INTEGER)
-- EXPLAIN: EXPR (BOB BIGINT)

SELECT 1 AS bob FROM t1;
-- BOB: 1

EXPLAIN SELECT 1 AS "Bob" FROM t1;
-- EXPLAIN: TABLE PUBLIC.T1 (PUBLIC.T1.X INTEGER)
-- EXPLAIN: TABLE ":memory:".PUBLIC.T1 (X INTEGER)
-- EXPLAIN: EXPR ("Bob" BIGINT)

SELECT 1 AS "Bob" FROM t1;
Expand Down
6 changes: 3 additions & 3 deletions tests/between.sql
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ INSERT INTO t1 (x) VALUES (0);
EXPLAIN SELECT * FROM t1 WHERE x NOT BETWEEN SYMMETRIC 'ka' AND 'h';
-- msg: CREATE TABLE 1
-- msg: INSERT 1
-- EXPLAIN: TABLE PUBLIC.T1 (PUBLIC.T1.X INTEGER)
-- EXPLAIN: WHERE PUBLIC.T1.X NOT BETWEEN SYMMETRIC 'ka' AND 'h'
-- EXPLAIN: EXPR (X INTEGER)
-- EXPLAIN: TABLE ":memory:".PUBLIC.T1 (X INTEGER)
-- EXPLAIN: WHERE ":memory:".PUBLIC.T1.X NOT BETWEEN SYMMETRIC 'ka' AND 'h'
-- EXPLAIN: EXPR (":memory:".PUBLIC.T1.X INTEGER)
75 changes: 75 additions & 0 deletions tests/catalogs.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
CREATE TABLE ":memory:".PUBLIC.foo (baz INTEGER);
INSERT INTO ":memory:".PUBLIC.foo (baz) VALUES (123);
INSERT INTO ":memory:".PUBLIC.foo (baz) VALUES (456);
SELECT * FROM ":memory:".PUBLIC.foo;
UPDATE ":memory:".PUBLIC.foo SET baz = 789 WHERE baz = 123;
SELECT * FROM ":memory:".PUBLIC.foo;
DELETE FROM ":memory:".PUBLIC.foo WHERE baz > 700;
SELECT * FROM ":memory:".PUBLIC.foo;
-- msg: CREATE TABLE 1
-- msg: INSERT 1
-- msg: INSERT 1
-- BAZ: 123
-- BAZ: 456
-- msg: UPDATE 1
-- BAZ: 789
-- BAZ: 456
-- msg: DELETE 1
-- BAZ: 456

/* create_catalog FOO */
CREATE TABLE foo.public.bar (baz INTEGER);
EXPLAIN SELECT * FROM foo.public.bar;
-- msg: CREATE TABLE 1
-- EXPLAIN: TABLE FOO.PUBLIC.BAR (BAZ INTEGER)
-- EXPLAIN: EXPR (FOO.PUBLIC.BAR.BAZ INTEGER)

/* create_catalog FOO */
CREATE TABLE foo.public.bar (baz INTEGER);
INSERT INTO foo.public.bar (baz) VALUES (123);
EXPLAIN SELECT * FROM foo.public.bar;
SET CATALOG ':memory:';
-- msg: CREATE TABLE 1
-- msg: INSERT 1
-- EXPLAIN: TABLE FOO.PUBLIC.BAR (BAZ INTEGER)
-- EXPLAIN: EXPR (FOO.PUBLIC.BAR.BAZ INTEGER)
-- msg: SET CATALOG 1

VALUES CURRENT_CATALOG;
/* create_catalog FOO */
/* create_catalog BAR */
VALUES CURRENT_CATALOG;
SET CATALOG 'FOO';
VALUES CURRENT_CATALOG;
SET CATALOG 'BAR';
VALUES CURRENT_CATALOG;
CREATE TABLE baz (num1 INTEGER);
INSERT INTO baz (num1) VALUES (123);
SELECT * FROM baz;
SET CATALOG 'FOO';
SELECT * FROM baz;
CREATE TABLE baz (num2 INTEGER);
INSERT INTO baz (num2) VALUES (456);
SELECT * FROM baz;
SET CATALOG ':memory:';
SELECT * FROM foo.public.baz;
SELECT * FROM bar.public.baz;
SELECT * FROM foo.public.baz JOIN bar.public.baz ON TRUE;
-- COL1: :memory:
-- COL1: BAR
-- msg: SET CATALOG 1
-- COL1: FOO
-- msg: SET CATALOG 1
-- COL1: BAR
-- msg: CREATE TABLE 1
-- msg: INSERT 1
-- NUM1: 123
-- msg: SET CATALOG 1
-- error 42P01: no such table: FOO.PUBLIC.BAZ
-- msg: CREATE TABLE 1
-- msg: INSERT 1
-- NUM2: 456
-- msg: SET CATALOG 1
-- NUM2: 456
-- NUM1: 123
-- NUM2: 456 NUM1: 123
4 changes: 2 additions & 2 deletions tests/create-sequence.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ EXPLAIN CREATE SEQUENCE seq1;
CREATE SEQUENCE seq1;
EXPLAIN VALUES NEXT VALUE FOR seq1;
-- msg: CREATE SEQUENCE 1
-- EXPLAIN: VALUES (COL1 INTEGER) = ROW(NEXT VALUE FOR PUBLIC.SEQ1)
-- EXPLAIN: VALUES (COL1 INTEGER) = ROW(NEXT VALUE FOR ":memory:".PUBLIC.SEQ1)

CREATE SEQUENCE seq1;
VALUES NEXT VALUE FOR seq1;
Expand All @@ -29,7 +29,7 @@ CREATE SEQUENCE foo.seq1;
-- msg: CREATE SEQUENCE 1

VALUES NEXT VALUE FOR seq1;
-- error 42P01: no such sequence: PUBLIC.SEQ1
-- error 42P01: no such sequence: ":memory:".PUBLIC.SEQ1

CREATE SEQUENCE seq1;
CREATE SEQUENCE seq2;
Expand Down
Loading

0 comments on commit bd6d3b1

Please sign in to comment.