mirror of
https://github.com/vlang/v.git
synced 2025-08-03 17:57:59 -04:00
876 lines
22 KiB
V
876 lines
22 KiB
V
module orm
|
|
|
|
import time
|
|
import strings.textscanner
|
|
|
|
const operators = ['=', '!=', '<>', '>=', '<=', '>', '<', 'LIKE', 'ILIKE', 'IS NULL', 'IS NOT NULL',
|
|
'IN', 'NOT IN']!
|
|
|
|
@[heap]
|
|
pub struct QueryBuilder[T] {
|
|
pub mut:
|
|
meta []TableField
|
|
valid_sql_field_names []string
|
|
conn Connection
|
|
config SelectConfig
|
|
data QueryData
|
|
where QueryData
|
|
}
|
|
|
|
// new_query create a new query object for struct `T`
|
|
pub fn new_query[T](conn Connection) &QueryBuilder[T] {
|
|
meta := struct_meta[T]()
|
|
return &QueryBuilder[T]{
|
|
meta: meta
|
|
valid_sql_field_names: meta.map(sql_field_name(it))
|
|
conn: conn
|
|
config: SelectConfig{
|
|
table: table_from_struct[T]()
|
|
}
|
|
data: QueryData{}
|
|
where: QueryData{}
|
|
}
|
|
}
|
|
|
|
// reset reset a query object, but keep the connection and table name
|
|
pub fn (qb_ &QueryBuilder[T]) reset() &QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
old_table := qb.config.table
|
|
qb.config = SelectConfig{
|
|
table: old_table
|
|
}
|
|
qb.data = QueryData{}
|
|
qb.where = QueryData{}
|
|
return qb
|
|
}
|
|
|
|
// where create a `where` clause, it will `AND` with previous `where` clause.
|
|
// valid token in the `condition` include: `field's names`, `operator`, `(`, `)`, `?`, `AND`, `OR`, `||`, `&&`,
|
|
// valid `operator` incldue: `=`, `!=`, `<>`, `>=`, `<=`, `>`, `<`, `LIKE`, `ILIKE`, `IS NULL`, `IS NOT NULL`, `IN`, `NOT IN`
|
|
// example: `where('(a > ? AND b <= ?) OR (c <> ? AND (x = ? OR y = ?))', a, b, c, x, y)`
|
|
pub fn (qb_ &QueryBuilder[T]) where(condition string, params ...Primitive) !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
if qb.where.fields.len > 0 {
|
|
// skip first field
|
|
qb.where.is_and << true // and
|
|
}
|
|
qb.parse_conditions(condition, params)!
|
|
qb.config.has_where = true
|
|
return qb
|
|
}
|
|
|
|
// or_where create a `where` clause, it will `OR` with previous `where` clause.
|
|
pub fn (qb_ &QueryBuilder[T]) or_where(condition string, params ...Primitive) !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
if qb.where.fields.len > 0 {
|
|
// skip first field
|
|
qb.where.is_and << false // or
|
|
}
|
|
qb.parse_conditions(condition, params)!
|
|
qb.config.has_where = true
|
|
return qb
|
|
}
|
|
|
|
fn parse_error(msg string, pos int, conds string) ! {
|
|
mut m := msg + '\n' + '\t' + conds + '\n\t' + ' '.repeat(pos) + '^\n'
|
|
return error(m)
|
|
}
|
|
|
|
enum ParserState {
|
|
field
|
|
op
|
|
qm
|
|
}
|
|
|
|
struct MyTextScanner {
|
|
textscanner.TextScanner
|
|
mut:
|
|
last_tok_start int
|
|
}
|
|
|
|
// next_tok get next token from scanner, skip whitespace
|
|
fn (mut ss MyTextScanner) next_tok() string {
|
|
mut ret := ''
|
|
ss.skip_whitespace()
|
|
ss.last_tok_start = ss.pos
|
|
|
|
// check for longest token first
|
|
if ss.input[ss.pos..].starts_with('IS NOT NULL') {
|
|
ss.pos += 11
|
|
return 'IS NOT NULL'
|
|
}
|
|
if ss.input[ss.pos..].starts_with('IS NULL') {
|
|
ss.pos += 7
|
|
return 'IS NULL'
|
|
}
|
|
if ss.input[ss.pos..].starts_with('NOT IN') {
|
|
ss.pos += 6
|
|
return 'NOT IN'
|
|
}
|
|
if ss.remaining() >= 2 {
|
|
two_chars := ss.input[ss.pos..ss.pos + 2]
|
|
if two_chars in ['>=', '<=', '<>', '!=', '||', '&&', 'IN'] {
|
|
ss.pos += 2
|
|
return two_chars
|
|
}
|
|
}
|
|
if ss.remaining() > 0 {
|
|
c := ss.input[ss.pos]
|
|
if c in [`>`, `<`, `=`] {
|
|
ss.pos++
|
|
return c.ascii_str()
|
|
}
|
|
}
|
|
for ss.remaining() > 0 {
|
|
c := u8(ss.next()) // only support ascii now
|
|
if c.is_alnum() || c == `_` || c == `$` {
|
|
ret += c.ascii_str()
|
|
} else {
|
|
if ret.len == 0 {
|
|
ret = c.ascii_str()
|
|
} else {
|
|
// already contain a tok
|
|
ss.back()
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// parse_conditions update `qb` by parsing the `conds` string
|
|
fn (qb_ &QueryBuilder[T]) parse_conditions(conds string, params []Primitive) ! {
|
|
// conditions: '(a > ? AND b <= ?) OR (c <> ? AND (x = ? OR y = ?))'
|
|
mut qb := unsafe { qb_ }
|
|
if conds.len == 0 {
|
|
return error('${@FN}(): empty condition')
|
|
}
|
|
required_params := conds.count('?')
|
|
if required_params != params.len {
|
|
parse_error('${@FN}(): condition requires `${required_params}` params but got `${params.len}`',
|
|
0, conds)!
|
|
}
|
|
|
|
mut s := MyTextScanner{
|
|
input: conds
|
|
ilen: conds.len
|
|
}
|
|
|
|
mut state := ParserState.field
|
|
mut tok := ''
|
|
mut current_field := ''
|
|
mut current_op := OperationKind.eq
|
|
mut current_is_and := true
|
|
mut i := 0
|
|
mut paren_stack := []int{}
|
|
mut is_first_field := true
|
|
for s.remaining() > 0 {
|
|
tok = s.next_tok()
|
|
match state {
|
|
.field {
|
|
// only support valid field names
|
|
if tok in qb.valid_sql_field_names {
|
|
current_field = tok
|
|
state = .op
|
|
} else if tok == '(' {
|
|
paren_stack << qb.where.fields.len
|
|
} else if tok == ')' {
|
|
if paren_stack.len == 0 {
|
|
parse_error('${@FN}: unexpected `)`', s.last_tok_start, conds)!
|
|
}
|
|
start_pos := paren_stack.pop()
|
|
qb.where.parentheses << [start_pos, qb.where.fields.len - 1]
|
|
} else {
|
|
parse_error("${@FN}: table `${qb.config.table}` has no field's name: `${tok}`",
|
|
s.last_tok_start, conds)!
|
|
}
|
|
}
|
|
.op {
|
|
current_op = match tok {
|
|
'=' {
|
|
OperationKind.eq
|
|
}
|
|
'<>' {
|
|
OperationKind.neq
|
|
}
|
|
'!=' {
|
|
OperationKind.neq
|
|
}
|
|
'>' {
|
|
OperationKind.gt
|
|
}
|
|
'<' {
|
|
OperationKind.lt
|
|
}
|
|
'>=' {
|
|
OperationKind.ge
|
|
}
|
|
'<=' {
|
|
OperationKind.le
|
|
}
|
|
'LIKE' {
|
|
OperationKind.orm_like
|
|
}
|
|
'ILIKE' {
|
|
OperationKind.orm_ilike
|
|
}
|
|
'IS NULL' {
|
|
OperationKind.is_null
|
|
}
|
|
'IS NOT NULL' {
|
|
OperationKind.is_not_null
|
|
}
|
|
'IN' {
|
|
OperationKind.in
|
|
}
|
|
'NOT IN' {
|
|
OperationKind.not_in
|
|
}
|
|
else {
|
|
parse_error('${@FN}(): unsupported operator: `${tok}`', s.last_tok_start,
|
|
conds)!
|
|
OperationKind.eq
|
|
}
|
|
}
|
|
if current_op in [.is_null, .is_not_null]! {
|
|
qb.where.fields << current_field
|
|
qb.where.kinds << current_op
|
|
if is_first_field {
|
|
is_first_field = false
|
|
} else {
|
|
// skip first field
|
|
qb.where.is_and << current_is_and
|
|
}
|
|
}
|
|
state = .qm
|
|
}
|
|
.qm {
|
|
if tok == '?' {
|
|
// finish an expr, update `qb`
|
|
qb.where.fields << current_field
|
|
qb.where.data << params[i]
|
|
qb.where.kinds << current_op
|
|
if is_first_field {
|
|
is_first_field = false
|
|
} else {
|
|
// skip first field
|
|
qb.where.is_and << current_is_and
|
|
}
|
|
i++
|
|
} else if tok == ')' {
|
|
if paren_stack.len == 0 {
|
|
parse_error('${@FN}: unexpected `)`', s.last_tok_start, conds)!
|
|
}
|
|
start_pos := paren_stack.pop()
|
|
qb.where.parentheses << [start_pos, qb.where.fields.len - 1]
|
|
} else if tok == 'AND' {
|
|
current_is_and = true
|
|
state = .field
|
|
} else if tok == 'OR' {
|
|
current_is_and = false
|
|
state = .field
|
|
} else if tok == '&&' {
|
|
current_is_and = true
|
|
state = .field
|
|
} else if tok == '||' {
|
|
current_is_and = false
|
|
state = .field
|
|
} else {
|
|
parse_error('${@FN}(): unexpected `${tok}`, maybe `AND`,`OR`', s.last_tok_start,
|
|
conds)!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// order create a `order` clause
|
|
pub fn (qb_ &QueryBuilder[T]) order(order_type OrderType, field string) !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
if field in qb.valid_sql_field_names {
|
|
qb.config.has_order = true
|
|
qb.config.order = field
|
|
qb.config.order_type = order_type
|
|
} else {
|
|
return error("${@FN}(): table `${qb.config.table}` has no field's name: `${field}`")
|
|
}
|
|
return qb
|
|
}
|
|
|
|
// limit create a `limit` clause
|
|
pub fn (qb_ &QueryBuilder[T]) limit(limit int) !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
if limit > 0 {
|
|
qb.config.has_limit = true
|
|
qb.data.data << Primitive(limit)
|
|
qb.data.types << type_idx['int']
|
|
} else {
|
|
return error('${@FN}(): `limit` should be a positive integer')
|
|
}
|
|
return qb
|
|
}
|
|
|
|
// offset create a `offset` clause
|
|
pub fn (qb_ &QueryBuilder[T]) offset(offset int) !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
if offset >= 0 {
|
|
qb.config.has_offset = true
|
|
qb.data.data << Primitive(offset)
|
|
qb.data.types << type_idx['int']
|
|
} else {
|
|
return error('${@FN}(): `offset` should be a integer > 0')
|
|
}
|
|
return qb
|
|
}
|
|
|
|
// select create a `select` clause
|
|
pub fn (qb_ &QueryBuilder[T]) select(fields ...string) !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
for f in fields {
|
|
if f !in qb.valid_sql_field_names {
|
|
return error("${@FN}(): table `${qb.config.table}` has no field's name: `${f}`")
|
|
}
|
|
}
|
|
qb.config.fields = fields
|
|
return qb
|
|
}
|
|
|
|
// set create a `set` clause for `update`
|
|
pub fn (qb_ &QueryBuilder[T]) set(assign string, values ...Primitive) !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
if assign.len == 0 {
|
|
return error('${@FN}(): empty `set`')
|
|
}
|
|
required_params := assign.count('?')
|
|
if required_params != values.len {
|
|
return error('${@FN}(): `set` requires `${required_params}` params but got `${values.len}`')
|
|
}
|
|
mut fields := []string{}
|
|
assign_splits := assign.split_any(',')
|
|
for assign_split in assign_splits {
|
|
f := assign_split.split_any('=')
|
|
if f.len != 2 {
|
|
return error('${@FN}(): `set` syntax error, it should look like : `a=?,b=?`')
|
|
}
|
|
if f[1].trim_space() != '?' {
|
|
return error('${@FN}(): `set` syntax error, it should look like : `a=?,b=?`')
|
|
}
|
|
field := f[0].trim_space()
|
|
if field !in qb.valid_sql_field_names {
|
|
return error("${@FN}(): table `${qb.config.table}` has no field's name: `${field}`")
|
|
}
|
|
fields << field
|
|
}
|
|
qb.data.fields = fields
|
|
qb.data.data = values
|
|
return qb
|
|
}
|
|
|
|
// table_from_struct get table from struct
|
|
fn table_from_struct[T]() Table {
|
|
mut table_name := T.name
|
|
mut attrs := []VAttribute{}
|
|
$for a in T.attributes {
|
|
$if a.name == 'table' && a.has_arg {
|
|
table_name = a.arg
|
|
}
|
|
attrs << a
|
|
}
|
|
return Table{
|
|
name: table_name
|
|
attrs: attrs
|
|
}
|
|
}
|
|
|
|
// struct_meta return a struct's fields info
|
|
fn struct_meta[T]() []TableField {
|
|
mut meta := []TableField{}
|
|
$for field in T.fields {
|
|
mut attrs := []VAttribute{}
|
|
mut is_skip := false
|
|
for attr in field.attrs {
|
|
f := attr.split_any(':')
|
|
if f.len == 1 {
|
|
ff := f[0].trim_space()
|
|
if ff == 'skip' {
|
|
is_skip = true
|
|
}
|
|
attrs << VAttribute{
|
|
name: ff
|
|
}
|
|
continue
|
|
}
|
|
if f.len == 2 {
|
|
ff := f[1].trim_space()
|
|
if f[0].trim_space() == 'sql' && ff == '-' {
|
|
is_skip = true
|
|
}
|
|
mut kind := AttributeKind.plain
|
|
if ff == 'true' || ff == 'false' {
|
|
kind = .bool
|
|
} else if ff.starts_with('if ') {
|
|
kind = .comptime_define
|
|
} else if (ff.starts_with("'") && ff.ends_with("'"))
|
|
|| (ff.starts_with('"') && ff.ends_with('"')) {
|
|
kind = .string
|
|
} else if ff.contains_only('0123456789') {
|
|
kind = .number
|
|
} else if ff !in ['serial', 'i8', 'i16', 'int', 'i64', 'u8', 'u16', 'u32', 'u64',
|
|
'f32', 'f64', 'bool', 'string'] {
|
|
// @[sql: data_type] need kind = .plain
|
|
// @[sql: column_name] need kind = .string
|
|
kind = .string
|
|
}
|
|
attrs << VAttribute{
|
|
name: f[0].trim_space()
|
|
has_arg: true
|
|
arg: ff
|
|
kind: kind
|
|
}
|
|
}
|
|
}
|
|
|
|
mut field_type := field.typ
|
|
if typeof(field).name.contains('time.Time') {
|
|
field_type = time_
|
|
} else if field.is_struct {
|
|
field_type = type_idx['int']
|
|
} else if field.is_enum {
|
|
field_type = enum_
|
|
}
|
|
|
|
if !is_skip {
|
|
meta << TableField{
|
|
name: field.name
|
|
typ: field_type
|
|
nullable: field.is_option
|
|
attrs: attrs
|
|
}
|
|
}
|
|
}
|
|
return meta
|
|
}
|
|
|
|
// map_row map a row result into a struct
|
|
fn (qb &QueryBuilder[T]) map_row(row []Primitive) !T {
|
|
mut instance := T{}
|
|
|
|
$for field in T.fields {
|
|
mut m := TableField{}
|
|
mm := qb.meta.filter(it.name == field.name)
|
|
if mm.len != 0 {
|
|
m = mm[0]
|
|
index := qb.config.fields.index(sql_field_name(m))
|
|
if index >= 0 {
|
|
value := row[index]
|
|
|
|
$if field.typ is $option {
|
|
if value == Primitive(Null{}) {
|
|
instance.$(field.name) = none
|
|
}
|
|
}
|
|
if value != Primitive(Null{}) {
|
|
$if field.typ is i8 || field.typ is ?i8 {
|
|
instance.$(field.name) = match value {
|
|
i8 { i8(value) }
|
|
i16 { i8(value) }
|
|
int { i8(value) }
|
|
i64 { i8(value) }
|
|
u8 { i8(value) }
|
|
u16 { i8(value) }
|
|
u32 { i8(value) }
|
|
u64 { i8(value) }
|
|
bool { i8(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is i16 || field.typ is ?i16 {
|
|
instance.$(field.name) = match value {
|
|
i8 { i16(value) }
|
|
i16 { i16(value) }
|
|
int { i16(value) }
|
|
i64 { i16(value) }
|
|
u8 { i16(value) }
|
|
u16 { i16(value) }
|
|
u32 { i16(value) }
|
|
u64 { i16(value) }
|
|
bool { i16(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is int || field.typ is ?int {
|
|
instance.$(field.name) = match value {
|
|
i8 { int(value) }
|
|
i16 { int(value) }
|
|
int { int(value) }
|
|
i64 { int(value) }
|
|
u8 { int(value) }
|
|
u16 { int(value) }
|
|
u32 { int(value) }
|
|
u64 { int(value) }
|
|
bool { int(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is i64 || field.typ is ?i64 {
|
|
instance.$(field.name) = match value {
|
|
i8 { i64(value) }
|
|
i16 { i64(value) }
|
|
int { i64(value) }
|
|
i64 { i64(value) }
|
|
u8 { i64(value) }
|
|
u16 { i64(value) }
|
|
u32 { i64(value) }
|
|
u64 { i64(value) }
|
|
bool { i64(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is u8 || field.typ is ?u8 {
|
|
instance.$(field.name) = match value {
|
|
i8 { u8(value) }
|
|
i16 { u8(value) }
|
|
int { u8(value) }
|
|
i64 { u8(value) }
|
|
u8 { u8(value) }
|
|
u16 { u8(value) }
|
|
u32 { u8(value) }
|
|
u64 { u8(value) }
|
|
bool { u8(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is u16 || field.typ is ?u16 {
|
|
instance.$(field.name) = match value {
|
|
i8 { u16(value) }
|
|
i16 { u16(value) }
|
|
int { u16(value) }
|
|
i64 { u16(value) }
|
|
u8 { u16(value) }
|
|
u16 { u16(value) }
|
|
u32 { u16(value) }
|
|
u64 { u16(value) }
|
|
bool { u16(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is u32 || field.typ is ?u32 {
|
|
instance.$(field.name) = match value {
|
|
i8 { u32(value) }
|
|
i16 { u32(value) }
|
|
int { u32(value) }
|
|
i64 { u32(value) }
|
|
u8 { u32(value) }
|
|
u16 { u32(value) }
|
|
u32 { u32(value) }
|
|
u64 { u32(value) }
|
|
bool { u32(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is u64 || field.typ is ?u64 {
|
|
instance.$(field.name) = match value {
|
|
i8 { u64(value) }
|
|
i16 { u64(value) }
|
|
int { u64(value) }
|
|
i64 { u64(value) }
|
|
u8 { u64(value) }
|
|
u16 { u64(value) }
|
|
u32 { u64(value) }
|
|
u64 { u64(value) }
|
|
bool { u64(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is f32 || field.typ is ?f32 {
|
|
instance.$(field.name) = match value {
|
|
i8 { f32(value) }
|
|
i16 { f32(value) }
|
|
int { f32(value) }
|
|
i64 { f32(value) }
|
|
u8 { f32(value) }
|
|
u16 { f32(value) }
|
|
u32 { f32(value) }
|
|
u64 { f32(value) }
|
|
bool { f32(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is f64 || field.typ is ?f64 {
|
|
instance.$(field.name) = match value {
|
|
i8 { f64(value) }
|
|
i16 { f64(value) }
|
|
int { f64(value) }
|
|
i64 { f64(value) }
|
|
u8 { f64(value) }
|
|
u16 { f64(value) }
|
|
u32 { f64(value) }
|
|
u64 { f64(value) }
|
|
bool { f64(value) }
|
|
else { 0 }
|
|
}
|
|
} $else $if field.typ is bool || field.typ is ?bool {
|
|
instance.$(field.name) = match value {
|
|
i8 { value != 0 }
|
|
i16 { value != 0 }
|
|
int { value != 0 }
|
|
i64 { value != 0 }
|
|
u8 { value != 0 }
|
|
u16 { value != 0 }
|
|
u32 { value != 0 }
|
|
u64 { value != 0 }
|
|
bool { value }
|
|
else { false }
|
|
}
|
|
} $else $if field.typ is string || field.typ is ?string {
|
|
instance.$(field.name) = value as string
|
|
} $else $if field.typ is time.Time || field.typ is ?time.Time {
|
|
if m.typ == time_ {
|
|
instance.$(field.name) = value as time.Time
|
|
} else if m.typ == type_string {
|
|
instance.$(field.name) = time.parse(value as string)!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return instance
|
|
}
|
|
|
|
// prepare QueryBuilder, ready for gen SQL
|
|
fn (qb_ &QueryBuilder[T]) prepare() ! {
|
|
mut qb := unsafe { qb_ }
|
|
|
|
// check for mismatch `(` and `)`
|
|
for p in qb.where.parentheses {
|
|
if p[1] == -1 {
|
|
return error('${@FN}(): missing `)`')
|
|
}
|
|
}
|
|
|
|
// auto fill field's names if not set by `select`
|
|
if qb.config.fields.len == 0 {
|
|
qb.config.fields = qb.meta.map(sql_field_name(it))
|
|
}
|
|
|
|
if qb.config.types.len == 0 {
|
|
// set field's types
|
|
mut types := []int{cap: qb.config.fields.len}
|
|
for f in qb.config.fields {
|
|
for ff in qb.meta {
|
|
if sql_field_name(ff) == f {
|
|
types << ff.typ
|
|
}
|
|
}
|
|
}
|
|
qb.config.types = types
|
|
}
|
|
}
|
|
|
|
// query start a query and return result in struct `T`
|
|
pub fn (qb_ &QueryBuilder[T]) query() ![]T {
|
|
mut qb := unsafe { qb_ }
|
|
defer {
|
|
qb.reset()
|
|
}
|
|
qb.prepare()!
|
|
rows := qb.conn.select(qb.config, qb.data, qb.where)!
|
|
mut result := []T{cap: rows.len}
|
|
for row in rows {
|
|
result << qb.map_row[T](row)!
|
|
}
|
|
return result
|
|
}
|
|
|
|
// count start a count query and return result
|
|
pub fn (qb_ &QueryBuilder[T]) count() !int {
|
|
mut qb := unsafe { qb_ }
|
|
defer {
|
|
qb.reset()
|
|
}
|
|
mut count_config := qb.config
|
|
count_config.is_count = true
|
|
count_config.fields = []
|
|
qb.prepare()!
|
|
result := qb.conn.select(count_config, qb.data, qb.where)!
|
|
|
|
if result.len == 0 || result[0].len == 0 {
|
|
return 0
|
|
}
|
|
count_val := result[0][0]
|
|
return match count_val {
|
|
int { count_val }
|
|
i64 { int(count_val) }
|
|
u64 { int(count_val) }
|
|
else { return error('${@FN}(): invalid count result type') }
|
|
}
|
|
}
|
|
|
|
// insert insert a record into the database
|
|
pub fn (qb_ &QueryBuilder[T]) insert[T](value T) !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
defer {
|
|
qb.reset()
|
|
}
|
|
qb.insert_many([value])!
|
|
return qb
|
|
}
|
|
|
|
// insert_many insert records into the database
|
|
pub fn (qb_ &QueryBuilder[T]) insert_many[T](values []T) !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
defer {
|
|
qb.reset()
|
|
}
|
|
qb.prepare()!
|
|
if values.len == 0 {
|
|
return error('${@FN}(): `insert` need at least one record')
|
|
}
|
|
for value in values {
|
|
new_qb := fill_data_with_struct[T](value, qb.meta)
|
|
qb.conn.insert(qb.config.table, new_qb)!
|
|
}
|
|
return qb
|
|
}
|
|
|
|
fn fill_data_with_struct[T](value T, meta []TableField) QueryData {
|
|
mut qb := QueryData{}
|
|
$for field in T.fields {
|
|
sql_fields := meta.filter(it.name == field.name)
|
|
if sql_fields.len == 1 {
|
|
sql_f := sql_fields[0]
|
|
sql_f_name := sql_field_name(sql_f)
|
|
sql_f_type := sql_field_type(sql_f)
|
|
|
|
if sql_f_type == serial {
|
|
// `serial` should be auto field
|
|
qb.auto_fields << qb.fields.len
|
|
}
|
|
qb.fields << sql_f_name
|
|
|
|
$if field.typ is bool {
|
|
qb.data << bool_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?bool {
|
|
qb.data << option_bool_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is f32 {
|
|
qb.data << f32_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?f32 {
|
|
qb.data << option_f32_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is f64 {
|
|
qb.data << f64_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?f64 {
|
|
qb.data << option_f64_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is i8 {
|
|
qb.data << i8_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?i8 {
|
|
qb.data << option_i8_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is i16 {
|
|
qb.data << i16_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?i16 {
|
|
qb.data << option_i16_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is int {
|
|
qb.data << int_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?int {
|
|
qb.data << option_int_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is i64 {
|
|
qb.data << i64_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?i64 {
|
|
qb.data << option_i64_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is u8 {
|
|
qb.data << u8_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?u8 {
|
|
qb.data << option_u8_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is u16 {
|
|
qb.data << u16_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?u16 {
|
|
qb.data << option_u16_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is u32 {
|
|
qb.data << u32_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?u32 {
|
|
qb.data << option_u32_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is u64 {
|
|
qb.data << u64_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?u64 {
|
|
qb.data << option_u64_to_primitive(value.$(field.name))
|
|
}
|
|
$if field.typ is string {
|
|
qb.data << string_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is ?string {
|
|
qb.data << option_string_to_primitive(value.$(field.name))
|
|
} $else $if field.typ is time.Time {
|
|
if sql_f_type == type_string {
|
|
qb.data << string_to_primitive(value.$(field.name).format_ss())
|
|
} else {
|
|
qb.data << time_to_primitive(value.$(field.name))
|
|
}
|
|
} $else $if field.typ is ?time.Time {
|
|
if sql_f_type == type_string {
|
|
b := value.$(field.name)
|
|
if b_ := b {
|
|
qb.data << Primitive(b_.format_ss())
|
|
} else {
|
|
qb.data << null_primitive
|
|
}
|
|
} else {
|
|
qb.data << option_time_to_primitive(value.$(field.name))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return qb
|
|
}
|
|
|
|
// update update record(s) in the database
|
|
pub fn (qb_ &QueryBuilder[T]) update() !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
defer {
|
|
qb.reset()
|
|
}
|
|
qb.prepare()!
|
|
if qb.data.fields.len == 0 {
|
|
return error('${@FN}(): `update` need at least one `set` clause')
|
|
}
|
|
qb.conn.update(qb.config.table, qb.data, qb.where)!
|
|
return qb
|
|
}
|
|
|
|
// delete delete record(s) in the database
|
|
pub fn (qb_ &QueryBuilder[T]) delete() !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
defer {
|
|
qb.reset()
|
|
}
|
|
qb.prepare()!
|
|
qb.conn.delete(qb.config.table, qb.where)!
|
|
return qb
|
|
}
|
|
|
|
// create create a table
|
|
pub fn (qb_ &QueryBuilder[T]) create() !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
defer {
|
|
qb.reset()
|
|
}
|
|
qb.conn.create(qb.config.table, qb.meta)!
|
|
return qb
|
|
}
|
|
|
|
// drop drop a table
|
|
pub fn (qb_ &QueryBuilder[T]) drop() !&QueryBuilder[T] {
|
|
mut qb := unsafe { qb_ }
|
|
defer {
|
|
qb.reset()
|
|
}
|
|
qb.conn.drop(qb.config.table)!
|
|
return qb
|
|
}
|
|
|
|
// last_id returns the last inserted id of the db
|
|
pub fn (qb_ &QueryBuilder[T]) last_id() int {
|
|
mut qb := unsafe { qb_ }
|
|
qb.reset()
|
|
return qb.conn.last_id()
|
|
}
|