-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
feat(sqlite): add preupdate hook #3625
base: main
Are you sure you want to change the base?
Conversation
d9eb625
to
5264ada
Compare
436694b
to
03d9a3f
Compare
03d9a3f
to
bcdb609
Compare
bcdb609
to
7f1fdd9
Compare
sqlx-sqlite/src/connection/mod.rs
Outdated
pub fn get_old_column_value(&self, i: i32) -> Result<SqliteValue, Error> { | ||
let mut p_value: *mut sqlite3_value = ptr::null_mut(); | ||
unsafe { | ||
let ret = sqlite3_preupdate_old(self.db, i, &mut p_value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sqlite3_value
returned by this call and sqlite3_preupdate_new
has weird semantics. It's a "protected" value so it's thread-safe, but at the same time the documentation also specifies:
The sqlite3_value that P points to will be destroyed when the preupdate callback returns.
We should handle this as a SqliteValueRef
instead, using the same lifetime. The user can then use .to_owned()
to get a fully independent value if they need it.
You'll need to add a new case here:
Line 19 in 1678b19
Value(&'r SqliteValue), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also check i
against sqlite3_preupdate_count
because the result is undefined if the index is out of bounds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should handle this as a SqliteValueRef instead, using the same lifetime. The user can then use .to_owned() to get a fully independent value if they need it.
SqliteValue::new
calls sqlite3_value_dup
which creates a copy of the sqlite3_value
. I tested this out by storing the returned SqliteValue
in a mutex and ensuring the returned value could still be decoded properly after the callback was completed.
I can add something to SqliteValueRef
for this to avoid the call to sqlite3_value_dup
, but I think that would require re-implementing a lot of the logic in SqliteValue
or modifying it so that it can operate on both an owned or borrowed value. Let me know if I'm missing something.
EDIT: added this in a separate commit. I can revert it if this isn't what you had in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also check i against sqlite3_preupdate_count because the result is undefined if the index is out of bounds.
I think the documentation might be a bit conservative here (or they don't want to make any guarantees) because it does check for these conditions and return an error, but I went ahead and added an explicit check here too.
d1a3a97
to
c90b843
Compare
c90b843
to
8da5e6b
Compare
pub database: &'a str, | ||
pub table: &'a str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not really comfortable having these public fields being the only thing anchoring the 'a
lifetime in this type. It seems like the user could replace these with &'static str
and somehow make the whole type PreupdateHookResult<'static>
and thus get SqliteValueRef<'static>
out of it, allowing a use-after-free.
I don't think this is actually possible to exploit in the language currently, but I could see the compiler accepting it in the future since it technically should be allowed.
It also just doesn't really document the fact that these strings aren't the only thing that are invalidated after the callback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I added a PhantomData
field for an additional lifetime check that's independent of the strings.
Adds bindings for SQLite's preupdate hook.
This is exposed as a separate feature because the system SQLite version generally does not have this flag enabled, so using it with
sqlite-unbundled
may cause linker errors.If we don't want to create a new feature flag in the main crate just for this, we could enable it by default with the
sqlite
(bundled) feature only. That would make the configuration a little simpler.