mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
orm: redesign orm (re-write it in V) (#10353)
This commit is contained in:
parent
ad41cd5c6f
commit
26db3b0995
@ -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` */,
|
||||
|
@ -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',
|
||||
|
@ -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()
|
||||
}
|
||||
}*/
|
||||
|
@ -121,7 +121,7 @@ pub:
|
||||
typ int
|
||||
}
|
||||
|
||||
enum AttributeKind {
|
||||
pub enum AttributeKind {
|
||||
plain // [name]
|
||||
string // ['name']
|
||||
number // [123]
|
||||
|
@ -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)
|
||||
|
||||
|
@ -1,4 +1,11 @@
|
||||
module mysql
|
||||
|
||||
#pkgconfig mysqlclient
|
||||
#include <mysql.h> # 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 <mysql/mysql.h> # Please install the mysqlclient development headers
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
77
vlib/mysql/mysql_orm_test.v
Normal file
77
vlib/mysql/mysql_orm_test.v
Normal 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
Normal file
296
vlib/mysql/orm.v
Normal 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
Normal file
233
vlib/mysql/stmt.c.v
Normal 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
Normal file
475
vlib/orm/orm.v
Normal 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)
|
||||
}
|
193
vlib/orm/orm_fn_test.v
Normal file
193
vlib/orm/orm_fn_test.v
Normal 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')
|
||||
}
|
||||
}
|
@ -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
|
||||
age_test := sql db {
|
||||
select from User where id == 1
|
||||
}
|
||||
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
|
||||
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<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
|
||||
where id > 1 && name == ''
|
||||
println(nr_modules)
|
||||
sql db {
|
||||
update User set age = age * 2 where id == 1
|
||||
}
|
||||
|
||||
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<Module>(m => m.nr_downloads > 1000).order_by(m => m.nr_downloads).desc().limit(10)
|
||||
names := select name from db.modules // []string
|
||||
first = sql db {
|
||||
select from User where id == 1
|
||||
}
|
||||
|
||||
|
||||
n := db.q_int('select count(*) from modules')
|
||||
println(n)
|
||||
*/
|
||||
assert first.age == 60
|
||||
}
|
||||
|
161
vlib/sqlite/orm.v
Normal file
161
vlib/sqlite/orm.v
Normal 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')
|
||||
}
|
||||
}
|
@ -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<T>(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') + ')')
|
||||
|
70
vlib/sqlite/sqlite_orm_test.v
Normal file
70
vlib/sqlite/sqlite_orm_test.v
Normal 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
Normal file
74
vlib/sqlite/stmt.v
Normal 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)
|
||||
}
|
@ -1480,12 +1480,12 @@ 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`
|
||||
pub mut:
|
||||
object_var_name string // `user`
|
||||
updated_columns []string // for `update set x=y`
|
||||
table_expr TypeNode
|
||||
fields []StructField
|
||||
sub_structs map[int]SqlStmtLine
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
2257
vlib/v/gen/c/sql.v
2257
vlib/v/gen/c/sql.v
File diff suppressed because it is too large
Load Diff
@ -39,7 +39,6 @@ fn test_orm_array() {
|
||||
}
|
||||
|
||||
sql db {
|
||||
drop table Chield
|
||||
drop table Parent
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user