sql-love 1.1.4 → 1.2.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 +7 -5
- package/dist/index.cjs +23 -7
- package/dist/index.d.ts +2 -4
- package/dist/index.mjs +23 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
<img alt="SQL Love Logo" src="https://github.com/kensnyder/sql-love/raw/main/assets/sql-love-logo.png?v=1.
|
|
1
|
+
<img alt="SQL Love Logo" src="https://github.com/kensnyder/sql-love/raw/main/assets/sql-love-logo.png?v=1.2.0" width="250" height="208" />
|
|
2
2
|
|
|
3
3
|
# sql-love
|
|
4
4
|
|
|
5
|
-
[](https://github.com/search?q=repo:kensnyder/sql-love++language:TypeScript&type=code)
|
|
6
|
+
[](https://codecov.io/gh/kensnyder/sql-love)
|
|
7
7
|

|
|
8
8
|

|
|
9
9
|

|
|
10
|
-
[](https://opensource.org/licenses/ISC)
|
|
11
11
|
|
|
12
|
-
Classes for parsing and building SQL select queries in Node/Bun/
|
|
12
|
+
Classes for parsing and building SQL select queries in Node/Bun/Workers.
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
@@ -258,6 +258,8 @@ query.where('status', 'NOT IN', ['pending', 'approved']);
|
|
|
258
258
|
query.where({ status: ['pending', 'approved'] });
|
|
259
259
|
query.where({ 'status NOT IN': ['pending', 'approved'] });
|
|
260
260
|
query.where({ 'status !=': ['pending', 'approved'] });
|
|
261
|
+
// IN and NOT IN with an empty array add no condition at all
|
|
262
|
+
query.where('status', 'IN', []); // (omitted)
|
|
261
263
|
query.where({ 'status <>': ['pending', 'approved'] });
|
|
262
264
|
|
|
263
265
|
// Question marks for binding
|
package/dist/index.cjs
CHANGED
|
@@ -337,6 +337,10 @@ function substrCount(haystack, needle) {
|
|
|
337
337
|
// src/SelectBuilder/SelectBuilder.ts
|
|
338
338
|
var isNullish = (v) => v === void 0 || v === null || v === false || Object.is(NaN, v);
|
|
339
339
|
var defaultEngine = "mysql";
|
|
340
|
+
var trailingOperatorRegex = /\s(=|!=|<>|<=|>=|<|>|(?:NOT\s+)?IN|(?:NOT\s+)?I?LIKE(?:\s+%?\??%?)?|(?:NOT\s+)?BETWEEN)$/i;
|
|
341
|
+
function isStandaloneExpression(column) {
|
|
342
|
+
return /\s/.test(column) && !trailingOperatorRegex.test(column);
|
|
343
|
+
}
|
|
340
344
|
var SelectBuilder = class _SelectBuilder {
|
|
341
345
|
/**
|
|
342
346
|
* The list of values to bind to the query
|
|
@@ -406,9 +410,10 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
406
410
|
*/
|
|
407
411
|
constructor(sql, bindings = {}) {
|
|
408
412
|
if (sql) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const
|
|
413
|
+
const keys = bindings ? Object.keys(bindings) : [];
|
|
414
|
+
if (keys.length > 0) {
|
|
415
|
+
const names = keys.sort((a, b) => b.length - a.length).map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
416
|
+
const regexp = new RegExp(`(?<!:):(${names})\\b`, "g");
|
|
412
417
|
sql = sql.replace(regexp, ($0, $1) => {
|
|
413
418
|
this._bindings.push(bindings[$1]);
|
|
414
419
|
return `__BOUND_${this._bindings.length - 1}__`;
|
|
@@ -846,6 +851,8 @@ UNION
|
|
|
846
851
|
* this._conditions(this._wheres, ['price', '=', [10,20]]); // `price` IN(10,20)
|
|
847
852
|
* this._conditions(this._wheres, ['price', 'IN', [10,20]]); // `price` IN(10,20)
|
|
848
853
|
* this._conditions(this._wheres, ['price', 'NOT IN', [10,20]]); // `price` NOT IN(10,20)
|
|
854
|
+
* this._conditions(this._wheres, ['price', 'IN', []]); // (condition omitted)
|
|
855
|
+
* this._conditions(this._wheres, ['price', 'NOT IN', []]); // (condition omitted)
|
|
849
856
|
*/
|
|
850
857
|
_conditions(collection, criteria) {
|
|
851
858
|
const numArgs = criteria.length;
|
|
@@ -859,7 +866,7 @@ UNION
|
|
|
859
866
|
if (numArgs === 1) {
|
|
860
867
|
collection.push(column);
|
|
861
868
|
return this;
|
|
862
|
-
} else if (numArgs === 2 && Array.isArray(operator) && substrCount(column, "?") === operator.length) {
|
|
869
|
+
} else if (numArgs === 2 && Array.isArray(operator) && substrCount(column, "?") === operator.length && (operator.length > 0 || isStandaloneExpression(column))) {
|
|
863
870
|
const values = operator;
|
|
864
871
|
const sql = column.replace(/\?/g, () => {
|
|
865
872
|
this._bindings.push(values.shift());
|
|
@@ -907,6 +914,9 @@ UNION
|
|
|
907
914
|
operator === "=" ? `${column} IS NULL` : `${column} IS NOT NULL`
|
|
908
915
|
);
|
|
909
916
|
} else if (Array.isArray(value)) {
|
|
917
|
+
if (value.length === 0) {
|
|
918
|
+
return this;
|
|
919
|
+
}
|
|
910
920
|
const inVals = [];
|
|
911
921
|
for (let i = 0; i < value.length; i++) {
|
|
912
922
|
inVals.push(`__BOUND_${this._bindings.length}__`);
|
|
@@ -978,7 +988,9 @@ UNION
|
|
|
978
988
|
for (const [key, value] of Object.entries(conditions)) {
|
|
979
989
|
const one = [];
|
|
980
990
|
this._conditions(one, [key, value]);
|
|
981
|
-
|
|
991
|
+
if (one.length > 0) {
|
|
992
|
+
items.push(one[0]);
|
|
993
|
+
}
|
|
982
994
|
}
|
|
983
995
|
}
|
|
984
996
|
const joined = items.join(" OR ");
|
|
@@ -994,7 +1006,9 @@ UNION
|
|
|
994
1006
|
*/
|
|
995
1007
|
orWhere(conditions) {
|
|
996
1008
|
const joined = this._orConditions(conditions);
|
|
997
|
-
|
|
1009
|
+
if (joined !== "") {
|
|
1010
|
+
this.where(`(${joined})`);
|
|
1011
|
+
}
|
|
998
1012
|
return this;
|
|
999
1013
|
}
|
|
1000
1014
|
/**
|
|
@@ -1003,7 +1017,9 @@ UNION
|
|
|
1003
1017
|
*/
|
|
1004
1018
|
orHaving(conditions) {
|
|
1005
1019
|
const joined = this._orConditions(conditions);
|
|
1006
|
-
|
|
1020
|
+
if (joined !== "") {
|
|
1021
|
+
this.having(`(${joined})`);
|
|
1022
|
+
}
|
|
1007
1023
|
return this;
|
|
1008
1024
|
}
|
|
1009
1025
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -14,10 +14,6 @@ export type WhereCriteria = [
|
|
|
14
14
|
] | [
|
|
15
15
|
Record<string, any>
|
|
16
16
|
];
|
|
17
|
-
/**
|
|
18
|
-
* Build a select query
|
|
19
|
-
* Class Select
|
|
20
|
-
*/
|
|
21
17
|
export declare class SelectBuilder {
|
|
22
18
|
/**
|
|
23
19
|
* The list of values to bind to the query
|
|
@@ -306,6 +302,8 @@ export declare class SelectBuilder {
|
|
|
306
302
|
* this._conditions(this._wheres, ['price', '=', [10,20]]); // `price` IN(10,20)
|
|
307
303
|
* this._conditions(this._wheres, ['price', 'IN', [10,20]]); // `price` IN(10,20)
|
|
308
304
|
* this._conditions(this._wheres, ['price', 'NOT IN', [10,20]]); // `price` NOT IN(10,20)
|
|
305
|
+
* this._conditions(this._wheres, ['price', 'IN', []]); // (condition omitted)
|
|
306
|
+
* this._conditions(this._wheres, ['price', 'NOT IN', []]); // (condition omitted)
|
|
309
307
|
*/
|
|
310
308
|
_conditions(collection: string[], criteria: WhereCriteria): this;
|
|
311
309
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -297,6 +297,10 @@ function substrCount(haystack, needle) {
|
|
|
297
297
|
// src/SelectBuilder/SelectBuilder.ts
|
|
298
298
|
var isNullish = (v) => v === void 0 || v === null || v === false || Object.is(NaN, v);
|
|
299
299
|
var defaultEngine = "mysql";
|
|
300
|
+
var trailingOperatorRegex = /\s(=|!=|<>|<=|>=|<|>|(?:NOT\s+)?IN|(?:NOT\s+)?I?LIKE(?:\s+%?\??%?)?|(?:NOT\s+)?BETWEEN)$/i;
|
|
301
|
+
function isStandaloneExpression(column) {
|
|
302
|
+
return /\s/.test(column) && !trailingOperatorRegex.test(column);
|
|
303
|
+
}
|
|
300
304
|
var SelectBuilder = class _SelectBuilder {
|
|
301
305
|
/**
|
|
302
306
|
* The list of values to bind to the query
|
|
@@ -366,9 +370,10 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
366
370
|
*/
|
|
367
371
|
constructor(sql, bindings = {}) {
|
|
368
372
|
if (sql) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
373
|
+
const keys = bindings ? Object.keys(bindings) : [];
|
|
374
|
+
if (keys.length > 0) {
|
|
375
|
+
const names = keys.sort((a, b) => b.length - a.length).map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
376
|
+
const regexp = new RegExp(`(?<!:):(${names})\\b`, "g");
|
|
372
377
|
sql = sql.replace(regexp, ($0, $1) => {
|
|
373
378
|
this._bindings.push(bindings[$1]);
|
|
374
379
|
return `__BOUND_${this._bindings.length - 1}__`;
|
|
@@ -806,6 +811,8 @@ UNION
|
|
|
806
811
|
* this._conditions(this._wheres, ['price', '=', [10,20]]); // `price` IN(10,20)
|
|
807
812
|
* this._conditions(this._wheres, ['price', 'IN', [10,20]]); // `price` IN(10,20)
|
|
808
813
|
* this._conditions(this._wheres, ['price', 'NOT IN', [10,20]]); // `price` NOT IN(10,20)
|
|
814
|
+
* this._conditions(this._wheres, ['price', 'IN', []]); // (condition omitted)
|
|
815
|
+
* this._conditions(this._wheres, ['price', 'NOT IN', []]); // (condition omitted)
|
|
809
816
|
*/
|
|
810
817
|
_conditions(collection, criteria) {
|
|
811
818
|
const numArgs = criteria.length;
|
|
@@ -819,7 +826,7 @@ UNION
|
|
|
819
826
|
if (numArgs === 1) {
|
|
820
827
|
collection.push(column);
|
|
821
828
|
return this;
|
|
822
|
-
} else if (numArgs === 2 && Array.isArray(operator) && substrCount(column, "?") === operator.length) {
|
|
829
|
+
} else if (numArgs === 2 && Array.isArray(operator) && substrCount(column, "?") === operator.length && (operator.length > 0 || isStandaloneExpression(column))) {
|
|
823
830
|
const values = operator;
|
|
824
831
|
const sql = column.replace(/\?/g, () => {
|
|
825
832
|
this._bindings.push(values.shift());
|
|
@@ -867,6 +874,9 @@ UNION
|
|
|
867
874
|
operator === "=" ? `${column} IS NULL` : `${column} IS NOT NULL`
|
|
868
875
|
);
|
|
869
876
|
} else if (Array.isArray(value)) {
|
|
877
|
+
if (value.length === 0) {
|
|
878
|
+
return this;
|
|
879
|
+
}
|
|
870
880
|
const inVals = [];
|
|
871
881
|
for (let i = 0; i < value.length; i++) {
|
|
872
882
|
inVals.push(`__BOUND_${this._bindings.length}__`);
|
|
@@ -938,7 +948,9 @@ UNION
|
|
|
938
948
|
for (const [key, value] of Object.entries(conditions)) {
|
|
939
949
|
const one = [];
|
|
940
950
|
this._conditions(one, [key, value]);
|
|
941
|
-
|
|
951
|
+
if (one.length > 0) {
|
|
952
|
+
items.push(one[0]);
|
|
953
|
+
}
|
|
942
954
|
}
|
|
943
955
|
}
|
|
944
956
|
const joined = items.join(" OR ");
|
|
@@ -954,7 +966,9 @@ UNION
|
|
|
954
966
|
*/
|
|
955
967
|
orWhere(conditions) {
|
|
956
968
|
const joined = this._orConditions(conditions);
|
|
957
|
-
|
|
969
|
+
if (joined !== "") {
|
|
970
|
+
this.where(`(${joined})`);
|
|
971
|
+
}
|
|
958
972
|
return this;
|
|
959
973
|
}
|
|
960
974
|
/**
|
|
@@ -963,7 +977,9 @@ UNION
|
|
|
963
977
|
*/
|
|
964
978
|
orHaving(conditions) {
|
|
965
979
|
const joined = this._orConditions(conditions);
|
|
966
|
-
|
|
980
|
+
if (joined !== "") {
|
|
981
|
+
this.having(`(${joined})`);
|
|
982
|
+
}
|
|
967
983
|
return this;
|
|
968
984
|
}
|
|
969
985
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sql-love",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Build SQL queries with JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@types/bun": "latest",
|
|
56
56
|
"@types/node": "latest",
|
|
57
57
|
"dts-bundle-generator": "9.5.1",
|
|
58
|
-
"prettier": "3.9.
|
|
58
|
+
"prettier": "3.9.10",
|
|
59
59
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
60
60
|
"typescript": "5.9.3"
|
|
61
61
|
}
|