diff options
Diffstat (limited to 'v_windows/v/old/vlib/orm')
-rw-r--r-- | v_windows/v/old/vlib/orm/README.md | 85 | ||||
-rw-r--r-- | v_windows/v/old/vlib/orm/orm.v | 475 | ||||
-rw-r--r-- | v_windows/v/old/vlib/orm/orm_fn_test.v | 193 | ||||
-rw-r--r-- | v_windows/v/old/vlib/orm/orm_test.v | 290 |
4 files changed, 1043 insertions, 0 deletions
diff --git a/v_windows/v/old/vlib/orm/README.md b/v_windows/v/old/vlib/orm/README.md new file mode 100644 index 0000000..5cfa1fd --- /dev/null +++ b/v_windows/v/old/vlib/orm/README.md @@ -0,0 +1,85 @@ +# ORM + +## Attributes + +### Structs + +- `[table: '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 +- `[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 + +```v ignore +struct Foo { + id int [primary; sql: serial] + name string [nonull] +} +``` + +### Create + +```v ignore +sql db { + create table Foo +} +``` + +### Drop + +```v ignore +sql db { + drop table Foo +} +``` + +### Insert + +```v ignore +var := Foo{ + name: 'abc' +} + +sql db { + insert var into Foo +} +``` + +### Update + +```v ignore +sql db { + update Foo set name = 'cde' where name == 'abc' +} +``` + +### Delete +```v ignore +sql db { + delete from Foo where id > 10 +} +``` + +### Select +```v ignore +result := sql db { + select from Foo where id == 1 +} +``` +```v ignore +result := sql db { + select from Foo where id > 1 limit 5 +} +``` +```v ignore +result := sql db { + select from Foo where id > 1 order by id +} +``` diff --git a/v_windows/v/old/vlib/orm/orm.v b/v_windows/v/old/vlib/orm/orm.v new file mode 100644 index 0000000..e866960 --- /dev/null +++ b/v_windows/v/old/vlib/orm/orm.v @@ -0,0 +1,475 @@ +module orm + +import time + +pub const ( + num64 = [8, 12] + nums = [5, 6, 7, 9, 10, 11, 16] + float = [13, 14] + string = 18 + time = -2 + type_idx = map{ + 'i8': 5 + 'i16': 6 + 'int': 7 + 'i64': 8 + 'byte': 9 + 'u16': 10 + 'u32': 11 + 'u64': 12 + 'f32': 13 + 'f64': 14 + 'bool': 16 + 'string': 18 + } + string_max_len = 2048 +) + +pub type Primitive = InfixType | bool | byte | f32 | f64 | i16 | i64 | i8 | int | string | + time.Time | u16 | u32 | u64 + +pub enum OperationKind { + neq // != + eq // == + gt // > + lt // < + ge // >= + le // <= +} + +pub enum MathOperationKind { + add // + + sub // - + mul // * + div // / +} + +pub enum StmtKind { + insert + update + delete +} + +pub enum OrderType { + asc + desc +} + +fn (kind OperationKind) to_str() string { + str := match kind { + .neq { '!=' } + .eq { '=' } + .gt { '>' } + .lt { '<' } + .ge { '>=' } + .le { '<=' } + } + return str +} + +fn (kind OrderType) to_str() string { + return match kind { + .desc { + 'DESC' + } + .asc { + 'ASC' + } + } +} + +pub struct QueryData { +pub: + fields []string + data []Primitive + types []int + kinds []OperationKind + is_and []bool +} + +pub struct InfixType { +pub: + name string + operator MathOperationKind + right Primitive +} + +pub struct TableField { +pub: + name string + typ int + is_time bool + default_val string + is_arr bool + attrs []StructAttribute +} + +pub struct SelectConfig { +pub: + table string + is_count bool + has_where bool + has_order bool + order string + order_type OrderType + has_limit bool + primary string = 'id' // should be set if primary is different than 'id' and 'has_limit' is false + has_offset bool + fields []string + types []int +} + +pub interface Connection { + @select(config SelectConfig, data QueryData, where QueryData) ?[][]Primitive + insert(table string, data QueryData) ? + update(table string, data QueryData, where QueryData) ? + delete(table string, where QueryData) ? + create(table string, fields []TableField) ? + drop(talbe string) ? + last_id() Primitive +} + +pub fn orm_stmt_gen(table string, para string, kind StmtKind, num bool, qm string, start_pos int, data QueryData, where QueryData) string { + mut str := '' + + mut c := start_pos + + match kind { + .insert { + str += 'INSERT INTO $para$table$para (' + for i, field in data.fields { + str += '$para$field$para' + if i < data.fields.len - 1 { + str += ', ' + } + } + str += ') VALUES (' + for i, _ in data.fields { + str += qm + if num { + str += '$c' + c++ + } + if i < data.fields.len - 1 { + str += ', ' + } + } + str += ')' + } + .update { + str += 'UPDATE $para$table$para SET ' + for i, field in data.fields { + str += '$para$field$para = ' + if data.data.len > i { + d := data.data[i] + if d is InfixType { + op := match d.operator { + .add { + '+' + } + .sub { + '-' + } + .mul { + '*' + } + .div { + '/' + } + } + str += '$d.name $op $qm' + } else { + str += '$qm' + } + } else { + str += '$qm' + } + if num { + str += '$c' + c++ + } + if i < data.fields.len - 1 { + str += ', ' + } + } + str += ' WHERE ' + } + .delete { + str += 'DELETE FROM $para$table$para WHERE ' + } + } + if kind == .update || kind == .delete { + for i, field in where.fields { + str += '$para$field$para ${where.kinds[i].to_str()} $qm' + if num { + str += '$c' + c++ + } + if i < where.fields.len - 1 { + str += ' AND ' + } + } + } + str += ';' + return str +} + +pub fn orm_select_gen(orm SelectConfig, para string, num bool, qm string, start_pos int, where QueryData) string { + mut str := 'SELECT ' + + if orm.is_count { + str += 'COUNT(*)' + } else { + for i, field in orm.fields { + str += '$para$field$para' + if i < orm.fields.len - 1 { + str += ', ' + } + } + } + + str += ' FROM $para$orm.table$para' + + mut c := start_pos + + if orm.has_where { + str += ' WHERE ' + for i, field in where.fields { + str += '$para$field$para ${where.kinds[i].to_str()} $qm' + if num { + str += '$c' + c++ + } + if i < where.fields.len - 1 { + if where.is_and[i] { + str += ' AND ' + } else { + str += ' OR ' + } + } + } + } + + str += ' ORDER BY ' + if orm.has_order { + str += '$para$orm.order$para ' + str += orm.order_type.to_str() + } else { + str += '$para$orm.primary$para ' + str += orm.order_type.to_str() + } + + if orm.has_limit { + str += ' LIMIT ?' + if num { + str += '$c' + c++ + } + } + + if orm.has_offset { + str += ' OFFSET ?' + if num { + str += '$c' + c++ + } + } + + str += ';' + return str +} + +pub fn orm_table_gen(table string, para string, defaults bool, def_unique_len int, fields []TableField, sql_from_v fn (int) ?string, alternative bool) ?string { + mut str := 'CREATE TABLE IF NOT EXISTS $para$table$para (' + + if alternative { + str = 'IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=$para$table$para and xtype=${para}U$para) CREATE TABLE $para$table$para (' + } + + mut fs := []string{} + mut unique_fields := []string{} + mut unique := map[string][]string{} + mut primary := '' + + for field in fields { + if field.is_arr { + continue + } + mut no_null := false + mut is_unique := false + mut is_skip := false + mut unique_len := 0 + // mut fkey := '' + for attr in field.attrs { + match attr.name { + 'primary' { + primary = field.name + } + 'unique' { + if attr.arg != '' { + if attr.kind == .string { + unique[attr.arg] << field.name + continue + } else if attr.kind == .number { + unique_len = attr.arg.int() + is_unique = true + continue + } + } + is_unique = true + } + 'nonull' { + no_null = true + } + 'skip' { + is_skip = true + } + /*'fkey' { + if attr.arg != '' { + if attr.kind == .string { + fkey = attr.arg + continue + } + } + }*/ + else {} + } + } + if is_skip { + continue + } + mut stmt := '' + mut field_name := sql_field_name(field) + mut ctyp := sql_from_v(sql_field_type(field)) or { + field_name = '${field_name}_id' + sql_from_v(8) ? + } + if ctyp == '' { + return error('Unknown type ($field.typ) for field $field.name in struct $table') + } + stmt = '$para$field_name$para $ctyp' + if defaults && field.default_val != '' { + stmt += ' DEFAULT $field.default_val' + } + if no_null { + stmt += ' NOT NULL' + } + if is_unique { + mut f := 'UNIQUE KEY($para$field.name$para' + if ctyp == 'TEXT' && def_unique_len > 0 { + if unique_len > 0 { + f += '($unique_len)' + } else { + f += '($def_unique_len)' + } + } + f += ')' + unique_fields << f + } + fs << stmt + } + if primary == '' { + return error('A primary key is required for $table') + } + if unique.len > 0 { + for k, v in unique { + mut tmp := []string{} + for f in v { + tmp << '$para$f$para' + } + fs << '/* $k */UNIQUE(${tmp.join(', ')})' + } + } + fs << 'PRIMARY KEY($para$primary$para)' + fs << unique_fields + str += fs.join(', ') + str += ');' + return str +} + +fn sql_field_type(field TableField) int { + mut typ := field.typ + if field.is_time { + return -2 + } + for attr in field.attrs { + if attr.kind == .plain && attr.name == 'sql' && attr.arg != '' { + if attr.arg.to_lower() == 'serial' { + typ = -1 + break + } + typ = orm.type_idx[attr.arg] + break + } + } + return typ +} + +fn sql_field_name(field TableField) string { + mut name := field.name + for attr in field.attrs { + if attr.name == 'sql' && attr.has_arg && attr.kind == .string { + name = attr.arg + break + } + } + return name +} + +// needed for backend functions + +pub fn bool_to_primitive(b bool) Primitive { + return Primitive(b) +} + +pub fn f32_to_primitive(b f32) Primitive { + return Primitive(b) +} + +pub fn f64_to_primitive(b f64) Primitive { + return Primitive(b) +} + +pub fn i8_to_primitive(b i8) Primitive { + return Primitive(b) +} + +pub fn i16_to_primitive(b i16) Primitive { + return Primitive(b) +} + +pub fn int_to_primitive(b int) Primitive { + return Primitive(b) +} + +pub fn i64_to_primitive(b i64) Primitive { + return Primitive(b) +} + +pub fn byte_to_primitive(b byte) Primitive { + return Primitive(b) +} + +pub fn u16_to_primitive(b u16) Primitive { + return Primitive(b) +} + +pub fn u32_to_primitive(b u32) Primitive { + return Primitive(b) +} + +pub fn u64_to_primitive(b u64) Primitive { + return Primitive(b) +} + +pub fn string_to_primitive(b string) Primitive { + return Primitive(b) +} + +pub fn time_to_primitive(b time.Time) Primitive { + return Primitive(b) +} + +pub fn infix_to_primitive(b InfixType) Primitive { + return Primitive(b) +} diff --git a/v_windows/v/old/vlib/orm/orm_fn_test.v b/v_windows/v/old/vlib/orm/orm_fn_test.v new file mode 100644 index 0000000..d74a0cb --- /dev/null +++ b/v_windows/v/old/vlib/orm/orm_fn_test.v @@ -0,0 +1,193 @@ +import orm + +fn test_orm_stmt_gen_update() { + query := orm.orm_stmt_gen('Test', "'", .update, true, '?', 0, orm.QueryData{ + fields: ['test', 'a'] + data: [] + types: [] + kinds: [] + }, orm.QueryData{ + fields: ['id', 'name'] + data: [] + types: [] + kinds: [.ge, .eq] + }) + assert query == "UPDATE 'Test' SET 'test' = ?0, 'a' = ?1 WHERE 'id' >= ?2 AND 'name' = ?3;" +} + +fn test_orm_stmt_gen_insert() { + query := orm.orm_stmt_gen('Test', "'", .insert, true, '?', 0, orm.QueryData{ + fields: ['test', 'a'] + data: [] + types: [] + kinds: [] + }, orm.QueryData{}) + assert query == "INSERT INTO 'Test' ('test', 'a') VALUES (?0, ?1);" +} + +fn test_orm_stmt_gen_delete() { + query := orm.orm_stmt_gen('Test', "'", .delete, true, '?', 0, orm.QueryData{ + fields: ['test', 'a'] + data: [] + types: [] + kinds: [] + }, orm.QueryData{ + fields: ['id', 'name'] + data: [] + types: [] + kinds: [.ge, .eq] + }) + assert query == "DELETE FROM 'Test' WHERE 'id' >= ?0 AND 'name' = ?1;" +} + +fn get_select_fields() []string { + return ['id', 'test', 'abc'] +} + +fn test_orm_select_gen() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY 'id' ASC;" +} + +fn test_orm_select_gen_with_limit() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_limit: true + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY 'id' ASC LIMIT ?0;" +} + +fn test_orm_select_gen_with_where() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_where: true + }, "'", true, '?', 0, orm.QueryData{ + fields: ['abc', 'test'] + kinds: [.eq, .gt] + is_and: [true] + }) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' WHERE 'abc' = ?0 AND 'test' > ?1 ORDER BY 'id' ASC;" +} + +fn test_orm_select_gen_with_order() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_order: true + order_type: .desc + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY '' DESC;" +} + +fn test_orm_select_gen_with_offset() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_offset: true + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY 'id' ASC OFFSET ?0;" +} + +fn test_orm_select_gen_with_all() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_limit: true + has_order: true + order_type: .desc + has_offset: true + has_where: true + }, "'", true, '?', 0, orm.QueryData{ + fields: ['abc', 'test'] + kinds: [.eq, .gt] + is_and: [true] + }) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' WHERE 'abc' = ?0 AND 'test' > ?1 ORDER BY '' DESC LIMIT ?2 OFFSET ?3;" +} + +fn test_orm_table_gen() { + query := orm.orm_table_gen('test_table', "'", true, 0, [ + orm.TableField{ + name: 'id' + typ: 7 + default_val: '10' + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + arg: 'serial' + kind: .plain + }, + ] + }, + orm.TableField{ + name: 'test' + typ: 18 + }, + orm.TableField{ + name: 'abc' + typ: 8 + default_val: '6754' + }, + ], sql_type_from_v, false) or { panic(err) } + assert query == "CREATE TABLE IF NOT EXISTS 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT, 'abc' INT64 DEFAULT 6754, PRIMARY KEY('id'));" + + alt_query := orm.orm_table_gen('test_table', "'", true, 0, [ + orm.TableField{ + name: 'id' + typ: 7 + default_val: '10' + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + arg: 'serial' + kind: .plain + }, + ] + }, + orm.TableField{ + name: 'test' + typ: 18 + }, + orm.TableField{ + name: 'abc' + typ: 8 + default_val: '6754' + }, + ], sql_type_from_v, true) or { panic(err) } + assert alt_query == "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='test_table' and xtype='U') CREATE TABLE 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT, 'abc' INT64 DEFAULT 6754, PRIMARY KEY('id'));" +} + +fn sql_type_from_v(typ int) ?string { + return if typ in orm.nums { + 'INT' + } else if typ in orm.num64 { + 'INT64' + } else if typ in orm.float { + 'DOUBLE' + } else if typ == orm.string { + 'TEXT' + } else if typ == -1 { + 'SERIAL' + } else { + error('Unknown type $typ') + } +} diff --git a/v_windows/v/old/vlib/orm/orm_test.v b/v_windows/v/old/vlib/orm/orm_test.v new file mode 100644 index 0000000..5be8fe2 --- /dev/null +++ b/v_windows/v/old/vlib/orm/orm_test.v @@ -0,0 +1,290 @@ +// import os +// import pg +// import term +import sqlite + +struct Module { + id int [primary; sql: serial] + name string + nr_downloads int + user User +} + +[table: 'userlist'] +struct User { + id int [primary; sql: serial] + age int + name string [sql: 'username'] + is_customer bool + skipped_string string [skip] +} + +struct Foo { + age int +} + +fn test_orm_sqlite() { + db := sqlite.connect(':memory:') or { panic(err) } + db.exec('drop table if exists User') + sql db { + create table User + } + + name := 'Peter' + + sam := User{ + age: 29 + name: 'Sam' + } + + peter := User{ + age: 31 + name: 'Peter' + } + + k := User{ + age: 30 + name: 'Kate' + is_customer: true + } + + sql db { + insert sam into User + insert peter into User + insert k into User + } + + c := sql db { + select count from User where id != 1 + } + assert c == 2 + + nr_all_users := sql db { + select count from User + } + assert nr_all_users == 3 + println('nr_all_users=$nr_all_users') + // + nr_users1 := sql db { + select count from User where id == 1 + } + assert nr_users1 == 1 + println('nr_users1=$nr_users1') + // + nr_peters := sql db { + select count from User where id == 2 && name == 'Peter' + } + assert nr_peters == 1 + println('nr_peters=$nr_peters') + // + nr_peters2 := sql db { + select count from User where id == 2 && name == name + } + assert nr_peters2 == 1 + nr_peters3 := sql db { + select count from User where name == name + } + assert nr_peters3 == 1 + peters := sql db { + select from User where name == name + } + assert peters.len == 1 + assert peters[0].name == 'Peter' + one_peter := sql db { + select from User where name == name limit 1 + } + assert one_peter.name == 'Peter' + assert one_peter.id == 2 + // + user := sql db { + select from User where id == 1 + } + println(user) + assert user.name == 'Sam' + assert user.id == 1 + assert user.age == 29 + // + users := sql db { + select from User where id > 0 + } + println(users) + assert users.len == 3 + assert users[0].name == 'Sam' + assert users[1].name == 'Peter' + assert users[1].age == 31 + // + users2 := sql db { + select from User where id < 0 + } + println(users2) + assert users2.len == 0 + // + users3 := sql db { + select from User where age == 29 || age == 31 + } + println(users3) + assert users3.len == 2 + assert users3[0].age == 29 + assert users3[1].age == 31 + // + missing_user := sql db { + select from User where id == 8777 + } + println('missing_user:') + println(missing_user) // zero struct + // + new_user := User{ + name: 'New user' + age: 30 + } + sql db { + insert new_user into User + } + + // db.insert<User>(user2) + x := sql db { + select from User where id == 4 + } + println(x) + assert x.age == 30 + assert x.id == 4 + assert x.name == 'New user' + // + kate := sql db { + select from User where id == 3 + } + assert kate.is_customer == true + // + customer := sql db { + select from User where is_customer == true limit 1 + } + assert customer.is_customer == true + assert customer.name == 'Kate' + // + sql db { + update User set age = 31 where name == 'Kate' + } + + kate2 := sql db { + select from User where id == 3 + } + assert kate2.age == 31 + assert kate2.name == 'Kate' + // + sql db { + update User set age = 32, name = 'Kate N' where name == 'Kate' + } + + mut kate3 := sql db { + select from User where id == 3 + } + assert kate3.age == 32 + assert kate3.name == 'Kate N' + // + /* + sql db { + update User set age = age + 1, name = 'Kate N' where name == 'Kate' + } + kate3 = sql db { + select from User where id == 3 + } + println(kate3) + assert kate3.age == 32 + assert kate3.name == 'Kate N' + */ + new_age := 33 + sql db { + update User set age = new_age, name = 'Kate N' where id == 3 + } + + kate3 = sql db { + select from User where id == 3 + } + assert kate3.age == 33 + assert kate3.name == 'Kate N' + // + foo := Foo{34} + sql db { + update User set age = foo.age, name = 'Kate N' where id == 3 + } + + kate3 = sql db { + select from User where id == 3 + } + assert kate3.age == 34 + assert kate3.name == 'Kate N' + // + no_user := sql db { + select from User where id == 30 + } + assert no_user.name == '' // TODO optional + assert no_user.age == 0 + // + two_users := sql db { + select from User limit 2 + } + assert two_users.len == 2 + assert two_users[0].id == 1 + // + y := sql db { + select from User limit 2 offset 1 + } + assert y.len == 2 + assert y[0].id == 2 + // + offset_const := 2 + z := sql db { + select from User limit 2 offset offset_const + } + assert z.len == 2 + assert z[0].id == 3 + oldest := sql db { + select from User order by age desc limit 1 + } + assert oldest.age == 34 + offs := 1 + second_oldest := sql db { + select from User order by age desc limit 1 offset offs + } + assert second_oldest.age == 31 + sql db { + delete from User where age == 34 + } + + updated_oldest := sql db { + select from User order by age desc limit 1 + } + assert updated_oldest.age == 31 + + db.exec('insert into User (name, age) values (NULL, 31)') + null_user := sql db { + select from User where id == 5 + } + assert null_user.name == '' + + age_test := sql db { + select from User where id == 1 + } + + assert age_test.age == 29 + + sql db { + update User set age = age + 1 where id == 1 + } + + mut first := sql db { + select from User where id == 1 + } + + assert first.age == 30 + + sql db { + update User set age = age * 2 where id == 1 + } + + first = sql db { + select from User where id == 1 + } + + assert first.age == 60 +} |