v/vlib/orm/orm.v

497 lines
8.8 KiB
V

module orm
import time
import v.ast
pub const (
num64 = [ast.i64_type_idx, ast.u64_type_idx]
nums = [
ast.i8_type_idx,
ast.i16_type_idx,
ast.int_type_idx,
ast.byte_type_idx,
ast.u16_type_idx,
ast.u32_type_idx,
ast.bool_type_idx,
]
float = [
ast.f32_type_idx,
ast.f64_type_idx,
]
string = ast.string_type_idx
time = -2
type_idx = {
'i8': ast.i8_type_idx
'i16': ast.i16_type_idx
'int': ast.int_type_idx
'i64': ast.i64_type_idx
'byte': ast.byte_type_idx
'u16': ast.u16_type_idx
'u32': ast.u32_type_idx
'u64': ast.u64_type_idx
'f32': ast.f32_type_idx
'f64': ast.f64_type_idx
'bool': ast.bool_type_idx
'string': ast.string_type_idx
}
string_max_len = 2048
)
pub type Primitive = InfixType
| bool
| f32
| f64
| i16
| i64
| i8
| int
| string
| time.Time
| u16
| u32
| u64
| u8
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(table 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 {
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
if num {
values << '$qm$c'
c++
} else {
values << '$qm'
}
}
str += 'INSERT INTO $para$table$para ('
str += data.fields.map('$para$it$para').join(', ')
str += ') VALUES ('
str += values.join(', ')
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 '
}
}
}
}
// Note: do not order, if the user did not want it explicitly,
// ordering is *slow*, especially if there are no indexes!
if orm.has_order {
str += ' ORDER BY '
str += '$para$orm.order$para '
str += orm.order_type.to_str()
}
if orm.has_limit {
str += ' LIMIT $qm'
if num {
str += '$c'
c++
}
}
if orm.has_offset {
str += ' OFFSET $qm'
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 := ''
mut field_name := sql_field_name(field)
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 ctyp := sql_from_v(sql_field_type(field)) or {
field_name = '${field_name}_id'
sql_from_v(7)?
}
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($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 u8_to_primitive(b u8) 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)
}