Skip to content
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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

aschey
Copy link
Contributor

@aschey aschey commented Dec 4, 2024

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.

@aschey aschey force-pushed the feat/preupdate_hook branch from d9eb625 to 5264ada Compare December 4, 2024 04:59
@aschey aschey force-pushed the feat/preupdate_hook branch 3 times, most recently from 436694b to 03d9a3f Compare December 4, 2024 07:36
@aschey aschey marked this pull request as draft December 4, 2024 16:24
@aschey aschey force-pushed the feat/preupdate_hook branch from 03d9a3f to bcdb609 Compare December 5, 2024 02:01
@aschey aschey force-pushed the feat/preupdate_hook branch from bcdb609 to 7f1fdd9 Compare December 5, 2024 02:09
@aschey aschey marked this pull request as ready for review December 5, 2024 02:24
Cargo.toml Show resolved Hide resolved
sqlx-sqlite/src/connection/mod.rs Outdated Show resolved Hide resolved
sqlx-sqlite/src/connection/mod.rs Outdated Show resolved Hide resolved
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);
Copy link
Collaborator

@abonander abonander Dec 13, 2024

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:

Value(&'r SqliteValue),

Copy link
Collaborator

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.

Copy link
Contributor Author

@aschey aschey Dec 13, 2024

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.

Copy link
Contributor Author

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.

sqlx-sqlite/src/connection/mod.rs Outdated Show resolved Hide resolved
sqlx-sqlite/src/connection/establish.rs Show resolved Hide resolved
@aschey aschey requested a review from abonander December 13, 2024 06:40
@aschey aschey force-pushed the feat/preupdate_hook branch 2 times, most recently from d1a3a97 to c90b843 Compare December 14, 2024 19:12
@aschey aschey force-pushed the feat/preupdate_hook branch from c90b843 to 8da5e6b Compare December 14, 2024 19:20
Comment on lines +36 to +37
pub database: &'a str,
pub table: &'a str,
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

sqlx-sqlite/src/connection/establish.rs Show resolved Hide resolved
@aschey aschey requested a review from abonander December 28, 2024 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants