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

orm: redesign orm (re-write it in V) (#10353)

This commit is contained in:
Louis Schmieder
2021-07-23 11:33:55 +02:00
committed by GitHub
parent ad41cd5c6f
commit 26db3b0995
23 changed files with 2350 additions and 1693 deletions

View File

@ -72,7 +72,9 @@ fn C.mysql_fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD
fn C.mysql_free_result(res &C.MYSQL_RES)
fn C.mysql_real_escape_string_quote(mysql &C.MYSQL, to &byte, from &byte, len u64, quote byte) u64
fn C.mysql_real_escape_string(mysql &C.MYSQL, to &byte, from &byte, len u64) u64
// fn C.mysql_real_escape_string_quote(mysql &C.MYSQL, to &byte, from &byte, len u64, quote byte) u64 (Don't exist in mariadb)
fn C.mysql_close(sock &C.MYSQL)

View File

@ -1,4 +1,11 @@
module mysql
#pkgconfig mysqlclient
#include <mysql.h> # Please install the mysqlclient development headers
// Need to check if mysqlclient is not there and use mariadb as alternative because newer system doesn't support mysql 8.0 as default
$if $pkgconfig('mysqlclient') {
#pkgconfig mysqlclient
} $else {
#pkgconfig mariadb
}
#include <mysql/mysql.h> # Please install the mysqlclient development headers

View File

@ -69,3 +69,10 @@ pub fn (f FieldType) str() string {
.type_geometry { 'geometry' }
}
}
pub fn (f FieldType) get_len() u32 {
return match f {
.type_blob { 262140 }
else { 0 }
}
}

View File

@ -19,6 +19,11 @@ pub enum ConnectionFlag {
client_remember_options = C.CLIENT_REMEMBER_OPTIONS
}
struct SQLError {
msg string
code int
}
// TODO: Documentation
pub struct Connection {
mut:
@ -46,7 +51,7 @@ pub fn (mut conn Connection) connect() ?bool {
// query - make an SQL query and receive the results.
// `query()` cannot be used for statements that contain binary data;
// Use `real_query()` instead.
pub fn (mut conn Connection) query(q string) ?Result {
pub fn (conn Connection) query(q string) ?Result {
if C.mysql_query(conn.conn, q.str) != 0 {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
@ -127,7 +132,7 @@ pub fn (conn &Connection) tables(wildcard string) ?[]string {
pub fn (conn &Connection) escape_string(s string) string {
unsafe {
to := malloc_noscan(2 * s.len + 1)
C.mysql_real_escape_string_quote(conn.conn, to, s.str, s.len, `\'`)
C.mysql_real_escape_string(conn.conn, to, s.str, s.len)
return to.vstring()
}
}

View File

@ -0,0 +1,77 @@
import orm
import mysql
fn test_mysql_orm() {
mut mdb := mysql.Connection{
host: 'localhost'
port: 3306
username: 'root'
password: ''
dbname: 'mysql'
}
mdb.connect() or { panic(err) }
db := orm.Connection(mdb)
db.create('Test', [
orm.TableField{
name: 'id'
typ: 7
attrs: [
StructAttribute{
name: 'primary'
},
StructAttribute{
name: 'sql'
has_arg: true
kind: .plain
arg: 'serial'
},
]
},
orm.TableField{
name: 'name'
typ: 18
attrs: []
},
orm.TableField{
name: 'age'
typ: 7
},
]) or { panic(err) }
db.insert('Test', orm.QueryData{
fields: ['name', 'age']
data: [orm.string_to_primitive('Louis'), orm.int_to_primitive(101)]
}) or { panic(err) }
res := db.@select(orm.SelectConfig{
table: 'Test'
has_where: true
fields: ['id', 'name', 'age']
types: [7, 18, 8]
}, orm.QueryData{}, orm.QueryData{
fields: ['name', 'age']
data: [orm.Primitive('Louis'), i64(101)]
types: [18, 8]
is_and: [true, true]
kinds: [.eq, .eq]
}) or { panic(err) }
id := res[0][0]
name := res[0][1]
age := res[0][2]
assert id is int
if id is int {
assert id == 1
}
assert name is string
if name is string {
assert name == 'Louis'
}
assert age is i64
if age is i64 {
assert age == 101
}
}

296
vlib/mysql/orm.v Normal file
View File

@ -0,0 +1,296 @@
module mysql
import orm
import time
type Prims = byte | f32 | f64 | i16 | i64 | i8 | int | string | u16 | u32 | u64
// sql expr
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 := []Prims{}
for i in 0 .. num_fields {
f := unsafe { fields[i] }
match FieldType(f.@type) {
.type_tiny {
dataptr << byte(0)
}
.type_short {
dataptr << u16(0)
}
.type_long {
dataptr << u32(0)
}
.type_longlong {
dataptr << u64(0)
}
.type_float {
dataptr << f32(0)
}
.type_double {
dataptr << f64(0)
}
.type_string {
dataptr << ''
}
else {
dataptr << byte(0)
}
}
}
mut vptr := []&char{}
for d in dataptr {
vptr << d.get_data_ptr()
}
unsafe { dataptr.free() }
lens := []u32{len: int(num_fields), init: 0}
stmt.bind_res(fields, vptr, lens, num_fields)
stmt.bind_result_buffer() ?
stmt.store_result() ?
mut row := 0
for {
status = stmt.fetch_stmt() ?
if status == 1 || status == 100 {
break
}
row++
data_list := buffer_to_primitive(vptr, config.types) ?
ret << data_list
}
stmt.close() ?
return ret
}
// sql stmt
pub fn (db Connection) insert(table string, data orm.QueryData) ? {
query := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, data, orm.QueryData{})
mysql_stmt_worker(db, query, data, 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)
mysql_stmt_worker(db, query, data, where) ?
}
pub fn (db Connection) delete(table string, where orm.QueryData) ? {
query := orm.orm_stmt_gen(table, '`', .delete, false, '?', 1, orm.QueryData{}, where)
mysql_stmt_worker(db, query, orm.QueryData{}, where) ?
}
pub fn (db Connection) last_id() orm.Primitive {
query := 'SELECT last_insert_rowid();'
id := db.query(query) or {
Result{
result: 0
}
}
return orm.Primitive(id.rows()[0].vals[0].int())
}
// table
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 {
return err
}
mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ?
}
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)
}
byte {
stmt.bind_byte(&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 {
stmt.bind_int(&int(data.unix))
}
orm.InfixType {
stmt_binder_match(mut stmt, data.right)
}
}
}
fn buffer_to_primitive(data_list []&char, types []int) ?[]orm.Primitive {
mut res := []orm.Primitive{}
for i, data in data_list {
mut primitive := orm.Primitive(0)
match types[i] {
5 {
primitive = *(&i8(data))
}
6 {
primitive = *(&i16(data))
}
7, -1 {
primitive = *(&int(data))
}
8 {
primitive = *(&i64(data))
}
9 {
primitive = *(&byte(data))
}
10 {
primitive = *(&u16(data))
}
11 {
primitive = *(&u32(data))
}
12 {
primitive = *(&u64(data))
}
13 {
primitive = *(&f32(data))
}
14 {
primitive = *(&f64(data))
}
15 {
primitive = *(&bool(data))
}
orm.string {
primitive = unsafe { cstring_to_vstring(&char(data)) }
}
orm.time {
timestamp := *(&int(data))
primitive = time.unix(timestamp)
}
else {
return error('Unknown type ${types[i]}')
}
}
res << primitive
}
return res
}
fn mysql_type_from_v(typ int) ?string {
str := match typ {
5, 9, 16 {
'TINYINT'
}
6, 10 {
'SMALLINT'
}
7, 11 {
'INT'
}
8, 12 {
'BIGINT'
}
13 {
'FLOAT'
}
14 {
'DOUBLE'
}
orm.string {
'TEXT'
}
-1 {
'SERIAL'
}
else {
''
}
}
if str == '' {
return error('Unknown type $typ')
}
return str
}
fn (p Prims) get_data_ptr() &char {
return match p {
string {
p.str
}
else {
&char(&p)
}
}
}

233
vlib/mysql/stmt.c.v Normal file
View File

@ -0,0 +1,233 @@
module mysql
[typedef]
struct C.MYSQL_STMT {
mysql &C.MYSQL
stmt_id u32
}
[typedef]
struct C.MYSQL_BIND {
mut:
buffer_type int
buffer voidptr
buffer_length u32
length &u32
}
const (
mysql_type_decimal = C.MYSQL_TYPE_DECIMAL
mysql_type_tiny = C.MYSQL_TYPE_TINY
mysql_type_short = C.MYSQL_TYPE_SHORT
mysql_type_long = C.MYSQL_TYPE_LONG
mysql_type_float = C.MYSQL_TYPE_FLOAT
mysql_type_double = C.MYSQL_TYPE_DOUBLE
mysql_type_null = C.MYSQL_TYPE_NULL
mysql_type_timestamp = C.MYSQL_TYPE_TIMESTAMP
mysql_type_longlong = C.MYSQL_TYPE_LONGLONG
mysql_type_int24 = C.MYSQL_TYPE_INT24
mysql_type_date = C.MYSQL_TYPE_DATE
mysql_type_time = C.MYSQL_TYPE_TIME
mysql_type_datetime = C.MYSQL_TYPE_DATETIME
mysql_type_year = C.MYSQL_TYPE_YEAR
mysql_type_varchar = C.MYSQL_TYPE_VARCHAR
mysql_type_bit = C.MYSQL_TYPE_BIT
mysql_type_timestamp22 = C.MYSQL_TYPE_TIMESTAMP
mysql_type_json = C.MYSQL_TYPE_JSON
mysql_type_newdecimal = C.MYSQL_TYPE_NEWDECIMAL
mysql_type_enum = C.MYSQL_TYPE_ENUM
mysql_type_set = C.MYSQL_TYPE_SET
mysql_type_tiny_blob = C.MYSQL_TYPE_TINY_BLOB
mysql_type_medium_blob = C.MYSQL_TYPE_MEDIUM_BLOB
mysql_type_long_blob = C.MYSQL_TYPE_LONG_BLOB
mysql_type_blob = C.MYSQL_TYPE_BLOB
mysql_type_var_string = C.MYSQL_TYPE_VAR_STRING
mysql_type_string = C.MYSQL_TYPE_STRING
mysql_type_geometry = C.MYSQL_TYPE_GEOMETRY
mysql_no_data = C.MYSQL_NO_DATA
)
fn C.mysql_stmt_init(&C.MYSQL) &C.MYSQL_STMT
fn C.mysql_stmt_prepare(&C.MYSQL_STMT, &char, u32) int
fn C.mysql_stmt_bind_param(&C.MYSQL_STMT, &C.MYSQL_BIND) bool
fn C.mysql_stmt_execute(&C.MYSQL_STMT) int
fn C.mysql_stmt_close(&C.MYSQL_STMT) bool
fn C.mysql_stmt_free_result(&C.MYSQL_STMT) bool
fn C.mysql_stmt_error(&C.MYSQL_STMT) &char
fn C.mysql_stmt_result_metadata(&C.MYSQL_STMT) &C.MYSQL_RES
fn C.mysql_stmt_field_count(&C.MYSQL_STMT) u16
fn C.mysql_stmt_bind_result(&C.MYSQL_STMT, &C.MYSQL_BIND) bool
fn C.mysql_stmt_fetch(&C.MYSQL_STMT) int
fn C.mysql_stmt_next_result(&C.MYSQL_STMT) int
fn C.mysql_stmt_store_result(&C.MYSQL_STMT) int
struct Stmt {
stmt &C.MYSQL_STMT = &C.MYSQL_STMT(0)
query string
mut:
binds []C.MYSQL_BIND
res []C.MYSQL_BIND
}
pub fn (db Connection) init_stmt(query string) Stmt {
return Stmt{
stmt: C.mysql_stmt_init(db.conn)
query: query
binds: []C.MYSQL_BIND{}
}
}
pub fn (stmt Stmt) prepare() ? {
res := C.mysql_stmt_prepare(stmt.stmt, stmt.query.str, stmt.query.len)
if res != 0 && stmt.get_error_msg() != '' {
return stmt.error(res)
}
}
pub fn (stmt Stmt) bind_params() ? {
res := C.mysql_stmt_bind_param(stmt.stmt, &C.MYSQL_BIND(stmt.binds.data))
if res && stmt.get_error_msg() != '' {
return stmt.error(1)
}
}
pub fn (stmt Stmt) execute() ?int {
res := C.mysql_stmt_execute(stmt.stmt)
if res != 0 && stmt.get_error_msg() != '' {
return stmt.error(res)
}
return res
}
pub fn (stmt Stmt) next() ?int {
res := C.mysql_stmt_next_result(stmt.stmt)
if res > 0 && stmt.get_error_msg() != '' {
return stmt.error(res)
}
return res
}
pub fn (stmt Stmt) gen_metadata() &C.MYSQL_RES {
return C.mysql_stmt_result_metadata(stmt.stmt)
}
pub fn (stmt Stmt) fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD {
return C.mysql_fetch_fields(res)
}
pub fn (stmt Stmt) fetch_stmt() ?int {
res := C.mysql_stmt_fetch(stmt.stmt)
if res !in [0, 100] && stmt.get_error_msg() != '' {
return stmt.error(res)
}
return res
}
pub fn (stmt Stmt) close() ? {
if !C.mysql_stmt_close(stmt.stmt) && stmt.get_error_msg() != '' {
return stmt.error(1)
}
if !C.mysql_stmt_free_result(stmt.stmt) && stmt.get_error_msg() != '' {
return stmt.error(1)
}
}
fn (stmt Stmt) get_error_msg() string {
return unsafe { cstring_to_vstring(&char(C.mysql_stmt_error(stmt.stmt))) }
}
pub fn (stmt Stmt) error(code int) IError {
msg := stmt.get_error_msg()
return IError(&SQLError{
msg: '$msg ($code) ($stmt.query)'
code: code
})
}
fn (stmt Stmt) get_field_count() u16 {
return C.mysql_stmt_field_count(stmt.stmt)
}
pub fn (mut stmt Stmt) bind_bool(b &bool) {
stmt.bind(mysql.mysql_type_tiny, b, 0)
}
pub fn (mut stmt Stmt) bind_byte(b &byte) {
stmt.bind(mysql.mysql_type_tiny, b, 0)
}
pub fn (mut stmt Stmt) bind_i8(b &i8) {
stmt.bind(mysql.mysql_type_tiny, b, 0)
}
pub fn (mut stmt Stmt) bind_i16(b &i16) {
stmt.bind(mysql.mysql_type_short, b, 0)
}
pub fn (mut stmt Stmt) bind_u16(b &u16) {
stmt.bind(mysql.mysql_type_short, b, 0)
}
pub fn (mut stmt Stmt) bind_int(b &int) {
stmt.bind(mysql.mysql_type_long, b, 0)
}
pub fn (mut stmt Stmt) bind_u32(b &u32) {
stmt.bind(mysql.mysql_type_long, b, 0)
}
pub fn (mut stmt Stmt) bind_i64(b &i64) {
stmt.bind(mysql.mysql_type_longlong, b, 0)
}
pub fn (mut stmt Stmt) bind_u64(b &u64) {
stmt.bind(mysql.mysql_type_longlong, b, 0)
}
pub fn (mut stmt Stmt) bind_f32(b &f32) {
stmt.bind(mysql.mysql_type_float, b, 0)
}
pub fn (mut stmt Stmt) bind_f64(b &f64) {
stmt.bind(mysql.mysql_type_double, b, 0)
}
pub fn (mut stmt Stmt) bind_text(b string) {
stmt.bind(mysql.mysql_type_string, b.str, u32(b.len))
}
pub fn (mut stmt Stmt) bind(typ int, buffer voidptr, buf_len u32) {
stmt.binds << C.MYSQL_BIND{
buffer_type: typ
buffer: buffer
buffer_length: buf_len
length: 0
}
}
pub fn (mut stmt Stmt) bind_res(fields &C.MYSQL_FIELD, dataptr []&char, lens []u32, num_fields int) {
for i in 0 .. num_fields {
len := FieldType(unsafe { fields[i].@type }).get_len()
stmt.res << C.MYSQL_BIND{
buffer_type: unsafe { fields[i].@type }
buffer: dataptr[i]
length: &lens[i]
buffer_length: &len
}
}
}
pub fn (mut stmt Stmt) bind_result_buffer() ? {
res := C.mysql_stmt_bind_result(stmt.stmt, &C.MYSQL_BIND(stmt.res.data))
if res && stmt.get_error_msg() != '' {
return stmt.error(1)
}
}
pub fn (mut stmt Stmt) store_result() ? {
res := C.mysql_stmt_store_result(stmt.stmt)
if res != 0 && stmt.get_error_msg() != '' {
return stmt.error(res)
}
}