@stamhoofd/sql 2.17.0 → 2.18.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.
@@ -1,5 +1,5 @@
1
1
  import { SimpleError } from "@simonbackx/simple-errors";
2
- import { StamhoofdFilter } from "@stamhoofd/structures";
2
+ import { StamhoofdCompareValue, StamhoofdFilter } from "@stamhoofd/structures";
3
3
  import { SQL } from "../SQL";
4
4
  import { SQLExpression } from "../SQLExpression";
5
5
  import { SQLArray, SQLCast, SQLColumnExpression, SQLNull, SQLSafeValue, SQLScalarValue, scalarToSQLExpression, scalarToSQLJSONExpression } from "../SQLExpressions";
@@ -25,17 +25,72 @@ export function notSQLFilterCompiler(filter: StamhoofdFilter, filters: SQLFilter
25
25
  return new SQLWhereNot(andRunner)
26
26
  }
27
27
 
28
- function guardScalar(s: any): asserts s is SQLScalarValue|null {
29
- if (typeof s !== 'string' && typeof s !== 'number' && typeof s !== 'boolean' && !(s instanceof Date) && s !== null) {
30
- throw new Error('Invalid scalar value')
28
+ function guardFilterCompareValue(val: any): StamhoofdCompareValue {
29
+ if (val instanceof Date) {
30
+ return val
31
+ }
32
+
33
+ if (typeof val === 'string') {
34
+ return val
35
+ }
36
+
37
+ if (typeof val === 'number') {
38
+ return val
39
+ }
40
+
41
+ if (typeof val === 'boolean') {
42
+ return val;
43
+ }
44
+
45
+ if (val === null) {
46
+ return null;
47
+ }
48
+
49
+ if (typeof val === 'object' && "$" in val) {
50
+ if (val["$"] === '$now') {
51
+ return val;
52
+ }
53
+ }
54
+
55
+ throw new Error('Invalid compare value. Expected a string, number, boolean, date or null.')
56
+ }
57
+
58
+ function doNormalizeValue(val: StamhoofdCompareValue): string|number|Date|null {
59
+ if (val instanceof Date) {
60
+ return val
61
+ }
62
+
63
+ if (typeof val === 'string') {
64
+ return val.toLocaleLowerCase()
65
+ }
66
+
67
+ if (typeof val === 'boolean') {
68
+ return val === true ? 1 : 0;
69
+ }
70
+
71
+ if (val === null) {
72
+ return null;
73
+ }
74
+
75
+ if (typeof val === 'object' && "$" in val) {
76
+ const specialValue = val["$"];
77
+
78
+ switch (specialValue) {
79
+ case '$now':
80
+ return doNormalizeValue(new Date())
81
+ default:
82
+ throw new Error('Unsupported magic value ' + specialValue)
83
+ }
31
84
  }
32
85
 
86
+ return val;
33
87
  }
34
88
 
35
- function guardNotNullScalar(s: any): asserts s is SQLScalarValue {
36
- if (typeof s !== 'string' && typeof s !== 'number' && typeof s !== 'boolean' && !(s instanceof Date)) {
89
+ function guardScalar(s: any): asserts s is SQLScalarValue|null {
90
+ if (typeof s !== 'string' && typeof s !== 'number' && typeof s !== 'boolean' && !(s instanceof Date) && s !== null) {
37
91
  throw new Error('Invalid scalar value')
38
92
  }
93
+
39
94
  }
40
95
 
41
96
  function guardString(s: any): asserts s is string {
@@ -68,7 +123,11 @@ export function createSQLFilterNamespace(definitions: SQLFilterDefinitions): SQL
68
123
  type SQLExpressionFilterOptions = {normalizeValue?: (v: SQLScalarValue|null) => SQLScalarValue|null, isJSONValue?: boolean, isJSONObject?: boolean, nullable?: boolean}
69
124
 
70
125
  export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression, {normalizeValue, isJSONObject = false, isJSONValue = false, nullable = false}: SQLExpressionFilterOptions = {}): SQLFilterCompiler {
71
- const norm = normalizeValue ?? ((v) => v);
126
+ normalizeValue = normalizeValue ?? ((v) => v);
127
+ const norm = (val: any) => {
128
+ const n = doNormalizeValue(guardFilterCompareValue(val));
129
+ return normalizeValue(n);
130
+ }
72
131
  const convertToExpression = isJSONValue ? scalarToSQLJSONExpression : scalarToSQLExpression
73
132
 
74
133
  return (filter: StamhoofdFilter, filters: SQLFilterDefinitions) => {
@@ -83,11 +142,9 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
83
142
  }
84
143
 
85
144
 
86
- const f = filter as any;
145
+ const f = filter;
87
146
 
88
147
  if ('$eq' in f) {
89
- guardScalar(f.$eq);
90
-
91
148
  if (isJSONObject) {
92
149
  const v = norm(f.$eq);
93
150
 
@@ -100,13 +157,9 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
100
157
  // }
101
158
 
102
159
  // else
103
- return new SQLWhereEqual(
104
- new SQLJsonContains(
105
- sqlExpression,
106
- convertToExpression(JSON.stringify(v))
107
- ),
108
- SQLWhereSign.Equal,
109
- new SQLSafeValue(1)
160
+ return new SQLJsonContains(
161
+ sqlExpression,
162
+ convertToExpression(JSON.stringify(v))
110
163
  );
111
164
  }
112
165
  return new SQLWhereEqual(sqlExpression, SQLWhereSign.Equal, convertToExpression(norm(f.$eq)));
@@ -133,25 +186,17 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
133
186
  // that makes comparing more difficult, to combat this, we still need to use SQLJsonOverlaps with the JSON null value
134
187
  return new SQLWhereOr([
135
188
  new SQLWhereEqual(sqlExpression, SQLWhereSign.Equal, new SQLNull()), // checks path not exists (= mysql null)
136
- new SQLWhereEqual(
137
- new SQLJsonOverlaps(
138
- sqlExpression,
139
- convertToExpression(JSON.stringify(v)) // contains json null
140
- ),
141
- SQLWhereSign.Equal,
142
- new SQLSafeValue(1)
189
+ new SQLJsonOverlaps(
190
+ sqlExpression,
191
+ convertToExpression(JSON.stringify(v)) // contains json null
143
192
  )
144
193
  ]);
145
194
  }
146
195
 
147
196
  // else
148
- return new SQLWhereEqual(
149
- new SQLJsonOverlaps(
150
- sqlExpression,
151
- convertToExpression(JSON.stringify(v))
152
- ),
153
- SQLWhereSign.Equal,
154
- new SQLSafeValue(1)
197
+ return new SQLJsonOverlaps(
198
+ sqlExpression,
199
+ convertToExpression(JSON.stringify(v))
155
200
  );
156
201
  }
157
202
 
@@ -165,30 +210,24 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
165
210
  new SQLWhereEqual(sqlExpression, SQLWhereSign.Equal, new SQLArray(remaining))
166
211
  ]);
167
212
  }
168
- return new SQLWhereEqual(sqlExpression, SQLWhereSign.Equal, new SQLArray(v));
213
+ return new SQLWhereEqual(sqlExpression, SQLWhereSign.Equal, new SQLArray(v as SQLScalarValue[]));
169
214
  }
170
215
 
171
216
  if ('$neq' in f) {
172
- guardScalar(f.$neq);
173
-
174
217
  if (isJSONObject) {
175
- const v = norm(f.$eq);
218
+ const v = norm(f.$neq);
176
219
 
177
- return new SQLWhereEqual(
220
+ return new SQLWhereNot(
178
221
  new SQLJsonContains(
179
222
  sqlExpression,
180
223
  convertToExpression(JSON.stringify(v))
181
- ),
182
- SQLWhereSign.Equal,
183
- new SQLSafeValue(0)
224
+ )
184
225
  );
185
226
  }
186
227
  return new SQLWhereEqual(sqlExpression, SQLWhereSign.NotEqual, convertToExpression(norm(f.$neq)));
187
228
  }
188
229
 
189
230
  if ('$gt' in f) {
190
- guardScalar(f.$gt);
191
-
192
231
  if (isJSONObject) {
193
232
  throw new Error('Greater than is not supported in this place')
194
233
  }
@@ -203,8 +242,6 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
203
242
  }
204
243
 
205
244
  if ('$gte' in f) {
206
- guardScalar(f.$gte);
207
-
208
245
  if (isJSONObject) {
209
246
  throw new Error('Greater than is not supported in this place')
210
247
  }
@@ -217,8 +254,6 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
217
254
  }
218
255
 
219
256
  if ('$lte' in f) {
220
- guardScalar(f.$lte);
221
-
222
257
  if (isJSONObject) {
223
258
  throw new Error('Greater than is not supported in this place')
224
259
  }
@@ -243,8 +278,6 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
243
278
 
244
279
 
245
280
  if ('$lt' in f) {
246
- guardScalar(f.$lt);
247
-
248
281
  if (isJSONObject) {
249
282
  throw new Error('Less than is not supported in this place')
250
283
  }
@@ -268,7 +301,11 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
268
301
  }
269
302
 
270
303
  if ('$contains' in f) {
271
- guardString(f.$contains);
304
+ const needle = norm(f.$contains);
305
+
306
+ if (typeof needle !== 'string') {
307
+ throw new Error('Invalid needle for contains filter')
308
+ }
272
309
 
273
310
  if (isJSONObject) {
274
311
  return new SQLWhereEqual(
@@ -276,7 +313,7 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
276
313
  sqlExpression,
277
314
  'one',
278
315
  convertToExpression(
279
- '%'+SQLWhereLike.escape(f.$contains)+'%'
316
+ '%'+SQLWhereLike.escape(needle)+'%'
280
317
  )
281
318
  ),
282
319
  SQLWhereSign.NotEqual,
@@ -289,7 +326,7 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
289
326
  return new SQLWhereLike(
290
327
  new SQLCast(new SQLJsonUnquote(sqlExpression), 'CHAR'),
291
328
  convertToExpression(
292
- '%'+SQLWhereLike.escape(f.$contains)+'%'
329
+ '%'+SQLWhereLike.escape(needle)+'%'
293
330
  )
294
331
  );
295
332
  }
@@ -297,7 +334,7 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
297
334
  return new SQLWhereLike(
298
335
  sqlExpression,
299
336
  convertToExpression(
300
- '%'+SQLWhereLike.escape(f.$contains)+'%'
337
+ '%'+SQLWhereLike.escape(needle)+'%'
301
338
  )
302
339
  );
303
340
  }
@@ -2,6 +2,7 @@ import { PlainObject } from "@simonbackx/simple-encoding";
2
2
  import { SortDefinition, SortList } from "@stamhoofd/structures";
3
3
 
4
4
  import { SQLOrderBy, SQLOrderByDirection } from "../SQLOrderBy";
5
+ import { SimpleError } from "@simonbackx/simple-errors";
5
6
 
6
7
  export type SQLSortDefinition<T, B extends PlainObject = PlainObject> = SortDefinition<T, B> & {
7
8
  toSQL(direction: SQLOrderByDirection): SQLOrderBy
@@ -10,6 +11,13 @@ export type SQLSortDefinition<T, B extends PlainObject = PlainObject> = SortDefi
10
11
  export type SQLSortDefinitions<T = any> = Record<string, SQLSortDefinition<T>>
11
12
 
12
13
  export function compileToSQLSorter(sortBy: SortList, definitions: SQLSortDefinitions): SQLOrderBy {
14
+ if (sortBy.length === 0 ){
15
+ throw new SimpleError({
16
+ code: 'empty_sort',
17
+ message: 'No sort passed'
18
+ })
19
+ }
20
+
13
21
  const sorters: SQLOrderBy[] = [];
14
22
 
15
23
  for (const s of sortBy) {