v / vlib / db / mysql / mysql.c.v
1010 lines · 903 sloc · 29.83 KB · 5ec8c10c31cb67d9ca9ab7036f96bf547dbae66e
Raw
1module mysql
2
3import sync
4
5// Values for the capabilities flag bitmask used by the MySQL protocol.
6// See more on https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html#details
7@[flag]
8pub enum ConnectionFlag {
9 client_long_password // (1 << 0) Use the improved version of Old Password Authentication
10 client_found_rows // (1 << 1) Send found rows instead of affected rows in EOF_Packet
11 client_long_flag // (1 << 2) Get all column flags
12 client_connect_with_db // (1 << 3) Database (schema) name can be specified on connect in Handshake Response Packet
13 client_no_schema // (1 << 4) DEPRECATED: Don't allow database.table.column
14 client_compress // (1 << 5) Compression protocol supported
15 client_odbc // (1 << 6) Special handling of ODBC behavior
16 client_local_files // (1 << 7) Can use LOAD DATA LOCAL
17 client_ignore_space // (1 << 8) Ignore spaces before '('
18 client_protocol_41 // (1 << 9) New 4.1 protocol
19 client_interactive // (1 << 10) This is an interactive client
20 client_ssl // (1 << 11) Use SSL encryption for the session
21 client_ignore_sigpipe // (1 << 12) Client only flag
22 client_transactions // (1 << 13) Client knows about transactions
23 client_reserved // (1 << 14) DEPRECATED: Old flag for 4.1 protocol
24 client_reserved2 // (1 << 15) DEPRECATED: Old flag for 4.1 authentication \ CLIENT_SECURE_CONNECTION
25 client_multi_statements // (1 << 16) Enable/disable multi-stmt support
26 client_multi_results // (1 << 17) Enable/disable multi-results
27 client_ps_multi_results // (1 << 18) Multi-results and OUT parameters in PS-protocol
28 client_plugin_auth // (1 << 19) Client supports plugin authentication
29 client_connect_attrs // (1 << 20) Client supports connection attributes
30 client_plugin_auth_lenenc_client_data // (1 << 21) Enable authentication response packet to be larger than 255 bytes
31 client_can_handle_expired_passwords // (1 << 22) Don't close the connection for a user account with expired password
32 client_session_track // (1 << 23) Capable of handling server state change information
33 client_deprecate_eof // (1 << 24) Client no longer needs EOF_Packet and will use OK_Packet instead
34 client_optional_resultset_metadata // (1 << 25) The client can handle optional metadata information in the resultset
35 client_zstd_compression_algorithm // (1 << 26) Compression protocol extended to support zstd compression method
36 client_query_attributes // (1 << 27) Support optional extension for query parameters into the COM_QUERY and COM_STMT_EXECUTE packets
37 multi_factor_authentication // (1 << 28) Support Multi factor authentication
38 client_capability_extension // (1 << 29) This flag will be reserved to extend the 32bit capabilities structure to 64bits
39 client_ssl_verify_server_cert // (1 << 30) Verify server certificate
40 client_remember_options // (1 << 31) Don't reset the options after an unsuccessful connect
41}
42
43pub enum MySQLTransactionLevel {
44 read_uncommitted
45 read_committed
46 repeatable_read
47 serializable
48}
49
50struct SQLError {
51 MessageError
52}
53
54const mysql_no_connection_error_message = 'No connection to a MySQL server, use `connect()` to connect to a database for working with it'
55const mysql_thread_init_error_message = 'db.mysql: failed to initialize MySQL thread state'
56
57struct ConnectionState {
58mut:
59 conn &C.MYSQL = unsafe { nil }
60 mutex &sync.Mutex = sync.new_mutex()
61}
62
63struct MySQLThreadGuard {
64mut:
65 active bool
66}
67
68struct MySQLConnectionGuard {
69mut:
70 conn &C.MYSQL = unsafe { nil }
71 state &ConnectionState = unsafe { nil }
72 thread MySQLThreadGuard
73}
74
75pub struct DB {
76mut:
77 conn &C.MYSQL = unsafe { nil }
78 state &ConnectionState = unsafe { nil }
79}
80
81@[params]
82pub struct Config {
83pub mut:
84 host string = '127.0.0.1'
85 port u32 = 3306
86 user string
87 username string
88 password string
89 dbname string
90 flag ConnectionFlag
91
92 // ssl_mode controls the SSL/TLS negotiation policy via `MYSQL_OPT_SSL_MODE`.
93 // Set it to `.disabled` to connect to servers without TLS support
94 // (libmysqlclient 8.x otherwise tries `.preferred`/`.required`, which can
95 // fail against servers built without SSL). When left as `.unset`, no
96 // `MYSQL_OPT_SSL_MODE` call is made and the libmysqlclient default applies.
97 ssl_mode SslMode
98
99 // SSL params, only valid when set .client_ssl
100 ssl_key string
101 ssl_cert string
102 ssl_ca string
103 ssl_capath string
104 ssl_cipher string
105}
106
107// connection_user returns the configured username, accepting both `user` and `username`.
108pub fn (config Config) connection_user() !string {
109 if config.user != '' && config.username != '' && config.user != config.username {
110 return error('db.mysql: Config.user and Config.username must match when both are set')
111 }
112 if config.username != '' {
113 return config.username
114 }
115 return config.user
116}
117
118// val returns the value at `index`.
119pub fn (row Row) val(index int) string {
120 return row.vals[index]
121}
122
123// values returns all row values.
124pub fn (row Row) values() []string {
125 return row.vals.clone()
126}
127
128// connect attempts to establish a connection to a MySQL server.
129pub fn connect(config Config) !DB {
130 mut db := DB{
131 conn: C.mysql_init(0)
132 }
133 username := config.connection_user()!
134
135 if config.ssl_mode != .unset {
136 ssl_mode := int(config.ssl_mode)
137 db.set_option(C.MYSQL_OPT_SSL_MODE, &ssl_mode)
138 }
139
140 if config.flag.has(.client_ssl) {
141 if config.ssl_key.len > 0 {
142 db.set_option(C.MYSQL_OPT_SSL_KEY, config.ssl_key.str)
143 }
144 if config.ssl_cert.len > 0 {
145 db.set_option(C.MYSQL_OPT_SSL_CERT, config.ssl_cert.str)
146 }
147 if config.ssl_ca.len > 0 {
148 db.set_option(C.MYSQL_OPT_SSL_CA, config.ssl_ca.str)
149 }
150 if config.ssl_capath.len > 0 {
151 db.set_option(C.MYSQL_OPT_SSL_CAPATH, config.ssl_capath.str)
152 }
153 if config.ssl_cipher.len > 0 {
154 db.set_option(C.MYSQL_OPT_SSL_CIPHER, config.ssl_cipher.str)
155 }
156 }
157
158 connection := C.mysql_real_connect(db.conn, config.host.str, username.str, config.password.str,
159 config.dbname.str, config.port, 0, config.flag)
160
161 if isnil(connection) {
162 db.throw_mysql_error()!
163 }
164
165 // Sets `db.conn` after checking for `null`
166 // because `throw_mysql_error` can't extract an error from a `null` connection,
167 // and `panic` will be with an empty message.
168 db.conn = connection
169 db.state = &ConnectionState{
170 conn: connection
171 }
172
173 return db
174}
175
176// query executes the SQL statement pointed to by the string `q`.
177// It cannot be used for statements that contain binary data;
178// Use `real_query()` instead.
179//
180// When the connection was opened with `ConnectionFlag.client_multi_statements`
181// and `q` contains more than one statement, only the first result set is
182// returned; the server queues the remaining statements and they will only
183// finish executing as the client drains them with `next_result()`. Use
184// `exec_multi()` to run multi-statement queries to completion.
185pub fn (db &DB) query(q string) !Result {
186 mut guard := db.acquire_connection_guard()!
187 defer {
188 guard.release()
189 }
190 if C.mysql_query(guard.conn, charptr(q.str)) != 0 {
191 throw_mysql_error_for_conn(guard.conn)!
192 }
193
194 result := C.mysql_store_result(guard.conn)
195 return Result{result}
196}
197
198// use_result reads the result of a query
199// used after invoking mysql_real_query() or mysql_query(),
200// for every statement that successfully produces a result set
201// (SELECT, SHOW, DESCRIBE, EXPLAIN, CHECK TABLE, and so forth).
202// This reads the result of a query directly from the server
203// without storing it in a temporary table or local buffer,
204// mysql_use_result is faster and uses much less memory than C.mysql_store_result().
205// You must mysql_free_result() after you are done with the result set.
206pub fn (db &DB) use_result() {
207 mut guard := db.acquire_connection_guard() or { return }
208 defer {
209 guard.release()
210 }
211 C.mysql_use_result(guard.conn)
212}
213
214// real_query makes an SQL query and receive the results.
215// `real_query()` can be used for statements containing binary data.
216// (Binary data may contain the `\0` character, which `query()`
217// interprets as the end of the statement string). In addition,
218// `real_query()` is faster than `query()`.
219//
220// When the connection was opened with `ConnectionFlag.client_multi_statements`
221// and `q` contains more than one statement, only the first result set is
222// returned; the server queues the remaining statements and they will only
223// finish executing as the client drains them with `next_result()`. Use
224// `exec_multi()` to run multi-statement queries to completion.
225pub fn (mut db DB) real_query(q string) !Result {
226 mut guard := db.acquire_connection_guard()!
227 defer {
228 guard.release()
229 }
230 if C.mysql_real_query(guard.conn, q.str, q.len) != 0 {
231 throw_mysql_error_for_conn(guard.conn)!
232 }
233
234 result := C.mysql_store_result(guard.conn)
235 return Result{result}
236}
237
238// more_results reports whether more result sets are available from the most
239// recently executed multi-statement query (issued with
240// `ConnectionFlag.client_multi_statements`). Pair it with `next_result()` to
241// drain pending result sets; otherwise the connection is left in a
242// "Commands out of sync" state and cannot run further statements.
243pub fn (db &DB) more_results() bool {
244 mut guard := db.acquire_connection_guard() or { return false }
245 defer {
246 guard.release()
247 }
248 return C.mysql_more_results(guard.conn)
249}
250
251// next_result advances the connection to the next result set of a
252// multi-statement query. It returns `true` if another result set is now
253// available (and can be read with `store_result()`/`Result.rows()`), `false`
254// if there are no more result sets, and an error if the server reported one.
255//
256// Any previous result set must be freed (via `Result.free()` or by consuming
257// it with `Result.rows()` and then `Result.free()`) before calling
258// `next_result()`. The high-level `exec_multi()` handles this automatically.
259pub fn (mut db DB) next_result() !bool {
260 mut guard := db.acquire_connection_guard()!
261 defer {
262 guard.release()
263 }
264 code := C.mysql_next_result(guard.conn)
265 if code == 0 {
266 return true
267 }
268 if code == -1 {
269 return false
270 }
271 throw_mysql_error_for_conn(guard.conn)!
272 return false
273}
274
275// store_result reads the result of the current statement into a `Result`,
276// which the caller is responsible for freeing (via `Result.free()`). This is
277// useful in combination with `next_result()` for iterating over result sets
278// of a multi-statement query started by `query()` or `real_query()`.
279// If the current statement did not produce a result set (e.g. an `INSERT`),
280// the returned `Result` has a `nil` inner pointer.
281// Returns an error if the server reported one.
282pub fn (db &DB) store_result() !Result {
283 mut guard := db.acquire_connection_guard()!
284 defer {
285 guard.release()
286 }
287 c_result := C.mysql_store_result(guard.conn)
288 if isnil(c_result) && get_errno(guard.conn) != 0 {
289 throw_mysql_error_for_conn(guard.conn)!
290 }
291 return Result{c_result}
292}
293
294// exec_multi executes a multi-statement `query` against the server and
295// returns one entry per executed statement, in execution order. Statements
296// that produce a result set (e.g. `SELECT`) contribute their rows; statements
297// that do not (e.g. `INSERT`, `UPDATE`) contribute an empty `[]Row`.
298//
299// Use this with connections opened using `ConnectionFlag.client_multi_statements`
300// so that the connection is fully drained and remains usable for subsequent
301// queries. All intermediate result sets are freed automatically.
302pub fn (mut db DB) exec_multi(query string) ![][]Row {
303 mut guard := db.acquire_connection_guard()!
304 defer {
305 guard.release()
306 }
307 if C.mysql_real_query(guard.conn, query.str, query.len) != 0 {
308 throw_mysql_error_for_conn(guard.conn)!
309 }
310 mut results := [][]Row{}
311 for {
312 c_result := C.mysql_store_result(guard.conn)
313 if !isnil(c_result) {
314 rows := Result{c_result}.rows()
315 C.mysql_free_result(c_result)
316 results << rows
317 } else {
318 if get_errno(guard.conn) != 0 {
319 throw_mysql_error_for_conn(guard.conn)!
320 }
321 results << []Row{}
322 }
323 code := C.mysql_next_result(guard.conn)
324 if code == -1 {
325 break
326 }
327 if code > 0 {
328 throw_mysql_error_for_conn(guard.conn)!
329 }
330 }
331 return results
332}
333
334// select_db causes the database specified by `db` to become
335// the default (current) database on the connection specified by mysql.
336pub fn (mut db DB) select_db(dbname string) !bool {
337 mut guard := db.acquire_connection_guard()!
338 defer {
339 guard.release()
340 }
341 if C.mysql_select_db(guard.conn, dbname.str) != 0 {
342 throw_mysql_error_for_conn(guard.conn)!
343 }
344
345 return true
346}
347
348// change_user changes the mysql user for the connection.
349// Passing an empty string for the `dbname` parameter, resultsg in only changing
350// the user and not changing the default database for the connection.
351pub fn (mut db DB) change_user(username string, password string, dbname string) !bool {
352 mut guard := db.acquire_connection_guard()!
353 defer {
354 guard.release()
355 }
356 mut result := true
357
358 if dbname != '' {
359 result = C.mysql_change_user(guard.conn, username.str, password.str, dbname.str)
360 } else {
361 result = C.mysql_change_user(guard.conn, username.str, password.str, 0)
362 }
363 if !result {
364 throw_mysql_error_for_conn(guard.conn)!
365 }
366
367 return result
368}
369
370// affected_rows returns the number of rows changed, deleted,
371// or inserted by the last statement if it was an `UPDATE`, `DELETE`, or `INSERT`.
372pub fn (db &DB) affected_rows() u64 {
373 mut guard := db.acquire_connection_guard() or { return 0 }
374 defer {
375 guard.release()
376 }
377 return C.mysql_affected_rows(guard.conn)
378}
379
380// autocommit turns on/off the auto-committing mode for the connection.
381// When it is on, then each query is committed right away.
382pub fn (mut db DB) autocommit(mode bool) ! {
383 mut guard := db.acquire_connection_guard()!
384 defer {
385 guard.release()
386 }
387 result := C.mysql_autocommit(guard.conn, mode)
388
389 if result != 0 {
390 throw_mysql_error_for_conn(guard.conn)!
391 }
392}
393
394// commit commits the current transaction.
395pub fn (db &DB) commit() ! {
396 mut guard := db.acquire_connection_guard()!
397 defer {
398 guard.release()
399 }
400 result := C.mysql_commit(guard.conn)
401
402 if result != 0 {
403 throw_mysql_error_for_conn(guard.conn)!
404 }
405}
406
407@[params]
408pub struct MySQLTransactionParam {
409 transaction_level MySQLTransactionLevel = .repeatable_read
410}
411
412// begin begins a new transaction.
413pub fn (db &DB) begin(param MySQLTransactionParam) ! {
414 db.check_connection_is_established()!
415 db.set_transaction_level(param.transaction_level)!
416 result := db.exec_none('START TRANSACTION')
417 if result != 0 {
418 db.throw_mysql_error()!
419 }
420}
421
422// set_transaction_level set level for the transaction
423pub fn (db &DB) set_transaction_level(level MySQLTransactionLevel) ! {
424 db.check_connection_is_established()!
425 mut sql_stmt := 'SET TRANSACTION ISOLATION LEVEL '
426 match level {
427 .read_uncommitted { sql_stmt += 'READ UNCOMMITTED' }
428 .read_committed { sql_stmt += 'READ COMMITTED' }
429 .repeatable_read { sql_stmt += 'REPEATABLE READ' }
430 .serializable { sql_stmt += 'SERIALIZABLE' }
431 }
432
433 result := db.exec_none(sql_stmt)
434 if result != 0 {
435 db.throw_mysql_error()!
436 }
437}
438
439// rollback rollbacks the current transaction.
440pub fn (db &DB) rollback() ! {
441 mut guard := db.acquire_connection_guard()!
442 defer {
443 guard.release()
444 }
445 result := C.mysql_rollback(guard.conn)
446
447 if result != 0 {
448 throw_mysql_error_for_conn(guard.conn)!
449 }
450}
451
452// rollback_to rollbacks to a specified savepoint.
453pub fn (db &DB) rollback_to(savepoint string) ! {
454 if !savepoint.is_identifier() {
455 return error('savepoint should be a identifier string')
456 }
457 db.check_connection_is_established()!
458 result := db.exec_none('ROLLBACK TO SAVEPOINT ${savepoint}')
459 if result != 0 {
460 db.throw_mysql_error()!
461 }
462}
463
464// savepoint create a new savepoint.
465pub fn (db &DB) savepoint(savepoint string) ! {
466 if !savepoint.is_identifier() {
467 return error('savepoint should be a identifier string')
468 }
469 db.check_connection_is_established()!
470 result := db.exec_none('SAVEPOINT ${savepoint}')
471 if result != 0 {
472 db.throw_mysql_error()!
473 }
474}
475
476// release_savepoint releases a specified savepoint.
477pub fn (db &DB) release_savepoint(savepoint string) ! {
478 if !savepoint.is_identifier() {
479 return error('savepoint should be a identifier string')
480 }
481 db.check_connection_is_established()!
482 result := db.exec_none('RELEASE SAVEPOINT ${savepoint}')
483 if result != 0 {
484 db.throw_mysql_error()!
485 }
486}
487
488// tables returns a list of the names of the tables in the current database,
489// that match the simple regular expression specified by the `wildcard` parameter.
490// The `wildcard` parameter may contain the wildcard characters `%` or `_`.
491// If an empty string is passed, it will return all tables.
492// Calling `tables()` is similar to executing query `SHOW TABLES [LIKE wildcard]`.
493pub fn (db &DB) tables(wildcard string) ![]string {
494 mut guard := db.acquire_connection_guard()!
495 defer {
496 guard.release()
497 }
498 c_mysql_result := C.mysql_list_tables(guard.conn, wildcard.str)
499 if isnil(c_mysql_result) {
500 throw_mysql_error_for_conn(guard.conn)!
501 }
502
503 result := Result{c_mysql_result}
504 mut tables := []string{}
505
506 for row in result.rows() {
507 tables << row.vals[0]
508 }
509
510 return tables
511}
512
513// escape_string creates a legal SQL string for use in an SQL statement.
514// The `s` argument is encoded to produce an escaped SQL string,
515// taking into account the current character set of the connection.
516pub fn (db &DB) escape_string(s string) string {
517 conn := db.current_conn()
518 if isnil(conn) {
519 return ''
520 }
521 mut thread_guard := mysql_thread_guard() or { return '' }
522 defer {
523 thread_guard.release()
524 }
525 unsafe {
526 to := malloc_noscan(2 * s.len + 1)
527 C.mysql_real_escape_string(conn, to, s.str, s.len)
528 return to.vstring()
529 }
530}
531
532// set_option sets extra connect options that affect the behavior of
533// a connection. This function may be called multiple times to set several
534// options. To retrieve the current values for an option, use `get_option()`.
535pub fn (mut db DB) set_option(option_type int, val voidptr) {
536 conn := db.current_conn()
537 if isnil(conn) {
538 return
539 }
540 mut thread_guard := mysql_thread_guard() or { return }
541 defer {
542 thread_guard.release()
543 }
544 C.mysql_options(conn, option_type, val)
545}
546
547// get_option returns the value of an option, settable by `set_option`.
548// https://dev.mysql.com/doc/c-api/5.7/en/mysql-get-option.html
549pub fn (db &DB) get_option(option_type int) !voidptr {
550 mut guard := db.acquire_connection_guard()!
551 defer {
552 guard.release()
553 }
554 mysql_option := unsafe { nil }
555 if C.mysql_get_option(guard.conn, option_type, &mysql_option) != 0 {
556 throw_mysql_error_for_conn(guard.conn)!
557 }
558
559 return mysql_option
560}
561
562// refresh flush the tables or caches, or resets replication server
563// information. The connected user must have the `RELOAD` privilege.
564pub fn (mut db DB) refresh(options u32) !bool {
565 mut guard := db.acquire_connection_guard()!
566 defer {
567 guard.release()
568 }
569 if C.mysql_refresh(guard.conn, options) != 0 {
570 throw_mysql_error_for_conn(guard.conn)!
571 }
572
573 return true
574}
575
576// reset resets the connection, and clear the session state.
577pub fn (mut db DB) reset() ! {
578 mut guard := db.acquire_connection_guard()!
579 defer {
580 guard.release()
581 }
582 if C.mysql_reset_connection(guard.conn) != 0 {
583 throw_mysql_error_for_conn(guard.conn)!
584 }
585}
586
587// ping pings a server connection, or tries to reconnect if the connection
588// has gone down.
589pub fn (mut db DB) ping() !bool {
590 mut guard := db.acquire_connection_guard()!
591 defer {
592 guard.release()
593 }
594 if C.mysql_ping(guard.conn) != 0 {
595 throw_mysql_error_for_conn(guard.conn)!
596 }
597
598 return true
599}
600
601// validate pings a server connection, or tries to reconnect if the connection
602// has gone down.
603pub fn (mut db DB) validate() !bool {
604 return db.ping()!
605}
606
607// close closes the connection.
608pub fn (mut db DB) close() ! {
609 if isnil(db.state) {
610 if isnil(db.conn) {
611 return
612 }
613 mut thread_guard := mysql_thread_guard()!
614 defer {
615 thread_guard.release()
616 }
617 C.mysql_close(db.conn)
618 db.conn = unsafe { nil }
619 return
620 }
621 db.state.mutex.@lock()
622 defer {
623 db.state.mutex.unlock()
624 }
625 if isnil(db.state.conn) {
626 db.conn = unsafe { nil }
627 return
628 }
629 mut thread_guard := mysql_thread_guard()!
630 defer {
631 thread_guard.release()
632 }
633 C.mysql_close(db.state.conn)
634 db.state.conn = unsafe { nil }
635 db.conn = unsafe { nil }
636}
637
638// info returns information about the most recently executed query.
639// See more on https://dev.mysql.com/doc/c-api/8.0/en/mysql-info.html
640pub fn (db &DB) info() string {
641 mut guard := db.acquire_connection_guard() or { return '' }
642 defer {
643 guard.release()
644 }
645 return resolve_nil_str(C.mysql_info(guard.conn))
646}
647
648// get_host_info returns a string describing the type of connection in use,
649// including the server host name.
650pub fn (db &DB) get_host_info() string {
651 mut guard := db.acquire_connection_guard() or { return '' }
652 defer {
653 guard.release()
654 }
655 return unsafe { C.mysql_get_host_info(guard.conn).vstring() }
656}
657
658// get_server_info returns a string representing the MySQL server version.
659// For example, `8.0.24`.
660pub fn (db &DB) get_server_info() string {
661 mut guard := db.acquire_connection_guard() or { return '' }
662 defer {
663 guard.release()
664 }
665 return unsafe { C.mysql_get_server_info(guard.conn).vstring() }
666}
667
668// get_server_version returns an integer, representing the MySQL server
669// version. The value has the format `XYYZZ` where `X` is the major version,
670// `YY` is the release level (or minor version), and `ZZ` is the sub-version
671// within the release level. For example, `8.0.24` is returned as `80024`.
672pub fn (db &DB) get_server_version() u64 {
673 mut guard := db.acquire_connection_guard() or { return 0 }
674 defer {
675 guard.release()
676 }
677 return C.mysql_get_server_version(guard.conn)
678}
679
680// dump_debug_info instructs the server to write debugging information
681// to the error log. The connected user must have the `SUPER` privilege.
682pub fn (mut db DB) dump_debug_info() !bool {
683 mut guard := db.acquire_connection_guard()!
684 defer {
685 guard.release()
686 }
687 if C.mysql_dump_debug_info(guard.conn) != 0 {
688 throw_mysql_error_for_conn(guard.conn)!
689 }
690
691 return true
692}
693
694// get_client_info returns client version information as a string.
695pub fn get_client_info() string {
696 return unsafe { C.mysql_get_client_info().vstring() }
697}
698
699// get_client_version returns the client version information as an integer.
700pub fn get_client_version() u64 {
701 return C.mysql_get_client_version()
702}
703
704// debug does a `DBUG_PUSH` with the given string.
705// `debug()` uses the Fred Fish debug library.
706// To use this function, you must compile the client library to support debugging.
707// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-debug.html
708pub fn debug(debug string) {
709 C.mysql_debug(debug.str)
710}
711
712// exec executes the `query` on the given `db`, and returns an array of all the results, or an error on failure
713pub fn (db &DB) exec(query string) ![]Row {
714 mut guard := db.acquire_connection_guard()!
715 defer {
716 guard.release()
717 }
718 if C.mysql_query(guard.conn, query.str) != 0 {
719 throw_mysql_error_for_conn(guard.conn)!
720 }
721
722 result := C.mysql_store_result(guard.conn)
723 if result == unsafe { nil } {
724 return []Row{}
725 } else {
726 return Result{result}.rows()
727 }
728}
729
730// exec_one executes the `query` on the given `db`, and returns either the first row from the result, if the query was successful, or an error
731pub fn (db &DB) exec_one(query string) !Row {
732 mut guard := db.acquire_connection_guard()!
733 defer {
734 guard.release()
735 }
736 if C.mysql_query(guard.conn, query.str) != 0 {
737 throw_mysql_error_for_conn(guard.conn)!
738 }
739
740 result := C.mysql_store_result(guard.conn)
741
742 if result == unsafe { nil } {
743 throw_mysql_error_for_conn(guard.conn)!
744 }
745 row_vals := C.mysql_fetch_row(result)
746 num_cols := C.mysql_num_fields(result)
747
748 if row_vals == unsafe { nil } {
749 return Row{}
750 }
751
752 mut row := Row{}
753 for i in 0 .. num_cols {
754 if unsafe { row_vals[i] == nil } {
755 row.vals << ''
756 } else {
757 row.vals << mystring(unsafe { &u8(row_vals[i]) })
758 }
759 }
760
761 return row
762}
763
764// exec_none executes the `query` on the given `db`, and returns the integer MySQL result code
765// Use it, in case you don't expect any row results, but still want a result code.
766// e.g. for queries like these: INSERT INTO ... VALUES (...)
767pub fn (db &DB) exec_none(query string) int {
768 mut guard := db.acquire_connection_guard() or { return 1 }
769 defer {
770 guard.release()
771 }
772 C.mysql_query(guard.conn, query.str)
773
774 return get_errno(guard.conn)
775}
776
777// exec_param_many executes the `query` with parameters provided as `?`'s in the query
778// It returns either the full result set, or an error on failure
779pub fn (db &DB) exec_param_many(query string, params []string) ![]Row {
780 stmt := db.prepare(query)!
781 defer {
782 stmt.close()
783 }
784 rows := stmt.execute(params)!
785 return rows
786}
787
788// exec_param executes the `query` with one parameter provided as an `?` in the query
789// It returns either the full result set, or an error on failure
790pub fn (db &DB) exec_param(query string, param string) ![]Row {
791 return db.exec_param_many(query, [param])!
792}
793
794// exec_param2 executes the `query` with two parameters provided as `?` placeholders.
795pub fn (db &DB) exec_param2(query string, param string, param2 string) ![]Row {
796 return db.exec_param_many(query, [param, param2])!
797}
798
799// A StmtHandle is created through prepare, it will be bound
800// to one DB connection and will become unusable if the connection
801// is closed
802pub struct StmtHandle {
803 stmt &C.MYSQL_STMT = &C.MYSQL_STMT(unsafe { nil })
804 db DB
805}
806
807// prepare takes in a query string, returning a StmtHandle
808// that can then be used to execute the query as many times
809// as needed, which must be closed manually by the user
810// Placeholders are represented by `?`
811pub fn (db &DB) prepare(query string) !StmtHandle {
812 mut guard := db.acquire_connection_guard()!
813 defer {
814 guard.release()
815 }
816 stmt := C.mysql_stmt_init(guard.conn)
817 if stmt == unsafe { nil } {
818 throw_mysql_error_for_conn(guard.conn)!
819 }
820
821 mut code := C.mysql_stmt_prepare(stmt, query.str, query.len)
822 if code != 0 {
823 throw_mysql_stmt_error(stmt)!
824 }
825
826 return StmtHandle{
827 stmt: stmt
828 db: DB{
829 conn: db.current_conn()
830 state: db.state
831 }
832 }
833}
834
835// execute takes in an array of params that will be bound to the statement,
836// followed by it's execution
837// Returns an array of Rows, which will be empty if nothing is returned
838// from the query, or possibly an error value
839pub fn (stmt &StmtHandle) execute(params []string) ![]Row {
840 mut guard := stmt.db.acquire_connection_guard()!
841 defer {
842 guard.release()
843 }
844 mut bind_params := []C.MYSQL_BIND{}
845 for param in params {
846 bind := C.MYSQL_BIND{
847 buffer_type: mysql_type_string
848 buffer: param.str
849 buffer_length: u32(param.len)
850 length: 0
851 is_null: 0
852 }
853 bind_params << bind
854 }
855
856 mut response := C.mysql_stmt_bind_param(stmt.stmt, unsafe { &C.MYSQL_BIND(bind_params.data) })
857 if response == true {
858 throw_mysql_stmt_error(stmt.stmt)!
859 }
860
861 mut code := C.mysql_stmt_execute(stmt.stmt)
862 if code != 0 {
863 throw_mysql_stmt_error(stmt.stmt)!
864 }
865
866 query_metadata := C.mysql_stmt_result_metadata(stmt.stmt)
867 // If the query returns no metadata we have no data to return
868 // This happens in insert queries
869 if query_metadata == unsafe { nil } {
870 return []Row{}
871 }
872 num_cols := C.mysql_num_fields(query_metadata)
873 mut length := []u32{len: num_cols}
874 mut is_null := []bool{len: num_cols}
875
876 mut binds := []C.MYSQL_BIND{}
877 for i in 0 .. num_cols {
878 bind := C.MYSQL_BIND{
879 buffer_type: mysql_type_string
880 buffer: 0
881 buffer_length: 0
882 length: unsafe { &length[i] }
883 is_null: unsafe { &is_null[i] }
884 }
885 binds << bind
886 }
887
888 mut rows := []Row{}
889 response = C.mysql_stmt_bind_result(stmt.stmt, unsafe { &C.MYSQL_BIND(binds.data) })
890 for {
891 code = C.mysql_stmt_fetch(stmt.stmt)
892 if code == mysql_no_data {
893 break
894 }
895 lengths := length[0..num_cols].clone()
896 mut row := Row{}
897 for i in 0 .. num_cols {
898 l := lengths[i]
899 data := unsafe { malloc(l) }
900 binds[i].buffer = data
901 binds[i].buffer_length = l
902 code = C.mysql_stmt_fetch_column(stmt.stmt, unsafe { &binds[i] }, i, 0)
903 if *(binds[i].is_null) {
904 row.vals << ''
905 } else {
906 row.vals << unsafe { data.vstring() }
907 }
908 }
909 rows << row
910 }
911 return rows
912}
913
914// close acts on a StmtHandle to close the mysql Stmt
915// meaning it is no longer available for use
916pub fn (stmt &StmtHandle) close() {
917 mut thread_guard := mysql_thread_guard() or { return }
918 defer {
919 thread_guard.release()
920 }
921 C.mysql_stmt_close(stmt.stmt)
922}
923
924@[inline]
925fn (db &DB) throw_mysql_error() ! {
926 conn := db.current_conn()
927 if isnil(conn) {
928 return error(mysql_no_connection_error_message)
929 }
930 return error_with_code(get_error_msg(conn), get_errno(conn))
931}
932
933@[inline]
934fn throw_mysql_stmt_error(stmt &C.MYSQL_STMT) ! {
935 return error_with_code(get_stmt_error_msg(stmt), get_stmt_errno(stmt))
936}
937
938@[inline]
939fn throw_mysql_error_for_conn(conn &C.MYSQL) ! {
940 return error_with_code(get_error_msg(conn), get_errno(conn))
941}
942
943@[inline]
944fn mysql_thread_guard() !MySQLThreadGuard {
945 if C.mysql_thread_init() {
946 return error(mysql_thread_init_error_message)
947 }
948 return MySQLThreadGuard{
949 active: true
950 }
951}
952
953@[inline]
954fn (mut guard MySQLThreadGuard) release() {
955 if guard.active {
956 C.mysql_thread_end()
957 guard.active = false
958 }
959}
960
961@[inline]
962fn (db &DB) current_conn() &C.MYSQL {
963 if !isnil(db.state) {
964 return db.state.conn
965 }
966 return db.conn
967}
968
969fn (db &DB) acquire_connection_guard() !MySQLConnectionGuard {
970 if !isnil(db.state) {
971 db.state.mutex.@lock()
972 conn := db.state.conn
973 if isnil(conn) {
974 db.state.mutex.unlock()
975 return error(mysql_no_connection_error_message)
976 }
977 mut thread_guard := mysql_thread_guard() or {
978 db.state.mutex.unlock()
979 return err
980 }
981 return MySQLConnectionGuard{
982 conn: conn
983 state: db.state
984 thread: thread_guard
985 }
986 }
987 if isnil(db.conn) {
988 return error(mysql_no_connection_error_message)
989 }
990 mut thread_guard := mysql_thread_guard()!
991 return MySQLConnectionGuard{
992 conn: db.conn
993 thread: thread_guard
994 }
995}
996
997@[inline]
998fn (mut guard MySQLConnectionGuard) release() {
999 guard.thread.release()
1000 if !isnil(guard.state) {
1001 guard.state.mutex.unlock()
1002 }
1003}
1004
1005@[inline]
1006fn (db &DB) check_connection_is_established() ! {
1007 if isnil(db.current_conn()) {
1008 return error(mysql_no_connection_error_message)
1009 }
1010}
1011