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

393 lines
9.3 KiB
V

module mysql
import orm
import time
// @select is used internally by V's ORM for processing `SELECT ` queries
pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive {
query := orm.orm_select_gen(config, '`', false, '?', 0, where)
mut ret := [][]orm.Primitive{}
mut stmt := db.init_stmt(query)
stmt.prepare()!
mysql_stmt_binder(mut stmt, where)!
mysql_stmt_binder(mut stmt, data)!
if data.data.len > 0 || where.data.len > 0 {
stmt.bind_params()!
}
mut status := stmt.execute()!
num_fields := stmt.get_field_count()
metadata := stmt.gen_metadata()
fields := stmt.fetch_fields(metadata)
mut dataptr := []&u8{}
for i in 0 .. num_fields {
field := unsafe { fields[i] }
match unsafe { FieldType(field.@type) } {
.type_tiny {
dataptr << unsafe { malloc(1) }
}
.type_short {
dataptr << unsafe { malloc(2) }
}
.type_long {
dataptr << unsafe { malloc(4) }
}
.type_longlong {
dataptr << unsafe { malloc(8) }
}
.type_float {
dataptr << unsafe { malloc(4) }
}
.type_double {
dataptr << unsafe { malloc(8) }
}
.type_time, .type_date, .type_datetime, .type_time2, .type_datetime2 {
dataptr << unsafe { malloc(sizeof(C.MYSQL_TIME)) }
}
.type_string, .type_var_string, .type_blob, .type_tiny_blob, .type_medium_blob,
.type_long_blob {
// Memory will be allocated later dynamically depending on the length of the value.
dataptr << &u8(0)
}
else {
return error('\'${unsafe { FieldType(field.@type) }}\' is not yet implemented. Please create a new issue at https://github.com/vlang/v/issues/new')
}
}
}
lengths := []u32{len: int(num_fields), init: 0}
stmt.bind_res(fields, dataptr, lengths, num_fields)
mut types := config.types.clone()
mut field_types := []FieldType{}
if config.is_count {
types = [orm.type_idx['u64']]
}
// Map stores column indexes and their binds in order to extract values
// for these columns separately, with individual memory allocation for each value.
mut string_binds_map := map[int]C.MYSQL_BIND{}
for i, mut mysql_bind in stmt.res {
field := unsafe { fields[i] }
field_type := unsafe { FieldType(field.@type) }
field_types << field_type
match types[i] {
orm.type_string {
string_binds_map[i] = mysql_bind
}
orm.time {
match field_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 ${field.@type}')
}
}
}
else {}
}
}
stmt.bind_result_buffer()!
stmt.store_result()!
for {
status = stmt.fetch_stmt()!
if status == 1 || status == 100 {
break
}
for index, mut bind in string_binds_map {
string_length := lengths[index] + 1
dataptr[index] = unsafe { malloc(string_length) }
bind.buffer = dataptr[index]
bind.buffer_length = string_length
bind.length = unsafe { nil }
stmt.fetch_column(bind, index)!
}
data_list := buffer_to_primitive(dataptr, types, field_types)!
ret << data_list
}
stmt.close()!
return ret
}
// sql stmt
// insert is used internally by V's ORM for processing `INSERT ` queries
pub fn (db Connection) insert(table string, data orm.QueryData) ! {
mut converted_primitive_array := db.factory_orm_primitive_converted_from_sql(table,
data)!
converted_primitive_data := orm.QueryData{
fields: data.fields
data: converted_primitive_array
types: []
kinds: []
is_and: []
}
query, converted_data := orm.orm_stmt_gen(.default, table, '`', .insert, false, '?',
1, converted_primitive_data, orm.QueryData{})
mysql_stmt_worker(db, query, converted_data, orm.QueryData{})!
}
// update is used internally by V's ORM for processing `UPDATE ` queries
pub fn (db Connection) update(table string, data orm.QueryData, where orm.QueryData) ! {
query, _ := orm.orm_stmt_gen(.default, table, '`', .update, false, '?', 1, data, where)
mysql_stmt_worker(db, query, data, where)!
}
// delete is used internally by V's ORM for processing `DELETE ` queries
pub fn (db Connection) delete(table string, where orm.QueryData) ! {
query, _ := orm.orm_stmt_gen(.default, table, '`', .delete, false, '?', 1, orm.QueryData{},
where)
mysql_stmt_worker(db, query, orm.QueryData{}, where)!
}
// last_id is used internally by V's ORM for post-processing `INSERT ` queries
pub fn (db Connection) last_id() int {
query := 'SELECT last_insert_id();'
id := db.query(query) or { return 0 }
return id.rows()[0].vals[0].int()
}
// DDL (table creation/destroying etc)
// create is used internally by V's ORM for processing table creation queries (DDL)
pub fn (db Connection) create(table string, fields []orm.TableField) ! {
query := orm.orm_table_gen(table, '`', true, 0, fields, mysql_type_from_v, false) or {
return err
}
mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
}
// drop is used internally by V's ORM for processing table destroying queries (DDL)
pub fn (db Connection) drop(table string) ! {
query := 'DROP TABLE `${table}`;'
mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
}
fn mysql_stmt_worker(db Connection, query string, data orm.QueryData, where orm.QueryData) ! {
mut stmt := db.init_stmt(query)
stmt.prepare()!
mysql_stmt_binder(mut stmt, data)!
mysql_stmt_binder(mut stmt, where)!
if data.data.len > 0 || where.data.len > 0 {
stmt.bind_params()!
}
stmt.execute()!
stmt.close()!
}
fn mysql_stmt_binder(mut stmt Stmt, d orm.QueryData) ! {
for data in d.data {
stmt_binder_match(mut stmt, data)
}
}
fn stmt_binder_match(mut stmt Stmt, data orm.Primitive) {
match data {
bool {
stmt.bind_bool(&data)
}
i8 {
stmt.bind_i8(&data)
}
i16 {
stmt.bind_i16(&data)
}
int {
stmt.bind_int(&data)
}
i64 {
stmt.bind_i64(&data)
}
u8 {
stmt.bind_u8(&data)
}
u16 {
stmt.bind_u16(&data)
}
u32 {
stmt.bind_u32(&data)
}
u64 {
stmt.bind_u64(&data)
}
f32 {
stmt.bind_f32(unsafe { &f32(&data) })
}
f64 {
stmt.bind_f64(unsafe { &f64(&data) })
}
string {
stmt.bind_text(data)
}
time.Time {
unix := int(data.unix)
stmt_binder_match(mut stmt, unix)
}
orm.InfixType {
stmt_binder_match(mut stmt, data.right)
}
}
}
fn buffer_to_primitive(data_list []&u8, types []int, field_types []FieldType) ![]orm.Primitive {
mut res := []orm.Primitive{}
for i, data in data_list {
mut primitive := orm.Primitive(0)
match types[i] {
orm.type_idx['i8'] {
primitive = *(unsafe { &i8(data) })
}
orm.type_idx['i16'] {
primitive = *(unsafe { &i16(data) })
}
orm.type_idx['int'], orm.serial {
primitive = *(unsafe { &int(data) })
}
orm.type_idx['i64'] {
primitive = *(unsafe { &i64(data) })
}
orm.type_idx['u8'] {
primitive = *(unsafe { &u8(data) })
}
orm.type_idx['u16'] {
primitive = *(unsafe { &u16(data) })
}
orm.type_idx['u32'] {
primitive = *(unsafe { &u32(data) })
}
orm.type_idx['u64'] {
primitive = *(unsafe { &u64(data) })
}
orm.type_idx['f32'] {
primitive = *(unsafe { &f32(data) })
}
orm.type_idx['f64'] {
primitive = *(unsafe { &f64(data) })
}
orm.type_idx['bool'] {
primitive = *(unsafe { &bool(data) })
}
orm.type_string {
primitive = unsafe { cstring_to_vstring(&char(data)) }
}
orm.time {
match field_types[i] {
.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 {
return error('Unknown type ${types[i]}')
}
}
res << primitive
}
return res
}
fn mysql_type_from_v(typ int) !string {
str := match typ {
orm.type_idx['i8'], orm.type_idx['u8'] {
'TINYINT'
}
orm.type_idx['i16'], orm.type_idx['u16'] {
'SMALLINT'
}
orm.type_idx['int'], orm.type_idx['u32'], orm.time {
'INT'
}
orm.type_idx['i64'], orm.type_idx['u64'] {
'BIGINT'
}
orm.type_idx['f32'] {
'FLOAT'
}
orm.type_idx['f64'] {
'DOUBLE'
}
orm.type_string {
'TEXT'
}
orm.serial {
'SERIAL'
}
orm.type_idx['bool'] {
'BOOLEAN'
}
else {
''
}
}
if str == '' {
return error('Unknown type ${typ}')
}
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
}