Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e49a94f

Browse files
committedMar 5, 2022
update README.md
1 parent 66af004 commit e49a94f

File tree

1 file changed

+221
-74
lines changed

1 file changed

+221
-74
lines changed
 

‎README.md

+221-74
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,78 @@
22

33
> An SQL query builder for [sqlx][sqlx]. **Work in progress**
44
5-
# example usages
5+
## Table of Contents
6+
7+
1. [Basic Query Building](#basic-query-building)
8+
1. [Insert statement](#insert-statement)
9+
2. [Select statement](#select-statement)
10+
3. [Update statement](#update-statement)
11+
4. [Delete statement](#delete-statement)
12+
2. [Blanket](#blanket)
13+
1. [Blanket on Expression](#blanket-on-expression)
14+
2. [Blanket on Table Expression](#blanket-on-table-expression)
15+
3. [Blanket on `SELECT` and `VALUES` statement](#blanket-on-select-and-values-statement)
16+
3. [Derive](#derive)
17+
4. [Execution](#execution)
18+
5. [Notes on `str` and `String`](#notes-on-str-and-string)
19+
20+
## Basic Query Building
621

722
Suppose you have a table like this:
823

924
```sql
1025
CREATE TABLE book(
11-
id INTEGER NOT NULL PRIMARY KEY,
12-
title TEXT NOT NULL,
13-
year SMALLINT NOT NULL,
14-
author TEXT
26+
id INTEGER PRIMARY KEY,
27+
title TEXT NOT NULL,
28+
author TEXT,
29+
lang TEXT,
30+
year SMALLINT
1531
);
1632
```
1733

1834
The CRUD (or ISUD with sql acronym) will be look like this:
1935

20-
1. `INSERT` statement.
36+
### `INSERT` statement.
2137

2238
```rust
2339
let book1 = "The Fellowship of the Rings".to_string();
2440
let auth1 = "J. R. R. Tolkien".to_string();
2541
let book2 = "Dune".to_string();
2642
let auth2 = "Frank Herbret".to_string();
43+
let english = "English".to_string();
2744

28-
let insert = xql::insert("book", ["id", "title", "year", "author"])
29-
.values([
30-
(1_i32, &book1, 1954_i16, &auth1),
31-
(2_i32, &book2, 1965_i16, &auth2),
32-
])
45+
let values = [
46+
(1_i32, &book1, &auth1, &english, 1954_i16),
47+
(2_i32, &book2, &auth2, &english, 1965_i16),
48+
];
49+
let insert = xql::insert("book", ["id", "title", "author", "lang", "year"])
50+
.values(values)
3351
.returning(["id"]);
3452

3553
assert_eq!(
3654
insert.to_string(),
37-
"INSERT INTO book(id, title, year, author) VALUES \
38-
(1, 'The Fellowship of the Rings', 1954, 'J. R. R. Tolkien'), \
39-
(2, 'Dune', 1965, 'Frank Herbret') \
55+
"INSERT INTO book(id, title, author, lang, year) VALUES \
56+
(1, 'The Fellowship of the Rings', 'J. R. R. Tolkien', 'English', 1954), \
57+
(2, 'Dune', 'Frank Herbret', 'English', 1965) \
4058
RETURNING id",
4159
);
4260
```
4361

44-
2. `SELECT` statement.
62+
### `SELECT` statement.
4563

4664
```rust
47-
use xql::blanket::ExprExt;
48-
use xql::ops::or;
49-
5065
let select = xql::select(["id", "title"])
5166
.from("book")
52-
.filter(or(
53-
"id".equal(1),
54-
"id".equal(2),
55-
))
67+
.filter(xql::or(xql::eq("id", 1), xql::eq("id", 2)))
5668
.order_by(xql::desc("year"));
5769

5870
assert_eq!(
5971
select.to_string(),
60-
"SELECT id, title \
61-
FROM book \
62-
WHERE id = 1 OR id = 2 \
63-
ORDER BY year DESC"
72+
"SELECT id, title FROM book WHERE id = 1 OR id = 2 ORDER BY year DESC"
6473
);
6574
```
6675

67-
3. `UPDATE` statement.
76+
### `UPDATE` statement.
6877

6978
```rust
7079
let author = &"Frank Herbert".to_string();
@@ -75,14 +84,11 @@ let update = xql::update("book")
7584

7685
assert_eq!(
7786
update.to_string(),
78-
"UPDATE book \
79-
SET author = 'Frank Herbert' \
80-
WHERE id = 2 \
81-
RETURNING id",
87+
"UPDATE book SET author = 'Frank Herbert' WHERE id = 2 RETURNING id",
8288
);
8389
```
8490

85-
4. `DELETE` statement.
91+
### `DELETE` statement.
8692

8793
```rust
8894
let delete = xql::delete("book")
@@ -91,59 +97,200 @@ let delete = xql::delete("book")
9197

9298
assert_eq!(
9399
delete.to_string(),
94-
"DELETE FROM book \
95-
WHERE id = 1 \
96-
RETURNING id, title",
100+
"DELETE FROM book WHERE id = 1 RETURNING id, title",
97101
);
98102
```
99103

100-
To execute those queries, add [sqlx][sqlx] to dependencies and enable the backend.
104+
## Blanket
105+
106+
There are some [blanket implementation][blanket-implementation] for traits that defined
107+
in `xql::blanket` to assist query building.
108+
109+
### Blanket on Expression
110+
111+
Most of expr's function defined in `xql::ops` have method of blanket
112+
implementation of `xql::blanket::ExprExt`.
101113

102114
```rust
103-
#[cfg(all(feature = "postgres", not(feature = "mysql"), not(feature = "sqlite")))]
104-
async fn execute<'a>(
105-
pool: sqlx::Pool<sqlx::Postgres>,
106-
insert: xql::stmt::insert::Insert<'a>,
107-
select: xql::stmt::select::Select<'a>,
108-
update: xql::stmt::update::Update<'a>,
109-
delete: xql::stmt::delete::Delete<'a>,
110-
) -> Result<(), sqlx::Error> {
111-
xql::exec::fetch_all(insert, &pool).await?;
112-
xql::exec::fetch_all(select, &pool).await?;
113-
xql::exec::fetch_all(update, &pool).await?;
114-
xql::exec::fetch_all(delete, &pool).await?;
115-
Ok(())
115+
use xql::blanket::ExprExt;
116+
117+
let cond = "year".greater_than(1900).and("year".less_equal(2000));
118+
assert_eq!(cond.to_string(), "year > 1900 AND year <= 2000");
119+
120+
let query = xql::select(["id"]).from("book").filter(cond);
121+
assert_eq!(query.to_string(), "SELECT id FROM book WHERE year > 1900 AND year <= 2000");
122+
```
123+
124+
Well, that looks verbose. It can't be helped, because using `gt` or `le` will
125+
clash with `PartialOrd` (which can't be disabled even with
126+
`no_implicit_prelude`). This one below will not compile.
127+
128+
```rust,compile_fail
129+
use xql::blanket::ExprExt;
130+
131+
let cond = "year".gt(1900).and("year".le(2000));
132+
```
133+
134+
A work around is to turn the left hand side into `Expr` first or using a table qualified
135+
column reference.
136+
137+
```rust
138+
use xql::expr::Expr;
139+
use xql::blanket::ExprExt;
140+
141+
let year = Expr::from("year");
142+
let qualified = ("book", "year");
143+
144+
let cond = year.gt(1900).and(qualified.le(2000));
145+
assert_eq!(cond.to_string(), "year > 1900 AND book.year <= 2000");
146+
```
147+
148+
### Blanket on Table Expression
149+
150+
`join` family functions have some blanket implementations.
151+
152+
```rust
153+
use xql::blanket::ExprExt;
154+
use xql::blanket::TableExprExt;
155+
156+
let table = "book".join("category", ("book", "category_id").eq(("category", "id")));
157+
assert_eq!(table.to_string(), "book JOIN category ON book.category_id = category.id");
158+
```
159+
160+
### Blanket on `SELECT` and `VALUES` statement
161+
162+
`SELECT` and `VALUES` are the only statements that can use `UNION` family functions.
163+
164+
```rust
165+
use xql::blanket::ResultExt;
166+
167+
let query = xql::select([1, 2]).union(xql::values([(3, 4)]));
168+
169+
assert_eq!(query.to_string(), "SELECT 1, 2 UNION VALUES (3, 4)");
170+
```
171+
172+
In case you're wondering, `ResultExt`'s name came from
173+
`xql::stmt::result::Result` which is an enum of only `Select` and `Values`. Why
174+
`Result`? Well, because naming is hard and it looks good in `Stmt` enum definition:
175+
176+
```rust
177+
enum Stmt {
178+
Insert,
179+
Select,
180+
Update,
181+
Delete,
182+
Values,
183+
Binary,
184+
Result, // See!? Exactly 6 characters! Perfectly balanced as all things should be!
116185
}
186+
```
117187

118-
#[cfg(all(not(feature = "postgres"), feature = "mysql", not(feature = "sqlite")))]
119-
async fn execute<'a>(
120-
pool: sqlx::Pool<sqlx::MySql>,
121-
insert: xql::stmt::insert::Insert<'a>,
122-
select: xql::stmt::select::Select<'a>,
123-
update: xql::stmt::update::Update<'a>,
124-
delete: xql::stmt::delete::Delete<'a>,
125-
) -> Result<(), sqlx::Error> {
126-
xql::exec::fetch_all(insert, &pool).await?;
127-
xql::exec::fetch_all(select, &pool).await?;
128-
xql::exec::fetch_all(update, &pool).await?;
129-
xql::exec::fetch_all(delete, &pool).await?;
130-
Ok(())
188+
## Derive
189+
190+
You can enable `derive` feature to make query building looks shorter or nicer.
191+
192+
```rust
193+
use xql::Schema;
194+
195+
#[derive(Schema)]
196+
struct Book {
197+
id: i32,
198+
title: String,
199+
author: Option<String>,
200+
lang: Option<String>,
201+
year: Option<i32>,
202+
}
203+
204+
let shorter = xql::select(Book::columns()).from(Book::table());
205+
assert_eq!(shorter.to_string(), "SELECT book.id, book.title, book.author, book.lang, book.year FROM book");
206+
207+
let nicer = xql::select([Book::id, Book::title, Book::author, Book::lang, Book::year]).from(Book);
208+
assert_eq!(nicer.to_string(), "SELECT book.id, book.title, book.author, book.lang, book.year FROM book");
209+
210+
assert_eq!(shorter, nicer);
211+
```
212+
213+
The table qualified column will turn to unqualified in `INSERT`'s columns or
214+
`UPDATE`'s `SET`.
215+
216+
```rust
217+
use xql::Schema;
218+
use xql::blanket::ExprExt;
219+
220+
#[derive(Schema)]
221+
struct Book {
222+
id: i32,
223+
title: String,
224+
author: Option<String>,
225+
lang: Option<String>,
226+
year: Option<i32>,
131227
}
132228

133-
#[cfg(all(not(feature = "postgres"), not(feature = "mysql"), feature = "sqlite"))]
134-
async fn execute<'a>(
135-
pool: sqlx::Pool<sqlx::Sqlite>,
136-
insert: xql::stmt::insert::Insert<'a>,
137-
select: xql::stmt::select::Select<'a>,
138-
update: xql::stmt::update::Update<'a>,
139-
delete: xql::stmt::delete::Delete<'a>,
140-
) -> Result<(), sqlx::Error> {
141-
xql::exec::fetch_all(insert, &pool).await?;
142-
xql::exec::fetch_all(select, &pool).await?;
143-
xql::exec::fetch_all(update, &pool).await?;
144-
xql::exec::fetch_all(delete, &pool).await?;
229+
let values = [(&"Dune".to_string(),)];
230+
let insert = xql::insert(Book, [Book::title]).values(values);
231+
assert_eq!(insert.to_string(), "INSERT INTO book(title) VALUES ('Dune')");
232+
233+
let author = "Frank Herbert".to_string();
234+
let update = xql::update(Book).set(Book::author, &author).filter(Book::id.eq(2));
235+
assert_eq!(update.to_string(), "UPDATE book SET author = 'Frank Herbert' WHERE book.id = 2");
236+
```
237+
238+
## Execution
239+
240+
To execute those queries, enable `sqlx` feature and one of `postgres`, `mysql`
241+
or `sqlite` feature.
242+
243+
```rust
244+
#[derive(sqlx::FromRow)]
245+
struct Output {
246+
id: i32,
247+
title: String,
248+
}
249+
250+
#[cfg(feature = "postgres")]
251+
async fn execute(pool: sqlx::Pool::<sqlx::Postgres>) -> Result<(), sqlx::Error> {
252+
253+
// sqlx::query(..).fetch_all
254+
let query = xql::select(["id", "title"]).from("book");
255+
let rows = xql::exec::fetch_all(query, &pool).await?;
256+
257+
// sqlx::query_as(..).fetch_all
258+
let query = xql::select(["id", "title"]).from("book");
259+
let rows: Vec<Output> = xql::exec::fetch_all_as(query, &pool).await?;
260+
261+
// sqlx::query_scalar(..).fetch_all
262+
let query = xql::select(["id"]).from("book");
263+
let rows: Vec<i32> = xql::exec::fetch_all_scalar(query, &pool).await?;
264+
265+
// or in blanket form
266+
use xql::blanket::StmtExt;
267+
268+
let rows = xql::select(["id", "title"])
269+
.from("book")
270+
.fetch_all(&pool).await?;
271+
272+
let rows: Vec<Output> = xql::select(["id", "title"])
273+
.from("book")
274+
.fetch_all_as(&pool)
275+
.await?;
276+
277+
278+
let rows: Vec<i32> = xql::select(["id"])
279+
.from("book")
280+
.fetch_all_scalar(&pool).await?;
281+
145282
Ok(())
146283
}
147284
```
148285

286+
Available variants are: `fetch_one`, `fetch_all`, `fetch_optional` with `_as`,
287+
`_scalar` or no suffix respectively.
288+
289+
## Notes on `str` and `String`
290+
291+
You may notice serveral use of `&"text".to_string()` in the examples above.
292+
That's because `&str` will turn into an identifier while `&String` will turn
293+
into a literal text.
294+
149295
[sqlx]: https://crates.io/crates/sqlx
296+
[blanket-implementation]: https://doc.rust-lang.org/book/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods

0 commit comments

Comments
 (0)
Please sign in to comment.