Skip to content

Commit

Permalink
orm: add references attribute to allow foreign key declarations on …
Browse files Browse the repository at this point in the history
…fields (#19349)
  • Loading branch information
asvln committed Sep 16, 2023
1 parent 09006fa commit 2cce907
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cmd/tools/vtest-self.v
Expand Up @@ -135,6 +135,7 @@ const (
'vlib/orm/orm_result_test.v',
'vlib/orm/orm_custom_operators_test.v',
'vlib/orm/orm_fk_test.v',
'vlib/orm/orm_references_test.v',
'vlib/db/sqlite/sqlite_test.v',
'vlib/db/sqlite/sqlite_orm_test.v',
'vlib/db/sqlite/sqlite_vfs_lowlevel_test.v',
Expand Down Expand Up @@ -162,6 +163,7 @@ const (
'vlib/orm/orm_create_and_drop_test.v',
'vlib/orm/orm_insert_test.v',
'vlib/orm/orm_insert_reserved_name_test.v',
'vlib/orm/orm_references_test.v',
'vlib/v/tests/websocket_logger_interface_should_compile_test.v',
'vlib/v/tests/orm_sub_array_struct_test.v',
'vlib/v/tests/orm_handle_error_for_select_from_not_created_table_test.v',
Expand All @@ -171,6 +173,7 @@ const (
'vlib/orm/orm_create_and_drop_test.v',
'vlib/orm/orm_insert_test.v',
'vlib/orm/orm_insert_reserved_name_test.v',
'vlib/orm/orm_references_test.v',
'vlib/v/tests/orm_sub_array_struct_test.v',
'vlib/v/tests/orm_handle_error_for_select_from_not_created_table_test.v',
'vlib/v/tests/project_with_cpp_code/compiling_cpp_files_with_a_cplusplus_compiler_test.v', // fails compilation with: undefined reference to vtable for __cxxabiv1::__function_type_info'
Expand Down Expand Up @@ -215,6 +218,7 @@ const (
'vlib/orm/orm_result_test.v',
'vlib/orm/orm_custom_operators_test.v',
'vlib/orm/orm_fk_test.v',
'vlib/orm/orm_references_test.v',
'vlib/v/tests/orm_sub_struct_test.v',
'vlib/v/tests/orm_sub_array_struct_test.v',
'vlib/v/tests/orm_joined_tables_select_test.v',
Expand Down
30 changes: 30 additions & 0 deletions vlib/orm/orm.v
Expand Up @@ -463,6 +463,8 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
mut is_unique := false
mut is_skip := false
mut unique_len := 0
mut references_table := ''
mut references_field := ''
mut field_name := sql_field_name(field)
mut ctyp := sql_from_v(sql_field_type(field)) or {
field_name = '${field_name}_id'
Expand Down Expand Up @@ -512,6 +514,31 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
default_val = attr.arg
}
}
'references' {
if attr.arg == '' {
if field.name.ends_with('_id') {
references_table = field.name.trim_right('_id')
references_field = 'id'
} else {
return error("references attribute can only be implicit if the field name ends with '_id'")
}
} else {
if attr.arg.trim(' ') == '' {
return error("references attribute needs to be in the format [references], [references: 'tablename'], or [references: 'tablename(field_id)']")
}
if attr.arg.contains('(') {
ref_table, ref_field := attr.arg.split_once('(')
if !ref_field.ends_with(')') {
return error("explicit references attribute should be written as [references: 'tablename(field_id)']")
}
references_table = ref_table
references_field = ref_field[..ref_field.len - 1]
} else {
references_table = attr.arg
references_field = 'id'
}
}
}
else {}
}
}
Expand Down Expand Up @@ -541,6 +568,9 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
f += ')'
unique_fields << f
}
if references_table != '' {
stmt += ' REFERENCES ${references_table} (${references_field})'
}
fs << stmt
}

Expand Down
33 changes: 33 additions & 0 deletions vlib/orm/orm_references_test.v
@@ -0,0 +1,33 @@
import db.sqlite

struct Boat {
id int [primary; sql: serial]
color_id int [references]
another1_id int [references: 'size']
another2_id int [references: 'size(secondary_id)']
}

struct Color {
id int [primary; sql: serial]
hex string
}

struct Size {
id int [primary; sql: serial]
secondary_id int
}

fn test_references_constraint() {
db := sqlite.connect(':memory:') or { panic(err) }

sql db {
create table Boat
create table Color
create table Size
} or { panic(err) }

// this pragma returns a row for each foreign key constraint on a table
pragma_result := db.exec('pragma foreign_key_list(boat)') or { panic('nope') }

assert pragma_result.len == 3
}

0 comments on commit 2cce907

Please sign in to comment.