-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Add wasm32-unknown-unknown target support for sqlite #4411
Conversation
nice, this seems to be cleaner than re-implementing the backend in a separate crate, and maybe better than storing them on function pointers like in the discussion? How would SQLite updates work with this. Further, it would be most efficient to implement a VFS in rust rather than go to JS and back, would this wrapper crate allow for that from diesel? |
The current implementation of It would be most efficient if a However, I know very little about VFS used by the web, such as OPFS. The opfs used by In short, if there is a |
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.
Thanks for opening this PR. Overall I'm very much in favor of this approach as it looks really clean in terms of code changes. I've added two questions about some details.
That written: I personally would prefer to run at least the tests in CI to ensure that this does not break in the future.
As for the VFS discussion: I can imaging that diesel exposes the necessary traits to implement a custom VFS. A quick search on crates.io brought up https://docs.rs/sqlite-vfs/latest/sqlite_vfs/, which might serve as basis for such an implementation.
diesel/src/lib.rs
Outdated
// Before using C-API, you must initialize sqlite. | ||
// | ||
// Initializing the database is a one-time operation during | ||
// the life of the program. | ||
#[cfg(feature = "sqlite")] | ||
#[cfg(all(target_family = "wasm", not(target_os = "wasi")))] | ||
pub use sqlite_wasm_rs::init_sqlite; |
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 would rather not expose this function from diesel as this would couple diesel's API stability to that one of sqlite_wasm_rs
. Ideally we would just call that function internally one, or even better sqlite_wasm_rs
would provide a singelton helper function to do this.
We do something similar to this for the mysql backend already:
diesel/diesel/src/mysql/connection/raw.rs
Lines 251 to 277 in 3f69aa4
/// > In a non-multi-threaded environment, `mysql_init()` invokes | |
/// > `mysql_library_init()` automatically as necessary. However, | |
/// > `mysql_library_init()` is not thread-safe in a multi-threaded environment, | |
/// > and thus neither is `mysql_init()`. Before calling `mysql_init()`, either | |
/// > call `mysql_library_init()` prior to spawning any threads, or use a mutex | |
/// > to protect the `mysql_library_init()` call. This should be done prior to | |
/// > any other client library call. | |
/// | |
/// <https://dev.mysql.com/doc/c-api/8.4/en/mysql-init.html> | |
static MYSQL_THREAD_UNSAFE_INIT: Once = Once::new(); | |
fn perform_thread_unsafe_library_initialization() { | |
MYSQL_THREAD_UNSAFE_INIT.call_once(|| { | |
// mysql_library_init is defined by `#define mysql_library_init mysql_server_init` | |
// which isn't picked up by bindgen | |
let error_code = unsafe { ffi::mysql_server_init(0, ptr::null_mut(), ptr::null_mut()) }; | |
if error_code != 0 { | |
// FIXME: This is documented as Nonzero if an error occurred. | |
// Presumably the value has some sort of meaning that we should | |
// reflect in this message. We are going to panic instead of return | |
// an error here, since the documentation does not indicate whether | |
// it is safe to call this function twice if the first call failed, | |
// so I will assume it is not. | |
panic!("Unable to perform MySQL global initialization"); | |
} | |
}) | |
} |
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.
Ideally we would just call that function internally one, or even better
sqlite_wasm_rs
would provide a singelton helper function to do this.
Yes, the best way is to implement a singleton function call internally. But unlike mysql_server_init
, init_sqlite
is asynchronous, and since the diesel
function is called synchronously, there is no opportunity to execute init_sqlite
.
Any good suggestions for this?
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.
if this is exclusively for the web, you can make some assumptions about the environment and spawn the init
future onto the web executor rather than immediately wait for it. I think this makes sense, and maybe you can even rename init_sqlite
to init_sqlite_web_lazy
. The function should return a handle to the spawned future to allow awaiting it to completion.
Diesel can store this future wherever it likes, like in another OnceCell. Then maybe add a new async fn to diesel to finish waiting for the spawned future handle. This should decouple the api and let the user wait for sqlite init.
Ideally we could avoid offloading the init
to the user, but since diesel is pretty much 100% synchronous not sure how that could work
https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html
happy to make a PR if this is something that would be acceptable!
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.
spawn the
init
future onto the web executor rather than immediately wait for it.
Spawn init_sqlite
means that init_sqlite
may not be called before sqlite ffi is called and wasm_bindgen_future::spawn
relies on event loop
, execution needs to wait until the main thread is idle.
Anyway this may cause the call to sqlite ffi to fail.
As you said, since diesel
is now fully synchronous, there is no way to initialize sqlite in diesel.
I think there are only three ways to do it, called by the user:
- export
sqlite_wasm_rs::init_sqlite
- Wrapper an
async
init function indiesel
and callsqlite_wasm_rs::init_sqlite
diesel
does not provide anyinit_sqlite
method, and the user addsqlite-wasm-rs
dependency somewhere and executessqlite_wasm_rs::init_sqlite
. As long as there is only one version ofsqlite-wasm-rs
in a.wasm
it is acceptable.
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.
As written above: My main concern for just reexporting this function is the API stability of the sqlite_wasm_rs
crate. I'm not sure who is in charge of maintaining it, so I'm also not sure how future updates are planed.
For diesel it would be problematic if some of the functions used by diesel change between versions. If that's not planed it's even fine to issue new major version releases of this crate, as diesel could just support a version range then. If one of the functions used by diesel changes between versions it is essentially impossible for diesel to support the new sqlite_wasm_rs
version without breaking its (diesels) semver promise as this exported function is then part of the public API. The other problematic point is: Even if we don't reexport that function the same public dependency relation ship would exist due to being a essentially a -sys
crate. After all you need to call this function for the right version of sqlite_wasm_rs
, right?
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 other problematic point is: Even if we don't reexport that function the same public dependency relation ship would exist due to being a essentially a -sys crate. After all you need to call this function for the right version of sqlite_wasm_rs, right?
Is this problematic only in the case of using sqlite_wasm_rs
and diesel
in the same project, or even if there is a mysql-like
init?
FWIW its not impossible to make this sync, although it is more involved:
Looking through sqlite/sqlite-wasm
code for sqlite3InitModule
1.) sqlite/sqlite-wasm
offers a way to pass an already-instantiated wasm module to sqlite3InitModule
here
2.) Initializing a WebAssembly module by-default is async, but it can be done syncronously, and we can do it from rust and pass it to sqlite/sqlite-wasm
from rust, using the InitOpts object. This InitOpts object is the same exact way sqlite-web-rs controls the same params, so I could also try it out there and report back on how it effects things
3.) Create an init that mirrors the mysql_server_init
- may require minor modifications/PR to
sqlite/sqlite-wasm
repo andsqlite3InitModule
Instantiating the WASM module sync should be fine in this case b/c using OPFS needs to be done in a dedicated worker anyway, so it won't block the main thread.
That should handle the async part of sqlite3InitModule
, which I think is the bulk of it but maybe I'm missing a promise somewhere in that file.
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.
As written above: My main concern for just reexporting this function is the API stability of the
sqlite_wasm_rs
crate. I'm not sure who is in charge of maintaining it, so I'm also not sure how future updates are planed.
sqlite-wasm-rs
is maintained by me, and its purpose is to wrap sqlite-wasm
and provide the same C-Like
API as libsqlite3-sys
.
- About the C-Like API, since the SQLite C API is stable, there will be no breaking change in this C-Like API.
- About
init_sqlite
, sincesqlite.wasm
must be initialized,init_sqlite
is also stable. Initialization is now an asynchronousOnceCell
. If there is a synchronous initialization method later,init_sqlite
still works, and the initialization can be called internally (when calling the C-Like API).
So there will be no breaking change in the future. The only problem is that if diesel use a new SQLite C API when developing new features, but sqlite-wasm-rs
has not yet been wrapped, it will cause compilation failure.
Fortunately, the cost of wrapping a new API is not high, and I can support it quickly.
I agree with you, but in order to solve the differences between native and wasm tests, I created the In addition, some changes are required to run rustdoc tests |
The VFS Implementation is only interesting for web because of the variety of ways to accomplish a VFS, the immaturity of OPFS vs IndexedDb, as well as the newness of SQLite Wasm build itself. Therefore (and maybe my opinion only) it is useful to expose methods for a flexible VFS implementation rather than forcing a specific implementation lower in the stack. At least, until standards and best practices are formed. Off the top, certain VFS implementations may play better with multiple open tabs to the same db, for instance, while other may completely disallow that kind of behavior. I'm satisfied with Weiznich's suggestion, though, didn't even know about that crate! An API to simply register the vfs is all that would be needed |
I would prefer to handle this via
I think it would be fine to just run unit and integration tests and skip the doc tests on this target. |
54027a0
to
c47f627
Compare
I'm ready for review.
The test has been successfully run in CI, but some CIs failed unexpectedly. I am not sure if it was caused by my changes. |
The CI failures are unrelated. They are caused by the new rust release and some changes to the CI runners. I've filled #4428 to address that. You likely need to rebase this PR after that one is merged. I will do another round of reviews later today. |
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.
Thanks for the update. Overall I think we are nearly there. I've noted a few minor organizational things that shouldn't be too hard to fix.
I'm also not happy with the test attribute crate name, sorry about that but I would really like to see a better/longer name there.
Beside these specific comments I think it would be great to have:
- A short documentation entry in the sqlite connection documentation that states
wasm32
is supported, outlines that you need to call the init function + contains some details about the vfs choices - A changelog entry as this is a rather large new feature
I totally agree with using a longer and clearer test attribute crate name. I also think diesel_test_helper needs to be in the root of the workspace, and it's a bit strange to put it in the diesel_tests dir, since the diesel crate also uses it. Thanks for taking the time to review, I will fix these comments tonight. |
All comments fixed, additional changes:
|
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.
Thanks for the update and thanks again for working on this ❤️
I think it's now ready to be merged. I've adjusted some documentation comments slightly to better fit the over all documentation format. I will just apply the suggestions for that and merge the PR afterwards, so no additional work from your side is required here.
Love it! Thanks for your work on this, with the next diesel release it should make sqlite-web-rs redundant since everything is provided in mainline diesel, assuming the sqlite-wasm-rs is kept up to date ! Excited to start experimenting with different rust-native VFS's! |
I recently created a rust wrapper crate for sqlite-wasm and now diesel can run successfully on the
wasm32-unknown-unknown
platform.Diesel’s integration tests and unit tests all run successfully,except for a few tests that required
std::fs::*
and doc test (wasm-bindgen-test currently does not support this).Branch: https://github.com/Spxg/diesel/tree/wasm32-test (There are many changes, not planning to merge them at the moment).
You will see output similar to this: https://raw.githubusercontent.com/Spxg/Spxg/refs/heads/master/resources/sqlitest.gif