mirror of
https://github.com/vlang/v.git
synced 2025-09-16 16:02:29 +03:00
orm: add null handling and option fields (#19379)
* orm: added is none and !is none handling * orm: added NullType, support option fields and deprecate [nonull] Nullable DB fields are now determined by corresponding option struct field. The [nonull] attribute is deprecated and fields are all NOT NULL now, unless they are option fields. New orm primitive, NullType, added to support passing none values to db backends, which have been updated to support it. Also, empty string and 0 numberic values are no longer skipped during insert (since they may be valid values). * orm: fix [nonull] deprecation warning * orm: add null handling to update and select also, improved formatting for orm cgen, and removed optimised operand handling of orm `is` and `!is` operators * sqlite: read/report NULLs using new orm NullType * postgres: returning data primitives now returns new orm.NullType * orm: initialise NullType Primitives properly * orm: do not smart cast operands inside sql * orm: fix bad setting of option value * orm: improve orm_null_test.v, adding/fixing selects * orm: cleanup: rename NullType->Null, use serial const, cgen output * orm: handle automatically generated fields more explicitly During insert, fields which are * [sql: serial] * [default: whatever] and where the data is a default value (e.g., 0, ""), those fields are not sent to the db, so that the db can generate auto-increment or default values. (This was previously done only for [primary] fields, and not in all circumstances, but that is not correct -- primary and serial/auto-increment fields are differnet.) * orm: udpated README * orm: select cgen fixes: read from uninit res; fail to init res * orm: udpated tests * orm: fix option sub-struct fields * orm: fixed joins to option structs Changed orm.write_orm_select() so that you pass to it the name of a resut variable which it populates with the result (or not) and changed use of it in sql_select_expr() and calls in write_orm_select() to populate substructs. * orm: fix pg driver handling of NULL results * orm: move runtime checks to comptime checker; cache checked tables * orm: vfmt :( * orm: markdown formatting * orm: renamed orm.time_ and orm.enum_; updated db drivers * checker: updated orm tests * orm: fix issue setting up ast option values as orm primitives * checker: ORM use of none/options and operations (added tests) * orm: fixed tests * db: clean code * examples: remove orm nonull attributes * orm: skip test memory santisation for orm_null_test.v * orm: make the type-to-primitive converstion fns not public * orm: mv object var c-code from checker->cgen; fix memory corruption Code in checker/orm.v used the SqlStmtLine object field name to store c-specific referenecs to option and array fields (for arrays of children). I moved this logic to cgen. And fixed an issue introduced with option fields, where an array of children was unpacked into a non-array result which could corrupt memory. * orm: fixed vast error * orm: skip 2 tests on ubuntu-musl which require sqlite3.h * cgen: prevent casting a struct (string) * v fmt orm_fkey_attribute.vv, orm_multidim_array.vv, orm_table_attributes.vv; run `VAUTOFIX=1 ./v vlib/v/compiler_errors_test.v`
This commit is contained in:
parent
32bb8cf86d
commit
756075380b
48 changed files with 1327 additions and 585 deletions
|
@ -1770,7 +1770,7 @@ fn (t Tree) sql_stmt_line(node ast.SqlStmtLine) &Node {
|
||||||
obj.add_terse('ast_type', t.string_node('SqlStmtLine'))
|
obj.add_terse('ast_type', t.string_node('SqlStmtLine'))
|
||||||
obj.add_terse('kind', t.enum_node(node.kind))
|
obj.add_terse('kind', t.enum_node(node.kind))
|
||||||
obj.add_terse('table_expr', t.type_expr(node.table_expr))
|
obj.add_terse('table_expr', t.type_expr(node.table_expr))
|
||||||
obj.add_terse('object_var_name', t.string_node(node.object_var_name))
|
obj.add_terse('object_var', t.string_node(node.object_var))
|
||||||
obj.add_terse('where_expr', t.expr(node.where_expr))
|
obj.add_terse('where_expr', t.expr(node.where_expr))
|
||||||
obj.add_terse('fields', t.array_node_struct_field(node.fields))
|
obj.add_terse('fields', t.array_node_struct_field(node.fields))
|
||||||
obj.add_terse('updated_columns', t.array_node_string(node.updated_columns))
|
obj.add_terse('updated_columns', t.array_node_string(node.updated_columns))
|
||||||
|
|
|
@ -132,6 +132,7 @@ const (
|
||||||
'vlib/orm/orm_string_interpolation_in_where_test.v',
|
'vlib/orm/orm_string_interpolation_in_where_test.v',
|
||||||
'vlib/orm/orm_interface_test.v',
|
'vlib/orm/orm_interface_test.v',
|
||||||
'vlib/orm/orm_mut_db_test.v',
|
'vlib/orm/orm_mut_db_test.v',
|
||||||
|
'vlib/orm/orm_null_test.v',
|
||||||
'vlib/orm/orm_result_test.v',
|
'vlib/orm/orm_result_test.v',
|
||||||
'vlib/orm/orm_custom_operators_test.v',
|
'vlib/orm/orm_custom_operators_test.v',
|
||||||
'vlib/orm/orm_fk_test.v',
|
'vlib/orm/orm_fk_test.v',
|
||||||
|
@ -214,6 +215,7 @@ const (
|
||||||
'vlib/orm/orm_insert_test.v',
|
'vlib/orm/orm_insert_test.v',
|
||||||
'vlib/orm/orm_insert_reserved_name_test.v',
|
'vlib/orm/orm_insert_reserved_name_test.v',
|
||||||
'vlib/orm/orm_fn_calls_test.v',
|
'vlib/orm/orm_fn_calls_test.v',
|
||||||
|
'vlib/orm/orm_null_test.v',
|
||||||
'vlib/orm/orm_last_id_test.v',
|
'vlib/orm/orm_last_id_test.v',
|
||||||
'vlib/orm/orm_string_interpolation_in_where_test.v',
|
'vlib/orm/orm_string_interpolation_in_where_test.v',
|
||||||
'vlib/orm/orm_interface_test.v',
|
'vlib/orm/orm_interface_test.v',
|
||||||
|
|
|
@ -4,6 +4,6 @@ module main
|
||||||
struct Product {
|
struct Product {
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
user_id int
|
user_id int
|
||||||
name string [nonull; sql_type: 'TEXT']
|
name string [sql_type: 'TEXT']
|
||||||
created_at string [default: 'CURRENT_TIMESTAMP']
|
created_at string [default: 'CURRENT_TIMESTAMP']
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ module main
|
||||||
pub struct User {
|
pub struct User {
|
||||||
mut:
|
mut:
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
username string [nonull; sql_type: 'TEXT'; unique]
|
username string [sql_type: 'TEXT'; unique]
|
||||||
password string [nonull; sql_type: 'TEXT']
|
password string [sql_type: 'TEXT']
|
||||||
active bool
|
active bool
|
||||||
products []Product [fkey: 'user_id']
|
products []Product [fkey: 'user_id']
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.Qu
|
||||||
orm.type_string {
|
orm.type_string {
|
||||||
string_binds_map[i] = mysql_bind
|
string_binds_map[i] = mysql_bind
|
||||||
}
|
}
|
||||||
orm.time {
|
orm.time_ {
|
||||||
match field_type {
|
match field_type {
|
||||||
.type_long {
|
.type_long {
|
||||||
mysql_bind.buffer_type = C.MYSQL_TYPE_LONG
|
mysql_bind.buffer_type = C.MYSQL_TYPE_LONG
|
||||||
|
@ -250,6 +250,9 @@ fn stmt_bind_primitive(mut stmt Stmt, data orm.Primitive) {
|
||||||
orm.InfixType {
|
orm.InfixType {
|
||||||
stmt_bind_primitive(mut stmt, data.right)
|
stmt_bind_primitive(mut stmt, data.right)
|
||||||
}
|
}
|
||||||
|
orm.Null {
|
||||||
|
stmt.bind_null()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,7 +300,7 @@ fn data_pointers_to_primitives(data_pointers []&u8, types []int, field_types []F
|
||||||
orm.type_string {
|
orm.type_string {
|
||||||
primitive = unsafe { cstring_to_vstring(&char(data)) }
|
primitive = unsafe { cstring_to_vstring(&char(data)) }
|
||||||
}
|
}
|
||||||
orm.time {
|
orm.time_ {
|
||||||
match field_types[i] {
|
match field_types[i] {
|
||||||
.type_long {
|
.type_long {
|
||||||
timestamp := *(unsafe { &int(data) })
|
timestamp := *(unsafe { &int(data) })
|
||||||
|
@ -310,6 +313,9 @@ fn data_pointers_to_primitives(data_pointers []&u8, types []int, field_types []F
|
||||||
else {}
|
else {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
orm.enum_ {
|
||||||
|
primitive = *(unsafe { &i64(data) })
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
return error('Unknown type ${types[i]}')
|
return error('Unknown type ${types[i]}')
|
||||||
}
|
}
|
||||||
|
@ -329,10 +335,10 @@ fn mysql_type_from_v(typ int) !string {
|
||||||
orm.type_idx['i16'], orm.type_idx['u16'] {
|
orm.type_idx['i16'], orm.type_idx['u16'] {
|
||||||
'SMALLINT'
|
'SMALLINT'
|
||||||
}
|
}
|
||||||
orm.type_idx['int'], orm.type_idx['u32'], orm.time {
|
orm.type_idx['int'], orm.type_idx['u32'], orm.time_ {
|
||||||
'INT'
|
'INT'
|
||||||
}
|
}
|
||||||
orm.type_idx['i64'], orm.type_idx['u64'] {
|
orm.type_idx['i64'], orm.type_idx['u64'], orm.enum_ {
|
||||||
'BIGINT'
|
'BIGINT'
|
||||||
}
|
}
|
||||||
orm.type_idx['f32'] {
|
orm.type_idx['f32'] {
|
||||||
|
|
|
@ -247,6 +247,14 @@ pub fn (mut stmt Stmt) bind_text(b string) {
|
||||||
stmt.bind(mysql.mysql_type_string, b.str, u32(b.len))
|
stmt.bind(mysql.mysql_type_string, b.str, u32(b.len))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bind_null binds a single NULL value to the statement `stmt`
|
||||||
|
pub fn (mut stmt Stmt) bind_null() {
|
||||||
|
stmt.binds << C.MYSQL_BIND{
|
||||||
|
buffer_type: mysql.mysql_type_null
|
||||||
|
length: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// bind binds a single value pointed by `buffer`, to the statement `stmt`. The buffer length must be passed as well in `buf_len`.
|
// bind binds a single value pointed by `buffer`, to the statement `stmt`. The buffer length must be passed as well in `buf_len`.
|
||||||
// Note: it is more convenient to use one of the other bind_XYZ methods.
|
// Note: it is more convenient to use one of the other bind_XYZ methods.
|
||||||
pub fn (mut stmt Stmt) bind(typ int, buffer voidptr, buf_len u32) {
|
pub fn (mut stmt Stmt) bind(typ int, buffer voidptr, buf_len u32) {
|
||||||
|
|
|
@ -10,18 +10,17 @@ import net.conv
|
||||||
pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive {
|
pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive {
|
||||||
query := orm.orm_select_gen(config, '"', true, '$', 1, where)
|
query := orm.orm_select_gen(config, '"', true, '$', 1, where)
|
||||||
|
|
||||||
res := pg_stmt_worker(db, query, where, data)!
|
rows := pg_stmt_worker(db, query, where, data)!
|
||||||
|
|
||||||
mut ret := [][]orm.Primitive{}
|
mut ret := [][]orm.Primitive{}
|
||||||
|
|
||||||
if config.is_count {
|
if config.is_count {
|
||||||
}
|
}
|
||||||
|
|
||||||
for row in res {
|
for row in rows {
|
||||||
mut row_data := []orm.Primitive{}
|
mut row_data := []orm.Primitive{}
|
||||||
for i, val in row.vals {
|
for i, val in row.vals {
|
||||||
field := str_to_primitive(val, config.types[i])!
|
row_data << val_to_primitive(val, config.types[i])!
|
||||||
row_data << field
|
|
||||||
}
|
}
|
||||||
ret << row_data
|
ret << row_data
|
||||||
}
|
}
|
||||||
|
@ -189,6 +188,12 @@ fn pg_stmt_match(mut types []u32, mut vals []&char, mut lens []int, mut formats
|
||||||
orm.InfixType {
|
orm.InfixType {
|
||||||
pg_stmt_match(mut types, mut vals, mut lens, mut formats, data.right)
|
pg_stmt_match(mut types, mut vals, mut lens, mut formats, data.right)
|
||||||
}
|
}
|
||||||
|
orm.Null {
|
||||||
|
types << u32(0) // we do not know col type, let server infer
|
||||||
|
vals << &char(0) // NULL pointer indicates NULL
|
||||||
|
lens << int(0) // ignored
|
||||||
|
formats << 0 // ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,9 +208,12 @@ fn pg_type_from_v(typ int) !string {
|
||||||
orm.type_idx['int'], orm.type_idx['u32'] {
|
orm.type_idx['int'], orm.type_idx['u32'] {
|
||||||
'INT'
|
'INT'
|
||||||
}
|
}
|
||||||
orm.time {
|
orm.time_ {
|
||||||
'TIMESTAMP'
|
'TIMESTAMP'
|
||||||
}
|
}
|
||||||
|
orm.enum_ {
|
||||||
|
'BIGINT'
|
||||||
|
}
|
||||||
orm.type_idx['i64'], orm.type_idx['u64'] {
|
orm.type_idx['i64'], orm.type_idx['u64'] {
|
||||||
'BIGINT'
|
'BIGINT'
|
||||||
}
|
}
|
||||||
|
@ -231,7 +239,8 @@ fn pg_type_from_v(typ int) !string {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
fn str_to_primitive(str string, typ int) !orm.Primitive {
|
fn val_to_primitive(val ?string, typ int) !orm.Primitive {
|
||||||
|
if str := val {
|
||||||
match typ {
|
match typ {
|
||||||
// bool
|
// bool
|
||||||
orm.type_idx['bool'] {
|
orm.type_idx['bool'] {
|
||||||
|
@ -284,7 +293,7 @@ fn str_to_primitive(str string, typ int) !orm.Primitive {
|
||||||
orm.type_string {
|
orm.type_string {
|
||||||
return orm.Primitive(str)
|
return orm.Primitive(str)
|
||||||
}
|
}
|
||||||
orm.time {
|
orm.time_ {
|
||||||
if str.contains_any(' /:-') {
|
if str.contains_any(' /:-') {
|
||||||
date_time_str := time.parse(str)!
|
date_time_str := time.parse(str)!
|
||||||
return orm.Primitive(date_time_str)
|
return orm.Primitive(date_time_str)
|
||||||
|
@ -293,7 +302,13 @@ fn str_to_primitive(str string, typ int) !orm.Primitive {
|
||||||
timestamp := str.int()
|
timestamp := str.int()
|
||||||
return orm.Primitive(time.unix(timestamp))
|
return orm.Primitive(time.unix(timestamp))
|
||||||
}
|
}
|
||||||
|
orm.enum_ {
|
||||||
|
return orm.Primitive(str.i64())
|
||||||
|
}
|
||||||
else {}
|
else {}
|
||||||
}
|
}
|
||||||
return error('Unknown field type ${typ}')
|
return error('Unknown field type ${typ}')
|
||||||
|
} else {
|
||||||
|
return orm.Null{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ mut:
|
||||||
|
|
||||||
pub struct Row {
|
pub struct Row {
|
||||||
pub mut:
|
pub mut:
|
||||||
vals []string
|
vals []?string
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -110,6 +110,8 @@ fn C.PQexec(res &C.PGconn, const_query &char) &C.PGresult
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
|
fn C.PQgetisnull(const_res &C.PGresult, int, int) int
|
||||||
|
|
||||||
fn C.PQgetvalue(const_res &C.PGresult, int, int) &char
|
fn C.PQgetvalue(const_res &C.PGresult, int, int) &char
|
||||||
|
|
||||||
fn C.PQresultStatus(const_res &C.PGresult) int
|
fn C.PQresultStatus(const_res &C.PGresult) int
|
||||||
|
@ -179,10 +181,14 @@ fn res_to_rows(res voidptr) []Row {
|
||||||
for i in 0 .. nr_rows {
|
for i in 0 .. nr_rows {
|
||||||
mut row := Row{}
|
mut row := Row{}
|
||||||
for j in 0 .. nr_cols {
|
for j in 0 .. nr_cols {
|
||||||
|
if C.PQgetisnull(res, i, j) != 0 {
|
||||||
|
row.vals << none
|
||||||
|
} else {
|
||||||
val := C.PQgetvalue(res, i, j)
|
val := C.PQgetvalue(res, i, j)
|
||||||
sval := unsafe { val.vstring() }
|
sval := unsafe { val.vstring() }
|
||||||
row.vals << sval
|
row.vals << sval
|
||||||
}
|
}
|
||||||
|
}
|
||||||
rows << row
|
rows << row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +215,7 @@ pub fn (db DB) q_int(query string) !int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
val := row.vals[0]
|
val := row.vals[0]
|
||||||
return val.int()
|
return val or { '0' }.int()
|
||||||
}
|
}
|
||||||
|
|
||||||
// q_string submit a command to the database server and
|
// q_string submit a command to the database server and
|
||||||
|
@ -226,7 +232,7 @@ pub fn (db DB) q_string(query string) !string {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
val := row.vals[0]
|
val := row.vals[0]
|
||||||
return val
|
return val or { '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
// q_strings submit a command to the database server and
|
// q_strings submit a command to the database server and
|
||||||
|
|
|
@ -145,35 +145,43 @@ fn bind(stmt Stmt, c &int, data orm.Primitive) int {
|
||||||
orm.InfixType {
|
orm.InfixType {
|
||||||
err = bind(stmt, c, data.right)
|
err = bind(stmt, c, data.right)
|
||||||
}
|
}
|
||||||
|
orm.Null {
|
||||||
|
err = stmt.bind_null(c)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selects column in result and converts it to an orm.Primitive
|
// Selects column in result and converts it to an orm.Primitive
|
||||||
fn (stmt Stmt) sqlite_select_column(idx int, typ int) !orm.Primitive {
|
fn (stmt Stmt) sqlite_select_column(idx int, typ int) !orm.Primitive {
|
||||||
mut primitive := orm.Primitive(0)
|
|
||||||
|
|
||||||
if typ in orm.nums || typ == -1 {
|
if typ in orm.nums || typ == -1 {
|
||||||
primitive = stmt.get_int(idx)
|
return stmt.get_int(idx) or { return orm.Null{} }
|
||||||
} else if typ in orm.num64 {
|
} else if typ in orm.num64 {
|
||||||
primitive = stmt.get_i64(idx)
|
return stmt.get_i64(idx) or { return orm.Null{} }
|
||||||
} else if typ in orm.float {
|
} else if typ in orm.float {
|
||||||
primitive = stmt.get_f64(idx)
|
return stmt.get_f64(idx) or { return orm.Null{} }
|
||||||
} else if typ == orm.type_string {
|
} else if typ == orm.type_string {
|
||||||
primitive = stmt.get_text(idx).clone()
|
if v := stmt.get_text(idx) {
|
||||||
} else if typ == orm.time {
|
return v.clone()
|
||||||
d := stmt.get_int(idx)
|
} else {
|
||||||
primitive = time.unix(d)
|
return orm.Null{}
|
||||||
|
}
|
||||||
|
} else if typ == orm.enum_ {
|
||||||
|
return stmt.get_i64(idx) or { return orm.Null{} }
|
||||||
|
} else if typ == orm.time_ {
|
||||||
|
if v := stmt.get_int(idx) {
|
||||||
|
return time.unix(v)
|
||||||
|
} else {
|
||||||
|
return orm.Null{}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return error('Unknown type ${typ}')
|
return error('Unknown type ${typ}')
|
||||||
}
|
}
|
||||||
|
|
||||||
return primitive
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert type int to sql type string
|
// Convert type int to sql type string
|
||||||
fn sqlite_type_from_v(typ int) !string {
|
fn sqlite_type_from_v(typ int) !string {
|
||||||
return if typ in orm.nums || typ < 0 || typ in orm.num64 || typ == orm.time {
|
return if typ in orm.nums || typ in orm.num64 || typ in [orm.serial, orm.time_, orm.enum_] {
|
||||||
'INTEGER'
|
'INTEGER'
|
||||||
} else if typ in orm.float {
|
} else if typ in orm.float {
|
||||||
'REAL'
|
'REAL'
|
||||||
|
|
|
@ -116,6 +116,8 @@ fn C.sqlite3_column_double(&C.sqlite3_stmt, int) f64
|
||||||
|
|
||||||
fn C.sqlite3_column_count(&C.sqlite3_stmt) int
|
fn C.sqlite3_column_count(&C.sqlite3_stmt) int
|
||||||
|
|
||||||
|
fn C.sqlite3_column_type(&C.sqlite3_stmt, int) int
|
||||||
|
|
||||||
//
|
//
|
||||||
fn C.sqlite3_errstr(int) &char
|
fn C.sqlite3_errstr(int) &char
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,9 @@ struct TestCustomSqlType {
|
||||||
struct TestDefaultAtribute {
|
struct TestDefaultAtribute {
|
||||||
id string [primary; sql: serial]
|
id string [primary; sql: serial]
|
||||||
name string
|
name string
|
||||||
created_at string [default: 'CURRENT_TIME']
|
created_at ?string [default: 'CURRENT_TIME']
|
||||||
created_at1 string [default: 'CURRENT_DATE']
|
created_at1 ?string [default: 'CURRENT_DATE']
|
||||||
created_at2 string [default: 'CURRENT_TIMESTAMP']
|
created_at2 ?string [default: 'CURRENT_TIMESTAMP']
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EntityToTest {
|
struct EntityToTest {
|
||||||
|
@ -153,12 +153,12 @@ fn test_sqlite_orm() {
|
||||||
|
|
||||||
result_test_default_atribute := test_default_atributes.first()
|
result_test_default_atribute := test_default_atributes.first()
|
||||||
assert result_test_default_atribute.name == 'Hitalo'
|
assert result_test_default_atribute.name == 'Hitalo'
|
||||||
assert test_default_atribute.created_at.len == 0
|
assert test_default_atribute.created_at or { '' } == ''
|
||||||
assert test_default_atribute.created_at1.len == 0
|
assert test_default_atribute.created_at1 or { '' } == ''
|
||||||
assert test_default_atribute.created_at2.len == 0
|
assert test_default_atribute.created_at2 or { '' } == ''
|
||||||
assert result_test_default_atribute.created_at.len == 8 // HH:MM:SS
|
assert result_test_default_atribute.created_at or { '' }.len == 8 // HH:MM:SS
|
||||||
assert result_test_default_atribute.created_at1.len == 10 // YYYY-MM-DD
|
assert result_test_default_atribute.created_at1 or { '' }.len == 10 // YYYY-MM-DD
|
||||||
assert result_test_default_atribute.created_at2.len == 19 // YYYY-MM-DD HH:MM:SS
|
assert result_test_default_atribute.created_at2 or { '' }.len == 19 // YYYY-MM-DD HH:MM:SS
|
||||||
|
|
||||||
sql db {
|
sql db {
|
||||||
drop table TestDefaultAtribute
|
drop table TestDefaultAtribute
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
module sqlite
|
module sqlite
|
||||||
|
|
||||||
|
fn C.sqlite3_bind_null(&C.sqlite3_stmt, int) int
|
||||||
fn C.sqlite3_bind_double(&C.sqlite3_stmt, int, f64) int
|
fn C.sqlite3_bind_double(&C.sqlite3_stmt, int, f64) int
|
||||||
fn C.sqlite3_bind_int(&C.sqlite3_stmt, int, int) int
|
fn C.sqlite3_bind_int(&C.sqlite3_stmt, int, int) int
|
||||||
fn C.sqlite3_bind_int64(&C.sqlite3_stmt, int, i64) int
|
fn C.sqlite3_bind_int64(&C.sqlite3_stmt, int, i64) int
|
||||||
|
@ -21,6 +22,10 @@ fn (db &DB) new_init_stmt(query string) !Stmt {
|
||||||
return Stmt{stmt, db}
|
return Stmt{stmt, db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) bind_null(idx int) int {
|
||||||
|
return C.sqlite3_bind_null(stmt.stmt, idx)
|
||||||
|
}
|
||||||
|
|
||||||
fn (stmt &Stmt) bind_int(idx int, v int) int {
|
fn (stmt &Stmt) bind_int(idx int, v int) int {
|
||||||
return C.sqlite3_bind_int(stmt.stmt, idx, v)
|
return C.sqlite3_bind_int(stmt.stmt, idx, v)
|
||||||
}
|
}
|
||||||
|
@ -37,26 +42,41 @@ fn (stmt &Stmt) bind_text(idx int, s string) int {
|
||||||
return C.sqlite3_bind_text(stmt.stmt, idx, voidptr(s.str), s.len, 0)
|
return C.sqlite3_bind_text(stmt.stmt, idx, voidptr(s.str), s.len, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (stmt &Stmt) get_int(idx int) int {
|
fn (stmt &Stmt) get_int(idx int) ?int {
|
||||||
|
if C.sqlite3_column_type(stmt.stmt, idx) == C.SQLITE_NULL {
|
||||||
|
return none
|
||||||
|
} else {
|
||||||
return C.sqlite3_column_int(stmt.stmt, idx)
|
return C.sqlite3_column_int(stmt.stmt, idx)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn (stmt &Stmt) get_i64(idx int) i64 {
|
fn (stmt &Stmt) get_i64(idx int) ?i64 {
|
||||||
|
if C.sqlite3_column_type(stmt.stmt, idx) == C.SQLITE_NULL {
|
||||||
|
return none
|
||||||
|
} else {
|
||||||
return C.sqlite3_column_int64(stmt.stmt, idx)
|
return C.sqlite3_column_int64(stmt.stmt, idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (stmt &Stmt) get_f64(idx int) f64 {
|
|
||||||
return C.sqlite3_column_double(stmt.stmt, idx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (stmt &Stmt) get_text(idx int) string {
|
fn (stmt &Stmt) get_f64(idx int) ?f64 {
|
||||||
b := &char(C.sqlite3_column_text(stmt.stmt, idx))
|
if C.sqlite3_column_type(stmt.stmt, idx) == C.SQLITE_NULL {
|
||||||
|
return none
|
||||||
|
} else {
|
||||||
|
return C.sqlite3_column_double(stmt.stmt, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (stmt &Stmt) get_text(idx int) ?string {
|
||||||
|
if C.sqlite3_column_type(stmt.stmt, idx) == C.SQLITE_NULL {
|
||||||
|
return none
|
||||||
|
} else {
|
||||||
|
b := &char(C.sqlite3_column_text(stmt.stmt, idx))
|
||||||
if b == &char(0) {
|
if b == &char(0) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return unsafe { b.vstring() }
|
return unsafe { b.vstring() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn (stmt &Stmt) get_count() int {
|
fn (stmt &Stmt) get_count() int {
|
||||||
return C.sqlite3_column_count(stmt.stmt)
|
return C.sqlite3_column_count(stmt.stmt)
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
# ORM
|
# ORM
|
||||||
|
|
||||||
|
## Null
|
||||||
|
|
||||||
|
Use option fields in V structs for fields which can be NULL. Regular,
|
||||||
|
non-option fields are defied as NOT NULL when creating tables.
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
### Structs
|
### Structs
|
||||||
|
@ -11,12 +16,13 @@
|
||||||
- `[primary]` sets the field as the primary key
|
- `[primary]` sets the field as the primary key
|
||||||
- `[unique]` sets the field as unique
|
- `[unique]` sets the field as unique
|
||||||
- `[unique: 'foo']` adds the field to a unique group
|
- `[unique: 'foo']` adds the field to a unique group
|
||||||
- `[nonull]` set the field as not null
|
|
||||||
- `[skip]` or `[sql: '-']` field will be skipped
|
- `[skip]` or `[sql: '-']` field will be skipped
|
||||||
- `[sql: type]` where `type` is a V type such as `int` or `f64`, or special type `serial`
|
- `[sql: type]` where `type` is a V type such as `int` or `f64`
|
||||||
|
- `[sql: serial]` lets the DB backend choose a column type for a auto-increment field
|
||||||
- `[sql: 'name']` sets a custom column name for the field
|
- `[sql: 'name']` sets a custom column name for the field
|
||||||
- `[sql_type: 'SQL TYPE']` sets the sql type which is used in sql
|
- `[sql_type: 'SQL TYPE']` sets the sql type which is used in sql
|
||||||
- `[default: 'sql defaults']` sets the default value or function when create a new table
|
- `[default: 'raw_sql]` inserts `raw_sql` verbatim in a "DEFAULT" clause when
|
||||||
|
create a new table, allowing for values like `CURRENT_TIME`
|
||||||
- `[fkey: 'parent_id']` sets foreign key for an field which holds an array
|
- `[fkey: 'parent_id']` sets foreign key for an field which holds an array
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -24,12 +30,13 @@
|
||||||
```v ignore
|
```v ignore
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
[table: 'foos']
|
||||||
struct Foo {
|
struct Foo {
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
name string [nonull]
|
name string
|
||||||
created_at time.Time [sql_type: 'DATETIME']
|
created_at time.Time [default: 'CURRENT_TIME]
|
||||||
updated_at string [sql_type: 'DATETIME']
|
updated_at ?string [sql_type: 'TIMESTAMP]
|
||||||
deleted_at time.Time
|
deleted_at ?time.Time
|
||||||
children []Child [fkey: 'parent_id']
|
children []Child [fkey: 'parent_id']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +69,8 @@ sql db {
|
||||||
foo := Foo{
|
foo := Foo{
|
||||||
name: 'abc'
|
name: 'abc'
|
||||||
created_at: time.now()
|
created_at: time.now()
|
||||||
updated_at: time.now()
|
// updated_at defaults to none
|
||||||
deleted_at: time.now()
|
// deleted_at defaults to none
|
||||||
children: [
|
children: [
|
||||||
Child{
|
Child{
|
||||||
name: 'abc'
|
name: 'abc'
|
||||||
|
@ -79,14 +86,22 @@ sql db {
|
||||||
}!
|
}!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When inserting, `[sql: serial]` fields, and fields with a `[default: 'raw_sql']`
|
||||||
|
attribute are not sent to the database when the value being sent is the default
|
||||||
|
for the V struct field (e.g., 0 int, or an empty string). This allows the
|
||||||
|
database to insert default values for auto-increment fields and where you have
|
||||||
|
specified a default.
|
||||||
|
|
||||||
### Update
|
### Update
|
||||||
|
|
||||||
```v ignore
|
```v ignore
|
||||||
sql db {
|
sql db {
|
||||||
update Foo set name = 'cde', updated_at = time.now() where name == 'abc'
|
update Foo set updated_at = time.now() where name == 'abc' && updated_at is none
|
||||||
}!
|
}!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that `is none` and `!is none` can be used to select for NULL fields.
|
||||||
|
|
||||||
### Delete
|
### Delete
|
||||||
```v ignore
|
```v ignore
|
||||||
sql db {
|
sql db {
|
||||||
|
|
278
vlib/orm/orm.v
278
vlib/orm/orm.v
|
@ -18,8 +18,9 @@ pub const (
|
||||||
typeof[f64]().idx,
|
typeof[f64]().idx,
|
||||||
]
|
]
|
||||||
type_string = typeof[string]().idx
|
type_string = typeof[string]().idx
|
||||||
time = -2
|
|
||||||
serial = -1
|
serial = -1
|
||||||
|
time_ = -2
|
||||||
|
enum_ = -3
|
||||||
type_idx = {
|
type_idx = {
|
||||||
'i8': typeof[i8]().idx
|
'i8': typeof[i8]().idx
|
||||||
'i16': typeof[i16]().idx
|
'i16': typeof[i16]().idx
|
||||||
|
@ -35,9 +36,11 @@ pub const (
|
||||||
'string': typeof[string]().idx
|
'string': typeof[string]().idx
|
||||||
}
|
}
|
||||||
string_max_len = 2048
|
string_max_len = 2048
|
||||||
|
null_primitive = Primitive(Null{})
|
||||||
)
|
)
|
||||||
|
|
||||||
pub type Primitive = InfixType
|
pub type Primitive = InfixType
|
||||||
|
| Null
|
||||||
| bool
|
| bool
|
||||||
| f32
|
| f32
|
||||||
| f64
|
| f64
|
||||||
|
@ -52,6 +55,8 @@ pub type Primitive = InfixType
|
||||||
| u64
|
| u64
|
||||||
| u8
|
| u8
|
||||||
|
|
||||||
|
pub struct Null {}
|
||||||
|
|
||||||
pub enum OperationKind {
|
pub enum OperationKind {
|
||||||
neq // !=
|
neq // !=
|
||||||
eq // ==
|
eq // ==
|
||||||
|
@ -60,6 +65,8 @@ pub enum OperationKind {
|
||||||
ge // >=
|
ge // >=
|
||||||
le // <=
|
le // <=
|
||||||
orm_like // LIKE
|
orm_like // LIKE
|
||||||
|
@is // is
|
||||||
|
is_not // !is
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MathOperationKind {
|
pub enum MathOperationKind {
|
||||||
|
@ -94,6 +101,8 @@ fn (kind OperationKind) to_str() string {
|
||||||
.ge { '>=' }
|
.ge { '>=' }
|
||||||
.le { '<=' }
|
.le { '<=' }
|
||||||
.orm_like { 'LIKE' }
|
.orm_like { 'LIKE' }
|
||||||
|
.@is { 'IS' }
|
||||||
|
.is_not { 'IS NOT' }
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
@ -114,6 +123,7 @@ fn (kind OrderType) to_str() string {
|
||||||
// Every field, data, type & kind of operation in the expr share the same index in the arrays
|
// Every field, data, type & kind of operation in the expr share the same index in the arrays
|
||||||
// is_and defines how they're addicted to each other either and or or
|
// is_and defines how they're addicted to each other either and or or
|
||||||
// parentheses defines which fields will be inside ()
|
// parentheses defines which fields will be inside ()
|
||||||
|
// auto_fields are indexes of fields where db should generate a value when absent in an insert
|
||||||
pub struct QueryData {
|
pub struct QueryData {
|
||||||
pub:
|
pub:
|
||||||
fields []string
|
fields []string
|
||||||
|
@ -121,7 +131,7 @@ pub:
|
||||||
types []int
|
types []int
|
||||||
parentheses [][]int
|
parentheses [][]int
|
||||||
kinds []OperationKind
|
kinds []OperationKind
|
||||||
primary_column_name string
|
auto_fields []int
|
||||||
is_and []bool
|
is_and []bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,11 +146,10 @@ pub struct TableField {
|
||||||
pub:
|
pub:
|
||||||
name string
|
name string
|
||||||
typ int
|
typ int
|
||||||
is_time bool
|
nullable bool
|
||||||
default_val string
|
default_val string
|
||||||
is_arr bool
|
|
||||||
is_enum bool
|
|
||||||
attrs []StructAttribute
|
attrs []StructAttribute
|
||||||
|
is_arr bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// table - Table name
|
// table - Table name
|
||||||
|
@ -190,7 +199,7 @@ pub interface Connection {
|
||||||
// Generates an sql stmt, from universal parameter
|
// Generates an sql stmt, from universal parameter
|
||||||
// q - The quotes character, which can be different in every type, so it's variable
|
// q - The quotes character, which can be different in every type, so it's variable
|
||||||
// num - Stmt uses nums at prepared statements (? or ?1)
|
// num - Stmt uses nums at prepared statements (? or ?1)
|
||||||
// qm - Character for prepared statement, qm because of quotation mark like in sqlite
|
// qm - Character for prepared statement (qm for question mark, as in sqlite)
|
||||||
// start_pos - When num is true, it's the start position of the counter
|
// start_pos - When num is true, it's the start position of the counter
|
||||||
pub fn orm_stmt_gen(sql_dialect SQLDialect, table string, q string, kind StmtKind, num bool, qm string, start_pos int, data QueryData, where QueryData) (string, QueryData) {
|
pub fn orm_stmt_gen(sql_dialect SQLDialect, table string, q string, kind StmtKind, num bool, qm string, start_pos int, data QueryData, where QueryData) (string, QueryData) {
|
||||||
mut str := ''
|
mut str := ''
|
||||||
|
@ -205,37 +214,28 @@ pub fn orm_stmt_gen(sql_dialect SQLDialect, table string, q string, kind StmtKin
|
||||||
|
|
||||||
for i in 0 .. data.fields.len {
|
for i in 0 .. data.fields.len {
|
||||||
column_name := data.fields[i]
|
column_name := data.fields[i]
|
||||||
is_primary_column := column_name == data.primary_column_name
|
is_auto_field := i in data.auto_fields
|
||||||
|
|
||||||
if data.data.len > 0 {
|
if data.data.len > 0 {
|
||||||
// Allow the database to insert an automatically generated primary key
|
// skip fields and allow the database to insert default and
|
||||||
// under the hood if it is not passed by the user.
|
// serial (auto-increment) values where a default (or no)
|
||||||
tidx := data.data[i].type_idx()
|
// value was provided
|
||||||
if is_primary_column && (tidx in orm.nums || tidx in orm.num64) {
|
if is_auto_field {
|
||||||
x := data.data[i]
|
mut x := data.data[i]
|
||||||
match x {
|
skip_auto_field := match mut x {
|
||||||
i8, i16, int, i64, u8, u16, u32, u64 {
|
Null { true }
|
||||||
if i64(x) == 0 {
|
string { x == '' }
|
||||||
|
i8, i16, int, i64, u8, u16, u32, u64 { u64(x) == 0 }
|
||||||
|
f32, f64 { f64(x) == 0 }
|
||||||
|
time.Time { x == time.Time{} }
|
||||||
|
bool { !x }
|
||||||
|
else { false }
|
||||||
|
}
|
||||||
|
if skip_auto_field {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match data.data[i].type_name() {
|
|
||||||
'string' {
|
|
||||||
if (data.data[i] as string).len == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'time.Time' {
|
|
||||||
if (data.data[i] as time.Time).unix == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
data_data << data.data[i]
|
data_data << data.data[i]
|
||||||
}
|
}
|
||||||
select_fields << '${q}${column_name}${q}'
|
select_fields << '${q}${column_name}${q}'
|
||||||
|
@ -300,35 +300,9 @@ pub fn orm_stmt_gen(sql_dialect SQLDialect, table string, q string, kind StmtKin
|
||||||
str += 'DELETE FROM ${q}${table}${q} WHERE '
|
str += 'DELETE FROM ${q}${table}${q} WHERE '
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// where
|
||||||
if kind == .update || kind == .delete {
|
if kind == .update || kind == .delete {
|
||||||
for i, field in where.fields {
|
str += gen_where_clause(where, q, qm, num, mut &c)
|
||||||
mut pre_par := false
|
|
||||||
mut post_par := false
|
|
||||||
for par in where.parentheses {
|
|
||||||
if i in par {
|
|
||||||
pre_par = par[0] == i
|
|
||||||
post_par = par[1] == i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pre_par {
|
|
||||||
str += '('
|
|
||||||
}
|
|
||||||
str += '${q}${field}${q} ${where.kinds[i].to_str()} ${qm}'
|
|
||||||
if num {
|
|
||||||
str += '${c}'
|
|
||||||
c++
|
|
||||||
}
|
|
||||||
if post_par {
|
|
||||||
str += ')'
|
|
||||||
}
|
|
||||||
if i < where.fields.len - 1 {
|
|
||||||
if where.is_and[i] {
|
|
||||||
str += ' AND '
|
|
||||||
} else {
|
|
||||||
str += ' OR '
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
str += ';'
|
str += ';'
|
||||||
$if trace_orm_stmt ? {
|
$if trace_orm_stmt ? {
|
||||||
|
@ -371,34 +345,7 @@ pub fn orm_select_gen(cfg SelectConfig, q string, num bool, qm string, start_pos
|
||||||
|
|
||||||
if cfg.has_where {
|
if cfg.has_where {
|
||||||
str += ' WHERE '
|
str += ' WHERE '
|
||||||
for i, field in where.fields {
|
str += gen_where_clause(where, q, qm, num, mut &c)
|
||||||
mut pre_par := false
|
|
||||||
mut post_par := false
|
|
||||||
for par in where.parentheses {
|
|
||||||
if i in par {
|
|
||||||
pre_par = par[0] == i
|
|
||||||
post_par = par[1] == i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pre_par {
|
|
||||||
str += '('
|
|
||||||
}
|
|
||||||
str += '${q}${field}${q} ${where.kinds[i].to_str()} ${qm}'
|
|
||||||
if num {
|
|
||||||
str += '${c}'
|
|
||||||
c++
|
|
||||||
}
|
|
||||||
if post_par {
|
|
||||||
str += ')'
|
|
||||||
}
|
|
||||||
if i < where.fields.len - 1 {
|
|
||||||
if where.is_and[i] {
|
|
||||||
str += ' AND '
|
|
||||||
} else {
|
|
||||||
str += ' OR '
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: do not order, if the user did not want it explicitly,
|
// Note: do not order, if the user did not want it explicitly,
|
||||||
|
@ -435,6 +382,39 @@ pub fn orm_select_gen(cfg SelectConfig, q string, num bool, qm string, start_pos
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gen_where_clause(where QueryData, q string, qm string, num bool, mut c &int) string {
|
||||||
|
mut str := ''
|
||||||
|
for i, field in where.fields {
|
||||||
|
mut pre_par := false
|
||||||
|
mut post_par := false
|
||||||
|
for par in where.parentheses {
|
||||||
|
if i in par {
|
||||||
|
pre_par = par[0] == i
|
||||||
|
post_par = par[1] == i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pre_par {
|
||||||
|
str += '('
|
||||||
|
}
|
||||||
|
str += '${q}${field}${q} ${where.kinds[i].to_str()} ${qm}'
|
||||||
|
if num {
|
||||||
|
str += '${c}'
|
||||||
|
c++
|
||||||
|
}
|
||||||
|
if post_par {
|
||||||
|
str += ')'
|
||||||
|
}
|
||||||
|
if i < where.fields.len - 1 {
|
||||||
|
if where.is_and[i] {
|
||||||
|
str += ' AND '
|
||||||
|
} else {
|
||||||
|
str += ' OR '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
// Generates an sql table stmt, from universal parameter
|
// Generates an sql table stmt, from universal parameter
|
||||||
// table - Table name
|
// table - Table name
|
||||||
// q - see orm_stmt_gen
|
// q - see orm_stmt_gen
|
||||||
|
@ -461,14 +441,14 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mut default_val := field.default_val
|
mut default_val := field.default_val
|
||||||
mut no_null := false
|
mut nullable := field.nullable
|
||||||
mut is_unique := false
|
mut is_unique := false
|
||||||
mut is_skip := false
|
mut is_skip := false
|
||||||
mut unique_len := 0
|
mut unique_len := 0
|
||||||
mut references_table := ''
|
mut references_table := ''
|
||||||
mut references_field := ''
|
mut references_field := ''
|
||||||
mut field_name := sql_field_name(field)
|
mut field_name := sql_field_name(field)
|
||||||
mut ctyp := sql_from_v(sql_field_type(field)) or {
|
mut col_typ := sql_from_v(sql_field_type(field)) or {
|
||||||
field_name = '${field_name}_id'
|
field_name = '${field_name}_id'
|
||||||
sql_from_v(primary_typ)!
|
sql_from_v(primary_typ)!
|
||||||
}
|
}
|
||||||
|
@ -497,24 +477,15 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
|
||||||
}
|
}
|
||||||
is_unique = true
|
is_unique = true
|
||||||
}
|
}
|
||||||
'nonull' {
|
|
||||||
no_null = true
|
|
||||||
}
|
|
||||||
'skip' {
|
'skip' {
|
||||||
is_skip = true
|
is_skip = true
|
||||||
}
|
}
|
||||||
'sql_type' {
|
'sql_type' {
|
||||||
if attr.kind != .string {
|
col_typ = attr.arg.str()
|
||||||
return error("sql_type attribute needs to be string. Try [sql_type: '${attr.arg}'] instead of [sql_type: ${attr.arg}]")
|
|
||||||
}
|
|
||||||
ctyp = attr.arg
|
|
||||||
}
|
}
|
||||||
'default' {
|
'default' {
|
||||||
if attr.kind != .string {
|
|
||||||
return error("default attribute needs to be string. Try [default: '${attr.arg}'] instead of [default: ${attr.arg}]")
|
|
||||||
}
|
|
||||||
if default_val == '' {
|
if default_val == '' {
|
||||||
default_val = attr.arg
|
default_val = attr.arg.str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'references' {
|
'references' {
|
||||||
|
@ -549,19 +520,19 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mut stmt := ''
|
mut stmt := ''
|
||||||
if ctyp == '' {
|
if col_typ == '' {
|
||||||
return error('Unknown type (${field.typ}) for field ${field.name} in struct ${table}')
|
return error('Unknown type (${field.typ}) for field ${field.name} in struct ${table}')
|
||||||
}
|
}
|
||||||
stmt = '${q}${field_name}${q} ${ctyp}'
|
stmt = '${q}${field_name}${q} ${col_typ}'
|
||||||
if defaults && default_val != '' {
|
if defaults && default_val != '' {
|
||||||
stmt += ' DEFAULT ${default_val}'
|
stmt += ' DEFAULT ${default_val}'
|
||||||
}
|
}
|
||||||
if no_null {
|
if !nullable {
|
||||||
stmt += ' NOT NULL'
|
stmt += ' NOT NULL'
|
||||||
}
|
}
|
||||||
if is_unique {
|
if is_unique {
|
||||||
mut f := 'UNIQUE(${q}${field_name}${q}'
|
mut f := 'UNIQUE(${q}${field_name}${q}'
|
||||||
if ctyp == 'TEXT' && def_unique_len > 0 {
|
if col_typ == 'TEXT' && def_unique_len > 0 {
|
||||||
if unique_len > 0 {
|
if unique_len > 0 {
|
||||||
f += '(${unique_len})'
|
f += '(${unique_len})'
|
||||||
} else {
|
} else {
|
||||||
|
@ -607,15 +578,10 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
|
||||||
// Get's the sql field type
|
// Get's the sql field type
|
||||||
fn sql_field_type(field TableField) int {
|
fn sql_field_type(field TableField) int {
|
||||||
mut typ := field.typ
|
mut typ := field.typ
|
||||||
if field.is_time {
|
|
||||||
return -2
|
|
||||||
} else if field.is_enum {
|
|
||||||
return typeof[i64]().idx
|
|
||||||
}
|
|
||||||
for attr in field.attrs {
|
for attr in field.attrs {
|
||||||
if attr.kind == .plain && attr.name == 'sql' && attr.arg != '' {
|
if attr.kind == .plain && attr.name == 'sql' && attr.arg != '' {
|
||||||
if attr.arg.to_lower() == 'serial' {
|
if attr.arg.to_lower() == 'serial' {
|
||||||
typ = -1
|
typ = orm.serial
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
typ = orm.type_idx[attr.arg]
|
typ = orm.type_idx[attr.arg]
|
||||||
|
@ -639,69 +605,129 @@ fn sql_field_name(field TableField) string {
|
||||||
|
|
||||||
// needed for backend functions
|
// needed for backend functions
|
||||||
|
|
||||||
pub fn bool_to_primitive(b bool) Primitive {
|
fn bool_to_primitive(b bool) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn f32_to_primitive(b f32) Primitive {
|
fn option_bool_to_primitive(b ?bool) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f32_to_primitive(b f32) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn f64_to_primitive(b f64) Primitive {
|
fn option_f32_to_primitive(b ?f32) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f64_to_primitive(b f64) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn i8_to_primitive(b i8) Primitive {
|
fn option_f64_to_primitive(b ?f64) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i8_to_primitive(b i8) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn i16_to_primitive(b i16) Primitive {
|
fn option_i8_to_primitive(b ?i8) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i16_to_primitive(b i16) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn int_to_primitive(b int) Primitive {
|
fn option_i16_to_primitive(b ?i16) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int_to_primitive(b int) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn option_int_to_primitive(b ?int) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
// int_literal_to_primitive handles int literal value
|
// int_literal_to_primitive handles int literal value
|
||||||
pub fn int_literal_to_primitive(b int) Primitive {
|
fn int_literal_to_primitive(b int) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn option_int_literal_to_primitive(b ?int) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
// float_literal_to_primitive handles float literal value
|
// float_literal_to_primitive handles float literal value
|
||||||
pub fn float_literal_to_primitive(b f64) Primitive {
|
fn float_literal_to_primitive(b f64) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn i64_to_primitive(b i64) Primitive {
|
fn option_float_literal_to_primitive(b ?f64) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i64_to_primitive(b i64) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn u8_to_primitive(b u8) Primitive {
|
fn option_i64_to_primitive(b ?i64) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u8_to_primitive(b u8) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn u16_to_primitive(b u16) Primitive {
|
fn option_u8_to_primitive(b ?u8) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u16_to_primitive(b u16) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn u32_to_primitive(b u32) Primitive {
|
fn option_u16_to_primitive(b ?u16) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u32_to_primitive(b u32) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn u64_to_primitive(b u64) Primitive {
|
fn option_u32_to_primitive(b ?u32) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u64_to_primitive(b u64) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn string_to_primitive(b string) Primitive {
|
fn option_u64_to_primitive(b ?u64) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_to_primitive(b string) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn time_to_primitive(b time.Time) Primitive {
|
fn option_string_to_primitive(b ?string) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn time_to_primitive(b time.Time) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn infix_to_primitive(b InfixType) Primitive {
|
fn option_time_to_primitive(b ?time.Time) Primitive {
|
||||||
|
return if b_ := b { Primitive(b_) } else { orm.null_primitive }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infix_to_primitive(b InfixType) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,7 @@ fn test_orm_table_gen() {
|
||||||
name: 'id'
|
name: 'id'
|
||||||
typ: typeof[int]().idx
|
typ: typeof[int]().idx
|
||||||
default_val: '10'
|
default_val: '10'
|
||||||
|
nullable: true
|
||||||
attrs: [
|
attrs: [
|
||||||
StructAttribute{
|
StructAttribute{
|
||||||
name: 'primary'
|
name: 'primary'
|
||||||
|
@ -167,10 +168,12 @@ fn test_orm_table_gen() {
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'test'
|
name: 'test'
|
||||||
typ: typeof[string]().idx
|
typ: typeof[string]().idx
|
||||||
|
nullable: true
|
||||||
},
|
},
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'abc'
|
name: 'abc'
|
||||||
typ: typeof[i64]().idx
|
typ: typeof[i64]().idx
|
||||||
|
nullable: true
|
||||||
default_val: '6754'
|
default_val: '6754'
|
||||||
},
|
},
|
||||||
], sql_type_from_v, false) or { panic(err) }
|
], sql_type_from_v, false) or { panic(err) }
|
||||||
|
@ -180,6 +183,7 @@ fn test_orm_table_gen() {
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'id'
|
name: 'id'
|
||||||
typ: typeof[int]().idx
|
typ: typeof[int]().idx
|
||||||
|
nullable: true
|
||||||
default_val: '10'
|
default_val: '10'
|
||||||
attrs: [
|
attrs: [
|
||||||
StructAttribute{
|
StructAttribute{
|
||||||
|
@ -196,10 +200,12 @@ fn test_orm_table_gen() {
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'test'
|
name: 'test'
|
||||||
typ: typeof[string]().idx
|
typ: typeof[string]().idx
|
||||||
|
nullable: true
|
||||||
},
|
},
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'abc'
|
name: 'abc'
|
||||||
typ: typeof[i64]().idx
|
typ: typeof[i64]().idx
|
||||||
|
nullable: true
|
||||||
default_val: '6754'
|
default_val: '6754'
|
||||||
},
|
},
|
||||||
], sql_type_from_v, true) or { panic(err) }
|
], sql_type_from_v, true) or { panic(err) }
|
||||||
|
@ -209,6 +215,7 @@ fn test_orm_table_gen() {
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'id'
|
name: 'id'
|
||||||
typ: typeof[int]().idx
|
typ: typeof[int]().idx
|
||||||
|
nullable: true
|
||||||
default_val: '10'
|
default_val: '10'
|
||||||
attrs: [
|
attrs: [
|
||||||
StructAttribute{
|
StructAttribute{
|
||||||
|
@ -237,12 +244,13 @@ fn test_orm_table_gen() {
|
||||||
default_val: '6754'
|
default_val: '6754'
|
||||||
},
|
},
|
||||||
], sql_type_from_v, false) or { panic(err) }
|
], sql_type_from_v, false) or { panic(err) }
|
||||||
assert unique_query == "CREATE TABLE IF NOT EXISTS 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT, 'abc' INT64 DEFAULT 6754, PRIMARY KEY('id'), UNIQUE('test'));"
|
assert unique_query == "CREATE TABLE IF NOT EXISTS 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT NOT NULL, 'abc' INT64 DEFAULT 6754 NOT NULL, PRIMARY KEY('id'), UNIQUE('test'));"
|
||||||
|
|
||||||
mult_unique_query := orm.orm_table_gen('test_table', "'", true, 0, [
|
mult_unique_query := orm.orm_table_gen('test_table', "'", true, 0, [
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'id'
|
name: 'id'
|
||||||
typ: typeof[int]().idx
|
typ: typeof[int]().idx
|
||||||
|
nullable: true
|
||||||
default_val: '10'
|
default_val: '10'
|
||||||
attrs: [
|
attrs: [
|
||||||
StructAttribute{
|
StructAttribute{
|
||||||
|
@ -259,6 +267,7 @@ fn test_orm_table_gen() {
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'test'
|
name: 'test'
|
||||||
typ: typeof[string]().idx
|
typ: typeof[string]().idx
|
||||||
|
nullable: true
|
||||||
attrs: [
|
attrs: [
|
||||||
StructAttribute{
|
StructAttribute{
|
||||||
name: 'unique'
|
name: 'unique'
|
||||||
|
@ -271,6 +280,7 @@ fn test_orm_table_gen() {
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'abc'
|
name: 'abc'
|
||||||
typ: typeof[i64]().idx
|
typ: typeof[i64]().idx
|
||||||
|
nullable: true
|
||||||
default_val: '6754'
|
default_val: '6754'
|
||||||
attrs: [
|
attrs: [
|
||||||
StructAttribute{
|
StructAttribute{
|
||||||
|
@ -294,7 +304,7 @@ fn sql_type_from_v(typ int) !string {
|
||||||
'DOUBLE'
|
'DOUBLE'
|
||||||
} else if typ == orm.type_string {
|
} else if typ == orm.type_string {
|
||||||
'TEXT'
|
'TEXT'
|
||||||
} else if typ == -1 {
|
} else if typ == orm.serial {
|
||||||
'SERIAL'
|
'SERIAL'
|
||||||
} else {
|
} else {
|
||||||
error('Unknown type ${typ}')
|
error('Unknown type ${typ}')
|
||||||
|
|
|
@ -29,7 +29,13 @@ struct Account {
|
||||||
struct Package {
|
struct Package {
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
name string [unique]
|
name string [unique]
|
||||||
author User [fkey: 'id']
|
author User [fkey: 'id'] // mandatory user
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Delivery {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
name string [unique]
|
||||||
|
author ?User [fkey: 'id'] // optional user
|
||||||
}
|
}
|
||||||
|
|
||||||
struct User {
|
struct User {
|
||||||
|
@ -138,7 +144,7 @@ fn test_float_primary_key() {
|
||||||
assert entities.first() == entity
|
assert entities.first() == entity
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_does_not_insert_uninitialized_field() {
|
fn test_does_not_insert_uninitialized_mandatory_field() {
|
||||||
db := sqlite.connect(':memory:')!
|
db := sqlite.connect(':memory:')!
|
||||||
|
|
||||||
sql db {
|
sql db {
|
||||||
|
@ -151,9 +157,13 @@ fn test_does_not_insert_uninitialized_field() {
|
||||||
// author
|
// author
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mut query_successful := true
|
||||||
|
|
||||||
sql db {
|
sql db {
|
||||||
insert package into Package
|
insert package into Package
|
||||||
}!
|
} or { query_successful = false }
|
||||||
|
|
||||||
|
assert !query_successful
|
||||||
|
|
||||||
users := sql db {
|
users := sql db {
|
||||||
select from User
|
select from User
|
||||||
|
@ -163,7 +173,7 @@ fn test_does_not_insert_uninitialized_field() {
|
||||||
assert users.len == 0
|
assert users.len == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_insert_empty_field() {
|
fn test_insert_empty_mandatory_field() {
|
||||||
db := sqlite.connect(':memory:')!
|
db := sqlite.connect(':memory:')!
|
||||||
|
|
||||||
sql db {
|
sql db {
|
||||||
|
@ -187,6 +197,54 @@ fn test_insert_empty_field() {
|
||||||
assert users.len == 1
|
assert users.len == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_does_insert_uninitialized_optional_field() {
|
||||||
|
db := sqlite.connect(':memory:')!
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table User
|
||||||
|
create table Delivery
|
||||||
|
}!
|
||||||
|
|
||||||
|
package := Delivery{
|
||||||
|
name: 'wow'
|
||||||
|
// author
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
insert package into Delivery
|
||||||
|
}!
|
||||||
|
|
||||||
|
users := sql db {
|
||||||
|
select from User
|
||||||
|
}!
|
||||||
|
|
||||||
|
assert users.len == 0 // no user added
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_insert_empty_optional_field() {
|
||||||
|
db := sqlite.connect(':memory:')!
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table User
|
||||||
|
create table Delivery
|
||||||
|
}!
|
||||||
|
|
||||||
|
package := Delivery{
|
||||||
|
name: 'bob'
|
||||||
|
author: User{}
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
insert package into Delivery
|
||||||
|
}!
|
||||||
|
|
||||||
|
users := sql db {
|
||||||
|
select from User
|
||||||
|
}!
|
||||||
|
|
||||||
|
assert users.len == 1 // user was added
|
||||||
|
}
|
||||||
|
|
||||||
fn test_insert_empty_object() {
|
fn test_insert_empty_object() {
|
||||||
db := sqlite.connect(':memory:')!
|
db := sqlite.connect(':memory:')!
|
||||||
|
|
||||||
|
|
307
vlib/orm/orm_null_test.v
Normal file
307
vlib/orm/orm_null_test.v
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
import orm
|
||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
struct MockDBState {
|
||||||
|
mut:
|
||||||
|
last string
|
||||||
|
data []orm.Primitive
|
||||||
|
where []orm.Primitive
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MockDB {
|
||||||
|
use_num bool
|
||||||
|
st &MockDBState = unsafe { nil }
|
||||||
|
db sqlite.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
fn MockDB.new() &MockDB {
|
||||||
|
return &MockDB{
|
||||||
|
st: &MockDBState{}
|
||||||
|
db: sqlite.connect(':memory:') or { panic(err) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db MockDB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive {
|
||||||
|
mut st := db.st
|
||||||
|
st.last = orm.orm_select_gen(config, '`', false, '?', 5, where)
|
||||||
|
st.data = data.data
|
||||||
|
st.where = where.data
|
||||||
|
return db.db.@select(config, data, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db MockDB) insert(table string, data orm.QueryData) ! {
|
||||||
|
mut st := db.st
|
||||||
|
last, qdata := orm.orm_stmt_gen(.sqlite, table, '`', .insert, false, '?', 1, data,
|
||||||
|
orm.QueryData{})
|
||||||
|
st.last = last
|
||||||
|
st.data = qdata.data
|
||||||
|
st.where = []orm.Primitive{}
|
||||||
|
return db.db.insert(table, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db MockDB) update(table string, data orm.QueryData, where orm.QueryData) ! {
|
||||||
|
mut st := db.st
|
||||||
|
st.last, _ = orm.orm_stmt_gen(.sqlite, table, '`', .update, false, '?', 1, data, where)
|
||||||
|
st.data = data.data
|
||||||
|
st.where = where.data
|
||||||
|
return db.db.update(table, data, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db MockDB) delete(table string, where orm.QueryData) ! {
|
||||||
|
mut st := db.st
|
||||||
|
st.last, _ = orm.orm_stmt_gen(.sqlite, table, '`', .delete, false, '?', 1, orm.QueryData{},
|
||||||
|
where)
|
||||||
|
return db.db.delete(table, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
typ_to_typename = {
|
||||||
|
typeof[i8]().idx: 'i8'
|
||||||
|
typeof[i16]().idx: 'i16'
|
||||||
|
typeof[int]().idx: 'int'
|
||||||
|
typeof[i64]().idx: 'i64'
|
||||||
|
typeof[u8]().idx: 'u8'
|
||||||
|
typeof[u16]().idx: 'u16'
|
||||||
|
typeof[u32]().idx: 'u32'
|
||||||
|
typeof[u64]().idx: 'u64'
|
||||||
|
typeof[f32]().idx: 'f32'
|
||||||
|
typeof[f64]().idx: 'f64'
|
||||||
|
typeof[string]().idx: 'string'
|
||||||
|
typeof[bool]().idx: 'bool'
|
||||||
|
orm.serial: 'serial'
|
||||||
|
orm.time_: 'time'
|
||||||
|
orm.enum_: 'enum'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fn mock_type_from_v(typ int) !string {
|
||||||
|
return if typ in typ_to_typename {
|
||||||
|
'${typ_to_typename[typ]}-type'
|
||||||
|
} else {
|
||||||
|
error('unknown type ${typ}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db MockDB) create(table string, fields []orm.TableField) ! {
|
||||||
|
mut st := db.st
|
||||||
|
st.last = orm.orm_table_gen(table, '`', true, 0, fields, mock_type_from_v, false)!
|
||||||
|
return db.db.create(table, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db MockDB) drop(table string) ! {
|
||||||
|
return db.db.drop(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (db MockDB) last_id() int {
|
||||||
|
return db.db.last_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --
|
||||||
|
|
||||||
|
[table: 'foo']
|
||||||
|
struct Foo {
|
||||||
|
mut:
|
||||||
|
id u64 [primary; sql: serial]
|
||||||
|
a string
|
||||||
|
// b string [default: '"yes"']
|
||||||
|
c ?string
|
||||||
|
d ?string = 'hi'
|
||||||
|
e int
|
||||||
|
// f int [default: 33]
|
||||||
|
g ?int
|
||||||
|
h ?int = 55
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_option_struct_fields_and_none() {
|
||||||
|
db := MockDB.new()
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table Foo
|
||||||
|
}!
|
||||||
|
assert db.st.last == 'CREATE TABLE IF NOT EXISTS `foo` (`id` serial-type NOT NULL, `a` string-type NOT NULL, `c` string-type, `d` string-type, `e` int-type NOT NULL, `g` int-type, `h` int-type, PRIMARY KEY(`id`));'
|
||||||
|
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where e > 5 && c is none && c !is none && h == 2
|
||||||
|
}!
|
||||||
|
assert db.st.last == 'SELECT `id`, `a`, `c`, `d`, `e`, `g`, `h` FROM `foo` WHERE `e` > ? AND `c` IS ? AND `c` IS NOT ? AND `h` = ?;'
|
||||||
|
assert db.st.data.len == 0
|
||||||
|
assert db.st.where.len == 4
|
||||||
|
assert db.st.where == [orm.Primitive(int(5)), orm.Null{},
|
||||||
|
orm.Null{}, orm.Primitive(int(2))]
|
||||||
|
|
||||||
|
foo := Foo{}
|
||||||
|
sql db {
|
||||||
|
insert foo into Foo
|
||||||
|
}!
|
||||||
|
assert db.st.last == 'INSERT INTO `foo` (`a`, `c`, `d`, `e`, `g`, `h`) VALUES (?, ?, ?, ?, ?, ?);'
|
||||||
|
assert db.st.data.len == 6
|
||||||
|
assert db.st.data == [orm.Primitive(string('')), orm.Null{}, orm.Primitive(string('hi')), int(0),
|
||||||
|
orm.Null{}, int(55)]
|
||||||
|
id := db.last_id()
|
||||||
|
|
||||||
|
res1 := sql db {
|
||||||
|
select from Foo where id == id
|
||||||
|
}!
|
||||||
|
assert db.st.last == 'SELECT `id`, `a`, `c`, `d`, `e`, `g`, `h` FROM `foo` WHERE `id` = ?;'
|
||||||
|
assert db.st.data.len == 0
|
||||||
|
assert db.st.where.len == 1
|
||||||
|
assert db.st.where == [orm.Primitive(int(id))]
|
||||||
|
assert res1.len == 1
|
||||||
|
assert res1[0] == Foo{
|
||||||
|
id: 1
|
||||||
|
a: ''
|
||||||
|
c: none
|
||||||
|
d: 'hi'
|
||||||
|
e: 0
|
||||||
|
g: none
|
||||||
|
h: 55
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
update Foo set c = 'yo', d = none, g = 44, h = none where id == id
|
||||||
|
}!
|
||||||
|
assert db.st.last == 'UPDATE `foo` SET `c` = ?, `d` = ?, `g` = ?, `h` = ? WHERE `id` = ?;'
|
||||||
|
assert db.st.data.len == 4
|
||||||
|
assert db.st.data == [orm.Primitive(string('yo')), orm.Null{}, int(44), orm.Null{}]
|
||||||
|
assert db.st.where.len == 1
|
||||||
|
assert db.st.where == [orm.Primitive(int(id))]
|
||||||
|
|
||||||
|
res2 := sql db {
|
||||||
|
select from Foo where id == id
|
||||||
|
}!
|
||||||
|
assert db.st.last == 'SELECT `id`, `a`, `c`, `d`, `e`, `g`, `h` FROM `foo` WHERE `id` = ?;'
|
||||||
|
assert db.st.data.len == 0
|
||||||
|
assert db.st.where.len == 1
|
||||||
|
assert db.st.where == [orm.Primitive(int(id))]
|
||||||
|
assert res2.len == 1
|
||||||
|
assert res2[0] == Foo{
|
||||||
|
id: 1
|
||||||
|
a: ''
|
||||||
|
c: 'yo'
|
||||||
|
d: none
|
||||||
|
e: 0
|
||||||
|
g: 44
|
||||||
|
h: none
|
||||||
|
}
|
||||||
|
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where a == 'yo'
|
||||||
|
}! == 0
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where d == 'yo'
|
||||||
|
}! == 0
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where c == 'yo'
|
||||||
|
}! == 1
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where a == ''
|
||||||
|
}! == 1
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where d == ''
|
||||||
|
}! == 0
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where c == ''
|
||||||
|
}! == 0
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where a is none
|
||||||
|
}! == 0
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where d is none
|
||||||
|
}! == 1
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where c is none
|
||||||
|
}! == 0
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where a !is none
|
||||||
|
}! == 1
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where d !is none
|
||||||
|
}! == 0
|
||||||
|
assert sql db {
|
||||||
|
select count from Foo where c !is none
|
||||||
|
}! == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Bar {
|
||||||
|
id u64 [primary; sql: serial]
|
||||||
|
name ?string
|
||||||
|
age int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_bar1(db MockDB, id u64, name ?string) ! {
|
||||||
|
foo := 66
|
||||||
|
sql db {
|
||||||
|
update Bar set name = name, age = age + 3 + foo where id == id
|
||||||
|
}!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_bar2(db MockDB, name ?string, new_name ?string) ! {
|
||||||
|
sql db {
|
||||||
|
update Bar set name = new_name where name == name
|
||||||
|
}!
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameFn = fn () ?string
|
||||||
|
|
||||||
|
fn update_bar3(db MockDB, name_fn NameFn, new_name string) ! {
|
||||||
|
sql db {
|
||||||
|
update Bar set name = new_name where name == name_fn()
|
||||||
|
}!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_inserting_passed_optionals() {
|
||||||
|
db := MockDB.new()
|
||||||
|
|
||||||
|
entry1 := Bar{}
|
||||||
|
entry2 := Bar{
|
||||||
|
name: 'Alice'
|
||||||
|
age: 55
|
||||||
|
}
|
||||||
|
entry3 := Bar{
|
||||||
|
name: 'Bob'
|
||||||
|
age: 66
|
||||||
|
}
|
||||||
|
sql db {
|
||||||
|
create table Bar
|
||||||
|
insert entry1 into Bar
|
||||||
|
insert entry2 into Bar
|
||||||
|
insert entry3 into Bar
|
||||||
|
}!
|
||||||
|
|
||||||
|
update_bar1(db, 2, none)!
|
||||||
|
update_bar1(db, 1, 'hi')!
|
||||||
|
|
||||||
|
res1 := sql db {
|
||||||
|
select from Bar
|
||||||
|
}!
|
||||||
|
assert res1.len == 3
|
||||||
|
assert res1[0].name or { '' } == 'hi'
|
||||||
|
assert res1[1].name == none
|
||||||
|
assert res1[2].name or { '' } == 'Bob'
|
||||||
|
|
||||||
|
update_bar2(db, none, 'xxx')! // no effect (select using "is none", not "== none")
|
||||||
|
update_bar2(db, 'hi', none)!
|
||||||
|
|
||||||
|
res2 := sql db {
|
||||||
|
select from Bar
|
||||||
|
}!
|
||||||
|
assert res2.len == 3
|
||||||
|
assert res2[0].name == none
|
||||||
|
assert res2[1].name == none
|
||||||
|
assert res2[2].name or { '' } == 'Bob'
|
||||||
|
|
||||||
|
update_bar3(db, fn () ?string {
|
||||||
|
return none // no effect (select using "is none", not "== none")
|
||||||
|
}, 'yyy')!
|
||||||
|
update_bar3(db, fn () ?string {
|
||||||
|
return 'Bob'
|
||||||
|
}, 'www')!
|
||||||
|
|
||||||
|
res3 := sql db {
|
||||||
|
select from Bar
|
||||||
|
}!
|
||||||
|
assert res3.len == 3
|
||||||
|
assert res3[0].name == none
|
||||||
|
assert res3[1].name == none
|
||||||
|
assert res3[2].name or { '' } == 'www'
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ struct Module {
|
||||||
name string
|
name string
|
||||||
nr_downloads int
|
nr_downloads int
|
||||||
test_id u64
|
test_id u64
|
||||||
user User
|
user ?User
|
||||||
created time.Time
|
created time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1919,7 +1919,7 @@ pub:
|
||||||
is_generated bool
|
is_generated bool
|
||||||
scope &Scope = unsafe { nil }
|
scope &Scope = unsafe { nil }
|
||||||
pub mut:
|
pub mut:
|
||||||
object_var_name string // `user`
|
object_var string // `user`
|
||||||
updated_columns []string // for `update set x=y`
|
updated_columns []string // for `update set x=y`
|
||||||
table_expr TypeNode
|
table_expr TypeNode
|
||||||
fields []StructField
|
fields []StructField
|
||||||
|
|
|
@ -133,7 +133,8 @@ mut:
|
||||||
goto_labels map[string]ast.GotoLabel // to check for unused goto labels
|
goto_labels map[string]ast.GotoLabel // to check for unused goto labels
|
||||||
enum_data_type ast.Type
|
enum_data_type ast.Type
|
||||||
fn_return_type ast.Type
|
fn_return_type ast.Type
|
||||||
|
orm_table_fields map[string][]ast.StructField // known table structs
|
||||||
|
//
|
||||||
v_current_commit_hash string // same as V_CURRENT_COMMIT_HASH
|
v_current_commit_hash string // same as V_CURRENT_COMMIT_HASH
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type {
|
||||||
c.expected_type = left_type
|
c.expected_type = left_type
|
||||||
|
|
||||||
// `if n is ast.Ident && n.is_mut { ... }`
|
// `if n is ast.Ident && n.is_mut { ... }`
|
||||||
if node.op == .and {
|
if !c.inside_sql && node.op == .and {
|
||||||
mut left_node := node.left
|
mut left_node := node.left
|
||||||
for mut left_node is ast.InfixExpr {
|
for mut left_node is ast.InfixExpr {
|
||||||
if left_node.op == .and && mut left_node.right is ast.InfixExpr {
|
if left_node.op == .and && mut left_node.right is ast.InfixExpr {
|
||||||
|
@ -654,9 +654,13 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type {
|
||||||
parent_left_type := left_sym.info.sum_type
|
parent_left_type := left_sym.info.sum_type
|
||||||
left_sym = c.table.sym(parent_left_type)
|
left_sym = c.table.sym(parent_left_type)
|
||||||
}
|
}
|
||||||
if left_sym.kind !in [.interface_, .sum_type] {
|
if c.inside_sql {
|
||||||
|
if typ != ast.none_type_idx {
|
||||||
|
c.error('`${op}` can only be used to test for none in sql', node.pos)
|
||||||
|
}
|
||||||
|
} else if left_sym.kind !in [.interface_, .sum_type] {
|
||||||
c.error('`${op}` can only be used with interfaces and sum types',
|
c.error('`${op}` can only be used with interfaces and sum types',
|
||||||
node.pos)
|
node.pos) // can be used in sql too, but keep err simple
|
||||||
} else if mut left_sym.info is ast.SumType {
|
} else if mut left_sym.info is ast.SumType {
|
||||||
if typ !in left_sym.info.variants {
|
if typ !in left_sym.info.variants {
|
||||||
c.error('`${left_sym.name}` has no variant `${right_sym.name}`',
|
c.error('`${left_sym.name}` has no variant `${right_sym.name}`',
|
||||||
|
@ -749,7 +753,8 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type {
|
||||||
if left_is_option || right_is_option {
|
if left_is_option || right_is_option {
|
||||||
opt_infix_pos := if left_is_option { left_pos } else { right_pos }
|
opt_infix_pos := if left_is_option { left_pos } else { right_pos }
|
||||||
if (node.left !in [ast.Ident, ast.SelectorExpr, ast.ComptimeSelector]
|
if (node.left !in [ast.Ident, ast.SelectorExpr, ast.ComptimeSelector]
|
||||||
|| node.op in [.eq, .ne, .lt, .gt, .le, .ge]) && right_sym.kind != .none_ {
|
|| node.op in [.eq, .ne, .lt, .gt, .le, .ge]) && right_sym.kind != .none_
|
||||||
|
&& !c.inside_sql {
|
||||||
c.error('unwrapped Option cannot be used in an infix expression', opt_infix_pos)
|
c.error('unwrapped Option cannot be used in an infix expression', opt_infix_pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,6 @@ import v.ast
|
||||||
import v.token
|
import v.token
|
||||||
import v.util
|
import v.util
|
||||||
|
|
||||||
const (
|
|
||||||
v_orm_prefix = 'V ORM'
|
|
||||||
fkey_attr_name = 'fkey'
|
|
||||||
pkey_attr_name = 'primary'
|
|
||||||
connection_interface_name = 'orm.Connection'
|
|
||||||
)
|
|
||||||
|
|
||||||
type ORMExpr = ast.SqlExpr | ast.SqlStmt
|
type ORMExpr = ast.SqlExpr | ast.SqlStmt
|
||||||
|
|
||||||
fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
|
fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
|
||||||
|
@ -43,44 +36,43 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
info := table_sym.info as ast.Struct
|
info := table_sym.info as ast.Struct
|
||||||
mut fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name,
|
mut fields := c.fetch_and_check_orm_fields(info, node.table_expr.pos, table_sym.name,
|
||||||
node)
|
node)
|
||||||
non_primitive_fields := c.get_orm_non_primitive_fields(fields)
|
non_primitive_fields := c.get_orm_non_primitive_fields(fields)
|
||||||
mut sub_structs := map[int]ast.SqlExpr{}
|
mut sub_structs := map[int]ast.SqlExpr{}
|
||||||
|
|
||||||
mut has_pkey_attr := false
|
mut has_primary := false
|
||||||
mut pkey_field := ast.StructField{}
|
mut primary_field := ast.StructField{}
|
||||||
|
|
||||||
for field in fields {
|
for field in fields {
|
||||||
for attr in field.attrs {
|
if field.attrs.contains('primary') {
|
||||||
if attr.name == checker.pkey_attr_name {
|
if has_primary {
|
||||||
if has_pkey_attr {
|
|
||||||
c.orm_error('a struct can only have one primary key', field.pos)
|
c.orm_error('a struct can only have one primary key', field.pos)
|
||||||
}
|
}
|
||||||
has_pkey_attr = true
|
has_primary = true
|
||||||
pkey_field = field
|
primary_field = field
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for field in non_primitive_fields {
|
for field in non_primitive_fields {
|
||||||
if c.table.sym(field.typ).kind == .array && !has_pkey_attr {
|
if c.table.sym(field.typ).kind == .array && !has_primary {
|
||||||
c.orm_error('a struct that has a field that holds an array must have a primary key',
|
c.orm_error('a struct that has a field that holds an array must have a primary key',
|
||||||
field.pos)
|
field.pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.check_orm_struct_field_attributes(field)
|
c.check_orm_non_primitive_struct_field_attrs(field)
|
||||||
|
|
||||||
typ := c.get_type_of_field_with_related_table(field)
|
foreign_typ := c.get_field_foreign_table_type(field)
|
||||||
|
|
||||||
mut subquery_expr := ast.SqlExpr{
|
mut subquery_expr := ast.SqlExpr{
|
||||||
pos: node.pos
|
pos: node.pos
|
||||||
has_where: true
|
has_where: true
|
||||||
where_expr: ast.None{}
|
where_expr: ast.None{}
|
||||||
typ: typ.set_flag(.result)
|
typ: field.typ.clear_flag(.option).set_flag(.result)
|
||||||
db_expr: node.db_expr
|
db_expr: node.db_expr
|
||||||
table_expr: ast.TypeNode{
|
table_expr: ast.TypeNode{
|
||||||
pos: node.table_expr.pos
|
pos: node.table_expr.pos
|
||||||
typ: typ
|
typ: foreign_typ
|
||||||
}
|
}
|
||||||
is_generated: true
|
is_generated: true
|
||||||
}
|
}
|
||||||
|
@ -123,25 +115,25 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
|
||||||
if c.table.sym(field.typ).kind == .array {
|
if c.table.sym(field.typ).kind == .array {
|
||||||
mut where_expr := subquery_expr.where_expr
|
mut where_expr := subquery_expr.where_expr
|
||||||
if mut where_expr is ast.InfixExpr {
|
if mut where_expr is ast.InfixExpr {
|
||||||
where_expr.left_type = pkey_field.typ
|
where_expr.left_type = primary_field.typ
|
||||||
where_expr.right_type = pkey_field.typ
|
where_expr.right_type = primary_field.typ
|
||||||
|
|
||||||
mut left := where_expr.left
|
mut left := where_expr.left
|
||||||
if mut left is ast.Ident {
|
if mut left is ast.Ident {
|
||||||
left.name = pkey_field.name
|
left.name = primary_field.name
|
||||||
}
|
}
|
||||||
|
|
||||||
mut right := where_expr.right
|
mut right := where_expr.right
|
||||||
if mut right is ast.Ident {
|
if mut right is ast.Ident {
|
||||||
mut right_info := right.info
|
mut right_info := right.info
|
||||||
if mut right_info is ast.IdentVar {
|
if mut right_info is ast.IdentVar {
|
||||||
right_info.typ = pkey_field.typ
|
right_info.typ = primary_field.typ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub_structs[int(typ)] = subquery_expr
|
sub_structs[int(field.typ)] = subquery_expr
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.is_count {
|
if node.is_count {
|
||||||
|
@ -235,7 +227,7 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
|
||||||
c.cur_orm_ts = old_ts
|
c.cur_orm_ts = old_ts
|
||||||
}
|
}
|
||||||
|
|
||||||
inserting_object_name := node.object_var_name
|
inserting_object_name := node.object_var
|
||||||
|
|
||||||
if node.kind == .insert && !node.is_generated {
|
if node.kind == .insert && !node.is_generated {
|
||||||
inserting_object := node.scope.find(inserting_object_name) or {
|
inserting_object := node.scope.find(inserting_object_name) or {
|
||||||
|
@ -263,8 +255,13 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
info := table_sym.info as ast.Struct
|
info := table_sym.info as ast.Struct
|
||||||
mut fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name,
|
mut fields := c.fetch_and_check_orm_fields(info, node.table_expr.pos, table_sym.name,
|
||||||
ast.SqlExpr{})
|
ast.SqlExpr{})
|
||||||
|
|
||||||
|
for field in fields {
|
||||||
|
c.check_orm_struct_field_attrs(node, field)
|
||||||
|
}
|
||||||
|
|
||||||
mut sub_structs := map[int]ast.SqlStmtLine{}
|
mut sub_structs := map[int]ast.SqlStmtLine{}
|
||||||
non_primitive_fields := c.get_orm_non_primitive_fields(fields)
|
non_primitive_fields := c.get_orm_non_primitive_fields(fields)
|
||||||
|
|
||||||
|
@ -276,30 +273,25 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.check_orm_struct_field_attributes(field)
|
c.check_orm_non_primitive_struct_field_attrs(field)
|
||||||
|
|
||||||
typ := c.get_type_of_field_with_related_table(field)
|
foreign_typ := c.get_field_foreign_table_type(field)
|
||||||
|
|
||||||
mut object_var_name := '${node.object_var_name}.${field.name}'
|
|
||||||
if typ != field.typ {
|
|
||||||
object_var_name = node.object_var_name
|
|
||||||
}
|
|
||||||
|
|
||||||
mut subquery_expr := ast.SqlStmtLine{
|
mut subquery_expr := ast.SqlStmtLine{
|
||||||
pos: node.pos
|
pos: node.pos
|
||||||
kind: node.kind
|
kind: node.kind
|
||||||
table_expr: ast.TypeNode{
|
table_expr: ast.TypeNode{
|
||||||
pos: node.table_expr.pos
|
pos: node.table_expr.pos
|
||||||
typ: typ
|
typ: foreign_typ
|
||||||
}
|
}
|
||||||
object_var_name: object_var_name
|
object_var: field.name
|
||||||
is_generated: true
|
is_generated: true
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp_inside_sql := c.inside_sql
|
tmp_inside_sql := c.inside_sql
|
||||||
c.sql_stmt_line(mut subquery_expr)
|
c.sql_stmt_line(mut subquery_expr)
|
||||||
c.inside_sql = tmp_inside_sql
|
c.inside_sql = tmp_inside_sql
|
||||||
sub_structs[typ] = subquery_expr
|
sub_structs[field.typ] = subquery_expr
|
||||||
}
|
}
|
||||||
|
|
||||||
node.fields = fields
|
node.fields = fields
|
||||||
|
@ -336,27 +328,29 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
|
||||||
return ast.void_type
|
return ast.void_type
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Checker) check_orm_struct_field_attributes(field ast.StructField) {
|
fn (mut c Checker) check_orm_struct_field_attrs(node ast.SqlStmtLine, field ast.StructField) {
|
||||||
|
for attr in field.attrs {
|
||||||
|
if attr.name == 'nonull' {
|
||||||
|
c.warn('`nonull` attribute is deprecated; non-optional fields are always "NOT NULL", use Option fields where they can be NULL',
|
||||||
|
node.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) check_orm_non_primitive_struct_field_attrs(field ast.StructField) {
|
||||||
field_type := c.table.sym(field.typ)
|
field_type := c.table.sym(field.typ)
|
||||||
mut has_fkey_attr := false
|
mut has_fkey_attr := false
|
||||||
|
|
||||||
for attr in field.attrs {
|
for attr in field.attrs {
|
||||||
if attr.name == checker.fkey_attr_name {
|
if attr.name == 'fkey' {
|
||||||
if field_type.kind != .array && field_type.kind != .struct_ {
|
if field_type.kind != .array && field_type.kind != .struct_ {
|
||||||
c.orm_error('the `${checker.fkey_attr_name}` attribute must be used only with arrays and structures',
|
c.orm_error('the `fkey` attribute must be used only with arrays and structures',
|
||||||
attr.pos)
|
attr.pos)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !attr.has_arg {
|
if !attr.has_arg {
|
||||||
c.orm_error('the `${checker.fkey_attr_name}` attribute must have an argument',
|
c.orm_error('the `fkey` attribute must have an argument', attr.pos)
|
||||||
attr.pos)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if attr.kind != .string {
|
|
||||||
c.orm_error("`${checker.fkey_attr_name}` attribute must be string. Try [${checker.fkey_attr_name}: '${attr.arg}'] instead of [${checker.fkey_attr_name}: ${attr.arg}]",
|
|
||||||
attr.pos)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,44 +371,60 @@ fn (mut c Checker) check_orm_struct_field_attributes(field ast.StructField) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if field_type.kind == .array && !has_fkey_attr {
|
if field_type.kind == .array && !has_fkey_attr {
|
||||||
c.orm_error('a field that holds an array must be defined with the `${checker.fkey_attr_name}` attribute',
|
c.orm_error('a field that holds an array must be defined with the `fkey` attribute',
|
||||||
field.pos)
|
field.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Checker) fetch_and_verify_orm_fields(info ast.Struct, pos token.Pos, table_name string, sql_expr ast.SqlExpr) []ast.StructField {
|
fn (mut c Checker) fetch_and_check_orm_fields(info ast.Struct, pos token.Pos, table_name string, sql_expr ast.SqlExpr) []ast.StructField {
|
||||||
field_pos := c.orm_get_field_pos(sql_expr.where_expr)
|
if cache := c.orm_table_fields[table_name] {
|
||||||
|
return cache
|
||||||
|
}
|
||||||
mut fields := []ast.StructField{}
|
mut fields := []ast.StructField{}
|
||||||
for field in info.fields {
|
for field in info.fields {
|
||||||
is_primitive := field.typ.is_string() || field.typ.is_bool() || field.typ.is_number()
|
if field.attrs.contains('skip') || field.attrs.contains_arg('sql', '-') {
|
||||||
fsym := c.table.sym(field.typ)
|
|
||||||
is_struct := fsym.kind == .struct_
|
|
||||||
is_array := fsym.kind == .array
|
|
||||||
is_enum := fsym.kind == .enum_
|
|
||||||
elem_sym := if is_array {
|
|
||||||
c.table.sym(fsym.array_info().elem_type)
|
|
||||||
} else {
|
|
||||||
ast.invalid_type_symbol
|
|
||||||
}
|
|
||||||
is_array_with_struct_elements := is_array && elem_sym.kind == .struct_
|
|
||||||
has_skip_attr := field.attrs.contains('skip') || field.attrs.contains_arg('sql', '-')
|
|
||||||
if has_skip_attr {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if is_primitive || is_struct || is_enum || is_array_with_struct_elements {
|
field_sym := c.table.sym(field.typ)
|
||||||
fields << field
|
is_primitive := field.typ.is_string() || field.typ.is_bool() || field.typ.is_number()
|
||||||
|
is_struct := field_sym.kind == .struct_
|
||||||
|
is_array := field_sym.kind == .array
|
||||||
|
is_enum := field_sym.kind == .enum_
|
||||||
|
mut is_array_of_structs := false
|
||||||
|
if is_array {
|
||||||
|
array_info := field_sym.array_info()
|
||||||
|
elem_sym := c.table.sym(array_info.elem_type)
|
||||||
|
is_array_of_structs = elem_sym.kind == .struct_
|
||||||
|
|
||||||
|
if attr := field.attrs.find_first('fkey') {
|
||||||
|
if attr.arg == '' {
|
||||||
|
c.orm_error('fkey attribute must have an argument', attr.pos)
|
||||||
}
|
}
|
||||||
if is_array && elem_sym.is_primitive() {
|
} else {
|
||||||
c.add_error_detail('')
|
c.orm_error('array fields must have an fkey attribute', field.pos)
|
||||||
c.add_error_detail(' field name: `${field.name}`')
|
}
|
||||||
c.add_error_detail(' data type: `${c.table.type_to_str(field.typ)}`')
|
if array_info.nr_dims > 1 || elem_sym.kind == .array {
|
||||||
c.orm_error('does not support array of primitive types', field_pos)
|
c.orm_error('multi-dimension array fields are not supported', field.pos)
|
||||||
return []ast.StructField{}
|
}
|
||||||
|
}
|
||||||
|
if attr := field.attrs.find_first('sql') {
|
||||||
|
if attr.arg == '' {
|
||||||
|
c.orm_error('sql attribute must have an argument', attr.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_primitive || is_struct || is_enum || is_array_of_structs {
|
||||||
|
fields << field
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fields.len == 0 {
|
if fields.len == 0 {
|
||||||
c.orm_error('select: empty fields in `${table_name}`', pos)
|
c.orm_error('select: empty fields in `${table_name}`', pos)
|
||||||
}
|
}
|
||||||
|
if attr := info.attrs.find_first('table') {
|
||||||
|
if attr.arg == '' {
|
||||||
|
c.orm_error('table attribute must have an argument', attr.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.orm_table_fields[table_name] = fields
|
||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,7 +475,7 @@ fn (mut c Checker) check_sql_expr_type_is_int(expr &ast.Expr, sql_keyword string
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Checker) orm_error(message string, pos token.Pos) {
|
fn (mut c Checker) orm_error(message string, pos token.Pos) {
|
||||||
c.error('${checker.v_orm_prefix}: ${message}', pos)
|
c.error('ORM: ${message}', pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check_expr_has_no_fn_calls_with_non_orm_return_type checks that an expression has no function calls
|
// check_expr_has_no_fn_calls_with_non_orm_return_type checks that an expression has no function calls
|
||||||
|
@ -499,6 +509,13 @@ fn (mut c Checker) check_expr_has_no_fn_calls_with_non_orm_return_type(expr &ast
|
||||||
} else if expr is ast.InfixExpr {
|
} else if expr is ast.InfixExpr {
|
||||||
c.check_expr_has_no_fn_calls_with_non_orm_return_type(expr.left)
|
c.check_expr_has_no_fn_calls_with_non_orm_return_type(expr.left)
|
||||||
c.check_expr_has_no_fn_calls_with_non_orm_return_type(expr.right)
|
c.check_expr_has_no_fn_calls_with_non_orm_return_type(expr.right)
|
||||||
|
if expr.right_type.has_flag(.option) && expr.op !in [.key_is, .not_is] {
|
||||||
|
c.warn('comparison with Option value probably isn\'t intended; use "is none" and "!is none" to select by NULL',
|
||||||
|
expr.pos)
|
||||||
|
} else if expr.right_type == ast.none_type && expr.op !in [.key_is, .not_is] {
|
||||||
|
c.warn('comparison with none probably isn\'t intended; use "is none" and "!is none" to select by NULL',
|
||||||
|
expr.pos)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,10 +589,10 @@ fn (mut c Checker) check_orm_or_expr(mut expr ORMExpr) {
|
||||||
|
|
||||||
if expr.or_expr.kind == .absent {
|
if expr.or_expr.kind == .absent {
|
||||||
if c.inside_defer {
|
if c.inside_defer {
|
||||||
c.error('V ORM returns a result, so it should have an `or {}` block at the end',
|
c.error('ORM returns a result, so it should have an `or {}` block at the end',
|
||||||
expr.pos)
|
expr.pos)
|
||||||
} else {
|
} else {
|
||||||
c.error('V ORM returns a result, so it should have either an `or {}` block, or `!` at the end',
|
c.error('ORM returns a result, so it should have either an `or {}` block, or `!` at the end',
|
||||||
expr.pos)
|
expr.pos)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -595,15 +612,14 @@ fn (mut c Checker) check_orm_or_expr(mut expr ORMExpr) {
|
||||||
|
|
||||||
// check_db_expr checks the `db_expr` implements `orm.Connection` and has no `option` flag.
|
// check_db_expr checks the `db_expr` implements `orm.Connection` and has no `option` flag.
|
||||||
fn (mut c Checker) check_db_expr(mut db_expr ast.Expr) bool {
|
fn (mut c Checker) check_db_expr(mut db_expr ast.Expr) bool {
|
||||||
connection_type_index := c.table.find_type_idx(checker.connection_interface_name)
|
connection_type_index := c.table.find_type_idx('orm.Connection')
|
||||||
connection_typ := ast.Type(connection_type_index)
|
connection_typ := ast.Type(connection_type_index)
|
||||||
db_expr_type := c.expr(mut db_expr)
|
db_expr_type := c.expr(mut db_expr)
|
||||||
|
|
||||||
// If we didn't find `orm.Connection`, we don't have any imported modules
|
// If we didn't find `orm.Connection`, we don't have any imported modules
|
||||||
// that depend on `orm` and implement the `orm.Connection` interface.
|
// that depend on `orm` and implement the `orm.Connection` interface.
|
||||||
if connection_type_index == 0 {
|
if connection_type_index == 0 {
|
||||||
c.error('expected a type that implements the `${checker.connection_interface_name}` interface',
|
c.error('expected a type that implements the `orm.Connection` interface', db_expr.pos())
|
||||||
db_expr.pos())
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,10 +646,10 @@ fn (mut c Checker) check_orm_table_expr_type(type_node &ast.TypeNode) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// get_type_of_field_with_related_table gets the type of table in which
|
// get_field_foreign_table_type gets the type of table in which the primary key
|
||||||
// the primary key is used as the foreign key in the current table.
|
// is referred to by the provided field. For example, the `[]Child` field
|
||||||
// For example, if you are using `[]Child`, the related table type would be `Child`.
|
// refers to the foreign table `Child`.
|
||||||
fn (c &Checker) get_type_of_field_with_related_table(table_field &ast.StructField) ast.Type {
|
fn (c &Checker) get_field_foreign_table_type(table_field &ast.StructField) ast.Type {
|
||||||
if c.table.sym(table_field.typ).kind == .struct_ {
|
if c.table.sym(table_field.typ).kind == .struct_ {
|
||||||
return table_field.typ
|
return table_field.typ
|
||||||
} else if c.table.sym(table_field.typ).kind == .array {
|
} else if c.table.sym(table_field.typ).kind == .array {
|
||||||
|
@ -673,7 +689,7 @@ fn (c &Checker) get_orm_non_primitive_fields(fields []ast.StructField) []ast.Str
|
||||||
// ```
|
// ```
|
||||||
// TODO: rewrite it, move to runtime.
|
// TODO: rewrite it, move to runtime.
|
||||||
fn (_ &Checker) check_field_of_inserting_struct_is_uninitialized(node &ast.SqlStmtLine, field_name string) bool {
|
fn (_ &Checker) check_field_of_inserting_struct_is_uninitialized(node &ast.SqlStmtLine, field_name string) bool {
|
||||||
struct_scope := node.scope.find_var(node.object_var_name) or { return false }
|
struct_scope := node.scope.find_var(node.object_var) or { return false }
|
||||||
|
|
||||||
if struct_scope.expr is ast.StructInit {
|
if struct_scope.expr is ast.StructInit {
|
||||||
return struct_scope.expr.init_fields.filter(it.name == field_name).len == 0
|
return struct_scope.expr.init_fields.filter(it.name == field_name).len == 0
|
||||||
|
|
|
@ -19,7 +19,7 @@ vlib/v/checker/tests/like_operator_with_non_string_type_error.vv:16:34: error: t
|
||||||
| ~~~~
|
| ~~~~
|
||||||
17 | }!)
|
17 | }!)
|
||||||
18 | }
|
18 | }
|
||||||
vlib/v/checker/tests/like_operator_with_non_string_type_error.vv:16:26: error: V ORM: left side of the `like` expression must be one of the `User`'s fields
|
vlib/v/checker/tests/like_operator_with_non_string_type_error.vv:16:26: error: ORM: left side of the `like` expression must be one of the `User`'s fields
|
||||||
14 |
|
14 |
|
||||||
15 | println(sql db {
|
15 | println(sql db {
|
||||||
16 | select from User where 10 like true
|
16 | select from User where 10 like true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/orm_empty_struct.vv:9:15: error: V ORM: select: empty fields in `Person`
|
vlib/v/checker/tests/orm_empty_struct.vv:9:15: error: ORM: select: empty fields in `Person`
|
||||||
7 | db := sqlite.connect(':memory:')!
|
7 | db := sqlite.connect(':memory:')!
|
||||||
8 | _ := sql db {
|
8 | _ := sql db {
|
||||||
9 | select from Person
|
9 | select from Person
|
||||||
|
|
7
vlib/v/checker/tests/orm_fkey_attribute.out
Normal file
7
vlib/v/checker/tests/orm_fkey_attribute.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/orm_fkey_attribute.vv:11:14: error: ORM: the `fkey` attribute must have an argument
|
||||||
|
9 | id int [primary; sql: serial]
|
||||||
|
10 | name string
|
||||||
|
11 | user User [fkey]
|
||||||
|
| ~~~~~~
|
||||||
|
12 | }
|
||||||
|
13 |
|
19
vlib/v/checker/tests/orm_fkey_attribute.vv
Normal file
19
vlib/v/checker/tests/orm_fkey_attribute.vv
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
struct User {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
}
|
||||||
|
|
||||||
|
[table: foo]
|
||||||
|
struct Foo {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
name string
|
||||||
|
user User [fkey]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
db := sqlite.connect(':memory:')!
|
||||||
|
sql db {
|
||||||
|
create table Foo
|
||||||
|
}!
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/orm_fkey_has_pkey.vv:5:2: error: V ORM: a struct that has a field that holds an array must have a primary key
|
vlib/v/checker/tests/orm_fkey_has_pkey.vv:5:2: error: ORM: a struct that has a field that holds an array must have a primary key
|
||||||
3 | struct Person {
|
3 | struct Person {
|
||||||
4 | id int
|
4 | id int
|
||||||
5 | child []Child [fkey: 'person_id']
|
5 | child []Child [fkey: 'person_id']
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/orm_fn_call_with_wrong_return_type.vv:52:37: error: V ORM: function calls must return only primitive types and time.Time, but `get_second_child` returns `Child`
|
vlib/v/checker/tests/orm_fn_call_with_wrong_return_type.vv:52:37: error: ORM: function calls must return only primitive types and time.Time, but `get_second_child` returns `Child`
|
||||||
50 |
|
50 |
|
||||||
51 | steve := sql db {
|
51 | steve := sql db {
|
||||||
52 | select from Parent where child == get_second_child()
|
52 | select from Parent where child == get_second_child()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/orm_left_side_expr_in_infix_expr_has_no_struct_field_error.vv:18:26: error: V ORM: left side of the `==` expression must be one of the `User`'s fields.
|
vlib/v/checker/tests/orm_left_side_expr_in_infix_expr_has_no_struct_field_error.vv:18:26: error: ORM: left side of the `==` expression must be one of the `User`'s fields.
|
||||||
1 possibility: `id`.
|
1 possibility: `id`.
|
||||||
16 |
|
16 |
|
||||||
17 | users := sql db {
|
17 | users := sql db {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/orm_limit_less_than_zero_error.vv:18:26: error: V ORM: `limit` must be greater than or equal to zero
|
vlib/v/checker/tests/orm_limit_less_than_zero_error.vv:18:26: error: ORM: `limit` must be greater than or equal to zero
|
||||||
16 |
|
16 |
|
||||||
17 | users := sql db {
|
17 | users := sql db {
|
||||||
18 | select from User limit user_limit
|
18 | select from User limit user_limit
|
||||||
|
|
7
vlib/v/checker/tests/orm_multidim_array.out
Normal file
7
vlib/v/checker/tests/orm_multidim_array.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/orm_multidim_array.vv:12:2: error: ORM: multi-dimension array fields are not supported
|
||||||
|
10 | id int [primary; sql: serial]
|
||||||
|
11 | users []User [fkey: id]
|
||||||
|
12 | bad [][]User [fkey: id]
|
||||||
|
| ~~~~~~~~~~~~~~
|
||||||
|
13 | }
|
||||||
|
14 |
|
20
vlib/v/checker/tests/orm_multidim_array.vv
Normal file
20
vlib/v/checker/tests/orm_multidim_array.vv
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
[table: users]
|
||||||
|
struct User {
|
||||||
|
id int [pimary]
|
||||||
|
}
|
||||||
|
|
||||||
|
[table: foos]
|
||||||
|
struct Foo {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
users []User [fkey: id]
|
||||||
|
bad [][]User [fkey: id]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
db := sqlite.connect(':memory:')!
|
||||||
|
sql db {
|
||||||
|
create table Foo
|
||||||
|
}!
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/orm_multiple_pkeys.vv:5:2: error: V ORM: a struct can only have one primary key
|
vlib/v/checker/tests/orm_multiple_pkeys.vv:5:2: error: ORM: a struct can only have one primary key
|
||||||
3 | struct Person {
|
3 | struct Person {
|
||||||
4 | id int [primary]
|
4 | id int [primary]
|
||||||
5 | name string [primary]
|
5 | name string [primary]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/orm_not_a_struct.vv:10:15: error: V ORM: the table symbol `Person` has to be a struct
|
vlib/v/checker/tests/orm_not_a_struct.vv:10:15: error: ORM: the table symbol `Person` has to be a struct
|
||||||
8 | db := sqlite.connect(':memory:')!
|
8 | db := sqlite.connect(':memory:')!
|
||||||
9 | _ := sql db {
|
9 | _ := sql db {
|
||||||
10 | select from Person
|
10 | select from Person
|
||||||
|
|
49
vlib/v/checker/tests/orm_op_with_option_and_none.out
Normal file
49
vlib/v/checker/tests/orm_op_with_option_and_none.out
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
vlib/v/checker/tests/orm_op_with_option_and_none.vv:11:30: warning: comparison with Option value probably isn't intended; use "is none" and "!is none" to select by NULL
|
||||||
|
9 | db := sqlite.connect(':memory:')!
|
||||||
|
10 | _ := sql db {
|
||||||
|
11 | select from Foo where name == val
|
||||||
|
| ~~
|
||||||
|
12 | }!
|
||||||
|
13 | }
|
||||||
|
vlib/v/checker/tests/orm_op_with_option_and_none.vv:25:30: warning: comparison with none probably isn't intended; use "is none" and "!is none" to select by NULL
|
||||||
|
23 |
|
||||||
|
24 | _ := sql db {
|
||||||
|
25 | select from Foo where name == none
|
||||||
|
| ~~
|
||||||
|
26 | }!
|
||||||
|
27 |
|
||||||
|
vlib/v/checker/tests/orm_op_with_option_and_none.vv:29:30: warning: comparison with none probably isn't intended; use "is none" and "!is none" to select by NULL
|
||||||
|
27 |
|
||||||
|
28 | _ := sql db {
|
||||||
|
29 | select from Foo where name != none
|
||||||
|
| ~~
|
||||||
|
30 | }!
|
||||||
|
31 |
|
||||||
|
vlib/v/checker/tests/orm_op_with_option_and_none.vv:33:30: warning: comparison with none probably isn't intended; use "is none" and "!is none" to select by NULL
|
||||||
|
31 |
|
||||||
|
32 | _ := sql db {
|
||||||
|
33 | select from Foo where name < none
|
||||||
|
| ^
|
||||||
|
34 | }!
|
||||||
|
35 |
|
||||||
|
vlib/v/checker/tests/orm_op_with_option_and_none.vv:37:30: warning: comparison with none probably isn't intended; use "is none" and "!is none" to select by NULL
|
||||||
|
35 |
|
||||||
|
36 | _ := sql db {
|
||||||
|
37 | select from Foo where name > none
|
||||||
|
| ^
|
||||||
|
38 | }!
|
||||||
|
39 |
|
||||||
|
vlib/v/checker/tests/orm_op_with_option_and_none.vv:41:30: warning: comparison with none probably isn't intended; use "is none" and "!is none" to select by NULL
|
||||||
|
39 |
|
||||||
|
40 | _ := sql db {
|
||||||
|
41 | select from Foo where name <= none
|
||||||
|
| ~~
|
||||||
|
42 | }!
|
||||||
|
43 |
|
||||||
|
vlib/v/checker/tests/orm_op_with_option_and_none.vv:45:30: warning: comparison with none probably isn't intended; use "is none" and "!is none" to select by NULL
|
||||||
|
43 |
|
||||||
|
44 | _ := sql db {
|
||||||
|
45 | select from Foo where name >= none
|
||||||
|
| ~~
|
||||||
|
46 | }!
|
||||||
|
47 |
|
55
vlib/v/checker/tests/orm_op_with_option_and_none.vv
Normal file
55
vlib/v/checker/tests/orm_op_with_option_and_none.vv
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
struct Foo {
|
||||||
|
id u64 [primary; sql: serial]
|
||||||
|
name ?string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar(val ?string) ! {
|
||||||
|
db := sqlite.connect(':memory:')!
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where name == val
|
||||||
|
}!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
db := sqlite.connect(':memory:')!
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table Foo
|
||||||
|
}!
|
||||||
|
|
||||||
|
bar(none) or {}
|
||||||
|
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where name == none
|
||||||
|
}!
|
||||||
|
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where name != none
|
||||||
|
}!
|
||||||
|
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where name < none
|
||||||
|
}!
|
||||||
|
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where name > none
|
||||||
|
}!
|
||||||
|
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where name <= none
|
||||||
|
}!
|
||||||
|
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where name >= none
|
||||||
|
}!
|
||||||
|
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where name is none
|
||||||
|
}!
|
||||||
|
|
||||||
|
_ := sql db {
|
||||||
|
select from Foo where name !is none
|
||||||
|
}!
|
||||||
|
}
|
7
vlib/v/checker/tests/orm_table_attributes.out
Normal file
7
vlib/v/checker/tests/orm_table_attributes.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/orm_table_attributes.vv:3:1: error: ORM: table attribute must have an argument
|
||||||
|
1 | import db.sqlite
|
||||||
|
2 |
|
||||||
|
3 | [table]
|
||||||
|
| ~~~~~~~
|
||||||
|
4 | struct Foo {
|
||||||
|
5 | id int [primary; sql: serial]
|
13
vlib/v/checker/tests/orm_table_attributes.vv
Normal file
13
vlib/v/checker/tests/orm_table_attributes.vv
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import db.sqlite
|
||||||
|
|
||||||
|
[table]
|
||||||
|
struct Foo {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
db := sqlite.connect(':memory:')!
|
||||||
|
sql db {
|
||||||
|
create table Foo
|
||||||
|
}!
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/orm_using_non_struct_field_in_order_by_error.vv:14:29: error: V ORM: `User` structure has no field with name `database`.
|
vlib/v/checker/tests/orm_using_non_struct_field_in_order_by_error.vv:14:29: error: ORM: `User` structure has no field with name `database`.
|
||||||
2 possibilities: `id`, `username`.
|
2 possibilities: `id`, `username`.
|
||||||
12 |
|
12 |
|
||||||
13 | users := sql db {
|
13 | users := sql db {
|
||||||
|
|
|
@ -1,20 +1,7 @@
|
||||||
vlib/v/checker/tests/orm_where_clause_unsupported_field_types_err.vv:15:29: error: V ORM: does not support array of primitive types
|
vlib/v/checker/tests/orm_where_clause_unsupported_field_types_err.vv:8:2: error: ORM: array fields must have an fkey attribute
|
||||||
13 | bytes := [u8(0)]
|
6 | pub struct Example {
|
||||||
14 | e := sql db {
|
7 | id int [primary; sql: serial]
|
||||||
15 | select from Example where example == bytes
|
8 | example []u8 [sql_type: 'bytea'; unique]
|
||||||
| ~~~~~~~
|
| ~~~~~~~~~~~~
|
||||||
16 | }!
|
9 | }
|
||||||
17 | f := sql db {
|
10 |
|
||||||
Details:
|
|
||||||
field name: `example`
|
|
||||||
data type: `[]u8`
|
|
||||||
vlib/v/checker/tests/orm_where_clause_unsupported_field_types_err.vv:18:30: error: V ORM: does not support array of primitive types
|
|
||||||
16 | }!
|
|
||||||
17 | f := sql db {
|
|
||||||
18 | select from Example where (example == bytes)
|
|
||||||
| ~~~~~~~
|
|
||||||
19 | }!
|
|
||||||
20 | print(e)
|
|
||||||
Details:
|
|
||||||
field name: `example`
|
|
||||||
data type: `[]u8`
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/orm_wrong_where_expr_error.vv:15:26: error: V ORM: `where` expression must have at least one comparison for filtering rows
|
vlib/v/checker/tests/orm_wrong_where_expr_error.vv:15:26: error: ORM: `where` expression must have at least one comparison for filtering rows
|
||||||
13 |
|
13 |
|
||||||
14 | users := sql db {
|
14 | users := sql db {
|
||||||
15 | select from User where 3
|
15 | select from User where 3
|
||||||
|
|
|
@ -19,6 +19,7 @@ const skip_on_cstrict = [
|
||||||
const skip_on_ubuntu_musl = [
|
const skip_on_ubuntu_musl = [
|
||||||
'vlib/v/checker/tests/vweb_tmpl_used_var.vv',
|
'vlib/v/checker/tests/vweb_tmpl_used_var.vv',
|
||||||
'vlib/v/checker/tests/vweb_routing_checks.vv',
|
'vlib/v/checker/tests/vweb_routing_checks.vv',
|
||||||
|
'vlib/v/checker/tests/orm_op_with_option_and_none.vv',
|
||||||
'vlib/v/tests/skip_unused/gg_code.vv',
|
'vlib/v/tests/skip_unused/gg_code.vv',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1478,7 +1478,7 @@ pub fn (mut f Fmt) sql_stmt_line(node ast.SqlStmtLine) {
|
||||||
f.write('\t')
|
f.write('\t')
|
||||||
match node.kind {
|
match node.kind {
|
||||||
.insert {
|
.insert {
|
||||||
f.writeln('insert ${node.object_var_name} into ${table_name}')
|
f.writeln('insert ${node.object_var} into ${table_name}')
|
||||||
}
|
}
|
||||||
.update {
|
.update {
|
||||||
f.write('update ${table_name} set ')
|
f.write('update ${table_name} set ')
|
||||||
|
|
|
@ -4582,8 +4582,12 @@ fn (mut g Gen) cast_expr(node ast.CastExpr) {
|
||||||
if sym.kind != .alias
|
if sym.kind != .alias
|
||||||
|| (!(sym.info as ast.Alias).parent_type.has_flag(.option)
|
|| (!(sym.info as ast.Alias).parent_type.has_flag(.option)
|
||||||
&& (sym.info as ast.Alias).parent_type !in [expr_type, ast.string_type]) {
|
&& (sym.info as ast.Alias).parent_type !in [expr_type, ast.string_type]) {
|
||||||
|
if sym.kind == .string && !node.typ.is_ptr() {
|
||||||
|
cast_label = '*(string*)&'
|
||||||
|
} else {
|
||||||
cast_label = '(${styp})'
|
cast_label = '(${styp})'
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if node.typ.has_flag(.option) && node.expr is ast.None {
|
if node.typ.has_flag(.option) && node.expr is ast.None {
|
||||||
g.gen_option_error(node.typ, node.expr)
|
g.gen_option_error(node.typ, node.expr)
|
||||||
} else if node.typ.has_flag(.option) {
|
} else if node.typ.has_flag(.option) {
|
||||||
|
|
|
@ -34,7 +34,12 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr) {
|
||||||
|
|
||||||
g.writeln('')
|
g.writeln('')
|
||||||
g.write_orm_connection_init(connection_var_name, &node.db_expr)
|
g.write_orm_connection_init(connection_var_name, &node.db_expr)
|
||||||
g.write_orm_select(node, connection_var_name, left, node.or_expr)
|
result_var := g.new_tmp_var()
|
||||||
|
result_c_typ := g.typ(node.typ)
|
||||||
|
g.writeln('${result_c_typ} ${result_var};')
|
||||||
|
g.write_orm_select(node, connection_var_name, result_var)
|
||||||
|
unwrapped_c_typ := g.typ(node.typ.clear_flags(.result))
|
||||||
|
g.write('${left} *(${unwrapped_c_typ}*)${result_var}.data')
|
||||||
}
|
}
|
||||||
|
|
||||||
// sql_stmt writes C code that calls ORM functions for
|
// sql_stmt writes C code that calls ORM functions for
|
||||||
|
@ -91,16 +96,14 @@ fn (mut g Gen) sql_stmt_line(stmt_line ast.SqlStmtLine, connection_var_name stri
|
||||||
// write_orm_connection_init writes C code that saves the database connection
|
// write_orm_connection_init writes C code that saves the database connection
|
||||||
// into a variable for later use in ORM queries.
|
// into a variable for later use in ORM queries.
|
||||||
fn (mut g Gen) write_orm_connection_init(connection_var_name string, db_expr &ast.Expr) {
|
fn (mut g Gen) write_orm_connection_init(connection_var_name string, db_expr &ast.Expr) {
|
||||||
db_expr_type := g.get_db_expr_type(db_expr) or {
|
db_expr_type := g.get_db_expr_type(db_expr) or { verror('ORM: unknown db type for ${db_expr}') }
|
||||||
verror('V ORM: unknown db type for ${db_expr}')
|
|
||||||
}
|
|
||||||
|
|
||||||
mut db_ctype_name := g.typ(db_expr_type)
|
mut db_ctype_name := g.typ(db_expr_type)
|
||||||
is_pointer := db_ctype_name.ends_with('*')
|
is_pointer := db_ctype_name.ends_with('*')
|
||||||
reference_sign := if is_pointer { '' } else { '&' }
|
reference_sign := if is_pointer { '' } else { '&' }
|
||||||
db_ctype_name = db_ctype_name.trim_right('*')
|
db_ctype_name = db_ctype_name.trim_right('*')
|
||||||
|
|
||||||
g.writeln('// V ORM')
|
g.writeln('// ORM')
|
||||||
g.write('orm__Connection ${connection_var_name} = ')
|
g.write('orm__Connection ${connection_var_name} = ')
|
||||||
|
|
||||||
if db_ctype_name == 'orm__Connection' {
|
if db_ctype_name == 'orm__Connection' {
|
||||||
|
@ -130,17 +133,17 @@ fn (mut g Gen) write_orm_create_table(node ast.SqlStmtLine, table_name string, c
|
||||||
for field in node.fields {
|
for field in node.fields {
|
||||||
g.writeln('// `${table_name}`.`${field.name}`')
|
g.writeln('// `${table_name}`.`${field.name}`')
|
||||||
sym := g.table.sym(field.typ)
|
sym := g.table.sym(field.typ)
|
||||||
|
typ := match true {
|
||||||
|
sym.name == 'time.Time' { '_const_orm__time_' }
|
||||||
|
sym.kind == .enum_ { '_const_orm__enum_' }
|
||||||
|
else { field.typ.idx().str() }
|
||||||
|
}
|
||||||
g.writeln('(orm__TableField){')
|
g.writeln('(orm__TableField){')
|
||||||
g.indent++
|
g.indent++
|
||||||
g.writeln('.name = _SLIT("${field.name}"),')
|
g.writeln('.name = _SLIT("${field.name}"),')
|
||||||
mut typ := int(field.typ)
|
|
||||||
if sym.name == 'time.Time' {
|
|
||||||
typ = -2
|
|
||||||
}
|
|
||||||
g.writeln('.typ = ${typ}, // `${sym.name}`')
|
g.writeln('.typ = ${typ}, // `${sym.name}`')
|
||||||
g.writeln('.is_arr = ${sym.kind == .array}, ')
|
g.writeln('.is_arr = ${sym.kind == .array}, ')
|
||||||
g.writeln('.is_enum = ${sym.kind == .enum_}, ')
|
g.writeln('.nullable = ${field.typ.has_flag(.option)},')
|
||||||
g.writeln('.is_time = ${g.table.get_type_name(field.typ) == 'time__Time'},')
|
|
||||||
g.writeln('.default_val = (string){ .str = (byteptr) "${field.default_val}", .is_lit = 1 },')
|
g.writeln('.default_val = (string){ .str = (byteptr) "${field.default_val}", .is_lit = 1 },')
|
||||||
g.writeln('.attrs = new_array_from_c_array(${field.attrs.len}, ${field.attrs.len}, sizeof(StructAttribute),')
|
g.writeln('.attrs = new_array_from_c_array(${field.attrs.len}, ${field.attrs.len}, sizeof(StructAttribute),')
|
||||||
g.indent++
|
g.indent++
|
||||||
|
@ -241,8 +244,8 @@ fn (mut g Gen) write_orm_update(node &ast.SqlStmtLine, table_name string, connec
|
||||||
g.write_orm_expr_to_primitive(e)
|
g.write_orm_expr_to_primitive(e)
|
||||||
}
|
}
|
||||||
g.indent--
|
g.indent--
|
||||||
g.indent--
|
|
||||||
g.writeln('})')
|
g.writeln('})')
|
||||||
|
g.indent--
|
||||||
}
|
}
|
||||||
|
|
||||||
g.writeln('),')
|
g.writeln('),')
|
||||||
|
@ -269,6 +272,7 @@ fn (mut g Gen) write_orm_delete(node &ast.SqlStmtLine, table_name string, connec
|
||||||
// inserting a struct into a table, saving inserted `id` into a passed variable.
|
// inserting a struct into a table, saving inserted `id` into a passed variable.
|
||||||
fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_var_name string, table_name string, last_ids_arr string, res string, pid string, fkey string, or_expr ast.OrExpr) {
|
fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_var_name string, table_name string, last_ids_arr string, res string, pid string, fkey string, or_expr ast.OrExpr) {
|
||||||
mut subs := []ast.SqlStmtLine{}
|
mut subs := []ast.SqlStmtLine{}
|
||||||
|
mut subs_unwrapped_c_typ := []string{}
|
||||||
mut arrs := []ast.SqlStmtLine{}
|
mut arrs := []ast.SqlStmtLine{}
|
||||||
mut fkeys := []string{}
|
mut fkeys := []string{}
|
||||||
mut field_names := []string{}
|
mut field_names := []string{}
|
||||||
|
@ -277,45 +281,55 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
|
||||||
sym := g.table.sym(field.typ)
|
sym := g.table.sym(field.typ)
|
||||||
if sym.kind == .struct_ && sym.name != 'time.Time' {
|
if sym.kind == .struct_ && sym.name != 'time.Time' {
|
||||||
subs << node.sub_structs[int(field.typ)]
|
subs << node.sub_structs[int(field.typ)]
|
||||||
|
unwrapped_c_typ := g.typ(field.typ.clear_flag(.option))
|
||||||
|
subs_unwrapped_c_typ << if field.typ.has_flag(.option) { unwrapped_c_typ } else { '' }
|
||||||
} else if sym.kind == .array {
|
} else if sym.kind == .array {
|
||||||
mut f_key := ''
|
if attr := field.attrs.find_first('fkey') {
|
||||||
for attr in field.attrs {
|
fkeys << attr.arg
|
||||||
if attr.name == 'fkey' && attr.has_arg {
|
|
||||||
if attr.kind == .string {
|
|
||||||
f_key = attr.arg
|
|
||||||
} else {
|
} else {
|
||||||
verror("`fkey` attribute need be string. Try [fkey: '${attr.arg}'] instead of [fkey: ${attr.arg}]")
|
verror('missing fkey attribute')
|
||||||
}
|
}
|
||||||
}
|
arrs << node.sub_structs[int(field.typ)]
|
||||||
}
|
|
||||||
if f_key == '' {
|
|
||||||
verror('a field which holds an array, needs a `fkey` defined ("${sym.name}")')
|
|
||||||
}
|
|
||||||
fkeys << f_key
|
|
||||||
info := sym.array_info()
|
|
||||||
if info.nr_dims == 1 {
|
|
||||||
arrs << node.sub_structs[int(info.elem_type)]
|
|
||||||
field_names << field.name
|
field_names << field.name
|
||||||
} else {
|
|
||||||
verror('V ORM only supports 1 dimensional arrays')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := node.fields.filter(g.table.sym(it.typ).kind != .array)
|
fields := node.fields.filter(g.table.sym(it.typ).kind != .array)
|
||||||
|
auto_fields := get_auto_field_idxs(fields)
|
||||||
|
|
||||||
primary_field := g.get_orm_struct_primary_field(fields) or { ast.StructField{} }
|
primary_field := g.get_orm_struct_primary_field(fields) or { ast.StructField{} }
|
||||||
|
|
||||||
mut is_serial := false
|
is_serial := primary_field.attrs.contains_arg('sql', 'serial')
|
||||||
for attr in primary_field.attrs {
|
&& primary_field.typ == ast.int_type
|
||||||
if attr.kind == .plain && attr.name == 'sql' && attr.arg.to_lower() == 'serial' {
|
|
||||||
is_serial = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is_serial = is_serial && primary_field.typ == ast.int_type
|
|
||||||
|
|
||||||
for sub in subs {
|
mut member_access_type := '.'
|
||||||
|
if node.scope != unsafe { nil } {
|
||||||
|
inserting_object := node.scope.find(node.object_var) or {
|
||||||
|
verror('`${node.object_var}` is not found in scope')
|
||||||
|
}
|
||||||
|
if inserting_object.typ.is_ptr() {
|
||||||
|
member_access_type = '->'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, mut sub in subs {
|
||||||
|
if subs_unwrapped_c_typ[i].len > 0 {
|
||||||
|
var := '${node.object_var}${member_access_type}${sub.object_var}'
|
||||||
|
g.writeln('if(${var}.state == 0) {')
|
||||||
|
g.indent++
|
||||||
|
sub.object_var = '(*(${subs_unwrapped_c_typ[i]}*)${node.object_var}${member_access_type}${sub.object_var}.data)'
|
||||||
|
} else {
|
||||||
|
sub.object_var = '${node.object_var}${member_access_type}${sub.object_var}'
|
||||||
|
}
|
||||||
g.sql_stmt_line(sub, connection_var_name, or_expr)
|
g.sql_stmt_line(sub, connection_var_name, or_expr)
|
||||||
g.writeln('array_push(&${last_ids_arr}, _MOV((orm__Primitive[]){orm__int_to_primitive(orm__Connection_name_table[${connection_var_name}._typ]._method_last_id(${connection_var_name}._object))}));')
|
g.writeln('array_push(&${last_ids_arr}, _MOV((orm__Primitive[1]){')
|
||||||
|
g.writeln('\torm__int_to_primitive(orm__Connection_name_table[${connection_var_name}._typ]._method_last_id(${connection_var_name}._object))}));')
|
||||||
|
if subs_unwrapped_c_typ[i].len > 0 {
|
||||||
|
g.indent--
|
||||||
|
g.writeln('} else {')
|
||||||
|
g.writeln('\tarray_push(&${last_ids_arr}, _MOV((orm__Primitive[1]){ _const_orm__null_primitive }));')
|
||||||
|
g.writeln('}')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.writeln('// sql { insert into `${table_name}` }')
|
g.writeln('// sql { insert into `${table_name}` }')
|
||||||
|
@ -329,10 +343,12 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
|
||||||
g.indent++
|
g.indent++
|
||||||
|
|
||||||
if fields.len > 0 {
|
if fields.len > 0 {
|
||||||
g.write('_MOV((string[${fields.len}]){ ')
|
g.writeln('_MOV((string[${fields.len}]){ ')
|
||||||
|
g.indent++
|
||||||
for f in fields {
|
for f in fields {
|
||||||
g.write('_SLIT("${g.get_orm_column_name_from_struct_field(f)}"), ')
|
g.writeln('_SLIT("${g.get_orm_column_name_from_struct_field(f)}"),')
|
||||||
}
|
}
|
||||||
|
g.indent--
|
||||||
g.writeln('})')
|
g.writeln('})')
|
||||||
} else {
|
} else {
|
||||||
g.writeln('NULL')
|
g.writeln('NULL')
|
||||||
|
@ -340,45 +356,39 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
|
||||||
g.indent--
|
g.indent--
|
||||||
g.writeln('),')
|
g.writeln('),')
|
||||||
|
|
||||||
mut member_access_type := '.'
|
|
||||||
|
|
||||||
if node.scope != unsafe { nil } {
|
|
||||||
inserting_object := node.scope.find(node.object_var_name) or {
|
|
||||||
verror('`${node.object_var_name}` is not found in scope')
|
|
||||||
}
|
|
||||||
|
|
||||||
if inserting_object.typ.is_ptr() {
|
|
||||||
member_access_type = '->'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.writeln('.data = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(orm__Primitive),')
|
g.writeln('.data = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(orm__Primitive),')
|
||||||
g.indent++
|
g.indent++
|
||||||
|
|
||||||
if fields.len > 0 {
|
if fields.len > 0 {
|
||||||
g.write('_MOV((orm__Primitive[${fields.len}]){ ')
|
g.writeln('_MOV((orm__Primitive[${fields.len}]){')
|
||||||
|
g.indent++
|
||||||
mut structs := 0
|
mut structs := 0
|
||||||
for field in fields {
|
for field in fields {
|
||||||
if field.name == fkey {
|
if field.name == fkey {
|
||||||
g.write('${pid}, ')
|
g.writeln('${pid}, ')
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mut sym := g.table.sym(field.typ)
|
mut sym := g.table.sym(field.typ)
|
||||||
mut typ := sym.cname
|
mut typ := sym.cname
|
||||||
if sym.kind == .struct_ && typ != 'time__Time' {
|
if sym.kind == .struct_ && typ != 'time__Time' {
|
||||||
g.write('(*(orm__Primitive*) array_get(${last_ids_arr}, ${structs})),')
|
g.writeln('(*(orm__Primitive*) array_get(${last_ids_arr}, ${structs})),')
|
||||||
structs++
|
structs++
|
||||||
continue
|
continue
|
||||||
} else if sym.kind == .enum_ {
|
|
||||||
g.write('orm__i64_to_primitive(${node.object_var_name}${member_access_type}${c_name(field.name)}), ')
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
// fields processed hereafter can be NULL...
|
||||||
if typ == 'time__Time' {
|
if typ == 'time__Time' {
|
||||||
typ = 'time'
|
typ = 'time'
|
||||||
|
} else if sym.kind == .enum_ {
|
||||||
|
typ = 'i64'
|
||||||
}
|
}
|
||||||
|
var := '${node.object_var}${member_access_type}${c_name(field.name)}'
|
||||||
g.write('orm__${typ}_to_primitive(${node.object_var_name}${member_access_type}${c_name(field.name)}), ')
|
if field.typ.has_flag(.option) {
|
||||||
|
g.writeln('${var}.state == 2? _const_orm__null_primitive : orm__${typ}_to_primitive(*(${typ}*)(${var}.data)),')
|
||||||
|
} else {
|
||||||
|
g.writeln('orm__${typ}_to_primitive(${var}),')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
g.indent--
|
||||||
g.writeln('})')
|
g.writeln('})')
|
||||||
} else {
|
} else {
|
||||||
g.write('NULL')
|
g.write('NULL')
|
||||||
|
@ -386,7 +396,18 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
|
||||||
g.indent--
|
g.indent--
|
||||||
g.writeln('),')
|
g.writeln('),')
|
||||||
g.writeln('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
|
g.writeln('.types = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
|
||||||
g.writeln('.primary_column_name = _SLIT("${primary_field.name}"),')
|
if auto_fields.len > 0 {
|
||||||
|
g.writeln('.auto_fields = new_array_from_c_array(${auto_fields.len}, ${auto_fields.len}, sizeof(int),')
|
||||||
|
g.indent++
|
||||||
|
g.write('_MOV((int[${auto_fields.len}]){')
|
||||||
|
for i in auto_fields {
|
||||||
|
g.write(' ${i},')
|
||||||
|
}
|
||||||
|
g.writeln(' })),')
|
||||||
|
g.indent--
|
||||||
|
} else {
|
||||||
|
g.writeln('.auto_fields = __new_array_with_default_noscan(0, 0, sizeof(int), 0),')
|
||||||
|
}
|
||||||
g.writeln('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
|
g.writeln('.kinds = __new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
|
||||||
g.writeln('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
|
g.writeln('.is_and = __new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
|
||||||
g.indent--
|
g.indent--
|
||||||
|
@ -406,19 +427,18 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
|
||||||
if typ == 'time__Time' {
|
if typ == 'time__Time' {
|
||||||
typ = 'time'
|
typ = 'time'
|
||||||
}
|
}
|
||||||
g.writeln('orm__Primitive ${id_name} = orm__${typ}_to_primitive(${node.object_var_name}${member_access_type}${c_name(primary_field.name)});')
|
g.writeln('orm__Primitive ${id_name} = orm__${typ}_to_primitive(${node.object_var}${member_access_type}${c_name(primary_field.name)});')
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, mut arr in arrs {
|
for i, mut arr in arrs {
|
||||||
c_field_name := c_name(field_names[i])
|
|
||||||
idx := g.new_tmp_var()
|
idx := g.new_tmp_var()
|
||||||
g.writeln('for (int ${idx} = 0; ${idx} < ${arr.object_var_name}${member_access_type}${c_field_name}.len; ${idx}++) {')
|
g.writeln('for (int ${idx} = 0; ${idx} < ${node.object_var}${member_access_type}${arr.object_var}.len; ${idx}++) {')
|
||||||
|
g.indent++
|
||||||
last_ids := g.new_tmp_var()
|
last_ids := g.new_tmp_var()
|
||||||
res_ := g.new_tmp_var()
|
res_ := g.new_tmp_var()
|
||||||
tmp_var := g.new_tmp_var()
|
tmp_var := g.new_tmp_var()
|
||||||
ctyp := g.typ(arr.table_expr.typ)
|
ctyp := g.typ(arr.table_expr.typ)
|
||||||
g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)array_get(${arr.object_var_name}${member_access_type}${c_field_name}, ${idx}));')
|
g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)array_get(${node.object_var}${member_access_type}${arr.object_var}, ${idx}));')
|
||||||
arr.object_var_name = tmp_var
|
arr.object_var = tmp_var
|
||||||
mut fff := []ast.StructField{}
|
mut fff := []ast.StructField{}
|
||||||
for f in arr.fields {
|
for f in arr.fields {
|
||||||
mut skip := false
|
mut skip := false
|
||||||
|
@ -438,6 +458,7 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
|
||||||
unsafe { fff.free() }
|
unsafe { fff.free() }
|
||||||
g.write_orm_insert_with_last_ids(arr, connection_var_name, g.get_table_name_by_struct_type(arr.table_expr.typ),
|
g.write_orm_insert_with_last_ids(arr, connection_var_name, g.get_table_name_by_struct_type(arr.table_expr.typ),
|
||||||
last_ids, res_, id_name, fkeys[i], or_expr)
|
last_ids, res_, id_name, fkeys[i], or_expr)
|
||||||
|
g.indent--
|
||||||
g.writeln('}')
|
g.writeln('}')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -475,9 +496,12 @@ fn (mut g Gen) write_orm_expr_to_primitive(expr ast.Expr) {
|
||||||
ast.CallExpr {
|
ast.CallExpr {
|
||||||
g.write_orm_primitive(expr.return_type, expr)
|
g.write_orm_primitive(expr.return_type, expr)
|
||||||
}
|
}
|
||||||
|
ast.None {
|
||||||
|
g.write_orm_primitive(ast.none_type, expr)
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
eprintln(expr)
|
eprintln(expr)
|
||||||
verror('V ORM: ${expr.type_name()} is not supported')
|
verror('ORM: ${expr.type_name()} is not supported')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -489,19 +513,17 @@ fn (mut g Gen) write_orm_primitive(t ast.Type, expr ast.Expr) {
|
||||||
mut typ := sym.cname
|
mut typ := sym.cname
|
||||||
if typ == 'orm__Primitive' {
|
if typ == 'orm__Primitive' {
|
||||||
g.expr(expr)
|
g.expr(expr)
|
||||||
g.write(',')
|
g.writeln(',')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if typ == 'time__Time' {
|
if typ == 'none' {
|
||||||
typ = 'time'
|
g.writeln('_const_orm__null_primitive,')
|
||||||
}
|
return
|
||||||
if typ == 'orm__InfixType' {
|
|
||||||
typ = 'infix'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g.write('orm__${typ}_to_primitive(')
|
|
||||||
if expr is ast.InfixExpr {
|
if expr is ast.InfixExpr {
|
||||||
g.write('(orm__InfixType){')
|
g.writeln('orm__infix_to_primitive((orm__InfixType){')
|
||||||
|
g.indent++
|
||||||
g.write('.name = _SLIT("${expr.left}"),')
|
g.write('.name = _SLIT("${expr.left}"),')
|
||||||
mut kind := match expr.op {
|
mut kind := match expr.op {
|
||||||
.plus {
|
.plus {
|
||||||
|
@ -520,16 +542,28 @@ fn (mut g Gen) write_orm_primitive(t ast.Type, expr ast.Expr) {
|
||||||
''
|
''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.write(' .operator = ${kind},')
|
g.writeln(' .operator = ${kind},')
|
||||||
g.write(' .right = ')
|
g.write(' .right = ')
|
||||||
g.write_orm_expr_to_primitive(expr.right)
|
g.write_orm_expr_to_primitive(expr.right)
|
||||||
g.write(' }')
|
g.indent--
|
||||||
} else if expr is ast.CallExpr {
|
g.writeln(' }),')
|
||||||
|
} else {
|
||||||
|
if typ == 'time__Time' {
|
||||||
|
typ = 'time'
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.has_flag(.option) {
|
||||||
|
typ = 'option_${typ}'
|
||||||
|
}
|
||||||
|
g.write('orm__${typ}_to_primitive(')
|
||||||
|
if expr is ast.CallExpr {
|
||||||
g.call_expr(expr)
|
g.call_expr(expr)
|
||||||
} else {
|
} else {
|
||||||
|
g.left_is_opt = true
|
||||||
g.expr(expr)
|
g.expr(expr)
|
||||||
}
|
}
|
||||||
g.write('),')
|
g.writeln('),')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// write_orm_where writes C code that generates
|
// write_orm_where writes C code that generates
|
||||||
|
@ -541,7 +575,8 @@ fn (mut g Gen) write_orm_where(where_expr ast.Expr) {
|
||||||
mut data := []ast.Expr{}
|
mut data := []ast.Expr{}
|
||||||
mut is_ands := []bool{}
|
mut is_ands := []bool{}
|
||||||
|
|
||||||
g.writeln('// V ORM where')
|
g.writeln('// ORM where')
|
||||||
|
|
||||||
g.writeln('(orm__QueryData){')
|
g.writeln('(orm__QueryData){')
|
||||||
g.indent++
|
g.indent++
|
||||||
g.write_orm_where_expr(where_expr, mut fields, mut parentheses, mut kinds, mut data, mut
|
g.write_orm_where_expr(where_expr, mut fields, mut parentheses, mut kinds, mut data, mut
|
||||||
|
@ -550,10 +585,12 @@ fn (mut g Gen) write_orm_where(where_expr ast.Expr) {
|
||||||
if fields.len > 0 {
|
if fields.len > 0 {
|
||||||
g.writeln('.fields = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
|
g.writeln('.fields = new_array_from_c_array(${fields.len}, ${fields.len}, sizeof(string),')
|
||||||
g.indent++
|
g.indent++
|
||||||
g.write('_MOV((string[${fields.len}]){')
|
g.writeln('_MOV((string[${fields.len}]){')
|
||||||
|
g.indent++
|
||||||
for field in fields {
|
for field in fields {
|
||||||
g.write(' _SLIT("${field}"),')
|
g.writeln('_SLIT("${field}"),')
|
||||||
}
|
}
|
||||||
|
g.indent--
|
||||||
g.writeln('})')
|
g.writeln('})')
|
||||||
g.indent--
|
g.indent--
|
||||||
} else {
|
} else {
|
||||||
|
@ -564,12 +601,15 @@ fn (mut g Gen) write_orm_where(where_expr ast.Expr) {
|
||||||
g.writeln('.data = new_array_from_c_array(${data.len}, ${data.len}, sizeof(orm__Primitive),')
|
g.writeln('.data = new_array_from_c_array(${data.len}, ${data.len}, sizeof(orm__Primitive),')
|
||||||
g.indent++
|
g.indent++
|
||||||
if data.len > 0 {
|
if data.len > 0 {
|
||||||
g.write('_MOV((orm__Primitive[${data.len}]){ ')
|
g.writeln('_MOV((orm__Primitive[${data.len}]){')
|
||||||
|
g.indent++
|
||||||
for e in data {
|
for e in data {
|
||||||
g.write_orm_expr_to_primitive(e)
|
g.write_orm_expr_to_primitive(e)
|
||||||
g.write(' ')
|
|
||||||
}
|
}
|
||||||
g.write('})')
|
g.indent--
|
||||||
|
g.writeln('})')
|
||||||
|
} else {
|
||||||
|
g.writeln('0')
|
||||||
}
|
}
|
||||||
g.indent--
|
g.indent--
|
||||||
g.writeln('),')
|
g.writeln('),')
|
||||||
|
@ -603,7 +643,7 @@ fn (mut g Gen) write_orm_where(where_expr ast.Expr) {
|
||||||
g.writeln('${k},')
|
g.writeln('${k},')
|
||||||
}
|
}
|
||||||
g.indent--
|
g.indent--
|
||||||
g.write('})')
|
g.writeln('})')
|
||||||
g.indent--
|
g.indent--
|
||||||
} else {
|
} else {
|
||||||
g.write('.kinds = __new_array_with_default_noscan(${kinds.len}, ${kinds.len}, sizeof(orm__OperationKind), 0')
|
g.write('.kinds = __new_array_with_default_noscan(${kinds.len}, ${kinds.len}, sizeof(orm__OperationKind), 0')
|
||||||
|
@ -612,11 +652,15 @@ fn (mut g Gen) write_orm_where(where_expr ast.Expr) {
|
||||||
|
|
||||||
if is_ands.len > 0 {
|
if is_ands.len > 0 {
|
||||||
g.write('.is_and = new_array_from_c_array(${is_ands.len}, ${is_ands.len}, sizeof(bool),')
|
g.write('.is_and = new_array_from_c_array(${is_ands.len}, ${is_ands.len}, sizeof(bool),')
|
||||||
g.write(' _MOV((bool[${is_ands.len}]){')
|
g.indent++
|
||||||
|
g.writeln('_MOV((bool[${is_ands.len}]){')
|
||||||
|
g.indent++
|
||||||
for is_and in is_ands {
|
for is_and in is_ands {
|
||||||
g.write(' ${is_and}, ')
|
g.writeln('${is_and},')
|
||||||
}
|
}
|
||||||
|
g.indent--
|
||||||
g.write('})')
|
g.write('})')
|
||||||
|
g.indent--
|
||||||
} else {
|
} else {
|
||||||
g.write('.is_and = __new_array_with_default_noscan(${is_ands.len}, ${is_ands.len}, sizeof(bool), 0')
|
g.write('.is_and = __new_array_with_default_noscan(${is_ands.len}, ${is_ands.len}, sizeof(bool), 0')
|
||||||
}
|
}
|
||||||
|
@ -654,6 +698,12 @@ fn (mut g Gen) write_orm_where_expr(expr ast.Expr, mut fields []string, mut pare
|
||||||
.key_like {
|
.key_like {
|
||||||
'orm__OperationKind__orm_like'
|
'orm__OperationKind__orm_like'
|
||||||
}
|
}
|
||||||
|
.key_is {
|
||||||
|
'orm__OperationKind__is'
|
||||||
|
}
|
||||||
|
.not_is {
|
||||||
|
'orm__OperationKind__is_not'
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
''
|
''
|
||||||
}
|
}
|
||||||
|
@ -712,12 +762,16 @@ fn (mut g Gen) write_orm_where_expr(expr ast.Expr, mut fields []string, mut pare
|
||||||
ast.CallExpr {
|
ast.CallExpr {
|
||||||
data << expr
|
data << expr
|
||||||
}
|
}
|
||||||
|
ast.None {
|
||||||
|
data << expr
|
||||||
|
}
|
||||||
else {}
|
else {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// write_orm_select writes C code that calls ORM functions for selecting rows.
|
// write_orm_select writes C code that calls ORM functions for selecting rows,
|
||||||
fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, left_expr_string string, or_expr ast.OrExpr) {
|
// storing the result in the provided variable name
|
||||||
|
fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, result_var string) {
|
||||||
mut fields := []ast.StructField{}
|
mut fields := []ast.StructField{}
|
||||||
mut primary_field := g.get_orm_struct_primary_field(node.fields) or { ast.StructField{} }
|
mut primary_field := g.get_orm_struct_primary_field(node.fields) or { ast.StructField{} }
|
||||||
|
|
||||||
|
@ -772,25 +826,27 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, le
|
||||||
select_fields := fields.filter(g.table.sym(it.typ).kind != .array)
|
select_fields := fields.filter(g.table.sym(it.typ).kind != .array)
|
||||||
g.writeln('.fields = new_array_from_c_array(${select_fields.len}, ${select_fields.len}, sizeof(string),')
|
g.writeln('.fields = new_array_from_c_array(${select_fields.len}, ${select_fields.len}, sizeof(string),')
|
||||||
g.indent++
|
g.indent++
|
||||||
mut types := []int{}
|
mut types := []string{}
|
||||||
if select_fields.len > 0 {
|
if select_fields.len > 0 {
|
||||||
g.write('_MOV((string[${select_fields.len}]){')
|
g.writeln('_MOV((string[${select_fields.len}]){')
|
||||||
|
g.indent++
|
||||||
for field in select_fields {
|
for field in select_fields {
|
||||||
g.write(' _SLIT("${g.get_orm_column_name_from_struct_field(field)}"),')
|
g.writeln('_SLIT("${g.get_orm_column_name_from_struct_field(field)}"),')
|
||||||
sym := g.table.sym(field.typ)
|
sym := g.table.sym(field.typ)
|
||||||
if sym.name == 'time.Time' {
|
if sym.name == 'time.Time' {
|
||||||
types << -2
|
types << '_const_orm__time_'
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if sym.kind == .struct_ {
|
if sym.kind == .struct_ {
|
||||||
types << int(ast.int_type)
|
types << int(ast.int_type).str()
|
||||||
continue
|
continue
|
||||||
} else if sym.kind == .enum_ {
|
} else if sym.kind == .enum_ {
|
||||||
types << int(ast.i64_type)
|
types << '_const_orm__enum_'
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
types << int(field.typ)
|
types << field.typ.idx().str()
|
||||||
}
|
}
|
||||||
|
g.indent--
|
||||||
g.writeln('})')
|
g.writeln('})')
|
||||||
} else {
|
} else {
|
||||||
g.writeln('NULL')
|
g.writeln('NULL')
|
||||||
|
@ -857,27 +913,22 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, le
|
||||||
g.indent--
|
g.indent--
|
||||||
g.writeln(');')
|
g.writeln(');')
|
||||||
|
|
||||||
unwrapped_typ := node.typ.clear_flag(.result)
|
g.writeln('${result_var}.is_error = ${select_result_var_name}.is_error;')
|
||||||
unwrapped_c_typ := g.typ(unwrapped_typ)
|
g.writeln('${result_var}.err = ${select_result_var_name}.err;')
|
||||||
c_typ := g.typ(node.typ)
|
g.or_block(result_var, node.or_expr, node.typ)
|
||||||
|
|
||||||
mut non_orm_result_var_name := g.new_tmp_var()
|
// or_block could have ended in return (longjump) or could have
|
||||||
g.writeln('${c_typ} ${non_orm_result_var_name};')
|
// yielded another value, so we must test for on non-error result
|
||||||
g.writeln('${non_orm_result_var_name}.is_error = ${select_result_var_name}.is_error;')
|
g.writeln('if (!${result_var}.is_error) {')
|
||||||
g.writeln('${non_orm_result_var_name}.err = ${select_result_var_name}.err;')
|
|
||||||
g.or_block(non_orm_result_var_name, node.or_expr, node.typ)
|
|
||||||
g.writeln('else {')
|
|
||||||
g.indent++
|
g.indent++
|
||||||
|
|
||||||
|
unwrapped_c_typ := g.typ(node.typ.clear_flag(.result))
|
||||||
select_unwrapped_result_var_name := g.new_tmp_var()
|
select_unwrapped_result_var_name := g.new_tmp_var()
|
||||||
|
|
||||||
g.writeln('Array_Array_orm__Primitive ${select_unwrapped_result_var_name} = (*(Array_Array_orm__Primitive*)${select_result_var_name}.data);')
|
g.writeln('Array_Array_orm__Primitive ${select_unwrapped_result_var_name} = (*(Array_Array_orm__Primitive*)${select_result_var_name}.data);')
|
||||||
|
|
||||||
if node.is_count {
|
if node.is_count {
|
||||||
g.writeln('*(${unwrapped_c_typ}*) ${non_orm_result_var_name}.data = *((*(orm__Primitive*) array_get((*(Array_orm__Primitive*)array_get(${select_unwrapped_result_var_name}, 0)), 0))._int);')
|
g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = *((*(orm__Primitive*) array_get((*(Array_orm__Primitive*)array_get(${select_unwrapped_result_var_name}, 0)), 0))._int);')
|
||||||
|
|
||||||
g.indent--
|
|
||||||
g.writeln('}')
|
|
||||||
} else {
|
} else {
|
||||||
tmp := g.new_tmp_var()
|
tmp := g.new_tmp_var()
|
||||||
idx := g.new_tmp_var()
|
idx := g.new_tmp_var()
|
||||||
|
@ -913,10 +964,12 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, le
|
||||||
g.writeln('if (${select_unwrapped_result_var_name}.len > 0) {')
|
g.writeln('if (${select_unwrapped_result_var_name}.len > 0) {')
|
||||||
g.indent++
|
g.indent++
|
||||||
|
|
||||||
mut selected_fields_idx := 0
|
mut fields_idx := 0
|
||||||
for field in fields {
|
for field in fields {
|
||||||
array_get_call_code := '(*(orm__Primitive*) array_get((*(Array_orm__Primitive*) array_get(${select_unwrapped_result_var_name}, ${idx})), ${selected_fields_idx}))'
|
array_get_call_code := '(*(orm__Primitive*) array_get((*(Array_orm__Primitive*) array_get(${select_unwrapped_result_var_name}, ${idx})), ${fields_idx}))'
|
||||||
sym := g.table.sym(field.typ)
|
sym := g.table.sym(field.typ)
|
||||||
|
field_var := '${tmp}.${c_name(field.name)}'
|
||||||
|
field_c_typ := g.typ(field.typ)
|
||||||
if sym.kind == .struct_ && sym.name != 'time.Time' {
|
if sym.kind == .struct_ && sym.name != 'time.Time' {
|
||||||
mut sub := node.sub_structs[int(field.typ)]
|
mut sub := node.sub_structs[int(field.typ)]
|
||||||
mut where_expr := sub.where_expr as ast.InfixExpr
|
mut where_expr := sub.where_expr as ast.InfixExpr
|
||||||
|
@ -933,28 +986,30 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, le
|
||||||
where_expr.right = ident
|
where_expr.right = ident
|
||||||
sub.where_expr = where_expr
|
sub.where_expr = where_expr
|
||||||
|
|
||||||
g.write_orm_select(sub, connection_var_name, '${tmp}.${c_name(field.name)} = ',
|
sub_result_var := g.new_tmp_var()
|
||||||
or_expr)
|
sub_result_c_typ := g.typ(sub.typ)
|
||||||
selected_fields_idx++
|
g.writeln('${sub_result_c_typ} ${sub_result_var};')
|
||||||
|
g.write_orm_select(sub, connection_var_name, sub_result_var)
|
||||||
|
|
||||||
|
if field.typ.has_flag(.option) {
|
||||||
|
unwrapped_field_c_typ := g.typ(field.typ.clear_flag(.option))
|
||||||
|
g.writeln('if (!${sub_result_var}.is_error)')
|
||||||
|
g.writeln('\t_option_ok(${sub_result_var}.data, (_option *)&${field_var}, sizeof(${unwrapped_field_c_typ}));')
|
||||||
|
g.writeln('else')
|
||||||
|
g.writeln('\t${field_var} = (${field_c_typ}){ .state = 2, .err = _const_none__, .data = {EMPTY_STRUCT_INITIALIZATION} };')
|
||||||
|
} else {
|
||||||
|
g.writeln('if (!${sub_result_var}.is_error)')
|
||||||
|
g.writeln('\t${field_var} = *(${field_c_typ}*)${sub_result_var}.data;')
|
||||||
|
}
|
||||||
|
fields_idx++
|
||||||
} else if sym.kind == .array {
|
} else if sym.kind == .array {
|
||||||
mut fkey := ''
|
mut fkey := ''
|
||||||
// TODO: move to the ORM checker
|
if attr := field.attrs.find_first('fkey') {
|
||||||
for attr in field.attrs {
|
|
||||||
if attr.name == 'fkey' && attr.has_arg {
|
|
||||||
if attr.kind == .string {
|
|
||||||
fkey = attr.arg
|
fkey = attr.arg
|
||||||
} else {
|
} else {
|
||||||
verror("`fkey` attribute need be string. Try [fkey: '${attr.arg}'] instead of [fkey: ${attr.arg}]")
|
verror('missing fkey attribute')
|
||||||
}
|
}
|
||||||
}
|
sub := node.sub_structs[field.typ]
|
||||||
}
|
|
||||||
// TODO: move to the ORM checker
|
|
||||||
if fkey == '' {
|
|
||||||
verror('a field which holds an array, needs a `fkey` defined ("${sym.name}")')
|
|
||||||
}
|
|
||||||
info := sym.array_info()
|
|
||||||
arr_typ := info.elem_type
|
|
||||||
sub := node.sub_structs[int(arr_typ)]
|
|
||||||
mut where_expr := sub.where_expr as ast.InfixExpr
|
mut where_expr := sub.where_expr as ast.InfixExpr
|
||||||
mut left_where_expr := where_expr.left as ast.Ident
|
mut left_where_expr := where_expr.left as ast.Ident
|
||||||
mut right_where_expr := where_expr.right as ast.Ident
|
mut right_where_expr := where_expr.right as ast.Ident
|
||||||
|
@ -990,20 +1045,30 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, le
|
||||||
where_expr: where_expr
|
where_expr: where_expr
|
||||||
}
|
}
|
||||||
|
|
||||||
g.write_orm_select(sql_expr_select_array, connection_var_name, '${tmp}.${c_name(field.name)} = ',
|
sub_result_var := g.new_tmp_var()
|
||||||
or_expr)
|
sub_result_c_typ := g.typ(sub.typ)
|
||||||
|
g.writeln('${sub_result_c_typ} ${sub_result_var};')
|
||||||
|
g.write_orm_select(sql_expr_select_array, connection_var_name, sub_result_var)
|
||||||
|
g.writeln('if (!${sub_result_var}.is_error)')
|
||||||
|
g.writeln('\t${field_var} = *(${unwrapped_c_typ}*)${sub_result_var}.data;')
|
||||||
|
} else if field.typ.has_flag(.option) {
|
||||||
|
prim_var := g.new_tmp_var()
|
||||||
|
g.writeln('orm__Primitive *${prim_var} = &${array_get_call_code};')
|
||||||
|
g.writeln('if (${prim_var}->_typ == ${g.table.find_type_idx('orm.Null')})')
|
||||||
|
g.writeln('\t${field_var} = (${field_c_typ}){ .state = 2, .err = _const_none__, .data = {EMPTY_STRUCT_INITIALIZATION} };')
|
||||||
|
|
||||||
|
g.writeln('else')
|
||||||
|
g.writeln('\t_option_ok(${prim_var}->_${sym.cname}, (_option *)&${field_var}, sizeof(${sym.cname}));')
|
||||||
|
fields_idx++
|
||||||
} else if sym.kind == .enum_ {
|
} else if sym.kind == .enum_ {
|
||||||
mut typ := sym.cname
|
mut typ := sym.cname
|
||||||
g.writeln('${tmp}.${c_name(field.name)} = (${typ}) (*(${array_get_call_code}._i64));')
|
g.writeln('${tmp}.${c_name(field.name)} = (${typ}) (*(${array_get_call_code}._i64));')
|
||||||
selected_fields_idx++
|
fields_idx++
|
||||||
} else {
|
} else {
|
||||||
mut typ := sym.cname
|
g.writeln('${field_var} = *(${array_get_call_code}._${sym.cname});')
|
||||||
g.writeln('${tmp}.${c_name(field.name)} = *(${array_get_call_code}._${typ});')
|
fields_idx++
|
||||||
selected_fields_idx++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.indent--
|
|
||||||
g.writeln('}')
|
|
||||||
|
|
||||||
if node.is_array {
|
if node.is_array {
|
||||||
g.writeln('array_push(&${tmp}_array, _MOV((${typ_str}[]){ ${tmp} }));')
|
g.writeln('array_push(&${tmp}_array, _MOV((${typ_str}[]){ ${tmp} }));')
|
||||||
|
@ -1011,45 +1076,37 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, le
|
||||||
g.writeln('}')
|
g.writeln('}')
|
||||||
}
|
}
|
||||||
|
|
||||||
g.write('*(${unwrapped_c_typ}*) ${non_orm_result_var_name}.data = ${tmp}')
|
g.indent--
|
||||||
|
g.writeln('}')
|
||||||
|
|
||||||
|
g.write('*(${unwrapped_c_typ}*) ${result_var}.data = ${tmp}')
|
||||||
if node.is_array {
|
if node.is_array {
|
||||||
g.write('_array')
|
g.write('_array')
|
||||||
}
|
}
|
||||||
g.writeln(';')
|
g.writeln(';')
|
||||||
|
}
|
||||||
|
|
||||||
g.indent--
|
g.indent--
|
||||||
g.writeln('}')
|
g.writeln('}')
|
||||||
}
|
|
||||||
g.write('${left_expr_string.trim_space()} *(${unwrapped_c_typ}*) ${non_orm_result_var_name}.data')
|
|
||||||
|
|
||||||
if node.is_generated {
|
if node.is_generated {
|
||||||
g.write(';')
|
g.writeln(';')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter_struct_fields_by_orm_attrs filters struct fields taking into its attributes.
|
// filter_struct_fields_by_orm_attrs filters struct fields taking into its attributes.
|
||||||
// Used by non-create queries for skipping fields.
|
// Used by non-create queries for skipping fields.
|
||||||
fn (_ &Gen) filter_struct_fields_by_orm_attrs(fields []ast.StructField) []ast.StructField {
|
fn (_ &Gen) filter_struct_fields_by_orm_attrs(fields []ast.StructField) []ast.StructField {
|
||||||
mut result := []ast.StructField{}
|
mut ret := []ast.StructField{}
|
||||||
|
|
||||||
for field in fields {
|
for field in fields {
|
||||||
mut skip := false
|
if field.attrs.contains('skip') || field.attrs.contains_arg('sql', '-') {
|
||||||
|
continue
|
||||||
for attr in field.attrs {
|
|
||||||
if attr.name == 'skip' {
|
|
||||||
skip = true
|
|
||||||
}
|
|
||||||
if attr.name == 'sql' && attr.arg == '-' {
|
|
||||||
skip = true
|
|
||||||
}
|
}
|
||||||
|
ret << field
|
||||||
}
|
}
|
||||||
|
|
||||||
if !skip {
|
return ret
|
||||||
result << field
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get_db_expr_type returns the database type from the database expression.
|
// get_db_expr_type returns the database type from the database expression.
|
||||||
|
@ -1067,13 +1124,12 @@ fn (g &Gen) get_db_expr_type(expr ast.Expr) ?ast.Type {
|
||||||
|
|
||||||
// get_table_name_by_struct_type converts the struct type to a table name.
|
// get_table_name_by_struct_type converts the struct type to a table name.
|
||||||
fn (g &Gen) get_table_name_by_struct_type(typ ast.Type) string {
|
fn (g &Gen) get_table_name_by_struct_type(typ ast.Type) string {
|
||||||
info := g.table.sym(typ).struct_info()
|
sym := g.table.sym(typ)
|
||||||
mut table_name := util.strip_mod_name(g.table.sym(typ).name)
|
info := sym.struct_info()
|
||||||
|
mut table_name := util.strip_mod_name(sym.name)
|
||||||
|
|
||||||
for attr in info.attrs {
|
if attr := info.attrs.find_first('table') {
|
||||||
if attr.kind == .string && attr.name == 'table' && attr.arg != '' {
|
table_name = attr.arg
|
||||||
return attr.arg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return table_name
|
return table_name
|
||||||
|
@ -1096,10 +1152,10 @@ fn (g &Gen) get_orm_current_table_field(name string) ?ast.StructField {
|
||||||
fn (g &Gen) get_orm_column_name_from_struct_field(field ast.StructField) string {
|
fn (g &Gen) get_orm_column_name_from_struct_field(field ast.StructField) string {
|
||||||
mut name := field.name
|
mut name := field.name
|
||||||
|
|
||||||
for attr in field.attrs {
|
if attr := field.attrs.find_first('sql') {
|
||||||
if attr.kind == .string && attr.name == 'sql' && attr.arg != '' {
|
if attr.arg !in ['serial', 'i8', 'i16', 'int', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32',
|
||||||
|
'f64', 'bool', 'string'] {
|
||||||
name = attr.arg
|
name = attr.arg
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1114,12 +1170,24 @@ fn (g &Gen) get_orm_column_name_from_struct_field(field ast.StructField) string
|
||||||
// get_orm_struct_primary_field returns the table's primary column field.
|
// get_orm_struct_primary_field returns the table's primary column field.
|
||||||
fn (_ &Gen) get_orm_struct_primary_field(fields []ast.StructField) ?ast.StructField {
|
fn (_ &Gen) get_orm_struct_primary_field(fields []ast.StructField) ?ast.StructField {
|
||||||
for field in fields {
|
for field in fields {
|
||||||
for attr in field.attrs {
|
if _ := field.attrs.find_first('primary') {
|
||||||
if attr.name == 'primary' {
|
|
||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return indexes of any auto-increment fields or fields with default values
|
||||||
|
fn get_auto_field_idxs(fields []ast.StructField) []int {
|
||||||
|
mut ret := []int{}
|
||||||
|
for i, field in fields {
|
||||||
|
for attr in field.attrs {
|
||||||
|
if attr.name == 'default' {
|
||||||
|
ret << i
|
||||||
|
} else if attr.name == 'sql' && attr.arg == 'serial' {
|
||||||
|
ret << i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
|
@ -1186,7 +1186,7 @@ pub fn (mut f Gen) sql_stmt_line(node ast.SqlStmtLine) {
|
||||||
f.write('\t')
|
f.write('\t')
|
||||||
match node.kind {
|
match node.kind {
|
||||||
.insert {
|
.insert {
|
||||||
f.writeln('insert ${node.object_var_name} into ${table_name}')
|
f.writeln('insert ${node.object_var} into ${table_name}')
|
||||||
}
|
}
|
||||||
.update {
|
.update {
|
||||||
f.write('update ${table_name} set ')
|
f.write('update ${table_name} set ')
|
||||||
|
|
|
@ -216,7 +216,7 @@ fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine {
|
||||||
scope: p.scope
|
scope: p.scope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mut inserted_var_name := ''
|
mut inserted_var := ''
|
||||||
mut table_type := ast.Type(0)
|
mut table_type := ast.Type(0)
|
||||||
if kind != .delete {
|
if kind != .delete {
|
||||||
if kind == .update {
|
if kind == .update {
|
||||||
|
@ -224,7 +224,7 @@ fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine {
|
||||||
} else if kind == .insert {
|
} else if kind == .insert {
|
||||||
expr := p.expr(0)
|
expr := p.expr(0)
|
||||||
if expr is ast.Ident {
|
if expr is ast.Ident {
|
||||||
inserted_var_name = expr.name
|
inserted_var = expr.name
|
||||||
} else {
|
} else {
|
||||||
p.error('can only insert variables')
|
p.error('can only insert variables')
|
||||||
return ast.SqlStmtLine{}
|
return ast.SqlStmtLine{}
|
||||||
|
@ -277,7 +277,7 @@ fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine {
|
||||||
typ: table_type
|
typ: table_type
|
||||||
pos: table_pos
|
pos: table_pos
|
||||||
}
|
}
|
||||||
object_var_name: inserted_var_name
|
object_var: inserted_var
|
||||||
pos: pos
|
pos: pos
|
||||||
updated_columns: updated_columns
|
updated_columns: updated_columns
|
||||||
update_exprs: update_exprs
|
update_exprs: update_exprs
|
||||||
|
@ -290,7 +290,7 @@ fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine {
|
||||||
|
|
||||||
fn (mut p Parser) check_sql_keyword(name string) ?bool {
|
fn (mut p Parser) check_sql_keyword(name string) ?bool {
|
||||||
if p.check_name() != name {
|
if p.check_name() != name {
|
||||||
p.error('V ORM: expecting `${name}`')
|
p.error('ORM: expecting `${name}`')
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/parser/tests/orm_no_error_handler.vv:10:7: error: V ORM returns a result, so it should have either an `or {}` block, or `!` at the end
|
vlib/v/parser/tests/orm_no_error_handler.vv:10:7: error: ORM returns a result, so it should have either an `or {}` block, or `!` at the end
|
||||||
8 | db := sqlite.connect(':memory:')!
|
8 | db := sqlite.connect(':memory:')!
|
||||||
9 |
|
9 |
|
||||||
10 | _ := sql db {
|
10 | _ := sql db {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue