Skip to content

Commit ca884c2

Browse files
committed
Initial commit - move from sqld/
0 parents  commit ca884c2

12 files changed

+1247
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Cargo.lock
2+
target/

Cargo.toml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
[package]
2+
name = "libsql-client"
3+
version = "0.5.10"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
description = "HTTP-based client for libSQL and sqld"
7+
keywords = ["libsql", "sqld", "database", "driver", "http"]
8+
repository = "https://github.com/libsql/libsql-client-rs"
9+
10+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11+
12+
[dependencies]
13+
url = "2.3.1"
14+
base64 = "0.21.0"
15+
num-traits = "0.2.15"
16+
serde_json = "1.0.91"
17+
worker = { version = "0.0.12", optional = true }
18+
anyhow = "1.0.69"
19+
async-trait = "0.1.64"
20+
reqwest = { version = "0.11.14", optional = true, default-features = false, features = ["rustls-tls"] }
21+
rusqlite = { version = "0.28.0", optional = true, default-features = false, features = [
22+
"column_decltype"
23+
] }
24+
25+
[features]
26+
default = ["local_backend", "reqwest_backend"]
27+
workers_backend = ["worker"]
28+
reqwest_backend = ["reqwest"]
29+
local_backend = ["rusqlite"]
30+
31+
[dev-dependencies]
32+
tokio = { version = "1", features = ["full"] }
33+
libsql-client = { path = ".", features = ["reqwest_backend", "local_backend"] }
34+
rand = "0.8.5"
35+
36+
[[example]]
37+
name = "select"
38+
path = "examples/select.rs"
39+
40+
[[example]]
41+
name = "connect_from_env"
42+
path = "examples/connect_from_env.rs"
43+
44+
[workspace]

README.md

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Rust libSQL client library
2+
3+
libSQL Rust client library can be used to communicate with [sqld](https://github.com/libsql/sqld/) natively over HTTP protocol with native Rust interface.
4+
5+
At the moment the library works with the following backends:
6+
- local
7+
- reqwest
8+
- Cloudflare Workers environment (optional)
9+
10+
## Quickstart
11+
12+
In order to use the database in your project, just call `libsql_client::connect()`:
13+
```rust
14+
let db = libsql_client::connect()?;
15+
```
16+
17+
The only thing you need to provide is an env variable with the database URL, e.g.
18+
```
19+
export LIBSQL_CLIENT_URL="file:////tmp/example.db"
20+
```
21+
for a local database stored in a file, or
22+
```
23+
export LIBSQL_CLIENT_URL="https://user:[email protected]"
24+
```
25+
for a remote database connection.
26+
27+
You can also explicitly use a specific backend. Examples of that are covered in the next paragraphs.
28+
29+
### Local
30+
31+
In order to connect to the database, set up the URL to point to a local path:
32+
```
33+
export LIBSQL_CLIENT_URL = "/tmp/example.db"
34+
```
35+
36+
`local_backend` feature is enabled by default, so add the dependency like this:
37+
```
38+
cargo add libsql-client
39+
```
40+
41+
Example for how to connect to the database and perform a query:
42+
```rust
43+
let db = libsql_client::local::Connection::connect_from_env()?;
44+
let response = db
45+
.execute("SELECT * FROM table WHERE key = 'key1'")
46+
.await?;
47+
(...)
48+
```
49+
50+
51+
### Reqwest
52+
In order to connect to the database, set up the following env variables:
53+
```
54+
export LIBSQL_CLIENT_URL = "https://your-db-url.example.com"
55+
export LIBSQL_CLIENT_USER = "me"
56+
export LIBSQL_CLIENT_PASS = "my-password"
57+
```
58+
... or just the URL, if it contains the required auth information:
59+
```
60+
export LIBSQL_CLIENT_URL = "https://user@pass:your-db-url.example.com"
61+
```
62+
63+
`reqwest_backend` feature is enabled by default, so add the dependency like this:
64+
```
65+
cargo add libsql-client
66+
```
67+
68+
Example for how to connect to the database and perform a query from a GET handler:
69+
```rust
70+
let db = libsql_client::reqwest::Connection::connect_from_env()?;
71+
let response = db
72+
.execute("SELECT * FROM table WHERE key = 'key1'")
73+
.await?;
74+
(...)
75+
```
76+
77+
### Cloudflare Workers
78+
79+
In order to connect to the database, set up the following variables in `.dev.vars`, or register them as secrets:
80+
```
81+
LIBSQL_CLIENT_URL = "https://your-db-url.example.com"
82+
LIBSQL_CLIENT_USER = "me"
83+
LIBSQL_CLIENT_PASS = "my-password"
84+
```
85+
86+
Add it as dependency with `workers_backend` backend enabled. Turn off default features, as they are not guaranteed to compile to `wasm32-unknown-unknown`,
87+
which is required in this environment:
88+
```
89+
cargo add libsql-client --no-default-features -F workers_backend
90+
```
91+
92+
Example for how to connect to the database and perform a query from a GET handler:
93+
```rust
94+
router.get_async("/", |_, ctx| async move {
95+
let db = libsql_client::workers::Connection::connect_from_ctx(&ctx)?;
96+
let response = db
97+
.execute("SELECT * FROM table WHERE key = 'key1'")
98+
.await?;
99+
(...)
100+
```

examples/connect_from_env.rs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use anyhow::Result;
2+
use libsql_client::{connect, params, Connection, QueryResult, ResultSet, Statement};
3+
use rand::prelude::SliceRandom;
4+
5+
fn result_to_string(query_result: QueryResult) -> Result<String> {
6+
let mut ret = String::new();
7+
let ResultSet { columns, rows } = query_result.into_result_set()?;
8+
for column in &columns {
9+
ret += &format!("| {column:16} |");
10+
}
11+
ret += "\n| -------------------------------------------------------- |\n";
12+
for row in rows {
13+
for column in &columns {
14+
ret += &format!("| {:16} |", row.cells[column].to_string());
15+
}
16+
ret += "\n";
17+
}
18+
Ok(ret)
19+
}
20+
21+
// Bumps a counter for one of the geographic locations picked at random.
22+
async fn bump_counter(db: impl Connection) -> Result<String> {
23+
// Recreate the tables if they do not exist yet
24+
db.batch([
25+
"CREATE TABLE IF NOT EXISTS counter(country TEXT, city TEXT, value, PRIMARY KEY(country, city)) WITHOUT ROWID",
26+
"CREATE TABLE IF NOT EXISTS coordinates(lat INT, long INT, airport TEXT, PRIMARY KEY (lat, long))"
27+
]).await?;
28+
29+
// For demo purposes, let's pick a pseudorandom location
30+
const FAKE_LOCATIONS: &[(&str, &str, &str, f64, f64)] = &[
31+
("WAW", "PL", "Warsaw", 52.22959, 21.0067),
32+
("EWR", "US", "Newark", 42.99259, -81.3321),
33+
("HAM", "DE", "Hamburg", 50.118801, 7.684300),
34+
("HEL", "FI", "Helsinki", 60.3183, 24.9497),
35+
("NSW", "AU", "Sydney", -33.9500, 151.1819),
36+
];
37+
38+
let (airport, country, city, latitude, longitude) =
39+
*FAKE_LOCATIONS.choose(&mut rand::thread_rng()).unwrap();
40+
41+
db.transaction([
42+
Statement::with_params(
43+
"INSERT OR IGNORE INTO counter VALUES (?, ?, 0)",
44+
// Parameters that have a single type can be passed as a regular slice
45+
&[country, city],
46+
),
47+
Statement::with_params(
48+
"UPDATE counter SET value = value + 1 WHERE country = ? AND city = ?",
49+
&[country, city],
50+
),
51+
Statement::with_params(
52+
"INSERT OR IGNORE INTO coordinates VALUES (?, ?, ?)",
53+
// Parameters with different types can be passed to a convenience macro - params!()
54+
params!(latitude, longitude, airport),
55+
),
56+
])
57+
.await?;
58+
59+
let counter_response = db.execute("SELECT * FROM counter").await?;
60+
let scoreboard = result_to_string(counter_response)?;
61+
let html = format!("Scoreboard:\n{scoreboard}");
62+
Ok(html)
63+
}
64+
65+
#[tokio::main]
66+
async fn main() {
67+
let db = connect().unwrap();
68+
let response = bump_counter(db)
69+
.await
70+
.unwrap_or_else(|e| format!("Error: {e}"));
71+
println!(
72+
"Connection parameters: backend={:?} url={:?}\n{response}",
73+
std::env::var("LIBSQL_CLIENT_BACKEND"),
74+
std::env::var("LIBSQL_CLIENT_URL")
75+
);
76+
}

examples/select.rs

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use anyhow::Result;
2+
use libsql_client::{params, Connection, QueryResult, ResultSet, Statement};
3+
use rand::prelude::SliceRandom;
4+
5+
fn result_to_string(query_result: QueryResult) -> Result<String> {
6+
let mut ret = String::new();
7+
let ResultSet { columns, rows } = query_result.into_result_set()?;
8+
for column in &columns {
9+
ret += &format!("| {column:16} |");
10+
}
11+
ret += "\n| -------------------------------------------------------- |\n";
12+
for row in rows {
13+
for column in &columns {
14+
ret += &format!("| {:16} |", row.cells[column].to_string());
15+
}
16+
ret += "\n";
17+
}
18+
Ok(ret)
19+
}
20+
21+
// Bumps a counter for one of the geographic locations picked at random.
22+
async fn bump_counter(db: impl Connection) -> Result<String> {
23+
// Recreate the tables if they do not exist yet
24+
db.batch([
25+
"CREATE TABLE IF NOT EXISTS counter(country TEXT, city TEXT, value, PRIMARY KEY(country, city)) WITHOUT ROWID",
26+
"CREATE TABLE IF NOT EXISTS coordinates(lat INT, long INT, airport TEXT, PRIMARY KEY (lat, long))"
27+
]).await?;
28+
29+
// For demo purposes, let's pick a pseudorandom location
30+
const FAKE_LOCATIONS: &[(&str, &str, &str, f64, f64)] = &[
31+
("WAW", "PL", "Warsaw", 52.22959, 21.0067),
32+
("EWR", "US", "Newark", 42.99259, -81.3321),
33+
("HAM", "DE", "Hamburg", 50.118801, 7.684300),
34+
("HEL", "FI", "Helsinki", 60.3183, 24.9497),
35+
("NSW", "AU", "Sydney", -33.9500, 151.1819),
36+
];
37+
38+
let (airport, country, city, latitude, longitude) =
39+
*FAKE_LOCATIONS.choose(&mut rand::thread_rng()).unwrap();
40+
41+
db.transaction([
42+
Statement::with_params(
43+
"INSERT OR IGNORE INTO counter VALUES (?, ?, 0)",
44+
// Parameters that have a single type can be passed as a regular slice
45+
&[country, city],
46+
),
47+
Statement::with_params(
48+
"UPDATE counter SET value = value + 1 WHERE country = ? AND city = ?",
49+
&[country, city],
50+
),
51+
Statement::with_params(
52+
"INSERT OR IGNORE INTO coordinates VALUES (?, ?, ?)",
53+
// Parameters with different types can be passed to a convenience macro - params!()
54+
params!(latitude, longitude, airport),
55+
),
56+
])
57+
.await?;
58+
59+
let counter_response = db.execute("SELECT * FROM counter").await?;
60+
let scoreboard = result_to_string(counter_response)?;
61+
let html = format!("Scoreboard:\n{scoreboard}");
62+
Ok(html)
63+
}
64+
65+
#[tokio::main]
66+
async fn main() {
67+
match libsql_client::reqwest::Connection::connect_from_env() {
68+
Ok(remote_db) => {
69+
match bump_counter(remote_db).await {
70+
Ok(response) => println!("Remote:\n{response}"),
71+
Err(e) => println!("Remote database query failed: {e}"),
72+
};
73+
}
74+
Err(e) => println!("Failed to fetch from a remote database: {e}"),
75+
}
76+
77+
let mut path_buf = std::env::temp_dir();
78+
path_buf.push("libsql_client_test_db.db");
79+
let local_db = libsql_client::local::Connection::connect(path_buf.as_path()).unwrap();
80+
match bump_counter(local_db).await {
81+
Ok(response) => println!("Local:\n{response}"),
82+
Err(e) => println!("Local database query failed: {e}"),
83+
};
84+
}

0 commit comments

Comments
 (0)