From 351b2e0e4266da5faafc3ec148aa1747d52ff06a Mon Sep 17 00:00:00 2001 From: Mark aka walkingdevel <104449470+walkingdevel@users.noreply.github.com> Date: Thu, 25 May 2023 00:50:15 +0000 Subject: [PATCH] mysql: refactor, comments, simplify (#18258) --- vlib/db/mysql/_cdefs.c.v | 72 +++++++++++++++-- vlib/db/mysql/_cdefs_nix.c.v | 3 +- vlib/db/mysql/consts.v | 3 +- vlib/db/mysql/enums.v | 7 +- vlib/db/mysql/mysql.v | 105 +++++++++++++----------- vlib/db/mysql/orm.v | 150 ++++++++++++++++++----------------- vlib/db/mysql/result.v | 16 ++-- vlib/db/mysql/stmt.c.v | 66 ++++++++------- vlib/db/mysql/utils.v | 6 +- 9 files changed, 259 insertions(+), 169 deletions(-) diff --git a/vlib/db/mysql/_cdefs.c.v b/vlib/db/mysql/_cdefs.c.v index 9c69d33a28..477afb5cfe 100644 --- a/vlib/db/mysql/_cdefs.c.v +++ b/vlib/db/mysql/_cdefs.c.v @@ -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) diff --git a/vlib/db/mysql/_cdefs_nix.c.v b/vlib/db/mysql/_cdefs_nix.c.v index 1f37210529..3f8231b27f 100644 --- a/vlib/db/mysql/_cdefs_nix.c.v +++ b/vlib/db/mysql/_cdefs_nix.c.v @@ -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 diff --git a/vlib/db/mysql/consts.v b/vlib/db/mysql/consts.v index 2e9962a335..68dcde1f63 100644 --- a/vlib/db/mysql/consts.v +++ b/vlib/db/mysql/consts.v @@ -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) diff --git a/vlib/db/mysql/enums.v b/vlib/db/mysql/enums.v index 0aa86f4f1e..76b3edfd91 100644 --- a/vlib/db/mysql/enums.v +++ b/vlib/db/mysql/enums.v @@ -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 } diff --git a/vlib/db/mysql/mysql.v b/vlib/db/mysql/mysql.v index b03e8a0b6f..aaf233dc6d 100644 --- a/vlib/db/mysql/mysql.v +++ b/vlib/db/mysql/mysql.v @@ -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 diff --git a/vlib/db/mysql/orm.v b/vlib/db/mysql/orm.v index b65fb73756..f585acc8d3 100644 --- a/vlib/db/mysql/orm.v +++ b/vlib/db/mysql/orm.v @@ -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 } diff --git a/vlib/db/mysql/result.v b/vlib/db/mysql/result.v index 0969c75c20..c9bcf2e666 100644 --- a/vlib/db/mysql/result.v +++ b/vlib/db/mysql/result.v @@ -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) diff --git a/vlib/db/mysql/stmt.c.v b/vlib/db/mysql/stmt.c.v index 2beefb8138..b4de350121 100644 --- a/vlib/db/mysql/stmt.c.v +++ b/vlib/db/mysql/stmt.c.v @@ -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) } } diff --git a/vlib/db/mysql/utils.v b/vlib/db/mysql/utils.v index d35554c805..233b1225d3 100644 --- a/vlib/db/mysql/utils.v +++ b/vlib/db/mysql/utils.v @@ -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 ''