@stamhoofd/sql 2.46.0 → 2.48.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/src/SQLSelect.ts CHANGED
@@ -1,54 +1,57 @@
1
- import { Database, SQLResultNamespacedRow } from "@simonbackx/simple-database";
2
- import { SQLExpression, SQLExpressionOptions, SQLQuery, joinSQLQuery, normalizeSQLQuery } from "./SQLExpression";
3
- import { SQLAlias, SQLColumnExpression, SQLCount, SQLSelectAs, SQLSum, SQLTableExpression } from "./SQLExpressions";
1
+ import { Database, SQLResultNamespacedRow } from '@simonbackx/simple-database';
2
+ import { SQLExpression, SQLExpressionOptions, SQLQuery, joinSQLQuery, normalizeSQLQuery } from './SQLExpression';
3
+ import { SQLAlias, SQLColumnExpression, SQLCount, SQLSelectAs, SQLSum, SQLTableExpression } from './SQLExpressions';
4
4
  import { SQLJoin } from './SQLJoin';
5
- import { Orderable } from "./SQLOrderBy";
6
- import { Whereable } from "./SQLWhere";
5
+ import { Orderable } from './SQLOrderBy';
6
+ import { Whereable } from './SQLWhere';
7
7
 
8
8
  class EmptyClass {}
9
9
 
10
- export function parseTable(tableOrExpressiongOrNamespace: SQLExpression|string, table?: string): SQLExpression {
10
+ export function parseTable(tableOrExpressiongOrNamespace: SQLExpression | string, table?: string): SQLExpression {
11
11
  if (table !== undefined && typeof tableOrExpressiongOrNamespace === 'string') {
12
- return new SQLTableExpression(tableOrExpressiongOrNamespace, table)
13
- } else if (typeof tableOrExpressiongOrNamespace === 'string') {
14
- return new SQLTableExpression(tableOrExpressiongOrNamespace)
15
- } else {
12
+ return new SQLTableExpression(tableOrExpressiongOrNamespace, table);
13
+ }
14
+ else if (typeof tableOrExpressiongOrNamespace === 'string') {
15
+ return new SQLTableExpression(tableOrExpressiongOrNamespace);
16
+ }
17
+ else {
16
18
  return tableOrExpressiongOrNamespace;
17
19
  }
18
20
  }
19
21
 
20
22
  export class SQLSelect<T = SQLResultNamespacedRow> extends Whereable(Orderable(EmptyClass)) implements SQLExpression {
21
- _columns: SQLExpression[]
23
+ _columns: SQLExpression[];
22
24
  _from: SQLExpression;
23
25
 
24
- _limit: number|null = null;
25
- _offset: number|null = null;
26
+ _limit: number | null = null;
27
+ _offset: number | null = null;
26
28
  _groupBy: SQLExpression[] = [];
27
29
  _joins: (InstanceType<typeof SQLJoin>)[] = [];
30
+ _max_execution_time: number | null = null;
28
31
 
29
- _transformer: ((row: SQLResultNamespacedRow) => T)|null = null;
32
+ _transformer: ((row: SQLResultNamespacedRow) => T) | null = null;
30
33
 
31
- constructor(...columns: (SQLExpression|string)[])
32
- constructor(transformer: ((row: SQLResultNamespacedRow) => T),...columns: (SQLExpression|string)[])
33
- constructor(...columns: (SQLExpression|string|((row: SQLResultNamespacedRow) => T))[]) {
34
+ constructor(...columns: (SQLExpression | string)[]);
35
+ constructor(transformer: ((row: SQLResultNamespacedRow) => T), ...columns: (SQLExpression | string)[]);
36
+ constructor(...columns: (SQLExpression | string | ((row: SQLResultNamespacedRow) => T))[]) {
34
37
  super();
35
-
38
+
36
39
  if (typeof columns[0] === 'function') {
37
40
  this._transformer = columns.shift() as any;
38
41
  }
39
- this._columns = columns.map(c => typeof c === 'string' ? new SQLColumnExpression(c) : c ) as any;
42
+ this._columns = columns.map(c => typeof c === 'string' ? new SQLColumnExpression(c) : c) as any;
40
43
  }
41
44
 
42
45
  clone(): this {
43
- const c = new SQLSelect(...this._columns)
46
+ const c = new SQLSelect(...this._columns);
44
47
  Object.assign(c, this);
45
48
  return c as any;
46
49
  }
47
50
 
48
- from(namespace: string, table: string): this
49
- from(table: string): this
50
- from(expression: SQLExpression): this
51
- from(tableOrExpressiongOrNamespace: SQLExpression|string, table?: string): this {
51
+ from(namespace: string, table: string): this;
52
+ from(table: string): this;
53
+ from(expression: SQLExpression): this;
54
+ from(tableOrExpressiongOrNamespace: SQLExpression | string, table?: string): this {
52
55
  this._from = parseTable(tableOrExpressiongOrNamespace, table);
53
56
 
54
57
  return this;
@@ -64,68 +67,77 @@ export class SQLSelect<T = SQLResultNamespacedRow> extends Whereable(Orderable(E
64
67
  return this;
65
68
  }
66
69
 
70
+ setMaxExecutionTime(ms: number): this {
71
+ this._max_execution_time = ms;
72
+ return this;
73
+ }
74
+
67
75
  getSQL(options?: SQLExpressionOptions): SQLQuery {
68
76
  const query: SQLQuery[] = [
69
- 'SELECT'
70
- ]
77
+ 'SELECT',
78
+ ];
79
+
80
+ if (this._max_execution_time !== null) {
81
+ query.push('/*+ MAX_EXECUTION_TIME(' + this._max_execution_time + ') */');
82
+ }
71
83
 
72
- options = options ?? {}
84
+ options = options ?? {};
73
85
  options.defaultNamespace = (this._from as any).namespace ?? (this._from as any).table ?? undefined;
74
86
 
75
- const columns = this._columns.map(c => c.getSQL(options))
87
+ const columns = this._columns.map(c => c.getSQL(options));
76
88
  query.push(
77
- joinSQLQuery(columns, ', ')
78
- )
89
+ joinSQLQuery(columns, ', '),
90
+ );
79
91
 
80
92
  query.push(
81
- 'FROM'
82
- )
93
+ 'FROM',
94
+ );
83
95
 
84
96
  query.push(this._from.getSQL(options));
85
97
 
86
- query.push(...this._joins.map(j => j.getSQL(options)))
98
+ query.push(...this._joins.map(j => j.getSQL(options)));
87
99
 
88
100
  if (this._where) {
89
- query.push('WHERE')
90
- query.push(this._where.getSQL(options))
101
+ query.push('WHERE');
102
+ query.push(this._where.getSQL(options));
91
103
  }
92
104
 
93
105
  if (this._groupBy.length > 0) {
94
- query.push('GROUP BY')
106
+ query.push('GROUP BY');
95
107
  query.push(
96
108
  joinSQLQuery(
97
- this._groupBy.map(c => c.getSQL(options)),
98
- ', '
99
- )
100
- )
109
+ this._groupBy.map(c => c.getSQL(options)),
110
+ ', ',
111
+ ),
112
+ );
101
113
  }
102
114
 
103
115
  if (this._orderBy) {
104
- query.push(this._orderBy.getSQL(options))
116
+ query.push(this._orderBy.getSQL(options));
105
117
  }
106
-
118
+
107
119
  if (this._limit !== null) {
108
- query.push('LIMIT ' + this._limit)
120
+ query.push('LIMIT ' + this._limit);
109
121
  if (this._offset !== null && this._offset !== 0) {
110
- query.push('OFFSET ' + this._offset)
122
+ query.push('OFFSET ' + this._offset);
111
123
  }
112
124
  }
113
-
125
+
114
126
  return joinSQLQuery(query, ' ');
115
127
  }
116
128
 
117
- limit(limit: number|null, offset: number|null = null): this {
129
+ limit(limit: number | null, offset: number | null = null): this {
118
130
  this._limit = limit;
119
131
  this._offset = offset;
120
132
  return this;
121
133
  }
122
134
 
123
135
  async fetch(): Promise<T[]> {
124
- const {query, params} = normalizeSQLQuery(this.getSQL())
136
+ const { query, params } = normalizeSQLQuery(this.getSQL());
125
137
 
126
138
  // when debugging: log all queries
127
139
  console.log(query, params);
128
- const [rows] = await Database.select(query, params, {nestTables: true});
140
+ const [rows] = await Database.select(query, params, { nestTables: true });
129
141
 
130
142
  // Now map aggregated queries to the correct namespace
131
143
  for (const row of rows) {
@@ -133,7 +145,7 @@ export class SQLSelect<T = SQLResultNamespacedRow> extends Whereable(Orderable(E
133
145
  for (const column in row['']) {
134
146
  const splitted = column.split('__');
135
147
  if (splitted.length <= 1) {
136
- console.warn('Aggregated column without namespace', column)
148
+ console.warn('Aggregated column without namespace', column);
137
149
  continue;
138
150
  }
139
151
  const namespace = splitted[0];
@@ -151,35 +163,35 @@ export class SQLSelect<T = SQLResultNamespacedRow> extends Whereable(Orderable(E
151
163
  return rows as T[];
152
164
  }
153
165
 
154
- first(required: false): Promise<T|null>
155
- first(required: true): Promise<T>
156
- async first(required = true): Promise<T|null> {
166
+ first(required: false): Promise<T | null>;
167
+ first(required: true): Promise<T>;
168
+ async first(required = true): Promise<T | null> {
157
169
  const rows = await this.limit(1).fetch();
158
170
  if (rows.length === 0) {
159
171
  if (required) {
160
- throw new Error('Required ' + this._from)
172
+ throw new Error('Required ' + this._from);
161
173
  }
162
174
  return null;
163
175
  }
164
176
 
165
- return rows[0]
177
+ return rows[0];
166
178
  }
167
179
 
168
180
  async count(): Promise<number> {
169
181
  this._columns = [
170
182
  new SQLSelectAs(
171
- new SQLCount(),
172
- new SQLAlias('c')
173
- )
174
- ]
183
+ new SQLCount(),
184
+ new SQLAlias('c'),
185
+ ),
186
+ ];
175
187
  this._offset = null;
176
188
  this._limit = null;
177
189
  this._orderBy = null;
178
190
 
179
- const {query, params} = normalizeSQLQuery(this.getSQL());
191
+ const { query, params } = normalizeSQLQuery(this.getSQL());
180
192
  console.log(query, params);
181
193
 
182
- const [rows] = await Database.select(query, params, {nestTables: true});
194
+ const [rows] = await Database.select(query, params, { nestTables: true });
183
195
  if (rows.length === 1) {
184
196
  const row = rows[0];
185
197
  if ('' in row) {
@@ -199,18 +211,18 @@ export class SQLSelect<T = SQLResultNamespacedRow> extends Whereable(Orderable(E
199
211
  async sum(expression: SQLExpression): Promise<number> {
200
212
  this._columns = [
201
213
  new SQLSelectAs(
202
- new SQLSum(expression),
203
- new SQLAlias('c')
204
- )
205
- ]
214
+ new SQLSum(expression),
215
+ new SQLAlias('c'),
216
+ ),
217
+ ];
206
218
  this._offset = null;
207
219
  this._limit = null;
208
220
  this._orderBy = null;
209
221
 
210
- const {query, params} = normalizeSQLQuery(this.getSQL());
222
+ const { query, params } = normalizeSQLQuery(this.getSQL());
211
223
  console.log(query, params);
212
224
 
213
- const [rows] = await Database.select(query, params, {nestTables: true});
225
+ const [rows] = await Database.select(query, params, { nestTables: true });
214
226
  if (rows.length === 1) {
215
227
  const row = rows[0];
216
228
  if ('' in row) {
@@ -2,8 +2,8 @@ import { SimpleError } from '@simonbackx/simple-errors';
2
2
  import { StamhoofdCompareValue, StamhoofdFilter } from '@stamhoofd/structures';
3
3
  import { SQL } from '../SQL';
4
4
  import { SQLExpression } from '../SQLExpression';
5
- import { SQLArray, SQLCast, SQLColumnExpression, SQLNull, SQLSafeValue, SQLScalarValue, scalarToSQLExpression, scalarToSQLJSONExpression } from '../SQLExpressions';
6
- import { SQLJsonContains, SQLJsonOverlaps, SQLJsonSearch, SQLJsonUnquote } from '../SQLJsonExpressions';
5
+ import { SQLArray, SQLCast, SQLColumnExpression, SQLLower, SQLNull, SQLSafeValue, SQLScalarValue, scalarToSQLExpression } from '../SQLExpressions';
6
+ import { SQLJsonContains, SQLJsonOverlaps, SQLJsonSearch, SQLJsonUnquote, scalarToSQLJSONExpression } from '../SQLJsonExpressions';
7
7
  import { SQLSelect } from '../SQLSelect';
8
8
  import { SQLWhere, SQLWhereAnd, SQLWhereEqual, SQLWhereExists, SQLWhereLike, SQLWhereNot, SQLWhereOr, SQLWhereSign } from '../SQLWhere';
9
9
 
@@ -61,6 +61,9 @@ function doNormalizeValue(val: StamhoofdCompareValue, options?: SQLExpressionFil
61
61
  }
62
62
 
63
63
  if (typeof val === 'string') {
64
+ if (options?.isJSONObject) {
65
+ return val;
66
+ }
64
67
  return val.toLocaleLowerCase();
65
68
  }
66
69
 
@@ -181,18 +184,25 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
181
184
  if (isJSONObject) {
182
185
  const v = norm(f.$eq);
183
186
 
184
- // if (typeof v === 'string') {
185
- // return new SQLWhereEqual(
186
- // new SQLJsonSearch(sqlExpression, 'one', convertToExpression(v)),
187
- // SQLWhereSign.NotEqual,
188
- // new SQLNull()
189
- // );
190
- // }
187
+ if (typeof v === 'string') {
188
+ // Custom query to support case insensitive comparing
191
189
 
190
+ return new SQLWhereEqual(
191
+ new SQLJsonSearch(
192
+ new SQLLower(sqlExpression),
193
+ 'one',
194
+ convertToExpression(
195
+ SQLWhereLike.escape(v.toLocaleLowerCase()),
196
+ ),
197
+ ),
198
+ SQLWhereSign.NotEqual,
199
+ new SQLNull(),
200
+ );
201
+ }
192
202
  // else
193
203
  return new SQLJsonContains(
194
204
  sqlExpression,
195
- convertToExpression(JSON.stringify(v)),
205
+ convertToExpression(v),
196
206
  );
197
207
  }
198
208
 
@@ -361,7 +371,7 @@ export function createSQLExpressionFilterCompiler(sqlExpression: SQLExpression,
361
371
  if (isJSONObject) {
362
372
  return new SQLWhereEqual(
363
373
  new SQLJsonSearch(
364
- sqlExpression,
374
+ new SQLLower(sqlExpression),
365
375
  'one',
366
376
  convertToExpression(
367
377
  '%' + SQLWhereLike.escape(needle) + '%',