v / vlib / orm
Raw file | 714 loc (653 sloc) | 12.78 KB | Latest commit hash 4747e70d9
1module orm
2
3import time
4
5pub const (
6 num64 = [typeof[i64]().idx, typeof[u64]().idx]
7 nums = [
8 typeof[i8]().idx,
9 typeof[i16]().idx,
10 typeof[int]().idx,
11 typeof[u8]().idx,
12 typeof[u16]().idx,
13 typeof[u32]().idx,
14 typeof[bool]().idx,
15 ]
16 float = [
17 typeof[f32]().idx,
18 typeof[f64]().idx,
19 ]
20 type_string = typeof[string]().idx
21 time = -2
22 serial = -1
23 type_idx = {
24 'i8': typeof[i8]().idx
25 'i16': typeof[i16]().idx
26 'int': typeof[int]().idx
27 'i64': typeof[i64]().idx
28 'u8': typeof[u8]().idx
29 'u16': typeof[u16]().idx
30 'u32': typeof[u32]().idx
31 'u64': typeof[u64]().idx
32 'f32': typeof[f32]().idx
33 'f64': typeof[f64]().idx
34 'bool': typeof[bool]().idx
35 'string': typeof[string]().idx
36 }
37 string_max_len = 2048
38)
39
40pub type Primitive = InfixType
41 | bool
42 | f32
43 | f64
44 | i16
45 | i64
46 | i8
47 | int
48 | string
49 | time.Time
50 | u16
51 | u32
52 | u64
53 | u8
54
55pub enum OperationKind {
56 neq // !=
57 eq // ==
58 gt // >
59 lt // <
60 ge // >=
61 le // <=
62 orm_like // LIKE
63}
64
65pub enum MathOperationKind {
66 add // +
67 sub // -
68 mul // *
69 div // /
70}
71
72pub enum StmtKind {
73 insert
74 update
75 delete
76}
77
78pub enum OrderType {
79 asc
80 desc
81}
82
83pub enum SQLDialect {
84 default
85 sqlite
86}
87
88fn (kind OperationKind) to_str() string {
89 str := match kind {
90 .neq { '!=' }
91 .eq { '=' }
92 .gt { '>' }
93 .lt { '<' }
94 .ge { '>=' }
95 .le { '<=' }
96 .orm_like { 'LIKE' }
97 }
98 return str
99}
100
101fn (kind OrderType) to_str() string {
102 return match kind {
103 .desc {
104 'DESC'
105 }
106 .asc {
107 'ASC'
108 }
109 }
110}
111
112// Examples for QueryData in SQL: abc == 3 && b == 'test'
113// => fields[abc, b]; data[3, 'test']; types[index of int, index of string]; kinds[.eq, .eq]; is_and[true];
114// Every field, data, type & kind of operation in the expr share the same index in the arrays
115// is_and defines how they're addicted to each other either and or or
116// parentheses defines which fields will be inside ()
117pub struct QueryData {
118pub:
119 fields []string
120 data []Primitive
121 types []int
122 parentheses [][]int
123 kinds []OperationKind
124 primary_column_name string
125 is_and []bool
126}
127
128pub struct InfixType {
129pub:
130 name string
131 operator MathOperationKind
132 right Primitive
133}
134
135pub struct TableField {
136pub:
137 name string
138 typ int
139 is_time bool
140 default_val string
141 is_arr bool
142 is_enum bool
143 attrs []StructAttribute
144}
145
146// table - Table name
147// is_count - Either the data will be returned or an integer with the count
148// has_where - Select all or use a where expr
149// has_order - Order the results
150// order - Name of the column which will be ordered
151// order_type - Type of order (asc, desc)
152// has_limit - Limits the output data
153// primary - Name of the primary field
154// has_offset - Add an offset to the result
155// fields - Fields to select
156// types - Types to select
157pub struct SelectConfig {
158pub:
159 table string
160 is_count bool
161 has_where bool
162 has_order bool
163 order string
164 order_type OrderType
165 has_limit bool
166 primary string = 'id' // should be set if primary is different than 'id' and 'has_limit' is false
167 has_offset bool
168 fields []string
169 types []int
170}
171
172// Interfaces gets called from the backend and can be implemented
173// Since the orm supports arrays aswell, they have to be returned too.
174// A row is represented as []Primitive, where the data is connected to the fields of the struct by their
175// index. The indices are mapped with the SelectConfig.field array. This is the mapping for a struct.
176// To have an array, there has to be an array of structs, basically [][]Primitive
177//
178// Every function without last_id() returns an optional, which returns an error if present
179// last_id returns the last inserted id of the db
180pub interface Connection {
181 @select(config SelectConfig, data QueryData, where QueryData) ![][]Primitive
182 insert(table string, data QueryData) !
183 update(table string, data QueryData, where QueryData) !
184 delete(table string, where QueryData) !
185 create(table string, fields []TableField) !
186 drop(table string) !
187 last_id() int
188}
189
190// Generates an sql stmt, from universal parameter
191// q - The quotes character, which can be different in every type, so it's variable
192// num - Stmt uses nums at prepared statements (? or ?1)
193// qm - Character for prepared statement, qm because of quotation mark like in sqlite
194// start_pos - When num is true, it's the start position of the counter
195pub fn orm_stmt_gen(sql_dialect SQLDialect, table string, q string, kind StmtKind, num bool, qm string, start_pos int, data QueryData, where QueryData) (string, QueryData) {
196 mut str := ''
197 mut c := start_pos
198 mut data_fields := []string{}
199 mut data_data := []Primitive{}
200
201 match kind {
202 .insert {
203 mut values := []string{}
204 mut select_fields := []string{}
205
206 for i in 0 .. data.fields.len {
207 column_name := data.fields[i]
208 is_primary_column := column_name == data.primary_column_name
209
210 if data.data.len > 0 {
211 // Allow the database to insert an automatically generated primary key
212 // under the hood if it is not passed by the user.
213 tidx := data.data[i].type_idx()
214 if is_primary_column && (tidx in orm.nums || tidx in orm.num64) {
215 x := data.data[i]
216 match x {
217 i8, i16, int, i64, u8, u16, u32, u64 {
218 if i64(x) == 0 {
219 continue
220 }
221 }
222 else {}
223 }
224 }
225
226 match data.data[i].type_name() {
227 'string' {
228 if (data.data[i] as string).len == 0 {
229 continue
230 }
231 }
232 'time.Time' {
233 if (data.data[i] as time.Time).unix == 0 {
234 continue
235 }
236 }
237 else {}
238 }
239 data_data << data.data[i]
240 }
241 select_fields << '${q}${column_name}${q}'
242 values << factory_insert_qm_value(num, qm, c)
243 data_fields << column_name
244 c++
245 }
246
247 str += 'INSERT INTO ${q}${table}${q} '
248
249 are_values_empty := values.len == 0
250
251 if sql_dialect == .sqlite && are_values_empty {
252 str += 'DEFAULT VALUES'
253 } else {
254 str += '('
255 str += select_fields.join(', ')
256 str += ') VALUES ('
257 str += values.join(', ')
258 str += ')'
259 }
260 }
261 .update {
262 str += 'UPDATE ${q}${table}${q} SET '
263 for i, field in data.fields {
264 str += '${q}${field}${q} = '
265 if data.data.len > i {
266 d := data.data[i]
267 if d is InfixType {
268 op := match d.operator {
269 .add {
270 '+'
271 }
272 .sub {
273 '-'
274 }
275 .mul {
276 '*'
277 }
278 .div {
279 '/'
280 }
281 }
282 str += '${d.name} ${op} ${qm}'
283 } else {
284 str += '${qm}'
285 }
286 } else {
287 str += '${qm}'
288 }
289 if num {
290 str += '${c}'
291 c++
292 }
293 if i < data.fields.len - 1 {
294 str += ', '
295 }
296 }
297 str += ' WHERE '
298 }
299 .delete {
300 str += 'DELETE FROM ${q}${table}${q} WHERE '
301 }
302 }
303 if kind == .update || kind == .delete {
304 for i, field in where.fields {
305 mut pre_par := false
306 mut post_par := false
307 for par in where.parentheses {
308 if i in par {
309 pre_par = par[0] == i
310 post_par = par[1] == i
311 }
312 }
313 if pre_par {
314 str += '('
315 }
316 str += '${q}${field}${q} ${where.kinds[i].to_str()} ${qm}'
317 if num {
318 str += '${c}'
319 c++
320 }
321 if post_par {
322 str += ')'
323 }
324 if i < where.fields.len - 1 {
325 if where.is_and[i] {
326 str += ' AND '
327 } else {
328 str += ' OR '
329 }
330 }
331 }
332 }
333 str += ';'
334 $if trace_orm_stmt ? {
335 eprintln('> orm_stmt sql_dialect: ${sql_dialect} | table: ${table} | kind: ${kind} | query: ${str}')
336 }
337 $if trace_orm ? {
338 eprintln('> orm: ${str}')
339 }
340
341 return str, QueryData{
342 fields: data_fields
343 data: data_data
344 types: data.types
345 kinds: data.kinds
346 is_and: data.is_and
347 }
348}
349
350// Generates an sql select stmt, from universal parameter
351// orm - See SelectConfig
352// q, num, qm, start_pos - see orm_stmt_gen
353// where - See QueryData
354pub fn orm_select_gen(cfg SelectConfig, q string, num bool, qm string, start_pos int, where QueryData) string {
355 mut str := 'SELECT '
356
357 if cfg.is_count {
358 str += 'COUNT(*)'
359 } else {
360 for i, field in cfg.fields {
361 str += '${q}${field}${q}'
362 if i < cfg.fields.len - 1 {
363 str += ', '
364 }
365 }
366 }
367
368 str += ' FROM ${q}${cfg.table}${q}'
369
370 mut c := start_pos
371
372 if cfg.has_where {
373 str += ' WHERE '
374 for i, field in where.fields {
375 mut pre_par := false
376 mut post_par := false
377 for par in where.parentheses {
378 if i in par {
379 pre_par = par[0] == i
380 post_par = par[1] == i
381 }
382 }
383 if pre_par {
384 str += '('
385 }
386 str += '${q}${field}${q} ${where.kinds[i].to_str()} ${qm}'
387 if num {
388 str += '${c}'
389 c++
390 }
391 if post_par {
392 str += ')'
393 }
394 if i < where.fields.len - 1 {
395 if where.is_and[i] {
396 str += ' AND '
397 } else {
398 str += ' OR '
399 }
400 }
401 }
402 }
403
404 // Note: do not order, if the user did not want it explicitly,
405 // ordering is *slow*, especially if there are no indexes!
406 if cfg.has_order {
407 str += ' ORDER BY '
408 str += '${q}${cfg.order}${q} '
409 str += cfg.order_type.to_str()
410 }
411
412 if cfg.has_limit {
413 str += ' LIMIT ${qm}'
414 if num {
415 str += '${c}'
416 c++
417 }
418 }
419
420 if cfg.has_offset {
421 str += ' OFFSET ${qm}'
422 if num {
423 str += '${c}'
424 c++
425 }
426 }
427
428 str += ';'
429 $if trace_orm_query ? {
430 eprintln('> orm_query: ${str}')
431 }
432 $if trace_orm ? {
433 eprintln('> orm: ${str}')
434 }
435 return str
436}
437
438// Generates an sql table stmt, from universal parameter
439// table - Table name
440// q - see orm_stmt_gen
441// defaults - enables default values in stmt
442// def_unique_len - sets default unique length for texts
443// fields - See TableField
444// sql_from_v - Function which maps type indices to sql type names
445// alternative - Needed for msdb
446pub 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 {
447 mut str := 'CREATE TABLE IF NOT EXISTS ${q}${table}${q} ('
448
449 if alternative {
450 str = 'IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=${q}${table}${q} and xtype=${q}U${q}) CREATE TABLE ${q}${table}${q} ('
451 }
452
453 mut fs := []string{}
454 mut unique_fields := []string{}
455 mut unique := map[string][]string{}
456 mut primary := ''
457 mut primary_typ := 0
458
459 for field in fields {
460 if field.is_arr {
461 continue
462 }
463 mut default_val := field.default_val
464 mut no_null := false
465 mut is_unique := false
466 mut is_skip := false
467 mut unique_len := 0
468 mut references_table := ''
469 mut references_field := ''
470 mut field_name := sql_field_name(field)
471 mut ctyp := sql_from_v(sql_field_type(field)) or {
472 field_name = '${field_name}_id'
473 sql_from_v(primary_typ)!
474 }
475 for attr in field.attrs {
476 match attr.name {
477 'sql' {
478 // [sql:'-']
479 if attr.arg == '-' {
480 is_skip = true
481 }
482 }
483 'primary' {
484 primary = field.name
485 primary_typ = field.typ
486 }
487 'unique' {
488 if attr.arg != '' {
489 if attr.kind == .string {
490 unique[attr.arg] << field_name
491 continue
492 } else if attr.kind == .number {
493 unique_len = attr.arg.int()
494 is_unique = true
495 continue
496 }
497 }
498 is_unique = true
499 }
500 'nonull' {
501 no_null = true
502 }
503 'skip' {
504 is_skip = true
505 }
506 'sql_type' {
507 if attr.kind != .string {
508 return error("sql_type attribute needs to be string. Try [sql_type: '${attr.arg}'] instead of [sql_type: ${attr.arg}]")
509 }
510 ctyp = attr.arg
511 }
512 'default' {
513 if attr.kind != .string {
514 return error("default attribute needs to be string. Try [default: '${attr.arg}'] instead of [default: ${attr.arg}]")
515 }
516 if default_val == '' {
517 default_val = attr.arg
518 }
519 }
520 'references' {
521 if attr.arg == '' {
522 if field.name.ends_with('_id') {
523 references_table = field.name.trim_right('_id')
524 references_field = 'id'
525 } else {
526 return error("references attribute can only be implicit if the field name ends with '_id'")
527 }
528 } else {
529 if attr.arg.trim(' ') == '' {
530 return error("references attribute needs to be in the format [references], [references: 'tablename'], or [references: 'tablename(field_id)']")
531 }
532 if attr.arg.contains('(') {
533 ref_table, ref_field := attr.arg.split_once('(')
534 if !ref_field.ends_with(')') {
535 return error("explicit references attribute should be written as [references: 'tablename(field_id)']")
536 }
537 references_table = ref_table
538 references_field = ref_field[..ref_field.len - 1]
539 } else {
540 references_table = attr.arg
541 references_field = 'id'
542 }
543 }
544 }
545 else {}
546 }
547 }
548 if is_skip {
549 continue
550 }
551 mut stmt := ''
552 if ctyp == '' {
553 return error('Unknown type (${field.typ}) for field ${field.name} in struct ${table}')
554 }
555 stmt = '${q}${field_name}${q} ${ctyp}'
556 if defaults && default_val != '' {
557 stmt += ' DEFAULT ${default_val}'
558 }
559 if no_null {
560 stmt += ' NOT NULL'
561 }
562 if is_unique {
563 mut f := 'UNIQUE(${q}${field_name}${q}'
564 if ctyp == 'TEXT' && def_unique_len > 0 {
565 if unique_len > 0 {
566 f += '(${unique_len})'
567 } else {
568 f += '(${def_unique_len})'
569 }
570 }
571 f += ')'
572 unique_fields << f
573 }
574 if references_table != '' {
575 stmt += ' REFERENCES ${references_table} (${references_field})'
576 }
577 fs << stmt
578 }
579
580 if unique.len > 0 {
581 for k, v in unique {
582 mut tmp := []string{}
583 for f in v {
584 tmp << '${q}${f}${q}'
585 }
586 fs << '/* ${k} */UNIQUE(${tmp.join(', ')})'
587 }
588 }
589
590 if primary != '' {
591 fs << 'PRIMARY KEY(${q}${primary}${q})'
592 }
593
594 fs << unique_fields
595 str += fs.join(', ')
596 str += ');'
597 $if trace_orm_create ? {
598 eprintln('> orm_create table: ${table} | query: ${str}')
599 }
600 $if trace_orm ? {
601 eprintln('> orm: ${str}')
602 }
603
604 return str
605}
606
607// Get's the sql field type
608fn sql_field_type(field TableField) int {
609 mut typ := field.typ
610 if field.is_time {
611 return -2
612 } else if field.is_enum {
613 return typeof[i64]().idx
614 }
615 for attr in field.attrs {
616 if attr.kind == .plain && attr.name == 'sql' && attr.arg != '' {
617 if attr.arg.to_lower() == 'serial' {
618 typ = -1
619 break
620 }
621 typ = orm.type_idx[attr.arg]
622 break
623 }
624 }
625 return typ
626}
627
628// Get's the sql field name
629fn sql_field_name(field TableField) string {
630 mut name := field.name
631 for attr in field.attrs {
632 if attr.name == 'sql' && attr.has_arg && attr.kind == .string {
633 name = attr.arg
634 break
635 }
636 }
637 return name
638}
639
640// needed for backend functions
641
642pub fn bool_to_primitive(b bool) Primitive {
643 return Primitive(b)
644}
645
646pub fn f32_to_primitive(b f32) Primitive {
647 return Primitive(b)
648}
649
650pub fn f64_to_primitive(b f64) Primitive {
651 return Primitive(b)
652}
653
654pub fn i8_to_primitive(b i8) Primitive {
655 return Primitive(b)
656}
657
658pub fn i16_to_primitive(b i16) Primitive {
659 return Primitive(b)
660}
661
662pub fn int_to_primitive(b int) Primitive {
663 return Primitive(b)
664}
665
666// int_literal_to_primitive handles int literal value
667pub fn int_literal_to_primitive(b int) Primitive {
668 return Primitive(b)
669}
670
671// float_literal_to_primitive handles float literal value
672pub fn float_literal_to_primitive(b f64) Primitive {
673 return Primitive(b)
674}
675
676pub fn i64_to_primitive(b i64) Primitive {
677 return Primitive(b)
678}
679
680pub fn u8_to_primitive(b u8) Primitive {
681 return Primitive(b)
682}
683
684pub fn u16_to_primitive(b u16) Primitive {
685 return Primitive(b)
686}
687
688pub fn u32_to_primitive(b u32) Primitive {
689 return Primitive(b)
690}
691
692pub fn u64_to_primitive(b u64) Primitive {
693 return Primitive(b)
694}
695
696pub fn string_to_primitive(b string) Primitive {
697 return Primitive(b)
698}
699
700pub fn time_to_primitive(b time.Time) Primitive {
701 return Primitive(b)
702}
703
704pub fn infix_to_primitive(b InfixType) Primitive {
705 return Primitive(b)
706}
707
708fn factory_insert_qm_value(num bool, qm string, c int) string {
709 if num {
710 return '${qm}${c}'
711 } else {
712 return '${qm}'
713 }
714}