1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

mysql: fix for adapting mysql types to v structs (#15100)

This commit is contained in:
Hitalo de Jesus do Rosário Souza 2022-07-19 12:29:09 -03:00 committed by GitHub
parent 041e90b2e2
commit a13b8ff0c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 185 additions and 15 deletions

View File

@ -38,6 +38,8 @@ fn C.mysql_real_connect(mysql &C.MYSQL, host &char, user &char, passwd &char, db
fn C.mysql_query(mysql &C.MYSQL, q &u8) int fn C.mysql_query(mysql &C.MYSQL, q &u8) int
fn C.mysql_use_result(mysql &C.MYSQL)
fn C.mysql_real_query(mysql &C.MYSQL, q &u8, len u32) int fn C.mysql_real_query(mysql &C.MYSQL, q &u8, len u32) int
fn C.mysql_select_db(mysql &C.MYSQL, db &u8) int fn C.mysql_select_db(mysql &C.MYSQL, db &u8) int

View File

@ -58,6 +58,18 @@ pub fn (conn Connection) query(q string) ?Result {
return Result{res} return Result{res}
} }
// use_result - reads the result of a query
// used after invoking mysql_real_query() or mysql_query(),
// for every statement that successfully produces a result set
// (SELECT, SHOW, DESCRIBE, EXPLAIN, CHECK TABLE, and so forth).
// This reads the result of a query directly from the server
// without storing it in a temporary table or local buffer,
// mysql_use_result is faster and uses much less memory than C.mysql_store_result().
// You must mysql_free_result() after you are done with the result set.
pub fn (conn Connection) use_result() {
C.mysql_use_result(conn.conn)
}
// real_query - make an SQL query and receive the results. // real_query - make an SQL query and receive the results.
// `real_query()` can be used for statements containing binary data. // `real_query()` can be used for statements containing binary data.
// (Binary data may contain the `\0` character, which `query()` // (Binary data may contain the `\0` character, which `query()`

View File

@ -1,5 +1,6 @@
import orm import orm
import mysql import mysql
import time
struct TestCustomSqlType { struct TestCustomSqlType {
id int [primary; sql: serial] id int [primary; sql: serial]
@ -19,6 +20,15 @@ struct TestCustomWrongSqlType {
custom3 string [sql_type: 'xml'] custom3 string [sql_type: 'xml']
} }
struct TestTimeType {
mut:
id int [primary; sql: serial]
username string
created_at time.Time [sql_type: 'DATETIME']
updated_at string [sql_type: 'DATETIME']
deleted_at time.Time
}
fn test_mysql_orm() { fn test_mysql_orm() {
mut mdb := mysql.Connection{ mut mdb := mysql.Connection{
host: 'localhost' host: 'localhost'
@ -78,6 +88,8 @@ 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
@ -153,9 +165,57 @@ fn test_orm() {
}, },
] ]
assert result_custom_sql.maps() == information_schema_custom_sql
sql db { sql db {
drop table TestCustomSqlType drop table TestCustomSqlType
} }
db.close()
assert result_custom_sql.maps() == information_schema_custom_sql
}
fn test_orm_time_type() ? {
mut db := mysql.Connection{
host: 'localhost'
port: 3306
username: 'root'
password: ''
dbname: 'mysql'
}
db.connect() or {
println(err)
panic(err)
}
today := time.parse('2022-07-16 15:13:27')?
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
}
db.close()
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
} }

View File

@ -24,7 +24,6 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher
num_fields := stmt.get_field_count() num_fields := stmt.get_field_count()
metadata := stmt.gen_metadata() metadata := stmt.gen_metadata()
fields := stmt.fetch_fields(metadata) fields := stmt.fetch_fields(metadata)
mut dataptr := []&u8{} mut dataptr := []&u8{}
for i in 0 .. num_fields { for i in 0 .. num_fields {
@ -48,26 +47,60 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher
.type_double { .type_double {
dataptr << unsafe { malloc(8) } dataptr << unsafe { malloc(8) }
} }
.type_time, .type_date, .type_datetime, .type_time2, .type_datetime2 {
dataptr << unsafe { malloc(sizeof(C.MYSQL_TIME)) }
}
.type_string, .type_blob { .type_string, .type_blob {
dataptr << unsafe { malloc(512) } dataptr << unsafe { malloc(512) }
} }
.type_var_string {
dataptr << unsafe { malloc(2) }
}
else { else {
dataptr << &u8(0) return error('\'${FieldType(f.@type)}\' is not yet implemented. Please create a new issue at https://github.com/vlang/v/issues/new')
} }
} }
} }
lens := []u32{len: int(num_fields), init: 0} lens := []u32{len: int(num_fields), init: 0}
stmt.bind_res(fields, dataptr, lens, num_fields) stmt.bind_res(fields, dataptr, lens, num_fields)
stmt.bind_result_buffer()?
stmt.store_result()?
mut row := 0 mut row := 0
mut types := config.types mut types := config.types
mut field_types := []FieldType{}
if config.is_count { if config.is_count {
types = [orm.type_idx['u64']] types = [orm.type_idx['u64']]
} }
for i, mut mysql_bind in stmt.res {
f := unsafe { fields[i] }
field_types << FieldType(f.@type)
match types[i] {
orm.string {
mysql_bind.buffer_type = C.MYSQL_TYPE_BLOB
mysql_bind.buffer_length = FieldType.type_blob.get_len()
}
orm.time {
match FieldType(f.@type) {
.type_long {
mysql_bind.buffer_type = C.MYSQL_TYPE_LONG
}
.type_time, .type_date, .type_datetime {
mysql_bind.buffer_type = C.MYSQL_TYPE_BLOB
mysql_bind.buffer_length = FieldType.type_blob.get_len()
}
.type_string, .type_blob {}
else {
return error('Unknown type ${f.@type}')
}
}
}
else {}
}
}
stmt.bind_result_buffer()?
stmt.store_result()?
for { for {
status = stmt.fetch_stmt()? status = stmt.fetch_stmt()?
@ -76,7 +109,7 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher
} }
row++ row++
data_list := buffer_to_primitive(dataptr, types)? data_list := buffer_to_primitive(dataptr, types, field_types)?
ret << data_list ret << data_list
} }
@ -88,8 +121,19 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher
// sql stmt // sql stmt
pub fn (db Connection) insert(table string, data orm.QueryData) ? { pub fn (db Connection) insert(table string, data orm.QueryData) ? {
query := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, data, orm.QueryData{}) mut converted_primitive_array := db.factory_orm_primitive_converted_from_sql(table,
mysql_stmt_worker(db, query, data, orm.QueryData{})? data)?
converted_data := orm.QueryData{
fields: data.fields
data: converted_primitive_array
types: []
kinds: []
is_and: []
}
query := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, 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) ? {
@ -191,7 +235,7 @@ fn stmt_binder_match(mut stmt Stmt, data orm.Primitive) {
} }
} }
fn buffer_to_primitive(data_list []&u8, types []int) ?[]orm.Primitive { fn buffer_to_primitive(data_list []&u8, types []int, field_types []FieldType) ?[]orm.Primitive {
mut res := []orm.Primitive{} mut res := []orm.Primitive{}
for i, data in data_list { for i, data in data_list {
@ -234,8 +278,17 @@ fn buffer_to_primitive(data_list []&u8, types []int) ?[]orm.Primitive {
primitive = unsafe { cstring_to_vstring(&char(data)) } primitive = unsafe { cstring_to_vstring(&char(data)) }
} }
orm.time { orm.time {
timestamp := *(unsafe { &int(data) }) match field_types[i] {
primitive = time.unix(timestamp) .type_long {
timestamp := *(unsafe { &int(data) })
primitive = time.unix(timestamp)
}
.type_datetime {
string_time := unsafe { cstring_to_vstring(&char(data)) }
primitive = time.parse(string_time)?
}
else {}
}
} }
else { else {
return error('Unknown type ${types[i]}') return error('Unknown type ${types[i]}')
@ -285,3 +338,40 @@ fn mysql_type_from_v(typ int) ?string {
} }
return str return str
} }
fn (db Connection) factory_orm_primitive_converted_from_sql(table string, data orm.QueryData) ?[]orm.Primitive {
mut map_val := db.get_table_data_type_map(table)?
// adapt v type to sql time
mut converted_data := []orm.Primitive{}
for i, field in data.fields {
match data.data[i].type_name() {
'time.Time' {
if map_val[field] == 'datetime' {
converted_data << orm.Primitive((data.data[i] as time.Time).str())
} else {
converted_data << data.data[i]
}
}
else {
converted_data << data.data[i]
}
}
}
return converted_data
}
fn (db Connection) get_table_data_type_map(table string) ?map[string]string {
data_type_querys := "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table'"
mut map_val := map[string]string{}
results := db.query(data_type_querys)?
db.use_result()
for row in results.rows() {
map_val[row.vals[0]] = row.vals[1]
}
unsafe { results.free() }
return map_val
}

View File

@ -20,8 +20,11 @@
```v ignore ```v ignore
struct Foo { struct Foo {
id int [primary; sql: serial] id int [primary; sql: serial]
name string [nonull] name string [nonull]
created_at time.Time [sql_type: 'DATETIME']
updated_at string [sql_type: 'DATETIME']
deleted_at time.Time
} }
``` ```
@ -45,7 +48,10 @@ sql db {
```v ignore ```v ignore
var := Foo{ var := Foo{
name: 'abc' name: 'abc'
created_at: time.now()
updated_at: time.now().str()
deleted_at: time.now()
} }
sql db { sql db {