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

mysql: refactor, comments, simplify (#18258)

This commit is contained in:
Mark aka walkingdevel 2023-05-25 00:50:15 +00:00 committed by GitHub
parent 010a5c26a0
commit 351b2e0e42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 259 additions and 169 deletions

View File

@ -14,9 +14,9 @@ struct C.MYSQL_FIELD {
org_name &u8 // Original column name, if an alias
table &u8 // Table of column if column was a field
org_table &u8 // Org table name, if table was an alias
db &u8 // Database for table
db &u8 // Name of the database that the field comes from
catalog &u8 // Catalog for table
def &u8 // Default value (set by mysql_list_fields)
def &u8 // Default value (set by `mysql_list_fields`)
length int // Width of column (create length)
max_length int // Max width for selected set
name_length u32
@ -26,78 +26,134 @@ struct C.MYSQL_FIELD {
db_length u32
catalog_length u32
def_length u32
flags u32 // Div flags
flags u32 // Bit-flags that describe the field
decimals u32 // Number of decimals in field
charsetnr u32 // Character set
@type int // Type of field. See mysql_com.h for types
@type int // Type of field. See enums.v for types
}
// C.mysql_init allocates or initializes a MYSQL object suitable for `mysql_real_connect()`.
fn C.mysql_init(mysql &C.MYSQL) &C.MYSQL
// C.mysql_real_connect attempts to establish a connection to a MySQL server running on `host`.
fn C.mysql_real_connect(mysql &C.MYSQL, host &char, user &char, passwd &char, db &char, port u32, unix_socket &char, client_flag ConnectionFlag) &C.MYSQL
// C.mysql_query executes the SQL statement pointed to by the null-terminated string `stmt_str`.
fn C.mysql_query(mysql &C.MYSQL, q &u8) int
// C.mysql_use_result initiates a result set retrieval but does not actually read
// the result set into the client like `mysql_store_result()` does.
fn C.mysql_use_result(mysql &C.MYSQL)
// C.mysql_real_query executes the SQL statement pointed to by `stmt_str`,
// a string length bytes long.
fn C.mysql_real_query(mysql &C.MYSQL, q &u8, len u32) int
// C.mysql_select_db causes the database specified by `db` to become
// the default (current) database on the connection specified by mysql.
fn C.mysql_select_db(mysql &C.MYSQL, db &u8) int
// C.mysql_change_user changes the user and causes the database specified by `db` to become
// the default (current) database on the connection specified by `mysql`.
fn C.mysql_change_user(mysql &C.MYSQL, user &u8, password &u8, db &u8) bool
// C.mysql_affected_rows returns the number of rows changed, deleted,
// or inserted by the last statement if it was an `UPDATE`, `DELETE`, or `INSERT`.
fn C.mysql_affected_rows(mysql &C.MYSQL) u64
// C.mysql_options sets extra connect options and affects behavior for a connection.
fn C.mysql_options(mysql &C.MYSQL, option int, arg voidptr) int
// C.mysql_get_option returns the current value of an option settable using `mysql_options()`.
fn C.mysql_get_option(mysql &C.MYSQL, option int, arg voidptr) int
// C.mysql_list_tables returns a result set consisting of table names in the current database
// that match the simple regular expression specified by the `wild` parameter.
// `wild` may contain the wildcard characters `%` or `_`,
// or may be a `NULL` pointer to match all tables.
fn C.mysql_list_tables(mysql &C.MYSQL, wild &u8) &C.MYSQL_RES
// C.mysql_num_fields returns the number of columns in a result set.
fn C.mysql_num_fields(res &C.MYSQL_RES) int
// C.mysql_num_rows returns the number of rows in the result set.
fn C.mysql_num_rows(res &C.MYSQL_RES) u64
// C.mysql_autocommit sets autocommit mode on if `mode` is 1, off if `mode` is 0.
fn C.mysql_autocommit(mysql &C.MYSQL, mode bool)
// C.mysql_refresh flush tables or caches, or resets replication server information.
fn C.mysql_refresh(mysql &C.MYSQL, options u32) int
// C.mysql_reset_connection resets the connection to clear the session state.
fn C.mysql_reset_connection(mysql &C.MYSQL) int
// C.mysql_ping checks whether the connection to the server is working.
// Returns zero if the connection to the server is active. Nonzero if an error occurred.
fn C.mysql_ping(mysql &C.MYSQL) int
// C.mysql_store_result reads the entire result of a query to the client,
// allocates a `MYSQL_RES` structure, and places the result into this structure.
// It is a synchronous function.
fn C.mysql_store_result(mysql &C.MYSQL) &C.MYSQL_RES
// C.mysql_fetch_row retrieves the next row of a result set.
fn C.mysql_fetch_row(res &C.MYSQL_RES) &&u8
// C.mysql_fetch_fields returns an array of all `MYSQL_FIELD` structures for a result set.
// Each structure provides the field definition for one column of the result set.
fn C.mysql_fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD
// C.mysql_free_result frees the memory allocated for a result set by `mysql_store_result()`,
// `mysql_use_result()`, `mysql_list_dbs()`, and so forth.
fn C.mysql_free_result(res &C.MYSQL_RES)
// C.mysql_real_escape_string creates a legal SQL string for use in an SQL statement.
fn C.mysql_real_escape_string(mysql &C.MYSQL, to &u8, from &u8, 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)
// C.mysql_close closes a previously opened connection.
fn C.mysql_close(sock &C.MYSQL)
// INFO & VERSION
// C.mysql_info retrieves a string providing information about the most recently executed statement.
fn C.mysql_info(mysql &C.MYSQL) &u8
// C.mysql_get_host_info returns a string describing the type of connection in use,
// including the server host name.
fn C.mysql_get_host_info(mysql &C.MYSQL) &u8
// C.mysql_get_server_info returns a string that represents
// the MySQL server version (for example, "8.0.33").
fn C.mysql_get_server_info(mysql &C.MYSQL) &u8
// C.mysql_get_server_version returns an integer that represents the MySQL server version.
// The value has the format `XYYZZ` where `X` is the major version,
// `YY` is the release level (or minor version),
// and `ZZ` is the sub-version within the release level:
// `major_version*10000 + release_level*100 + sub_version`
// For example, "8.0.33" is returned as 80033.
fn C.mysql_get_server_version(mysql &C.MYSQL) u64
// C.mysql_get_client_version returns an integer that represents the MySQL client library version.
// The value has the format `XYYZZ` where `X` is the major version,
// `YY` is the release level (or minor version),
// and `ZZ` is the sub-version within the release level:
// `major_version*10000 + release_level*100 + sub_version`
// For example, "8.0.33" is returned as 80033.
fn C.mysql_get_client_version() u64
// C.mysql_get_client_info returns a string that represents
// the MySQL client library version (for example, "8.0.33").
fn C.mysql_get_client_info() &u8
// DEBUG & ERROR INFO
// C.mysql_error returns a null-terminated string containing the error message
// for the most recently invoked API function that failed.
fn C.mysql_error(mysql &C.MYSQL) &u8
// C.mysql_errno returns the error code for the most recently invoked API function that can succeed or fail.
fn C.mysql_errno(mysql &C.MYSQL) int
// C.mysql_dump_debug_info instructs the server to write debugging information to the error log.
fn C.mysql_dump_debug_info(mysql &C.MYSQL) int
// C.mysql_debug does a `DBUG_PUSH` with the given string.
fn C.mysql_debug(debug &u8)

View File

@ -1,6 +1,7 @@
module mysql
// Need to check if mysqlclient is not there and use mariadb as alternative because newer system doesn't support mysql 8.0 as default
// 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

View File

@ -1,6 +1,7 @@
module mysql
// MYSQL REFRESH FLAGS
// MySQL refresh flags.
// Docs: https://dev.mysql.com/doc/c-api/8.0/en/mysql-refresh.html
pub const (
refresh_grant = u32(C.REFRESH_GRANT)
refresh_log = u32(C.REFRESH_LOG)

View File

@ -1,6 +1,6 @@
module mysql
// FieldType is a list of all supported MYSQL field types
// FieldType is a list of all supported MYSQL field types.
pub enum FieldType {
type_decimal
type_tiny
@ -35,7 +35,7 @@ pub enum FieldType {
type_geometry
}
// str returns a text representation of the field type `f`
// str returns a text representation of the field type `f`.
pub fn (f FieldType) str() string {
return match f {
.type_decimal { 'decimal' }
@ -72,7 +72,8 @@ pub fn (f FieldType) str() string {
}
}
// get_len returns the length in bytes, for the given field type `f`
// get_len returns the length in bytes, for the given field type `f`.
// Should be deleted after the `time` type reimplementation.
pub fn (f FieldType) get_len() u32 {
return match f {
.type_blob { 262140 }

View File

@ -3,7 +3,6 @@ module mysql
// Values for the capabilities flag bitmask used by the MySQL protocol.
// See more on https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html#details
pub enum ConnectionFlag {
// CAN_HANDLE_EXPIRED_PASSWORDS = C.CAN_HANDLE_EXPIRED_PASSWORDS
client_compress = C.CLIENT_COMPRESS
client_found_rows = C.CLIENT_FOUND_ROWS
client_ignore_sigpipe = C.CLIENT_IGNORE_SIGPIPE
@ -14,7 +13,6 @@ pub enum ConnectionFlag {
client_multi_statements = C.CLIENT_MULTI_STATEMENTS
client_no_schema = C.CLIENT_NO_SCHEMA
client_odbc = C.CLIENT_ODBC
// client_optional_resultset_metadata = C.CLIENT_OPTIONAL_RESULTSET_METADATA
client_ssl = C.CLIENT_SSL
client_remember_options = C.CLIENT_REMEMBER_OPTIONS
}
@ -23,7 +21,6 @@ struct SQLError {
MessageError
}
// TODO: Documentation
pub struct Connection {
mut:
conn &C.MYSQL = C.mysql_init(0)
@ -36,29 +33,32 @@ pub mut:
flag ConnectionFlag
}
// connect - create a new connection to the MySQL server.
// connect attempts to establish a connection to a MySQL server.
pub fn (mut conn Connection) connect() !bool {
instance := C.mysql_init(conn.conn)
conn.conn = C.mysql_real_connect(instance, conn.host.str, conn.username.str, conn.password.str,
conn.dbname.str, conn.port, 0, conn.flag)
if isnil(conn.conn) {
return error_with_code(get_error_msg(instance), get_errno(instance))
}
return true
}
// query - make an SQL query and receive the results.
// `query()` cannot be used for statements that contain binary data;
// query executes the SQL statement pointed to by the string `q`.
// It cannot be used for statements that contain binary data;
// Use `real_query()` instead.
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))
}
res := C.mysql_store_result(conn.conn)
return Result{res}
result := C.mysql_store_result(conn.conn)
return Result{result}
}
// use_result - reads the result of a query
// 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).
@ -70,7 +70,7 @@ pub fn (conn Connection) use_result() {
C.mysql_use_result(conn.conn)
}
// real_query - make an SQL query and receive the results.
// real_query makes an SQL query and receive the results.
// `real_query()` can be used for statements containing binary data.
// (Binary data may contain the `\0` character, which `query()`
// interprets as the end of the statement string). In addition,
@ -79,65 +79,73 @@ pub fn (mut conn Connection) real_query(q string) !Result {
if C.mysql_real_query(conn.conn, q.str, q.len) != 0 {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
res := C.mysql_store_result(conn.conn)
return Result{res}
result := C.mysql_store_result(conn.conn)
return Result{result}
}
// select_db - change the default database for database queries.
// select_db causes the database specified by `db` to become
// the default (current) database on the connection specified by mysql.
pub fn (mut conn Connection) select_db(dbname string) !bool {
if C.mysql_select_db(conn.conn, dbname.str) != 0 {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
return true
}
// change_user - change the mysql user for the connection.
// change_user changes the mysql user for the connection.
// Passing an empty string for the `dbname` parameter, resultsg in only changing
// the user and not changing the default database for the connection.
pub fn (mut conn Connection) change_user(username string, password string, dbname string) !bool {
mut ret := true
mut result := true
if dbname != '' {
ret = C.mysql_change_user(conn.conn, username.str, password.str, dbname.str)
result = C.mysql_change_user(conn.conn, username.str, password.str, dbname.str)
} else {
ret = C.mysql_change_user(conn.conn, username.str, password.str, 0)
result = C.mysql_change_user(conn.conn, username.str, password.str, 0)
}
if !ret {
if !result {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
return ret
return result
}
// affected_rows - return the number of rows changed/deleted/inserted
// by the last `UPDATE`, `DELETE`, or `INSERT` query.
// affected_rows returns the number of rows changed, deleted,
// or inserted by the last statement if it was an `UPDATE`, `DELETE`, or `INSERT`.
pub fn (conn &Connection) affected_rows() u64 {
return C.mysql_affected_rows(conn.conn)
}
// autocommit - turns on/off the auto-committing mode for the connection.
// When it is on, then each query is commited right away.
// autocommit turns on/off the auto-committing mode for the connection.
// When it is on, then each query is committed right away.
pub fn (mut conn Connection) autocommit(mode bool) {
C.mysql_autocommit(conn.conn, mode)
}
// tables - returns a list of the names of the tables in the current database,
// tables returns a list of the names of the tables in the current database,
// that match the simple regular expression specified by the `wildcard` parameter.
// The `wildcard` parameter may contain the wildcard characters `%` or `_`.
// If an empty string is passed, it will return all tables.
// Calling `tables()` is similar to executing query `SHOW TABLES [LIKE wildcard]`.
pub fn (conn &Connection) tables(wildcard string) ![]string {
cres := C.mysql_list_tables(conn.conn, wildcard.str)
if isnil(cres) {
c_mysql_result := C.mysql_list_tables(conn.conn, wildcard.str)
if isnil(c_mysql_result) {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
res := Result{cres}
result := Result{c_mysql_result}
mut tables := []string{}
for row in res.rows() {
for row in result.rows() {
tables << row.vals[0]
}
return tables
}
// escape_string - creates a legal SQL string for use in an SQL statement.
// escape_string creates a legal SQL string for use in an SQL statement.
// The `s` argument is encoded to produce an escaped SQL string,
// taking into account the current character set of the connection.
pub fn (conn &Connection) escape_string(s string) string {
@ -148,73 +156,77 @@ pub fn (conn &Connection) escape_string(s string) string {
}
}
// set_option - sets extra connect options that affect the behavior of
// set_option sets extra connect options that affect the behavior of
// a connection. This function may be called multiple times to set several
// options. To retrieve the current values for an option, use `get_option()`.
pub fn (mut conn Connection) set_option(option_type int, val voidptr) {
C.mysql_options(conn.conn, option_type, val)
}
// get_option - return the value of an option, settable by `set_option`.
// get_option returns the value of an option, settable by `set_option`.
// https://dev.mysql.com/doc/c-api/5.7/en/mysql-get-option.html
pub fn (conn &Connection) get_option(option_type int) !voidptr {
ret := unsafe { nil }
if C.mysql_get_option(conn.conn, option_type, &ret) != 0 {
mysql_option := unsafe { nil }
if C.mysql_get_option(conn.conn, option_type, &mysql_option) != 0 {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
return ret
return mysql_option
}
// refresh - flush the tables or caches, or resets replication server
// refresh flush the tables or caches, or resets replication server
// information. The connected user must have the `RELOAD` privilege.
pub fn (mut conn Connection) refresh(options u32) !bool {
if C.mysql_refresh(conn.conn, options) != 0 {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
return true
}
// reset - resets the connection, and clear the session state.
// reset resets the connection, and clear the session state.
pub fn (mut conn Connection) reset() !bool {
if C.mysql_reset_connection(conn.conn) != 0 {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
return true
}
// ping - pings a server connection, or tries to reconnect if the connection
// ping pings a server connection, or tries to reconnect if the connection
// has gone down.
pub fn (mut conn Connection) ping() !bool {
if C.mysql_ping(conn.conn) != 0 {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
return true
}
// close - closes the connection.
// close closes the connection.
pub fn (mut conn Connection) close() {
C.mysql_close(conn.conn)
}
// info - returns information about the most recently executed query.
// info returns information about the most recently executed query.
// See more on https://dev.mysql.com/doc/c-api/8.0/en/mysql-info.html
pub fn (conn &Connection) info() string {
return resolve_nil_str(C.mysql_info(conn.conn))
}
// get_host_info - returns a string describing the type of connection in use,
// get_host_info returns a string describing the type of connection in use,
// including the server host name.
pub fn (conn &Connection) get_host_info() string {
return unsafe { C.mysql_get_host_info(conn.conn).vstring() }
}
// get_server_info - returns a string representing the MySQL server version.
// get_server_info returns a string representing the MySQL server version.
// For example, `8.0.24`.
pub fn (conn &Connection) get_server_info() string {
return unsafe { C.mysql_get_server_info(conn.conn).vstring() }
}
// get_server_version - returns an integer, representing the MySQL server
// get_server_version returns an integer, representing the MySQL server
// version. The value has the format `XYYZZ` where `X` is the major version,
// `YY` is the release level (or minor version), and `ZZ` is the sub-version
// within the release level. For example, `8.0.24` is returned as `80024`.
@ -222,26 +234,27 @@ pub fn (conn &Connection) get_server_version() u64 {
return C.mysql_get_server_version(conn.conn)
}
// dump_debug_info - instructs the server to write debugging information
// dump_debug_info instructs the server to write debugging information
// to the error log. The connected user must have the `SUPER` privilege.
pub fn (mut conn Connection) dump_debug_info() !bool {
if C.mysql_dump_debug_info(conn.conn) != 0 {
return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn))
}
return true
}
// get_client_info - returns client version information as a string.
// get_client_info returns client version information as a string.
pub fn get_client_info() string {
return unsafe { C.mysql_get_client_info().vstring() }
}
// get_client_version - returns the client version information as an integer.
// get_client_version returns the client version information as an integer.
pub fn get_client_version() u64 {
return C.mysql_get_client_version()
}
// debug - does a `DBUG_PUSH` with the given string.
// debug does a `DBUG_PUSH` with the given string.
// `debug()` uses the Fred Fish debug library.
// To use this function, you must compile the client library to support debugging.
// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-debug.html

View File

@ -3,54 +3,49 @@ module mysql
import orm
import time
// @select is used internally by V's ORM for processing `SELECT ` queries
// @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 result := [][]orm.Primitive{}
mut stmt := db.init_stmt(query)
stmt.prepare()!
mysql_stmt_binder(mut stmt, where)!
mysql_stmt_binder(mut stmt, data)!
mysql_stmt_bind_query_data(mut stmt, where)!
mysql_stmt_bind_query_data(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()
stmt.execute()!
metadata := stmt.gen_metadata()
fields := stmt.fetch_fields(metadata)
mut dataptr := []&u8{}
num_fields := stmt.get_field_count()
mut data_pointers := []&u8{}
// Allocate memory for each column.
for i in 0 .. num_fields {
field := unsafe { fields[i] }
match unsafe { FieldType(field.@type) } {
.type_tiny {
dataptr << unsafe { malloc(1) }
data_pointers << unsafe { malloc(1) }
}
.type_short {
dataptr << unsafe { malloc(2) }
data_pointers << unsafe { malloc(2) }
}
.type_long {
dataptr << unsafe { malloc(4) }
.type_long, .type_float {
data_pointers << unsafe { malloc(4) }
}
.type_longlong {
dataptr << unsafe { malloc(8) }
}
.type_float {
dataptr << unsafe { malloc(4) }
}
.type_double {
dataptr << unsafe { malloc(8) }
.type_longlong, .type_double {
data_pointers << unsafe { malloc(8) }
}
.type_time, .type_date, .type_datetime, .type_time2, .type_datetime2 {
dataptr << unsafe { malloc(sizeof(C.MYSQL_TIME)) }
data_pointers << 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)
data_pointers << &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')
@ -59,13 +54,14 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher
}
lengths := []u32{len: int(num_fields), init: 0}
stmt.bind_res(fields, dataptr, lengths, num_fields)
stmt.bind_res(fields, data_pointers, 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{}
@ -85,6 +81,7 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher
mysql_bind.buffer_type = C.MYSQL_TYPE_LONG
}
.type_time, .type_date, .type_datetime {
// FIXME: Allocate memory for blobs dynamically.
mysql_bind.buffer_type = C.MYSQL_TYPE_BLOB
mysql_bind.buffer_length = FieldType.type_blob.get_len()
}
@ -102,37 +99,37 @@ pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, wher
stmt.store_result()!
for {
status = stmt.fetch_stmt()!
// Fetch every row from the `select` result.
status := stmt.fetch_stmt()!
is_error := status == 1
are_no_rows_to_fetch := status == mysql_no_data
if status == 1 || status == 100 {
if is_error || are_no_rows_to_fetch {
break
}
// Fetch columns that should be allocated dynamically.
for index, mut bind in string_binds_map {
string_length := lengths[index] + 1
dataptr[index] = unsafe { malloc(string_length) }
bind.buffer = dataptr[index]
data_pointers[index] = unsafe { malloc(string_length) }
bind.buffer = data_pointers[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
result << data_pointers_to_primitives(data_pointers, types, field_types)!
}
stmt.close()!
return ret
return result
}
// 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)!
mut converted_primitive_array := db.convert_query_data_to_primitives(table, data)!
converted_primitive_data := orm.QueryData{
fields: data.fields
@ -168,8 +165,6 @@ pub fn (db Connection) last_id() int {
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 {
@ -184,25 +179,33 @@ pub fn (db Connection) drop(table string) ! {
mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
}
// mysql_stmt_worker executes the `query` with the provided `data` and `where` parameters
// without returning the result.
// This is commonly used for `INSERT`, `UPDATE`, `CREATE`, `DROP`, and `DELETE` queries.
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)!
mysql_stmt_bind_query_data(mut stmt, data)!
mysql_stmt_bind_query_data(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) ! {
// mysql_stmt_bind_query_data binds all the fields of `q` to the `stmt`.
fn mysql_stmt_bind_query_data(mut stmt Stmt, d orm.QueryData) ! {
for data in d.data {
stmt_binder_match(mut stmt, data)
stmt_bind_primitive(mut stmt, data)
}
}
fn stmt_binder_match(mut stmt Stmt, data orm.Primitive) {
// stmt_bind_primitive binds the `data` to the `stmt`.
fn stmt_bind_primitive(mut stmt Stmt, data orm.Primitive) {
match data {
bool {
stmt.bind_bool(&data)
@ -242,18 +245,20 @@ fn stmt_binder_match(mut stmt Stmt, data orm.Primitive) {
}
time.Time {
unix := int(data.unix)
stmt_binder_match(mut stmt, unix)
stmt_bind_primitive(mut stmt, unix)
}
orm.InfixType {
stmt_binder_match(mut stmt, data.right)
stmt_bind_primitive(mut stmt, data.right)
}
}
}
fn buffer_to_primitive(data_list []&u8, types []int, field_types []FieldType) ![]orm.Primitive {
mut res := []orm.Primitive{}
// data_pointers_to_primitives returns an array of `Primitive`
// cast from `data_pointers` using `types`.
fn data_pointers_to_primitives(data_pointers []&u8, types []int, field_types []FieldType) ![]orm.Primitive {
mut result := []orm.Primitive{}
for i, data in data_list {
for i, data in data_pointers {
mut primitive := orm.Primitive(0)
match types[i] {
orm.type_idx['i8'] {
@ -309,14 +314,15 @@ fn buffer_to_primitive(data_list []&u8, types []int, field_types []FieldType) ![
return error('Unknown type ${types[i]}')
}
}
res << primitive
result << primitive
}
return res
return result
}
// mysql_type_from_v converts the V type to the corresponding MySQL type.
fn mysql_type_from_v(typ int) !string {
str := match typ {
sql_type := match typ {
orm.type_idx['i8'], orm.type_idx['u8'] {
'TINYINT'
}
@ -345,48 +351,48 @@ fn mysql_type_from_v(typ int) !string {
'BOOLEAN'
}
else {
''
return error('Unknown type ${typ}')
}
}
if str == '' {
return error('Unknown type ${typ}')
}
return str
return sql_type
}
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
// convert_query_data_to_primitives converts the `data` representing the `QueryData`
// into an array of `Primitive`.
fn (db Connection) convert_query_data_to_primitives(table string, data orm.QueryData) ![]orm.Primitive {
mut column_type_map := db.get_table_column_type_map(table)!
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 {
if data.data[i].type_name() == 'time.Time' {
if column_type_map[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{}
// get_table_column_type_map returns a map where the key represents the column name,
// and the value represents its data type.
fn (db Connection) get_table_column_type_map(table string) !map[string]string {
data_type_query := "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '${table}'"
mut column_type_map := map[string]string{}
results := db.query(data_type_query)!
results := db.query(data_type_querys)!
db.use_result()
for row in results.rows() {
map_val[row.vals[0]] = row.vals[1]
column_type_map[row.vals[0]] = row.vals[1]
}
unsafe { results.free() }
return map_val
return column_type_map
}

View File

@ -32,22 +32,22 @@ pub struct Field {
type_ FieldType
}
// fetch_row - fetches the next row from a result.
// fetch_row fetches the next row from a result.
pub fn (r Result) fetch_row() &&u8 {
return C.mysql_fetch_row(r.result)
}
// n_rows - returns the number of rows from a result.
// n_rows returns the number of rows from a result.
pub fn (r Result) n_rows() u64 {
return C.mysql_num_rows(r.result)
}
// n_fields - returns the number of columns from a result.
// n_fields returns the number of columns from a result.
pub fn (r Result) n_fields() int {
return C.mysql_num_fields(r.result)
}
// rows - returns array of rows, each containing an array of values,
// rows returns array of rows, each containing an array of values,
// one for each column.
pub fn (r Result) rows() []Row {
mut rows := []Row{}
@ -66,7 +66,7 @@ pub fn (r Result) rows() []Row {
return rows
}
// maps - returns an array of maps, each containing a set of
// maps returns an array of maps, each containing a set of
// field name: field value pairs.
pub fn (r Result) maps() []map[string]string {
mut array_map := []map[string]string{}
@ -82,7 +82,7 @@ pub fn (r Result) maps() []map[string]string {
return array_map
}
// fields - returns an array of fields/columns.
// fields returns an array of fields/columns.
// The definitions apply primarily for columns of results,
// such as those produced by `SELECT` statements.
pub fn (r Result) fields() []Field {
@ -118,7 +118,7 @@ pub fn (r Result) fields() []Field {
return fields
}
// str - serializes the field
// str serializes the field.
pub fn (f Field) str() string {
return '
{
@ -146,7 +146,7 @@ pub fn (f Field) str() string {
'
}
// free - frees the memory used by a result
// free frees the memory used by a result.
[unsafe]
pub fn (r &Result) free() {
C.mysql_free_result(r.result)

View File

@ -71,12 +71,12 @@ mut:
res []C.MYSQL_BIND
}
// str returns a text representation of the given mysql statement `s`
// str returns a text representation of the given mysql statement `s`.
pub fn (s &Stmt) str() string {
return 'mysql.Stmt{ stmt: ${voidptr(s.stmt):x}, query: `${s.query}`, binds.len: ${s.binds.len}, res.len: ${s.res.len} }'
}
// init_stmt creates a new statement, given the `query`
// init_stmt creates a new statement, given the `query`.
pub fn (db Connection) init_stmt(query string) Stmt {
return Stmt{
stmt: C.mysql_stmt_init(db.conn)
@ -85,38 +85,44 @@ pub fn (db Connection) init_stmt(query string) Stmt {
}
}
// prepare a statement for execution
// prepare a statement for execution.
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)
result := C.mysql_stmt_prepare(stmt.stmt, stmt.query.str, stmt.query.len)
if result != 0 && stmt.get_error_msg() != '' {
return stmt.error(result)
}
}
// bind_params binds all the parameters in `stmt`
// bind_params binds all the parameters in `stmt`.
pub fn (stmt Stmt) bind_params() ! {
res := C.mysql_stmt_bind_param(stmt.stmt, unsafe { &C.MYSQL_BIND(stmt.binds.data) })
if res && stmt.get_error_msg() != '' {
result := C.mysql_stmt_bind_param(stmt.stmt, unsafe { &C.MYSQL_BIND(stmt.binds.data) })
if result && stmt.get_error_msg() != '' {
return stmt.error(1)
}
}
// execute executes the given `stmt` and waits for the result
// execute executes the given `stmt` and waits for the result.
pub fn (stmt Stmt) execute() !int {
res := C.mysql_stmt_execute(stmt.stmt)
if res != 0 && stmt.get_error_msg() != '' {
return stmt.error(res)
result := C.mysql_stmt_execute(stmt.stmt)
if result != 0 && stmt.get_error_msg() != '' {
return stmt.error(result)
}
return res
return result
}
// next retrieves the next available result from the execution of `stmt`
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)
result := C.mysql_stmt_next_result(stmt.stmt)
if result != 0 && stmt.get_error_msg() != '' {
return stmt.error(result)
}
return res
return result
}
// gen_metadata executes mysql_stmt_result_metadata over the given `stmt`
@ -136,11 +142,13 @@ pub fn (stmt Stmt) fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD {
// fetch_stmt fetches the next row in the result set. It returns the status of the execution of mysql_stmt_fetch .
// See https://dev.mysql.com/doc/c-api/5.7/en/mysql-stmt-fetch.html
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)
result := C.mysql_stmt_fetch(stmt.stmt)
if result !in [0, 100] && stmt.get_error_msg() != '' {
return stmt.error(result)
}
return res
return result
}
// close disposes the prepared `stmt`. The statement becomes invalid, and should not be used anymore after this call.
@ -150,6 +158,7 @@ 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)
}
@ -162,6 +171,7 @@ fn (stmt Stmt) get_error_msg() string {
// error returns a proper V error with a human readable description, given the error code returned by MySQL
pub fn (stmt Stmt) error(code int) IError {
msg := stmt.get_error_msg()
return &SQLError{
msg: '${msg} (${code}) (${stmt.query})'
code: code
@ -262,8 +272,9 @@ pub fn (mut stmt Stmt) bind_res(fields &C.MYSQL_FIELD, dataptr []&u8, lengths []
// bind_result_buffer binds one result value, by calling mysql_stmt_bind_result .
// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-stmt-bind-result.html
pub fn (mut stmt Stmt) bind_result_buffer() ! {
res := C.mysql_stmt_bind_result(stmt.stmt, unsafe { &C.MYSQL_BIND(stmt.res.data) })
if res && stmt.get_error_msg() != '' {
result := C.mysql_stmt_bind_result(stmt.stmt, unsafe { &C.MYSQL_BIND(stmt.res.data) })
if result && stmt.get_error_msg() != '' {
return stmt.error(1)
}
}
@ -277,9 +288,10 @@ pub fn (mut stmt Stmt) bind_result_buffer() ! {
// and *before* calling fetch_stmt to fetch rows.
// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-stmt-store-result.html
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)
result := C.mysql_stmt_store_result(stmt.stmt)
if result != 0 && stmt.get_error_msg() != '' {
return stmt.error(result)
}
}

View File

@ -1,16 +1,16 @@
module mysql
// get_error_msg - returns error message from MySQL instance.
// get_error_msg returns error message from MySQL instance.
fn get_error_msg(conn &C.MYSQL) string {
return unsafe { C.mysql_error(conn).vstring() }
}
// get_errno - returns error number from MySQL instance.
// get_errno returns error number from MySQL instance.
fn get_errno(conn &C.MYSQL) int {
return C.mysql_errno(conn)
}
// resolve_nil_str - returns an empty string if passed value is a nil pointer.
// resolve_nil_str returns an empty string if passed value is a nil pointer.
fn resolve_nil_str(ptr &u8) string {
if isnil(ptr) {
return ''