orm: redesign orm (re-write it in V) (#10353)
parent
ad41cd5c6f
commit
26db3b0995
|
@ -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` */,
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}*/
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
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)
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
#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') + ')')
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 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
|
||||||
updated_columns []string // for `update set x=y`
|
|
||||||
update_exprs []Expr // for `update`
|
update_exprs []Expr // for `update`
|
||||||
pub mut:
|
pub mut:
|
||||||
|
object_var_name string // `user`
|
||||||
|
updated_columns []string // for `update set x=y`
|
||||||
table_expr TypeNode
|
table_expr TypeNode
|
||||||
fields []StructField
|
fields []StructField
|
||||||
sub_structs map[int]SqlStmtLine
|
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.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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
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 {
|
sql db {
|
||||||
drop table Chield
|
|
||||||
drop table Parent
|
drop table Parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue