orm: document & fix pg (#14533)
parent
dca8739eeb
commit
29fc96c040
|
@ -102,6 +102,10 @@ fn (kind OrderType) to_str() string {
|
|||
}
|
||||
}
|
||||
|
||||
// Examples for QueryData in SQL: abc == 3 && b == 'test'
|
||||
// => fields[abc, b]; data[3, 'test']; types[index of int, index of string]; kinds[.eq, .eq]; is_and[true];
|
||||
// Every field, data, type & kind of operation in the expr share the same index in the arrays
|
||||
// is_and defines how they're addicted to each other either and or or
|
||||
pub struct QueryData {
|
||||
pub:
|
||||
fields []string
|
||||
|
@ -128,6 +132,17 @@ pub:
|
|||
attrs []StructAttribute
|
||||
}
|
||||
|
||||
// table - Table name
|
||||
// is_count - Either the data will be returned or an integer with the count
|
||||
// has_where - Select all or use a where expr
|
||||
// has_order - Order the results
|
||||
// order - Name of the column which will be ordered
|
||||
// order_type - Type of order (asc, desc)
|
||||
// has_limit - Limits the output data
|
||||
// primary - Name of the primary field
|
||||
// has_offset - Add an offset to the result
|
||||
// fields - Fields to select
|
||||
// types - Types to select
|
||||
pub struct SelectConfig {
|
||||
pub:
|
||||
table string
|
||||
|
@ -143,6 +158,14 @@ pub:
|
|||
types []int
|
||||
}
|
||||
|
||||
// Interfaces gets called from the backend and can be implemented
|
||||
// Since the orm supports arrays aswell, they have to be returned too.
|
||||
// A row is represented as []Primitive, where the data is connected to the fields of the struct by their
|
||||
// index. The indices are mapped with the SelectConfig.field array. This is the mapping for a struct.
|
||||
// To have an array, there has to be an array of structs, basically [][]Primitive
|
||||
//
|
||||
// Every function without last_id() returns an optional, which returns an error if present
|
||||
// last_id returns the last inserted id of the db
|
||||
pub interface Connection {
|
||||
@select(config SelectConfig, data QueryData, where QueryData) ?[][]Primitive
|
||||
insert(table string, data QueryData) ?
|
||||
|
@ -153,7 +176,12 @@ pub interface Connection {
|
|||
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 {
|
||||
// Generates an sql stmt, from universal parameter
|
||||
// q - The quotes character, which can be different in every type, so it's variable
|
||||
// num - Stmt uses nums at prepared statements (? or ?1)
|
||||
// qm - Character for prepared statment, qm because of quotation mark like in sqlite
|
||||
// start_pos - When num is true, it's the start position of the counter
|
||||
pub fn orm_stmt_gen(table string, q string, kind StmtKind, num bool, qm string, start_pos int, data QueryData, where QueryData) string {
|
||||
mut str := ''
|
||||
|
||||
mut c := start_pos
|
||||
|
@ -163,7 +191,7 @@ pub fn orm_stmt_gen(table string, para string, kind StmtKind, num bool, qm strin
|
|||
mut values := []string{}
|
||||
|
||||
for _ in 0 .. data.fields.len {
|
||||
// loop over the length of data.field and generate ?0, ?1 or just ? based on the $num parameter for value placeholders
|
||||
// loop over the length of data.field and generate ?0, ?1 or just ? based on the $num qmeter for value placeholders
|
||||
if num {
|
||||
values << '$qm$c'
|
||||
c++
|
||||
|
@ -172,16 +200,16 @@ pub fn orm_stmt_gen(table string, para string, kind StmtKind, num bool, qm strin
|
|||
}
|
||||
}
|
||||
|
||||
str += 'INSERT INTO $para$table$para ('
|
||||
str += data.fields.map('$para$it$para').join(', ')
|
||||
str += 'INSERT INTO $q$table$q ('
|
||||
str += data.fields.map('$q$it$q').join(', ')
|
||||
str += ') VALUES ('
|
||||
str += values.join(', ')
|
||||
str += ')'
|
||||
}
|
||||
.update {
|
||||
str += 'UPDATE $para$table$para SET '
|
||||
str += 'UPDATE $q$table$q SET '
|
||||
for i, field in data.fields {
|
||||
str += '$para$field$para = '
|
||||
str += '$q$field$q = '
|
||||
if data.data.len > i {
|
||||
d := data.data[i]
|
||||
if d is InfixType {
|
||||
|
@ -217,12 +245,12 @@ pub fn orm_stmt_gen(table string, para string, kind StmtKind, num bool, qm strin
|
|||
str += ' WHERE '
|
||||
}
|
||||
.delete {
|
||||
str += 'DELETE FROM $para$table$para WHERE '
|
||||
str += 'DELETE FROM $q$table$q WHERE '
|
||||
}
|
||||
}
|
||||
if kind == .update || kind == .delete {
|
||||
for i, field in where.fields {
|
||||
str += '$para$field$para ${where.kinds[i].to_str()} $qm'
|
||||
str += '$q$field$q ${where.kinds[i].to_str()} $qm'
|
||||
if num {
|
||||
str += '$c'
|
||||
c++
|
||||
|
@ -236,28 +264,32 @@ pub fn orm_stmt_gen(table string, para string, kind StmtKind, num bool, qm strin
|
|||
return str
|
||||
}
|
||||
|
||||
pub fn orm_select_gen(orm SelectConfig, para string, num bool, qm string, start_pos int, where QueryData) string {
|
||||
// Generates an sql select stmt, from universal parameter
|
||||
// orm - See SelectConfig
|
||||
// q, num, qm, start_pos - see orm_stmt_gen
|
||||
// where - See QueryData
|
||||
pub fn orm_select_gen(orm SelectConfig, q 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'
|
||||
str += '$q$field$q'
|
||||
if i < orm.fields.len - 1 {
|
||||
str += ', '
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str += ' FROM $para$orm.table$para'
|
||||
str += ' FROM $q$orm.table$q'
|
||||
|
||||
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'
|
||||
str += '$q$field$q ${where.kinds[i].to_str()} $qm'
|
||||
if num {
|
||||
str += '$c'
|
||||
c++
|
||||
|
@ -276,7 +308,7 @@ pub fn orm_select_gen(orm SelectConfig, para string, num bool, qm string, start_
|
|||
// ordering is *slow*, especially if there are no indexes!
|
||||
if orm.has_order {
|
||||
str += ' ORDER BY '
|
||||
str += '$para$orm.order$para '
|
||||
str += '$q$orm.order$q '
|
||||
str += orm.order_type.to_str()
|
||||
}
|
||||
|
||||
|
@ -300,11 +332,19 @@ pub fn orm_select_gen(orm SelectConfig, para string, num bool, qm string, start_
|
|||
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 ('
|
||||
// Generates an sql table stmt, from universal parameter
|
||||
// table - Table name
|
||||
// q - see orm_stmt_gen
|
||||
// defaults - enables default values in stmt
|
||||
// def_unique_len - sets default unique length for texts
|
||||
// fields - See TableField
|
||||
// sql_from_v - Function which maps type indices to sql type names
|
||||
// alternative - Needed for msdb
|
||||
pub fn orm_table_gen(table string, q 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 $q$table$q ('
|
||||
|
||||
if alternative {
|
||||
str = 'IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=$para$table$para and xtype=${para}U$para) CREATE TABLE $para$table$para ('
|
||||
str = 'IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=$q$table$q and xtype=${q}U$q) CREATE TABLE $q$table$q ('
|
||||
}
|
||||
|
||||
mut fs := []string{}
|
||||
|
@ -368,7 +408,7 @@ pub fn orm_table_gen(table string, para string, defaults bool, def_unique_len in
|
|||
if ctyp == '' {
|
||||
return error('Unknown type ($field.typ) for field $field.name in struct $table')
|
||||
}
|
||||
stmt = '$para$field_name$para $ctyp'
|
||||
stmt = '$q$field_name$q $ctyp'
|
||||
if defaults && field.default_val != '' {
|
||||
stmt += ' DEFAULT $field.default_val'
|
||||
}
|
||||
|
@ -376,7 +416,7 @@ pub fn orm_table_gen(table string, para string, defaults bool, def_unique_len in
|
|||
stmt += ' NOT NULL'
|
||||
}
|
||||
if is_unique {
|
||||
mut f := 'UNIQUE($para$field_name$para'
|
||||
mut f := 'UNIQUE($q$field_name$q'
|
||||
if ctyp == 'TEXT' && def_unique_len > 0 {
|
||||
if unique_len > 0 {
|
||||
f += '($unique_len)'
|
||||
|
@ -396,18 +436,19 @@ pub fn orm_table_gen(table string, para string, defaults bool, def_unique_len in
|
|||
for k, v in unique {
|
||||
mut tmp := []string{}
|
||||
for f in v {
|
||||
tmp << '$para$f$para'
|
||||
tmp << '$q$f$q'
|
||||
}
|
||||
fs << '/* $k */UNIQUE(${tmp.join(', ')})'
|
||||
}
|
||||
}
|
||||
fs << 'PRIMARY KEY($para$primary$para)'
|
||||
fs << 'PRIMARY KEY($q$primary$q)'
|
||||
fs << unique_fields
|
||||
str += fs.join(', ')
|
||||
str += ');'
|
||||
return str
|
||||
}
|
||||
|
||||
// Get's the sql field type
|
||||
fn sql_field_type(field TableField) int {
|
||||
mut typ := field.typ
|
||||
if field.is_time {
|
||||
|
@ -426,6 +467,7 @@ fn sql_field_type(field TableField) int {
|
|||
return typ
|
||||
}
|
||||
|
||||
// Get's the sql field name
|
||||
fn sql_field_name(field TableField) string {
|
||||
mut name := field.name
|
||||
for attr in field.attrs {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// import os
|
||||
// import pg
|
||||
// import term
|
||||
import time
|
||||
// import pg
|
||||
import sqlite
|
||||
|
||||
struct Module {
|
||||
|
@ -31,9 +31,12 @@ struct TestTime {
|
|||
create time.Time
|
||||
}
|
||||
|
||||
fn test_orm_sqlite() {
|
||||
fn test_orm() {
|
||||
db := sqlite.connect(':memory:') or { panic(err) }
|
||||
db.exec('drop table if exists User')
|
||||
// db.exec('drop table if exists User')
|
||||
|
||||
// db := pg.connect(host: 'localhost', port: 5432, user: 'louis', password: 'abc', dbname: 'orm') or { panic(err) }
|
||||
|
||||
sql db {
|
||||
create table Module
|
||||
}
|
||||
|
@ -242,7 +245,7 @@ fn test_orm_sqlite() {
|
|||
//
|
||||
offset_const := 2
|
||||
z := sql db {
|
||||
select from User limit 2 offset offset_const
|
||||
select from User order by id limit 2 offset offset_const
|
||||
}
|
||||
assert z.len == 2
|
||||
assert z[0].id == 3
|
||||
|
@ -264,6 +267,7 @@ fn test_orm_sqlite() {
|
|||
}
|
||||
assert updated_oldest.age == 31
|
||||
|
||||
// Remove this when pg is used
|
||||
db.exec('insert into User (name, age) values (NULL, 31)')
|
||||
null_user := sql db {
|
||||
select from User where id == 5
|
||||
|
@ -336,11 +340,18 @@ fn test_orm_sqlite() {
|
|||
sql db {
|
||||
update Module set created = t where id == 1
|
||||
}
|
||||
|
||||
updated_time_mod := sql db {
|
||||
select from Module where id == 1
|
||||
}
|
||||
|
||||
// Note: usually updated_time_mod.created != t, because t has
|
||||
// its microseconds set, while the value retrieved from the DB
|
||||
// has them zeroed, because the db field resolution is seconds.
|
||||
assert updated_time_mod.created.format_ss() == t.format_ss()
|
||||
|
||||
sql db {
|
||||
drop table Module
|
||||
drop table TestTime
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,13 @@ import net.conv
|
|||
|
||||
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)
|
||||
|
||||
res := pg_stmt_worker(db, query, where, data)?
|
||||
|
||||
mut ret := [][]orm.Primitive{}
|
||||
|
||||
res := pg_stmt_worker(db, query, orm.QueryData{}, where)?
|
||||
if config.is_count {
|
||||
}
|
||||
|
||||
for row in res {
|
||||
mut row_data := []orm.Primitive{}
|
||||
|
@ -166,7 +170,9 @@ fn pg_stmt_match(mut types []u32, mut vals []&char, mut lens []int, mut formats
|
|||
}
|
||||
time.Time {
|
||||
types << u32(Oid.t_int4)
|
||||
vals << &char(&int(data.unix))
|
||||
unix := int(data.unix)
|
||||
num := conv.htn32(unsafe { &u32(&unix) })
|
||||
vals << &char(&num)
|
||||
lens << int(sizeof(u32))
|
||||
formats << 1
|
||||
}
|
||||
|
@ -178,19 +184,22 @@ fn pg_stmt_match(mut types []u32, mut vals []&char, mut lens []int, mut formats
|
|||
|
||||
fn pg_type_from_v(typ int) ?string {
|
||||
str := match typ {
|
||||
6, 10 {
|
||||
orm.type_idx['i8'], orm.type_idx['i16'], orm.type_idx['byte'], orm.type_idx['u16'] {
|
||||
'SMALLINT'
|
||||
}
|
||||
7, 11, orm.time {
|
||||
orm.type_idx['bool'] {
|
||||
'BOOLEAN'
|
||||
}
|
||||
orm.type_idx['int'], orm.type_idx['u32'], orm.time {
|
||||
'INT'
|
||||
}
|
||||
8, 12 {
|
||||
orm.type_idx['i64'], orm.type_idx['u64'] {
|
||||
'BIGINT'
|
||||
}
|
||||
13 {
|
||||
orm.float[0] {
|
||||
'REAL'
|
||||
}
|
||||
14 {
|
||||
orm.float[1] {
|
||||
'DOUBLE PRECISION'
|
||||
}
|
||||
orm.string {
|
||||
|
@ -212,54 +221,51 @@ fn pg_type_from_v(typ int) ?string {
|
|||
fn str_to_primitive(str string, typ int) ?orm.Primitive {
|
||||
match typ {
|
||||
// bool
|
||||
16 {
|
||||
return orm.Primitive(str.i8() == 1)
|
||||
}
|
||||
18 {
|
||||
orm.type_idx['bool'] {
|
||||
return orm.Primitive(str == 't')
|
||||
}
|
||||
// i8
|
||||
5 {
|
||||
orm.type_idx['i8'] {
|
||||
return orm.Primitive(str.i8())
|
||||
}
|
||||
// i16
|
||||
6 {
|
||||
orm.type_idx['i16'] {
|
||||
return orm.Primitive(str.i16())
|
||||
}
|
||||
// int
|
||||
7 {
|
||||
orm.type_idx['int'] {
|
||||
return orm.Primitive(str.int())
|
||||
}
|
||||
// i64
|
||||
8 {
|
||||
orm.type_idx['i64'] {
|
||||
return orm.Primitive(str.i64())
|
||||
}
|
||||
// byte
|
||||
9 {
|
||||
orm.type_idx['byte'] {
|
||||
data := str.i8()
|
||||
return orm.Primitive(*unsafe { &u8(&data) })
|
||||
}
|
||||
// u16
|
||||
10 {
|
||||
orm.type_idx['u16'] {
|
||||
data := str.i16()
|
||||
return orm.Primitive(*unsafe { &u16(&data) })
|
||||
}
|
||||
// u32
|
||||
11 {
|
||||
orm.type_idx['u32'] {
|
||||
data := str.int()
|
||||
return orm.Primitive(*unsafe { &u32(&data) })
|
||||
}
|
||||
// u64
|
||||
12 {
|
||||
orm.type_idx['u64'] {
|
||||
data := str.i64()
|
||||
return orm.Primitive(*unsafe { &u64(&data) })
|
||||
}
|
||||
// f32
|
||||
13 {
|
||||
orm.type_idx['f32'] {
|
||||
return orm.Primitive(str.f32())
|
||||
}
|
||||
// f64
|
||||
14 {
|
||||
orm.type_idx['f64'] {
|
||||
return orm.Primitive(str.f64())
|
||||
}
|
||||
orm.string {
|
||||
|
|
|
@ -6,6 +6,7 @@ import time
|
|||
// sql expr
|
||||
|
||||
pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ?[][]orm.Primitive {
|
||||
// 1. Create query and bind necessary data
|
||||
query := orm.orm_select_gen(config, '`', true, '?', 1, where)
|
||||
stmt := db.new_init_stmt(query)?
|
||||
mut c := 1
|
||||
|
@ -19,6 +20,7 @@ pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.Qu
|
|||
mut ret := [][]orm.Primitive{}
|
||||
|
||||
if config.is_count {
|
||||
// 2. Get count of returned values & add it to ret array
|
||||
step := stmt.step()
|
||||
if step !in [sqlite_row, sqlite_ok, sqlite_done] {
|
||||
return db.error_message(step, query)
|
||||
|
@ -28,6 +30,7 @@ pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.Qu
|
|||
return ret
|
||||
}
|
||||
for {
|
||||
// 2. Parse returned values
|
||||
step := stmt.step()
|
||||
if step == sqlite_done {
|
||||
break
|
||||
|
@ -83,6 +86,7 @@ pub fn (db DB) drop(table string) ? {
|
|||
|
||||
// helper
|
||||
|
||||
// Executes query and bind prepared statement data directly
|
||||
fn sqlite_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ? {
|
||||
stmt := db.new_init_stmt(query)?
|
||||
mut c := 1
|
||||
|
@ -92,6 +96,7 @@ fn sqlite_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryDa
|
|||
stmt.finalize()
|
||||
}
|
||||
|
||||
// Binds all values of d in the prepared statement
|
||||
fn sqlite_stmt_binder(stmt Stmt, d orm.QueryData, query string, mut c &int) ? {
|
||||
for data in d.data {
|
||||
err := bind(stmt, c, data)
|
||||
|
@ -103,6 +108,7 @@ fn sqlite_stmt_binder(stmt Stmt, d orm.QueryData, query string, mut c &int) ? {
|
|||
}
|
||||
}
|
||||
|
||||
// Universal bind function
|
||||
fn bind(stmt Stmt, c &int, data orm.Primitive) int {
|
||||
mut err := 0
|
||||
match data {
|
||||
|
@ -128,6 +134,7 @@ fn bind(stmt Stmt, c &int, data orm.Primitive) int {
|
|||
return err
|
||||
}
|
||||
|
||||
// Selects column in result and converts it to an orm.Primitive
|
||||
fn (stmt Stmt) sqlite_select_column(idx int, typ int) ?orm.Primitive {
|
||||
mut primitive := orm.Primitive(0)
|
||||
|
||||
|
@ -149,6 +156,7 @@ fn (stmt Stmt) sqlite_select_column(idx int, typ int) ?orm.Primitive {
|
|||
return primitive
|
||||
}
|
||||
|
||||
// Convert type int to sql type string
|
||||
fn sqlite_type_from_v(typ int) ?string {
|
||||
return if typ in orm.nums || typ < 0 || typ in orm.num64 || typ == orm.time {
|
||||
'INTEGER'
|
||||
|
|
Loading…
Reference in New Issue