From dcf4a6b008e9603dec4ca0c86b1e87dc537c748e Mon Sep 17 00:00:00 2001 From: Louis Schmieder Date: Thu, 15 Apr 2021 09:53:43 +0200 Subject: [PATCH] orm: add support for custom table names & custom field names (#9697) --- examples/database/orm.v | 5 +- vlib/orm/README.md | 6 ++- vlib/orm/orm_test.v | 12 +++-- vlib/v/gen/c/sql.v | 83 +++++++++++++++++++++--------- vlib/v/tests/orm_sub_struct_test.v | 9 ++-- 5 files changed, 80 insertions(+), 35 deletions(-) diff --git a/examples/database/orm.v b/examples/database/orm.v index dd52cfc6e6..538bb49b19 100644 --- a/examples/database/orm.v +++ b/examples/database/orm.v @@ -1,6 +1,7 @@ import sqlite import mysql +[table: 'modules'] struct Module { id int [primary; sql: serial] name string @@ -11,8 +12,8 @@ struct Module { struct User { id int [primary; sql: serial] age int [unique: 'user'] - name string [unique] - is_customer bool [unique: 'user'] + name string [sql: 'username'; unique] + is_customer bool [sql: 'abc'; unique: 'user'] skipped_string string [skip] } diff --git a/vlib/orm/README.md b/vlib/orm/README.md index 61556c1cf2..ad0aa379e9 100644 --- a/vlib/orm/README.md +++ b/vlib/orm/README.md @@ -2,14 +2,18 @@ ## Attributes +### Structs + +- `[tablename: 'name']` sets a custom table name + ### Fields - `[primary]` sets the field as the primary key - `[unique]` sets the field as unique - `[unique: 'foo']` adds the field to a unique group -- `[nonull]` field will be `NOT NULL` in table creation - `[skip]` field will be skipped - `[sql: type]` sets the type which is used in sql (special type `serial`) +- `[sql: 'name']` sets a custom column name for the field ## Usage diff --git a/vlib/orm/orm_test.v b/vlib/orm/orm_test.v index bfef599add..9077f9cc42 100644 --- a/vlib/orm/orm_test.v +++ b/vlib/orm/orm_test.v @@ -9,10 +9,11 @@ struct Module { nr_downloads int } +[table: 'userlist'] struct User { - id int [primary] + id int [primary; sql: serial] age int - name string + name string [sql: 'username'] is_customer bool skipped_string string [skip] } @@ -28,9 +29,9 @@ fn test_orm_sqlite() { create table User } name := 'Peter' - db.exec("insert into User (name, age) values ('Sam', 29)") - db.exec("insert into User (name, age) values ('Peter', 31)") - db.exec("insert into User (name, age, is_customer) values ('Kate', 30, 1)") + db.exec("insert into userlist (username, age) values ('Sam', 29)") + db.exec("insert into userlist (username, age) values ('Peter', 31)") + db.exec("insert into userlist (username, age, is_customer) values ('Kate', 30, 1)") c := sql db { select count from User where id != 1 @@ -144,6 +145,7 @@ fn test_orm_sqlite() { sql db { update User set age = 32, name = 'Kate N' where name == 'Kate' } + mut kate3 := sql db { select from User where id == 3 } diff --git a/vlib/v/gen/c/sql.v b/vlib/v/gen/c/sql.v index 641d15f84c..514431f6c3 100644 --- a/vlib/v/gen/c/sql.v +++ b/vlib/v/gen/c/sql.v @@ -135,7 +135,7 @@ fn (mut g Gen) sqlite3_stmt(node ast.SqlStmt, typ SqlType) { if node.kind == .insert { // build the object now (`x.name = ... x.id == ...`) for i, field in node.fields { - if field.name == 'id' { + if g.get_sql_field_type(field) == ast.Type(-1) { continue } x := '${node.object_var_name}.$field.name' @@ -319,7 +319,7 @@ fn (mut g Gen) sqlite3_create_table(node ast.SqlStmt, typ SqlType) { } fn (mut g Gen) sqlite3_drop_table(node ast.SqlStmt, typ SqlType) { - table_name := util.strip_mod_name(g.table.get_type_symbol(node.table_expr.typ).name) + table_name := g.get_table_name(node.table_expr) g.writeln('// sqlite3 table drop') create_string := 'DROP TABLE $table_name;' g.write('sqlite__DB_exec(') @@ -381,7 +381,7 @@ fn (mut g Gen) mysql_stmt(node ast.SqlStmt, typ SqlType) { g.writeln('memset($bind, 0, sizeof(MYSQL_BIND)*$g.sql_i);') if node.kind == .insert { for i, field in node.fields { - if field.name == 'id' { + if g.get_sql_field_type(field) == ast.Type(-1) { continue } g.writeln('//$field.name ($field.typ)') @@ -613,7 +613,7 @@ fn (mut g Gen) mysql_create_table(node ast.SqlStmt, typ SqlType) { } fn (mut g Gen) mysql_drop_table(node ast.SqlStmt, typ SqlType) { - table_name := util.strip_mod_name(g.table.get_type_symbol(node.table_expr.typ).name) + table_name := g.get_table_name(node.table_expr) g.writeln('// mysql table drop') create_string := 'DROP TABLE $table_name;' tmp := g.new_tmp_var() @@ -747,14 +747,14 @@ fn (mut g Gen) sql_expr_defaults(node ast.SqlExpr, sql_typ SqlType) { fn (mut g Gen) get_base_sql_select_query(node ast.SqlExpr) string { mut sql_query := 'SELECT ' - table_name := util.strip_mod_name(g.table.get_type_symbol(node.table_expr.typ).name) + table_name := g.get_table_name(node.table_expr) if node.is_count { // `select count(*) from User` sql_query += 'COUNT(*) FROM `$table_name` ' } else { // `select id, name, country from User` for i, field in node.fields { - sql_query += '`$field.name`' + sql_query += '`${g.get_field_name(field)}`' if i < node.fields.len - 1 { sql_query += ', ' } @@ -768,7 +768,7 @@ fn (mut g Gen) get_base_sql_select_query(node ast.SqlExpr) string { } fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType) { - table_name := util.strip_mod_name(g.table.get_type_symbol(node.table_expr.typ).name) + table_name := g.get_table_name(node.table_expr) if node.kind == .insert { g.write('INSERT INTO `$table_name` (') } else if node.kind == .update { @@ -778,17 +778,17 @@ fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType) { } if node.kind == .insert { for i, field in node.fields { - if field.name == 'id' { + if g.get_sql_field_type(field) == ast.Type(-1) { continue } - g.write('`$field.name`') + g.write('`${g.get_field_name(field)}`') if i < node.fields.len - 1 { g.write(', ') } } g.write(') values (') for i, field in node.fields { - if field.name == 'id' { + if g.get_sql_field_type(field) == ast.Type(-1) { continue } if typ == .sqlite3 { @@ -804,7 +804,7 @@ fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType) { g.write(')') } else if node.kind == .update { for i, col in node.updated_columns { - g.write(' $col = ') + g.write(' ${g.get_field_name(g.get_struct_field(col))} = ') g.expr_to_sql(node.update_exprs[i], typ) if i < node.updated_columns.len - 1 { g.write(', ') @@ -822,11 +822,8 @@ fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType) { fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string { typ_sym := g.table.get_type_symbol(node.table_expr.typ) - if typ_sym.info !is ast.Struct { - verror('Type `$typ_sym.name` has to be a struct') - } - struct_data := typ_sym.info as ast.Struct - table_name := typ_sym.name.split('.').last() + struct_data := typ_sym.struct_info() + table_name := g.get_table_name(node.table_expr) mut create_string := 'CREATE TABLE IF NOT EXISTS `$table_name` (' mut fields := []string{} @@ -835,18 +832,20 @@ fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string { mut unique := map[string][]string{} for field in struct_data.fields { + name := g.get_field_name(field) mut is_primary := false mut no_null := false mut is_unique := false + mut is_skip := false for attr in field.attrs { match attr.name { 'primary' { is_primary = true - primary = field.name + primary = name } 'unique' { if attr.arg != '' { - unique[attr.arg] << field.name + unique[attr.arg] << name } else { is_unique = true } @@ -854,12 +853,17 @@ fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string { 'nonull' { no_null = true } + 'skip' { + is_skip = true + } else {} } } + if is_skip { + continue + } mut stmt := '' mut converted_typ := g.sql_type_from_v(typ, g.get_sql_field_type(field)) - mut name := field.name if converted_typ == '' { if g.table.get_type_symbol(field.typ).kind == .struct_ { converted_typ = g.sql_type_from_v(typ, ast.int_type) @@ -873,8 +877,7 @@ fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string { } }) } else { - eprintln(g.table.get_type_symbol(field.typ).kind) - verror('unknown type ($field.typ)') + verror('unknown type ($field.typ) for field $field.name in struct $table_name') continue } } @@ -963,7 +966,7 @@ fn (mut g Gen) expr_to_sql(expr ast.Expr, typ SqlType) { if g.sql_side == .left { // println("sql gen left $expr.name") g.sql_left_type = g.get_struct_field_typ(expr.name) - g.write(expr.name) + g.write(g.get_field_name(g.get_struct_field(expr.name))) } else { g.inc_sql_i(typ) info := expr.info as ast.IdentVar @@ -1071,7 +1074,7 @@ fn (mut g Gen) parse_db_from_type_string(name string) SqlType { fn (mut g Gen) get_sql_field_type(field ast.StructField) ast.Type { mut typ := field.typ for attr in field.attrs { - if attr.name == 'sql' && attr.arg != '' { + if attr.name == 'sql' && !attr.is_string_arg && attr.arg != '' { if attr.arg.to_lower() == 'serial' { typ = ast.Type(-1) break @@ -1081,3 +1084,37 @@ fn (mut g Gen) get_sql_field_type(field ast.StructField) ast.Type { } return typ } + +fn (mut g Gen) get_table_name(table_expr ast.TypeNode) string { + info := g.table.get_type_symbol(table_expr.typ).struct_info() + mut tablename := util.strip_mod_name(g.table.get_type_symbol(table_expr.typ).name) + for attr in info.attrs { + if attr.name == 'table' && attr.is_string_arg && attr.arg != '' { + tablename = attr.arg + break + } + } + return tablename +} + +fn (mut g Gen) get_struct_field(name string) ast.StructField { + info := g.table.get_type_symbol(g.table.type_idxs[g.sql_table_name]).struct_info() + mut f := ast.StructField{} + for field in info.fields { + if field.name == name { + f = field + } + } + return f +} + +fn (mut g Gen) get_field_name(field ast.StructField) string { + mut name := field.name + for attr in field.attrs { + if attr.name == 'sql' && attr.is_string_arg && attr.arg != '' { + name = attr.arg + break + } + } + return name +} diff --git a/vlib/v/tests/orm_sub_struct_test.v b/vlib/v/tests/orm_sub_struct_test.v index 81c0035fa1..ef26eb817f 100644 --- a/vlib/v/tests/orm_sub_struct_test.v +++ b/vlib/v/tests/orm_sub_struct_test.v @@ -1,19 +1,20 @@ import sqlite struct Upper { - id int + id int [primary; sql: serial] sub SubStruct } struct SubStruct { - id int + id int [primary; sql: serial] name string } fn test_orm_sub_structs() { db := sqlite.connect(':memory:') or { panic(err) } - db.exec('create table Upper (id integer primary key, sub int default 0)') - db.exec('create table SubStruct (id integer primary key, name string default "")') + sql db { + create table Upper + } upper_1 := Upper{ sub: SubStruct{