diff --git a/examples/database/orm.v b/examples/database/orm.v index 9f1601b99b..734137560a 100644 --- a/examples/database/orm.v +++ b/examples/database/orm.v @@ -18,12 +18,145 @@ struct User { skipped_string string [skip] } +struct Parent { + id int [primary; sql: serial] + name string + chields []Chield [fkey: 'parent_id'] +} + +struct Chield { + id int [primary; sql: serial] + parent_id int + name string +} + fn main() { + sqlite3_array() + mysql_array() + psql_array() + sqlite3() mysql() psql() } +fn sqlite3_array() { + mut db := sqlite.connect(':memory:') or { panic(err) } + sql db { + create table Parent + } + + par := Parent{ + name: 'test' + chields: [ + Chield{ + name: 'abc' + }, + Chield{ + name: 'def' + }, + ] + } + + sql db { + insert par into Parent + } + + parent := sql db { + select from Parent where id == 1 + } + + sql db { + drop table Chield + drop table Parent + } + + eprintln(parent) +} + +fn mysql_array() { + mut db := mysql.Connection{ + host: 'localhost' + port: 3306 + username: 'root' + password: 'abc' + dbname: 'test' + } + db.connect() or { panic(err) } + + sql db { + create table Parent + } + + par := Parent{ + name: 'test' + chields: [ + Chield{ + name: 'abc' + }, + Chield{ + name: 'def' + }, + ] + } + + sql db { + insert par into Parent + } + + parent := sql db { + select from Parent where id == 1 + } + + eprintln(parent) + + sql db { + drop table Chield + drop table Parent + } + + db.close() +} + +fn psql_array() { + mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or { + panic(err) + } + + sql db { + create table Parent + } + + par := Parent{ + name: 'test' + chields: [ + Chield{ + name: 'abc' + }, + Chield{ + name: 'def' + }, + ] + } + + sql db { + insert par into Parent + } + + parent := sql db { + select from Parent where id == 1 + } + + eprintln(parent) + + sql db { + drop table Chield + drop table Parent + } + + db.close() +} + fn sqlite3() { mut db := sqlite.connect(':memory:') or { panic(err) } sql db { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 67cd5d6740..21d0f2466a 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -6439,15 +6439,24 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { info := sym.info as ast.Struct fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, sym.name) mut sub_structs := map[int]ast.SqlExpr{} - for f in fields.filter(c.table.type_symbols[int(it.typ)].kind == .struct_) { + for f in fields.filter(c.table.type_symbols[int(it.typ)].kind == .struct_ + || (c.table.get_type_symbol(it.typ).kind == .array + && c.table.get_type_symbol(c.table.get_type_symbol(it.typ).array_info().elem_type).kind == .struct_)) { + typ := if c.table.get_type_symbol(f.typ).kind == .struct_ { + f.typ + } else if c.table.get_type_symbol(f.typ).kind == .array { + c.table.get_type_symbol(f.typ).array_info().elem_type + } else { + ast.Type(0) + } mut n := ast.SqlExpr{ pos: node.pos has_where: true - typ: f.typ + typ: typ db_expr: node.db_expr table_expr: ast.TypeNode{ pos: node.table_expr.pos - typ: f.typ + typ: typ } } tmp_inside_sql := c.inside_sql @@ -6484,7 +6493,7 @@ fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { or_block: ast.OrExpr{} } - sub_structs[int(f.typ)] = n + sub_structs[int(typ)] = n } node.fields = fields node.sub_structs = sub_structs.move() @@ -6531,20 +6540,29 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { info := table_sym.info as ast.Struct fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name) mut sub_structs := map[int]ast.SqlStmtLine{} - for f in fields.filter(c.table.type_symbols[int(it.typ)].kind == .struct_) { + for f in fields.filter((c.table.type_symbols[int(it.typ)].kind == .struct_) + || (c.table.get_type_symbol(it.typ).kind == .array + && c.table.get_type_symbol(c.table.get_type_symbol(it.typ).array_info().elem_type).kind == .struct_)) { + typ := if c.table.get_type_symbol(f.typ).kind == .struct_ { + f.typ + } else if c.table.get_type_symbol(f.typ).kind == .array { + c.table.get_type_symbol(f.typ).array_info().elem_type + } else { + ast.Type(0) + } mut n := ast.SqlStmtLine{ pos: node.pos kind: node.kind table_expr: ast.TypeNode{ pos: node.table_expr.pos - typ: f.typ + typ: typ } object_var_name: '${node.object_var_name}.$f.name' } tmp_inside_sql := c.inside_sql c.sql_stmt_line(mut n) c.inside_sql = tmp_inside_sql - sub_structs[int(f.typ)] = n + sub_structs[typ] = n } node.fields = fields node.sub_structs = sub_structs.move() @@ -6562,7 +6580,10 @@ fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { fn (mut c Checker) fetch_and_verify_orm_fields(info ast.Struct, pos token.Position, table_name string) []ast.StructField { fields := info.fields.filter((it.typ in [ast.string_type, ast.int_type, ast.bool_type] - || c.table.type_symbols[int(it.typ)].kind == .struct_) && !it.attrs.contains('skip')) + || c.table.type_symbols[int(it.typ)].kind == .struct_ + || (c.table.get_type_symbol(it.typ).kind == .array + && c.table.get_type_symbol(c.table.get_type_symbol(it.typ).array_info().elem_type).kind == .struct_)) + && !it.attrs.contains('skip')) if fields.len == 0 { c.error('V orm: select: empty fields in `$table_name`', pos) return []ast.StructField{} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 4aebfd9631..d6a10155ea 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -121,6 +121,8 @@ mut: sql_idents_types []ast.Type sql_left_type ast.Type sql_table_name string + sql_fkey string + sql_parent_id string sql_side SqlExprSide // left or right, to distinguish idents in `name == name` inside_vweb_tmpl bool inside_return bool diff --git a/vlib/v/gen/c/sql.v b/vlib/v/gen/c/sql.v index 3695f71031..7b1704e6a4 100644 --- a/vlib/v/gen/c/sql.v +++ b/vlib/v/gen/c/sql.v @@ -157,12 +157,18 @@ fn (mut g Gen) sqlite3_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) g.write('sqlite3_stmt* $g.sql_stmt_name = ${c.dbtype}__DB_init_stmt($db_name, _SLIT("') g.sql_defaults(node, typ) g.writeln(');') + mut arr_stmt := []ast.SqlStmtLine{} + mut arr_fkeys := []string{} if node.kind == .insert { // build the object now (`x.name = ... x.id == ...`) for i, field in node.fields { if g.get_sql_field_type(field) == ast.Type(-1) { continue } + if field.name == g.sql_fkey && g.sql_fkey != '' { + g.writeln('sqlite3_bind_int($g.sql_stmt_name, ${i + 0} , $g.sql_parent_id); // parent id') + continue + } x := '${node.object_var_name}.$field.name' if field.typ == ast.string_type { g.writeln('sqlite3_bind_text($g.sql_stmt_name, ${i + 0}, (char*)${x}.str, ${x}.len, 0);') @@ -180,6 +186,24 @@ fn (mut g Gen) sqlite3_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) id_name := g.new_tmp_var() g.writeln('int $id_name = string_int((*(string*)array_get((*(sqlite__Row*)array_get($res, 0)).vals, 0)));') g.writeln('sqlite3_bind_int($g.sql_stmt_name, ${i + 0} , $id_name); // id') + } else if g.table.get_type_symbol(field.typ).kind == .array { + t := g.table.get_type_symbol(field.typ).array_info().elem_type + if g.table.get_type_symbol(t).kind == .struct_ { + mut fkey := '' + for attr in field.attrs { + if attr.name == 'fkey' && attr.arg != '' && attr.kind == .string { + fkey = attr.arg + break + } + } + if fkey == '' { + verror('fkey attribute has to be set for arrays in orm') + continue + } + + arr_stmt << node.sub_structs[int(t)] + arr_fkeys << fkey + } } else { g.writeln('sqlite3_bind_int($g.sql_stmt_name, ${i + 0} , $x); // stmt') } @@ -193,6 +217,15 @@ fn (mut g Gen) sqlite3_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) g.writeln('\tint $step_res = sqlite3_step($g.sql_stmt_name);') g.writeln('\tif( ($step_res != SQLITE_OK) && ($step_res != SQLITE_DONE)){ puts(sqlite3_errmsg(${db_name}.conn)); }') g.writeln('\tsqlite3_finalize($g.sql_stmt_name);') + + if arr_stmt.len > 0 { + res := g.new_tmp_var() + g.writeln('Array_sqlite__Row $res = sqlite__DB_exec($db_name, _SLIT("SELECT last_insert_rowid()")).arg0;') + id_name := g.new_tmp_var() + g.writeln('int $id_name = string_int((*(string*)array_get((*(sqlite__Row*)array_get($res, 0)).vals, 0)));') + + g.sql_arr_stmt(arr_stmt, arr_fkeys, id_name, db_expr) + } } fn (mut g Gen) sqlite3_select_expr(node ast.SqlExpr, sub bool, line string, sql_typ SqlType) { @@ -287,7 +320,14 @@ fn (mut g Gen) sqlite3_select_expr(node ast.SqlExpr, sub bool, line string, sql_ // g.writeln('printf("RES: %d\\n", _step_res$tmp) ;') g.writeln('\tif (_step_res$tmp == SQLITE_OK || _step_res$tmp == SQLITE_ROW) {') } + mut primary := '' for i, field in node.fields { + for attr in field.attrs { + if attr.name == 'primary' { + primary = '${tmp}.$field.name' + break + } + } mut func := 'sqlite3_column_int' if field.typ == ast.string_type { func = 'sqlite3_column_text' @@ -319,6 +359,8 @@ fn (mut g Gen) sqlite3_select_expr(node ast.SqlExpr, sub bool, line string, sql_ g.sql_buf = tmp_sql_buf g.sql_i = tmp_sql_i g.sql_table_name = tmp_sql_table_name + } else if g.table.get_type_symbol(field.typ).kind == .array { + g.sql_select_arr(field, node, primary, tmp) } else { g.writeln('${tmp}.$field.name = ${func}($g.sql_stmt_name, $i);') } @@ -405,11 +447,28 @@ fn (mut g Gen) mysql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { bind := g.new_tmp_var() g.writeln('MYSQL_BIND $bind[$g.sql_i];') g.writeln('memset($bind, 0, sizeof(MYSQL_BIND)*$g.sql_i);') + mut arr_stmt := []ast.SqlStmtLine{} + mut arr_fkeys := []string{} if node.kind == .insert { for i, field in node.fields { if g.get_sql_field_type(field) == ast.Type(-1) { continue } + if field.name == g.sql_fkey && g.sql_fkey != '' { + t, sym := g.mysql_buffer_typ_from_field(field) + g.writeln('$bind[${i - 1}].buffer_type = $t;') + if sym == 'char' { + g.writeln('$bind[${i - 1}].buffer = ($sym*) ${g.sql_parent_id}.str;') + } else { + g.writeln('$bind[${i - 1}].buffer = ($sym*) &$g.sql_parent_id;') + } + if sym == 'char' { + g.writeln('$bind[${i - 1}].buffer_length = ${g.sql_parent_id}.len;') + } + g.writeln('$bind[${i - 1}].is_null = 0;') + g.writeln('$bind[${i - 1}].length = 0;') + continue + } g.writeln('//$field.name ($field.typ)') x := '${node.object_var_name}.$field.name' if g.table.get_type_symbol(field.typ).kind == .struct_ { @@ -434,6 +493,24 @@ fn (mut g Gen) mysql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.writeln('$bind[${i - 1}].buffer = &${x}.id;') g.writeln('$bind[${i - 1}].is_null = 0;') g.writeln('$bind[${i - 1}].length = 0;') + } else if g.table.get_type_symbol(field.typ).kind == .array { + t := g.table.get_type_symbol(field.typ).array_info().elem_type + if g.table.get_type_symbol(t).kind == .struct_ { + mut fkey := '' + for attr in field.attrs { + if attr.name == 'fkey' && attr.arg != '' && attr.kind == .string { + fkey = attr.arg + break + } + } + if fkey == '' { + verror('fkey attribute has to be set for arrays in orm') + continue + } + + arr_stmt << node.sub_structs[int(t)] + arr_fkeys << fkey + } } else { t, sym := g.mysql_buffer_typ_from_field(field) g.writeln('$bind[${i - 1}].buffer_type = $t;') @@ -460,6 +537,20 @@ fn (mut g Gen) mysql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.writeln('if ($res != 0) { puts(mysql_error(${db_name}.conn)); puts(mysql_stmt_error($g.sql_stmt_name)); }') g.writeln('mysql_stmt_close($g.sql_stmt_name);') g.writeln('mysql_stmt_free_result($g.sql_stmt_name);') + + if arr_stmt.len > 0 { + rs := g.new_tmp_var() + g.writeln('int ${rs}_err = mysql_real_query(${db_name}.conn, "SELECT LAST_INSERT_ID();", 24);') + g.writeln('if (${rs}_err != 0) { puts(mysql_error(${db_name}.conn)); }') + g.writeln('MYSQL_RES* $rs = mysql_store_result(${db_name}.conn);') + g.writeln('if (mysql_num_rows($rs) != 1) { puts("Something went wrong"); }') + g.writeln('MYSQL_ROW ${rs}_row = mysql_fetch_row($rs);') + id_name := g.new_tmp_var() + g.writeln('int $id_name = string_int(tos_clone(${rs}_row[0]));') + g.writeln('mysql_free_result($rs);') + + g.sql_arr_stmt(arr_stmt, arr_fkeys, id_name, db_expr) + } } fn (mut g Gen) mysql_select_expr(node ast.SqlExpr, sub bool, line string, typ SqlType) { @@ -536,7 +627,14 @@ fn (mut g Gen) mysql_select_expr(node ast.SqlExpr, sub bool, line string, typ Sq char_ptr := g.new_tmp_var() g.writeln('char* $char_ptr = "";') + mut primary := '' for i, field in node.fields { + for attr in field.attrs { + if attr.name == 'primary' { + primary = '${tmp}.$field.name' + break + } + } g.writeln('$char_ptr = $fields[$i];') g.writeln('if ($char_ptr == NULL) { $char_ptr = ""; }') name := g.table.get_type_symbol(field.typ).cname @@ -563,6 +661,8 @@ fn (mut g Gen) mysql_select_expr(node ast.SqlExpr, sub bool, line string, typ Sq g.sql_buf = tmp_sql_buf g.sql_i = tmp_sql_i g.sql_table_name = tmp_sql_table_name + } else if g.table.get_type_symbol(field.typ).kind == .array { + g.sql_select_arr(field, node, primary, tmp) } else if field.typ == ast.string_type { g.writeln('${tmp}.$field.name = tos_clone($char_ptr);') } else if field.typ == ast.byte_type { @@ -724,15 +824,23 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.sql_defaults(node, typ) g.writeln(';') + mut arr_stmt := []ast.SqlStmtLine{} + mut arr_fkeys := []string{} if node.kind == .insert { for i, field in node.fields { if g.get_sql_field_type(field) == ast.Type(-1) { continue } g.sql_i = i + field_type := g.get_sql_field_type(field) + if field.name == g.sql_fkey && g.sql_fkey != '' { + g.sql_buf = strings.new_builder(100) + g.sql_bind(g.sql_parent_id, '', field_type, typ) + g.writeln(g.sql_buf.str()) + continue + } g.writeln('//$field.name ($field.typ)') x := '${node.object_var_name}.$field.name' - field_type := g.get_sql_field_type(field) if g.table.get_type_symbol(field.typ).kind == .struct_ { // insert again expr := node.sub_structs[int(field.typ)] @@ -749,6 +857,24 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.sql_bind('string_int((*(string*)array_get((*(pg__Row*)${res}.data).vals, 0)))', '', ast.int_type, typ) g.writeln(g.sql_buf.str()) + } else if g.table.get_type_symbol(field.typ).kind == .array { + t := g.table.get_type_symbol(field.typ).array_info().elem_type + if g.table.get_type_symbol(t).kind == .struct_ { + mut fkey := '' + for attr in field.attrs { + if attr.name == 'fkey' && attr.arg != '' && attr.kind == .string { + fkey = attr.arg + break + } + } + if fkey == '' { + verror('fkey attribute has to be set for arrays in orm') + continue + } + + arr_stmt << node.sub_structs[int(t)] + arr_fkeys << fkey + } } else { g.sql_buf = strings.new_builder(100) g.sql_bind(x, '', field_type, typ) @@ -761,6 +887,16 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { g.writeln(binds) g.writeln('pg__DB_exec($db_name, $g.sql_stmt_name);') + + if arr_stmt.len > 0 { + res := g.new_tmp_var() + g.writeln('Option_pg__Row $res = pg__DB_exec_one($db_name, _SLIT("SELECT LASTVAL();"));') + g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln(_STR("\\000%.*s", 2, IError_str(err))); }') + id_name := g.new_tmp_var() + g.writeln('int $id_name = string_int((*(string*)array_get((*(pg__Row*)${res}.data).vals, 0)));') + + g.sql_arr_stmt(arr_stmt, arr_fkeys, id_name, db_expr) + } } fn (mut g Gen) psql_select_expr(node ast.SqlExpr, sub bool, line string, typ SqlType) { @@ -834,7 +970,18 @@ fn (mut g Gen) psql_select_expr(node ast.SqlExpr, sub bool, line string, typ Sql g.writeln('Array_string $fields = (*(pg__Row*) array_get($rows, $tmp_i)).vals;') fld := g.new_tmp_var() g.writeln('string $fld;') + mut primary := '' for i, field in node.fields { + for attr in field.attrs { + if attr.name == 'primary' { + primary = '${tmp}.$field.name' + break + } + } + if g.table.get_type_symbol(field.typ).kind == .array { + g.sql_select_arr(field, node, primary, tmp) + continue + } g.writeln('$fld = (*(string*)array_get($fields, $i));') name := g.table.get_type_symbol(field.typ).cname @@ -965,6 +1112,87 @@ fn (mut g Gen) psql_bind(val string, typ ast.Type) { // utils +fn (mut g Gen) sql_select_arr(field ast.StructField, node ast.SqlExpr, primary string, tmp string) { + t := g.table.get_type_symbol(field.typ).array_info().elem_type + if g.table.get_type_symbol(t).kind == .struct_ { + mut fkey := '' + for attr in field.attrs { + if attr.name == 'fkey' && attr.arg != '' && attr.kind == .string { + fkey = attr.arg + break + } + } + if fkey == '' { + verror('fkey attribute has to be set for arrays in orm') + return + } + g.writeln('//parse array start') + + e := node.sub_structs[int(t)] + mut where_expr := e.where_expr as ast.InfixExpr + mut lidt := where_expr.left as ast.Ident + mut ridt := where_expr.right as ast.Ident + ridt.name = primary + lidt.name = fkey + where_expr.right = ridt + where_expr.left = lidt + expr := ast.SqlExpr{ + typ: field.typ + has_where: e.has_where + db_expr: e.db_expr + is_array: true + pos: e.pos + where_expr: where_expr + table_expr: e.table_expr + fields: e.fields + sub_structs: e.sub_structs + } + tmp_sql_i := g.sql_i + tmp_sql_stmt_name := g.sql_stmt_name + tmp_sql_buf := g.sql_buf + tmp_sql_table_name := g.sql_table_name + + g.sql_select_expr(expr, true, '\t${tmp}.$field.name =') + g.writeln('//parse array end') + + g.sql_stmt_name = tmp_sql_stmt_name + g.sql_buf = tmp_sql_buf + g.sql_i = tmp_sql_i + g.sql_table_name = tmp_sql_table_name + } +} + +fn (mut g Gen) sql_arr_stmt(arr_stmt []ast.SqlStmtLine, arr_fkeys []string, id_name string, db_expr ast.Expr) { + for i, s in arr_stmt { + cnt := g.new_tmp_var() + g.writeln('for (int $cnt = 0; $cnt < ${s.object_var_name}.len; $cnt++) {') + name := g.table.get_type_symbol(s.table_expr.typ).cname + tmp_var := g.new_tmp_var() + g.writeln('\t$name $tmp_var = (*($name*)array_get($s.object_var_name, $cnt));') + + stmt := ast.SqlStmtLine{ + pos: s.pos + kind: s.kind + table_expr: s.table_expr + object_var_name: tmp_var + fields: s.fields + sub_structs: s.sub_structs + } + + tmp_fkey := g.sql_fkey + tmp_parent_id := g.sql_parent_id + g.sql_fkey = arr_fkeys[i] + g.sql_parent_id = id_name + + g.sql_stmt_line(stmt, db_expr) + + g.sql_fkey = tmp_fkey + g.sql_parent_id = tmp_parent_id + + g.writeln('}') + } +} + fn (mut g Gen) sql_expr_defaults(node ast.SqlExpr, sql_typ SqlType) { if node.has_where && node.where_expr is ast.InfixExpr { g.expr_to_sql(node.where_expr, sql_typ) @@ -1003,9 +1231,10 @@ fn (mut g Gen) get_base_sql_select_query(node ast.SqlExpr, typ SqlType) string { sql_query += 'COUNT(*) FROM $lit$table_name$lit ' } else { // `select id, name, country from User` - for i, field in node.fields { + fields := node.fields.filter(g.table.get_type_symbol(it.typ).kind != .array) + for i, field in fields { sql_query += '$lit${g.get_field_name(field)}$lit' - if i < node.fields.len - 1 { + if i < fields.len - 1 { sql_query += ', ' } } @@ -1031,22 +1260,23 @@ fn (mut g Gen) sql_defaults(node ast.SqlStmtLine, typ SqlType) { g.write('DELETE FROM $lit$table_name$lit ') } if node.kind == .insert { - for i, field in node.fields { + fields := node.fields.filter(g.table.get_type_symbol(it.typ).kind != .array) + for i, field in fields { if g.get_sql_field_type(field) == ast.Type(-1) { continue } g.write('$lit${g.get_field_name(field)}$lit') - if i < node.fields.len - 1 { + if i < fields.len - 1 { g.write(', ') } } g.write(') VALUES (') - for i, field in node.fields { + for i, field in fields { if g.get_sql_field_type(field) == ast.Type(-1) { continue } g.inc_sql_i(typ) - if i < node.fields.len - 1 { + if i < fields.len - 1 { g.write(', ') } } @@ -1092,6 +1322,7 @@ fn (mut g Gen) table_gen(node ast.SqlStmtLine, typ SqlType, expr ast.Expr) strin mut is_unique := false mut is_skip := false mut unique_len := 0 + mut fkey := '' for attr in field.attrs { match attr.name { 'primary' { @@ -1117,6 +1348,14 @@ fn (mut g Gen) table_gen(node ast.SqlStmtLine, typ SqlType, expr ast.Expr) strin 'skip' { is_skip = true } + 'fkey' { + if attr.arg != '' { + if attr.kind == .string { + fkey = attr.arg + continue + } + } + } else {} } } @@ -1136,6 +1375,30 @@ fn (mut g Gen) table_gen(node ast.SqlStmtLine, typ SqlType, expr ast.Expr) strin pos: node.table_expr.pos } }, expr) + } else if g.table.get_type_symbol(field.typ).kind == .array { + arr_info := g.table.get_type_symbol(field.typ).array_info() + if arr_info.nr_dims > 1 { + verror('array with one dim are supported in orm') + continue + } + atyp := arr_info.elem_type + if g.table.get_type_symbol(atyp).kind == .struct_ { + if fkey == '' { + verror('array field ($field.name) needs a fkey') + continue + } + g.sql_create_table(ast.SqlStmtLine{ + kind: node.kind + pos: node.pos + table_expr: ast.TypeNode{ + typ: atyp + pos: node.table_expr.pos + } + }, expr) + } else { + verror('unknown type ($field.typ) for field $field.name in struct $table_name') + } + continue } else { verror('unknown type ($field.typ) for field $field.name in struct $table_name') continue