Skip to content

Commit

Permalink
Support for the V ORM
Browse files Browse the repository at this point in the history
DO NOT MERGE.

This is partial support for the ORM. However, there are some challenges
that need to be addressed before this can be properly reviewed and
landed:

1. The ORM in V requires drivers to be hard-coded. See #90.

2. The Connection doesn't _really_ implement orm.Connection because the
vsql Connection is required to be mut and the current interface
definition does not allow this.

3. We need to create a new test suite for the ORM. `vsql/orm_test.v`
filled with combinations of statements "sql" commands will work just
fine. Specifically, we need to test different combinations of
expressions and types.

Fixes #90
  • Loading branch information
elliotchance committed Dec 30, 2023
1 parent 061e53a commit a220a6d
Show file tree
Hide file tree
Showing 11 changed files with 631 additions and 53 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ btree-test: oldv
sql-test: oldv
$(V) -stats $(BUILD_OPTIONS) test vsql/sql_test.v

orm-test: oldv
$(V) -stats $(BUILD_OPTIONS) test vsql/orm_test.v

# CLI Tests

cli-test: bin/vsql
Expand Down
133 changes: 83 additions & 50 deletions docs/v-client-library-docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,29 @@ Constants
fn open
-------
fn open_database
----------------


.. code-block:: v
pub fn open(path string) !&Connection
pub fn open_database(path string, options ConnectionOptions) !&Connection
open is the convenience function for open_database() with default options.
open_database will open an existing database file or create a new file if the path does not exist.

If the file does exist, open_database will assume that the file is a valid database file (not corrupt). Otherwise unexpected behavior or even a crash may occur.

The special file name ":memory:" can be used to create an entirely in-memory database. This will be faster but all data will be lost when the connection is closed.

open_database can be used concurrently for reading and writing to the same file and provides the following default protections:

- Fine: Multiple processes open_database() the same file.

- Fine: Multiple goroutines sharing an open_database() on the same file.

- Bad: Multiple goroutines open_database() the same file.

See ConnectionOptions and default_connection_options().

fn catalog_name_from_path
-------------------------
Expand Down Expand Up @@ -85,29 +99,15 @@ fn default_connection_options
default_connection_options returns the sensible defaults used by open() and the correct base to provide your own option overrides. See ConnectionOptions.

fn open_database
----------------
fn open_orm
-----------


.. code-block:: v
pub fn open_database(path string, options ConnectionOptions) !&Connection
open_database will open an existing database file or create a new file if the path does not exist.

If the file does exist, open_database will assume that the file is a valid database file (not corrupt). Otherwise unexpected behavior or even a crash may occur.
pub fn open_orm(path string) !ORMConnection
The special file name ":memory:" can be used to create an entirely in-memory database. This will be faster but all data will be lost when the connection is closed.
open_database can be used concurrently for reading and writing to the same file and provides the following default protections:

- Fine: Multiple processes open_database() the same file.

- Fine: Multiple goroutines sharing an open_database() on the same file.

- Bad: Multiple goroutines open_database() the same file.

See ConnectionOptions and default_connection_options().
fn new_benchmark
----------------
Expand Down Expand Up @@ -217,6 +217,16 @@ new_numeric_value expects a value to be valid and the size and scale are determi



fn new_primitive_value
----------------------


.. code-block:: v
pub fn new_primitive_value(p orm.Primitive) !Value
new_primitive_value returns the Value of a Primitive. Primitives are used by the ORM.

fn new_query_cache
------------------

Expand Down Expand Up @@ -287,25 +297,35 @@ fn new_timestamp_value
new_timestamp_value creates a ``TIMESTAMP`` value.

fn new_varchar_value
fn new_unknown_value
--------------------


.. code-block:: v
pub fn new_varchar_value(x string) Value
pub fn new_unknown_value() Value
new_varchar_value creates a ``CHARACTER VARYING`` value.
new_unknown_value returns an ``UNKNOWN`` value. This is the ``NULL`` representation of ``BOOLEAN``.

fn new_unknown_value
fn open
-------


.. code-block:: v
pub fn open(path string) !&Connection
open is the convenience function for open_database() with default options.

fn new_varchar_value
--------------------


.. code-block:: v
pub fn new_unknown_value() Value
pub fn new_varchar_value(x string) Value
new_unknown_value returns an ``UNKNOWN`` value. This is the ``NULL`` representation of ``BOOLEAN``.
new_varchar_value creates a ``CHARACTER VARYING`` value.

type Row
--------
Expand Down Expand Up @@ -341,6 +361,37 @@ enum Boolean
Possible values for a BOOLEAN.

struct PageObject
-----------------


.. code-block:: v
pub struct PageObject {
// The key is not required to be unique in the page. It becomes unique when
// combined with tid. However, no more than two version of the same key can
// exist in a page. See the caveats at the top of btree.v.
key []u8
// The value contains the serialized data for the object. The first byte of
// key is used to both identify what type of object this is and also keep
// objects within the same collection also within the same range.
value []u8
// When is_blob_ref is true, the value will be always be 5 bytes. See
// blob_info().
is_blob_ref bool
mut:
// The tid is the transaction that created the object.
//
// TODO(elliotchance): It makes more sense to construct a new PageObject
// when changing the tid and xid.
tid int
// The xid is the transaciton that deleted the object, or zero if it has
// never been deleted.
xid int
}
TODO(elliotchance): This does not need to be public. It was required for a bug at the time with V not being able to pass this to the shuffle function. At some point in the future remove the pub and see if it works.

struct ConnectionOptions
------------------------

Expand Down Expand Up @@ -684,36 +735,18 @@ struct Value
A single value. It contains it's type information in ``typ``.

struct PageObject
-----------------
struct ORMConnection
--------------------


.. code-block:: v
pub struct PageObject {
// The key is not required to be unique in the page. It becomes unique when
// combined with tid. However, no more than two version of the same key can
// exist in a page. See the caveats at the top of btree.v.
key []u8
// The value contains the serialized data for the object. The first byte of
// key is used to both identify what type of object this is and also keep
// objects within the same collection also within the same range.
value []u8
// When is_blob_ref is true, the value will be always be 5 bytes. See
// blob_info().
is_blob_ref bool
pub struct ORMConnection {
mut:
// The tid is the transaction that created the object.
//
// TODO(elliotchance): It makes more sense to construct a new PageObject
// when changing the tid and xid.
tid int
// The xid is the transaciton that deleted the object, or zero if it has
// never been deleted.
xid int
c Connection
}
TODO(elliotchance): This does not need to be public. It was required for a bug at the time with V not being able to pass this to the shuffle function. At some point in the future remove the pub and see if it works.
struct Identifier
-----------------
Expand Down
43 changes: 43 additions & 0 deletions examples/orm.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import os
import vsql

fn main() {
os.rm('test.vsql') or {}
example() or { panic(err) }
}

struct Product {
id int @[primary]
product_name string @[sql_type: 'varchar(100)']
price f64
}

fn (p Product) str() string {
return '${p.product_name} ($${p.price})'
}

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

sql db {
create table Product
}!

products := [
Product{1, 'Ice Cream', 5.99},
Product{2, 'Ham Sandwhich', 3.47},
Product{3, 'Bagel', 1.25},
]
for product in products {
sql db {
insert product into Product
}!
}

println('Products over $2:')
for row in sql db {
select from Product where price > 2
}! {
println(row)
}
}
21 changes: 21 additions & 0 deletions vsql/connection.v
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ pub fn open(path string) !&Connection {
return open_database(path, default_connection_options())
}

pub fn open_orm(path string) !ORMConnection {
return ORMConnection{open(path)!}
}

// open_database will open an existing database file or create a new file if the
// path does not exist.
//
Expand Down Expand Up @@ -412,6 +416,23 @@ pub fn (mut conn CatalogConnection) schema_tables(schema string) ![]Table {
return tables
}

// schema_table returns the table for the provided schema. If the schema or
// table does not exist and empty list will be returned.
pub fn (mut conn CatalogConnection) schema_table(schema string, table string) !Table {
conn.open_read_connection()!
defer {
conn.release_read_connection()
}

for _, t in conn.storage.tables {
if t.name.schema_name == schema && t.name.entity_name == table {
return t
}
}

return sqlstate_42p01('table', table) // table does not exist
}

// resolve_identifier returns a new identifier that would represent the
// canonical (fully qualified) form.
fn (conn Connection) resolve_identifier(identifier Identifier) Identifier {
Expand Down
3 changes: 2 additions & 1 deletion vsql/earley.v
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ fn parse(tokens []Token) !Stmt {
mut columns := tokenize_earley_columns(tokens)
mut grammar := get_grammar()

q0 := parse_earley(grammar['<preparable statement>'], mut columns)!
q0 := parse_earley(grammar['<preparable statement>'] or { panic('no entry rule') }, mut
columns)!

trees := build_trees(q0)
if trees.len == 0 {
Expand Down

0 comments on commit a220a6d

Please sign in to comment.