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

sqlx: initial commit w/ todos example #1759

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
564 changes: 551 additions & 13 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ members = [
"libsql-wal",
"libsql-storage",
"libsql-storage-server",
"sqlx-libsql",
"sqlx-libsql/examples/todos"
]

exclude = [
Expand Down
6 changes: 6 additions & 0 deletions libsql/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,9 @@ impl Connection {
self.conn.load_extension(dylib_path.as_ref(), entry_point)
}
}

impl fmt::Debug for Connection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Connection").finish()
}
}
12 changes: 12 additions & 0 deletions sqlx-libsql/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "sqlx-libsql"
version = "0.1.0"
edition = "2021"

[dependencies]
libsql = { version = "0.6", path = "../libsql" }
sqlx-core = "=0.8.2"
futures-core = "0.3"
futures-util = "0.3"
log = "0.4"
async-stream = "0.3"
15 changes: 15 additions & 0 deletions sqlx-libsql/examples/todos/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "sqlx-example-sqlite-todos"
version = "0.1.0"
edition = "2021"
publish = false
workspace = "../../../"

[dependencies]
anyhow = "1.0"
futures = "0.3"
sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-native-tls" ] }
sqlx-libsql = { path = "../.." }
clap = { version = "4", features = ["derive"] }
tokio = { version = "1.20.0", features = ["rt", "macros"]}
tokio-stream = "0.1"
41 changes: 41 additions & 0 deletions sqlx-libsql/examples/todos/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# TODOs Example

## Setup

1. Declare the database URL

```
export DATABASE_URL="sqlite:todos.db"
```

2. Create the database.

```
$ sqlx db create
```

3. Run sql migrations

```
$ sqlx migrate run
```

## Usage

Add a todo

```
cargo run -- add "todo description"
```

Complete a todo.

```
cargo run -- done <todo id>
```

List all todos

```
cargo run
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS todos
(
id INTEGER PRIMARY KEY NOT NULL,
description TEXT NOT NULL,
done BOOLEAN NOT NULL DEFAULT 0
);
113 changes: 113 additions & 0 deletions sqlx-libsql/examples/todos/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use clap::{Parser, Subcommand};
use sqlx_libsql::LibsqlPool;
use std::env;

#[derive(Parser)]
struct Args {
#[command(subcommand)]
cmd: Option<Command>,
}

#[derive(Subcommand)]
enum Command {
Add { description: String },
Done { id: i64 },
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let pool = LibsqlPool::connect(&env::var("DATABASE_URL")?).await?;

// Migrations currently do not work, so we must do them manually
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS todos
(
id INTEGER PRIMARY KEY NOT NULL,
description TEXT NOT NULL,
done BOOLEAN NOT NULL DEFAULT 0
);
"#,
)
.execute(&pool)
.await?;

match args.cmd {
Some(Command::Add { description }) => {
println!("Adding new todo with description '{description}'");
let todo_id = add_todo(&pool, description).await?;
println!("Added new todo with id {todo_id}");
}
Some(Command::Done { id }) => {
println!("Marking todo {id} as done");
if complete_todo(&pool, id).await? {
println!("Todo {id} is marked as done");
} else {
println!("Invalid id {id}");
}
}
None => {
println!("Printing list of all todos");
list_todos(&pool).await?;
}
}

Ok(())
}

async fn add_todo(pool: &LibsqlPool, description: String) -> anyhow::Result<i64> {
let mut conn = pool.acquire().await?;

// Insert the task, then obtain the ID of this row
let id = sqlx::query(
r#"
INSERT INTO todos ( description )
VALUES ( ?1 )
"#,
)
.bind(description)
.execute(&mut *conn)
.await?
.last_insert_rowid();

Ok(id)
}

async fn complete_todo(pool: &LibsqlPool, id: i64) -> anyhow::Result<bool> {
let rows_affected = sqlx::query(
r#"
UPDATE todos
SET done = TRUE
WHERE id = ?1
"#,
)
.bind(id)
.execute(pool)
.await?
.rows_affected();

Ok(rows_affected > 0)
}

async fn list_todos(pool: &LibsqlPool) -> anyhow::Result<()> {
let recs = sqlx::query(
r#"
SELECT id, description, done
FROM todos
ORDER BY id
"#,
)
.fetch_all(pool)
.await?;

for rec in recs {
let done = rec.get::<bool>(2).unwrap();
let id = rec.get::<u64>(0).unwrap();
let desc = rec.get::<String>(1).unwrap();

println!("- [{}] {}: {}", if done { "x" } else { " " }, id, &desc,);
}

Ok(())
}
Loading
Loading