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 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.4" width="250" height="208" />
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
- [![Language](https://badgen.net/static/language/TS?v=1.1.4)](https://github.com/search?q=repo:kensnyder/sql-love++language:TypeScript&type=code)
6
- [![Code Coverage](https://codecov.io/gh/kensnyder/sql-love/branch/main/graph/badge.svg?v=1.1.4)](https://codecov.io/gh/kensnyder/sql-love)
5
+ [![Language](https://badgen.net/static/language/TS?v=1.2.0)](https://github.com/search?q=repo:kensnyder/sql-love++language:TypeScript&type=code)
6
+ [![Code Coverage](https://codecov.io/gh/kensnyder/sql-love/branch/main/graph/badge.svg?v=1.2.0)](https://codecov.io/gh/kensnyder/sql-love)
7
7
  ![Dependencies](https://badgen.net/static/dependencies/0/green)
8
8
  ![Tree shakeable](https://badgen.net/static/tree%20shakeable/Yes/green)
9
9
  ![Gzip Size](https://badgen.net/static/gzip%20size/9kb/blue)
10
- [![ISC License](https://badgen.net/static/license/ISC/green?v=1.1.4)](https://opensource.org/licenses/ISC)
10
+ [![ISC License](https://badgen.net/static/license/ISC/green?v=1.2.0)](https://opensource.org/licenses/ISC)
11
11
 
12
- Classes for parsing and building SQL select queries in Node/Bun/Deno.
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
- if (bindings) {
410
- const keys = Object.keys(bindings);
411
- const regexp = new RegExp(`:(${keys.join("|")})`, "g");
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
- items.push(one[0]);
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
- this.where(`(${joined})`);
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
- this.having(`(${joined})`);
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
- if (bindings) {
370
- const keys = Object.keys(bindings);
371
- const regexp = new RegExp(`:(${keys.join("|")})`, "g");
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
- items.push(one[0]);
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
- this.where(`(${joined})`);
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
- this.having(`(${joined})`);
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.1.4",
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.9",
58
+ "prettier": "3.9.10",
59
59
  "prettier-plugin-organize-imports": "4.3.0",
60
60
  "typescript": "5.9.3"
61
61
  }