@stamhoofd/sql 2.83.5 → 2.84.1
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/src/QueryableModel.d.ts +1 -0
- package/dist/src/QueryableModel.d.ts.map +1 -1
- package/dist/src/QueryableModel.js +20 -2
- package/dist/src/QueryableModel.js.map +1 -1
- package/dist/src/SQL.d.ts +9 -16
- package/dist/src/SQL.d.ts.map +1 -1
- package/dist/src/SQL.js +16 -13
- package/dist/src/SQL.js.map +1 -1
- package/dist/src/SQLDelete.d.ts +2 -2
- package/dist/src/SQLDelete.d.ts.map +1 -1
- package/dist/src/SQLDelete.js +13 -3
- package/dist/src/SQLDelete.js.map +1 -1
- package/dist/src/SQLExpression.d.ts +3 -0
- package/dist/src/SQLExpression.d.ts.map +1 -1
- package/dist/src/SQLExpression.js.map +1 -1
- package/dist/src/SQLExpressions.d.ts +17 -6
- package/dist/src/SQLExpressions.d.ts.map +1 -1
- package/dist/src/SQLExpressions.js +20 -12
- package/dist/src/SQLExpressions.js.map +1 -1
- package/dist/src/SQLJoin.d.ts +3 -3
- package/dist/src/SQLJoin.d.ts.map +1 -1
- package/dist/src/SQLJoin.js +5 -1
- package/dist/src/SQLJoin.js.map +1 -1
- package/dist/src/SQLJsonExpressions.d.ts +22 -0
- package/dist/src/SQLJsonExpressions.d.ts.map +1 -1
- package/dist/src/SQLJsonExpressions.js +56 -4
- package/dist/src/SQLJsonExpressions.js.map +1 -1
- package/dist/src/SQLSelect.d.ts +10 -4
- package/dist/src/SQLSelect.d.ts.map +1 -1
- package/dist/src/SQLSelect.js +25 -11
- package/dist/src/SQLSelect.js.map +1 -1
- package/dist/src/SQLWhere.d.ts +29 -1
- package/dist/src/SQLWhere.d.ts.map +1 -1
- package/dist/src/SQLWhere.js +181 -12
- package/dist/src/SQLWhere.js.map +1 -1
- package/dist/src/filters/SQLFilter.d.ts +1 -0
- package/dist/src/filters/SQLFilter.d.ts.map +1 -1
- package/dist/src/filters/SQLFilter.js +8 -0
- package/dist/src/filters/SQLFilter.js.map +1 -1
- package/dist/src/filters/modern/SQLModernFilter.d.ts +73 -0
- package/dist/src/filters/modern/SQLModernFilter.d.ts.map +1 -0
- package/dist/src/filters/modern/SQLModernFilter.js +200 -0
- package/dist/src/filters/modern/SQLModernFilter.js.map +1 -0
- package/dist/src/filters/modern/compilers/contains.d.ts +4 -0
- package/dist/src/filters/modern/compilers/contains.d.ts.map +1 -0
- package/dist/src/filters/modern/compilers/contains.js +28 -0
- package/dist/src/filters/modern/compilers/contains.js.map +1 -0
- package/dist/src/filters/modern/compilers/equals.d.ts +4 -0
- package/dist/src/filters/modern/compilers/equals.d.ts.map +1 -0
- package/dist/src/filters/modern/compilers/equals.js +46 -0
- package/dist/src/filters/modern/compilers/equals.js.map +1 -0
- package/dist/src/filters/modern/compilers/greater.d.ts +4 -0
- package/dist/src/filters/modern/compilers/greater.d.ts.map +1 -0
- package/dist/src/filters/modern/compilers/greater.js +17 -0
- package/dist/src/filters/modern/compilers/greater.js.map +1 -0
- package/dist/src/filters/modern/compilers/in.d.ts +4 -0
- package/dist/src/filters/modern/compilers/in.d.ts.map +1 -0
- package/dist/src/filters/modern/compilers/in.js +50 -0
- package/dist/src/filters/modern/compilers/in.js.map +1 -0
- package/dist/src/filters/modern/compilers/index.d.ts +5 -0
- package/dist/src/filters/modern/compilers/index.d.ts.map +1 -0
- package/dist/src/filters/modern/compilers/index.js +8 -0
- package/dist/src/filters/modern/compilers/index.js.map +1 -0
- package/dist/src/filters/modern/compilers/less.d.ts +4 -0
- package/dist/src/filters/modern/compilers/less.d.ts.map +1 -0
- package/dist/src/filters/modern/compilers/less.js +17 -0
- package/dist/src/filters/modern/compilers/less.js.map +1 -0
- package/dist/src/filters/modern/helpers/isJSONColumn.d.ts +4 -0
- package/dist/src/filters/modern/helpers/isJSONColumn.d.ts.map +1 -0
- package/dist/src/filters/modern/helpers/isJSONColumn.js +16 -0
- package/dist/src/filters/modern/helpers/isJSONColumn.js.map +1 -0
- package/dist/src/filters/modern/helpers/normalizeCompareValue.d.ts +9 -0
- package/dist/src/filters/modern/helpers/normalizeCompareValue.d.ts.map +1 -0
- package/dist/src/filters/modern/helpers/normalizeCompareValue.js +82 -0
- package/dist/src/filters/modern/helpers/normalizeCompareValue.js.map +1 -0
- package/dist/tests/filters/$and.test.d.ts +2 -0
- package/dist/tests/filters/$and.test.d.ts.map +1 -0
- package/dist/tests/filters/$and.test.js +185 -0
- package/dist/tests/filters/$and.test.js.map +1 -0
- package/dist/tests/filters/$contains.test.d.ts +2 -0
- package/dist/tests/filters/$contains.test.d.ts.map +1 -0
- package/dist/tests/filters/$contains.test.js +701 -0
- package/dist/tests/filters/$contains.test.js.map +1 -0
- package/dist/tests/filters/$eq.test.d.ts +2 -0
- package/dist/tests/filters/$eq.test.d.ts.map +1 -0
- package/dist/tests/filters/$eq.test.js +986 -0
- package/dist/tests/filters/$eq.test.js.map +1 -0
- package/dist/tests/filters/$gt.test.d.ts +2 -0
- package/dist/tests/filters/$gt.test.d.ts.map +1 -0
- package/dist/tests/filters/$gt.test.js +463 -0
- package/dist/tests/filters/$gt.test.js.map +1 -0
- package/dist/tests/filters/$gte.test.d.ts +2 -0
- package/dist/tests/filters/$gte.test.d.ts.map +1 -0
- package/dist/tests/filters/$gte.test.js +433 -0
- package/dist/tests/filters/$gte.test.js.map +1 -0
- package/dist/tests/filters/$in.test.d.ts +2 -0
- package/dist/tests/filters/$in.test.d.ts.map +1 -0
- package/dist/tests/filters/$in.test.js +590 -0
- package/dist/tests/filters/$in.test.js.map +1 -0
- package/dist/tests/filters/$lt.test.d.ts +2 -0
- package/dist/tests/filters/$lt.test.d.ts.map +1 -0
- package/dist/tests/filters/$lt.test.js +433 -0
- package/dist/tests/filters/$lt.test.js.map +1 -0
- package/dist/tests/filters/$lte.test.d.ts +2 -0
- package/dist/tests/filters/$lte.test.d.ts.map +1 -0
- package/dist/tests/filters/$lte.test.js +472 -0
- package/dist/tests/filters/$lte.test.js.map +1 -0
- package/dist/tests/filters/$neq.test.d.ts +2 -0
- package/dist/tests/filters/$neq.test.d.ts.map +1 -0
- package/dist/tests/filters/$neq.test.js +32 -0
- package/dist/tests/filters/$neq.test.js.map +1 -0
- package/dist/tests/filters/$not.test.d.ts +2 -0
- package/dist/tests/filters/$not.test.d.ts.map +1 -0
- package/dist/tests/filters/$not.test.js +50 -0
- package/dist/tests/filters/$not.test.js.map +1 -0
- package/dist/tests/filters/$or.test.d.ts +2 -0
- package/dist/tests/filters/$or.test.d.ts.map +1 -0
- package/dist/tests/filters/$or.test.js +185 -0
- package/dist/tests/filters/$or.test.js.map +1 -0
- package/dist/tests/filters/dot-syntax.test.d.ts +2 -0
- package/dist/tests/filters/dot-syntax.test.d.ts.map +1 -0
- package/dist/tests/filters/dot-syntax.test.js +210 -0
- package/dist/tests/filters/dot-syntax.test.js.map +1 -0
- package/dist/tests/filters/exists.test.d.ts +2 -0
- package/dist/tests/filters/exists.test.d.ts.map +1 -0
- package/dist/tests/filters/exists.test.js +106 -0
- package/dist/tests/filters/exists.test.js.map +1 -0
- package/dist/tests/filters/joined-relations.test.d.ts +2 -0
- package/dist/tests/filters/joined-relations.test.d.ts.map +1 -0
- package/dist/tests/filters/joined-relations.test.js +167 -0
- package/dist/tests/filters/joined-relations.test.js.map +1 -0
- package/dist/tests/filters/special-cases.test.d.ts +2 -0
- package/dist/tests/filters/special-cases.test.d.ts.map +1 -0
- package/dist/tests/filters/special-cases.test.js +114 -0
- package/dist/tests/filters/special-cases.test.js.map +1 -0
- package/dist/tests/filters/wildcard.test.d.ts +2 -0
- package/dist/tests/filters/wildcard.test.d.ts.map +1 -0
- package/dist/tests/filters/wildcard.test.js +67 -0
- package/dist/tests/filters/wildcard.test.js.map +1 -0
- package/dist/tests/jest.global.setup.d.ts +3 -0
- package/dist/tests/jest.global.setup.d.ts.map +1 -0
- package/dist/tests/jest.global.setup.js +7 -0
- package/dist/tests/jest.global.setup.js.map +1 -0
- package/dist/tests/jest.setup.d.ts +2 -0
- package/dist/tests/jest.setup.d.ts.map +1 -0
- package/dist/tests/jest.setup.js +5 -0
- package/dist/tests/jest.setup.js.map +1 -0
- package/dist/tests/utils/index.d.ts +57 -0
- package/dist/tests/utils/index.d.ts.map +1 -0
- package/dist/tests/utils/index.js +206 -0
- package/dist/tests/utils/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/src/QueryableModel.ts +22 -2
- package/src/SQL.ts +21 -30
- package/src/SQLDelete.ts +26 -15
- package/src/SQLExpression.ts +4 -0
- package/src/SQLExpressions.ts +23 -14
- package/src/SQLJoin.ts +8 -4
- package/src/SQLJsonExpressions.ts +65 -4
- package/src/SQLSelect.ts +31 -15
- package/src/SQLWhere.ts +208 -13
- package/src/filters/SQLFilter.ts +8 -0
- package/src/filters/modern/SQLModernFilter.ts +256 -0
- package/src/filters/modern/compilers/contains.ts +43 -0
- package/src/filters/modern/compilers/equals.ts +72 -0
- package/src/filters/modern/compilers/greater.ts +20 -0
- package/src/filters/modern/compilers/in.ts +62 -0
- package/src/filters/modern/compilers/index.ts +4 -0
- package/src/filters/modern/compilers/less.ts +19 -0
- package/src/filters/modern/helpers/isJSONColumn.ts +13 -0
- package/src/filters/modern/helpers/normalizeCompareValue.ts +95 -0
package/src/SQLSelect.ts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
import { Database, SQLResultNamespacedRow } from '@simonbackx/simple-database';
|
|
2
|
-
import {
|
|
2
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
3
|
+
import { SQLExpression, SQLExpressionOptions, SQLNamedExpression, SQLQuery, joinSQLQuery, normalizeSQLQuery } from './SQLExpression';
|
|
3
4
|
import { SQLAlias, SQLColumnExpression, SQLCount, SQLSelectAs, SQLSum, SQLTableExpression } from './SQLExpressions';
|
|
4
5
|
import { SQLJoin } from './SQLJoin';
|
|
5
6
|
import { Orderable } from './SQLOrderBy';
|
|
6
7
|
import { Whereable } from './SQLWhere';
|
|
7
|
-
import { Formatter } from '@stamhoofd/utility';
|
|
8
8
|
|
|
9
9
|
class EmptyClass {}
|
|
10
10
|
|
|
11
|
-
export function parseTable(
|
|
12
|
-
if (
|
|
13
|
-
return new SQLTableExpression(
|
|
14
|
-
}
|
|
15
|
-
else if (typeof tableOrExpressiongOrNamespace === 'string') {
|
|
16
|
-
return new SQLTableExpression(tableOrExpressiongOrNamespace);
|
|
11
|
+
export function parseTable(tableOrExpression: SQLNamedExpression | string, asNamespace?: string): SQLNamedExpression {
|
|
12
|
+
if (typeof tableOrExpression === 'string') {
|
|
13
|
+
return new SQLTableExpression(tableOrExpression, asNamespace);
|
|
17
14
|
}
|
|
18
15
|
else {
|
|
19
|
-
return
|
|
16
|
+
return tableOrExpression;
|
|
20
17
|
}
|
|
21
18
|
}
|
|
22
19
|
|
|
@@ -34,7 +31,7 @@ export type IterableSQLSelectOptions = {
|
|
|
34
31
|
|
|
35
32
|
export class SQLSelect<T extends object = SQLResultNamespacedRow> extends Whereable(Orderable(EmptyClass)) implements SQLExpression {
|
|
36
33
|
_columns: SQLExpression[];
|
|
37
|
-
_from:
|
|
34
|
+
_from: SQLNamedExpression;
|
|
38
35
|
|
|
39
36
|
_limit: number | null = null;
|
|
40
37
|
_offset: number | null = null;
|
|
@@ -63,8 +60,8 @@ export class SQLSelect<T extends object = SQLResultNamespacedRow> extends Wherea
|
|
|
63
60
|
|
|
64
61
|
from(namespace: string, table: string): this;
|
|
65
62
|
from(table: string): this;
|
|
66
|
-
from(expression:
|
|
67
|
-
from(tableOrExpressiongOrNamespace:
|
|
63
|
+
from(expression: SQLNamedExpression): this;
|
|
64
|
+
from(tableOrExpressiongOrNamespace: SQLNamedExpression | string, table?: string): this {
|
|
68
65
|
this._from = parseTable(tableOrExpressiongOrNamespace, table);
|
|
69
66
|
|
|
70
67
|
return this;
|
|
@@ -102,7 +99,7 @@ export class SQLSelect<T extends object = SQLResultNamespacedRow> extends Wherea
|
|
|
102
99
|
// Create a clone since we are mutating the default namespaces
|
|
103
100
|
const parentOptions = options;
|
|
104
101
|
options = options ? { ...options } : {};
|
|
105
|
-
options.defaultNamespace =
|
|
102
|
+
options.defaultNamespace = this._from.getName();
|
|
106
103
|
|
|
107
104
|
if (parentOptions?.defaultNamespace) {
|
|
108
105
|
options.parentNamespace = parentOptions.defaultNamespace;
|
|
@@ -128,8 +125,14 @@ export class SQLSelect<T extends object = SQLResultNamespacedRow> extends Wherea
|
|
|
128
125
|
|
|
129
126
|
// Where
|
|
130
127
|
if (this._where) {
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
const always = this._where.isAlways;
|
|
129
|
+
if (always === false) {
|
|
130
|
+
throw new Error('Cannot use SQLSelect with a where that is not always true');
|
|
131
|
+
}
|
|
132
|
+
else if (always === null) {
|
|
133
|
+
query.push('WHERE');
|
|
134
|
+
query.push(this._where.getSQL(options));
|
|
135
|
+
}
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
if (this._groupBy.length > 0) {
|
|
@@ -156,6 +159,15 @@ export class SQLSelect<T extends object = SQLResultNamespacedRow> extends Wherea
|
|
|
156
159
|
return joinSQLQuery(query, ' ');
|
|
157
160
|
}
|
|
158
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Returns true when it know all results will be included without filtering.
|
|
164
|
+
* Returns false when it knows no single result will be included.
|
|
165
|
+
* Null when it does not know.
|
|
166
|
+
*/
|
|
167
|
+
get isAlways() {
|
|
168
|
+
return this._where ? this._where.isAlways : true;
|
|
169
|
+
}
|
|
170
|
+
|
|
159
171
|
limit(limit: number | null, offset: number | null = null): this {
|
|
160
172
|
this._limit = limit;
|
|
161
173
|
this._offset = offset;
|
|
@@ -163,6 +175,10 @@ export class SQLSelect<T extends object = SQLResultNamespacedRow> extends Wherea
|
|
|
163
175
|
}
|
|
164
176
|
|
|
165
177
|
async fetch(): Promise<T[]> {
|
|
178
|
+
if (this._where && this._where.isAlways === false) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
|
|
166
182
|
const { query, params } = normalizeSQLQuery(this.getSQL());
|
|
167
183
|
|
|
168
184
|
// when debugging: log all queries
|
package/src/SQLWhere.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SQLExpression, SQLExpressionOptions, SQLQuery, joinSQLQuery, normalizeSQLQuery } from './SQLExpression';
|
|
2
2
|
import { SQLArray, SQLColumnExpression, SQLDynamicExpression, SQLNull, readDynamicSQLExpression } from './SQLExpressions';
|
|
3
|
-
import { SQLJoin } from './SQLJoin';
|
|
3
|
+
import { SQLJoin, SQLJoinType } from './SQLJoin';
|
|
4
|
+
import { SQLSelect } from './SQLSelect';
|
|
4
5
|
|
|
5
6
|
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
6
7
|
|
|
@@ -121,6 +122,18 @@ export abstract class SQLWhere implements SQLExpression {
|
|
|
121
122
|
return false;
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
get isAlways(): boolean | null {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get isAlwaysTrue(): boolean {
|
|
130
|
+
return this.isAlways === true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get isAlwaysFalse(): boolean {
|
|
134
|
+
return this.isAlways === false;
|
|
135
|
+
}
|
|
136
|
+
|
|
124
137
|
abstract getSQL(options?: SQLExpressionOptions): SQLQuery;
|
|
125
138
|
getJoins(): SQLJoin[] {
|
|
126
139
|
return [];
|
|
@@ -151,12 +164,17 @@ export class SQLEmptyWhere extends SQLWhere {
|
|
|
151
164
|
getSQL(options?: SQLExpressionOptions): SQLQuery {
|
|
152
165
|
throw new Error('Empty where');
|
|
153
166
|
}
|
|
167
|
+
|
|
168
|
+
get isAlways() {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
154
171
|
}
|
|
155
172
|
|
|
156
173
|
export class SQLWhereEqual extends SQLWhere {
|
|
157
174
|
column: SQLExpression;
|
|
158
175
|
sign = SQLWhereSign.Equal;
|
|
159
176
|
value: SQLExpression;
|
|
177
|
+
nullable = false;
|
|
160
178
|
|
|
161
179
|
static parseWhere(...parsed: ParseWhereArguments): SQLWhere {
|
|
162
180
|
if (parsed[1] === undefined) {
|
|
@@ -199,7 +217,11 @@ export class SQLWhereEqual extends SQLWhere {
|
|
|
199
217
|
}
|
|
200
218
|
|
|
201
219
|
get isSingle(): boolean {
|
|
202
|
-
return true;
|
|
220
|
+
return this.transformed?.isSingle ?? true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
get isAlways(): boolean | null {
|
|
224
|
+
return this.transformed?.isAlways ?? null;
|
|
203
225
|
}
|
|
204
226
|
|
|
205
227
|
inverted(): this {
|
|
@@ -230,7 +252,55 @@ export class SQLWhereEqual extends SQLWhere {
|
|
|
230
252
|
return this;
|
|
231
253
|
}
|
|
232
254
|
|
|
255
|
+
setNullable(nullable: boolean = true): this {
|
|
256
|
+
this.nullable = nullable;
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
get transformed() {
|
|
261
|
+
if (this.value instanceof SQLNull) {
|
|
262
|
+
// We'll do some transformations to make this query work as expected.
|
|
263
|
+
// < null = always false
|
|
264
|
+
// > null = (IS NOT null)
|
|
265
|
+
// <= null = (IS null)
|
|
266
|
+
// >= null = always true
|
|
267
|
+
if (this.sign === SQLWhereSign.Less) {
|
|
268
|
+
// always false
|
|
269
|
+
return new SQLWhereOr([]);
|
|
270
|
+
}
|
|
271
|
+
if (this.sign === SQLWhereSign.Greater) {
|
|
272
|
+
// > null = (IS NOT null)
|
|
273
|
+
return new SQLWhereEqual(this.column, SQLWhereSign.NotEqual, this.value);
|
|
274
|
+
}
|
|
275
|
+
if (this.sign === SQLWhereSign.LessEqual) {
|
|
276
|
+
// (IS null)
|
|
277
|
+
return new SQLWhereEqual(this.column, SQLWhereSign.Equal, this.value);
|
|
278
|
+
}
|
|
279
|
+
if (this.sign === SQLWhereSign.GreaterEqual) {
|
|
280
|
+
// always true
|
|
281
|
+
return new SQLWhereAnd([]);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// If the expression is nullable, we'll need to do some handling to make sure the query works as expected.
|
|
286
|
+
if (this.nullable && !(this.value instanceof SQLNull)) {
|
|
287
|
+
// <: should also include null values
|
|
288
|
+
// <=: should also include null values
|
|
289
|
+
if (this.sign === SQLWhereSign.Less || this.sign === SQLWhereSign.LessEqual) {
|
|
290
|
+
return new SQLWhereOr([
|
|
291
|
+
this.clone().setNullable(false),
|
|
292
|
+
new SQLWhereEqual(this.column, SQLWhereSign.Equal, new SQLNull()),
|
|
293
|
+
]);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
233
300
|
getSQL(options?: SQLExpressionOptions): SQLQuery {
|
|
301
|
+
if (this.transformed) {
|
|
302
|
+
return this.transformed.getSQL(options);
|
|
303
|
+
}
|
|
234
304
|
if (this.value instanceof SQLArray) {
|
|
235
305
|
if (this.sign !== SQLWhereSign.Equal && this.sign !== SQLWhereSign.NotEqual) {
|
|
236
306
|
throw new Error('Unsupported sign for array: ' + this.sign);
|
|
@@ -250,7 +320,7 @@ export class SQLWhereEqual extends SQLWhere {
|
|
|
250
320
|
|
|
251
321
|
return joinSQLQuery([
|
|
252
322
|
this.column.getSQL(options),
|
|
253
|
-
` IS ${(this.sign === SQLWhereSign.NotEqual) ? 'NOT ' : ''}
|
|
323
|
+
` IS ${(this.sign === SQLWhereSign.NotEqual) ? 'NOT ' : ''}`,
|
|
254
324
|
this.value.getSQL(options),
|
|
255
325
|
]);
|
|
256
326
|
}
|
|
@@ -275,7 +345,7 @@ export class SQLWhereLike extends SQLWhere {
|
|
|
275
345
|
}
|
|
276
346
|
|
|
277
347
|
static escape(str: string) {
|
|
278
|
-
return str.replace(/([%_])/g, '\\$1');
|
|
348
|
+
return str.replace(/([%_\\])/g, '\\$1');
|
|
279
349
|
}
|
|
280
350
|
|
|
281
351
|
clone(): this {
|
|
@@ -385,6 +455,24 @@ export class SQLWhereExists extends SQLWhere {
|
|
|
385
455
|
return this;
|
|
386
456
|
}
|
|
387
457
|
|
|
458
|
+
get isAlways(): boolean | null {
|
|
459
|
+
if (this.subquery instanceof SQLSelect) {
|
|
460
|
+
const value = this.subquery.isAlways;
|
|
461
|
+
if (this.notExists) {
|
|
462
|
+
if (value === true) {
|
|
463
|
+
// If the subquery is always true, then NOT EXISTS is always false
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
if (value === false) {
|
|
467
|
+
// If the subquery is always false, then NOT EXISTS is always true
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return value;
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
388
476
|
getSQL(options?: SQLExpressionOptions): SQLQuery {
|
|
389
477
|
return joinSQLQuery([
|
|
390
478
|
`${this.notExists ? 'NOT EXISTS' : 'EXISTS'} (`,
|
|
@@ -401,22 +489,45 @@ export class SQLWhereJoin extends SQLWhere {
|
|
|
401
489
|
join: SQLJoin;
|
|
402
490
|
where: SQLWhere;
|
|
403
491
|
|
|
404
|
-
|
|
492
|
+
/**
|
|
493
|
+
* When this is true, this means we know this relation will always exist.
|
|
494
|
+
*
|
|
495
|
+
* This information will be used to optimize the query.
|
|
496
|
+
*/
|
|
497
|
+
doesRelationAlwaysExist = false;
|
|
498
|
+
|
|
499
|
+
constructor(join: SQLJoin, where: SQLWhere, options?: { doesRelationAlwaysExist?: boolean }) {
|
|
405
500
|
super();
|
|
406
501
|
this.join = join;
|
|
407
502
|
this.where = where;
|
|
503
|
+
this.doesRelationAlwaysExist = options?.doesRelationAlwaysExist ?? false;
|
|
408
504
|
}
|
|
409
505
|
|
|
410
506
|
get isSingle(): boolean {
|
|
411
507
|
return this.where.isSingle;
|
|
412
508
|
}
|
|
413
509
|
|
|
510
|
+
get isAlways(): boolean | null {
|
|
511
|
+
return this.where.isAlways;
|
|
512
|
+
}
|
|
513
|
+
|
|
414
514
|
getSQL(options?: SQLExpressionOptions): SQLQuery {
|
|
415
|
-
|
|
515
|
+
if (this.where.isAlways !== null && (this.doesRelationAlwaysExist || this.join.type === SQLJoinType.Left)) {
|
|
516
|
+
throw new Error('SQLWhereJoin: should not be included in query if result is determined');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return this.where.getSQL({
|
|
520
|
+
...options,
|
|
521
|
+
parentNamespace: options?.defaultNamespace,
|
|
522
|
+
defaultNamespace: this.join.table.getName(),
|
|
523
|
+
});
|
|
416
524
|
}
|
|
417
525
|
|
|
418
526
|
getJoins(): SQLJoin[] {
|
|
419
|
-
|
|
527
|
+
if (this.where.isAlways !== null && (this.doesRelationAlwaysExist || this.join.type === SQLJoinType.Left)) {
|
|
528
|
+
return [];
|
|
529
|
+
}
|
|
530
|
+
return [this.join, ...this.where.getJoins()];
|
|
420
531
|
}
|
|
421
532
|
}
|
|
422
533
|
|
|
@@ -429,9 +540,13 @@ export class SQLWhereAnd extends SQLWhere {
|
|
|
429
540
|
}
|
|
430
541
|
|
|
431
542
|
getSQL(options?: SQLExpressionOptions): SQLQuery {
|
|
543
|
+
if (this.isAlways === false) {
|
|
544
|
+
throw new Error('SQLWhereAnd: $and is always false and should be removed from the query');
|
|
545
|
+
}
|
|
546
|
+
|
|
432
547
|
return joinSQLQuery(
|
|
433
|
-
this.
|
|
434
|
-
if (c.isSingle) {
|
|
548
|
+
this.filteredChildren.map((c) => {
|
|
549
|
+
if (c.isSingle || this.filteredChildren.length === 1) {
|
|
435
550
|
return c.getSQL(options);
|
|
436
551
|
}
|
|
437
552
|
return joinSQLQuery(['(', c.getSQL(options), ')']);
|
|
@@ -440,8 +555,38 @@ export class SQLWhereAnd extends SQLWhere {
|
|
|
440
555
|
);
|
|
441
556
|
}
|
|
442
557
|
|
|
558
|
+
get filteredChildren(): SQLWhere[] {
|
|
559
|
+
// Children that always return true should not be included in the query (because the result only depends on the other children)
|
|
560
|
+
return this.children.filter(c => c.isAlways !== true).flatMap(c => c instanceof SQLWhereAnd ? c.filteredChildren : [c]);
|
|
561
|
+
}
|
|
562
|
+
|
|
443
563
|
getJoins(): SQLJoin[] {
|
|
444
|
-
return this.children.flatMap(c => c.getJoins());
|
|
564
|
+
return this.children.flatMap(c => c.getJoins()); // note: keep all joins
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
get isSingle(): boolean {
|
|
568
|
+
return this.filteredChildren.length === 1 && this.filteredChildren[0].isSingle;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
get isAlways(): boolean | null {
|
|
572
|
+
let allTrue = true;
|
|
573
|
+
for (const c of this.children) {
|
|
574
|
+
const v = c.isAlways;
|
|
575
|
+
if (v === false) {
|
|
576
|
+
// If any child is always false, the whole AND is false
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
if (v === null) {
|
|
580
|
+
allTrue = false;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return allTrue ? true : null;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
inverted(): SQLWhereOr {
|
|
588
|
+
// NOT (A AND B) is the same as (NOT A OR NOT B)
|
|
589
|
+
return new SQLWhereOr(this.children.map(c => new SQLWhereNot(c)));
|
|
445
590
|
}
|
|
446
591
|
}
|
|
447
592
|
|
|
@@ -454,9 +599,14 @@ export class SQLWhereOr extends SQLWhere {
|
|
|
454
599
|
}
|
|
455
600
|
|
|
456
601
|
getSQL(options?: SQLExpressionOptions): SQLQuery {
|
|
602
|
+
if (this.filteredChildren.length === 0) {
|
|
603
|
+
// Always false: throw an error (the parent should filter out this query)
|
|
604
|
+
throw new Error('SQLWhereOr: empty $or is always false and should be removed from the query');
|
|
605
|
+
}
|
|
606
|
+
|
|
457
607
|
return joinSQLQuery(
|
|
458
|
-
this.
|
|
459
|
-
if (c.isSingle) {
|
|
608
|
+
this.filteredChildren.map((c) => {
|
|
609
|
+
if (c.isSingle || this.filteredChildren.length === 1) {
|
|
460
610
|
return c.getSQL(options);
|
|
461
611
|
}
|
|
462
612
|
return joinSQLQuery(['(', c.getSQL(options), ')']);
|
|
@@ -468,6 +618,36 @@ export class SQLWhereOr extends SQLWhere {
|
|
|
468
618
|
getJoins(): SQLJoin[] {
|
|
469
619
|
return this.children.flatMap(c => c.getJoins());
|
|
470
620
|
}
|
|
621
|
+
|
|
622
|
+
get filteredChildren(): SQLWhere[] {
|
|
623
|
+
// Children that always return false should not be included in the query (because the result only depends on the other children)
|
|
624
|
+
return this.children.filter(c => c.isAlways !== false).flatMap(c => c instanceof SQLWhereOr ? c.filteredChildren : [c]);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
get isSingle(): boolean {
|
|
628
|
+
return this.filteredChildren.length === 1 && this.filteredChildren[0].isSingle;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
get isAlways(): boolean | null {
|
|
632
|
+
let isAllFalse = true;
|
|
633
|
+
for (const c of this.children) {
|
|
634
|
+
const v = c.isAlways;
|
|
635
|
+
if (v === true) {
|
|
636
|
+
// If any child is always true, the whole OR is true
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
if (v === null) {
|
|
640
|
+
isAllFalse = false;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return isAllFalse ? false : null;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
inverted(): SQLWhereOr {
|
|
648
|
+
// NOT (A OR B) is the same as (NOT A AND NOT B)
|
|
649
|
+
return new SQLWhereAnd(this.children.map(c => new SQLWhereNot(c)));
|
|
650
|
+
}
|
|
471
651
|
}
|
|
472
652
|
|
|
473
653
|
export class SQLWhereNot extends SQLWhere {
|
|
@@ -484,7 +664,7 @@ export class SQLWhereNot extends SQLWhere {
|
|
|
484
664
|
|
|
485
665
|
getSQL(options?: SQLExpressionOptions): SQLQuery {
|
|
486
666
|
// Optimize query
|
|
487
|
-
if (this.a instanceof SQLWhereEqual) {
|
|
667
|
+
if (this.a instanceof SQLWhereEqual || this.a instanceof SQLWhereAnd || this.a instanceof SQLWhereOr || this.a instanceof SQLWhereNot) {
|
|
488
668
|
return this.a.inverted().getSQL(options);
|
|
489
669
|
}
|
|
490
670
|
|
|
@@ -499,4 +679,19 @@ export class SQLWhereNot extends SQLWhere {
|
|
|
499
679
|
getJoins(): SQLJoin[] {
|
|
500
680
|
return this.a.getJoins();
|
|
501
681
|
}
|
|
682
|
+
|
|
683
|
+
get isAlways(): boolean | null {
|
|
684
|
+
const v = this.a.isAlways;
|
|
685
|
+
if (v === true) {
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
if (v === false) {
|
|
689
|
+
return true;
|
|
690
|
+
}
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
inverted(): SQLWhere {
|
|
695
|
+
return this.a; // NOT NOT A is just A
|
|
696
|
+
}
|
|
502
697
|
}
|
package/src/filters/SQLFilter.ts
CHANGED
|
@@ -137,6 +137,14 @@ export function createSQLRelationFilterCompiler(baseSelect: InstanceType<typeof
|
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
export function createSQLOneToOneRelationFilterCompiler(baseSelect: InstanceType<typeof SQLSelect> & SQLExpression, definitions: SQLFilterDefinitions): SQLFilterCompiler {
|
|
141
|
+
return async (filter: StamhoofdFilter) => {
|
|
142
|
+
const w = await compileToSQLFilter(filter, definitions);
|
|
143
|
+
const q = baseSelect.clone().where(w);
|
|
144
|
+
return new SQLWhereExists(q);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
140
148
|
// Already joined, but creates a namespace
|
|
141
149
|
export function createSQLFilterNamespace(definitions: SQLFilterDefinitions): SQLFilterCompiler {
|
|
142
150
|
return (filter: StamhoofdFilter) => {
|