usql 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +23 -16
- package/.github/workflows/test.yml +8 -7
- package/.tool-versions +1 -1
- package/README.md +298 -90
- package/dist/index.js +1 -1
- package/index.d.ts +286 -0
- package/package.json +11 -11
- package/src/db.js +302 -51
- package/src/raw.js +14 -0
- package/tests/index.spec.js +232 -5
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
name: Publish to NPM
|
|
2
2
|
|
|
3
|
+
permissions:
|
|
4
|
+
id-token: write # Required for OIDC
|
|
5
|
+
contents: read
|
|
6
|
+
|
|
3
7
|
on:
|
|
4
8
|
release:
|
|
5
9
|
types: [published]
|
|
@@ -8,26 +12,29 @@ jobs:
|
|
|
8
12
|
build:
|
|
9
13
|
runs-on: ubuntu-latest
|
|
10
14
|
steps:
|
|
11
|
-
- uses: actions/checkout@
|
|
12
|
-
- uses:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
- uses: pnpm/action-setup@v5
|
|
17
|
+
- uses: actions/setup-node@v6
|
|
13
18
|
with:
|
|
14
|
-
node-version:
|
|
15
|
-
|
|
16
|
-
- run:
|
|
17
|
-
- run:
|
|
19
|
+
node-version: "24"
|
|
20
|
+
cache: "pnpm"
|
|
21
|
+
- run: pnpm install
|
|
22
|
+
- run: NODE_OPTIONS=--openssl-legacy-provider pnpm run build --if-present
|
|
23
|
+
- run: pnpm test
|
|
18
24
|
|
|
19
25
|
publish-npm:
|
|
20
26
|
needs: build
|
|
21
27
|
runs-on: ubuntu-latest
|
|
22
28
|
steps:
|
|
23
|
-
- uses: actions/checkout@
|
|
24
|
-
- uses:
|
|
29
|
+
- uses: actions/checkout@v6
|
|
30
|
+
- uses: pnpm/action-setup@v5
|
|
31
|
+
- uses: actions/setup-node@v6
|
|
25
32
|
with:
|
|
26
|
-
node-version:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- run:
|
|
30
|
-
- run:
|
|
31
|
-
- run:
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
node-version: "24"
|
|
34
|
+
cache: "pnpm"
|
|
35
|
+
registry-url: "https://registry.npmjs.org/"
|
|
36
|
+
- run: npm -v
|
|
37
|
+
- run: pnpm install --frozen-lockfile
|
|
38
|
+
- run: NODE_OPTIONS=--openssl-legacy-provider pnpm run build --if-present
|
|
39
|
+
- run: pnpm test
|
|
40
|
+
- run: pnpm publish --no-git-checks
|
|
@@ -6,11 +6,12 @@ jobs:
|
|
|
6
6
|
build:
|
|
7
7
|
runs-on: ubuntu-latest
|
|
8
8
|
steps:
|
|
9
|
-
- uses: actions/checkout@
|
|
10
|
-
- uses:
|
|
9
|
+
- uses: actions/checkout@v6
|
|
10
|
+
- uses: pnpm/action-setup@v5
|
|
11
|
+
- uses: actions/setup-node@v6
|
|
11
12
|
with:
|
|
12
|
-
node-version:
|
|
13
|
-
|
|
14
|
-
- run:
|
|
15
|
-
- run:
|
|
16
|
-
|
|
13
|
+
node-version: "24"
|
|
14
|
+
cache: "pnpm"
|
|
15
|
+
- run: pnpm install --frozen-lockfile
|
|
16
|
+
- run: NODE_OPTIONS=--openssl-legacy-provider pnpm run build --if-present
|
|
17
|
+
- run: pnpm test
|
package/.tool-versions
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
nodejs
|
|
1
|
+
nodejs 22.10.0
|
package/README.md
CHANGED
|
@@ -1,259 +1,467 @@
|
|
|
1
1
|
# µSQL
|
|
2
2
|
|
|
3
3
|
An easy-to-use super tiny flexible and 0-dependency SQL query builder with Knex compatible API!
|
|
4
|
-
|
|
4
|
+
Works both in the browser and as a Node.js package.
|
|
5
5
|
|
|
6
6
|
## Installation
|
|
7
|
+
|
|
7
8
|
### yarn
|
|
9
|
+
|
|
8
10
|
```sh
|
|
9
11
|
yarn add usql
|
|
10
12
|
```
|
|
13
|
+
|
|
11
14
|
### npm
|
|
15
|
+
|
|
12
16
|
```sh
|
|
13
17
|
npm install usql
|
|
14
18
|
```
|
|
15
19
|
|
|
16
|
-
##
|
|
20
|
+
## Quick Start
|
|
17
21
|
|
|
18
22
|
```js
|
|
19
|
-
import USql from
|
|
23
|
+
import USql from "usql";
|
|
20
24
|
|
|
21
|
-
const sql = new USql(
|
|
25
|
+
const sql = new USql("table").where({ column: "5", column2: "4" });
|
|
22
26
|
```
|
|
27
|
+
|
|
23
28
|
Then `sql.toString()` will produce:
|
|
29
|
+
|
|
24
30
|
```sql
|
|
25
31
|
SELECT * FROM `table` WHERE `column` = "5" AND `column2` = "4"
|
|
26
32
|
```
|
|
27
33
|
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Security
|
|
37
|
+
|
|
38
|
+
### Parameterised queries (recommended)
|
|
39
|
+
|
|
40
|
+
The safest way to execute a query is via `.toSQL()`, which returns a `{ sql, bindings }`
|
|
41
|
+
object. Pass `sql` and `bindings` directly to your database driver so it handles
|
|
42
|
+
value escaping:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
const { sql, bindings } = new USql("users").where("id", userId).toSQL();
|
|
46
|
+
// sql → 'SELECT * FROM `users` WHERE `id` = ?'
|
|
47
|
+
// bindings → [userId]
|
|
48
|
+
|
|
49
|
+
await db.execute(sql, bindings); // driver binds values safely
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`.toString()` is still available for logging and debugging, but it relies on inline
|
|
53
|
+
string escaping. Prefer `.toSQL()` for any code that talks to a real database.
|
|
54
|
+
|
|
55
|
+
### `DB.raw()` — use with care
|
|
56
|
+
|
|
57
|
+
`DB.raw()` inserts its argument **verbatim** into the generated SQL with **no escaping
|
|
58
|
+
or validation**. Only pass hard-coded string literals or values you have already
|
|
59
|
+
fully validated yourself:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
// ✅ Safe — hard-coded fragment
|
|
63
|
+
DB.raw("COUNT(*) as total");
|
|
64
|
+
|
|
65
|
+
// ❌ UNSAFE — never pass user input
|
|
66
|
+
DB.raw(req.query.column);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Input validation
|
|
70
|
+
|
|
71
|
+
The following methods throw on invalid input rather than silently producing injectable SQL:
|
|
72
|
+
|
|
73
|
+
| Method | Throws | When |
|
|
74
|
+
| --------------------- | ------------ | ---------------------------------------- |
|
|
75
|
+
| `where(col, op, val)` | `RangeError` | `op` is not in the allowed operator set |
|
|
76
|
+
| `join(…, op, …)` | `RangeError` | `op` is not in the allowed operator set |
|
|
77
|
+
| `orderBy(col, dir)` | `RangeError` | `dir` is not `'ASC'` or `'DESC'` |
|
|
78
|
+
| `limit(n)` | `TypeError` | `n` cannot be parsed as a finite integer |
|
|
79
|
+
| `offset(n)` | `TypeError` | `n` cannot be parsed as a finite integer |
|
|
80
|
+
| `where(null, …)` | `TypeError` | column argument is `null` or `undefined` |
|
|
81
|
+
|
|
82
|
+
Allowed comparison operators: `=`, `!=`, `<>`, `<`, `>`, `<=`, `>=`, `LIKE`, `NOT LIKE`, `IN`, `NOT IN`, `IS`, `IS NOT`.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
28
86
|
## API
|
|
87
|
+
|
|
29
88
|
### Column selection
|
|
30
|
-
|
|
89
|
+
|
|
90
|
+
#### select — `.select([...columns])`
|
|
91
|
+
|
|
31
92
|
```js
|
|
32
|
-
new USql(
|
|
93
|
+
new USql("books").select("title", "author", "year");
|
|
33
94
|
```
|
|
95
|
+
|
|
34
96
|
Result:
|
|
97
|
+
|
|
35
98
|
```sql
|
|
36
99
|
SELECT `title`, `author`, `year` FROM `books`
|
|
37
100
|
```
|
|
38
|
-
|
|
101
|
+
|
|
102
|
+
`select` is optional — when omitted, `*` is used:
|
|
103
|
+
|
|
39
104
|
```js
|
|
40
|
-
new USql(
|
|
105
|
+
new USql("books");
|
|
41
106
|
```
|
|
107
|
+
|
|
42
108
|
Result:
|
|
109
|
+
|
|
43
110
|
```sql
|
|
44
111
|
SELECT * FROM `books`
|
|
45
112
|
```
|
|
46
113
|
|
|
114
|
+
---
|
|
115
|
+
|
|
47
116
|
### Where Methods
|
|
48
|
-
#### where — .where(~mixed~)
|
|
49
117
|
|
|
50
|
-
|
|
118
|
+
#### where — `.where(~mixed~)`
|
|
119
|
+
|
|
120
|
+
Object syntax:
|
|
121
|
+
|
|
51
122
|
```js
|
|
52
|
-
new USql(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
123
|
+
new USql("users")
|
|
124
|
+
.where({
|
|
125
|
+
first_name: "Test",
|
|
126
|
+
last_name: "User",
|
|
127
|
+
})
|
|
128
|
+
.select("id");
|
|
56
129
|
```
|
|
130
|
+
|
|
57
131
|
Result:
|
|
132
|
+
|
|
58
133
|
```sql
|
|
59
|
-
SELECT `id` FROM `users` WHERE `first_name` =
|
|
134
|
+
SELECT `id` FROM `users` WHERE `first_name` = "Test" AND `last_name` = "User"
|
|
60
135
|
```
|
|
61
136
|
|
|
62
|
-
Key
|
|
137
|
+
Key/value (defaults to `=`):
|
|
138
|
+
|
|
63
139
|
```js
|
|
64
|
-
new USql(
|
|
140
|
+
new USql("users").where("id", 1).where("info", null);
|
|
65
141
|
```
|
|
142
|
+
|
|
66
143
|
Result:
|
|
144
|
+
|
|
67
145
|
```sql
|
|
68
146
|
SELECT * FROM `users` WHERE `id` = "1" AND `info` IS NULL
|
|
69
147
|
```
|
|
70
148
|
|
|
71
|
-
|
|
149
|
+
Three-argument form (explicit operator):
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
new USql("users").where("age", ">=", 18);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Result:
|
|
156
|
+
|
|
157
|
+
```sql
|
|
158
|
+
SELECT * FROM `users` WHERE `age` >= "18"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Can be chained:
|
|
162
|
+
|
|
72
163
|
```js
|
|
73
|
-
new USql(
|
|
164
|
+
new USql("table")
|
|
165
|
+
.where("id", 1)
|
|
166
|
+
.whereNot("role", "admin")
|
|
167
|
+
.orWhere({ created_at: Date.now() })
|
|
168
|
+
.where({ is_deleted: 0 });
|
|
74
169
|
```
|
|
170
|
+
|
|
75
171
|
Result:
|
|
172
|
+
|
|
76
173
|
```sql
|
|
77
174
|
SELECT * FROM `table` WHERE `id` = "1" AND `role` != "admin" OR `created_at` = "1576417577608" AND `is_deleted` = "0"
|
|
78
175
|
```
|
|
79
176
|
|
|
80
|
-
|
|
177
|
+
---
|
|
81
178
|
|
|
82
|
-
#### whereNot —
|
|
179
|
+
#### whereNot — `.whereNot(~mixed~)`
|
|
180
|
+
|
|
181
|
+
Object syntax:
|
|
83
182
|
|
|
84
|
-
Object Syntax:
|
|
85
183
|
```js
|
|
86
|
-
new USql(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
184
|
+
new USql("users")
|
|
185
|
+
.whereNot({
|
|
186
|
+
first_name: "Test",
|
|
187
|
+
last_name: "User",
|
|
188
|
+
})
|
|
189
|
+
.select("id");
|
|
90
190
|
```
|
|
191
|
+
|
|
91
192
|
Result:
|
|
193
|
+
|
|
92
194
|
```sql
|
|
93
|
-
SELECT `id` FROM `users` WHERE `first_name` !=
|
|
195
|
+
SELECT `id` FROM `users` WHERE `first_name` != "Test" AND `last_name` != "User"
|
|
94
196
|
```
|
|
95
197
|
|
|
96
|
-
Key
|
|
198
|
+
Key/value:
|
|
199
|
+
|
|
97
200
|
```js
|
|
98
|
-
new USql(
|
|
201
|
+
new USql("users").whereNot("id", 1).whereNot("name", null);
|
|
99
202
|
```
|
|
203
|
+
|
|
100
204
|
Result:
|
|
205
|
+
|
|
101
206
|
```sql
|
|
102
207
|
SELECT * FROM `users` WHERE `id` != "1" AND `name` IS NOT NULL
|
|
103
208
|
```
|
|
104
209
|
|
|
105
|
-
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
#### orWhere — `.orWhere(~mixed~)`
|
|
213
|
+
|
|
214
|
+
Object syntax:
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
new USql("users")
|
|
218
|
+
.orWhere({
|
|
219
|
+
first_name: "Test",
|
|
220
|
+
last_name: "User",
|
|
221
|
+
})
|
|
222
|
+
.select("id");
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Result:
|
|
106
226
|
|
|
107
|
-
|
|
227
|
+
```sql
|
|
228
|
+
SELECT `id` FROM `users` WHERE `first_name` = "Test" OR `last_name` = "User"
|
|
229
|
+
```
|
|
108
230
|
|
|
109
|
-
|
|
231
|
+
Key/value:
|
|
110
232
|
|
|
111
|
-
Object Syntax:
|
|
112
233
|
```js
|
|
113
|
-
new USql(
|
|
114
|
-
first_name: 'Test',
|
|
115
|
-
last_name: 'User'
|
|
116
|
-
}).select('id')
|
|
234
|
+
new USql("users").orWhere("id", 1).orWhere("name", null);
|
|
117
235
|
```
|
|
236
|
+
|
|
118
237
|
Result:
|
|
238
|
+
|
|
119
239
|
```sql
|
|
120
|
-
SELECT
|
|
240
|
+
SELECT * FROM `users` WHERE `id` = "1" OR `name` IS NULL
|
|
121
241
|
```
|
|
122
242
|
|
|
123
|
-
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
#### whereGroup — `.whereGroup(callback)`
|
|
246
|
+
|
|
247
|
+
#### orWhereGroup — `.orWhereGroup(callback)`
|
|
248
|
+
|
|
249
|
+
Group conditions in parentheses to control AND/OR precedence. The callback receives
|
|
250
|
+
a fresh builder; call `.where()` / `.orWhere()` on it to populate the group.
|
|
251
|
+
|
|
124
252
|
```js
|
|
125
|
-
new USql(
|
|
253
|
+
new USql("users")
|
|
254
|
+
.where("active", 1)
|
|
255
|
+
.whereGroup((q) => q.where("role", "admin").orWhere("role", "moderator"));
|
|
126
256
|
```
|
|
257
|
+
|
|
127
258
|
Result:
|
|
259
|
+
|
|
128
260
|
```sql
|
|
129
|
-
SELECT * FROM `users` WHERE `
|
|
261
|
+
SELECT * FROM `users` WHERE `active` = "1" AND (`role` = "admin" OR `role` = "moderator")
|
|
130
262
|
```
|
|
131
263
|
|
|
132
|
-
|
|
264
|
+
```js
|
|
265
|
+
new USql("users")
|
|
266
|
+
.where("is_deleted", 0)
|
|
267
|
+
.orWhereGroup((q) => q.where("role", "superadmin").where("active", 1));
|
|
268
|
+
```
|
|
133
269
|
|
|
134
|
-
|
|
270
|
+
Result:
|
|
271
|
+
|
|
272
|
+
```sql
|
|
273
|
+
SELECT * FROM `users` WHERE `is_deleted` = "0" OR (`role` = "superadmin" AND `active` = "1")
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
> **Why does this matter?** Without grouping, SQL evaluates `AND` before `OR`, so
|
|
277
|
+
> `.where('a', 1).orWhere('b', 2).where('c', 3)` produces
|
|
278
|
+
> `a = 1 OR b = 2 AND c = 3`, which is `a = 1 OR (b = 2 AND c = 3)` — almost never
|
|
279
|
+
> what you want in an authorization check. Use `whereGroup` / `orWhereGroup` to make
|
|
280
|
+
> precedence explicit.
|
|
281
|
+
|
|
282
|
+
---
|
|
135
283
|
|
|
136
284
|
### Join method
|
|
137
|
-
#### join — .join(table, first, [operator], second)
|
|
138
285
|
|
|
139
|
-
|
|
286
|
+
#### join — `.join(table, first, [operator], second)`
|
|
287
|
+
|
|
140
288
|
```js
|
|
141
|
-
new USql(
|
|
142
|
-
.join(
|
|
143
|
-
.select(
|
|
289
|
+
new USql("table")
|
|
290
|
+
.join("contacts", "users.id", "=", "contacts.user_id")
|
|
291
|
+
.select("id");
|
|
144
292
|
```
|
|
293
|
+
|
|
145
294
|
Result:
|
|
295
|
+
|
|
146
296
|
```sql
|
|
147
297
|
SELECT `id` FROM `table` JOIN `contacts` ON `users`.`id` = `contacts`.`user_id`
|
|
148
298
|
```
|
|
149
299
|
|
|
150
|
-
You can omit the operator
|
|
300
|
+
You can omit the operator (defaults to `=`):
|
|
301
|
+
|
|
151
302
|
```js
|
|
152
|
-
new USql(
|
|
153
|
-
.join('contacts', 'users.id', 'contacts.user_id')
|
|
154
|
-
.select('id')
|
|
303
|
+
new USql("table").join("contacts", "users.id", "contacts.user_id").select("id");
|
|
155
304
|
```
|
|
305
|
+
|
|
156
306
|
Result:
|
|
307
|
+
|
|
157
308
|
```sql
|
|
158
309
|
SELECT `id` FROM `table` JOIN `contacts` ON `users`.`id` = `contacts`.`user_id`
|
|
159
310
|
```
|
|
160
311
|
|
|
161
|
-
|
|
312
|
+
---
|
|
162
313
|
|
|
163
|
-
|
|
314
|
+
### Ordering
|
|
164
315
|
|
|
165
|
-
|
|
166
|
-
#### orderBy — .orderBy(column|columns, [direction])
|
|
316
|
+
#### orderBy — `.orderBy(column, [direction])`
|
|
167
317
|
|
|
168
|
-
|
|
318
|
+
Direction defaults to `ASC` and is normalised to uppercase.
|
|
169
319
|
|
|
170
320
|
```js
|
|
171
|
-
new USql(
|
|
172
|
-
.orderBy('table1.column1_value', 'desc')
|
|
321
|
+
new USql("table").orderBy("table1.column1_value", "DESC");
|
|
173
322
|
```
|
|
323
|
+
|
|
174
324
|
Result:
|
|
325
|
+
|
|
175
326
|
```sql
|
|
176
|
-
SELECT * FROM `table1` ORDER BY `table1`.`column1_value`
|
|
327
|
+
SELECT * FROM `table1` ORDER BY `table1`.`column1_value` DESC
|
|
177
328
|
```
|
|
178
329
|
|
|
179
|
-
Multiple
|
|
330
|
+
Multiple columns:
|
|
331
|
+
|
|
180
332
|
```js
|
|
181
|
-
new USql(
|
|
182
|
-
.orderBy(
|
|
183
|
-
.orderBy(
|
|
333
|
+
new USql("table")
|
|
334
|
+
.orderBy("table1.column1_value", "DESC")
|
|
335
|
+
.orderBy("table1.column2_value", "ASC");
|
|
184
336
|
```
|
|
337
|
+
|
|
185
338
|
Result:
|
|
339
|
+
|
|
186
340
|
```sql
|
|
187
|
-
SELECT * FROM `table1` ORDER BY `table1`.`column1_value`
|
|
341
|
+
SELECT * FROM `table1` ORDER BY `table1`.`column1_value` DESC, `table1`.`column2_value` ASC
|
|
188
342
|
```
|
|
189
343
|
|
|
190
|
-
|
|
344
|
+
---
|
|
191
345
|
|
|
192
|
-
|
|
346
|
+
### Pagination
|
|
193
347
|
|
|
194
|
-
|
|
348
|
+
#### limit — `.limit(value)`
|
|
195
349
|
|
|
196
350
|
```js
|
|
197
|
-
new USql(
|
|
351
|
+
new USql("table").limit(2);
|
|
198
352
|
```
|
|
353
|
+
|
|
199
354
|
Result:
|
|
355
|
+
|
|
200
356
|
```sql
|
|
201
|
-
SELECT * FROM `
|
|
357
|
+
SELECT * FROM `table` LIMIT 2
|
|
202
358
|
```
|
|
203
359
|
|
|
204
|
-
|
|
360
|
+
#### offset — `.offset(value)`
|
|
205
361
|
|
|
206
|
-
|
|
362
|
+
Requires `limit()` to be set; ignored otherwise.
|
|
207
363
|
|
|
208
|
-
Adds an offset clause to the query. Doesn't work without explicit set of limit value
|
|
209
364
|
```js
|
|
210
|
-
new USql(
|
|
365
|
+
new USql("table").limit(2).offset(5);
|
|
211
366
|
```
|
|
367
|
+
|
|
212
368
|
Result:
|
|
369
|
+
|
|
213
370
|
```sql
|
|
214
|
-
SELECT * FROM `
|
|
371
|
+
SELECT * FROM `table` LIMIT 5, 2
|
|
215
372
|
```
|
|
216
373
|
|
|
217
|
-
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### Aliasing
|
|
377
|
+
|
|
378
|
+
#### as — `.as(name)`
|
|
379
|
+
|
|
380
|
+
Alias a sub-query. Ignored when the query is used at the top level.
|
|
218
381
|
|
|
219
|
-
Allows for aliasing a subquery, taking the string you wish to name the current query. If the query is not a sub-query, it will be ignored.
|
|
220
382
|
```js
|
|
221
|
-
new USql(
|
|
383
|
+
new USql("table").select("column").as("subquery");
|
|
222
384
|
```
|
|
385
|
+
|
|
223
386
|
Result:
|
|
387
|
+
|
|
224
388
|
```sql
|
|
225
389
|
(SELECT `column` FROM `table`) as `subquery`
|
|
226
390
|
```
|
|
227
391
|
|
|
228
|
-
|
|
392
|
+
Full sub-query example:
|
|
229
393
|
|
|
230
394
|
```js
|
|
231
|
-
const subquery = new USql(
|
|
232
|
-
|
|
233
|
-
|
|
395
|
+
const subquery = new USql("groups")
|
|
396
|
+
.select("groups.name")
|
|
397
|
+
.where("users.group_id", USql.raw("`groups`.`id`"))
|
|
398
|
+
.as("group_name");
|
|
234
399
|
|
|
400
|
+
const sql = new USql("users").select("users.*", subquery);
|
|
235
401
|
```
|
|
402
|
+
|
|
236
403
|
Result:
|
|
404
|
+
|
|
237
405
|
```sql
|
|
238
406
|
SELECT `users`.*, (SELECT `groups`.`name` FROM `groups` WHERE `users`.`group_id` = `groups`.`id`) as `group_name` FROM `users`
|
|
239
407
|
```
|
|
240
408
|
|
|
241
|
-
|
|
409
|
+
---
|
|
242
410
|
|
|
243
|
-
###
|
|
411
|
+
### Cloning
|
|
412
|
+
|
|
413
|
+
#### clone — `.clone()`
|
|
414
|
+
|
|
415
|
+
Returns a deep copy of the builder. Use this when you want to reuse a base query
|
|
416
|
+
across multiple code paths without risk of shared-state mutation:
|
|
417
|
+
|
|
418
|
+
```js
|
|
419
|
+
const base = new USql("users").where("active", 1);
|
|
244
420
|
|
|
245
|
-
|
|
421
|
+
const admins = base.clone().where("role", "admin").limit(10);
|
|
422
|
+
const mods = base.clone().where("role", "moderator");
|
|
246
423
|
|
|
247
|
-
|
|
424
|
+
// base is unchanged
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
### Parameterised output
|
|
430
|
+
|
|
431
|
+
#### toSQL — `.toSQL()`
|
|
432
|
+
|
|
433
|
+
Returns `{ sql, bindings }` with `?` placeholders instead of inline values.
|
|
434
|
+
Pass both to your database driver for safe parameterised execution.
|
|
435
|
+
|
|
436
|
+
```js
|
|
437
|
+
const { sql, bindings } = new USql("users")
|
|
438
|
+
.where("email", userEmail)
|
|
439
|
+
.where("active", 1)
|
|
440
|
+
.toSQL();
|
|
441
|
+
|
|
442
|
+
// sql → 'SELECT * FROM `users` WHERE `email` = ? AND `active` = ?'
|
|
443
|
+
// bindings → [userEmail, 1]
|
|
248
444
|
|
|
249
|
-
|
|
445
|
+
await connection.execute(sql, bindings);
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
### Raw queries
|
|
451
|
+
|
|
452
|
+
#### raw — `USql.raw(statement)`
|
|
453
|
+
|
|
454
|
+
Inserts a raw SQL fragment verbatim. See the [Security](#security) section for
|
|
455
|
+
important warnings before use.
|
|
250
456
|
|
|
251
457
|
```js
|
|
252
|
-
new USql(
|
|
458
|
+
new USql("users").select(USql.raw("count(*) as item_number"));
|
|
253
459
|
```
|
|
460
|
+
|
|
254
461
|
Result:
|
|
462
|
+
|
|
255
463
|
```sql
|
|
256
|
-
SELECT count(*) as item_number FROM `
|
|
464
|
+
SELECT count(*) as item_number FROM `users`
|
|
257
465
|
```
|
|
258
466
|
|
|
259
|
-
|
|
467
|
+
`raw` is supported in `select`, `where`, `join`, and `orderBy`.
|