mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
orm: default attribute (#15221)
This commit is contained in:

committed by
GitHub

parent
c976a691ad
commit
e5e750d533
@ -1,6 +1,7 @@
|
|||||||
import orm
|
import orm
|
||||||
import mysql
|
import mysql
|
||||||
import time
|
import time
|
||||||
|
import v.ast
|
||||||
|
|
||||||
struct TestCustomSqlType {
|
struct TestCustomSqlType {
|
||||||
id int [primary; sql: serial]
|
id int [primary; sql: serial]
|
||||||
@ -29,20 +30,28 @@ mut:
|
|||||||
deleted_at time.Time
|
deleted_at time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TestDefaultAtribute {
|
||||||
|
id string [primary; sql: serial]
|
||||||
|
name string
|
||||||
|
created_at string [default: 'CURRENT_TIMESTAMP'; sql_type: 'TIMESTAMP']
|
||||||
|
}
|
||||||
|
|
||||||
fn test_mysql_orm() {
|
fn test_mysql_orm() {
|
||||||
mut mdb := mysql.Connection{
|
mut db := mysql.Connection{
|
||||||
host: 'localhost'
|
host: 'localhost'
|
||||||
port: 3306
|
port: 3306
|
||||||
username: 'root'
|
username: 'root'
|
||||||
password: ''
|
password: ''
|
||||||
dbname: 'mysql'
|
dbname: 'mysql'
|
||||||
}
|
}
|
||||||
mdb.connect() or { panic(err) }
|
db.connect() or { panic(err) }
|
||||||
db := orm.Connection(mdb)
|
defer {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
db.create('Test', [
|
db.create('Test', [
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'id'
|
name: 'id'
|
||||||
typ: 7
|
typ: ast.int_type_idx
|
||||||
attrs: [
|
attrs: [
|
||||||
StructAttribute{
|
StructAttribute{
|
||||||
name: 'primary'
|
name: 'primary'
|
||||||
@ -57,12 +66,12 @@ fn test_mysql_orm() {
|
|||||||
},
|
},
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'name'
|
name: 'name'
|
||||||
typ: 20
|
typ: ast.string_type_idx
|
||||||
attrs: []
|
attrs: []
|
||||||
},
|
},
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'age'
|
name: 'age'
|
||||||
typ: 7
|
typ: ast.int_type_idx
|
||||||
},
|
},
|
||||||
]) or { panic(err) }
|
]) or { panic(err) }
|
||||||
|
|
||||||
@ -75,11 +84,11 @@ fn test_mysql_orm() {
|
|||||||
table: 'Test'
|
table: 'Test'
|
||||||
has_where: true
|
has_where: true
|
||||||
fields: ['id', 'name', 'age']
|
fields: ['id', 'name', 'age']
|
||||||
types: [7, 20, 8]
|
types: [ast.int_type_idx, ast.string_type_idx, ast.i64_type_idx]
|
||||||
}, orm.QueryData{}, orm.QueryData{
|
}, orm.QueryData{}, orm.QueryData{
|
||||||
fields: ['name', 'age']
|
fields: ['name', 'age']
|
||||||
data: [orm.Primitive('Louis'), i64(101)]
|
data: [orm.Primitive('Louis'), i64(101)]
|
||||||
types: [20, 8]
|
types: [ast.string_type_idx, ast.i64_type_idx]
|
||||||
is_and: [true, true]
|
is_and: [true, true]
|
||||||
kinds: [.eq, .eq]
|
kinds: [.eq, .eq]
|
||||||
}) or { panic(err) }
|
}) or { panic(err) }
|
||||||
@ -88,8 +97,6 @@ fn test_mysql_orm() {
|
|||||||
name := res[0][1]
|
name := res[0][1]
|
||||||
age := res[0][2]
|
age := res[0][2]
|
||||||
|
|
||||||
mdb.close()
|
|
||||||
|
|
||||||
assert id is int
|
assert id is int
|
||||||
if id is int {
|
if id is int {
|
||||||
assert id == 1
|
assert id == 1
|
||||||
@ -104,22 +111,10 @@ fn test_mysql_orm() {
|
|||||||
if age is i64 {
|
if age is i64 {
|
||||||
assert age == 101
|
assert age == 101
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn test_orm() {
|
|
||||||
mut db := mysql.Connection{
|
|
||||||
host: 'localhost'
|
|
||||||
port: 3306
|
|
||||||
username: 'root'
|
|
||||||
password: ''
|
|
||||||
dbname: 'mysql'
|
|
||||||
}
|
|
||||||
|
|
||||||
db.connect() or {
|
|
||||||
println(err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/** test orm sql type
|
||||||
|
* - verify if all type create by attribute sql_type has created
|
||||||
|
*/
|
||||||
sql db {
|
sql db {
|
||||||
create table TestCustomSqlType
|
create table TestCustomSqlType
|
||||||
}
|
}
|
||||||
@ -168,27 +163,19 @@ fn test_orm() {
|
|||||||
sql db {
|
sql db {
|
||||||
drop table TestCustomSqlType
|
drop table TestCustomSqlType
|
||||||
}
|
}
|
||||||
db.close()
|
|
||||||
|
|
||||||
assert result_custom_sql.maps() == information_schema_custom_sql
|
assert result_custom_sql.maps() == information_schema_custom_sql
|
||||||
}
|
|
||||||
|
|
||||||
fn test_orm_time_type() ? {
|
/** test_orm_time_type
|
||||||
mut db := mysql.Connection{
|
* - test time.Time v type with sql_type: 'TIMESTAMP'
|
||||||
host: 'localhost'
|
* - test string v type with sql_type: 'TIMESTAMP'
|
||||||
port: 3306
|
* - test time.Time v type without
|
||||||
username: 'root'
|
*/
|
||||||
password: ''
|
today := time.parse('2022-07-16 15:13:27') or {
|
||||||
dbname: 'mysql'
|
|
||||||
}
|
|
||||||
|
|
||||||
db.connect() or {
|
|
||||||
println(err)
|
println(err)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
today := time.parse('2022-07-16 15:13:27')?
|
|
||||||
|
|
||||||
model := TestTimeType{
|
model := TestTimeType{
|
||||||
username: 'hitalo'
|
username: 'hitalo'
|
||||||
created_at: today
|
created_at: today
|
||||||
@ -212,10 +199,38 @@ fn test_orm_time_type() ? {
|
|||||||
drop table TestTimeType
|
drop table TestTimeType
|
||||||
}
|
}
|
||||||
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
assert results[0].username == model.username
|
assert results[0].username == model.username
|
||||||
assert results[0].created_at == model.created_at
|
assert results[0].created_at == model.created_at
|
||||||
assert results[0].updated_at == model.updated_at
|
assert results[0].updated_at == model.updated_at
|
||||||
assert results[0].deleted_at == model.deleted_at
|
assert results[0].deleted_at == model.deleted_at
|
||||||
|
|
||||||
|
/** test default attribute
|
||||||
|
*/
|
||||||
|
sql db {
|
||||||
|
create table TestDefaultAtribute
|
||||||
|
}
|
||||||
|
|
||||||
|
mut result_defaults := db.query("
|
||||||
|
SELECT COLUMN_DEFAULT
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_NAME = 'TestDefaultAtribute'
|
||||||
|
ORDER BY ORDINAL_POSITION
|
||||||
|
") or {
|
||||||
|
println(err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mut information_schema_defaults_results := []string{}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
drop table TestDefaultAtribute
|
||||||
|
}
|
||||||
|
|
||||||
|
information_schema_column_default_sql := [{
|
||||||
|
'COLUMN_DEFAULT': ''
|
||||||
|
}, {
|
||||||
|
'COLUMN_DEFAULT': ''
|
||||||
|
}, {
|
||||||
|
'COLUMN_DEFAULT': 'CURRENT_TIMESTAMP'
|
||||||
|
}]
|
||||||
|
assert information_schema_column_default_sql == result_defaults.maps()
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ pub fn (db Connection) insert(table string, data orm.QueryData) ? {
|
|||||||
mut converted_primitive_array := db.factory_orm_primitive_converted_from_sql(table,
|
mut converted_primitive_array := db.factory_orm_primitive_converted_from_sql(table,
|
||||||
data)?
|
data)?
|
||||||
|
|
||||||
converted_data := orm.QueryData{
|
converted_primitive_data := orm.QueryData{
|
||||||
fields: data.fields
|
fields: data.fields
|
||||||
data: converted_primitive_array
|
data: converted_primitive_array
|
||||||
types: []
|
types: []
|
||||||
@ -132,17 +132,19 @@ pub fn (db Connection) insert(table string, data orm.QueryData) ? {
|
|||||||
is_and: []
|
is_and: []
|
||||||
}
|
}
|
||||||
|
|
||||||
query := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, converted_data, orm.QueryData{})
|
query, converted_data := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, converted_primitive_data,
|
||||||
|
orm.QueryData{})
|
||||||
mysql_stmt_worker(db, query, converted_data, orm.QueryData{})?
|
mysql_stmt_worker(db, query, converted_data, orm.QueryData{})?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (db Connection) update(table string, data orm.QueryData, where orm.QueryData) ? {
|
pub fn (db Connection) update(table string, data orm.QueryData, where orm.QueryData) ? {
|
||||||
query := orm.orm_stmt_gen(table, '`', .update, false, '?', 1, data, where)
|
query, _ := orm.orm_stmt_gen(table, '`', .update, false, '?', 1, data, where)
|
||||||
mysql_stmt_worker(db, query, data, where)?
|
mysql_stmt_worker(db, query, data, where)?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (db Connection) delete(table string, where orm.QueryData) ? {
|
pub fn (db Connection) delete(table string, where orm.QueryData) ? {
|
||||||
query := orm.orm_stmt_gen(table, '`', .delete, false, '?', 1, orm.QueryData{}, where)
|
query, _ := orm.orm_stmt_gen(table, '`', .delete, false, '?', 1, orm.QueryData{},
|
||||||
|
where)
|
||||||
mysql_stmt_worker(db, query, orm.QueryData{}, where)?
|
mysql_stmt_worker(db, query, orm.QueryData{}, where)?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +160,7 @@ pub fn (db Connection) last_id() orm.Primitive {
|
|||||||
|
|
||||||
// table
|
// table
|
||||||
pub fn (db Connection) create(table string, fields []orm.TableField) ? {
|
pub fn (db Connection) create(table string, fields []orm.TableField) ? {
|
||||||
query := orm.orm_table_gen(table, '`', false, 0, fields, mysql_type_from_v, false) or {
|
query := orm.orm_table_gen(table, '`', true, 0, fields, mysql_type_from_v, false) or {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})?
|
mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})?
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
- `[sql: type]` where `type` is a V type such as `int` or `f64`, or special type `serial`
|
- `[sql: type]` where `type` is a V type such as `int` or `f64`, or special type `serial`
|
||||||
- `[sql: 'name']` sets a custom column name for the field
|
- `[sql: 'name']` sets a custom column name for the field
|
||||||
- `[sql_type: 'SQL TYPE']` sets the sql type which is used in sql
|
- `[sql_type: 'SQL TYPE']` sets the sql type which is used in sql
|
||||||
|
- `[default: 'sql defaults']` sets the default value or function when create a new table
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -90,3 +91,53 @@ result := sql db {
|
|||||||
select from Foo where id > 1 order by id
|
select from Foo where id > 1 order by id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```v ignore
|
||||||
|
import pg
|
||||||
|
|
||||||
|
struct Member {
|
||||||
|
id string [default: 'gen_random_uuid()'; primary; sql_type: 'uuid']
|
||||||
|
name string
|
||||||
|
created_at string [default: 'CURRENT_TIMESTAMP'; sql_type: 'TIMESTAMP']
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
db := pg.connect(pg.Config{
|
||||||
|
host: 'localhost'
|
||||||
|
port: 5432
|
||||||
|
user: 'user'
|
||||||
|
password: 'password'
|
||||||
|
dbname: 'dbname'
|
||||||
|
}) or {
|
||||||
|
println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table Member
|
||||||
|
}
|
||||||
|
|
||||||
|
new_member := Member{
|
||||||
|
name: 'John Doe'
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
insert new_member into Member
|
||||||
|
}
|
||||||
|
|
||||||
|
selected_member := sql db {
|
||||||
|
select from Member where name == 'John Doe' limit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
update Member set name = 'Hitalo' where id == selected_member.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
@ -182,27 +182,42 @@ pub interface Connection {
|
|||||||
// num - Stmt uses nums at prepared statements (? or ?1)
|
// num - Stmt uses nums at prepared statements (? or ?1)
|
||||||
// qm - Character for prepared statment, qm because of quotation mark like in sqlite
|
// qm - Character for prepared statment, qm because of quotation mark like in sqlite
|
||||||
// start_pos - When num is true, it's the start position of the counter
|
// start_pos - When num is true, it's the start position of the counter
|
||||||
pub fn orm_stmt_gen(table string, q string, kind StmtKind, num bool, qm string, start_pos int, data QueryData, where QueryData) string {
|
pub fn orm_stmt_gen(table string, q string, kind StmtKind, num bool, qm string, start_pos int, data QueryData, where QueryData) (string, QueryData) {
|
||||||
mut str := ''
|
mut str := ''
|
||||||
|
|
||||||
mut c := start_pos
|
mut c := start_pos
|
||||||
|
mut data_fields := []string{}
|
||||||
|
mut data_data := []Primitive{}
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
.insert {
|
.insert {
|
||||||
mut values := []string{}
|
mut values := []string{}
|
||||||
|
mut select_fields := []string{}
|
||||||
|
|
||||||
for _ in 0 .. data.fields.len {
|
for i in 0 .. data.fields.len {
|
||||||
// loop over the length of data.field and generate ?0, ?1 or just ? based on the $num qmeter for value placeholders
|
if data.data.len > 0 {
|
||||||
if num {
|
match data.data[i].type_name() {
|
||||||
values << '$qm$c'
|
'string' {
|
||||||
c++
|
if (data.data[i] as string).len == 0 {
|
||||||
} else {
|
continue
|
||||||
values << '$qm'
|
}
|
||||||
|
}
|
||||||
|
'time.Time' {
|
||||||
|
if (data.data[i] as time.Time).unix == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
data_data << data.data[i]
|
||||||
}
|
}
|
||||||
|
select_fields << '$q${data.fields[i]}$q'
|
||||||
|
values << factory_insert_qm_value(num, qm, c)
|
||||||
|
data_fields << data.fields[i]
|
||||||
|
c++
|
||||||
}
|
}
|
||||||
|
|
||||||
str += 'INSERT INTO $q$table$q ('
|
str += 'INSERT INTO $q$table$q ('
|
||||||
str += data.fields.map('$q$it$q').join(', ')
|
str += select_fields.join(', ')
|
||||||
str += ') VALUES ('
|
str += ') VALUES ('
|
||||||
str += values.join(', ')
|
str += values.join(', ')
|
||||||
str += ')'
|
str += ')'
|
||||||
@ -262,7 +277,13 @@ pub fn orm_stmt_gen(table string, q string, kind StmtKind, num bool, qm string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
str += ';'
|
str += ';'
|
||||||
return str
|
return str, QueryData{
|
||||||
|
fields: data_fields
|
||||||
|
data: data_data
|
||||||
|
types: data.types
|
||||||
|
kinds: data.kinds
|
||||||
|
is_and: data.is_and
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates an sql select stmt, from universal parameter
|
// Generates an sql select stmt, from universal parameter
|
||||||
@ -357,6 +378,7 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
|
|||||||
if field.is_arr {
|
if field.is_arr {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
mut default_val := field.default_val
|
||||||
mut no_null := false
|
mut no_null := false
|
||||||
mut is_unique := false
|
mut is_unique := false
|
||||||
mut is_skip := false
|
mut is_skip := false
|
||||||
@ -397,6 +419,14 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
|
|||||||
}
|
}
|
||||||
ctyp = attr.arg
|
ctyp = attr.arg
|
||||||
}
|
}
|
||||||
|
'default' {
|
||||||
|
if attr.kind != .string {
|
||||||
|
return error("default attribute need be string. Try [default: '$attr.arg'] instead of [default: $attr.arg]")
|
||||||
|
}
|
||||||
|
if default_val == '' {
|
||||||
|
default_val = attr.arg
|
||||||
|
}
|
||||||
|
}
|
||||||
/*'fkey' {
|
/*'fkey' {
|
||||||
if attr.arg != '' {
|
if attr.arg != '' {
|
||||||
if attr.kind == .string {
|
if attr.kind == .string {
|
||||||
@ -416,8 +446,8 @@ pub fn orm_table_gen(table string, q string, defaults bool, def_unique_len int,
|
|||||||
return error('Unknown type ($field.typ) for field $field.name in struct $table')
|
return error('Unknown type ($field.typ) for field $field.name in struct $table')
|
||||||
}
|
}
|
||||||
stmt = '$q$field_name$q $ctyp'
|
stmt = '$q$field_name$q $ctyp'
|
||||||
if defaults && field.default_val != '' {
|
if defaults && default_val != '' {
|
||||||
stmt += ' DEFAULT $field.default_val'
|
stmt += ' DEFAULT $default_val'
|
||||||
}
|
}
|
||||||
if no_null {
|
if no_null {
|
||||||
stmt += ' NOT NULL'
|
stmt += ' NOT NULL'
|
||||||
@ -543,3 +573,11 @@ pub fn time_to_primitive(b time.Time) Primitive {
|
|||||||
pub fn infix_to_primitive(b InfixType) Primitive {
|
pub fn infix_to_primitive(b InfixType) Primitive {
|
||||||
return Primitive(b)
|
return Primitive(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn factory_insert_qm_value(num bool, qm string, c int) string {
|
||||||
|
if num {
|
||||||
|
return '$qm$c'
|
||||||
|
} else {
|
||||||
|
return '$qm'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ import orm
|
|||||||
import v.ast
|
import v.ast
|
||||||
|
|
||||||
fn test_orm_stmt_gen_update() {
|
fn test_orm_stmt_gen_update() {
|
||||||
query := orm.orm_stmt_gen('Test', "'", .update, true, '?', 0, orm.QueryData{
|
query, _ := orm.orm_stmt_gen('Test', "'", .update, true, '?', 0, orm.QueryData{
|
||||||
fields: ['test', 'a']
|
fields: ['test', 'a']
|
||||||
data: []
|
data: []
|
||||||
types: []
|
types: []
|
||||||
@ -17,7 +17,7 @@ fn test_orm_stmt_gen_update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_orm_stmt_gen_insert() {
|
fn test_orm_stmt_gen_insert() {
|
||||||
query := orm.orm_stmt_gen('Test', "'", .insert, true, '?', 0, orm.QueryData{
|
query, _ := orm.orm_stmt_gen('Test', "'", .insert, true, '?', 0, orm.QueryData{
|
||||||
fields: ['test', 'a']
|
fields: ['test', 'a']
|
||||||
data: []
|
data: []
|
||||||
types: []
|
types: []
|
||||||
@ -27,7 +27,7 @@ fn test_orm_stmt_gen_insert() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_orm_stmt_gen_delete() {
|
fn test_orm_stmt_gen_delete() {
|
||||||
query := orm.orm_stmt_gen('Test', "'", .delete, true, '?', 0, orm.QueryData{
|
query, _ := orm.orm_stmt_gen('Test', "'", .delete, true, '?', 0, orm.QueryData{
|
||||||
fields: ['test', 'a']
|
fields: ['test', 'a']
|
||||||
data: []
|
data: []
|
||||||
types: []
|
types: []
|
||||||
|
@ -31,17 +31,18 @@ pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.Qu
|
|||||||
// sql stmt
|
// sql stmt
|
||||||
|
|
||||||
pub fn (db DB) insert(table string, data orm.QueryData) ? {
|
pub fn (db DB) insert(table string, data orm.QueryData) ? {
|
||||||
query := orm.orm_stmt_gen(table, '"', .insert, true, '$', 1, data, orm.QueryData{})
|
query, converted_data := orm.orm_stmt_gen(table, '"', .insert, true, '$', 1, data,
|
||||||
pg_stmt_worker(db, query, data, orm.QueryData{})?
|
orm.QueryData{})
|
||||||
|
pg_stmt_worker(db, query, converted_data, orm.QueryData{})?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ? {
|
pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ? {
|
||||||
query := orm.orm_stmt_gen(table, '"', .update, true, '$', 1, data, where)
|
query, _ := orm.orm_stmt_gen(table, '"', .update, true, '$', 1, data, where)
|
||||||
pg_stmt_worker(db, query, data, where)?
|
pg_stmt_worker(db, query, data, where)?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (db DB) delete(table string, where orm.QueryData) ? {
|
pub fn (db DB) delete(table string, where orm.QueryData) ? {
|
||||||
query := orm.orm_stmt_gen(table, '"', .delete, true, '$', 1, orm.QueryData{}, where)
|
query, _ := orm.orm_stmt_gen(table, '"', .delete, true, '$', 1, orm.QueryData{}, where)
|
||||||
pg_stmt_worker(db, query, orm.QueryData{}, where)?
|
pg_stmt_worker(db, query, orm.QueryData{}, where)?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,18 +164,20 @@ fn pg_stmt_match(mut types []u32, mut vals []&char, mut lens []int, mut formats
|
|||||||
formats << 1
|
formats << 1
|
||||||
}
|
}
|
||||||
string {
|
string {
|
||||||
types << u32(Oid.t_text)
|
// If paramTypes is NULL, or any particular element in the array is zero,
|
||||||
|
// the server infers a data type for the parameter symbol in the same way
|
||||||
|
// it would do for an untyped literal string.
|
||||||
|
types << &u8(0)
|
||||||
vals << data.str
|
vals << data.str
|
||||||
lens << data.len
|
lens << data.len
|
||||||
formats << 0
|
formats << 0
|
||||||
}
|
}
|
||||||
time.Time {
|
time.Time {
|
||||||
types << u32(Oid.t_int4)
|
datetime := ((d as time.Time).format_ss() as string)
|
||||||
unix := int(data.unix)
|
types << &u8(0)
|
||||||
num := conv.htn32(unsafe { &u32(&unix) })
|
vals << datetime.str
|
||||||
vals << &char(&num)
|
lens << datetime.len
|
||||||
lens << int(sizeof(u32))
|
formats << 0
|
||||||
formats << 1
|
|
||||||
}
|
}
|
||||||
orm.InfixType {
|
orm.InfixType {
|
||||||
pg_stmt_match(mut types, mut vals, mut lens, mut formats, data.right)
|
pg_stmt_match(mut types, mut vals, mut lens, mut formats, data.right)
|
||||||
@ -190,9 +193,12 @@ fn pg_type_from_v(typ int) ?string {
|
|||||||
orm.type_idx['bool'] {
|
orm.type_idx['bool'] {
|
||||||
'BOOLEAN'
|
'BOOLEAN'
|
||||||
}
|
}
|
||||||
orm.type_idx['int'], orm.type_idx['u32'], orm.time {
|
orm.type_idx['int'], orm.type_idx['u32'] {
|
||||||
'INT'
|
'INT'
|
||||||
}
|
}
|
||||||
|
orm.time {
|
||||||
|
'TIMESTAMP'
|
||||||
|
}
|
||||||
orm.type_idx['i64'], orm.type_idx['u64'] {
|
orm.type_idx['i64'], orm.type_idx['u64'] {
|
||||||
'BIGINT'
|
'BIGINT'
|
||||||
}
|
}
|
||||||
|
@ -2,39 +2,89 @@ module main
|
|||||||
|
|
||||||
import orm
|
import orm
|
||||||
import pg
|
import pg
|
||||||
|
import v.ast
|
||||||
|
import time
|
||||||
|
|
||||||
|
struct TestCustomSqlType {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
custom string [sql_type: 'TEXT']
|
||||||
|
custom1 string [sql_type: 'VARCHAR(191)']
|
||||||
|
custom2 string [sql_type: 'TIMESTAMP']
|
||||||
|
custom3 string [sql_type: 'uuid']
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestCustomWrongSqlType {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
custom string
|
||||||
|
custom1 string [sql_type: 'VARCHAR']
|
||||||
|
custom2 string [sql_type: 'money']
|
||||||
|
custom3 string [sql_type: 'xml']
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestTimeType {
|
||||||
|
mut:
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
username string
|
||||||
|
created_at time.Time [sql_type: 'TIMESTAMP']
|
||||||
|
updated_at string [sql_type: 'TIMESTAMP']
|
||||||
|
deleted_at time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestDefaultAtribute {
|
||||||
|
id string [default: 'gen_random_uuid()'; primary; sql_type: 'uuid']
|
||||||
|
name string
|
||||||
|
created_at string [default: 'CURRENT_TIMESTAMP'; sql_type: 'TIMESTAMP']
|
||||||
|
}
|
||||||
|
|
||||||
fn test_pg_orm() {
|
fn test_pg_orm() {
|
||||||
mut db := pg.connect(
|
mut db := pg.connect(
|
||||||
host: 'localhost'
|
host: 'localhost'
|
||||||
user: 'postgres'
|
user: 'postgres'
|
||||||
password: ''
|
password: 'password'
|
||||||
dbname: 'postgres'
|
dbname: 'postgres'
|
||||||
) or { panic(err) }
|
) or { panic(err) }
|
||||||
|
|
||||||
|
defer {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
db.create('Test', [
|
db.create('Test', [
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'id'
|
name: 'id'
|
||||||
typ: 7
|
typ: ast.string_type_idx
|
||||||
|
is_time: false
|
||||||
|
default_val: ''
|
||||||
|
is_arr: false
|
||||||
attrs: [
|
attrs: [
|
||||||
StructAttribute{
|
StructAttribute{
|
||||||
name: 'primary'
|
name: 'primary'
|
||||||
|
has_arg: false
|
||||||
|
arg: ''
|
||||||
|
kind: .plain
|
||||||
},
|
},
|
||||||
StructAttribute{
|
StructAttribute{
|
||||||
name: 'sql'
|
name: 'sql'
|
||||||
has_arg: true
|
has_arg: true
|
||||||
kind: .plain
|
|
||||||
arg: 'serial'
|
arg: 'serial'
|
||||||
|
kind: .plain
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'name'
|
name: 'name'
|
||||||
typ: 18
|
typ: ast.string_type_idx
|
||||||
|
is_time: false
|
||||||
|
default_val: ''
|
||||||
|
is_arr: false
|
||||||
attrs: []
|
attrs: []
|
||||||
},
|
},
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'age'
|
name: 'age'
|
||||||
typ: 7
|
typ: ast.i64_type_idx
|
||||||
|
is_time: false
|
||||||
|
default_val: ''
|
||||||
|
is_arr: false
|
||||||
|
attrs: []
|
||||||
},
|
},
|
||||||
]) or { panic(err) }
|
]) or { panic(err) }
|
||||||
|
|
||||||
@ -45,15 +95,22 @@ fn test_pg_orm() {
|
|||||||
|
|
||||||
res := db.@select(orm.SelectConfig{
|
res := db.@select(orm.SelectConfig{
|
||||||
table: 'Test'
|
table: 'Test'
|
||||||
|
is_count: false
|
||||||
has_where: true
|
has_where: true
|
||||||
|
has_order: false
|
||||||
|
order: ''
|
||||||
|
order_type: .asc
|
||||||
|
has_limit: false
|
||||||
|
primary: 'id'
|
||||||
|
has_offset: false
|
||||||
fields: ['id', 'name', 'age']
|
fields: ['id', 'name', 'age']
|
||||||
types: [7, 18, 8]
|
types: [ast.int_type_idx, ast.string_type_idx, ast.i64_type_idx]
|
||||||
}, orm.QueryData{}, orm.QueryData{
|
}, orm.QueryData{}, orm.QueryData{
|
||||||
fields: ['name']
|
fields: ['name', 'age']
|
||||||
data: [orm.Primitive('Louis'), i64(101)]
|
data: [orm.Primitive('Louis'), orm.Primitive(101)]
|
||||||
types: [18]
|
types: []
|
||||||
|
kinds: [.eq, .eq]
|
||||||
is_and: [true]
|
is_and: [true]
|
||||||
kinds: [.eq]
|
|
||||||
}) or { panic(err) }
|
}) or { panic(err) }
|
||||||
|
|
||||||
id := res[0][0]
|
id := res[0][0]
|
||||||
@ -74,4 +131,97 @@ fn test_pg_orm() {
|
|||||||
if age is i64 {
|
if age is i64 {
|
||||||
assert age == 101
|
assert age == 101
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** test orm sql type
|
||||||
|
* - verify if all type create by attribute sql_type has created
|
||||||
|
*/
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table TestCustomSqlType
|
||||||
|
}
|
||||||
|
|
||||||
|
mut result_custom_sql := db.exec("
|
||||||
|
SELECT DATA_TYPE
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_NAME = 'TestCustomSqlType'
|
||||||
|
ORDER BY ORDINAL_POSITION
|
||||||
|
") or {
|
||||||
|
println(err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mut information_schema_data_types_results := []string{}
|
||||||
|
information_schema_custom_sql := ['integer', 'text', 'character varying',
|
||||||
|
'timestamp without time zone', 'uuid']
|
||||||
|
for data_type in result_custom_sql {
|
||||||
|
information_schema_data_types_results << data_type.vals[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
drop table TestCustomSqlType
|
||||||
|
}
|
||||||
|
|
||||||
|
assert information_schema_data_types_results == information_schema_custom_sql
|
||||||
|
|
||||||
|
/** test_orm_time_type
|
||||||
|
* - test time.Time v type with sql_type: 'TIMESTAMP'
|
||||||
|
* - test string v type with sql_type: 'TIMESTAMP'
|
||||||
|
* - test time.Time v type without
|
||||||
|
*/
|
||||||
|
today := time.parse('2022-07-16 15:13:27') or {
|
||||||
|
println(err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
model := TestTimeType{
|
||||||
|
username: 'hitalo'
|
||||||
|
created_at: today
|
||||||
|
updated_at: today.str()
|
||||||
|
deleted_at: today
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table TestTimeType
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
insert model into TestTimeType
|
||||||
|
}
|
||||||
|
|
||||||
|
results := sql db {
|
||||||
|
select from TestTimeType where username == 'hitalo'
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
drop table TestTimeType
|
||||||
|
}
|
||||||
|
|
||||||
|
assert results[0].username == model.username
|
||||||
|
assert results[0].created_at == model.created_at
|
||||||
|
assert results[0].updated_at == model.updated_at
|
||||||
|
assert results[0].deleted_at == model.deleted_at
|
||||||
|
|
||||||
|
/** test default attribute
|
||||||
|
*/
|
||||||
|
sql db {
|
||||||
|
create table TestDefaultAtribute
|
||||||
|
}
|
||||||
|
|
||||||
|
mut result_defaults := db.exec("
|
||||||
|
SELECT column_default
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_NAME = 'TestDefaultAtribute'
|
||||||
|
ORDER BY ORDINAL_POSITION
|
||||||
|
") or {
|
||||||
|
println(err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mut information_schema_defaults_results := []string{}
|
||||||
|
|
||||||
|
for defaults in result_defaults {
|
||||||
|
information_schema_defaults_results << defaults.vals[0]
|
||||||
|
}
|
||||||
|
sql db {
|
||||||
|
drop table TestDefaultAtribute
|
||||||
|
}
|
||||||
|
assert ['gen_random_uuid()', '', 'CURRENT_TIMESTAMP'] == information_schema_defaults_results
|
||||||
}
|
}
|
||||||
|
@ -51,17 +51,18 @@ pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.Qu
|
|||||||
// sql stmt
|
// sql stmt
|
||||||
|
|
||||||
pub fn (db DB) insert(table string, data orm.QueryData) ? {
|
pub fn (db DB) insert(table string, data orm.QueryData) ? {
|
||||||
query := orm.orm_stmt_gen(table, '`', .insert, true, '?', 1, data, orm.QueryData{})
|
query, converted_data := orm.orm_stmt_gen(table, '`', .insert, true, '?', 1, data,
|
||||||
sqlite_stmt_worker(db, query, data, orm.QueryData{})?
|
orm.QueryData{})
|
||||||
|
sqlite_stmt_worker(db, query, converted_data, orm.QueryData{})?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ? {
|
pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ? {
|
||||||
query := orm.orm_stmt_gen(table, '`', .update, true, '?', 1, data, where)
|
query, _ := orm.orm_stmt_gen(table, '`', .update, true, '?', 1, data, where)
|
||||||
sqlite_stmt_worker(db, query, data, where)?
|
sqlite_stmt_worker(db, query, data, where)?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (db DB) delete(table string, where orm.QueryData) ? {
|
pub fn (db DB) delete(table string, where orm.QueryData) ? {
|
||||||
query := orm.orm_stmt_gen(table, '`', .delete, true, '?', 1, orm.QueryData{}, where)
|
query, _ := orm.orm_stmt_gen(table, '`', .delete, true, '?', 1, orm.QueryData{}, where)
|
||||||
sqlite_stmt_worker(db, query, orm.QueryData{}, where)?
|
sqlite_stmt_worker(db, query, orm.QueryData{}, where)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,32 @@
|
|||||||
import orm
|
import orm
|
||||||
import sqlite
|
import sqlite
|
||||||
import v.ast
|
import v.ast
|
||||||
|
import time
|
||||||
|
|
||||||
|
struct TestCustomSqlType {
|
||||||
|
id int [primary; sql: serial]
|
||||||
|
custom string [sql_type: 'INTEGER']
|
||||||
|
custom1 string [sql_type: 'TEXT']
|
||||||
|
custom2 string [sql_type: 'REAL']
|
||||||
|
custom3 string [sql_type: 'NUMERIC']
|
||||||
|
custom4 string
|
||||||
|
custom5 int
|
||||||
|
custom6 time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestDefaultAtribute {
|
||||||
|
id string [primary; sql: serial]
|
||||||
|
name string
|
||||||
|
created_at string [default: 'CURRENT_TIME']
|
||||||
|
created_at1 string [default: 'CURRENT_DATE']
|
||||||
|
created_at2 string [default: 'CURRENT_TIMESTAMP']
|
||||||
|
}
|
||||||
|
|
||||||
fn test_sqlite_orm() {
|
fn test_sqlite_orm() {
|
||||||
sdb := sqlite.connect(':memory:') or { panic(err) }
|
mut db := sqlite.connect(':memory:') or { panic(err) }
|
||||||
db := orm.Connection(sdb)
|
defer {
|
||||||
|
db.close() or { panic(err) }
|
||||||
|
}
|
||||||
db.create('Test', [
|
db.create('Test', [
|
||||||
orm.TableField{
|
orm.TableField{
|
||||||
name: 'id'
|
name: 'id'
|
||||||
@ -68,4 +90,74 @@ fn test_sqlite_orm() {
|
|||||||
if age is i64 {
|
if age is i64 {
|
||||||
assert age == 100
|
assert age == 100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** test orm sql type
|
||||||
|
* - verify if all type create by attribute sql_type has created
|
||||||
|
*/
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table TestCustomSqlType
|
||||||
|
}
|
||||||
|
|
||||||
|
mut result_custom_sql, mut exec_custom_code := db.exec('
|
||||||
|
pragma table_info(TestCustomSqlType);
|
||||||
|
')
|
||||||
|
|
||||||
|
assert exec_custom_code == 101
|
||||||
|
mut table_info_types_results := []string{}
|
||||||
|
information_schema_custom_sql := ['INTEGER', 'INTEGER', 'TEXT', 'REAL', 'NUMERIC', 'TEXT',
|
||||||
|
'INTEGER', 'INTEGER']
|
||||||
|
|
||||||
|
for data_type in result_custom_sql {
|
||||||
|
table_info_types_results << data_type.vals[2]
|
||||||
|
}
|
||||||
|
assert table_info_types_results == information_schema_custom_sql
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
drop table TestCustomSqlType
|
||||||
|
}
|
||||||
|
|
||||||
|
/** test default attribute
|
||||||
|
*/
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
create table TestDefaultAtribute
|
||||||
|
}
|
||||||
|
|
||||||
|
mut result_default_sql, mut code := db.exec('
|
||||||
|
pragma table_info(TestDefaultAtribute);
|
||||||
|
')
|
||||||
|
|
||||||
|
assert code == 101
|
||||||
|
mut information_schema_data_types_results := []string{}
|
||||||
|
information_schema_default_sql := ['', '', 'CURRENT_TIME', 'CURRENT_DATE', 'CURRENT_TIMESTAMP']
|
||||||
|
|
||||||
|
for data_type in result_default_sql {
|
||||||
|
information_schema_data_types_results << data_type.vals[4]
|
||||||
|
}
|
||||||
|
assert information_schema_data_types_results == information_schema_default_sql
|
||||||
|
|
||||||
|
test_default_atribute := TestDefaultAtribute{
|
||||||
|
name: 'Hitalo'
|
||||||
|
}
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
insert test_default_atribute into TestDefaultAtribute
|
||||||
|
}
|
||||||
|
|
||||||
|
result_test_default_atribute := sql db {
|
||||||
|
select from TestDefaultAtribute limit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert result_test_default_atribute.name == 'Hitalo'
|
||||||
|
assert test_default_atribute.created_at.len == 0
|
||||||
|
assert test_default_atribute.created_at1.len == 0
|
||||||
|
assert test_default_atribute.created_at2.len == 0
|
||||||
|
assert result_test_default_atribute.created_at.len == 8 // HH:MM:SS
|
||||||
|
assert result_test_default_atribute.created_at1.len == 10 // YYYY-MM-DD
|
||||||
|
assert result_test_default_atribute.created_at2.len == 19 // YYYY-MM-DD HH:MM:SS
|
||||||
|
|
||||||
|
sql db {
|
||||||
|
drop table TestDefaultAtribute
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ fn test_sqlite() {
|
|||||||
fn test_can_access_sqlite_result_consts() {
|
fn test_can_access_sqlite_result_consts() {
|
||||||
assert sqlite.sqlite_ok == 0
|
assert sqlite.sqlite_ok == 0
|
||||||
assert sqlite.sqlite_error == 1
|
assert sqlite.sqlite_error == 1
|
||||||
|
// assert sqlite.misuse == 21
|
||||||
assert sqlite.sqlite_row == 100
|
assert sqlite.sqlite_row == 100
|
||||||
assert sqlite.sqlite_done == 101
|
assert sqlite.sqlite_done == 101
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,10 @@ fn (stmt Stmt) get_f64(idx int) f64 {
|
|||||||
|
|
||||||
fn (stmt Stmt) get_text(idx int) string {
|
fn (stmt Stmt) get_text(idx int) string {
|
||||||
b := &char(C.sqlite3_column_text(stmt.stmt, idx))
|
b := &char(C.sqlite3_column_text(stmt.stmt, idx))
|
||||||
|
|
||||||
|
if b == &char(0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
return unsafe { b.vstring() }
|
return unsafe { b.vstring() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user