mirror of
https://github.com/vlang/v.git
synced 2025-08-04 02:07:28 -04:00
db.mysql: add ability to prepare and execute statements separately (#20146)
This commit is contained in:
parent
e6a30dc4ef
commit
d86a11c257
@ -90,6 +90,7 @@ const skip_test_files = [
|
||||
'vlib/context/onecontext/onecontext_test.v', // backtrace_symbols is missing
|
||||
'vlib/db/mysql/mysql_orm_test.v', // mysql not installed
|
||||
'vlib/db/mysql/mysql_test.v', // mysql not installed
|
||||
'vlib/db/mysql/prepared_stmt_test.v', // mysql not installed
|
||||
'vlib/db/pg/pg_orm_test.v', // pg not installed
|
||||
]
|
||||
// These tests are too slow to be run in the CI on each PR/commit
|
||||
|
@ -345,6 +345,33 @@ pub fn (db &DB) exec_none(query string) int {
|
||||
// exec_param_many executes the `query` with parameters provided as `?`'s in the query
|
||||
// It returns either the full result set, or an error on failure
|
||||
pub fn (db &DB) exec_param_many(query string, params []string) ![]Row {
|
||||
stmt := db.prepare(query)!
|
||||
defer {
|
||||
stmt.close()
|
||||
}
|
||||
rows := stmt.execute(params)!
|
||||
return rows
|
||||
}
|
||||
|
||||
// exec_param executes the `query` with one parameter provided as an `?` in the query
|
||||
// It returns either the full result set, or an error on failure
|
||||
pub fn (db &DB) exec_param(query string, param string) ![]Row {
|
||||
return db.exec_param_many(query, [param])!
|
||||
}
|
||||
|
||||
// A StmtHandle is created through prepare, it will be bound
|
||||
// to one DB connection and will become unusable if the connection
|
||||
// is closed
|
||||
pub struct StmtHandle {
|
||||
stmt &C.MYSQL_STMT = &C.MYSQL_STMT(unsafe { nil })
|
||||
db DB
|
||||
}
|
||||
|
||||
// prepare takes in a query string, returning a StmtHandle
|
||||
// that can then be used to execute the query as many times
|
||||
// as needed, which must be closed manually by the user
|
||||
// Placeholders are represented by `?`
|
||||
pub fn (db &DB) prepare(query string) !StmtHandle {
|
||||
stmt := C.mysql_stmt_init(db.conn)
|
||||
if stmt == unsafe { nil } {
|
||||
db.throw_mysql_error()!
|
||||
@ -355,6 +382,19 @@ pub fn (db &DB) exec_param_many(query string, params []string) ![]Row {
|
||||
db.throw_mysql_error()!
|
||||
}
|
||||
|
||||
return StmtHandle{
|
||||
stmt: stmt
|
||||
db: DB{
|
||||
conn: db.conn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// execute takes in an array of params that will be bound to the statement,
|
||||
// followed by it's execution
|
||||
// Returns an array of Rows, which will be empty if nothing is returned
|
||||
// from the query, or possibly an error value
|
||||
pub fn (stmt &StmtHandle) execute(params []string) ![]Row {
|
||||
mut bind_params := []C.MYSQL_BIND{}
|
||||
for param in params {
|
||||
bind := C.MYSQL_BIND{
|
||||
@ -366,17 +406,22 @@ pub fn (db &DB) exec_param_many(query string, params []string) ![]Row {
|
||||
bind_params << bind
|
||||
}
|
||||
|
||||
mut response := C.mysql_stmt_bind_param(stmt, unsafe { &C.MYSQL_BIND(bind_params.data) })
|
||||
mut response := C.mysql_stmt_bind_param(stmt.stmt, unsafe { &C.MYSQL_BIND(bind_params.data) })
|
||||
if response == true {
|
||||
db.throw_mysql_error()!
|
||||
stmt.db.throw_mysql_error()!
|
||||
}
|
||||
|
||||
code = C.mysql_stmt_execute(stmt)
|
||||
mut code := C.mysql_stmt_execute(stmt.stmt)
|
||||
if code != 0 {
|
||||
db.throw_mysql_error()!
|
||||
stmt.db.throw_mysql_error()!
|
||||
}
|
||||
|
||||
query_metadata := C.mysql_stmt_result_metadata(stmt)
|
||||
query_metadata := C.mysql_stmt_result_metadata(stmt.stmt)
|
||||
// If the query returns no metadata we have no data to return
|
||||
// This happens in insert queries
|
||||
if query_metadata == unsafe { nil } {
|
||||
return []Row{}
|
||||
}
|
||||
num_cols := C.mysql_num_fields(query_metadata)
|
||||
mut length := []u32{len: num_cols}
|
||||
|
||||
@ -392,9 +437,9 @@ pub fn (db &DB) exec_param_many(query string, params []string) ![]Row {
|
||||
}
|
||||
|
||||
mut rows := []Row{}
|
||||
response = C.mysql_stmt_bind_result(stmt, unsafe { &C.MYSQL_BIND(binds.data) })
|
||||
response = C.mysql_stmt_bind_result(stmt.stmt, unsafe { &C.MYSQL_BIND(binds.data) })
|
||||
for {
|
||||
code = C.mysql_stmt_fetch(stmt)
|
||||
code = C.mysql_stmt_fetch(stmt.stmt)
|
||||
if code == mysql_no_data {
|
||||
break
|
||||
}
|
||||
@ -405,20 +450,19 @@ pub fn (db &DB) exec_param_many(query string, params []string) ![]Row {
|
||||
data := unsafe { malloc(l) }
|
||||
binds[i].buffer = data
|
||||
binds[i].buffer_length = l
|
||||
code = C.mysql_stmt_fetch_column(stmt, unsafe { &binds[i] }, i, 0)
|
||||
code = C.mysql_stmt_fetch_column(stmt.stmt, unsafe { &binds[i] }, i, 0)
|
||||
|
||||
row.vals << unsafe { data.vstring() }
|
||||
}
|
||||
rows << row
|
||||
}
|
||||
C.mysql_stmt_close(stmt)
|
||||
return rows
|
||||
}
|
||||
|
||||
// exec_param executes the `query` with one parameter provided as an `?` in the query
|
||||
// It returns either the full result set, or an error on failure
|
||||
pub fn (db &DB) exec_param(query string, param string) ![]Row {
|
||||
return db.exec_param_many(query, [param])!
|
||||
// close acts on a StmtHandle to close the mysql Stmt
|
||||
// meaning it is no longer available for use
|
||||
pub fn (stmt &StmtHandle) close() {
|
||||
C.mysql_stmt_close(stmt.stmt)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
|
@ -28,6 +28,8 @@ fn test_mysql() {
|
||||
assert result_code == 0
|
||||
result_code = db.exec_none('insert into users (username) values ("blaze")')
|
||||
assert result_code == 0
|
||||
rows := db.exec_param('insert into users (username) values (?)', 'Hi')!
|
||||
assert rows == []mysql.Row{}
|
||||
|
||||
// Regression testing to ensure the query and exec return the same values
|
||||
res := db.query('select * from users')!
|
||||
@ -69,6 +71,9 @@ fn test_mysql() {
|
||||
mysql.Row{
|
||||
vals: ['4', 'blaze']
|
||||
},
|
||||
mysql.Row{
|
||||
vals: ['5', 'Hi']
|
||||
},
|
||||
]
|
||||
|
||||
response = db.exec_param('select * from users where username = ?', 'blaze')!
|
||||
|
54
vlib/db/mysql/prepared_stmt_test.v
Normal file
54
vlib/db/mysql/prepared_stmt_test.v
Normal file
@ -0,0 +1,54 @@
|
||||
import db.mysql
|
||||
|
||||
fn test_prep() {
|
||||
config := mysql.Config{
|
||||
host: '127.0.0.1'
|
||||
port: 3306
|
||||
username: 'root'
|
||||
password: ''
|
||||
dbname: 'mysql'
|
||||
}
|
||||
|
||||
db := mysql.connect(config)!
|
||||
|
||||
mut response := db.exec('drop table if exists test')!
|
||||
assert response == []mysql.Row{}
|
||||
|
||||
response = db.exec('create table if not exists test (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
value TEXT)')!
|
||||
assert response == []mysql.Row{}
|
||||
|
||||
stmt := db.prepare('insert into test (value) values (?)')!
|
||||
defer {
|
||||
stmt.close()
|
||||
}
|
||||
|
||||
names := ['jackson', 'hello', 'Disney', 'Marz', 'Bailey', 'Claxton']
|
||||
for name in names {
|
||||
response = stmt.execute([name])!
|
||||
assert response == []mysql.Row{}
|
||||
}
|
||||
|
||||
response = db.exec_param_many('select * from test', [''])!
|
||||
assert response == [
|
||||
mysql.Row{
|
||||
vals: ['1', 'jackson']
|
||||
},
|
||||
mysql.Row{
|
||||
vals: ['2', 'hello']
|
||||
},
|
||||
mysql.Row{
|
||||
vals: ['3', 'Disney']
|
||||
},
|
||||
mysql.Row{
|
||||
vals: ['4', 'Marz']
|
||||
},
|
||||
mysql.Row{
|
||||
vals: ['5', 'Bailey']
|
||||
},
|
||||
mysql.Row{
|
||||
vals: ['6', 'Claxton']
|
||||
},
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user