1
0
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:
Louis Schmieder
2021-07-23 11:33:55 +02:00
committed by GitHub
parent ad41cd5c6f
commit 26db3b0995
23 changed files with 2350 additions and 1693 deletions

161
vlib/sqlite/orm.v Normal file
View File

@@ -0,0 +1,161 @@
module sqlite
import orm
import time
// sql expr
pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ?[][]orm.Primitive {
query := orm.orm_select_gen(config, '`', true, '?', 1, where)
stmt := db.new_init_stmt(query) ?
mut c := 1
sqlite_stmt_binder(stmt, where, query, mut c) ?
sqlite_stmt_binder(stmt, data, query, mut c) ?
defer {
stmt.finalize()
}
mut ret := [][]orm.Primitive{}
if config.is_count {
step := stmt.step()
if step !in [sqlite_row, sqlite_ok, sqlite_done] {
return db.error_message(step, query)
}
count := stmt.sqlite_select_column(0, 8) ?
ret << [count]
return ret
}
for {
step := stmt.step()
if step == sqlite_done {
break
}
if step != sqlite_ok && step != sqlite_row {
break
}
mut row := []orm.Primitive{}
for i, typ in config.types {
primitive := stmt.sqlite_select_column(i, typ) ?
row << primitive
}
ret << row
}
return ret
}
// sql stmt
pub fn (db DB) insert(table string, data orm.QueryData) ? {
query := orm.orm_stmt_gen(table, '`', .insert, true, '?', 1, data, orm.QueryData{})
sqlite_stmt_worker(db, query, data, orm.QueryData{}) ?
}
pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ? {
query := orm.orm_stmt_gen(table, '`', .update, true, '?', 1, data, where)
sqlite_stmt_worker(db, query, data, where) ?
}
pub fn (db DB) delete(table string, where orm.QueryData) ? {
query := orm.orm_stmt_gen(table, '`', .delete, true, '?', 1, orm.QueryData{}, where)
sqlite_stmt_worker(db, query, orm.QueryData{}, where) ?
}
pub fn (db DB) last_id() orm.Primitive {
query := 'SELECT last_insert_rowid();'
id := db.q_int(query)
return orm.Primitive(id)
}
// table
pub fn (db DB) create(table string, fields []orm.TableField) ? {
query := orm.orm_table_gen(table, '`', true, 0, fields, sqlite_type_from_v, false) or {
return err
}
sqlite_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ?
}
pub fn (db DB) drop(table string) ? {
query := 'DROP TABLE `$table`;'
sqlite_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ?
}
// helper
fn sqlite_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ? {
stmt := db.new_init_stmt(query) ?
mut c := 1
sqlite_stmt_binder(stmt, data, query, mut c) ?
sqlite_stmt_binder(stmt, where, query, mut c) ?
stmt.orm_step(query) ?
stmt.finalize()
}
fn sqlite_stmt_binder(stmt Stmt, d orm.QueryData, query string, mut c &int) ? {
for data in d.data {
err := bind(stmt, c, data)
if err != 0 {
return stmt.db.error_message(err, query)
}
c++
}
}
fn bind(stmt Stmt, c &int, data orm.Primitive) int {
mut err := 0
match data {
i8, i16, int, byte, u16, u32, bool {
err = stmt.bind_int(c, int(data))
}
i64, u64 {
err = stmt.bind_i64(c, i64(data))
}
f32, f64 {
err = stmt.bind_f64(c, unsafe { *(&f64(&data)) })
}
string {
err = stmt.bind_text(c, data)
}
time.Time {
err = stmt.bind_int(c, int(data.unix))
}
orm.InfixType {
err = bind(stmt, c, data.right)
}
}
return err
}
fn (stmt Stmt) sqlite_select_column(idx int, typ int) ?orm.Primitive {
mut primitive := orm.Primitive(0)
if typ in orm.nums || typ == -1 {
primitive = stmt.get_int(idx)
} else if typ in orm.num64 {
primitive = stmt.get_i64(idx)
} else if typ in orm.float {
primitive = stmt.get_f64(idx)
} else if typ == orm.string {
primitive = stmt.get_text(idx).clone()
} else if typ == orm.time {
primitive = time.unix(stmt.get_int(idx))
} else {
return error('Unknown type $typ')
}
return primitive
}
fn sqlite_type_from_v(typ int) ?string {
return if typ in orm.nums || typ < 0 || typ in orm.num64 {
'INTEGER'
} else if typ in orm.float {
'REAL'
} else if typ == orm.string {
'TEXT'
} else {
error('Unknown type $typ')
}
}

View File

@@ -14,12 +14,24 @@ $if windows {
#include "sqlite3.h"
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') + ')')

View File

@@ -0,0 +1,70 @@
import orm
import sqlite
fn test_sqlite_orm() {
sdb := sqlite.connect(':memory:') or { panic(err) }
db := orm.Connection(sdb)
db.create('Test', [
orm.TableField{
name: 'id'
typ: 7
attrs: [
StructAttribute{
name: 'primary'
},
StructAttribute{
name: 'sql'
has_arg: true
kind: .plain
arg: 'serial'
},
]
},
orm.TableField{
name: 'name'
typ: 18
attrs: []
},
orm.TableField{
name: 'age'
typ: 8
},
]) or { panic(err) }
db.insert('Test', orm.QueryData{
fields: ['name', 'age']
data: [orm.string_to_primitive('Louis'), orm.i64_to_primitive(100)]
}) or { panic(err) }
res := db.@select(orm.SelectConfig{
table: 'Test'
has_where: true
fields: ['id', 'name', 'age']
types: [7, 18, 8]
}, orm.QueryData{}, orm.QueryData{
fields: ['name', 'age']
data: [orm.Primitive('Louis'), i64(100)]
types: [18, 8]
is_and: [true, true]
kinds: [.eq, .eq]
}) or { panic(err) }
id := res[0][0]
name := res[0][1]
age := res[0][2]
assert id is int
if id is int {
assert id == 1
}
assert name is string
if name is string {
assert name == 'Louis'
}
assert age is i64
if age is i64 {
assert age == 100
}
}

74
vlib/sqlite/stmt.v Normal file
View File

@@ -0,0 +1,74 @@
module sqlite
fn C.sqlite3_bind_double(&C.sqlite3_stmt, int, f64) int
fn C.sqlite3_bind_int(&C.sqlite3_stmt, int, int) int
fn C.sqlite3_bind_int64(&C.sqlite3_stmt, int, i64) int
fn C.sqlite3_bind_text(&C.sqlite3_stmt, int, &char, int, voidptr) int
// Only for V ORM
fn (db DB) init_stmt(query string) (&C.sqlite3_stmt, int) {
// println('init_stmt("$query")')
stmt := &C.sqlite3_stmt(0)
err := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
return stmt, err
}
fn (db DB) new_init_stmt(query string) ?Stmt {
stmt, err := db.init_stmt(query)
if err != sqlite_ok {
return db.error_message(err, query)
}
return Stmt{stmt, unsafe { &db }}
}
fn (stmt Stmt) bind_int(idx int, v int) int {
return C.sqlite3_bind_int(stmt.stmt, idx, v)
}
fn (stmt Stmt) bind_i64(idx int, v i64) int {
return C.sqlite3_bind_int64(stmt.stmt, idx, v)
}
fn (stmt Stmt) bind_f64(idx int, v f64) int {
return C.sqlite3_bind_double(stmt.stmt, idx, v)
}
fn (stmt Stmt) bind_text(idx int, s string) int {
return C.sqlite3_bind_text(stmt.stmt, idx, voidptr(s.str), s.len, 0)
}
fn (stmt Stmt) get_int(idx int) int {
return C.sqlite3_column_int(stmt.stmt, idx)
}
fn (stmt Stmt) get_i64(idx int) i64 {
return C.sqlite3_column_int64(stmt.stmt, idx)
}
fn (stmt Stmt) get_f64(idx int) f64 {
return C.sqlite3_column_double(stmt.stmt, idx)
}
fn (stmt Stmt) get_text(idx int) string {
b := &char(C.sqlite3_column_text(stmt.stmt, idx))
return unsafe { b.vstring() }
}
fn (stmt Stmt) get_count() int {
return C.sqlite3_column_count(stmt.stmt)
}
fn (stmt Stmt) step() int {
return C.sqlite3_step(stmt.stmt)
}
fn (stmt Stmt) orm_step(query string) ? {
res := stmt.step()
if res != sqlite_ok && res != sqlite_done && res != sqlite_row {
return stmt.db.error_message(res, query)
}
}
fn (stmt Stmt) finalize() {
C.sqlite3_finalize(stmt.stmt)
}