Skip to content

Commit

Permalink
Merge pull request #4432 from Spxg/wasm-example
Browse files Browse the repository at this point in the history
Add a sqlite-wasm example
  • Loading branch information
weiznich authored Jan 20, 2025
2 parents 8f63147 + 1793051 commit 94d5a0d
Show file tree
Hide file tree
Showing 12 changed files with 417 additions and 1 deletion.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ members = [
"examples/sqlite/getting_started_step_1",
"examples/sqlite/getting_started_step_2",
"examples/sqlite/getting_started_step_3",
"examples/sqlite/relations", "xtask",
"examples/sqlite/relations",
"examples/sqlite/wasm",
"xtask",
]

[workspace.package]
Expand Down
1 change: 1 addition & 0 deletions examples/sqlite/wasm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pkg
16 changes: 16 additions & 0 deletions examples/sqlite/wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "sqlite-wasm-example"
version = "0.1.0"
edition.workspace = true
publish = false

[dependencies]
diesel = { path = "../../../diesel", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }
diesel_migrations = { path = "../../../diesel_migrations", features = ["sqlite"] }
serde = { version = "1.0.217", features = ["derive"] }
serde-wasm-bindgen = "0.6.5"
wasm-bindgen = "0.2.99"
wasm-bindgen-futures = "0.4.49"

[lib]
crate-type = ["cdylib"]
23 changes: 23 additions & 0 deletions examples/sqlite/wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# `rs-diesel-sqlite`

Diesel's `Getting Started` guide using SQLite instead of Postgresql

## Usage

Compile wasm and start the web server:

```
rustup target add wasm32-unknown-unknown
# Add wasm32-unknown-unknown toolchain
cargo install wasm-pack
# Install the wasm-pack toolchain
wasm-pack build --target web
# Build wasm
python3 server.py
# Start server
```

Next, try it on the web page: [on the web page](http://localhost:8000)
108 changes: 108 additions & 0 deletions examples/sqlite/wasm/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>diesel sqlite-wasm example</title>
</head>

<body>
<label for="vfs">choose vfs:</label>
<select id="vfs">
<option value="0">memory</option>
<option value="1">opfs</option>
<option value="2">opfs-sahpool</option>
</select>
<button id="SwitchVFS">Switch VFS</button>
<p></p>

<button id="ShowButton">Show 5 Published Posts</button>
<p id="show_result"></p>

<input id="create_title" type="string" placeholder="Enter title" />
<input id="create_body" type="string" placeholder="Enter body">
<button id="CreateButton">CreatePost</button>
<p id="create_result"></p>

<input id="get_post_id" type="number" placeholder="Enter PostId" />
<button id="GetButton">GetPost</button>
<p id="get_result"></p>

<input id="publish_post_id" type="number" placeholder="Enter PostId" />
<button id="PublishButton">PublishPost</button>
<p id="publish_result"></p>

<input id="delete_post_title" type="string" placeholder="Enter PostTitle" />
<button id="DeleteButton">DeletePost</button>
<p id="delete_result"></p>

<script type="module">
function call(payload) {
worker.postMessage(payload);
}

async function run() {
document.getElementById('SwitchVFS').onclick = () => {
const id = parseInt(document.getElementById('vfs').value);
call({ cmd: "switch_vfs", id: id })
};

document.getElementById('ShowButton').onclick = () => {
call({ cmd: "show_posts" })
};

document.getElementById('CreateButton').onclick = () => {
const title = document.getElementById('create_title').value;
const body = document.getElementById('create_body').value;
call({ cmd: "create_post", title: title, body: body })
};

document.getElementById('GetButton').onclick = () => {
const post_id = parseInt(document.getElementById('get_post_id').value);
call({ cmd: "get_post", post_id: post_id })
};

document.getElementById('PublishButton').onclick = () => {
const post_id = parseInt(document.getElementById('publish_post_id').value);
call({ cmd: "publish_post", post_id: post_id })
};

document.getElementById('DeleteButton').onclick = () => {
const title = document.getElementById('delete_post_title').value;
call({ cmd: "delete_post", title: title })
};
}

const worker = new Worker('worker.js', { type: 'module' });
worker.onmessage = function (event) {
run();
worker.onmessage = function (event) {
const payload = event.data;
switch (payload.cmd) {
case 'show_posts':
var posts = payload.posts;
document.getElementById('show_result').innerText = JSON.stringify(posts);
break;
case 'create_post':
var post = payload.post;
document.getElementById('create_result').innerText = JSON.stringify(post);
break;
case 'get_post':
var post = payload.post;
document.getElementById('get_result').innerText = JSON.stringify(post);
break;
case 'publish_post':
document.getElementById('publish_result').innerText = 'Publish done.';
break;
case 'delete_post':
document.getElementById('delete_result').innerText = 'Delete done.';
break;
default:
break;
};
}
}
</script>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE posts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE posts (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
title VARCHAR NOT NULL,
body TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT 0
)
12 changes: 12 additions & 0 deletions examples/sqlite/wasm/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env python3
from http.server import HTTPServer, SimpleHTTPRequestHandler, test
import sys

class RequestHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
SimpleHTTPRequestHandler.end_headers(self)

if __name__ == '__main__':
test(RequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000)
145 changes: 145 additions & 0 deletions examples/sqlite/wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
pub mod models;
pub mod schema;

use std::sync::Mutex;
use std::sync::Once;

use crate::models::{NewPost, Post};
use diesel::prelude::*;
use diesel_migrations::embed_migrations;
use diesel_migrations::EmbeddedMigrations;
use diesel_migrations::MigrationHarness;
use wasm_bindgen::prelude::*;

const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");

#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}

// Next let's define a macro that's like `println!`, only it works for
// `console.log`. Note that `println!` doesn't actually work on the Wasm target
// because the standard library currently just eats all output. To get
// `println!`-like behavior in your app you'll likely want a macro like this.
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}

static VFS: Mutex<(i32, Once)> = Mutex::new((0, Once::new()));

pub fn establish_connection() -> SqliteConnection {
let (vfs, once) = &*VFS.lock().unwrap();
let url = match vfs {
0 => "post.db",
1 => "file:post.db?vfs=opfs",
2 => "file:post.db?vfs=opfs-sahpool",
_ => unreachable!(),
};
let mut conn =
SqliteConnection::establish(url).unwrap_or_else(|_| panic!("Error connecting to post.db"));
once.call_once(|| {
conn.run_pending_migrations(MIGRATIONS).unwrap();
});
conn
}

#[cfg(all(target_family = "wasm", target_os = "unknown"))]
#[wasm_bindgen]
pub async fn init_sqlite() {
let sqlite = diesel::init_sqlite().await.unwrap();
sqlite.install_opfs_sahpool(None).await.unwrap();
}

#[wasm_bindgen]
pub fn switch_vfs(id: i32) {
*VFS.lock().unwrap() = (id, Once::new());
}

#[wasm_bindgen]
pub fn create_post(title: &str, body: &str) -> JsValue {
use crate::schema::posts;

let new_post = NewPost { title, body };

let post = diesel::insert_into(posts::table)
.values(&new_post)
.returning(Post::as_returning())
.get_result(&mut establish_connection())
.expect("Error saving new post");

serde_wasm_bindgen::to_value(&post).unwrap()
}

#[wasm_bindgen]
pub fn delete_post(pattern: &str) {
let connection = &mut establish_connection();
let num_deleted = diesel::delete(
schema::posts::dsl::posts.filter(schema::posts::title.like(pattern.to_string())),
)
.execute(connection)
.expect("Error deleting posts");

console_log!("Deleted {num_deleted} posts");
}

#[wasm_bindgen]
pub fn get_post(post_id: i32) -> JsValue {
use schema::posts::dsl::posts;

let connection = &mut establish_connection();

let post = posts
.find(post_id)
.select(Post::as_select())
.first(connection)
.optional(); // This allows for returning an Option<Post>, otherwise it will throw an error

match &post {
Ok(Some(post)) => console_log!("Post with id: {} has a title: {}", post.id, post.title),
Ok(None) => console_log!("Unable to find post {}", post_id),
Err(_) => console_log!("An error occurred while fetching post {}", post_id),
}
serde_wasm_bindgen::to_value(&post.ok().flatten()).unwrap()
}

#[wasm_bindgen]
pub fn publish_post(id: i32) {
let connection = &mut establish_connection();

let post = diesel::update(schema::posts::dsl::posts.find(id))
.set(schema::posts::dsl::published.eq(true))
.returning(Post::as_returning())
.get_result(connection)
.unwrap();

console_log!("Published post {}", post.title);
}

#[wasm_bindgen]
pub fn show_posts() -> Vec<JsValue> {
let connection = &mut establish_connection();
let results = schema::posts::dsl::posts
.filter(schema::posts::dsl::published.eq(true))
.limit(5)
.select(Post::as_select())
.load(connection)
.expect("Error loading posts");

console_log!("Displaying {} posts", results.len());
for post in &results {
console_log!("{}", post.title);
console_log!("----------\n");
console_log!("{}", post.body);
}

results
.into_iter()
.map(|x| serde_wasm_bindgen::to_value(&x).unwrap())
.collect()
}
20 changes: 20 additions & 0 deletions examples/sqlite/wasm/src/models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use super::schema::posts;
use diesel::prelude::*;
use serde::Serialize;

#[derive(Queryable, Selectable, Serialize)]
#[diesel(table_name = posts)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct Post {
pub id: i32,
pub title: String,
pub body: String,
pub published: bool,
}

#[derive(Insertable)]
#[diesel(table_name = posts)]
pub struct NewPost<'a> {
pub title: &'a str,
pub body: &'a str,
}
10 changes: 10 additions & 0 deletions examples/sqlite/wasm/src/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @generated automatically by Diesel CLI.

diesel::table! {
posts (id) {
id -> Integer,
title -> Text,
body -> Text,
published -> Bool,
}
}
Loading

0 comments on commit 94d5a0d

Please sign in to comment.