From b180a03253d44538d5277b70007d7cd6546d1171 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Tue, 15 Apr 2025 01:19:31 +0800 Subject: [PATCH] orm: fix option type, convert from int to i8, add examples, etc (fix #24211) (#24213) --- examples/orm/orm_func.v | 67 ++++++ examples/orm/orm_sql.v | 79 +++++++ vlib/orm/orm_func.v | 220 ++++++++++++++----- vlib/orm/orm_func_test.v | 447 +++++++++++++++++++++++++++++++-------- 4 files changed, 674 insertions(+), 139 deletions(-) create mode 100644 examples/orm/orm_func.v create mode 100644 examples/orm/orm_sql.v diff --git a/examples/orm/orm_func.v b/examples/orm/orm_func.v new file mode 100644 index 0000000000..711c1d5f68 --- /dev/null +++ b/examples/orm/orm_func.v @@ -0,0 +1,67 @@ +module main + +import db.sqlite +import time +import orm + +@[table: 'sys_users'] +struct User { +pub: + id string @[immutable; primary; sql: 'id'; sql_type: 'VARCHAR(255)'; unique] + name ?string @[immutable; sql: 'nick_name'; sql_type: 'VARCHAR(255)'; unique] + created_at ?time.Time @[omitempty; sql_type: 'TIMESTAMP'] + updated_at time.Time @[default: new; omitempty; sql_type: 'TIMESTAMP'] +} + +fn main() { + mut db := sqlite.connect(':memory:')! + defer { db.close() or {} } + + user1 := User{ + id: '001' + name: 'Jengro' + created_at: time.now() + updated_at: time.now() + } + + user2 := User{ + id: '002' + name: 'Dev' + created_at: time.now() + updated_at: time.now() + } + + mut qb := orm.new_query[User](db) + + // create table + qb.create()! + + // insert into table + qb.insert(user1)! + qb.insert(user2)! + + // query all fields + all_users := qb.query()! + dump(all_users) + + // query all users' nick_name + all_user_names := qb.select('nick_name')!.query()! + dump(all_user_names) + + // where + selected_users := qb.where('id = ?', '001')!.query()! + dump(selected_users) + + // update + qb.set('nick_name = ?', 'Tom')!.where('id = ?', '001')!.update()! + updated_user := qb.query()! + dump(updated_user) + + // delete + qb.where('id = ?', '001')!.delete()! + remain_users := qb.query()! + dump(remain_users) + + // drop table + qb.drop()! +} diff --git a/examples/orm/orm_sql.v b/examples/orm/orm_sql.v new file mode 100644 index 0000000000..877eadb77c --- /dev/null +++ b/examples/orm/orm_sql.v @@ -0,0 +1,79 @@ +module main + +import db.sqlite +import time +import orm + +@[table: 'sys_users'] +struct User { +pub: + id string @[immutable; primary; sql: 'id'; sql_type: 'VARCHAR(255)'; unique] + name ?string @[immutable; sql: 'name'; sql_type: 'VARCHAR(255)'; unique] + created_at ?time.Time @[omitempty; sql_type: 'TIMESTAMP'] + updated_at time.Time @[default: new; omitempty; sql_type: 'TIMESTAMP'] +} + +fn main() { + mut db := sqlite.connect(':memory:')! + defer { db.close() or {} } + + user1 := User{ + id: '001' + name: 'Jengro' + created_at: time.now() + updated_at: time.now() + } + + user2 := User{ + id: '002' + name: 'Dev' + created_at: time.now() + updated_at: time.now() + } + + // create table + sql db { + create table User + }! + + // insert into table + sql db { + insert user1 into User + insert user2 into User + }! + + // query all fields + all_users := sql db { + select from User + }! + dump(all_users) + + // where + selected_users := sql db { + select from User where id == '001' + }! + dump(selected_users) + + // update + sql db { + update User set name = 'Tom' where id == '001' + }! + updated_user := sql db { + select from User + }! + dump(updated_user) + + // delete + sql db { + delete from User where id == '001' + }! + remain_users := sql db { + select from User + }! + dump(remain_users) + + // drop table + sql db { + drop table User + }! +} diff --git a/vlib/orm/orm_func.v b/vlib/orm/orm_func.v index b24cc11843..7980981be7 100644 --- a/vlib/orm/orm_func.v +++ b/vlib/orm/orm_func.v @@ -43,22 +43,6 @@ pub fn (qb_ &QueryBuilder[T]) reset() &QueryBuilder[T] { return qb } -// from vlib/v/gen/c/orm.v write_orm_select() -fn type_from(value string) int { - if ret_type := type_idx[value] { - return ret_type - } else { - if value.contains('time.Time') { - return time_ - } else if value.contains('struct') { - return type_idx['int'] - } else if value.contains('enum') { - return enum_ - } - } - return 0 -} - // where create a `where` clause // valid token in the `condition` include: `field's names`, `operator`, `(`, `)`, `?`, `AND`, `OR`, `||`, `&&`, // valid `operator` incldue: `=`, `!=`, `<>`, `>=`, `<=`, `>`, `<`, `LIKE`, `ILIKE`, `IS NULL`, `IS NOT NULL` @@ -414,10 +398,19 @@ fn struct_meta[T]() []TableField { } } + mut field_type := field.typ + if typeof(field).name.contains('time.Time') { + field_type = time_ + } else if field.is_struct { + field_type = type_idx['int'] + } else if field.is_enum { + field_type = enum_ + } + if !is_skip { meta << TableField{ name: field.name - typ: type_from(typeof(field).name) + typ: field_type nullable: field.is_option attrs: attrs } @@ -435,43 +428,164 @@ fn (qb &QueryBuilder[T]) map_row(row []Primitive) !T { mm := qb.meta.filter(it.name == field.name) if mm.len != 0 { m = mm[0] - } - index := qb.config.fields.index(field.name) - if index >= 0 { - value := row[index] + index := qb.config.fields.index(sql_field_name(m)) + if index >= 0 { + value := row[index] - if value == Primitive(Null{}) && m.nullable { - // set to none by default - } else { - $if field.typ is i8 || field.typ is ?i8 { - instance.$(field.name) = value as i8 - } $else $if field.typ is i16 || field.typ is ?i16 { - instance.$(field.name) = value as i16 - } $else $if field.typ is int || field.typ is ?int { - instance.$(field.name) = value as int - } $else $if field.typ is i64 || field.typ is ?i64 { - instance.$(field.name) = value as i64 - } $else $if field.typ is u8 || field.typ is ?u8 { - instance.$(field.name) = value as u8 - } $else $if field.typ is u16 || field.typ is ?u16 { - instance.$(field.name) = value as u16 - } $else $if field.typ is u32 || field.typ is ?u32 { - instance.$(field.name) = value as u32 - } $else $if field.typ is u64 || field.typ is ?u64 { - instance.$(field.name) = value as u64 - } $else $if field.typ is f32 || field.typ is ?f32 { - instance.$(field.name) = value as f32 - } $else $if field.typ is f64 || field.typ is ?f64 { - instance.$(field.name) = value as f64 - } $else $if field.typ is bool || field.typ is ?bool { - instance.$(field.name) = value as bool - } $else $if field.typ is string || field.typ is ?string { - instance.$(field.name) = value as string - } $else $if field.typ is time.Time || field.typ is ?time.Time { - if m.typ == time_ { - instance.$(field.name) = value as time.Time - } else if m.typ == type_string { - instance.$(field.name) = time.parse(value as string)! + if value == Primitive(Null{}) && m.nullable { + // set to none by default + } else { + $if field.typ is i8 || field.typ is ?i8 { + instance.$(field.name) = match value { + i8 { i8(value) } + i16 { i8(value) } + int { i8(value) } + i64 { i8(value) } + u8 { i8(value) } + u16 { i8(value) } + u32 { i8(value) } + u64 { i8(value) } + bool { i8(value) } + else { 0 } + } + } $else $if field.typ is i16 || field.typ is ?i16 { + instance.$(field.name) = match value { + i8 { i16(value) } + i16 { i16(value) } + int { i16(value) } + i64 { i16(value) } + u8 { i16(value) } + u16 { i16(value) } + u32 { i16(value) } + u64 { i16(value) } + bool { i16(value) } + else { 0 } + } + } $else $if field.typ is int || field.typ is ?int { + instance.$(field.name) = match value { + i8 { int(value) } + i16 { int(value) } + int { int(value) } + i64 { int(value) } + u8 { int(value) } + u16 { int(value) } + u32 { int(value) } + u64 { int(value) } + bool { int(value) } + else { 0 } + } + } $else $if field.typ is i64 || field.typ is ?i64 { + instance.$(field.name) = match value { + i8 { i64(value) } + i16 { i64(value) } + int { i64(value) } + i64 { i64(value) } + u8 { i64(value) } + u16 { i64(value) } + u32 { i64(value) } + u64 { i64(value) } + bool { i64(value) } + else { 0 } + } + } $else $if field.typ is u8 || field.typ is ?u8 { + instance.$(field.name) = match value { + i8 { u8(value) } + i16 { u8(value) } + int { u8(value) } + i64 { u8(value) } + u8 { u8(value) } + u16 { u8(value) } + u32 { u8(value) } + u64 { u8(value) } + bool { u8(value) } + else { 0 } + } + } $else $if field.typ is u16 || field.typ is ?u16 { + instance.$(field.name) = match value { + i8 { u16(value) } + i16 { u16(value) } + int { u16(value) } + i64 { u16(value) } + u8 { u16(value) } + u16 { u16(value) } + u32 { u16(value) } + u64 { u16(value) } + bool { u16(value) } + else { 0 } + } + } $else $if field.typ is u32 || field.typ is ?u32 { + instance.$(field.name) = match value { + i8 { u32(value) } + i16 { u32(value) } + int { u32(value) } + i64 { u32(value) } + u8 { u32(value) } + u16 { u32(value) } + u32 { u32(value) } + u64 { u32(value) } + bool { u32(value) } + else { 0 } + } + } $else $if field.typ is u64 || field.typ is ?u64 { + instance.$(field.name) = match value { + i8 { u64(value) } + i16 { u64(value) } + int { u64(value) } + i64 { u64(value) } + u8 { u64(value) } + u16 { u64(value) } + u32 { u64(value) } + u64 { u64(value) } + bool { u64(value) } + else { 0 } + } + } $else $if field.typ is f32 || field.typ is ?f32 { + instance.$(field.name) = match value { + i8 { f32(value) } + i16 { f32(value) } + int { f32(value) } + i64 { f32(value) } + u8 { f32(value) } + u16 { f32(value) } + u32 { f32(value) } + u64 { f32(value) } + bool { f32(value) } + else { 0 } + } + } $else $if field.typ is f64 || field.typ is ?f64 { + instance.$(field.name) = match value { + i8 { f64(value) } + i16 { f64(value) } + int { f64(value) } + i64 { f64(value) } + u8 { f64(value) } + u16 { f64(value) } + u32 { f64(value) } + u64 { f64(value) } + bool { f64(value) } + else { 0 } + } + } $else $if field.typ is bool || field.typ is ?bool { + instance.$(field.name) = match value { + i8 { value != 0 } + i16 { value != 0 } + int { value != 0 } + i64 { value != 0 } + u8 { value != 0 } + u16 { value != 0 } + u32 { value != 0 } + u64 { value != 0 } + bool { value } + else { false } + } + } $else $if field.typ is string || field.typ is ?string { + instance.$(field.name) = value as string + } $else $if field.typ is time.Time || field.typ is ?time.Time { + if m.typ == time_ { + instance.$(field.name) = value as time.Time + } else if m.typ == type_string { + instance.$(field.name) = time.parse(value as string)! + } } } } diff --git a/vlib/orm/orm_func_test.v b/vlib/orm/orm_func_test.v index dd9834ce5e..a662fe35e9 100644 --- a/vlib/orm/orm_func_test.v +++ b/vlib/orm/orm_func_test.v @@ -4,15 +4,40 @@ import time @[table: 'sys_users'] struct User { - id int @[primary; serial] - name string - age int - role string - status int - salary int - title string - score int - created_at ?time.Time @[sql_type: 'TIMESTAMP'] + id int @[primary; serial] + name string + age int + role string + status int + salary int + title string + score int + created_at ?time.Time @[sql_type: 'TIMESTAMP'] + updated_at time.Time @[sql_type: 'TIMESTAMP'] + type_i8 i8 + type_i16 i16 + type_int int + type_i64 i64 + type_u8 u8 + type_u16 u16 + type_u32 u32 + type_u64 u64 + type_f32 f32 + type_f64 f64 + type_bool bool + type_string string + option_i8 ?i8 + option_i16 ?i16 + option_int ?int + option_i64 ?i64 + option_u8 ?u8 + option_u16 ?u16 + option_u32 ?u32 + option_u64 ?u64 + option_f32 ?f32 + option_f64 ?f64 + option_bool ?bool + option_string ?string } fn test_orm_func_where() { @@ -56,101 +81,351 @@ fn test_orm_func_where() { fn test_orm_func_stmts() { users := [ User{ - name: 'Tom' - age: 30 - role: 'admin' - status: 1 - salary: 5000 - title: 'manager' - score: 90 - created_at: time.now() + name: 'Tom' + age: 30 + role: 'admin' + status: 1 + salary: 5000 + title: 'manager' + score: 90 + created_at: time.now() + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, User{ - name: 'Alice' - age: 20 - role: 'employee' - status: 2 - salary: 2000 - title: 'doctor' - score: 95 - created_at: time.now() + name: 'Alice' + age: 20 + role: 'employee' + status: 2 + salary: 2000 + title: 'doctor' + score: 95 + created_at: time.now() + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, User{ - name: 'Mars' - age: 40 - role: 'employer' - status: 3 - salary: 1000 - title: 'doctor' - score: 85 - created_at: time.now() + name: 'Mars' + age: 40 + role: 'employer' + status: 3 + salary: 1000 + title: 'doctor' + score: 85 + created_at: time.now() + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, User{ - name: 'Kitty' - age: 18 - role: 'employer' - status: 1 - salary: 1500 - title: 'doctor' - score: 87 - created_at: time.now() + name: 'Kitty' + age: 18 + role: 'employer' + status: 1 + salary: 1500 + title: 'doctor' + score: 87 + created_at: time.now() + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, User{ - name: 'Silly' - age: 27 - role: 'employer' - status: 5 - salary: 2500 - title: 'doctor' - score: 81 + name: 'Silly' + age: 27 + role: 'employer' + status: 5 + salary: 2500 + title: 'doctor' + score: 81 + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, User{ - name: 'Smith' - age: 37 - role: 'employer' - status: 1 - salary: 4500 - title: 'doctor' - score: 89 - created_at: time.now() + name: 'Smith' + age: 37 + role: 'employer' + status: 1 + salary: 4500 + title: 'doctor' + score: 89 + created_at: time.now() + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, User{ - name: 'Bob' - age: 26 - role: 'employer' - status: 2 - salary: 6500 - title: 'doctor' - score: 81 - created_at: time.now() + name: 'Bob' + age: 26 + role: 'employer' + status: 2 + salary: 6500 + title: 'doctor' + score: 81 + created_at: time.now() + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, User{ - name: 'Peter' - age: 29 - role: 'employer' - status: 1 - salary: 3500 - title: 'doctor' - score: 80 - created_at: time.now() + name: 'Peter' + age: 29 + role: 'employer' + status: 1 + salary: 3500 + title: 'doctor' + score: 80 + created_at: time.now() + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, User{ - name: 'See' - age: 45 - role: 'employer' - status: 2 - salary: 8500 - title: 'doctor' - score: 82 + name: 'See' + age: 45 + role: 'employer' + status: 2 + salary: 8500 + title: 'doctor' + score: 82 + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, User{ - name: 'John' - age: 42 - role: 'employer' - status: 1 - salary: 10000 - title: 'doctor' - score: 88 + name: 'John' + age: 42 + role: 'employer' + status: 1 + salary: 10000 + title: 'doctor' + score: 88 + updated_at: time.now() + type_i8: 1 + type_i16: 2 + type_int: 3 + type_i64: 4 + type_u8: 5 + type_u16: 6 + type_u32: 7 + type_u64: 8 + type_f32: 1.1 + type_f64: 2.2 + type_bool: true + type_string: 'hello' + option_i8: 1 + option_i16: 2 + option_int: 3 + option_i64: 4 + option_u8: 5 + option_u16: 6 + option_u32: 7 + option_u64: 8 + option_f32: 1.1 + option_f64: 2.2 + option_bool: true + option_string: 'hello' }, ] mut db := sqlite.connect(':memory:')!