1 | module orm |
2 | |
3 | import time |
4 | |
5 | pub 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 | |
40 | pub 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 | |
55 | pub enum OperationKind { |
56 | neq // != |
57 | eq // == |
58 | gt // > |
59 | lt // < |
60 | ge // >= |
61 | le // <= |
62 | orm_like // LIKE |
63 | } |
64 | |
65 | pub enum MathOperationKind { |
66 | add // + |
67 | sub // - |
68 | mul // * |
69 | div // / |
70 | } |
71 | |
72 | pub enum StmtKind { |
73 | insert |
74 | update |
75 | delete |
76 | } |
77 | |
78 | pub enum OrderType { |
79 | asc |
80 | desc |
81 | } |
82 | |
83 | pub enum SQLDialect { |
84 | default |
85 | sqlite |
86 | } |
87 | |
88 | fn (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 | |
101 | fn (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 () |
117 | pub struct QueryData { |
118 | pub: |
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 | |
128 | pub struct InfixType { |
129 | pub: |
130 | name string |
131 | operator MathOperationKind |
132 | right Primitive |
133 | } |
134 | |
135 | pub struct TableField { |
136 | pub: |
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 |
157 | pub struct SelectConfig { |
158 | pub: |
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 |
180 | pub 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 |
195 | pub 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 |
354 | pub 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 |
446 | 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 { |
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 |
608 | fn 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 |
629 | fn 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 | |
642 | pub fn bool_to_primitive(b bool) Primitive { |
643 | return Primitive(b) |
644 | } |
645 | |
646 | pub fn f32_to_primitive(b f32) Primitive { |
647 | return Primitive(b) |
648 | } |
649 | |
650 | pub fn f64_to_primitive(b f64) Primitive { |
651 | return Primitive(b) |
652 | } |
653 | |
654 | pub fn i8_to_primitive(b i8) Primitive { |
655 | return Primitive(b) |
656 | } |
657 | |
658 | pub fn i16_to_primitive(b i16) Primitive { |
659 | return Primitive(b) |
660 | } |
661 | |
662 | pub fn int_to_primitive(b int) Primitive { |
663 | return Primitive(b) |
664 | } |
665 | |
666 | // int_literal_to_primitive handles int literal value |
667 | pub fn int_literal_to_primitive(b int) Primitive { |
668 | return Primitive(b) |
669 | } |
670 | |
671 | // float_literal_to_primitive handles float literal value |
672 | pub fn float_literal_to_primitive(b f64) Primitive { |
673 | return Primitive(b) |
674 | } |
675 | |
676 | pub fn i64_to_primitive(b i64) Primitive { |
677 | return Primitive(b) |
678 | } |
679 | |
680 | pub fn u8_to_primitive(b u8) Primitive { |
681 | return Primitive(b) |
682 | } |
683 | |
684 | pub fn u16_to_primitive(b u16) Primitive { |
685 | return Primitive(b) |
686 | } |
687 | |
688 | pub fn u32_to_primitive(b u32) Primitive { |
689 | return Primitive(b) |
690 | } |
691 | |
692 | pub fn u64_to_primitive(b u64) Primitive { |
693 | return Primitive(b) |
694 | } |
695 | |
696 | pub fn string_to_primitive(b string) Primitive { |
697 | return Primitive(b) |
698 | } |
699 | |
700 | pub fn time_to_primitive(b time.Time) Primitive { |
701 | return Primitive(b) |
702 | } |
703 | |
704 | pub fn infix_to_primitive(b InfixType) Primitive { |
705 | return Primitive(b) |
706 | } |
707 | |
708 | fn 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 | } |