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:
parent
041e90b2e2
commit
a13b8ff0c8
@ -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
|
||||||
|
@ -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()`
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
106
vlib/mysql/orm.v
106
vlib/mysql/orm.v
@ -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,9 +278,18 @@ 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 {
|
||||||
|
match field_types[i] {
|
||||||
|
.type_long {
|
||||||
timestamp := *(unsafe { &int(data) })
|
timestamp := *(unsafe { &int(data) })
|
||||||
primitive = time.unix(timestamp)
|
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
|
||||||
|
}
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
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
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -46,6 +49,9 @@ 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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user