orm: redesign orm (re-write it in V) (#10353)

pull/10901/head
Louis Schmieder 2021-07-23 11:33:55 +02:00 committed by GitHub
parent ad41cd5c6f
commit 26db3b0995
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 2350 additions and 1693 deletions

View File

@ -28,6 +28,7 @@ const (
'vlib/gg/m4/graphic.v', 'vlib/gg/m4/graphic.v',
'vlib/gg/m4/m4_test.v', 'vlib/gg/m4/m4_test.v',
'vlib/gg/m4/matrix.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 */, 'vlib/builtin/int_test.v' /* special number formatting that should be tested */,
// TODOs and unfixed vfmt bugs // 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` */, 'vlib/builtin/int.v' /* TODO byteptr: vfmt converts `pub fn (nn byteptr) str() string {` to `nn &byte` and that conflicts with `nn byte` */,

View File

@ -9,6 +9,7 @@ const github_job = os.getenv('GITHUB_JOB')
const ( const (
skip_test_files = [ skip_test_files = [
'vlib/context/deadline_test.v' /* sometimes blocks */, 'vlib/context/deadline_test.v' /* sometimes blocks */,
'vlib/mysql/mysql_orm_test.v' /* mysql not installed */,
] ]
skip_fsanitize_too_slow = [ skip_fsanitize_too_slow = [
// These tests are too slow to be run in the CI on each PR/commit // 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/net/tcp_test.v',
'vlib/orm/orm_test.v', 'vlib/orm/orm_test.v',
'vlib/sqlite/sqlite_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_struct_test.v',
'vlib/v/tests/orm_sub_array_struct_test.v', 'vlib/v/tests/orm_sub_array_struct_test.v',
'vlib/vweb/tests/vweb_test.v', 'vlib/vweb/tests/vweb_test.v',
@ -72,6 +74,7 @@ const (
'vlib/net/http/status_test.v', 'vlib/net/http/status_test.v',
'vlib/net/websocket/ws_test.v', 'vlib/net/websocket/ws_test.v',
'vlib/sqlite/sqlite_test.v', 'vlib/sqlite/sqlite_test.v',
'vlib/sqlite/sqlite_orm_test.v',
'vlib/orm/orm_test.v', 'vlib/orm/orm_test.v',
'vlib/v/tests/orm_sub_struct_test.v', 'vlib/v/tests/orm_sub_struct_test.v',
'vlib/v/tests/orm_sub_array_struct_test.v', 'vlib/v/tests/orm_sub_array_struct_test.v',

View File

@ -1,6 +1,6 @@
import sqlite import sqlite
import mysql import mysql
import pg // import pg
[table: 'modules'] [table: 'modules']
struct Module { struct Module {
@ -33,11 +33,11 @@ struct Child {
fn main() { fn main() {
sqlite3_array() sqlite3_array()
mysql_array() mysql_array()
psql_array() // psql_array()
sqlite3() sqlite3()
mysql() mysql()
psql() // psql()
} }
fn sqlite3_array() { fn sqlite3_array() {
@ -118,6 +118,7 @@ fn mysql_array() {
db.close() db.close()
} }
/*
fn psql_array() { fn psql_array() {
mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or { mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or {
panic(err) panic(err)
@ -155,7 +156,7 @@ fn psql_array() {
} }
db.close() db.close()
} }*/
fn sqlite3() { fn sqlite3() {
mut db := sqlite.connect(':memory:') or { panic(err) } mut db := sqlite.connect(':memory:') or { panic(err) }
@ -224,6 +225,7 @@ fn mysql() {
conn.close() conn.close()
} }
/*
fn psql() { fn psql() {
mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or { mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or {
panic(err) panic(err)
@ -257,4 +259,4 @@ fn psql() {
eprintln(modul) eprintln(modul)
db.close() db.close()
} }*/

View File

@ -121,7 +121,7 @@ pub:
typ int typ int
} }
enum AttributeKind { pub enum AttributeKind {
plain // [name] plain // [name]
string // ['name'] string // ['name']
number // [123] number // [123]

View File

@ -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_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) fn C.mysql_close(sock &C.MYSQL)

View File

@ -1,4 +1,11 @@
module mysql module mysql
#pkgconfig mysqlclient // Need to check if mysqlclient is not there and use mariadb as alternative because newer system doesn't support mysql 8.0 as default
#include <mysql.h> # Please install the mysqlclient development headers
$if $pkgconfig('mysqlclient') {
#pkgconfig mysqlclient
} $else {
#pkgconfig mariadb
}
#include <mysql/mysql.h> # Please install the mysqlclient development headers

View File

@ -69,3 +69,10 @@ pub fn (f FieldType) str() string {
.type_geometry { 'geometry' } .type_geometry { 'geometry' }
} }
} }
pub fn (f FieldType) get_len() u32 {
return match f {
.type_blob { 262140 }
else { 0 }
}
}

View File

@ -19,6 +19,11 @@ pub enum ConnectionFlag {
client_remember_options = C.CLIENT_REMEMBER_OPTIONS client_remember_options = C.CLIENT_REMEMBER_OPTIONS
} }
struct SQLError {
msg string
code int
}
// TODO: Documentation // TODO: Documentation
pub struct Connection { pub struct Connection {
mut: mut:
@ -46,7 +51,7 @@ pub fn (mut conn Connection) connect() ?bool {
// query - make an SQL query and receive the results. // query - make an SQL query and receive the results.
// `query()` cannot be used for statements that contain binary data; // `query()` cannot be used for statements that contain binary data;
// Use `real_query()` instead. // 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 { if C.mysql_query(conn.conn, q.str) != 0 {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) 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 { pub fn (conn &Connection) escape_string(s string) string {
unsafe { unsafe {
to := malloc_noscan(2 * s.len + 1) 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() return to.vstring()
} }
} }

View File

@ -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
}
}

296
vlib/mysql/orm.v 100644
View File

@ -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)
}
}
}

233
vlib/mysql/stmt.c.v 100644
View File

@ -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)
}
}

475
vlib/orm/orm.v 100644
View File

@ -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)
}

View File

@ -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')
}
}

View File

@ -4,9 +4,10 @@
import sqlite import sqlite
struct Module { struct Module {
id int id int [primary; sql: serial]
name string name string
nr_downloads int nr_downloads int
user User
} }
[table: 'userlist'] [table: 'userlist']
@ -260,57 +261,30 @@ fn test_orm_sqlite() {
select from User where id == 5 select from User where id == 5
} }
assert null_user.name == '' assert null_user.name == ''
}
fn test_orm_pg() { age_test := sql db {
/* select from User where id == 1
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)
mod := db.select from Modules where id = 1 limit 1 assert age_test.age == 29
println(mod)
mods := db.select from Modules limit 10 sql db {
for mod in mods { update User set age = age + 1 where id == 1
println(mod)
} }
*/
/*
mod := db.retrieve<Module>(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
}
assert first.age == 30
nr_modules := db.select count from Modules sql db {
where id > 1 && name == '' update User set age = age * 2 where id == 1
println(nr_modules) }
nr_modules := db.select count from modules first = sql db {
nr_modules := db.select from modules select from User where id == 1
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<Module>(m => m.nr_downloads > 1000).order_by(m => m.nr_downloads).desc().limit(10)
names := select name from db.modules // []string
assert first.age == 60
n := db.q_int('select count(*) from modules')
println(n)
*/
} }

161
vlib/sqlite/orm.v 100644
View File

@ -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')
}
}

View File

@ -14,12 +14,24 @@ $if windows {
#include "sqlite3.h" #include "sqlite3.h"
const (
sqlite_ok = 0
sqlite_error = 1
sqlite_row = 100
sqlite_done = 101
)
struct C.sqlite3 { struct C.sqlite3 {
} }
struct C.sqlite3_stmt { struct C.sqlite3_stmt {
} }
struct Stmt {
stmt &C.sqlite3_stmt
db &DB
}
struct SQLError { struct SQLError {
msg string msg string
code int code int
@ -72,6 +84,8 @@ fn C.sqlite3_column_count(&C.sqlite3_stmt) int
// //
fn C.sqlite3_errstr(int) &char fn C.sqlite3_errstr(int) &char
fn C.sqlite3_errmsg(&C.sqlite3) &char
fn C.sqlite3_free(voidptr) fn C.sqlite3_free(voidptr)
// connect Opens the connection with a database. // connect Opens the connection with a database.
@ -106,14 +120,6 @@ pub fn (mut db DB) close() ?bool {
return true // successfully closed 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 // Only for V ORM
fn get_int_from_stmt(stmt &C.sqlite3_stmt) int { fn get_int_from_stmt(stmt &C.sqlite3_stmt) int {
x := C.sqlite3_step(stmt) x := C.sqlite3_step(stmt)
@ -204,6 +210,14 @@ pub fn (db DB) exec_one(query string) ?Row {
return rows[0] 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 // In case you don't expect any result, but still want an error code
// e.g. INSERT INTO ... VALUES (...) // e.g. INSERT INTO ... VALUES (...)
pub fn (db DB) exec_none(query string) int { 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) exec_param(query string, param string) []Row {
} }
*/ */
pub fn (db DB) insert<T>(x T) {
}
pub fn (db DB) create_table(table_name string, columns []string) { pub fn (db DB) create_table(table_name string, columns []string) {
db.exec('create table if not exists $table_name (' + columns.join(',\n') + ')') db.exec('create table if not exists $table_name (' + columns.join(',\n') + ')')

View File

@ -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
}
}

74
vlib/sqlite/stmt.v 100644
View File

@ -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)
}

View File

@ -1479,16 +1479,16 @@ pub mut:
pub struct SqlStmtLine { pub struct SqlStmtLine {
pub: pub:
kind SqlStmtKind kind SqlStmtKind
object_var_name string // `user` pos token.Position
pos token.Position where_expr Expr
where_expr Expr update_exprs []Expr // for `update`
updated_columns []string // for `update set x=y`
update_exprs []Expr // for `update`
pub mut: pub mut:
table_expr TypeNode object_var_name string // `user`
fields []StructField updated_columns []string // for `update set x=y`
sub_structs map[int]SqlStmtLine table_expr TypeNode
fields []StructField
sub_structs map[int]SqlStmtLine
} }
pub struct SqlExpr { pub struct SqlExpr {

View File

@ -7655,6 +7655,10 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
} }
node.fields = fields node.fields = fields
node.sub_structs = sub_structs.move() 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 { if node.kind == .update {
for expr in node.update_exprs { for expr in node.update_exprs {
c.expr(expr) c.expr(expr)
@ -7683,6 +7687,21 @@ fn (mut c Checker) fetch_and_verify_orm_fields(info ast.Struct, pos token.Positi
return fields 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() { fn (mut c Checker) post_process_generic_fns() {
// Loop thru each generic function concrete type. // Loop thru each generic function concrete type.
// Check each specific fn instantiation. // Check each specific fn instantiation.

View File

@ -3454,7 +3454,7 @@ fn (mut g Gen) expr(node ast.Expr) {
g.write('/*OffsetOf*/ (u32)(__offsetof(${util.no_dots(styp)}, $node.field))') g.write('/*OffsetOf*/ (u32)(__offsetof(${util.no_dots(styp)}, $node.field))')
} }
ast.SqlExpr { ast.SqlExpr {
g.sql_select_expr(node, false, '') g.sql_select_expr(node)
} }
ast.StringLiteral { ast.StringLiteral {
g.string_literal(node) g.string_literal(node)

File diff suppressed because it is too large Load Diff

View File

@ -39,7 +39,6 @@ fn test_orm_array() {
} }
sql db { sql db {
drop table Chield
drop table Parent drop table Parent
} }