diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v index 6fd6ea57ce..9bdc1b95c4 100644 --- a/cmd/tools/vtest-self.v +++ b/cmd/tools/vtest-self.v @@ -146,6 +146,7 @@ const skip_with_fsanitize_memory = [ 'vlib/orm/orm_custom_operators_test.v', 'vlib/orm/orm_fk_test.v', 'vlib/orm/orm_references_test.v', + 'vlib/orm/orm_option_array_test.v', 'vlib/orm/orm_option_time_test.v', 'vlib/db/sqlite/sqlite_test.v', 'vlib/db/sqlite/sqlite_orm_test.v', @@ -234,6 +235,7 @@ const skip_on_ubuntu_musl = [ 'vlib/orm/orm_custom_operators_test.v', 'vlib/orm/orm_fk_test.v', 'vlib/orm/orm_references_test.v', + 'vlib/orm/orm_option_array_test.v', 'vlib/orm/orm_option_time_test.v', 'vlib/v/tests/orm_enum_test.v', 'vlib/v/tests/orm_sub_struct_test.v', diff --git a/vlib/orm/orm_option_array_test.v b/vlib/orm/orm_option_array_test.v new file mode 100644 index 0000000000..69004b0203 --- /dev/null +++ b/vlib/orm/orm_option_array_test.v @@ -0,0 +1,45 @@ +import db.sqlite + +struct Member { + id int @[primary] + children ?[]Child @[fkey: parent_id] +} + +struct Child { + id int @[primary] + parent_id int +} + +fn test_main() { + mut db := sqlite.connect(':memory:') or { panic(err) } + defer { + db.close() or { panic(err) } + } + sql db { + create table Member + } or { println(err) } + sql db { + create table Child + } or { println(err) } + new_member := Member{ + id: 1 + children: [Child{ + id: 1 + parent_id: 1 + }, Child{ + id: 2 + parent_id: 1 + }] + } + sql db { + insert new_member into Member + }! + + rows := sql db { + select from Member + }! + + assert rows[0].children?.len == 2 + assert rows[0].children?[0].id == 1 + assert rows[0].children?[1].id == 2 +} diff --git a/vlib/v/gen/c/orm.v b/vlib/v/gen/c/orm.v index 683a05b260..4134b41371 100644 --- a/vlib/v/gen/c/orm.v +++ b/vlib/v/gen/c/orm.v @@ -276,6 +276,7 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v mut arrs := []ast.SqlStmtLine{} mut fkeys := []string{} mut field_names := []string{} + mut opt_fields := []int{} for field in node.fields { sym := g.table.sym(field.typ) @@ -289,6 +290,9 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v } else { verror('missing fkey attribute') } + if field.typ.has_flag(.option) { + opt_fields << arrs.len + } arrs << node.sub_structs[int(field.typ)] field_names << field.name } @@ -433,13 +437,22 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v } for i, mut arr in arrs { idx := g.new_tmp_var() - g.writeln('for (int ${idx} = 0; ${idx} < ${node.object_var}${member_access_type}${arr.object_var}.len; ${idx}++) {') + ctyp := g.typ(arr.table_expr.typ) + is_option := opt_fields.contains(i) + if is_option { + g.writeln('for (int ${idx} = 0; ${node.object_var}${member_access_type}${arr.object_var}.state != 2 && ${idx} < (*(Array_${ctyp}*)${node.object_var}${member_access_type}${arr.object_var}.data).len; ${idx}++) {') + } else { + g.writeln('for (int ${idx} = 0; ${idx} < ${node.object_var}${member_access_type}${arr.object_var}.len; ${idx}++) {') + } g.indent++ last_ids := g.new_tmp_var() res_ := g.new_tmp_var() tmp_var := g.new_tmp_var() - ctyp := g.typ(arr.table_expr.typ) - g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)array_get(${node.object_var}${member_access_type}${arr.object_var}, ${idx}));') + if is_option { + g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)array_get(*(Array_${ctyp}*)${node.object_var}${member_access_type}${arr.object_var}.data, ${idx}));') + } else { + g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)array_get(${node.object_var}${member_access_type}${arr.object_var}, ${idx}));') + } arr.object_var = tmp_var mut fff := []ast.StructField{} for f in arr.fields { @@ -941,7 +954,13 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, re if node.is_array { info := g.table.sym(node.typ).array_info() typ_str = g.typ(info.elem_type) - g.writeln('${unwrapped_c_typ} ${tmp}_array = __new_array(0, ${select_unwrapped_result_var_name}.len, sizeof(${typ_str}));') + base_typ := g.base_type(node.typ) + if node.typ.has_flag(.option) { + g.writeln('${unwrapped_c_typ} ${tmp}_array = { .state = 2, .err = _const_none__, .data = {EMPTY_STRUCT_INITIALIZATION} };') + g.writeln('_option_ok(&(${base_typ}[]) { __new_array(0, ${select_unwrapped_result_var_name}.len, sizeof(${typ_str})) }, (_option *)&${tmp}_array, sizeof(${base_typ}));') + } else { + g.writeln('${unwrapped_c_typ} ${tmp}_array = __new_array(0, ${select_unwrapped_result_var_name}.len, sizeof(${typ_str}));') + } g.writeln('for (; ${idx} < ${select_unwrapped_result_var_name}.len; ${idx}++) {') g.indent++ g.write('${typ_str} ${tmp} = (${typ_str}) {') @@ -1053,8 +1072,14 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, re sub_result_c_typ := g.typ(sub.typ) g.writeln('${sub_result_c_typ} ${sub_result_var};') g.write_orm_select(sql_expr_select_array, connection_var_name, sub_result_var) - g.writeln('if (!${sub_result_var}.is_error)') - g.writeln('\t${field_var} = *(${unwrapped_c_typ}*)${sub_result_var}.data;') + g.writeln('if (!${sub_result_var}.is_error) {') + if field.typ.has_flag(.option) { + g.writeln('\t${field_var}.state = 0;') + g.writeln('\t*(${g.base_type(field.typ)}*)${field_var}.data = *(${g.base_type(field.typ)}*)${sub_result_var}.data;') + } else { + g.writeln('\t${field_var} = *(${unwrapped_c_typ}*)${sub_result_var}.data;') + } + g.writeln('}') } else if field.typ.has_flag(.option) { prim_var := g.new_tmp_var() g.writeln('orm__Primitive *${prim_var} = &${array_get_call_code};') @@ -1075,7 +1100,12 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, re } if node.is_array { - g.writeln('array_push(&${tmp}_array, _MOV((${typ_str}[]){ ${tmp} }));') + if node.typ.has_flag(.option) { + g.writeln('${tmp}_array.state = 0;') + g.writeln('array_push((${g.base_type(node.typ)}*)&${tmp}_array.data, _MOV((${typ_str}[]){ ${tmp} }));') + } else { + g.writeln('array_push(&${tmp}_array, _MOV((${typ_str}[]){ ${tmp} }));') + } g.indent-- g.writeln('}') } @@ -1083,11 +1113,15 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, re g.indent-- g.writeln('}') - g.write('*(${unwrapped_c_typ}*) ${result_var}.data = ${tmp}') if node.is_array { - g.write('_array') + if node.typ.has_flag(.option) { + g.writeln('*(${g.base_type(node.typ)}*) ${result_var}.data = *(${g.base_type(node.typ)}*)${tmp}_array.data;') + } else { + g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = ${tmp}_array;') + } + } else { + g.writeln('*(${unwrapped_c_typ}*) ${result_var}.data = ${tmp};') } - g.writeln(';') } g.indent--