2
2
3
3
> An SQL query builder for [ sqlx] [ sqlx ] . ** Work in progress**
4
4
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
6
21
7
22
Suppose you have a table like this:
8
23
9
24
``` sql
10
25
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
15
31
);
16
32
```
17
33
18
34
The CRUD (or ISUD with sql acronym) will be look like this:
19
35
20
- 1 . ` INSERT ` statement.
36
+ ### ` INSERT ` statement.
21
37
22
38
``` rust
23
39
let book1 = " The Fellowship of the Rings" . to_string ();
24
40
let auth1 = " J. R. R. Tolkien" . to_string ();
25
41
let book2 = " Dune" . to_string ();
26
42
let auth2 = " Frank Herbret" . to_string ();
43
+ let english = " English" . to_string ();
27
44
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 )
33
51
. returning ([" id" ]);
34
52
35
53
assert_eq! (
36
54
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 ) \
40
58
RETURNING id" ,
41
59
);
42
60
```
43
61
44
- 2 . ` SELECT ` statement.
62
+ ### ` SELECT ` statement.
45
63
46
64
``` rust
47
- use xql :: blanket :: ExprExt ;
48
- use xql :: ops :: or;
49
-
50
65
let select = xql :: select ([" id" , " title" ])
51
66
. 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 )))
56
68
. order_by (xql :: desc (" year" ));
57
69
58
70
assert_eq! (
59
71
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"
64
73
);
65
74
```
66
75
67
- 3 . ` UPDATE ` statement.
76
+ ### ` UPDATE ` statement.
68
77
69
78
``` rust
70
79
let author = & " Frank Herbert" . to_string ();
@@ -75,14 +84,11 @@ let update = xql::update("book")
75
84
76
85
assert_eq! (
77
86
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" ,
82
88
);
83
89
```
84
90
85
- 4 . ` DELETE ` statement.
91
+ ### ` DELETE ` statement.
86
92
87
93
``` rust
88
94
let delete = xql :: delete (" book" )
@@ -91,59 +97,200 @@ let delete = xql::delete("book")
91
97
92
98
assert_eq! (
93
99
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" ,
97
101
);
98
102
```
99
103
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 ` .
101
113
102
114
``` 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!
116
185
}
186
+ ```
117
187
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 >,
131
227
}
132
228
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
+
145
282
Ok (())
146
283
}
147
284
```
148
285
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
+
149
295
[ 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