-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
9f82626
commit c621c5e
Showing
4 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import os | ||
import vsql | ||
import orm | ||
import v.ast | ||
|
||
fn main() { | ||
os.rm('test.vsql') or {} | ||
example() or { panic(err) } | ||
} | ||
|
||
struct Product { | ||
id int [primary] | ||
product_name string [sql: 'varchar(100)'] | ||
price f32 | ||
} | ||
|
||
fn example() ? { | ||
mut db := vsql.open('test.vsql') ? | ||
|
||
// sql db { | ||
// create table Product | ||
// } | ||
db.create("PRODUCT", [ | ||
orm.TableField{ | ||
name: "id" | ||
typ: ast.int_type_idx | ||
} | ||
orm.TableField{ | ||
name: "product_name" | ||
typ: ast.string_type_idx, | ||
attrs: [StructAttribute{ | ||
name: "sql" | ||
arg: "varchar(100)" | ||
}] | ||
} | ||
orm.TableField{ | ||
name: "price" | ||
typ: ast.f32_type_idx | ||
} | ||
]) ? | ||
|
||
products := [ | ||
Product{ | ||
id: 1 | ||
product_name: 'Ice Cream' | ||
price: 5.99 | ||
} | ||
Product{ | ||
id: 2 | ||
product_name: 'Ham Sandwhich' | ||
price: 3.47 | ||
} | ||
Product{ | ||
id: 3 | ||
product_name: 'Bagel' | ||
price: 1.25 | ||
} | ||
] | ||
|
||
for product in products { | ||
// sql db { | ||
// insert product into Product | ||
// } | ||
db.insert("PRODUCT", orm.QueryData{ | ||
fields: ["id", "product_name", "price"] | ||
data: [ | ||
orm.int_to_primitive(product.id) | ||
orm.string_to_primitive(product.product_name) | ||
orm.f32_to_primitive(product.price) | ||
] | ||
types: [ast.int_type_idx, ast.string_type_idx, ast.f32_type_idx] | ||
kinds: [.eq, .eq, .eq] | ||
is_and: [false, false, false] | ||
}) ? | ||
} | ||
|
||
// result := sql db { | ||
// select from Product where price > 2 | ||
// } | ||
result := db.@select(orm.SelectConfig{ | ||
table: 'PRODUCT' | ||
has_where: true | ||
// primary string = 'id' // should be set if primary is different than 'id' and 'has_limit' is false | ||
fields: ["ID", "PRODUCT_NAME", "PRICE"] | ||
types: [ast.int_type_idx, ast.string_type_idx, ast.f32_type_idx] | ||
}, orm.QueryData{ | ||
|
||
}, orm.QueryData{ | ||
fields: ['PRICE'] | ||
data: [orm.int_to_primitive(2)] | ||
types: [ast.int_type_idx] | ||
kinds: [.gt] | ||
is_and: [false] | ||
}) ? | ||
|
||
for row in result { | ||
// I guess internally the []Primitive would be mapped back to a Product | ||
// object? For now, we can show all the individual fields. | ||
println(row) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// orm.v implements the V language ORM: https://modules.vlang.io/orm.html | ||
|
||
module vsql | ||
|
||
import orm | ||
import v.ast | ||
import time | ||
|
||
/* | ||
interface Connection { | ||
@select(config SelectConfig, data QueryData, where QueryData) ?[][]Primitive | ||
insert(table string, data QueryData) ? | ||
update(table string, data QueryData, where QueryData) ? | ||
delete(table string, where QueryData) ? | ||
create(table string, fields []TableField) ? | ||
drop(table string) ? | ||
last_id() Primitive | ||
} | ||
*/ | ||
|
||
pub fn (mut c Connection) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ?[][]orm.Primitive { | ||
stmt := orm.orm_select_gen(config, '"', true, ':p', 0, where) | ||
|
||
mut bound := map[string]Value{} | ||
for i, _ in where.fields { | ||
bound['p$i'] = new_primitive_value(where.data[i]) ? | ||
} | ||
|
||
mut prepared := c.prepare(stmt)? | ||
result := prepared.query(bound)? | ||
mut all_rows := [][]orm.Primitive{} | ||
|
||
for row in result { | ||
mut primitive_row := []orm.Primitive{} | ||
for column in result.columns { | ||
primitive_row << row.get_primitive(column.name) ? | ||
} | ||
|
||
all_rows << primitive_row | ||
} | ||
|
||
return all_rows | ||
} | ||
|
||
pub fn (mut c Connection) insert(table string, data orm.QueryData) ? { | ||
values := data.data.map(fn (p orm.Primitive) string { | ||
match p { | ||
orm.InfixType { | ||
// TODO(elliotchance): Not sure what this is? | ||
return '$p' | ||
} | ||
time.Time { | ||
// TODO(elliotchance): This doesn't work. | ||
return '$p' | ||
} | ||
bool { | ||
if p { | ||
return 'TRUE' | ||
} | ||
|
||
return 'FALSE' | ||
} | ||
string { | ||
// TODO(elliotchance): Does not escape correctly. | ||
return '\'$p\'' | ||
} | ||
f32 { | ||
return '$p' | ||
} | ||
f64 { | ||
return '$p' | ||
} | ||
i16 { | ||
return '$p' | ||
} | ||
i64 { | ||
return '$p' | ||
} | ||
i8 { | ||
return '$p' | ||
} | ||
int { | ||
return '$p' | ||
} | ||
u16 { | ||
return '$p' | ||
} | ||
u32 { | ||
return '$p' | ||
} | ||
u64 { | ||
return '$p' | ||
} | ||
u8 { | ||
return '$p' | ||
} | ||
} | ||
}) | ||
|
||
c.query('INSERT INTO "$table" (${data.fields.join(', ')}) VALUES (${values.join(', ')})') ? | ||
} | ||
|
||
pub fn (mut c Connection) update(table string, data orm.QueryData, where orm.QueryData) ? { | ||
panic('update') | ||
} | ||
|
||
pub fn (mut c Connection) delete(table string, where orm.QueryData) ? { | ||
panic('delete') | ||
} | ||
|
||
pub fn (mut c Connection) create(table string, fields []orm.TableField) ? { | ||
mut sql_fields := []string{} | ||
for field in fields { | ||
mut typ := "" | ||
match field.typ { | ||
ast.i8_type_idx, ast.i16_type_idx, ast.byte_type_idx, ast.u16_type_idx { | ||
typ = 'SMALLINT' | ||
} | ||
ast.int_type_idx { | ||
typ = 'INTEGER' | ||
} | ||
ast.i64_type_idx, ast.u32_type_idx, ast.u64_type_idx { | ||
typ = 'BIGINT' | ||
} | ||
ast.f32_type_idx { | ||
typ = 'FLOAT' | ||
} | ||
ast.f64_type_idx { | ||
typ = 'DOUBLE PRECISION' | ||
} | ||
ast.bool_type_idx { | ||
typ = 'BOOLEAN' | ||
} | ||
ast.string_type_idx { | ||
typ = 'VARCHAR(255)' | ||
} | ||
else { | ||
return error('unsupported type: $field.typ') | ||
} | ||
} | ||
|
||
for attr in field.attrs { | ||
if attr.name == 'sql' { | ||
typ = attr.arg | ||
} | ||
} | ||
|
||
sql_fields << '$field.name $typ' | ||
} | ||
|
||
c.query('CREATE TABLE "$table" (${sql_fields.join(', ')})') ? | ||
} | ||
|
||
pub fn (mut c Connection) drop(table string) ? { | ||
panic('drop') | ||
} | ||
|
||
pub fn (mut c Connection) last_id() orm.Primitive { | ||
panic('last_id') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters