From 26db3b0995994cf45eb1ab472690f9d200ece3fc Mon Sep 17 00:00:00 2001 From: Louis Schmieder Date: Fri, 23 Jul 2021 11:33:55 +0200 Subject: [PATCH] orm: redesign orm (re-write it in V) (#10353) --- cmd/tools/vtest-cleancode.v | 1 + cmd/tools/vtest-self.v | 3 + examples/database/orm.v | 12 +- vlib/builtin/builtin.v | 2 +- vlib/mysql/_cdefs.c.v | 4 +- vlib/mysql/_cdefs_nix.c.v | 11 +- vlib/mysql/enums.v | 7 + vlib/mysql/mysql.v | 9 +- vlib/mysql/mysql_orm_test.v | 77 + vlib/mysql/orm.v | 296 +++ vlib/mysql/stmt.c.v | 233 +++ vlib/orm/orm.v | 475 +++++ vlib/orm/orm_fn_test.v | 193 ++ vlib/orm/orm_test.v | 64 +- vlib/sqlite/orm.v | 161 ++ vlib/sqlite/sqlite.v | 32 +- vlib/sqlite/sqlite_orm_test.v | 70 + vlib/sqlite/stmt.v | 74 + vlib/v/ast/ast.v | 18 +- vlib/v/checker/checker.v | 19 + vlib/v/gen/c/cgen.v | 2 +- vlib/v/gen/c/sql.v | 2243 +++++++--------------- vlib/v/tests/orm_sub_array_struct_test.v | 1 - 23 files changed, 2332 insertions(+), 1675 deletions(-) create mode 100644 vlib/mysql/mysql_orm_test.v create mode 100644 vlib/mysql/orm.v create mode 100644 vlib/mysql/stmt.c.v create mode 100644 vlib/orm/orm.v create mode 100644 vlib/orm/orm_fn_test.v create mode 100644 vlib/sqlite/orm.v create mode 100644 vlib/sqlite/sqlite_orm_test.v create mode 100644 vlib/sqlite/stmt.v diff --git a/cmd/tools/vtest-cleancode.v b/cmd/tools/vtest-cleancode.v index 150495bb1..3c849b743 100644 --- a/cmd/tools/vtest-cleancode.v +++ b/cmd/tools/vtest-cleancode.v @@ -28,6 +28,7 @@ const ( 'vlib/gg/m4/graphic.v', 'vlib/gg/m4/m4_test.v', 'vlib/gg/m4/matrix.v', + 'vlib/sqlite/orm.v' /* mut c &int -> mut c int */, 'vlib/builtin/int_test.v' /* special number formatting that should be tested */, // TODOs and unfixed vfmt bugs 'vlib/builtin/int.v' /* TODO byteptr: vfmt converts `pub fn (nn byteptr) str() string {` to `nn &byte` and that conflicts with `nn byte` */, diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v index 3b586a8bb..90df76de2 100644 --- a/cmd/tools/vtest-self.v +++ b/cmd/tools/vtest-self.v @@ -9,6 +9,7 @@ const github_job = os.getenv('GITHUB_JOB') const ( skip_test_files = [ 'vlib/context/deadline_test.v' /* sometimes blocks */, + 'vlib/mysql/mysql_orm_test.v' /* mysql not installed */, ] skip_fsanitize_too_slow = [ // These tests are too slow to be run in the CI on each PR/commit @@ -38,6 +39,7 @@ const ( 'vlib/net/tcp_test.v', 'vlib/orm/orm_test.v', 'vlib/sqlite/sqlite_test.v', + 'vlib/sqlite/sqlite_orm_test.v', 'vlib/v/tests/orm_sub_struct_test.v', 'vlib/v/tests/orm_sub_array_struct_test.v', 'vlib/vweb/tests/vweb_test.v', @@ -72,6 +74,7 @@ const ( 'vlib/net/http/status_test.v', 'vlib/net/websocket/ws_test.v', 'vlib/sqlite/sqlite_test.v', + 'vlib/sqlite/sqlite_orm_test.v', 'vlib/orm/orm_test.v', 'vlib/v/tests/orm_sub_struct_test.v', 'vlib/v/tests/orm_sub_array_struct_test.v', diff --git a/examples/database/orm.v b/examples/database/orm.v index a7db00c16..7a5cd23fd 100644 --- a/examples/database/orm.v +++ b/examples/database/orm.v @@ -1,6 +1,6 @@ import sqlite import mysql -import pg +// import pg [table: 'modules'] struct Module { @@ -33,11 +33,11 @@ struct Child { fn main() { sqlite3_array() mysql_array() - psql_array() + // psql_array() sqlite3() mysql() - psql() + // psql() } fn sqlite3_array() { @@ -118,6 +118,7 @@ fn mysql_array() { db.close() } +/* fn psql_array() { mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or { panic(err) @@ -155,7 +156,7 @@ fn psql_array() { } db.close() -} +}*/ fn sqlite3() { mut db := sqlite.connect(':memory:') or { panic(err) } @@ -224,6 +225,7 @@ fn mysql() { conn.close() } +/* fn psql() { mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or { panic(err) @@ -257,4 +259,4 @@ fn psql() { eprintln(modul) db.close() -} +}*/ diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index ec6ebc813..3fc73fac7 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -121,7 +121,7 @@ pub: typ int } -enum AttributeKind { +pub enum AttributeKind { plain // [name] string // ['name'] number // [123] diff --git a/vlib/mysql/_cdefs.c.v b/vlib/mysql/_cdefs.c.v index fe070236e..23aebed9b 100644 --- a/vlib/mysql/_cdefs.c.v +++ b/vlib/mysql/_cdefs.c.v @@ -72,7 +72,9 @@ fn C.mysql_fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD fn C.mysql_free_result(res &C.MYSQL_RES) -fn C.mysql_real_escape_string_quote(mysql &C.MYSQL, to &byte, from &byte, len u64, quote byte) u64 +fn C.mysql_real_escape_string(mysql &C.MYSQL, to &byte, from &byte, len u64) u64 + +// fn C.mysql_real_escape_string_quote(mysql &C.MYSQL, to &byte, from &byte, len u64, quote byte) u64 (Don't exist in mariadb) fn C.mysql_close(sock &C.MYSQL) diff --git a/vlib/mysql/_cdefs_nix.c.v b/vlib/mysql/_cdefs_nix.c.v index 445aaa4dc..1462e89f9 100644 --- a/vlib/mysql/_cdefs_nix.c.v +++ b/vlib/mysql/_cdefs_nix.c.v @@ -1,4 +1,11 @@ module mysql -#pkgconfig mysqlclient -#include # Please install the mysqlclient development headers +// Need to check if mysqlclient is not there and use mariadb as alternative because newer system doesn't support mysql 8.0 as default + +$if $pkgconfig('mysqlclient') { + #pkgconfig mysqlclient +} $else { + #pkgconfig mariadb +} + +#include # Please install the mysqlclient development headers diff --git a/vlib/mysql/enums.v b/vlib/mysql/enums.v index f0faf56f9..e2bfef564 100644 --- a/vlib/mysql/enums.v +++ b/vlib/mysql/enums.v @@ -69,3 +69,10 @@ pub fn (f FieldType) str() string { .type_geometry { 'geometry' } } } + +pub fn (f FieldType) get_len() u32 { + return match f { + .type_blob { 262140 } + else { 0 } + } +} diff --git a/vlib/mysql/mysql.v b/vlib/mysql/mysql.v index 8a9fbb3f5..90e7008e8 100644 --- a/vlib/mysql/mysql.v +++ b/vlib/mysql/mysql.v @@ -19,6 +19,11 @@ pub enum ConnectionFlag { client_remember_options = C.CLIENT_REMEMBER_OPTIONS } +struct SQLError { + msg string + code int +} + // TODO: Documentation pub struct Connection { mut: @@ -46,7 +51,7 @@ pub fn (mut conn Connection) connect() ?bool { // query - make an SQL query and receive the results. // `query()` cannot be used for statements that contain binary data; // Use `real_query()` instead. -pub fn (mut conn Connection) query(q string) ?Result { +pub fn (conn Connection) query(q string) ?Result { if C.mysql_query(conn.conn, q.str) != 0 { return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) } @@ -127,7 +132,7 @@ pub fn (conn &Connection) tables(wildcard string) ?[]string { pub fn (conn &Connection) escape_string(s string) string { unsafe { to := malloc_noscan(2 * s.len + 1) - C.mysql_real_escape_string_quote(conn.conn, to, s.str, s.len, `\'`) + C.mysql_real_escape_string(conn.conn, to, s.str, s.len) return to.vstring() } } diff --git a/vlib/mysql/mysql_orm_test.v b/vlib/mysql/mysql_orm_test.v new file mode 100644 index 000000000..2acb182b5 --- /dev/null +++ b/vlib/mysql/mysql_orm_test.v @@ -0,0 +1,77 @@ +import orm +import mysql + +fn test_mysql_orm() { + mut mdb := mysql.Connection{ + host: 'localhost' + port: 3306 + username: 'root' + password: '' + dbname: 'mysql' + } + mdb.connect() or { panic(err) } + db := orm.Connection(mdb) + db.create('Test', [ + orm.TableField{ + name: 'id' + typ: 7 + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + kind: .plain + arg: 'serial' + }, + ] + }, + orm.TableField{ + name: 'name' + typ: 18 + attrs: [] + }, + orm.TableField{ + name: 'age' + typ: 7 + }, + ]) or { panic(err) } + + db.insert('Test', orm.QueryData{ + fields: ['name', 'age'] + data: [orm.string_to_primitive('Louis'), orm.int_to_primitive(101)] + }) or { panic(err) } + + res := db.@select(orm.SelectConfig{ + table: 'Test' + has_where: true + fields: ['id', 'name', 'age'] + types: [7, 18, 8] + }, orm.QueryData{}, orm.QueryData{ + fields: ['name', 'age'] + data: [orm.Primitive('Louis'), i64(101)] + types: [18, 8] + is_and: [true, true] + kinds: [.eq, .eq] + }) or { panic(err) } + + id := res[0][0] + name := res[0][1] + age := res[0][2] + + assert id is int + if id is int { + assert id == 1 + } + + assert name is string + if name is string { + assert name == 'Louis' + } + + assert age is i64 + if age is i64 { + assert age == 101 + } +} diff --git a/vlib/mysql/orm.v b/vlib/mysql/orm.v new file mode 100644 index 000000000..c68be6330 --- /dev/null +++ b/vlib/mysql/orm.v @@ -0,0 +1,296 @@ +module mysql + +import orm +import time + +type Prims = byte | f32 | f64 | i16 | i64 | i8 | int | string | u16 | u32 | u64 + +// sql expr + +pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ?[][]orm.Primitive { + query := orm.orm_select_gen(config, '`', false, '?', 0, where) + mut ret := [][]orm.Primitive{} + mut stmt := db.init_stmt(query) + stmt.prepare() ? + + mysql_stmt_binder(mut stmt, where) ? + mysql_stmt_binder(mut stmt, data) ? + if data.data.len > 0 || where.data.len > 0 { + stmt.bind_params() ? + } + + mut status := stmt.execute() ? + num_fields := stmt.get_field_count() + metadata := stmt.gen_metadata() + fields := stmt.fetch_fields(metadata) + + mut dataptr := []Prims{} + + for i in 0 .. num_fields { + f := unsafe { fields[i] } + match FieldType(f.@type) { + .type_tiny { + dataptr << byte(0) + } + .type_short { + dataptr << u16(0) + } + .type_long { + dataptr << u32(0) + } + .type_longlong { + dataptr << u64(0) + } + .type_float { + dataptr << f32(0) + } + .type_double { + dataptr << f64(0) + } + .type_string { + dataptr << '' + } + else { + dataptr << byte(0) + } + } + } + + mut vptr := []&char{} + + for d in dataptr { + vptr << d.get_data_ptr() + } + + unsafe { dataptr.free() } + + lens := []u32{len: int(num_fields), init: 0} + stmt.bind_res(fields, vptr, lens, num_fields) + stmt.bind_result_buffer() ? + stmt.store_result() ? + + mut row := 0 + + for { + status = stmt.fetch_stmt() ? + + if status == 1 || status == 100 { + break + } + row++ + data_list := buffer_to_primitive(vptr, config.types) ? + ret << data_list + } + + stmt.close() ? + + return ret +} + +// sql stmt + +pub fn (db Connection) insert(table string, data orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, data, orm.QueryData{}) + mysql_stmt_worker(db, query, data, orm.QueryData{}) ? +} + +pub fn (db Connection) update(table string, data orm.QueryData, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .update, false, '?', 1, data, where) + mysql_stmt_worker(db, query, data, where) ? +} + +pub fn (db Connection) delete(table string, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .delete, false, '?', 1, orm.QueryData{}, where) + mysql_stmt_worker(db, query, orm.QueryData{}, where) ? +} + +pub fn (db Connection) last_id() orm.Primitive { + query := 'SELECT last_insert_rowid();' + id := db.query(query) or { + Result{ + result: 0 + } + } + return orm.Primitive(id.rows()[0].vals[0].int()) +} + +// table +pub fn (db Connection) create(table string, fields []orm.TableField) ? { + query := orm.orm_table_gen(table, '`', false, 0, fields, mysql_type_from_v, false) or { + return err + } + mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +pub fn (db Connection) drop(table string) ? { + query := 'DROP TABLE `$table`;' + mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +fn mysql_stmt_worker(db Connection, query string, data orm.QueryData, where orm.QueryData) ? { + mut stmt := db.init_stmt(query) + stmt.prepare() ? + mysql_stmt_binder(mut stmt, data) ? + mysql_stmt_binder(mut stmt, where) ? + if data.data.len > 0 || where.data.len > 0 { + stmt.bind_params() ? + } + stmt.execute() ? + stmt.close() ? +} + +fn mysql_stmt_binder(mut stmt Stmt, d orm.QueryData) ? { + for data in d.data { + stmt_binder_match(mut stmt, data) + } +} + +fn stmt_binder_match(mut stmt Stmt, data orm.Primitive) { + match data { + bool { + stmt.bind_bool(&data) + } + i8 { + stmt.bind_i8(&data) + } + i16 { + stmt.bind_i16(&data) + } + int { + stmt.bind_int(&data) + } + i64 { + stmt.bind_i64(&data) + } + byte { + stmt.bind_byte(&data) + } + u16 { + stmt.bind_u16(&data) + } + u32 { + stmt.bind_u32(&data) + } + u64 { + stmt.bind_u64(&data) + } + f32 { + stmt.bind_f32(unsafe { &f32(&data) }) + } + f64 { + stmt.bind_f64(unsafe { &f64(&data) }) + } + string { + stmt.bind_text(data) + } + time.Time { + stmt.bind_int(&int(data.unix)) + } + orm.InfixType { + stmt_binder_match(mut stmt, data.right) + } + } +} + +fn buffer_to_primitive(data_list []&char, types []int) ?[]orm.Primitive { + mut res := []orm.Primitive{} + + for i, data in data_list { + mut primitive := orm.Primitive(0) + match types[i] { + 5 { + primitive = *(&i8(data)) + } + 6 { + primitive = *(&i16(data)) + } + 7, -1 { + primitive = *(&int(data)) + } + 8 { + primitive = *(&i64(data)) + } + 9 { + primitive = *(&byte(data)) + } + 10 { + primitive = *(&u16(data)) + } + 11 { + primitive = *(&u32(data)) + } + 12 { + primitive = *(&u64(data)) + } + 13 { + primitive = *(&f32(data)) + } + 14 { + primitive = *(&f64(data)) + } + 15 { + primitive = *(&bool(data)) + } + orm.string { + primitive = unsafe { cstring_to_vstring(&char(data)) } + } + orm.time { + timestamp := *(&int(data)) + primitive = time.unix(timestamp) + } + else { + return error('Unknown type ${types[i]}') + } + } + res << primitive + } + + return res +} + +fn mysql_type_from_v(typ int) ?string { + str := match typ { + 5, 9, 16 { + 'TINYINT' + } + 6, 10 { + 'SMALLINT' + } + 7, 11 { + 'INT' + } + 8, 12 { + 'BIGINT' + } + 13 { + 'FLOAT' + } + 14 { + 'DOUBLE' + } + orm.string { + 'TEXT' + } + -1 { + 'SERIAL' + } + else { + '' + } + } + if str == '' { + return error('Unknown type $typ') + } + return str +} + +fn (p Prims) get_data_ptr() &char { + return match p { + string { + p.str + } + else { + &char(&p) + } + } +} diff --git a/vlib/mysql/stmt.c.v b/vlib/mysql/stmt.c.v new file mode 100644 index 000000000..ee91746d4 --- /dev/null +++ b/vlib/mysql/stmt.c.v @@ -0,0 +1,233 @@ +module mysql + +[typedef] +struct C.MYSQL_STMT { + mysql &C.MYSQL + stmt_id u32 +} + +[typedef] +struct C.MYSQL_BIND { +mut: + buffer_type int + buffer voidptr + buffer_length u32 + length &u32 +} + +const ( + mysql_type_decimal = C.MYSQL_TYPE_DECIMAL + mysql_type_tiny = C.MYSQL_TYPE_TINY + mysql_type_short = C.MYSQL_TYPE_SHORT + mysql_type_long = C.MYSQL_TYPE_LONG + mysql_type_float = C.MYSQL_TYPE_FLOAT + mysql_type_double = C.MYSQL_TYPE_DOUBLE + mysql_type_null = C.MYSQL_TYPE_NULL + mysql_type_timestamp = C.MYSQL_TYPE_TIMESTAMP + mysql_type_longlong = C.MYSQL_TYPE_LONGLONG + mysql_type_int24 = C.MYSQL_TYPE_INT24 + mysql_type_date = C.MYSQL_TYPE_DATE + mysql_type_time = C.MYSQL_TYPE_TIME + mysql_type_datetime = C.MYSQL_TYPE_DATETIME + mysql_type_year = C.MYSQL_TYPE_YEAR + mysql_type_varchar = C.MYSQL_TYPE_VARCHAR + mysql_type_bit = C.MYSQL_TYPE_BIT + mysql_type_timestamp22 = C.MYSQL_TYPE_TIMESTAMP + mysql_type_json = C.MYSQL_TYPE_JSON + mysql_type_newdecimal = C.MYSQL_TYPE_NEWDECIMAL + mysql_type_enum = C.MYSQL_TYPE_ENUM + mysql_type_set = C.MYSQL_TYPE_SET + mysql_type_tiny_blob = C.MYSQL_TYPE_TINY_BLOB + mysql_type_medium_blob = C.MYSQL_TYPE_MEDIUM_BLOB + mysql_type_long_blob = C.MYSQL_TYPE_LONG_BLOB + mysql_type_blob = C.MYSQL_TYPE_BLOB + mysql_type_var_string = C.MYSQL_TYPE_VAR_STRING + mysql_type_string = C.MYSQL_TYPE_STRING + mysql_type_geometry = C.MYSQL_TYPE_GEOMETRY + mysql_no_data = C.MYSQL_NO_DATA +) + +fn C.mysql_stmt_init(&C.MYSQL) &C.MYSQL_STMT +fn C.mysql_stmt_prepare(&C.MYSQL_STMT, &char, u32) int +fn C.mysql_stmt_bind_param(&C.MYSQL_STMT, &C.MYSQL_BIND) bool +fn C.mysql_stmt_execute(&C.MYSQL_STMT) int +fn C.mysql_stmt_close(&C.MYSQL_STMT) bool +fn C.mysql_stmt_free_result(&C.MYSQL_STMT) bool +fn C.mysql_stmt_error(&C.MYSQL_STMT) &char +fn C.mysql_stmt_result_metadata(&C.MYSQL_STMT) &C.MYSQL_RES + +fn C.mysql_stmt_field_count(&C.MYSQL_STMT) u16 +fn C.mysql_stmt_bind_result(&C.MYSQL_STMT, &C.MYSQL_BIND) bool +fn C.mysql_stmt_fetch(&C.MYSQL_STMT) int +fn C.mysql_stmt_next_result(&C.MYSQL_STMT) int +fn C.mysql_stmt_store_result(&C.MYSQL_STMT) int + +struct Stmt { + stmt &C.MYSQL_STMT = &C.MYSQL_STMT(0) + query string +mut: + binds []C.MYSQL_BIND + res []C.MYSQL_BIND +} + +pub fn (db Connection) init_stmt(query string) Stmt { + return Stmt{ + stmt: C.mysql_stmt_init(db.conn) + query: query + binds: []C.MYSQL_BIND{} + } +} + +pub fn (stmt Stmt) prepare() ? { + res := C.mysql_stmt_prepare(stmt.stmt, stmt.query.str, stmt.query.len) + if res != 0 && stmt.get_error_msg() != '' { + return stmt.error(res) + } +} + +pub fn (stmt Stmt) bind_params() ? { + res := C.mysql_stmt_bind_param(stmt.stmt, &C.MYSQL_BIND(stmt.binds.data)) + if res && stmt.get_error_msg() != '' { + return stmt.error(1) + } +} + +pub fn (stmt Stmt) execute() ?int { + res := C.mysql_stmt_execute(stmt.stmt) + if res != 0 && stmt.get_error_msg() != '' { + return stmt.error(res) + } + return res +} + +pub fn (stmt Stmt) next() ?int { + res := C.mysql_stmt_next_result(stmt.stmt) + if res > 0 && stmt.get_error_msg() != '' { + return stmt.error(res) + } + return res +} + +pub fn (stmt Stmt) gen_metadata() &C.MYSQL_RES { + return C.mysql_stmt_result_metadata(stmt.stmt) +} + +pub fn (stmt Stmt) fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD { + return C.mysql_fetch_fields(res) +} + +pub fn (stmt Stmt) fetch_stmt() ?int { + res := C.mysql_stmt_fetch(stmt.stmt) + if res !in [0, 100] && stmt.get_error_msg() != '' { + return stmt.error(res) + } + return res +} + +pub fn (stmt Stmt) close() ? { + if !C.mysql_stmt_close(stmt.stmt) && stmt.get_error_msg() != '' { + return stmt.error(1) + } + if !C.mysql_stmt_free_result(stmt.stmt) && stmt.get_error_msg() != '' { + return stmt.error(1) + } +} + +fn (stmt Stmt) get_error_msg() string { + return unsafe { cstring_to_vstring(&char(C.mysql_stmt_error(stmt.stmt))) } +} + +pub fn (stmt Stmt) error(code int) IError { + msg := stmt.get_error_msg() + return IError(&SQLError{ + msg: '$msg ($code) ($stmt.query)' + code: code + }) +} + +fn (stmt Stmt) get_field_count() u16 { + return C.mysql_stmt_field_count(stmt.stmt) +} + +pub fn (mut stmt Stmt) bind_bool(b &bool) { + stmt.bind(mysql.mysql_type_tiny, b, 0) +} + +pub fn (mut stmt Stmt) bind_byte(b &byte) { + stmt.bind(mysql.mysql_type_tiny, b, 0) +} + +pub fn (mut stmt Stmt) bind_i8(b &i8) { + stmt.bind(mysql.mysql_type_tiny, b, 0) +} + +pub fn (mut stmt Stmt) bind_i16(b &i16) { + stmt.bind(mysql.mysql_type_short, b, 0) +} + +pub fn (mut stmt Stmt) bind_u16(b &u16) { + stmt.bind(mysql.mysql_type_short, b, 0) +} + +pub fn (mut stmt Stmt) bind_int(b &int) { + stmt.bind(mysql.mysql_type_long, b, 0) +} + +pub fn (mut stmt Stmt) bind_u32(b &u32) { + stmt.bind(mysql.mysql_type_long, b, 0) +} + +pub fn (mut stmt Stmt) bind_i64(b &i64) { + stmt.bind(mysql.mysql_type_longlong, b, 0) +} + +pub fn (mut stmt Stmt) bind_u64(b &u64) { + stmt.bind(mysql.mysql_type_longlong, b, 0) +} + +pub fn (mut stmt Stmt) bind_f32(b &f32) { + stmt.bind(mysql.mysql_type_float, b, 0) +} + +pub fn (mut stmt Stmt) bind_f64(b &f64) { + stmt.bind(mysql.mysql_type_double, b, 0) +} + +pub fn (mut stmt Stmt) bind_text(b string) { + stmt.bind(mysql.mysql_type_string, b.str, u32(b.len)) +} + +pub fn (mut stmt Stmt) bind(typ int, buffer voidptr, buf_len u32) { + stmt.binds << C.MYSQL_BIND{ + buffer_type: typ + buffer: buffer + buffer_length: buf_len + length: 0 + } +} + +pub fn (mut stmt Stmt) bind_res(fields &C.MYSQL_FIELD, dataptr []&char, lens []u32, num_fields int) { + for i in 0 .. num_fields { + len := FieldType(unsafe { fields[i].@type }).get_len() + stmt.res << C.MYSQL_BIND{ + buffer_type: unsafe { fields[i].@type } + buffer: dataptr[i] + length: &lens[i] + buffer_length: &len + } + } +} + +pub fn (mut stmt Stmt) bind_result_buffer() ? { + res := C.mysql_stmt_bind_result(stmt.stmt, &C.MYSQL_BIND(stmt.res.data)) + if res && stmt.get_error_msg() != '' { + return stmt.error(1) + } +} + +pub fn (mut stmt Stmt) store_result() ? { + res := C.mysql_stmt_store_result(stmt.stmt) + if res != 0 && stmt.get_error_msg() != '' { + return stmt.error(res) + } +} diff --git a/vlib/orm/orm.v b/vlib/orm/orm.v new file mode 100644 index 000000000..e86696096 --- /dev/null +++ b/vlib/orm/orm.v @@ -0,0 +1,475 @@ +module orm + +import time + +pub const ( + num64 = [8, 12] + nums = [5, 6, 7, 9, 10, 11, 16] + float = [13, 14] + string = 18 + time = -2 + type_idx = map{ + 'i8': 5 + 'i16': 6 + 'int': 7 + 'i64': 8 + 'byte': 9 + 'u16': 10 + 'u32': 11 + 'u64': 12 + 'f32': 13 + 'f64': 14 + 'bool': 16 + 'string': 18 + } + string_max_len = 2048 +) + +pub type Primitive = InfixType | bool | byte | f32 | f64 | i16 | i64 | i8 | int | string | + time.Time | u16 | u32 | u64 + +pub enum OperationKind { + neq // != + eq // == + gt // > + lt // < + ge // >= + le // <= +} + +pub enum MathOperationKind { + add // + + sub // - + mul // * + div // / +} + +pub enum StmtKind { + insert + update + delete +} + +pub enum OrderType { + asc + desc +} + +fn (kind OperationKind) to_str() string { + str := match kind { + .neq { '!=' } + .eq { '=' } + .gt { '>' } + .lt { '<' } + .ge { '>=' } + .le { '<=' } + } + return str +} + +fn (kind OrderType) to_str() string { + return match kind { + .desc { + 'DESC' + } + .asc { + 'ASC' + } + } +} + +pub struct QueryData { +pub: + fields []string + data []Primitive + types []int + kinds []OperationKind + is_and []bool +} + +pub struct InfixType { +pub: + name string + operator MathOperationKind + right Primitive +} + +pub struct TableField { +pub: + name string + typ int + is_time bool + default_val string + is_arr bool + attrs []StructAttribute +} + +pub struct SelectConfig { +pub: + table string + is_count bool + has_where bool + has_order bool + order string + order_type OrderType + has_limit bool + primary string = 'id' // should be set if primary is different than 'id' and 'has_limit' is false + has_offset bool + fields []string + types []int +} + +pub interface Connection { + @select(config SelectConfig, data QueryData, where QueryData) ?[][]Primitive + insert(table string, data QueryData) ? + update(table string, data QueryData, where QueryData) ? + delete(table string, where QueryData) ? + create(table string, fields []TableField) ? + drop(talbe string) ? + last_id() Primitive +} + +pub fn orm_stmt_gen(table string, para string, kind StmtKind, num bool, qm string, start_pos int, data QueryData, where QueryData) string { + mut str := '' + + mut c := start_pos + + match kind { + .insert { + str += 'INSERT INTO $para$table$para (' + for i, field in data.fields { + str += '$para$field$para' + if i < data.fields.len - 1 { + str += ', ' + } + } + str += ') VALUES (' + for i, _ in data.fields { + str += qm + if num { + str += '$c' + c++ + } + if i < data.fields.len - 1 { + str += ', ' + } + } + str += ')' + } + .update { + str += 'UPDATE $para$table$para SET ' + for i, field in data.fields { + str += '$para$field$para = ' + if data.data.len > i { + d := data.data[i] + if d is InfixType { + op := match d.operator { + .add { + '+' + } + .sub { + '-' + } + .mul { + '*' + } + .div { + '/' + } + } + str += '$d.name $op $qm' + } else { + str += '$qm' + } + } else { + str += '$qm' + } + if num { + str += '$c' + c++ + } + if i < data.fields.len - 1 { + str += ', ' + } + } + str += ' WHERE ' + } + .delete { + str += 'DELETE FROM $para$table$para WHERE ' + } + } + if kind == .update || kind == .delete { + for i, field in where.fields { + str += '$para$field$para ${where.kinds[i].to_str()} $qm' + if num { + str += '$c' + c++ + } + if i < where.fields.len - 1 { + str += ' AND ' + } + } + } + str += ';' + return str +} + +pub fn orm_select_gen(orm SelectConfig, para string, num bool, qm string, start_pos int, where QueryData) string { + mut str := 'SELECT ' + + if orm.is_count { + str += 'COUNT(*)' + } else { + for i, field in orm.fields { + str += '$para$field$para' + if i < orm.fields.len - 1 { + str += ', ' + } + } + } + + str += ' FROM $para$orm.table$para' + + mut c := start_pos + + if orm.has_where { + str += ' WHERE ' + for i, field in where.fields { + str += '$para$field$para ${where.kinds[i].to_str()} $qm' + if num { + str += '$c' + c++ + } + if i < where.fields.len - 1 { + if where.is_and[i] { + str += ' AND ' + } else { + str += ' OR ' + } + } + } + } + + str += ' ORDER BY ' + if orm.has_order { + str += '$para$orm.order$para ' + str += orm.order_type.to_str() + } else { + str += '$para$orm.primary$para ' + str += orm.order_type.to_str() + } + + if orm.has_limit { + str += ' LIMIT ?' + if num { + str += '$c' + c++ + } + } + + if orm.has_offset { + str += ' OFFSET ?' + if num { + str += '$c' + c++ + } + } + + str += ';' + return str +} + +pub fn orm_table_gen(table string, para string, defaults bool, def_unique_len int, fields []TableField, sql_from_v fn (int) ?string, alternative bool) ?string { + mut str := 'CREATE TABLE IF NOT EXISTS $para$table$para (' + + if alternative { + str = 'IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=$para$table$para and xtype=${para}U$para) CREATE TABLE $para$table$para (' + } + + mut fs := []string{} + mut unique_fields := []string{} + mut unique := map[string][]string{} + mut primary := '' + + for field in fields { + if field.is_arr { + continue + } + mut no_null := false + mut is_unique := false + mut is_skip := false + mut unique_len := 0 + // mut fkey := '' + for attr in field.attrs { + match attr.name { + 'primary' { + primary = field.name + } + 'unique' { + if attr.arg != '' { + if attr.kind == .string { + unique[attr.arg] << field.name + continue + } else if attr.kind == .number { + unique_len = attr.arg.int() + is_unique = true + continue + } + } + is_unique = true + } + 'nonull' { + no_null = true + } + 'skip' { + is_skip = true + } + /*'fkey' { + if attr.arg != '' { + if attr.kind == .string { + fkey = attr.arg + continue + } + } + }*/ + else {} + } + } + if is_skip { + continue + } + mut stmt := '' + mut field_name := sql_field_name(field) + mut ctyp := sql_from_v(sql_field_type(field)) or { + field_name = '${field_name}_id' + sql_from_v(8) ? + } + if ctyp == '' { + return error('Unknown type ($field.typ) for field $field.name in struct $table') + } + stmt = '$para$field_name$para $ctyp' + if defaults && field.default_val != '' { + stmt += ' DEFAULT $field.default_val' + } + if no_null { + stmt += ' NOT NULL' + } + if is_unique { + mut f := 'UNIQUE KEY($para$field.name$para' + if ctyp == 'TEXT' && def_unique_len > 0 { + if unique_len > 0 { + f += '($unique_len)' + } else { + f += '($def_unique_len)' + } + } + f += ')' + unique_fields << f + } + fs << stmt + } + if primary == '' { + return error('A primary key is required for $table') + } + if unique.len > 0 { + for k, v in unique { + mut tmp := []string{} + for f in v { + tmp << '$para$f$para' + } + fs << '/* $k */UNIQUE(${tmp.join(', ')})' + } + } + fs << 'PRIMARY KEY($para$primary$para)' + fs << unique_fields + str += fs.join(', ') + str += ');' + return str +} + +fn sql_field_type(field TableField) int { + mut typ := field.typ + if field.is_time { + return -2 + } + for attr in field.attrs { + if attr.kind == .plain && attr.name == 'sql' && attr.arg != '' { + if attr.arg.to_lower() == 'serial' { + typ = -1 + break + } + typ = orm.type_idx[attr.arg] + break + } + } + return typ +} + +fn sql_field_name(field TableField) string { + mut name := field.name + for attr in field.attrs { + if attr.name == 'sql' && attr.has_arg && attr.kind == .string { + name = attr.arg + break + } + } + return name +} + +// needed for backend functions + +pub fn bool_to_primitive(b bool) Primitive { + return Primitive(b) +} + +pub fn f32_to_primitive(b f32) Primitive { + return Primitive(b) +} + +pub fn f64_to_primitive(b f64) Primitive { + return Primitive(b) +} + +pub fn i8_to_primitive(b i8) Primitive { + return Primitive(b) +} + +pub fn i16_to_primitive(b i16) Primitive { + return Primitive(b) +} + +pub fn int_to_primitive(b int) Primitive { + return Primitive(b) +} + +pub fn i64_to_primitive(b i64) Primitive { + return Primitive(b) +} + +pub fn byte_to_primitive(b byte) Primitive { + return Primitive(b) +} + +pub fn u16_to_primitive(b u16) Primitive { + return Primitive(b) +} + +pub fn u32_to_primitive(b u32) Primitive { + return Primitive(b) +} + +pub fn u64_to_primitive(b u64) Primitive { + return Primitive(b) +} + +pub fn string_to_primitive(b string) Primitive { + return Primitive(b) +} + +pub fn time_to_primitive(b time.Time) Primitive { + return Primitive(b) +} + +pub fn infix_to_primitive(b InfixType) Primitive { + return Primitive(b) +} diff --git a/vlib/orm/orm_fn_test.v b/vlib/orm/orm_fn_test.v new file mode 100644 index 000000000..d74a0cb01 --- /dev/null +++ b/vlib/orm/orm_fn_test.v @@ -0,0 +1,193 @@ +import orm + +fn test_orm_stmt_gen_update() { + query := orm.orm_stmt_gen('Test', "'", .update, true, '?', 0, orm.QueryData{ + fields: ['test', 'a'] + data: [] + types: [] + kinds: [] + }, orm.QueryData{ + fields: ['id', 'name'] + data: [] + types: [] + kinds: [.ge, .eq] + }) + assert query == "UPDATE 'Test' SET 'test' = ?0, 'a' = ?1 WHERE 'id' >= ?2 AND 'name' = ?3;" +} + +fn test_orm_stmt_gen_insert() { + query := orm.orm_stmt_gen('Test', "'", .insert, true, '?', 0, orm.QueryData{ + fields: ['test', 'a'] + data: [] + types: [] + kinds: [] + }, orm.QueryData{}) + assert query == "INSERT INTO 'Test' ('test', 'a') VALUES (?0, ?1);" +} + +fn test_orm_stmt_gen_delete() { + query := orm.orm_stmt_gen('Test', "'", .delete, true, '?', 0, orm.QueryData{ + fields: ['test', 'a'] + data: [] + types: [] + kinds: [] + }, orm.QueryData{ + fields: ['id', 'name'] + data: [] + types: [] + kinds: [.ge, .eq] + }) + assert query == "DELETE FROM 'Test' WHERE 'id' >= ?0 AND 'name' = ?1;" +} + +fn get_select_fields() []string { + return ['id', 'test', 'abc'] +} + +fn test_orm_select_gen() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY 'id' ASC;" +} + +fn test_orm_select_gen_with_limit() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_limit: true + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY 'id' ASC LIMIT ?0;" +} + +fn test_orm_select_gen_with_where() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_where: true + }, "'", true, '?', 0, orm.QueryData{ + fields: ['abc', 'test'] + kinds: [.eq, .gt] + is_and: [true] + }) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' WHERE 'abc' = ?0 AND 'test' > ?1 ORDER BY 'id' ASC;" +} + +fn test_orm_select_gen_with_order() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_order: true + order_type: .desc + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY '' DESC;" +} + +fn test_orm_select_gen_with_offset() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_offset: true + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY 'id' ASC OFFSET ?0;" +} + +fn test_orm_select_gen_with_all() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_limit: true + has_order: true + order_type: .desc + has_offset: true + has_where: true + }, "'", true, '?', 0, orm.QueryData{ + fields: ['abc', 'test'] + kinds: [.eq, .gt] + is_and: [true] + }) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' WHERE 'abc' = ?0 AND 'test' > ?1 ORDER BY '' DESC LIMIT ?2 OFFSET ?3;" +} + +fn test_orm_table_gen() { + query := orm.orm_table_gen('test_table', "'", true, 0, [ + orm.TableField{ + name: 'id' + typ: 7 + default_val: '10' + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + arg: 'serial' + kind: .plain + }, + ] + }, + orm.TableField{ + name: 'test' + typ: 18 + }, + orm.TableField{ + name: 'abc' + typ: 8 + default_val: '6754' + }, + ], sql_type_from_v, false) or { panic(err) } + assert query == "CREATE TABLE IF NOT EXISTS 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT, 'abc' INT64 DEFAULT 6754, PRIMARY KEY('id'));" + + alt_query := orm.orm_table_gen('test_table', "'", true, 0, [ + orm.TableField{ + name: 'id' + typ: 7 + default_val: '10' + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + arg: 'serial' + kind: .plain + }, + ] + }, + orm.TableField{ + name: 'test' + typ: 18 + }, + orm.TableField{ + name: 'abc' + typ: 8 + default_val: '6754' + }, + ], sql_type_from_v, true) or { panic(err) } + assert alt_query == "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='test_table' and xtype='U') CREATE TABLE 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT, 'abc' INT64 DEFAULT 6754, PRIMARY KEY('id'));" +} + +fn sql_type_from_v(typ int) ?string { + return if typ in orm.nums { + 'INT' + } else if typ in orm.num64 { + 'INT64' + } else if typ in orm.float { + 'DOUBLE' + } else if typ == orm.string { + 'TEXT' + } else if typ == -1 { + 'SERIAL' + } else { + error('Unknown type $typ') + } +} diff --git a/vlib/orm/orm_test.v b/vlib/orm/orm_test.v index 020fd584b..5be8fe274 100644 --- a/vlib/orm/orm_test.v +++ b/vlib/orm/orm_test.v @@ -4,9 +4,10 @@ import sqlite struct Module { - id int + id int [primary; sql: serial] name string nr_downloads int + user User } [table: 'userlist'] @@ -260,57 +261,30 @@ fn test_orm_sqlite() { select from User where id == 5 } assert null_user.name == '' -} -fn test_orm_pg() { - /* - dbname := os.getenv('VDB_NAME') - dbuser := os.getenv('VDB_USER') - if dbname == '' || dbuser == '' { - eprintln(term.red('NB: this test requires VDB_NAME and VDB_USER env variables to be set')) - return - } - db := pg.connect(dbname: dbname, user: dbuser) or { panic(err) } - _ = db - nr_modules := db.select count from modules - //nr_modules := db.select count from Modules where id == 1 - nr_modules := db.select count from Modules where - name == 'Bob' && id == 1 - println(nr_modules) + age_test := sql db { + select from User where id == 1 + } - mod := db.select from Modules where id = 1 limit 1 - println(mod) + assert age_test.age == 29 - mods := db.select from Modules limit 10 - for mod in mods { - println(mod) + sql db { + update User set age = age + 1 where id == 1 } - */ - /* - mod := db.retrieve(1) - mod := db.select from Module where id = 1 - - mod := db.update Module set name = name + '!' where id > 10 + mut first := sql db { + select from User where id == 1 + } - nr_modules := db.select count from Modules - where id > 1 && name == '' - println(nr_modules) + assert first.age == 30 - nr_modules := db.select count from modules - nr_modules := db.select from modules - nr_modules := db[:modules].select - */ - /* - mod := select from db.modules where id = 1 limit 1 - println(mod.name) - top_mods := select from db.modules where nr_downloads > 1000 order by nr_downloads desc limit 10 - top_mods := db.select from modules where nr_downloads > 1000 order by nr_downloads desc limit 10 - top_mods := db.select(m => m.nr_downloads > 1000).order_by(m => m.nr_downloads).desc().limit(10) - names := select name from db.modules // []string + sql db { + update User set age = age * 2 where id == 1 + } + first = sql db { + select from User where id == 1 + } - n := db.q_int('select count(*) from modules') - println(n) - */ + assert first.age == 60 } diff --git a/vlib/sqlite/orm.v b/vlib/sqlite/orm.v new file mode 100644 index 000000000..a1df1c779 --- /dev/null +++ b/vlib/sqlite/orm.v @@ -0,0 +1,161 @@ +module sqlite + +import orm +import time + +// sql expr + +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) + stmt := db.new_init_stmt(query) ? + mut c := 1 + sqlite_stmt_binder(stmt, where, query, mut c) ? + sqlite_stmt_binder(stmt, data, query, mut c) ? + + defer { + stmt.finalize() + } + + mut ret := [][]orm.Primitive{} + + if config.is_count { + step := stmt.step() + if step !in [sqlite_row, sqlite_ok, sqlite_done] { + return db.error_message(step, query) + } + count := stmt.sqlite_select_column(0, 8) ? + ret << [count] + return ret + } + for { + step := stmt.step() + if step == sqlite_done { + break + } + if step != sqlite_ok && step != sqlite_row { + break + } + mut row := []orm.Primitive{} + for i, typ in config.types { + primitive := stmt.sqlite_select_column(i, typ) ? + row << primitive + } + ret << row + } + return ret +} + +// sql stmt + +pub fn (db DB) insert(table string, data orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .insert, true, '?', 1, data, orm.QueryData{}) + sqlite_stmt_worker(db, query, data, orm.QueryData{}) ? +} + +pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .update, true, '?', 1, data, where) + sqlite_stmt_worker(db, query, data, where) ? +} + +pub fn (db DB) delete(table string, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .delete, true, '?', 1, orm.QueryData{}, where) + sqlite_stmt_worker(db, query, orm.QueryData{}, where) ? +} + +pub fn (db DB) last_id() orm.Primitive { + query := 'SELECT last_insert_rowid();' + id := db.q_int(query) + return orm.Primitive(id) +} + +// table +pub fn (db DB) create(table string, fields []orm.TableField) ? { + query := orm.orm_table_gen(table, '`', true, 0, fields, sqlite_type_from_v, false) or { + return err + } + sqlite_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +pub fn (db DB) drop(table string) ? { + query := 'DROP TABLE `$table`;' + sqlite_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +// helper + +fn sqlite_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ? { + stmt := db.new_init_stmt(query) ? + mut c := 1 + sqlite_stmt_binder(stmt, data, query, mut c) ? + sqlite_stmt_binder(stmt, where, query, mut c) ? + stmt.orm_step(query) ? + stmt.finalize() +} + +fn sqlite_stmt_binder(stmt Stmt, d orm.QueryData, query string, mut c &int) ? { + for data in d.data { + err := bind(stmt, c, data) + + if err != 0 { + return stmt.db.error_message(err, query) + } + c++ + } +} + +fn bind(stmt Stmt, c &int, data orm.Primitive) int { + mut err := 0 + match data { + i8, i16, int, byte, u16, u32, bool { + err = stmt.bind_int(c, int(data)) + } + i64, u64 { + err = stmt.bind_i64(c, i64(data)) + } + f32, f64 { + err = stmt.bind_f64(c, unsafe { *(&f64(&data)) }) + } + string { + err = stmt.bind_text(c, data) + } + time.Time { + err = stmt.bind_int(c, int(data.unix)) + } + orm.InfixType { + err = bind(stmt, c, data.right) + } + } + return err +} + +fn (stmt Stmt) sqlite_select_column(idx int, typ int) ?orm.Primitive { + mut primitive := orm.Primitive(0) + + if typ in orm.nums || typ == -1 { + primitive = stmt.get_int(idx) + } else if typ in orm.num64 { + primitive = stmt.get_i64(idx) + } else if typ in orm.float { + primitive = stmt.get_f64(idx) + } else if typ == orm.string { + primitive = stmt.get_text(idx).clone() + } else if typ == orm.time { + primitive = time.unix(stmt.get_int(idx)) + } else { + return error('Unknown type $typ') + } + + return primitive +} + +fn sqlite_type_from_v(typ int) ?string { + return if typ in orm.nums || typ < 0 || typ in orm.num64 { + 'INTEGER' + } else if typ in orm.float { + 'REAL' + } else if typ == orm.string { + 'TEXT' + } else { + error('Unknown type $typ') + } +} diff --git a/vlib/sqlite/sqlite.v b/vlib/sqlite/sqlite.v index 0704e61f6..4bbe40e09 100644 --- a/vlib/sqlite/sqlite.v +++ b/vlib/sqlite/sqlite.v @@ -14,12 +14,24 @@ $if windows { #include "sqlite3.h" +const ( + sqlite_ok = 0 + sqlite_error = 1 + sqlite_row = 100 + sqlite_done = 101 +) + struct C.sqlite3 { } struct C.sqlite3_stmt { } +struct Stmt { + stmt &C.sqlite3_stmt + db &DB +} + struct SQLError { msg string code int @@ -72,6 +84,8 @@ fn C.sqlite3_column_count(&C.sqlite3_stmt) int // fn C.sqlite3_errstr(int) &char +fn C.sqlite3_errmsg(&C.sqlite3) &char + fn C.sqlite3_free(voidptr) // connect Opens the connection with a database. @@ -106,14 +120,6 @@ pub fn (mut db DB) close() ?bool { return true // successfully closed } -// Only for V ORM -fn (db DB) init_stmt(query string) &C.sqlite3_stmt { - // println('init_stmt("$query")') - stmt := &C.sqlite3_stmt(0) - C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) - return stmt -} - // Only for V ORM fn get_int_from_stmt(stmt &C.sqlite3_stmt) int { x := C.sqlite3_step(stmt) @@ -204,6 +210,14 @@ pub fn (db DB) exec_one(query string) ?Row { return rows[0] } +pub fn (db DB) error_message(code int, query string) IError { + msg := unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db.conn))) } + return IError(&SQLError{ + msg: '$msg ($code) ($query)' + code: code + }) +} + // In case you don't expect any result, but still want an error code // e.g. INSERT INTO ... VALUES (...) pub fn (db DB) exec_none(query string) int { @@ -216,8 +230,6 @@ TODO pub fn (db DB) exec_param(query string, param string) []Row { } */ -pub fn (db DB) insert(x T) { -} pub fn (db DB) create_table(table_name string, columns []string) { db.exec('create table if not exists $table_name (' + columns.join(',\n') + ')') diff --git a/vlib/sqlite/sqlite_orm_test.v b/vlib/sqlite/sqlite_orm_test.v new file mode 100644 index 000000000..efbd9fe4b --- /dev/null +++ b/vlib/sqlite/sqlite_orm_test.v @@ -0,0 +1,70 @@ +import orm +import sqlite + +fn test_sqlite_orm() { + sdb := sqlite.connect(':memory:') or { panic(err) } + db := orm.Connection(sdb) + db.create('Test', [ + orm.TableField{ + name: 'id' + typ: 7 + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + kind: .plain + arg: 'serial' + }, + ] + }, + orm.TableField{ + name: 'name' + typ: 18 + attrs: [] + }, + orm.TableField{ + name: 'age' + typ: 8 + }, + ]) or { panic(err) } + + db.insert('Test', orm.QueryData{ + fields: ['name', 'age'] + data: [orm.string_to_primitive('Louis'), orm.i64_to_primitive(100)] + }) or { panic(err) } + + res := db.@select(orm.SelectConfig{ + table: 'Test' + has_where: true + fields: ['id', 'name', 'age'] + types: [7, 18, 8] + }, orm.QueryData{}, orm.QueryData{ + fields: ['name', 'age'] + data: [orm.Primitive('Louis'), i64(100)] + types: [18, 8] + is_and: [true, true] + kinds: [.eq, .eq] + }) or { panic(err) } + + id := res[0][0] + name := res[0][1] + age := res[0][2] + + assert id is int + if id is int { + assert id == 1 + } + + assert name is string + if name is string { + assert name == 'Louis' + } + + assert age is i64 + if age is i64 { + assert age == 100 + } +} diff --git a/vlib/sqlite/stmt.v b/vlib/sqlite/stmt.v new file mode 100644 index 000000000..da07e821b --- /dev/null +++ b/vlib/sqlite/stmt.v @@ -0,0 +1,74 @@ +module sqlite + +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_int64(&C.sqlite3_stmt, int, i64) int +fn C.sqlite3_bind_text(&C.sqlite3_stmt, int, &char, int, voidptr) int + +// Only for V ORM +fn (db DB) init_stmt(query string) (&C.sqlite3_stmt, int) { + // println('init_stmt("$query")') + stmt := &C.sqlite3_stmt(0) + err := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) + return stmt, err +} + +fn (db DB) new_init_stmt(query string) ?Stmt { + stmt, err := db.init_stmt(query) + if err != sqlite_ok { + return db.error_message(err, query) + } + return Stmt{stmt, unsafe { &db }} +} + +fn (stmt Stmt) bind_int(idx int, v int) int { + return C.sqlite3_bind_int(stmt.stmt, idx, v) +} + +fn (stmt Stmt) bind_i64(idx int, v i64) int { + return C.sqlite3_bind_int64(stmt.stmt, idx, v) +} + +fn (stmt Stmt) bind_f64(idx int, v f64) int { + return C.sqlite3_bind_double(stmt.stmt, idx, v) +} + +fn (stmt Stmt) bind_text(idx int, s string) int { + return C.sqlite3_bind_text(stmt.stmt, idx, voidptr(s.str), s.len, 0) +} + +fn (stmt Stmt) get_int(idx int) int { + return C.sqlite3_column_int(stmt.stmt, idx) +} + +fn (stmt Stmt) get_i64(idx int) i64 { + 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 { + b := &char(C.sqlite3_column_text(stmt.stmt, idx)) + return unsafe { b.vstring() } +} + +fn (stmt Stmt) get_count() int { + return C.sqlite3_column_count(stmt.stmt) +} + +fn (stmt Stmt) step() int { + return C.sqlite3_step(stmt.stmt) +} + +fn (stmt Stmt) orm_step(query string) ? { + res := stmt.step() + if res != sqlite_ok && res != sqlite_done && res != sqlite_row { + return stmt.db.error_message(res, query) + } +} + +fn (stmt Stmt) finalize() { + C.sqlite3_finalize(stmt.stmt) +} diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 7692a4057..04a5f851c 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1479,16 +1479,16 @@ pub mut: pub struct SqlStmtLine { pub: - kind SqlStmtKind - object_var_name string // `user` - pos token.Position - where_expr Expr - updated_columns []string // for `update set x=y` - update_exprs []Expr // for `update` + kind SqlStmtKind + pos token.Position + where_expr Expr + update_exprs []Expr // for `update` pub mut: - table_expr TypeNode - fields []StructField - sub_structs map[int]SqlStmtLine + object_var_name string // `user` + updated_columns []string // for `update set x=y` + table_expr TypeNode + fields []StructField + sub_structs map[int]SqlStmtLine } pub struct SqlExpr { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index e817eaa8b..3eaa86f0d 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -7655,6 +7655,10 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { } node.fields = fields node.sub_structs = sub_structs.move() + for i, column in node.updated_columns { + field := node.fields.filter(it.name == column)[0] + node.updated_columns[i] = c.fetch_field_name(field) + } if node.kind == .update { for expr in node.update_exprs { c.expr(expr) @@ -7683,6 +7687,21 @@ fn (mut c Checker) fetch_and_verify_orm_fields(info ast.Struct, pos token.Positi return fields } +fn (mut c Checker) fetch_field_name(field ast.StructField) string { + mut name := field.name + for attr in field.attrs { + if attr.kind == .string && attr.name == 'sql' && attr.arg != '' { + name = attr.arg + break + } + } + sym := c.table.get_type_symbol(field.typ) + if sym.kind == .struct_ { + name = '${name}_id' + } + return name +} + fn (mut c Checker) post_process_generic_fns() { // Loop thru each generic function concrete type. // Check each specific fn instantiation. diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 8f6450a72..8d4bedca2 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3454,7 +3454,7 @@ fn (mut g Gen) expr(node ast.Expr) { g.write('/*OffsetOf*/ (u32)(__offsetof(${util.no_dots(styp)}, $node.field))') } ast.SqlExpr { - g.sql_select_expr(node, false, '') + g.sql_select_expr(node) } ast.StringLiteral { g.string_literal(node) diff --git a/vlib/v/gen/c/sql.v b/vlib/v/gen/c/sql.v index 31879a32b..b7bd42bca 100644 --- a/vlib/v/gen/c/sql.v +++ b/vlib/v/gen/c/sql.v @@ -3,15 +3,8 @@ module c import v.ast -import strings import v.util -// pg,mysql etc -const ( - dbtype = 'sqlite' - default_unique_str_len = 256 -) - enum SqlExprSide { left right @@ -26,1667 +19,731 @@ enum SqlType { } fn (mut g Gen) sql_stmt(node ast.SqlStmt) { - for line in node.lines { - g.sql_stmt_line(line, node.db_expr) - } -} - -fn (mut g Gen) sql_stmt_line(node ast.SqlStmtLine, expr ast.Expr) { - if node.kind == .create { - g.sql_create_table(node, expr) - return - } else if node.kind == .drop { - g.sql_drop_table(node, expr) - return - } - g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name - typ := g.parse_db_type(expr) - match typ { - .sqlite3 { - g.sqlite3_stmt(node, typ, expr) - } - .mysql { - g.mysql_stmt(node, typ, expr) - } - .psql { - g.psql_stmt(node, typ, expr) - } - else { - verror('This database type `$typ` is not implemented yet in orm') // TODO add better error - } - } -} - -fn (mut g Gen) sql_create_table(node ast.SqlStmtLine, expr ast.Expr) { - typ := g.parse_db_type(expr) - match typ { - .sqlite3 { - g.sqlite3_create_table(node, typ, expr) - } - .mysql { - g.mysql_create_table(node, typ, expr) - } - .psql { - g.psql_create_table(node, typ, expr) - } - .mssql { - g.mssql_create_table(node, typ, expr) - } - else { - verror('This database type `$typ` is not implemented yet in orm') // TODO add better error - } - } -} - -fn (mut g Gen) sql_drop_table(node ast.SqlStmtLine, expr ast.Expr) { - typ := g.parse_db_type(expr) - match typ { - .sqlite3 { - g.sqlite3_drop_table(node, typ, expr) - } - .mysql { - g.mysql_drop_table(node, typ, expr) - } - .psql { - g.psql_drop_table(node, typ, expr) - } - .mssql { - g.mssql_drop_table(node, typ, expr) - } - else { - verror('This database type `$typ` is not implemented yet in orm') // TODO add better error - } - } -} - -fn (mut g Gen) sql_select_expr(node ast.SqlExpr, sub bool, line string) { - g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name + conn := g.new_tmp_var() + g.writeln('') + g.writeln('// orm') + g.write('orm__Connection $conn = (orm__Connection){._') + mut fn_prefix := '' typ := g.parse_db_type(node.db_expr) match typ { - .sqlite3 { - g.sqlite3_select_expr(node, sub, line, typ) - } - .mysql { - g.mysql_select_expr(node, sub, line, typ) - } - .psql { - g.psql_select_expr(node, sub, line, typ) - } - else { - verror('This database type `$typ` is not implemented yet in orm') // TODO add better error - } - } -} - -fn (mut g Gen) sql_bind(val string, len string, real_type ast.Type, typ SqlType) { - match typ { - .sqlite3 { - g.sqlite3_bind(val, len, real_type) - } - .mysql { - g.mysql_bind(val, real_type) - } - .psql { - g.psql_bind(val, real_type) - } - else {} - } -} - -fn (mut g Gen) sql_type_from_v(typ SqlType, v_typ ast.Type) string { - match typ { - .sqlite3 { - return g.sqlite3_type_from_v(v_typ) - } - .mysql { - return g.mysql_get_table_type(v_typ) - } - .psql { - return g.psql_get_table_type(v_typ) - } - .mssql { - return g.mssql_get_table_type(v_typ) - } - else { - // add error - } - } - return '' -} - -// sqlite3 - -fn (mut g Gen) sqlite3_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - g.sql_i = 0 - g.writeln('\n\t// sql insert') - db_name := g.new_tmp_var() - g.sql_stmt_name = g.new_tmp_var() - g.write('${c.dbtype}__DB $db_name = ') - g.expr(db_expr) - g.writeln(';') - g.write('sqlite3_stmt* $g.sql_stmt_name = ${c.dbtype}__DB_init_stmt($db_name, _SLIT("') - g.sql_defaults(node, typ) - g.writeln(');') - mut arr_stmt := []ast.SqlStmtLine{} - mut arr_fkeys := []string{} - mut arr_field_name := []string{} - if node.kind == .insert { - // build the object now (`x.name = ... x.id == ...`) - for i, field in node.fields { - if g.get_sql_field_type(field) == ast.Type(-1) { - continue - } - if field.name == g.sql_fkey && g.sql_fkey != '' { - g.writeln('sqlite3_bind_int($g.sql_stmt_name, ${i + 0} , $g.sql_parent_id); // parent id') - continue - } - x := '${node.object_var_name}.$field.name' - if field.typ == ast.string_type { - g.writeln('sqlite3_bind_text($g.sql_stmt_name, ${i + 0}, (char*)${x}.str, ${x}.len, 0);') - } else if g.table.get_type_symbol(field.typ).kind == .struct_ { - // insert again - expr := node.sub_structs[int(field.typ)] - tmp_sql_stmt_name := g.sql_stmt_name - tmp_sql_table_name := g.sql_table_name - g.sql_stmt_line(expr, db_expr) - g.sql_stmt_name = tmp_sql_stmt_name - g.sql_table_name = tmp_sql_table_name - // get last inserted id - res := g.new_tmp_var() - g.writeln('Array_sqlite__Row $res = sqlite__DB_exec($db_name, _SLIT("SELECT last_insert_rowid()")).arg0;') - id_name := g.new_tmp_var() - g.writeln('int $id_name = string_int((*(string*)array_get((*(sqlite__Row*)array_get($res, 0)).vals, 0)));') - g.writeln('sqlite3_bind_int($g.sql_stmt_name, ${i + 0} , $id_name); // id') - } else if g.table.get_type_symbol(field.typ).kind == .array { - t := g.table.get_type_symbol(field.typ).array_info().elem_type - if g.table.get_type_symbol(t).kind == .struct_ { - mut fkey := '' - for attr in field.attrs { - if attr.name == 'fkey' && attr.arg != '' && attr.kind == .string { - fkey = attr.arg - break - } - } - if fkey == '' { - verror('fkey attribute has to be set for arrays in orm') - continue - } - - arr_stmt << node.sub_structs[int(t)] - arr_fkeys << fkey - arr_field_name << field.name - } - } else { - g.writeln('sqlite3_bind_int($g.sql_stmt_name, ${i + 0} , $x); // stmt') - } - } - } - // Dump all sql parameters generated by our custom expr handler - binds := g.sql_buf.str() - g.sql_buf = strings.new_builder(100) - g.writeln(binds) - step_res := g.new_tmp_var() - g.writeln('\tint $step_res = sqlite3_step($g.sql_stmt_name);') - g.writeln('\tif( ($step_res != SQLITE_OK) && ($step_res != SQLITE_DONE)){ puts(sqlite3_errmsg(${db_name}.conn)); }') - g.writeln('\tsqlite3_finalize($g.sql_stmt_name);') - - if arr_stmt.len > 0 { - res := g.new_tmp_var() - g.writeln('Array_sqlite__Row $res = sqlite__DB_exec($db_name, _SLIT("SELECT last_insert_rowid()")).arg0;') - id_name := g.new_tmp_var() - g.writeln('int $id_name = string_int((*(string*)array_get((*(sqlite__Row*)array_get($res, 0)).vals, 0)));') - - g.sql_arr_stmt(arr_stmt, arr_fkeys, arr_field_name, id_name, db_expr) - } -} - -fn (mut g Gen) sqlite3_select_expr(node ast.SqlExpr, sub bool, line string, sql_typ SqlType) { - g.sql_i = 0 - /* - `nr_users := sql db { ... }` => - ``` - sql_init_stmt() - sqlite3_bind_int() - sqlite3_bind_string() - ... - int nr_users = get_int(stmt) - ``` - */ - mut cur_line := line - if !sub { - cur_line = g.go_before_stmt(0) - } - // g.write('${dbtype}__DB_q_int(*(${dbtype}__DB*)${node.db_var_name}.data, _SLIT("$sql_query') - g.sql_stmt_name = g.new_tmp_var() - db_name := g.new_tmp_var() - g.writeln('\n\t// sql select') - // g.write('${dbtype}__DB $db_name = *(${dbtype}__DB*)${node.db_var_name}.data;') - g.write('${c.dbtype}__DB $db_name = ') // $node.db_var_name;') - g.expr(node.db_expr) - g.writeln(';') - stmt_name := g.new_tmp_var() - g.write('string $stmt_name = _SLIT("') - g.write(g.get_base_sql_select_query(node, sql_typ)) - g.sql_expr_defaults(node, sql_typ) - g.writeln('");') - // g.write('sqlite3_stmt* $g.sql_stmt_name = ${dbtype}__DB_init_stmt(*(${dbtype}__DB*)${node.db_var_name}.data, _SLIT("$sql_query') - g.write('sqlite3_stmt* $g.sql_stmt_name = ${c.dbtype}__DB_init_stmt($db_name, $stmt_name);') - // Dump all sql parameters generated by our custom expr handler - binds := g.sql_buf.str() - g.sql_buf = strings.new_builder(100) - g.writeln(binds) - binding_res := g.new_tmp_var() - g.writeln('int $binding_res = sqlite3_extended_errcode(${db_name}.conn);') - g.writeln('if ($binding_res != SQLITE_OK) { puts(sqlite3_errmsg(${db_name}.conn)); }') - // - if node.is_count { - g.writeln('$cur_line ${c.dbtype}__get_int_from_stmt($g.sql_stmt_name);') - } else { - // `user := sql db { select from User where id = 1 }` - tmp := g.new_tmp_var() - styp := g.typ(node.typ) - mut elem_type_str := '' - if node.is_array { - // array_User array_tmp; - // for { User tmp; ... array_tmp << tmp; } - array_sym := g.table.get_type_symbol(node.typ) - array_info := array_sym.info as ast.Array - elem_type_str = g.typ(array_info.elem_type) - g.writeln('$styp ${tmp}_array = __new_array(0, 10, sizeof($elem_type_str));') - g.writeln('while (1) {') - g.writeln('\t$elem_type_str $tmp = ($elem_type_str) {') - // - sym := g.table.get_type_symbol(array_info.elem_type) - info := sym.info as ast.Struct - for i, field in info.fields { - g.zero_struct_field(field) - if i != info.fields.len - 1 { - g.write(', ') - } - } - g.writeln('};') - } else { - // `User tmp;` - g.writeln('$styp $tmp = ($styp){') - // Zero fields, (only the [skip] ones?) - // If we don't, string values are going to be nil etc for fields that are not returned - // by the db engine. - sym := g.table.get_type_symbol(node.typ) - info := sym.info as ast.Struct - for i, field in info.fields { - g.zero_struct_field(field) - if i != info.fields.len - 1 { - g.write(', ') - } - } - g.writeln('};') - } - // - g.writeln('int _step_res$tmp = sqlite3_step($g.sql_stmt_name);') - if node.is_array { - // g.writeln('\tprintf("step res=%d\\n", _step_res$tmp);') - g.writeln('\tif (_step_res$tmp == SQLITE_DONE) break;') - g.writeln('\tif (_step_res$tmp == SQLITE_ROW) ;') // another row - g.writeln('\telse if (_step_res$tmp != SQLITE_OK) break;') - } else { - // g.writeln('printf("RES: %d\\n", _step_res$tmp) ;') - g.writeln('\tif (_step_res$tmp == SQLITE_OK || _step_res$tmp == SQLITE_ROW) {') - } - mut primary := '' - for i, field in node.fields { - for attr in field.attrs { - if attr.name == 'primary' { - primary = '${tmp}.$field.name' - break - } - } - mut func := 'sqlite3_column_int' - if field.typ == ast.string_type { - func = 'sqlite3_column_text' - string_data := g.new_tmp_var() - g.writeln('byte* $string_data = (byte*)${func}($g.sql_stmt_name, $i);') - g.writeln('if ($string_data != NULL) {') - g.writeln('\t${tmp}.$field.name = tos_clone($string_data);') - g.writeln('}') - } else if g.table.get_type_symbol(field.typ).kind == .struct_ { - id_name := g.new_tmp_var() - g.writeln('//parse struct start') - g.writeln('int $id_name = ${func}($g.sql_stmt_name, $i);') - mut expr := node.sub_structs[int(field.typ)] - mut where_expr := expr.where_expr as ast.InfixExpr - mut ident := where_expr.right as ast.Ident - ident.name = id_name - where_expr.right = ident - expr.where_expr = where_expr - - tmp_sql_i := g.sql_i - tmp_sql_stmt_name := g.sql_stmt_name - tmp_sql_buf := g.sql_buf - tmp_sql_table_name := g.sql_table_name - - g.sql_select_expr(expr, true, '\t${tmp}.$field.name =') - g.writeln('//parse struct end') - - g.sql_stmt_name = tmp_sql_stmt_name - g.sql_buf = tmp_sql_buf - g.sql_i = tmp_sql_i - g.sql_table_name = tmp_sql_table_name - } else if g.table.get_type_symbol(field.typ).kind == .array { - g.sql_select_arr(field, node, primary, tmp) - } else { - g.writeln('${tmp}.$field.name = ${func}($g.sql_stmt_name, $i);') - } - } - if node.is_array { - g.writeln('\t array_push((array*)&${tmp}_array, _MOV(($elem_type_str[]){ $tmp }));\n') - } - g.writeln('}') - g.writeln('sqlite3_finalize($g.sql_stmt_name);') - if node.is_array { - g.writeln('$cur_line ${tmp}_array; ') // `array_User users = tmp_array;` - } else { - g.writeln('$cur_line $tmp; ') // `User user = tmp;` - } - } -} - -fn (mut g Gen) sqlite3_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - g.writeln('// sqlite3 table creator') - create_string := g.table_gen(node, typ, db_expr) - g.write('sqlite__DB_exec(') - g.expr(db_expr) - g.writeln(', _SLIT("$create_string"));') -} - -fn (mut g Gen) sqlite3_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - table_name := g.get_table_name(node.table_expr) - g.writeln('// sqlite3 table drop') - drop_string := 'DROP TABLE `$table_name`;' - g.write('sqlite__DB_exec(') - g.expr(db_expr) - g.writeln(', _SLIT("$drop_string"));') -} - -fn (mut g Gen) sqlite3_bind(val string, len string, typ ast.Type) { - match g.sqlite3_type_from_v(typ) { - 'INTEGER' { - g.sqlite3_bind_int(val) - } - 'TEXT' { - g.sqlite3_bind_string(val, len) - } - else { - verror('bad sql type=$typ ident_name=$val') - } - } -} - -fn (mut g Gen) sqlite3_bind_int(val string) { - g.sql_buf.writeln('sqlite3_bind_int($g.sql_stmt_name, $g.sql_i, $val);') -} - -fn (mut g Gen) sqlite3_bind_string(val string, len string) { - g.sql_buf.writeln('sqlite3_bind_text($g.sql_stmt_name, $g.sql_i, (char*)$val, $len, 0);') -} - -fn (mut g Gen) sqlite3_type_from_v(v_typ ast.Type) string { - if v_typ.is_number() || v_typ == ast.bool_type || v_typ == -1 { - return 'INTEGER' - } - if v_typ.is_string() { - return 'TEXT' - } - return '' -} - -// mysql - -fn (mut g Gen) mysql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - g.sql_i = 0 - g.writeln('\n\t//mysql insert') - db_name := g.new_tmp_var() - g.sql_stmt_name = g.new_tmp_var() - g.write('mysql__Connection $db_name = ') - g.expr(db_expr) - g.writeln(';') - stmt_name := g.new_tmp_var() - g.write('string $stmt_name = _SLIT("') - g.sql_defaults(node, typ) - g.writeln(';') - g.writeln('MYSQL_STMT* $g.sql_stmt_name = mysql_stmt_init(${db_name}.conn);') - g.writeln('mysql_stmt_prepare($g.sql_stmt_name, ${stmt_name}.str, ${stmt_name}.len);') - - bind := g.new_tmp_var() - g.writeln('MYSQL_BIND $bind[$g.sql_i];') - g.writeln('memset($bind, 0, sizeof(MYSQL_BIND)*$g.sql_i);') - mut arr_stmt := []ast.SqlStmtLine{} - mut arr_fkeys := []string{} - mut arr_field_name := []string{} - if node.kind == .insert { - for i, field in node.fields { - if g.get_sql_field_type(field) == ast.Type(-1) { - continue - } - if field.name == g.sql_fkey && g.sql_fkey != '' { - t, sym := g.mysql_buffer_typ_from_field(field) - g.writeln('$bind[${i - 1}].buffer_type = $t;') - if sym == 'char' { - g.writeln('$bind[${i - 1}].buffer = ($sym*) ${g.sql_parent_id}.str;') - } else { - g.writeln('$bind[${i - 1}].buffer = ($sym*) &$g.sql_parent_id;') - } - if sym == 'char' { - g.writeln('$bind[${i - 1}].buffer_length = ${g.sql_parent_id}.len;') - } - g.writeln('$bind[${i - 1}].is_null = 0;') - g.writeln('$bind[${i - 1}].length = 0;') - continue - } - g.writeln('//$field.name ($field.typ)') - x := '${node.object_var_name}.$field.name' - if g.table.get_type_symbol(field.typ).kind == .struct_ { - // insert again - expr := node.sub_structs[int(field.typ)] - tmp_sql_stmt_name := g.sql_stmt_name - tmp_sql_table_name := g.sql_table_name - g.sql_stmt_line(expr, db_expr) - g.sql_stmt_name = tmp_sql_stmt_name - g.sql_table_name = tmp_sql_table_name - - res := g.new_tmp_var() - g.writeln('int ${res}_err = mysql_real_query(${db_name}.conn, "SELECT LAST_INSERT_ID();", 24);') - g.writeln('if (${res}_err != 0) { puts(mysql_error(${db_name}.conn)); }') - g.writeln('MYSQL_RES* $res = mysql_store_result(${db_name}.conn);') - g.writeln('if (mysql_num_rows($res) != 1) { puts("Something went wrong"); }') - g.writeln('MYSQL_ROW ${res}_row = mysql_fetch_row($res);') - g.writeln('${x}.id = string_int(tos_clone(${res}_row[0]));') - g.writeln('mysql_free_result($res);') - - g.writeln('$bind[${i - 1}].buffer_type = MYSQL_TYPE_LONG;') - g.writeln('$bind[${i - 1}].buffer = &${x}.id;') - g.writeln('$bind[${i - 1}].is_null = 0;') - g.writeln('$bind[${i - 1}].length = 0;') - } else if g.table.get_type_symbol(field.typ).kind == .array { - t := g.table.get_type_symbol(field.typ).array_info().elem_type - if g.table.get_type_symbol(t).kind == .struct_ { - mut fkey := '' - for attr in field.attrs { - if attr.name == 'fkey' && attr.arg != '' && attr.kind == .string { - fkey = attr.arg - break - } - } - if fkey == '' { - verror('fkey attribute has to be set for arrays in orm') - continue - } - - arr_stmt << node.sub_structs[int(t)] - arr_fkeys << fkey - arr_field_name << field.name - } - } else { - t, sym := g.mysql_buffer_typ_from_field(field) - g.writeln('$bind[${i - 1}].buffer_type = $t;') - if sym == 'char' { - g.writeln('$bind[${i - 1}].buffer = ($sym*) ${x}.str;') - } else { - g.writeln('$bind[${i - 1}].buffer = ($sym*) &$x;') - } - if sym == 'char' { - g.writeln('$bind[${i - 1}].buffer_length = ${x}.len;') - } - g.writeln('$bind[${i - 1}].is_null = 0;') - g.writeln('$bind[${i - 1}].length = 0;') - } - } - } - binds := g.sql_buf.str() - g.sql_buf = strings.new_builder(100) - g.writeln(binds) - res := g.new_tmp_var() - g.writeln('int $res = mysql_stmt_bind_param($g.sql_stmt_name, $bind);') - g.writeln('if ($res != 0) { puts(mysql_error(${db_name}.conn)); }') - g.writeln('$res = mysql_stmt_execute($g.sql_stmt_name);') - g.writeln('if ($res != 0) { puts(mysql_error(${db_name}.conn)); puts(mysql_stmt_error($g.sql_stmt_name)); }') - g.writeln('mysql_stmt_close($g.sql_stmt_name);') - g.writeln('mysql_stmt_free_result($g.sql_stmt_name);') - - if arr_stmt.len > 0 { - rs := g.new_tmp_var() - g.writeln('int ${rs}_err = mysql_real_query(${db_name}.conn, "SELECT LAST_INSERT_ID();", 24);') - g.writeln('if (${rs}_err != 0) { puts(mysql_error(${db_name}.conn)); }') - g.writeln('MYSQL_RES* $rs = mysql_store_result(${db_name}.conn);') - g.writeln('if (mysql_num_rows($rs) != 1) { puts("Something went wrong"); }') - g.writeln('MYSQL_ROW ${rs}_row = mysql_fetch_row($rs);') - id_name := g.new_tmp_var() - g.writeln('int $id_name = string_int(tos_clone(${rs}_row[0]));') - g.writeln('mysql_free_result($rs);') - - g.sql_arr_stmt(arr_stmt, arr_fkeys, arr_field_name, id_name, db_expr) - } -} - -fn (mut g Gen) mysql_select_expr(node ast.SqlExpr, sub bool, line string, typ SqlType) { - g.sql_i = 0 - mut cur_line := line - if !sub { - cur_line = g.go_before_stmt(0) - } - g.sql_stmt_name = g.new_tmp_var() - g.sql_bind_name = g.new_tmp_var() - db_name := g.new_tmp_var() - g.writeln('\n\t// sql select') - g.write('mysql__Connection $db_name = ') - g.expr(node.db_expr) - g.writeln(';') - - g.write('string $g.sql_stmt_name = _SLIT("') - g.write(g.get_base_sql_select_query(node, typ)) - g.sql_expr_defaults(node, typ) - g.writeln('");') - - rplc := g.sql_buf.str() - g.sql_buf = strings.new_builder(100) - g.writeln(rplc) - - query := g.new_tmp_var() - res := g.new_tmp_var() - fields := g.new_tmp_var() - - g.writeln('int $query = mysql_real_query(${db_name}.conn, ${g.sql_stmt_name}.str, ${g.sql_stmt_name}.len);') - g.writeln('if ($query != 0) { puts(mysql_error(${db_name}.conn)); }') - g.writeln('MYSQL_RES* $res = mysql_store_result(${db_name}.conn);') - g.writeln('MYSQL_ROW $fields = mysql_fetch_row($res);') - if node.is_count { - g.writeln('$cur_line string_int(tos_clone($fields[0]));') - } else { - tmp := g.new_tmp_var() - styp := g.typ(node.typ) - tmp_i := g.new_tmp_var() - mut elem_type_str := '' - g.writeln('int $tmp_i = 0;') - if node.is_array { - array_sym := g.table.get_type_symbol(node.typ) - array_info := array_sym.info as ast.Array - elem_type_str = g.typ(array_info.elem_type) - g.writeln('$styp ${tmp}_array = __new_array(0, 10, sizeof($elem_type_str));') - g.writeln('for ($tmp_i = 0; $tmp_i < mysql_num_rows($res); $tmp_i++) {') - g.writeln('\t$elem_type_str $tmp = ($elem_type_str) {') - // - sym := g.table.get_type_symbol(array_info.elem_type) - info := sym.info as ast.Struct - for i, field in info.fields { - g.zero_struct_field(field) - if i != info.fields.len - 1 { - g.write(', ') - } - } - g.writeln('};') - } else { - g.writeln('$styp $tmp = ($styp){') - // Zero fields, (only the [skip] ones?) - // If we don't, string values are going to be nil etc for fields that are not returned - // by the db engine. - sym := g.table.get_type_symbol(node.typ) - info := sym.info as ast.Struct - for i, field in info.fields { - g.zero_struct_field(field) - if i != info.fields.len - 1 { - g.write(', ') - } - } - g.writeln('};') - } - - char_ptr := g.new_tmp_var() - g.writeln('char* $char_ptr = "";') - mut primary := '' - for i, field in node.fields { - for attr in field.attrs { - if attr.name == 'primary' { - primary = '${tmp}.$field.name' - break - } - } - g.writeln('$char_ptr = $fields[$i];') - g.writeln('if ($char_ptr == NULL) { $char_ptr = ""; }') - name := g.table.get_type_symbol(field.typ).cname - if g.table.get_type_symbol(field.typ).kind == .struct_ { - g.writeln('//parse struct start') - - mut expr := node.sub_structs[int(field.typ)] - mut where_expr := expr.where_expr as ast.InfixExpr - mut ident := where_expr.right as ast.Ident - - ident.name = '$char_ptr[$i]' - where_expr.right = ident - expr.where_expr = where_expr - - tmp_sql_i := g.sql_i - tmp_sql_stmt_name := g.sql_stmt_name - tmp_sql_buf := g.sql_buf - tmp_sql_table_name := g.sql_table_name - - g.sql_select_expr(expr, true, '\t${tmp}.$field.name =') - g.writeln('//parse struct end') - - g.sql_stmt_name = tmp_sql_stmt_name - g.sql_buf = tmp_sql_buf - g.sql_i = tmp_sql_i - g.sql_table_name = tmp_sql_table_name - } else if g.table.get_type_symbol(field.typ).kind == .array { - g.sql_select_arr(field, node, primary, tmp) - } else if field.typ == ast.string_type { - g.writeln('${tmp}.$field.name = tos_clone($char_ptr);') - } else if field.typ == ast.byte_type { - g.writeln('${tmp}.$field.name = (byte) string_${name}(tos_clone($char_ptr));') - } else if field.typ == ast.i8_type { - g.writeln('${tmp}.$field.name = (i8) string_${name}(tos_clone($char_ptr));') - } else { - g.writeln('${tmp}.$field.name = string_${name}(tos_clone($char_ptr));') - } - } - if node.is_array { - g.writeln('\t array_push((array*)&${tmp}_array, _MOV(($elem_type_str[]) { $tmp }));\n') - g.writeln('\t $fields = mysql_fetch_row($res);') - g.writeln('}') - } - g.writeln('string_free(&$g.sql_stmt_name);') - g.writeln('mysql_free_result($res);') - if node.is_array { - g.writeln('$cur_line ${tmp}_array; ') - } else { - g.writeln('$cur_line $tmp; ') - } - } -} - -fn (mut g Gen) mysql_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - g.writeln('// mysql table creator') - create_string := g.table_gen(node, typ, db_expr) - tmp := g.new_tmp_var() - g.write('Option_mysql__Result $tmp = mysql__Connection_query(&') - g.expr(db_expr) - g.writeln(', _SLIT("$create_string"));') - - tmp_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}}))' - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') -} - -fn (mut g Gen) mysql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - table_name := g.get_table_name(node.table_expr) - g.writeln('// mysql table drop') - drop_string := 'DROP TABLE `$table_name`;' - tmp := g.new_tmp_var() - g.write('Option_mysql__Result $tmp = mysql__Connection_query(&') - g.expr(db_expr) - g.writeln(', _SLIT("$drop_string"));') - - tmp_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}}))' - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') -} - -fn (mut g Gen) mysql_bind(val string, typ ast.Type) { - g.write('$g.sql_i') - mut sym := g.table.get_type_symbol(typ).cname - g.sql_buf.write_string('$g.sql_stmt_name = string_replace($g.sql_stmt_name, _SLIT("?$g.sql_i"), ') - if sym != 'string' { - mut num := false - if sym != 'bool' { - num = true - g.sql_buf.write_string('${sym}_str(') - } - g.sql_buf.write_string('(($sym) $val)') - - if sym == 'bool' { - g.sql_buf.write_string('? _SLIT("1") : _SLIT("0")') - } - - if num { - g.sql_buf.write_string(')') - } - } else { - g.sql_buf.write_string('string__plus(_SLIT("\'"), string__plus(((string) $val), _SLIT("\'")))') - } - g.sql_buf.writeln(');') -} - -fn (mut g Gen) mysql_get_table_type(typ ast.Type) string { - mut table_typ := '' - match typ { - ast.i8_type, ast.byte_type, ast.bool_type { - table_typ = 'TINYINT' - } - ast.i16_type, ast.u16_type { - table_typ = 'SMALLINT' - } - ast.int_type, ast.u32_type { - table_typ = 'INT' - } - ast.i64_type, ast.u64_type { - table_typ = 'BIGINT' - } - ast.f32_type { - table_typ = 'FLOAT' - } - ast.f64_type { - table_typ = 'DOUBLE' - } - ast.string_type { - table_typ = 'TEXT' - } - -1 { - table_typ = 'SERIAL' - } - else {} - } - return table_typ -} - -fn (mut g Gen) mysql_buffer_typ_from_typ(typ ast.Type) string { - mut buf_typ := '' - match typ { - ast.i8_type, ast.byte_type, ast.bool_type { - buf_typ = 'MYSQL_TYPE_TINY' - } - ast.i16_type, ast.u16_type { - buf_typ = 'MYSQL_TYPE_SHORT' - } - ast.int_type, ast.u32_type { - buf_typ = 'MYSQL_TYPE_LONG' - } - ast.i64_type, ast.u64_type { - buf_typ = 'MYSQL_TYPE_LONGLONG' - } - ast.f32_type { - buf_typ = 'MYSQL_TYPE_FLOAT' - } - ast.f64_type { - buf_typ = 'MYSQL_TYPE_DOUBLE' - } - ast.string_type { - buf_typ = 'MYSQL_TYPE_STRING' - } - else { - buf_typ = 'MYSQL_TYPE_NULL' - } - } - return buf_typ -} - -fn (mut g Gen) mysql_buffer_typ_from_field(field ast.StructField) (string, string) { - mut typ := g.get_sql_field_type(field) - mut sym := g.table.get_type_symbol(typ).cname - buf_typ := g.mysql_buffer_typ_from_typ(typ) - - if typ == ast.string_type { - sym = 'char' - } - - return buf_typ, sym -} - -// psql - -fn (mut g Gen) psql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - g.sql_i = 0 - g.sql_idents = []string{} - g.writeln('\n\t//psql insert') - db_name := g.new_tmp_var() - g.sql_stmt_name = g.new_tmp_var() - g.write('pg__DB $db_name = ') - g.expr(db_expr) - g.writeln(';') - g.write('string $g.sql_stmt_name = _SLIT("') - g.sql_defaults(node, typ) - g.writeln(';') - - mut arr_stmt := []ast.SqlStmtLine{} - mut arr_fkeys := []string{} - mut arr_field_name := []string{} - if node.kind == .insert { - for i, field in node.fields { - if g.get_sql_field_type(field) == ast.Type(-1) { - continue - } - g.sql_i = i - field_type := g.get_sql_field_type(field) - if field.name == g.sql_fkey && g.sql_fkey != '' { - g.sql_buf = strings.new_builder(100) - g.sql_bind(g.sql_parent_id, '', field_type, typ) - g.writeln(g.sql_buf.str()) - continue - } - g.writeln('//$field.name ($field.typ)') - x := '${node.object_var_name}.$field.name' - if g.table.get_type_symbol(field.typ).kind == .struct_ { - // insert again - expr := node.sub_structs[int(field.typ)] - tmp_sql_stmt_name := g.sql_stmt_name - tmp_sql_table_name := g.sql_table_name - g.sql_stmt_line(expr, db_expr) - g.sql_stmt_name = tmp_sql_stmt_name - g.sql_table_name = tmp_sql_table_name - - res := g.new_tmp_var() - g.writeln('Option_pg__Row $res = pg__DB_exec_one($db_name, _SLIT("SELECT LASTVAL();"));') - - tmp_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, $si_s_code ,{.d_s=IError_str(err)}}}))' - g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln($tmp_str); }') - - g.sql_buf = strings.new_builder(100) - g.sql_bind('string_int((*(string*)array_get((*(pg__Row*)${res}.data).vals, 0)))', - '', ast.int_type, typ) - g.writeln(g.sql_buf.str()) - } else if g.table.get_type_symbol(field.typ).kind == .array { - t := g.table.get_type_symbol(field.typ).array_info().elem_type - if g.table.get_type_symbol(t).kind == .struct_ { - mut fkey := '' - for attr in field.attrs { - if attr.name == 'fkey' && attr.arg != '' && attr.kind == .string { - fkey = attr.arg - break - } - } - if fkey == '' { - verror('fkey attribute has to be set for arrays in orm') - continue - } - - arr_stmt << node.sub_structs[int(t)] - arr_fkeys << fkey - arr_field_name << field.name - } - } else { - g.sql_buf = strings.new_builder(100) - g.sql_bind(x, '', field_type, typ) - g.writeln(g.sql_buf.str()) - } - } - } - binds := g.sql_buf.str() - g.sql_buf = strings.new_builder(100) - g.writeln(binds) - - g.writeln('pg__DB_exec($db_name, $g.sql_stmt_name);') - - if arr_stmt.len > 0 { - res := g.new_tmp_var() - g.writeln('Option_pg__Row $res = pg__DB_exec_one($db_name, _SLIT("SELECT LASTVAL();"));') - - tmp_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, $si_s_code ,{.d_s=IError_str(err)}}}))' - g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln($tmp_str); }') - - id_name := g.new_tmp_var() - g.writeln('int $id_name = string_int((*(string*)array_get((*(pg__Row*)${res}.data).vals, 0)));') - g.sql_arr_stmt(arr_stmt, arr_fkeys, arr_field_name, id_name, db_expr) - } -} - -fn (mut g Gen) psql_select_expr(node ast.SqlExpr, sub bool, line string, typ SqlType) { - g.sql_i = 0 - mut cur_line := line - if !sub { - cur_line = g.go_before_stmt(0) - } - g.sql_stmt_name = g.new_tmp_var() - db_name := g.new_tmp_var() - g.writeln('\n\t// psql select') - g.write('pg__DB $db_name = ') - g.expr(node.db_expr) - g.writeln(';') - - g.write('string $g.sql_stmt_name = _SLIT("') - g.write(g.get_base_sql_select_query(node, typ)) - g.sql_expr_defaults(node, typ) - g.writeln('");') - - buf := g.sql_buf.str() - g.sql_buf = strings.new_builder(100) - g.writeln(buf) - - res := g.new_tmp_var() - g.writeln('Option_Array_pg__Row $res = pg__DB_exec($db_name, $g.sql_stmt_name);') - - tmp_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}}))' - g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln($tmp_str); }') - - rows := g.new_tmp_var() - - g.writeln('Array_pg__Row $rows = *(Array_pg__Row*) ${res}.data;') - - if node.is_count { - g.writeln('$cur_line string_int((*(string*)array_get(array_get($rows, 0).vals), 0)));') - } else { - tmp := g.new_tmp_var() - styp := g.typ(node.typ) - tmp_i := g.new_tmp_var() - mut elem_type_str := '' - g.writeln('int $tmp_i = 0;') - if node.is_array { - array_sym := g.table.get_type_symbol(node.typ) - array_info := array_sym.info as ast.Array - elem_type_str = g.typ(array_info.elem_type) - g.writeln('$styp ${tmp}_array = __new_array(0, 10, sizeof($elem_type_str));') - g.writeln('for ($tmp_i = 0; $tmp_i < ${rows}.len; $tmp_i++) {') - g.writeln('\t$elem_type_str $tmp = ($elem_type_str) {') - // - sym := g.table.get_type_symbol(array_info.elem_type) - info := sym.info as ast.Struct - for i, field in info.fields { - g.zero_struct_field(field) - if i != info.fields.len - 1 { - g.write(', ') - } - } - g.writeln('};') - } else { - g.writeln('$styp $tmp = ($styp){') - sym := g.table.get_type_symbol(node.typ) - info := sym.info as ast.Struct - for i, field in info.fields { - g.zero_struct_field(field) - if i != info.fields.len - 1 { - g.write(', ') - } - } - g.writeln('};') - } - fields := g.new_tmp_var() - g.writeln('if (${rows}.len > 0) {') - g.writeln('Array_string $fields = (*(pg__Row*) array_get($rows, $tmp_i)).vals;') - fld := g.new_tmp_var() - g.writeln('string $fld;') - mut primary := '' - for i, field in node.fields { - for attr in field.attrs { - if attr.name == 'primary' { - primary = '${tmp}.$field.name' - break - } - } - if g.table.get_type_symbol(field.typ).kind == .array { - g.sql_select_arr(field, node, primary, tmp) - continue - } - g.writeln('$fld = (*(string*)array_get($fields, $i));') - name := g.table.get_type_symbol(field.typ).cname - - if g.table.get_type_symbol(field.typ).kind == .struct_ { - g.writeln('//parse struct start') - - mut expr := node.sub_structs[int(field.typ)] - mut where_expr := expr.where_expr as ast.InfixExpr - mut ident := where_expr.right as ast.Ident - - ident.name = '$fld' - where_expr.right = ident - expr.where_expr = where_expr - - tmp_sql_i := g.sql_i - tmp_sql_stmt_name := g.sql_stmt_name - tmp_sql_buf := g.sql_buf - tmp_sql_table_name := g.sql_table_name - - g.sql_select_expr(expr, true, '\t${tmp}.$field.name =') - g.writeln('//parse struct end') - g.sql_stmt_name = tmp_sql_stmt_name - g.sql_buf = tmp_sql_buf - g.sql_i = tmp_sql_i - g.sql_table_name = tmp_sql_table_name - } else if field.typ == ast.string_type { - g.writeln('${tmp}.$field.name = $fld;') - } else if field.typ == ast.byte_type { - g.writeln('${tmp}.$field.name = (byte) string_${name}($fld);') - } else if field.typ == ast.i8_type { - g.writeln('${tmp}.$field.name = (i8) string_${name}($fld);') - } else if field.typ == ast.bool_type { - g.writeln('${tmp}.$field.name = string__eq($fld, _SLIT("0")) ? false : true;') - } else { - g.writeln('${tmp}.$field.name = string_${name}($fld);') - } - } - if node.is_array { - g.writeln('\t array_push((array*)&${tmp}_array, _MOV(($elem_type_str[]) { $tmp }));\n') - g.writeln('}') - } - g.writeln('}') - g.writeln('string_free(&$g.sql_stmt_name);') - if node.is_array { - g.writeln('$cur_line ${tmp}_array; ') - } else { - g.writeln('$cur_line $tmp; ') - } - } -} - -fn (mut g Gen) psql_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - g.writeln('// psql table creator') - create_string := g.table_gen(node, typ, db_expr) - tmp := g.new_tmp_var() - g.write('Option_Array_pg__Row $tmp = pg__DB_exec(') - g.expr(db_expr) - g.writeln(', _SLIT("$create_string"));') - - tmp_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}}))' - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') -} - -fn (mut g Gen) psql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - table_name := g.get_table_name(node.table_expr) - g.writeln('// psql table drop') - lit := '\\"' - drop_string := 'DROP TABLE $lit$table_name$lit;' - tmp := g.new_tmp_var() - g.write('Option_Array_pg__Row $tmp = pg__DB_exec(') - g.expr(db_expr) - g.writeln(', _SLIT("$drop_string"));') - - tmp_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}}))' - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') -} - -fn (mut g Gen) psql_get_table_type(typ ast.Type) string { - mut table_typ := '' - match typ { - ast.i8_type, ast.byte_type, ast.bool_type { - table_typ = 'CHAR(1)' - } - ast.i16_type, ast.u16_type { - table_typ = 'SMALLINT' - } - ast.int_type, ast.u32_type { - table_typ = 'INT' - } - ast.i64_type, ast.u64_type { - table_typ = 'BIGINT' - } - ast.f32_type { - table_typ = 'FLOAT4' - } - ast.f64_type { - table_typ = 'FLOAT8' - } - ast.string_type { - table_typ = 'TEXT' - } - -1 { - table_typ = 'SERIAL' - } - else {} - } - return table_typ -} - -fn (mut g Gen) psql_bind(val string, typ ast.Type) { - mut sym := g.table.get_type_symbol(typ).cname - g.sql_buf.write_string('$g.sql_stmt_name = string_replace($g.sql_stmt_name, _SLIT("\$$g.sql_i"), ') - if sym != 'string' { - mut num := false - if sym != 'bool' { - num = true - g.sql_buf.write_string('${sym}_str(') - } - g.sql_buf.write_string('(($sym) $val)') - - if sym == 'bool' { - g.sql_buf.write_string('? _SLIT("1") : _SLIT("0")') - } - - if num { - g.sql_buf.write_string(')') - } - } else { - g.sql_buf.write_string('string__plus(_SLIT("\'"), string__plus(((string) $val), _SLIT("\'")))') - } - g.sql_buf.writeln(');') -} - -// mssql - -fn (mut g Gen) mssql_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - g.writeln('// mssql table creator') - create_string := g.table_gen(node, typ, db_expr) - tmp := g.new_tmp_var() - g.write('Option_mssql__Result $tmp = mssql__Connection_query(&') - g.expr(db_expr) - g.writeln(', _SLIT("$create_string"));') - - tmp_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}}))' - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') -} - -fn (mut g Gen) mssql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { - table_name := g.get_table_name(node.table_expr) - g.writeln('// mssql table drop') - lit := '\\"' - drop_string := 'DROP TABLE $lit$table_name$lit;' - tmp := g.new_tmp_var() - g.write('Option_mssql__Result $tmp = mssql__Connection_query(&') - g.expr(db_expr) - g.writeln(', _SLIT("$drop_string"));') - - tmp_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}}))' - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') -} - -fn (mut g Gen) mssql_get_table_type(typ ast.Type) string { - mut table_typ := '' - match typ { - ast.i8_type, ast.byte_type, ast.bool_type { - table_typ = 'TINYINT' - } - ast.i16_type, ast.u16_type { - table_typ = 'SMALLINT' - } - ast.int_type, ast.u32_type { - table_typ = 'INT' - } - ast.i64_type, ast.u64_type { - table_typ = 'BIGINT' - } - ast.f32_type { - table_typ = 'FLOAT(24)' + .sqlite3 { + fn_prefix = 'sqlite__DB' } - ast.f64_type { - table_typ = 'FLOAT(53)' + .mysql { + fn_prefix = 'mysql__Connection' } - ast.string_type { - table_typ = 'TEXT' + .mssql { + // g.mssql_create_table(node, typ, expr) } - -1 { - table_typ = 'INT IDENTITY' + else { + verror('This database type `$typ` is not implemented yet in orm') // TODO add better error } - else {} } - return table_typ + g.write('$fn_prefix = &') + g.expr(node.db_expr) + g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};') + for line in node.lines { + g.sql_stmt_line(line, conn) + } } -// utils - -fn (mut g Gen) sql_select_arr(field ast.StructField, node ast.SqlExpr, primary string, tmp string) { - t := g.table.get_type_symbol(field.typ).array_info().elem_type - if g.table.get_type_symbol(t).kind == .struct_ { - mut fkey := '' - for attr in field.attrs { - if attr.name == 'fkey' && attr.arg != '' && attr.kind == .string { - fkey = attr.arg - break +fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string) { + mut node := nd + table_name := g.get_table_name(node.table_expr) + g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name + res := g.new_tmp_var() + mut subs := false + mut dcheck := false + + if node.kind != .create { + mut fields := []ast.StructField{} + for f in node.fields { + mut skip := false + mut primary := false + for attr in f.attrs { + if attr.name == 'primary' { + primary = true + } + if attr.name == 'skip' { + skip = true + } + } + if !skip && !primary { + fields << f } } - if fkey == '' { - verror('fkey attribute has to be set for arrays in orm') - return - } - g.writeln('//parse array start') + node.fields = fields.clone() + unsafe { fields.free() } + } - e := node.sub_structs[int(t)] - mut where_expr := e.where_expr as ast.InfixExpr - mut lidt := where_expr.left as ast.Ident - mut ridt := where_expr.right as ast.Ident - ridt.name = primary - lidt.name = fkey - where_expr.right = ridt - where_expr.left = lidt - expr := ast.SqlExpr{ - typ: field.typ - has_where: e.has_where - db_expr: e.db_expr - is_array: true - pos: e.pos - where_expr: where_expr - table_expr: e.table_expr - fields: e.fields - sub_structs: e.sub_structs + if node.kind == .create { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.sql_create_table(node, expr, table_name) + subs = true + } else if node.kind == .drop { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.writeln('drop(${expr}._object, _SLIT("$table_name"));') + subs = true + } else if node.kind == .insert { + arr := g.new_tmp_var() + g.writeln('Array_orm__Primitive $arr = new_array_from_c_array(0, 0, sizeof(orm__Primitive), NULL);') + g.sql_insert(node, expr, table_name, arr, res, '', false, '') + dcheck = true + } else if node.kind == .update { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.sql_update(node, expr, table_name) + } else if node.kind == .delete { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.sql_delete(node, expr, table_name) + } + if !dcheck { + g.writeln('if (${res}.state != 0 && ${res}.err._typ != _IError_None___index) { _v_panic(IError_str(${res}.err)); }') + } + if subs { + for _, sub in node.sub_structs { + g.sql_stmt_line(sub, expr) + } + } +} + +fn (mut g Gen) sql_create_table(node ast.SqlStmtLine, expr string, table_name string) { + g.write('create(${expr}._object, _SLIT("$table_name"), new_array_from_c_array($node.fields.len, $node.fields.len, sizeof(orm__TableField),') + if node.fields.len > 0 { + g.write(' _MOV((orm__TableField[$node.fields.len]){') + for field in node.fields { + sym := g.table.get_type_symbol(field.typ) + g.write('(orm__TableField){') + g.write('.name = _SLIT("$field.name"),') + g.write('.typ = ${int(field.typ)},') + g.write('.is_arr = ${sym.kind == .array}, ') + g.write('.is_time = ${int(g.table.get_type_name(field.typ) == 'time__Time')},') + g.write('.default_val = (string){.str = (byteptr) "$field.default_val", .is_lit = 1},') + g.write('.attrs = new_array_from_c_array($field.attrs.len, $field.attrs.len, sizeof(StructAttribute),') + if field.attrs.len > 0 { + g.write(' _MOV((StructAttribute[$field.attrs.len]){') + for attr in field.attrs { + g.write('(StructAttribute){') + g.write('.name = _SLIT("$attr.name"),') + g.write('.has_arg = ${int(attr.has_arg)},') + g.write('.arg = _SLIT("$attr.arg"),') + g.write('.kind = ${int(attr.kind)},') + g.write('},') + } + g.write('})') + } else { + g.write('NULL') + } + g.write(')') + g.write('},') + } + g.write('})') + } else { + g.write('NULL') + } + g.writeln('));') +} + +fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, last_ids_arr string, res string, pid string, is_array bool, fkey string) { + mut subs := []ast.SqlStmtLine{} + mut arrs := []ast.SqlStmtLine{} + mut fkeys := []string{} + mut field_names := []string{} + + for f in node.fields { + sym := g.table.get_type_symbol(f.typ) + if sym.kind == .struct_ { + subs << node.sub_structs[int(f.typ)] + } else if sym.kind == .array { + mut f_key := '' + for attr in f.attrs { + if attr.name == 'fkey' && attr.has_arg && attr.kind == .string { + f_key = attr.arg + } + } + if f_key == '' { + verror('An field which holds an array, needs a fkey defined') + } + fkeys << f_key + info := sym.array_info() + if info.nr_dims == 1 { + arrs << node.sub_structs[int(info.elem_type)] + field_names << f.name + } else { + verror('V ORM only supports 1 dimensional arrays') + } } - tmp_sql_i := g.sql_i - tmp_sql_stmt_name := g.sql_stmt_name - tmp_sql_buf := g.sql_buf - tmp_sql_table_name := g.sql_table_name + } - g.sql_select_expr(expr, true, '\t${tmp}.$field.name =') - g.writeln('//parse array end') + fields := node.fields.filter(g.table.get_type_symbol(it.typ).kind != .array) - g.sql_stmt_name = tmp_sql_stmt_name - g.sql_buf = tmp_sql_buf - g.sql_i = tmp_sql_i - g.sql_table_name = tmp_sql_table_name + for sub in subs { + g.sql_stmt_line(sub, expr) + g.writeln('array_push(&$last_ids_arr, _MOV((orm__Primitive[]){orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object)}));') } -} -fn (mut g Gen) sql_arr_stmt(arr_stmt []ast.SqlStmtLine, arr_fkeys []string, arr_field_name []string, id_name string, db_expr ast.Expr) { - for i, s in arr_stmt { - cnt := g.new_tmp_var() - g.writeln('for (int $cnt = 0; $cnt < ${s.object_var_name}.${arr_field_name[i]}.len; $cnt++) {') - name := g.table.get_type_symbol(s.table_expr.typ).cname - tmp_var := g.new_tmp_var() - g.writeln('\t$name $tmp_var = (*($name*)array_get(${s.object_var_name}.${arr_field_name[i]}, $cnt));') + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.write('insert(${expr}._object, _SLIT("$table_name"), (orm__QueryData){') - mut sub_structs := map[int]ast.SqlStmtLine{} + g.write('.fields = new_array_from_c_array($fields.len, $fields.len, sizeof(string),') + if fields.len > 0 { + g.write('_MOV((string[$fields.len]){') + for f in fields { + g.write('_SLIT("${g.get_field_name(f)}"),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') - for key, sub in s.sub_structs { - sub_structs[key] = ast.SqlStmtLine{ - pos: sub.pos - kind: sub.kind - table_expr: sub.table_expr - object_var_name: tmp_var - fields: sub.fields - sub_structs: sub.sub_structs + g.write('.data = new_array_from_c_array($fields.len, $fields.len, sizeof(orm__Primitive),') + if fields.len > 0 { + g.write(' _MOV((orm__Primitive[$fields.len]){') + mut structs := 0 + for f in fields { + if f.name == fkey { + g.write('$pid, ') + continue } + mut sym := g.table.get_type_symbol(f.typ) + if sym.kind == .struct_ { + g.write('(*(orm__Primitive*) array_get($last_ids_arr, $structs)),') + structs++ + continue + } + mut typ := sym.cname + if typ == 'time__Time' { + typ = 'time' + } + g.write('orm__${typ}_to_primitive(${node.object_var_name}.$f.name),') + } + g.write('})') + } + g.write('),') + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') + g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.writeln('});') + + g.writeln('if (${res}.state != 0 && ${res}.err._typ != _IError_None___index) { _v_panic(IError_str(${res}.err)); }') + if arrs.len > 0 { + mut id_name := g.new_tmp_var() + g.writeln('orm__Primitive $id_name = orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object);') + for i, mut arr in arrs { + idx := g.new_tmp_var() + g.writeln('for (int $idx = 0; $idx < ${arr.object_var_name}.${field_names[i]}.len; $idx++) {') + last_ids := g.new_tmp_var() + res_ := g.new_tmp_var() + tmp_var := g.new_tmp_var() + ctyp := g.typ(arr.table_expr.typ) + g.writeln('$ctyp $tmp_var = (*($ctyp*)array_get(${arr.object_var_name}.${field_names[i]}, $idx));') + arr.object_var_name = tmp_var + mut fff := []ast.StructField{} + for f in arr.fields { + mut skip := false + mut primary := false + for attr in f.attrs { + if attr.name == 'primary' { + primary = true + } + if attr.name == 'skip' { + skip = true + } + } + if !skip && !primary { + fff << f + } + } + arr.fields = fff.clone() + unsafe { fff.free() } + g.sql_insert(arr, expr, g.get_table_name(arr.table_expr), last_ids, res_, + id_name, true, fkeys[i]) + g.writeln('}') } - stmt := ast.SqlStmtLine{ - pos: s.pos - kind: s.kind - table_expr: s.table_expr - object_var_name: tmp_var - fields: s.fields - sub_structs: sub_structs - } - tmp_fkey := g.sql_fkey - tmp_parent_id := g.sql_parent_id - g.sql_fkey = arr_fkeys[i] - g.sql_parent_id = id_name - - g.sql_stmt_line(stmt, db_expr) - - g.sql_fkey = tmp_fkey - g.sql_parent_id = tmp_parent_id - - g.writeln('}') } } -fn (mut g Gen) sql_expr_defaults(node ast.SqlExpr, sql_typ SqlType) { - if node.has_where && node.where_expr is ast.InfixExpr { - g.expr_to_sql(node.where_expr, sql_typ) - } - if node.has_order { - g.write(' ORDER BY ') - g.sql_side = .left - g.expr_to_sql(node.order_expr, sql_typ) - if node.has_desc { - g.write(' DESC ') +fn (mut g Gen) sql_update(node ast.SqlStmtLine, expr string, table_name string) { + g.write('update(${expr}._object, _SLIT("$table_name"), (orm__QueryData){') + g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') + g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.fields = new_array_from_c_array($node.updated_columns.len, $node.updated_columns.len, sizeof(string),') + if node.updated_columns.len > 0 { + g.write(' _MOV((string[$node.updated_columns.len]){') + for field in node.updated_columns { + g.write('_SLIT("$field"),') } + g.write('})') } else { - g.write(' ORDER BY id ') - } - if node.has_limit { - g.write(' LIMIT ') - g.sql_side = .right - g.expr_to_sql(node.limit_expr, sql_typ) + g.write('NULL') } - if node.has_offset { - g.write(' OFFSET ') - g.sql_side = .right - g.expr_to_sql(node.offset_expr, sql_typ) + g.write('),') + g.write('.data = new_array_from_c_array($node.update_exprs.len, $node.update_exprs.len, sizeof(orm__Primitive),') + if node.update_exprs.len > 0 { + g.write(' _MOV((orm__Primitive[$node.update_exprs.len]){') + for e in node.update_exprs { + g.sql_expr_to_orm_primitive(e) + } + g.write('})') } + g.write('),},') + g.sql_gen_where_data(node.where_expr) + g.writeln(');') } -fn (mut g Gen) get_base_sql_select_query(node ast.SqlExpr, typ SqlType) string { - mut lit := '`' - if typ == .psql { - lit = '\\"' - } - mut sql_query := 'SELECT ' - table_name := g.get_table_name(node.table_expr) - if node.is_count { - // `select count(*) from User` - sql_query += 'COUNT(*) FROM $lit$table_name$lit ' - } else { - // `select id, name, country from User` - fields := node.fields.filter(g.table.get_type_symbol(it.typ).kind != .array) - for i, field in fields { - sql_query += '$lit${g.get_field_name(field)}$lit' - if i < fields.len - 1 { - sql_query += ', ' - } +fn (mut g Gen) sql_delete(node ast.SqlStmtLine, expr string, table_name string) { + g.write('_v_delete(${expr}._object, _SLIT("$table_name"),') + g.sql_gen_where_data(node.where_expr) + g.writeln(');') +} + +fn (mut g Gen) sql_expr_to_orm_primitive(expr ast.Expr) { + match expr { + ast.InfixExpr { + g.sql_write_orm_primitive(g.table.find_type_idx('orm.InfixType'), expr) + } + ast.StringLiteral { + g.sql_write_orm_primitive(ast.string_type, expr) + } + ast.IntegerLiteral { + g.sql_write_orm_primitive(ast.int_type, expr) + } + ast.BoolLiteral { + g.sql_write_orm_primitive(ast.bool_type, expr) + } + ast.Ident { + info := expr.info as ast.IdentVar + g.sql_write_orm_primitive(info.typ, expr) + } + ast.SelectorExpr { + g.sql_write_orm_primitive(expr.typ, expr) + } + else { + eprintln(expr) + verror('Unknown expr') } - sql_query += ' FROM $lit$table_name$lit' - } - if node.has_where { - sql_query += ' WHERE ' } - return sql_query } -fn (mut g Gen) sql_defaults(node ast.SqlStmtLine, typ SqlType) { - table_name := g.get_table_name(node.table_expr) - mut lit := '`' - if typ == .psql { - lit = '\\"' +fn (mut g Gen) sql_write_orm_primitive(t ast.Type, expr ast.Expr) { + mut sym := g.table.get_type_symbol(t) + mut typ := sym.cname + if typ == 'orm__Primitive' { + g.expr(expr) + g.write(',') + return } - if node.kind == .insert { - g.write('INSERT INTO $lit$table_name$lit (') - } else if node.kind == .update { - g.write('UPDATE $lit$table_name$lit SET ') - } else if node.kind == .delete { - g.write('DELETE FROM $lit$table_name$lit ') + if typ == 'time__Time' { + typ = 'time' } - if node.kind == .insert { - fields := node.fields.filter(g.table.get_type_symbol(it.typ).kind != .array) - for i, field in fields { - if g.get_sql_field_type(field) == ast.Type(-1) { - continue + if typ == 'orm__InfixType' { + typ = 'infix' + } + g.write('orm__${typ}_to_primitive(') + if expr is ast.InfixExpr { + g.write('(orm__InfixType){') + g.write('.name = _SLIT("$expr.left"),') + mut kind := match expr.op { + .plus { + 'orm__MathOperationKind__add' } - g.write('$lit${g.get_field_name(field)}$lit') - if i < fields.len - 1 { - g.write(', ') + .minus { + 'orm__MathOperationKind__sub' } - } - g.write(') VALUES (') - for i, field in fields { - if g.get_sql_field_type(field) == ast.Type(-1) { - continue + .div { + 'orm__MathOperationKind__div' } - g.inc_sql_i(typ) - if i < fields.len - 1 { - g.write(', ') + .mul { + 'orm__MathOperationKind__mul' } - } - g.write(')') - } else if node.kind == .update { - for i, col in node.updated_columns { - g.write(' ${g.get_field_name(g.get_struct_field(col))} = ') - g.expr_to_sql(node.update_exprs[i], typ) - if i < node.updated_columns.len - 1 { - g.write(', ') + else { + '' } } - g.write(' WHERE ') - } else if node.kind == .delete { - g.write(' WHERE ') - } - if node.kind == .update || node.kind == .delete { - g.expr_to_sql(node.where_expr, typ) + g.write('.operator = $kind,') + g.write('.right = ') + g.sql_expr_to_orm_primitive(expr.right) + g.write('}') + } else { + g.expr(expr) } - g.write(';")') + g.write('),') } -fn (mut g Gen) table_gen(node ast.SqlStmtLine, typ SqlType, expr ast.Expr) string { - typ_sym := g.table.get_type_symbol(node.table_expr.typ) - struct_data := typ_sym.struct_info() - table_name := g.get_table_name(node.table_expr) - mut lit := '`' - if typ == .psql || typ == .mssql { - lit = '\\"' - } - - mut create_string := 'CREATE TABLE IF NOT EXISTS $lit$table_name$lit (' - if typ == .mssql { - // mssql detecting create if not exist is awkward - create_string = 'IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=\'$table_name\' and xtype=\'U\') CREATE TABLE $lit$table_name$lit (' - } - - mut fields := []string{} - mut unique_fields := []string{} - - mut primary := '' // for mysql - mut unique := map[string][]string{} - - for field in struct_data.fields { - name := g.get_field_name(field) - mut is_primary := false - mut no_null := false - mut is_unique := false - mut is_skip := false - mut unique_len := 0 - mut fkey := '' - for attr in field.attrs { - match attr.name { - 'primary' { - is_primary = true - primary = name +fn (mut g Gen) sql_where_data(expr ast.Expr, mut fields []string, mut kinds []string, mut data []ast.Expr, mut is_and []bool) { + match expr { + ast.InfixExpr { + g.sql_side = .left + g.sql_where_data(expr.left, mut fields, mut kinds, mut data, mut is_and) + mut kind := match expr.op { + .ne { + 'orm__OperationKind__neq' } - 'unique' { - if attr.arg != '' { - if attr.kind == .string { - unique[attr.arg] << name - continue - } else if attr.kind == .number { - unique_len = attr.arg.int() - is_unique = true - continue - } - } - is_unique = true + .eq { + 'orm__OperationKind__eq' } - 'nonull' { - no_null = true + .lt { + 'orm__OperationKind__lt' } - 'skip' { - is_skip = true + .gt { + 'orm__OperationKind__gt' } - 'fkey' { - if attr.arg != '' { - if attr.kind == .string { - fkey = attr.arg - continue - } - } + .ge { + 'orm__OperationKind__ge' } - else {} - } - } - if is_skip { - continue - } - mut stmt := '' - mut converted_typ := g.sql_type_from_v(typ, g.get_sql_field_type(field)) - if converted_typ == '' { - if g.table.get_type_symbol(field.typ).kind == .struct_ { - converted_typ = g.sql_type_from_v(typ, ast.int_type) - g.sql_create_table(ast.SqlStmtLine{ - kind: node.kind - pos: node.pos - table_expr: ast.TypeNode{ - typ: field.typ - pos: node.table_expr.pos - } - }, expr) - } else if g.table.get_type_symbol(field.typ).kind == .array { - arr_info := g.table.get_type_symbol(field.typ).array_info() - if arr_info.nr_dims > 1 { - verror('array with one dim are supported in orm') - continue + .le { + 'orm__OperationKind__le' } - atyp := arr_info.elem_type - if g.table.get_type_symbol(atyp).kind == .struct_ { - if fkey == '' { - verror('array field ($field.name) needs a fkey') - continue - } - g.sql_create_table(ast.SqlStmtLine{ - kind: node.kind - pos: node.pos - table_expr: ast.TypeNode{ - typ: atyp - pos: node.table_expr.pos - } - }, expr) + else { + '' + } + } + if kind == '' { + if expr.op == .logical_or { + is_and << false + } else if expr.op == .and { + is_and << true } else { - verror('unknown type ($field.typ) for field $field.name in struct $table_name') + kind = 'orm__OperationKind__eq' } - continue + } + if expr.left !is ast.InfixExpr && expr.right !is ast.InfixExpr { + kinds << kind + } + g.sql_side = .right + g.sql_where_data(expr.right, mut fields, mut kinds, mut data, mut is_and) + } + ast.Ident { + if g.sql_side == .left { + fields << g.get_field_name(g.get_struct_field(expr.name)) } else { - verror('unknown type ($field.typ) for field $field.name in struct $table_name') - continue + data << expr } } - stmt = '$lit$name$lit $converted_typ' - - if field.has_default_expr && typ != .mysql { - stmt += ' DEFAULT ' - stmt += field.default_expr.str() + ast.StringLiteral { + data << expr } - if no_null { - stmt += ' NOT NULL' + ast.IntegerLiteral { + data << expr } - if is_unique { - if typ == .mysql { - mut f := 'UNIQUE KEY($lit$name$lit' - if converted_typ == 'TEXT' { - if unique_len > 0 { - f += '($unique_len)' - } else { - f += '($c.default_unique_str_len)' - } - } - f += ')' - unique_fields << f - } else { - stmt += ' UNIQUE' - } + ast.SelectorExpr { + data << expr } - if is_primary && typ == .sqlite3 { - stmt += ' PRIMARY KEY' + ast.BoolLiteral { + data << expr } - fields << stmt + else {} } - if unique.len > 0 { - for k, v in unique { - mut tmp := []string{} - for f in v { - tmp << '$lit$f$lit' - } - fields << '/* $k */UNIQUE(${tmp.join(', ')})' +} + +fn (mut g Gen) sql_gen_where_data(where_expr ast.Expr) { + g.write('(orm__QueryData){') + mut fields := []string{} + mut kinds := []string{} + mut data := []ast.Expr{} + mut is_and := []bool{} + g.sql_where_data(where_expr, mut fields, mut kinds, mut data, mut is_and) + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.fields = new_array_from_c_array($fields.len, $fields.len, sizeof(string),') + if fields.len > 0 { + g.write(' _MOV((string[$fields.len]){') + for field in fields { + g.write('_SLIT("$field"),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + + g.write('.data = new_array_from_c_array($data.len, $data.len, sizeof(orm__Primitive),') + if data.len > 0 { + g.write(' _MOV((orm__Primitive[$data.len]){') + for e in data { + g.sql_expr_to_orm_primitive(e) } + g.write('})') } - if typ == .mysql || typ == .psql { - fields << 'PRIMARY KEY($lit$primary$lit)' + g.write('),') + + g.write('.kinds = new_array_from_c_array($kinds.len, $kinds.len, sizeof(orm__OperationKind),') + if kinds.len > 0 { + g.write(' _MOV((orm__OperationKind[$kinds.len]){') + for k in kinds { + g.write('$k,') + } + g.write('})') + } else { + g.write('NULL') } - fields << unique_fields - create_string += fields.join(', ') - create_string += ');' - return create_string -} + g.write('),') -fn (mut g Gen) expr_to_sql(expr ast.Expr, typ SqlType) { - // Custom handling for infix exprs (since we need e.g. `and` instead of `&&` in SQL queries), - // strings. Everything else (like numbers, a.b) is handled by g.expr() - // - // TODO `where id = some_column + 1` needs literal generation of `some_column` as a string, - // not a V variable. Need to distinguish column names from V variables. - match expr { - ast.InfixExpr { - g.sql_side = .left - g.expr_to_sql(expr.left, typ) - match expr.op { - .ne { g.write(' != ') } - .eq { g.write(' = ') } - .gt { g.write(' > ') } - .lt { g.write(' < ') } - .ge { g.write(' >= ') } - .le { g.write(' <= ') } - .and { g.write(' and ') } - .logical_or { g.write(' or ') } - .plus { g.write(' + ') } - .minus { g.write(' - ') } - .mul { g.write(' * ') } - .div { g.write(' / ') } - else {} - } - g.sql_side = .right - g.expr_to_sql(expr.right, typ) + g.write('.is_and = new_array_from_c_array($is_and.len, $is_and.len, sizeof(bool),') + if is_and.len > 0 { + g.write(' _MOV((bool[$is_and.len]){') + for b in is_and { + g.write('$b, ') } - ast.StringLiteral { - // g.write("'$it.val'") - g.inc_sql_i(typ) - g.sql_bind('"$expr.val"', expr.val.len.str(), g.sql_get_real_type(ast.string_type), - typ) + g.write('})') + } else { + g.write('NULL') + } + g.write('),}') +} + +fn (mut g Gen) sql_select_expr(node ast.SqlExpr) { + left := g.go_before_stmt(0) + conn := g.new_tmp_var() + g.writeln('') + g.writeln('// orm') + g.write('orm__Connection $conn = (orm__Connection){._') + mut fn_prefix := '' + typ := g.parse_db_type(node.db_expr) + match typ { + .sqlite3 { + fn_prefix = 'sqlite__DB' } - ast.IntegerLiteral { - g.inc_sql_i(typ) - g.sql_bind(expr.val, '', g.sql_get_real_type(ast.int_type), typ) + .mysql { + fn_prefix = 'mysql__Connection' } - ast.BoolLiteral { - // true/false literals were added to Sqlite 3.23 (2018-04-02) - // but lots of apps/distros use older sqlite (e.g. Ubuntu 18.04 LTS ) - g.inc_sql_i(typ) - eval := if expr.val { '1' } else { '0' } - g.sql_bind(eval, '', g.sql_get_real_type(ast.byte_type), typ) + else { + verror('This database type `$typ` is not implemented yet in orm') // TODO add better error } - ast.Ident { - // `name == user_name` => `name == ?1` - // for left sides just add a string, for right sides, generate the bindings - if g.sql_side == .left { - // println("sql gen left $expr.name") - g.sql_left_type = g.get_struct_field_typ(expr.name) - g.write(g.get_field_name(g.get_struct_field(expr.name))) - } else { - g.inc_sql_i(typ) - info := expr.info as ast.IdentVar - ityp := info.typ - if typ == .sqlite3 { - if ityp == ast.string_type { - g.sql_bind('${expr.name}.str', '${expr.name}.len', g.sql_get_real_type(ityp), - typ) - } else { - g.sql_bind(expr.name, '', g.sql_get_real_type(ityp), typ) - } - } else { - g.sql_bind('$g.sql_i.str()', '', g.sql_get_real_type(ityp), typ) - g.sql_idents << expr.name - g.sql_idents_types << g.sql_get_real_type(ityp) - } + } + + g.write('$fn_prefix = &') + g.expr(node.db_expr) + g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};') + g.sql_select(node, conn, left) +} + +fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string) { + mut fields := []ast.StructField{} + mut prim := '' + for f in node.fields { + mut skip := false + for attr in f.attrs { + if attr.name == 'primary' { + prim = f.name } - } - ast.SelectorExpr { - g.inc_sql_i(typ) - if expr.expr !is ast.Ident { - verror('orm selector not ident') + if attr.name == 'skip' { + skip = true } - ident := expr.expr as ast.Ident - g.sql_bind(ident.name + '.' + expr.field_name, '', g.sql_get_real_type(expr.typ), - typ) } - else { - g.expr(expr) + if !skip { + fields << f } } - /* - ast.Ident { - g.write('$it.name') - } - else {} - */ -} -fn (mut g Gen) get_struct_field_typ(f string) ast.Type { - sym := g.table.get_type_symbol(g.table.type_idxs[g.sql_table_name]) - - mut typ := ast.Type(-1) - - if sym.kind != .struct_ { - str := sym.info as ast.Struct - for field in str.fields { - if field.name != f { + res := g.new_tmp_var() + table_name := g.get_table_name(node.table_expr) + g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name + g.write('Option_Array_Array_orm__Primitive _o$res = orm__Connection_name_table[${expr}._typ]._method_select(${expr}._object, ') + g.write('(orm__SelectConfig){') + g.write('.table = _SLIT("$table_name"),') + g.write('.is_count = $node.is_count,') + g.write('.has_where = $node.has_where,') + g.write('.has_order = $node.has_order,') + if node.has_order { + g.write('.order = _SLIT("') + g.expr(node.order_expr) + g.write('"),') + if node.has_desc { + g.write('.order_type = orm__OrderType__desc,') + } else { + g.write('.order_type = orm__OrderType__asc,') + } + } + g.write('.has_limit = $node.has_limit,') + g.write('.has_offset = $node.has_offset,') + if prim != '' { + g.write('.primary = _SLIT("$prim"),') + } + select_fields := fields.filter(g.table.get_type_symbol(it.typ).kind != .array) + g.write('.fields = new_array_from_c_array($select_fields.len, $select_fields.len, sizeof(string),') + mut types := []int{} + if select_fields.len > 0 { + g.write(' _MOV((string[$select_fields.len]){') + for field in select_fields { + g.write('_SLIT("${g.get_field_name(field)}"),') + sym := g.table.get_type_symbol(field.typ) + if sym.kind == .struct_ { + types << int(ast.int_type) continue } - typ = g.get_sql_field_type(field) - break + types << int(field.typ) } + g.write('})') + } else { + g.write('NULL') } + g.write('),') + g.write('.types = new_array_from_c_array($types.len, $types.len, sizeof(int),') + if types.len > 0 { + g.write(' _MOV((int[$types.len]){') + for typ in types { + g.write('$typ,') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),},') - return typ -} - -fn (mut g Gen) sql_get_real_type(typ ast.Type) ast.Type { - if typ != g.sql_left_type && g.sql_left_type >= 0 { - return g.sql_left_type + mut exprs := []ast.Expr{} + if node.has_limit { + exprs << node.limit_expr } - return typ -} + if node.has_offset { + exprs << node.offset_expr + } + g.write('(orm__QueryData) {') + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') + g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.write('.data = new_array_from_c_array($exprs.len, $exprs.len, sizeof(orm__Primitive),') + if exprs.len > 0 { + g.write(' _MOV((orm__Primitive[$exprs.len]){') + for e in exprs { + g.sql_expr_to_orm_primitive(e) + } + g.write('})') + } else { + g.write('NULL') + } + g.write(')},') -fn (mut g Gen) inc_sql_i(typ SqlType) { - g.sql_i++ - if typ == .psql { - g.write('$') + if node.has_where { + g.sql_gen_where_data(node.where_expr) } else { - g.write('?') // used in sqlite `?i` and mysql `?` + g.write('(orm__QueryData) {}') } - if typ != .mysql { - g.write('$g.sql_i') + g.writeln(');') + g.writeln('if (_o${res}.state != 0 && _o${res}.err._typ != _IError_None___index) { _v_panic(IError_str(_o${res}.err)); }') + g.writeln('Array_Array_orm__Primitive $res = (*(Array_Array_orm__Primitive*)_o${res}.data);') + + if node.is_count { + g.writeln('$left *((*(orm__Primitive*) array_get((*(Array_orm__Primitive*)array_get($res, 0)), 0))._int);') + } else { + tmp := g.new_tmp_var() + styp := g.typ(node.typ) + idx := g.new_tmp_var() + g.writeln('int $idx = 0;') + mut typ_str := '' + if node.is_array { + info := g.table.get_type_symbol(node.typ).array_info() + typ_str = g.typ(info.elem_type) + g.writeln('$styp ${tmp}_array = __new_array(0, ${res}.len, sizeof($typ_str));') + g.writeln('for (; $idx < ${res}.len; $idx++) {') + g.write('\t$typ_str $tmp = ($typ_str) {') + inf := g.table.get_type_symbol(info.elem_type).struct_info() + for i, field in inf.fields { + g.zero_struct_field(field) + if i != inf.fields.len - 1 { + g.write(', ') + } + } + g.writeln('};') + } else { + g.write('$styp $tmp = ($styp){') + info := g.table.get_type_symbol(node.typ).struct_info() + for i, field in info.fields { + g.zero_struct_field(field) + if i != info.fields.len - 1 { + g.write(', ') + } + } + g.writeln('};') + } + + g.writeln('if (${res}.len > 0) {') + for i, field in fields { + sel := '(*(orm__Primitive*) array_get((*(Array_orm__Primitive*) array_get($res, $idx)), $i))' + sym := g.table.get_type_symbol(field.typ) + if sym.kind == .struct_ { + mut sub := node.sub_structs[int(field.typ)] + mut where_expr := sub.where_expr as ast.InfixExpr + mut ident := where_expr.right as ast.Ident + name := sel + s := g.table.find_type_idx('orm.Primitive') + if s != 0 { + if ident.info is ast.IdentVar { + mut info := ident.info as ast.IdentVar + info.typ = s + ident.info = info + } + } + ident.name = name + where_expr.right = ident + sub.where_expr = where_expr + + g.sql_select(sub, expr, '${tmp}.$field.name = ') + } else if sym.kind == .array { + mut fkey := '' + for attr in field.attrs { + if attr.name == 'fkey' && attr.has_arg && attr.kind == .string { + fkey = attr.arg + } + } + if fkey == '' { + verror('An field which holds an array, needs a fkey defined') + } + 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 l := where_expr.left as ast.Ident + mut r := where_expr.right as ast.Ident + l.name = fkey + r.name = tmp + where_expr.left = l + where_expr.right = ast.SelectorExpr{ + pos: r.pos + field_name: prim + is_mut: false + expr: r + expr_type: (r.info as ast.IdentVar).typ + typ: ast.int_type + scope: 0 + } + mut arr := ast.SqlExpr{ + typ: field.typ + is_count: sub.is_count + db_expr: sub.db_expr + has_where: sub.has_where + has_offset: sub.has_offset + offset_expr: sub.offset_expr + has_order: sub.has_order + order_expr: sub.order_expr + has_desc: sub.has_desc + is_array: true + pos: sub.pos + has_limit: sub.has_limit + limit_expr: sub.limit_expr + table_expr: sub.table_expr + fields: sub.fields + where_expr: where_expr + } + + g.sql_select(arr, expr, '${tmp}.$field.name = ') + } else { + mut typ := sym.cname + g.writeln('${tmp}.$field.name = *(${sel}._$typ);') + } + } + g.writeln('}') + + if node.is_array { + g.writeln('array_push(&${tmp}_array, _MOV(($typ_str[]){ $tmp }));') + g.writeln('}') + } + + g.write('$left $tmp') + if node.is_array { + g.write('_array') + } + g.writeln(';') } } @@ -1727,20 +784,6 @@ fn (mut g Gen) parse_db_from_type_string(name string) SqlType { } } -fn (mut g Gen) get_sql_field_type(field ast.StructField) ast.Type { - mut typ := field.typ - for attr in field.attrs { - if attr.kind == .plain && attr.name == 'sql' && attr.arg != '' { - if attr.arg.to_lower() == 'serial' { - typ = ast.Type(-1) - break - } - typ = g.table.type_idxs[attr.arg] - } - } - return typ -} - fn (mut g Gen) get_table_name(table_expr ast.TypeNode) string { info := g.table.get_type_symbol(table_expr.typ).struct_info() mut tablename := util.strip_mod_name(g.table.get_type_symbol(table_expr.typ).name) @@ -1772,5 +815,9 @@ fn (mut g Gen) get_field_name(field ast.StructField) string { break } } + sym := g.table.get_type_symbol(field.typ) + if sym.kind == .struct_ { + name = '${name}_id' + } return name } diff --git a/vlib/v/tests/orm_sub_array_struct_test.v b/vlib/v/tests/orm_sub_array_struct_test.v index 8442ba526..f5322be29 100644 --- a/vlib/v/tests/orm_sub_array_struct_test.v +++ b/vlib/v/tests/orm_sub_array_struct_test.v @@ -39,7 +39,6 @@ fn test_orm_array() { } sql db { - drop table Chield drop table Parent } -- 2.30.2