sql-love 1.0.5 → 1.1.0
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/README.md +75 -29
- package/dist/index.cjs +96 -539
- package/dist/index.d.ts +18 -8
- package/dist/index.mjs +72 -506
- package/package.json +19 -18
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
<img alt="SQL Love Logo" src="https://github.com/kensnyder/sql-love/raw/main/assets/sql-love-logo.png?v=1.0
|
|
1
|
+
<img alt="SQL Love Logo" src="https://github.com/kensnyder/sql-love/raw/main/assets/sql-love-logo.png?v=1.1.0" width="250" height="208" />
|
|
2
2
|
|
|
3
3
|
# sql-love
|
|
4
4
|
|
|
5
|
-
[](https://npmjs.com/package/sql-love)
|
|
6
|
+
[](https://github.com/search?q=repo:kensnyder/sql-love++language:TypeScript&type=code)
|
|
7
|
+
[](https://github.com/kensnyder/sql-love/actions)
|
|
8
|
+
[](https://codecov.io/gh/kensnyder/sql-love)
|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
[](https://www.npmjs.com/package/sql-love)
|
|
12
|
+
[](https://opensource.org/licenses/ISC)
|
|
13
13
|
|
|
14
|
-
Classes for parsing and building SQL select queries in Node
|
|
14
|
+
Classes for parsing and building SQL select queries in Node/Bun/Deno.
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
@@ -39,6 +39,7 @@ npm install sql-love
|
|
|
39
39
|
- [toSafeJson functions](#tosafejson)
|
|
40
40
|
- [How to contribute](./CONTRIBUTING.md)
|
|
41
41
|
- [ISC license](./LICENSE.md)
|
|
42
|
+
- [Hello Bun](#hello-bun)
|
|
42
43
|
|
|
43
44
|
## SelectBuilder
|
|
44
45
|
|
|
@@ -189,15 +190,16 @@ Use the following methods to build queries. And see
|
|
|
189
190
|
- `query.having(expression)` - Having an arbitrary expression
|
|
190
191
|
- `query.having(columnValueRecord)` - Add multiple conditions
|
|
191
192
|
- `query.orHaving(expressions)` - Multiple `having()`s joined by OR
|
|
192
|
-
- `query.orderBy(column)` - Add ORDER BY clause
|
|
193
|
-
- `query.sortField(column, mapNames)` - Add ORDER BY clause with mapNames
|
|
193
|
+
- `query.orderBy(column, direction)` - Add ORDER BY clause
|
|
194
|
+
- `query.sortField(column, mapNames, modifier)` - Add ORDER BY clause with mapNames
|
|
194
195
|
- `query.limit(num)` - Limit by the given number
|
|
195
196
|
- `query.offset(num)` - Specify an offset
|
|
196
197
|
- `query.page(num)` - Automatically calculate offset based on limit and page
|
|
197
198
|
- `query.columns(columnNames)` - Add column names to fetch (alias `select()`)
|
|
198
199
|
- `query.column(columnName)` - Add a column name to fetch
|
|
199
|
-
- `query.table(tableName)` -
|
|
200
|
-
- `query.
|
|
200
|
+
- `query.table(tableName)` - Add a table to add to the FROM clause
|
|
201
|
+
- `query.tables(tableNames)` - Add an array of tables to add to the FROM clause
|
|
202
|
+
- `query.from(tableName)` - Alias for `table()`
|
|
201
203
|
- `query.join(expression)` - Add a JOIN expression
|
|
202
204
|
- `query.innerJoin(expression)` - Add an INNER JOIN expression
|
|
203
205
|
- `query.leftJoin(expression)` - Add a LEFT JOIN expression
|
|
@@ -212,7 +214,13 @@ Use the following methods to build queries. And see
|
|
|
212
214
|
### More examples
|
|
213
215
|
|
|
214
216
|
```js
|
|
215
|
-
//
|
|
217
|
+
// building the FROM clause
|
|
218
|
+
query.column('id');
|
|
219
|
+
query.column('"Media"."postId"');
|
|
220
|
+
query.column('CONCAT(first_name, " ", last_name) AS full_name');
|
|
221
|
+
query.columns(['id', 'first_name', 'last_name']);
|
|
222
|
+
|
|
223
|
+
// Ways to test against NULL
|
|
216
224
|
query.where('deleted_at', '=', null);
|
|
217
225
|
query.where('deleted_at', '=', undefined);
|
|
218
226
|
query.where('deleted_at', null);
|
|
@@ -221,15 +229,16 @@ query.where('deleted_at IS NULL');
|
|
|
221
229
|
query.where({ deleted_at: null });
|
|
222
230
|
query.where({ deleted_at: undefined });
|
|
223
231
|
|
|
224
|
-
//
|
|
232
|
+
// Using LIKE
|
|
225
233
|
query.where('name', 'LIKE', 'son');
|
|
226
234
|
query.where('name', 'LIKE ?', 'son');
|
|
227
235
|
query.where('name', 'LIKE ?%', 'son');
|
|
228
236
|
query.where('name', 'LIKE %?', 'son');
|
|
229
237
|
query.where('name', 'LIKE %?%', 'son');
|
|
230
238
|
query.where('name', 'LIKE', '%son%');
|
|
239
|
+
query.where('name', 'ILIKE %?', 'son');
|
|
231
240
|
|
|
232
|
-
//
|
|
241
|
+
// Various operators
|
|
233
242
|
query.where('price', '=', 100);
|
|
234
243
|
query.where('price', '!=', 100);
|
|
235
244
|
query.where('price', '<>', 100);
|
|
@@ -242,7 +251,7 @@ query.whereBetween('price', [100, null]); // price > 100
|
|
|
242
251
|
query.whereBetween('price', [null, 200]); // price < 200
|
|
243
252
|
query.whereBetween('price', [null, null]); // clause is ignored
|
|
244
253
|
|
|
245
|
-
//
|
|
254
|
+
// Using IN and NOT IN
|
|
246
255
|
query.where('status', 'IN', ['pending', 'approved']);
|
|
247
256
|
query.where('status', ['pending', 'approved']);
|
|
248
257
|
query.where('status', '=', ['pending', 'approved']);
|
|
@@ -253,7 +262,7 @@ query.where({ 'status NOT IN': ['pending', 'approved'] });
|
|
|
253
262
|
query.where({ 'status !=': ['pending', 'approved'] });
|
|
254
263
|
query.where({ 'status <>': ['pending', 'approved'] });
|
|
255
264
|
|
|
256
|
-
//
|
|
265
|
+
// Question marks for binding
|
|
257
266
|
query.where(
|
|
258
267
|
'users.id IN (SELECT user_id FROM roles WHERE customer_id IN (?, ?))',
|
|
259
268
|
[1, 2]
|
|
@@ -264,7 +273,7 @@ query.where(
|
|
|
264
273
|
[1]
|
|
265
274
|
);
|
|
266
275
|
|
|
267
|
-
//
|
|
276
|
+
// Multiple conditions
|
|
268
277
|
query.where({
|
|
269
278
|
deleted_at: null,
|
|
270
279
|
'price >': 100,
|
|
@@ -276,7 +285,7 @@ query.where({
|
|
|
276
285
|
|
|
277
286
|
// Note: .having() supports the same signatures as .where()
|
|
278
287
|
|
|
279
|
-
//
|
|
288
|
+
// Ways to use OR
|
|
280
289
|
query.orWhere([{ approved_at: null }, { denied_at: null }]);
|
|
281
290
|
query.orWhere({ approved_at: null, denied_at: null });
|
|
282
291
|
query.orWhere(['approved_at IS NULL', 'denied_at IS NULL']);
|
|
@@ -288,7 +297,7 @@ query.where('(approved_at IS NULL OR denied_at IS NULL)');
|
|
|
288
297
|
|
|
289
298
|
// Note: .orHaving() supports the same signatures as .orWhere()
|
|
290
299
|
|
|
291
|
-
//
|
|
300
|
+
// Joins
|
|
292
301
|
query.innerJoin('phone_numbers p ON p.user_id = u.id');
|
|
293
302
|
query.leftJoin('phone_numbers p ON p.user_id = u.id AND p.type = ?', ['main']);
|
|
294
303
|
query.outerJoin('phone_numbers p ON p.user_id = u.id AND p.type NOT IN(?, ?)', [
|
|
@@ -296,10 +305,40 @@ query.outerJoin('phone_numbers p ON p.user_id = u.id AND p.type NOT IN(?, ?)', [
|
|
|
296
305
|
'cell',
|
|
297
306
|
]);
|
|
298
307
|
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
//
|
|
308
|
+
// Grouping
|
|
309
|
+
|
|
310
|
+
// Sorting
|
|
311
|
+
query.orderBy('modified_at'); // ORDER BY modified_at
|
|
312
|
+
query.orderBy('modified_at', 'DESC'); // ORDER BY modified_at DESC
|
|
313
|
+
query.orderBy('-modified_at'); // ORDER BY modified_at DESC
|
|
314
|
+
query.orderBy('COUNT(*)'); // ORDER BY COUNT(*)
|
|
315
|
+
query.orderBy('COUNT(*)', 'DESC'); // ORDER BY COUNT(*) DESC
|
|
316
|
+
query.orderBy('COUNT(*) DESC'); // ORDER BY COUNT(*) DESC
|
|
317
|
+
query.orderBy('modified_at DESC NULLS LAST'); // ORDER BY modified_at DESC NULLS LAST
|
|
318
|
+
query.orderBy('modified_at USING >'); // ORDER BY modified_at USING >
|
|
319
|
+
|
|
320
|
+
// Sorting with support for hyphens and mapping
|
|
321
|
+
query.sortField('-modified_at'); // ORDER BY modified_at DESC
|
|
322
|
+
query.sortField('-created', { created: 'created_at' }); // ORDER BY created_at DESC
|
|
323
|
+
query.sortField(
|
|
324
|
+
'-created_at',
|
|
325
|
+
{
|
|
326
|
+
created_at: 'posts.created_timestamp',
|
|
327
|
+
},
|
|
328
|
+
'NULLS LAST'
|
|
329
|
+
); // ORDER BY posts.created_timestamp DESC NULLS LAST
|
|
330
|
+
query.sortField('-created', {
|
|
331
|
+
created: 'created_at NULLS LAST',
|
|
332
|
+
'-created': 'created_at DESC NULLS LAST',
|
|
333
|
+
}); // ORDER BY created_at DESC NULLS LAST
|
|
334
|
+
query.sortField('-name', {
|
|
335
|
+
name: 'name COLLATE "jp_JP"',
|
|
336
|
+
}); // ORDER BY name COLLATE "jp_JP" DESC
|
|
337
|
+
|
|
338
|
+
// Pagination
|
|
339
|
+
query.limit(10).page(3); // "LIMIT 10 OFFSET 20" (or for mssql, "OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY")
|
|
340
|
+
|
|
341
|
+
// Cloning for creating similar queries
|
|
303
342
|
const query1 = new SelectBuilder();
|
|
304
343
|
query1.where('email', 'john@example.com');
|
|
305
344
|
const query2 = query1.getClone();
|
|
@@ -397,10 +436,10 @@ If you need to use SQL keywords in strings, use bindings.
|
|
|
397
436
|
|
|
398
437
|
```js
|
|
399
438
|
// ❌ WILL NOT WORK
|
|
400
|
-
new SelectBuilder(`SELECT id, CONCAT('where ',
|
|
439
|
+
new SelectBuilder(`SELECT id, CONCAT('where ', column) FROM users`);
|
|
401
440
|
|
|
402
441
|
// ✅ WORKING EQUIVALENT
|
|
403
|
-
new SelectBuilder(`SELECT id, CONCAT(:prefix,
|
|
442
|
+
new SelectBuilder(`SELECT id, CONCAT(:prefix, column) FROM users`, {
|
|
404
443
|
prefix: 'where ',
|
|
405
444
|
});
|
|
406
445
|
```
|
|
@@ -497,7 +536,9 @@ const grouped = extractGrouped('dept', records);
|
|
|
497
536
|
{ id: 1, name: 'John', dept: 'Marketing' },
|
|
498
537
|
{ id: 3, name: 'Tim', dept: 'Marketing' },
|
|
499
538
|
],
|
|
500
|
-
Finance: [
|
|
539
|
+
Finance: [
|
|
540
|
+
{ id: 2, name: 'Jane', dept: 'Finance' }
|
|
541
|
+
],
|
|
501
542
|
}
|
|
502
543
|
*/
|
|
503
544
|
```
|
|
@@ -629,3 +670,8 @@ toSafeJsonString(records);
|
|
|
629
670
|
|
|
630
671
|
// As you can see, BigInt values too big for Number are converted to strings
|
|
631
672
|
```
|
|
673
|
+
|
|
674
|
+
## Hello Bun
|
|
675
|
+
|
|
676
|
+
This code repository uses [Bun](https://bun.sh) for a package manager, test
|
|
677
|
+
runner and builder.
|