-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prepared Statements: Ruby support #105
Comments
So now that I think about it, I'm actually surprised this doesn't cause race conditions. Another thought I had, is that it would be worth looking at what SQLite and Postgres client libraries do. MySQL may not be using the best solution here. |
Hm, yeah taking a look again: we send a Will investigate the other drivers 👍 |
Okay, from the SQLite source: // sqlite3.c
** CAPI3REF: Closing A Database Connection
** DESTRUCTOR: sqlite3
**
** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors
** for the [sqlite3] object.
**
** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if
** the [sqlite3] object is successfully destroyed and all associated
** resources are deallocated.
** Ideally, applications should [sqlite3_finalize | finalize] all
** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and
** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated
** with the [sqlite3] object prior to attempting to close the object.
** ^If the database connection is associated with unfinalized prepared
** statements, BLOB handlers, and/or unfinished sqlite3_backup objects then
** sqlite3_close() will leave the database connection open and return
** [SQLITE_BUSY]. So rather than MySQL's flow of:
SQLite does the following:
Interestingly, the SQLite documentation for the close routine also says this: The sqlite3_close_v2() interface
^If sqlite3_close_v2() is called with unfinalized prepared
** statements, unclosed BLOB handlers, and/or unfinished sqlite3_backups,
** it returns [SQLITE_OK] regardless, but instead of deallocating the database
** connection immediately, it marks the database connection as an unusable
** "zombie" and makes arrangements to automatically deallocate the database
** connection after all prepared statements are finalized, all BLOB handles
** are closed, and all backups have finished. The sqlite3_close_v2() interface
** is intended for use with host languages that are garbage collected, and
** where the order in which destructors are called is arbitrary. So although |
PostgreSQL, OTOH, doesn't even support closing prepared statements in the client:
(https://www.postgresql.org/docs/current/libpq-exec.html under |
Ref: trilogy-libraries#105 To close a prepared statement you need to have access to the connection that created it. But in managed languages like Ruby, the obvious thing to do is to close the prepared statement when the associated object is garbage collected. But the order in which objects are garbaged collected is never guaranteed so when freeing a statement the connection might have been freed already and it's hard to detect. We checked how libmysqlclient handles it, and each `MYSQL_STMT` has a reference to its `MYSQL` (connection), and the connection keeps a doubly linked list of the statements it created. When a statement is closed it's removed from the list, when the connection is closed, all the connection references are set to NULL. We implemented exactly the same logic here. Additionally, prepared statement can only be used with the connection they were created from. As such having all the `trilogy_stmt_*` function take a connection isn't great for usability. So this change opens the door to only taking a `trilogy_stmt_t *`. Co-Authored-By: Adrianna Chang <[email protected]>
Ref: trilogy-libraries#105 To close a prepared statement you need to have access to the connection that created it. But in managed languages like Ruby, the obvious thing to do is to close the prepared statement when the associated object is garbage collected. But the order in which objects are garbaged collected is never guaranteed so when freeing a statement the connection might have been freed already and it's hard to detect. We checked how libmysqlclient handles it, and each `MYSQL_STMT` has a reference to its `MYSQL` (connection), and the connection keeps a doubly linked list of the statements it created. When a statement is closed it's removed from the list, when the connection is closed, all the connection references are set to NULL. We implemented exactly the same logic here. Additionally, prepared statement can only be used with the connection they were created from. As such having all the `trilogy_stmt_*` function take a connection isn't great for usability. So this change opens the door to only taking a `trilogy_stmt_t *`. Co-Authored-By: Adrianna Chang <[email protected]>
This PR should handle the blockers described in this issue. I started this branch to work on the Ruby bindings, but unfortunately will have to shelve this work until I have more time to dedicate to it. Happy to chat if anyone wants to take it over. |
We shipped the C API for prepared statements in #3. The next step is to add support to the Ruby bindings.
API
I imagine we'll want something similar to Mysql2's implementation: a
Trilogy#prepare
method that returns aTrilogy::Statement
object, and aTrilogy::Statement#execute
method that can be used to execute a PS with given parameters, e.g.:Trilogy::Statement
should, at a minimum, expose the underlying data values from the C API: the statement id, column count, parameter count, and warning count.Mysql2::Statement
exposes things likeaffected_rows
,fields
,last_id
, etc. that we may eventually wantTrilogy::Statement
to expose as well.Blockers
@byroot and I began working on this and ran into some issues while fleshing out the Ruby class that will wrap the C prepared statement struct (
trilogy_stmt_t
). As Mysql2 does, we'll need to close the prepared statement when freeing the RubyTrilogy::Statement
object:https://github.com/brianmario/mysql2/blob/6cf5e1d732b4a6ee5485def98637f100cbc86f1b/ext/mysql2/statement.c#L26-L29
https://github.com/brianmario/mysql2/blob/master/ext/mysql2/statement.c#L75
https://github.com/brianmario/mysql2/blob/master/ext/mysql2/statement.c#L65
trilogy_stmt_close
needs the stmt and the connection:trilogy/src/blocking.c
Lines 366 to 368 in 61a8b40
We need the connection to send
TRILOGY_CMD_STMT_CLOSE
-- so we can create an intermediate struct (that will be wrapped intoTrilogy::Statement
) that encapsulates bothtrilogy_stmt_t
and the connection. The problem arises if the connection ends up being closed / freed before we attempt to close the prepared statement / free its memory. In this case, attempting to access the connection on the statement wrapper will raise anEXC_BAD_ACCESS
exception.The solution is likely to do something similar to what the native SQL client library does -- it stores a linked list of all prepared statements on the client itself, and then when the client is closed, it "detaches" the statement list by clearing the connection pointer of every statement. That way, we can no-op in
trilogy_stmt_close
if the connection is gone, and avoid accessing memory for a conn that's already been freed.If anyone else has opinions on this though, would love to hear!
I've started a branch here for the Ruby bindings, but it's still very much a WIP: https://github.com/trilogy-libraries/trilogy/compare/main...adrianna-chang-shopify:trilogy:ac-prepared-statements-ruby?expand=1
The text was updated successfully, but these errors were encountered: