@travetto/model-sql 2.1.5 → 2.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 +2 -2
- package/package.json +6 -6
- package/src/connection/base.ts +7 -7
- package/src/connection/decorator.ts +12 -12
- package/src/dialect/base.ts +67 -42
- package/src/dialect/mysql/connection.ts +5 -5
- package/src/dialect/mysql/dialect.ts +8 -7
- package/src/dialect/postgresql/connection.ts +6 -5
- package/src/dialect/postgresql/dialect.ts +6 -5
- package/src/dialect/sqlite/connection.ts +13 -8
- package/src/dialect/sqlite/dialect.ts +8 -7
- package/src/internal/util.ts +43 -25
- package/src/service.ts +31 -22
- package/src/table-manager.ts +14 -10
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ or
|
|
|
20
20
|
npm install pg
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This module provides a [SQL](https://en.wikipedia.org/wiki/SQL)-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module. This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [SQL](https://en.wikipedia.org/wiki/SQL) databases. In development mode, the [SQLModelService](https://github.com/travetto/travetto/tree/main/module/model-sql/src/service.ts#
|
|
23
|
+
This module provides a [SQL](https://en.wikipedia.org/wiki/SQL)-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module. This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [SQL](https://en.wikipedia.org/wiki/SQL) databases. In development mode, the [SQLModelService](https://github.com/travetto/travetto/tree/main/module/model-sql/src/service.ts#L38) will also modify the database schema in real time to minimize impact to development.
|
|
24
24
|
|
|
25
25
|
The schema generated will not generally map to existing tables as it is attempting to produce a document store like experience on top of
|
|
26
26
|
a [SQL](https://en.wikipedia.org/wiki/SQL) database. Every table generated will have a `path_id` which determines it's location in the document hierarchy as well as sub tables will have a `parent_path_id` to associate records with the parent values.
|
|
@@ -34,7 +34,7 @@ The current SQL client support stands at:
|
|
|
34
34
|
|
|
35
35
|
**Note**: Wider client support will roll out as usage increases.
|
|
36
36
|
|
|
37
|
-
Supported
|
|
37
|
+
Supported features:
|
|
38
38
|
|
|
39
39
|
* [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11)
|
|
40
40
|
* [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/service/bulk.ts#L23)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-sql",
|
|
3
3
|
"displayName": "SQL Model Service",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"description": "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"sql",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"directory": "module/model-sql"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@travetto/config": "^2.
|
|
32
|
-
"@travetto/context": "^2.
|
|
33
|
-
"@travetto/model": "^2.
|
|
34
|
-
"@travetto/model-query": "2.
|
|
31
|
+
"@travetto/config": "^2.2.0",
|
|
32
|
+
"@travetto/context": "^2.2.0",
|
|
33
|
+
"@travetto/model": "^2.2.0",
|
|
34
|
+
"@travetto/model-query": "2.2.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@travetto/app": "^2.
|
|
37
|
+
"@travetto/app": "^2.2.0"
|
|
38
38
|
},
|
|
39
39
|
"optionalPeerDependencies": {
|
|
40
40
|
"@types/mysql": "^2.15.21",
|
package/src/connection/base.ts
CHANGED
|
@@ -24,14 +24,14 @@ export abstract class Connection<C = unknown> {
|
|
|
24
24
|
* Get active connection
|
|
25
25
|
*/
|
|
26
26
|
get active(): C {
|
|
27
|
-
return this.context.get(ContextActiveⲐ)
|
|
27
|
+
return this.context.get<C>(ContextActiveⲐ);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Get active tx state
|
|
32
32
|
*/
|
|
33
|
-
get activeTx() {
|
|
34
|
-
return !!this.context.get(TxActiveⲐ)
|
|
33
|
+
get activeTx(): boolean {
|
|
34
|
+
return !!this.context.get<boolean>(TxActiveⲐ);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -85,7 +85,7 @@ export abstract class Connection<C = unknown> {
|
|
|
85
85
|
* @param op
|
|
86
86
|
* @param args
|
|
87
87
|
*/
|
|
88
|
-
async * iterateWithActive<R>(op: () => AsyncGenerator<R>) {
|
|
88
|
+
async * iterateWithActive<R>(op: () => AsyncGenerator<R>): AsyncIterable<R> {
|
|
89
89
|
if (this.active) {
|
|
90
90
|
yield* op();
|
|
91
91
|
}
|
|
@@ -134,7 +134,7 @@ export abstract class Connection<C = unknown> {
|
|
|
134
134
|
/**
|
|
135
135
|
* Start a transaction
|
|
136
136
|
*/
|
|
137
|
-
async startTx(conn: C, transactionId?: string) {
|
|
137
|
+
async startTx(conn: C, transactionId?: string): Promise<void> {
|
|
138
138
|
if (transactionId) {
|
|
139
139
|
if (this.nestedTransactions) {
|
|
140
140
|
await this.execute(conn, `SAVEPOINT ${transactionId};`);
|
|
@@ -150,7 +150,7 @@ export abstract class Connection<C = unknown> {
|
|
|
150
150
|
/**
|
|
151
151
|
* Commit active transaction
|
|
152
152
|
*/
|
|
153
|
-
async commitTx(conn: C, transactionId?: string) {
|
|
153
|
+
async commitTx(conn: C, transactionId?: string): Promise<void> {
|
|
154
154
|
if (transactionId) {
|
|
155
155
|
if (this.nestedTransactions) {
|
|
156
156
|
await this.execute(conn, `RELEASE SAVEPOINT ${transactionId};`);
|
|
@@ -163,7 +163,7 @@ export abstract class Connection<C = unknown> {
|
|
|
163
163
|
/**
|
|
164
164
|
* Rollback active transaction
|
|
165
165
|
*/
|
|
166
|
-
async rollbackTx(conn: C, transactionId?: string) {
|
|
166
|
+
async rollbackTx(conn: C, transactionId?: string): Promise<void> {
|
|
167
167
|
if (transactionId) {
|
|
168
168
|
if (this.isolatedTransactions) {
|
|
169
169
|
await this.execute(conn, `ROLLBACK TO ${transactionId};`);
|
|
@@ -12,12 +12,12 @@ export interface ConnectionAware<C = unknown> {
|
|
|
12
12
|
* Decorator to ensure a method runs with a valid connection
|
|
13
13
|
*/
|
|
14
14
|
export function Connected<T extends ConnectionAware>() {
|
|
15
|
-
return function (target: T, prop: string | symbol, desc: MethodDescriptor) {
|
|
15
|
+
return function (target: T, prop: string | symbol, desc: MethodDescriptor): void {
|
|
16
16
|
const og = desc.value!;
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/
|
|
18
|
-
desc.value = async function (this:
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
18
|
+
desc.value = async function (this: T, ...args: unknown[]) {
|
|
19
19
|
return this.conn.runWithActive(() => og.call(this, ...args));
|
|
20
|
-
};
|
|
20
|
+
} as typeof og;
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -25,12 +25,12 @@ export function Connected<T extends ConnectionAware>() {
|
|
|
25
25
|
* Decorator to ensure a method runs with a valid connection
|
|
26
26
|
*/
|
|
27
27
|
export function ConnectedIterator<T extends ConnectionAware>() {
|
|
28
|
-
return function (target: T, prop: string | symbol, desc: MethodDescriptor) {
|
|
28
|
+
return function (target: T, prop: string | symbol, desc: MethodDescriptor): void {
|
|
29
29
|
const og = desc.value!;
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/
|
|
31
|
-
desc.value = async function* (this:
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
31
|
+
desc.value = async function* (this: T, ...args: unknown[]) {
|
|
32
32
|
yield* this.conn.iterateWithActive(() => og.call(this, ...args));
|
|
33
|
-
};
|
|
33
|
+
} as typeof og;
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -38,11 +38,11 @@ export function ConnectedIterator<T extends ConnectionAware>() {
|
|
|
38
38
|
* Decorator to ensure a method runs with a valid transaction
|
|
39
39
|
*/
|
|
40
40
|
export function Transactional<T extends ConnectionAware>(mode: TransactionType = 'required') {
|
|
41
|
-
return function (target: T, prop: string | symbol, desc: MethodDescriptor) {
|
|
41
|
+
return function (target: T, prop: string | symbol, desc: MethodDescriptor): void {
|
|
42
42
|
const og = desc.value!;
|
|
43
|
-
// eslint-disable-next-line @typescript-eslint/
|
|
44
|
-
desc.value = function (this:
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
44
|
+
desc.value = function (this: T, ...args: unknown[]) {
|
|
45
45
|
return this.conn.runWithTransaction(mode, () => og.call(this, ...args));
|
|
46
|
-
};
|
|
46
|
+
} as typeof og;
|
|
47
47
|
};
|
|
48
48
|
}
|
package/src/dialect/base.ts
CHANGED
|
@@ -10,8 +10,11 @@ import { SQLUtil, VisitStack } from '../internal/util';
|
|
|
10
10
|
import { DeleteWrapper, InsertWrapper, DialectState } from '../internal/types';
|
|
11
11
|
import { Connection } from '../connection/base';
|
|
12
12
|
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
13
14
|
const has$And = (o: unknown): o is ({ $and: WhereClause<unknown>[] }) => !!o && '$and' in (o as object);
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
14
16
|
const has$Or = (o: unknown): o is ({ $or: WhereClause<unknown>[] }) => !!o && '$or' in (o as object);
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
15
18
|
const has$Not = (o: unknown): o is ({ $not: WhereClause<unknown> }) => !!o && '$not' in (o as object);
|
|
16
19
|
|
|
17
20
|
interface Alias {
|
|
@@ -24,15 +27,16 @@ class Total {
|
|
|
24
27
|
total: number;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
function makeField(name: string, type: Class, required: boolean, extra: Partial<FieldConfig>) {
|
|
30
|
+
function makeField(name: string, type: Class, required: boolean, extra: Partial<FieldConfig>): FieldConfig {
|
|
28
31
|
return {
|
|
29
32
|
name,
|
|
30
|
-
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
34
|
+
owner: null as unknown as Class,
|
|
31
35
|
type,
|
|
32
36
|
array: false,
|
|
33
37
|
...(required ? { required: { active: true } } : {}),
|
|
34
38
|
...extra
|
|
35
|
-
}
|
|
39
|
+
};
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
/**
|
|
@@ -81,7 +85,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
81
85
|
BOOLEAN: 'BOOLEAN',
|
|
82
86
|
TINYINT: 'TINYINT',
|
|
83
87
|
SMALLINT: 'SMALLINT',
|
|
84
|
-
|
|
88
|
+
MEDIUMINT: 'MEDIUMINT',
|
|
85
89
|
INT: 'INT',
|
|
86
90
|
BIGINT: 'BIGINT',
|
|
87
91
|
TIMESTAMP: 'TIMESTAMP',
|
|
@@ -91,7 +95,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
91
95
|
/**
|
|
92
96
|
* Column types with inputs
|
|
93
97
|
*/
|
|
94
|
-
PARAMETERIZED_COLUMN_TYPES: Record<'VARCHAR' | 'DECIMAL', (...
|
|
98
|
+
PARAMETERIZED_COLUMN_TYPES: Record<'VARCHAR' | 'DECIMAL', (...values: number[]) => string> = {
|
|
95
99
|
VARCHAR: n => `VARCHAR(${n})`,
|
|
96
100
|
DECIMAL: (d, p) => `DECIMAL(${d},${p})`
|
|
97
101
|
};
|
|
@@ -153,7 +157,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
153
157
|
*/
|
|
154
158
|
abstract hash(inp: string): string;
|
|
155
159
|
|
|
156
|
-
executeSQL<T>(sql: string) {
|
|
160
|
+
executeSQL<T>(sql: string): Promise<{ records: T[], count: number }> {
|
|
157
161
|
return this.conn.execute<T>(this.conn.active, sql);
|
|
158
162
|
}
|
|
159
163
|
|
|
@@ -171,15 +175,15 @@ export abstract class SQLDialect implements DialectState {
|
|
|
171
175
|
* @param value
|
|
172
176
|
* @returns
|
|
173
177
|
*/
|
|
174
|
-
resolveDateValue(value: Date) {
|
|
175
|
-
const [day, time] =
|
|
178
|
+
resolveDateValue(value: Date): string {
|
|
179
|
+
const [day, time] = value.toISOString().split(/[TZ]/);
|
|
176
180
|
return this.quote(`${day} ${time}`);
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
/**
|
|
180
184
|
* Convert value to SQL valid representation
|
|
181
185
|
*/
|
|
182
|
-
resolveValue(conf: FieldConfig, value: unknown) {
|
|
186
|
+
resolveValue(conf: FieldConfig, value: unknown): string {
|
|
183
187
|
if (value === undefined || value === null) {
|
|
184
188
|
return 'NULL';
|
|
185
189
|
} else if (conf.type === String) {
|
|
@@ -187,6 +191,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
187
191
|
const src = Util.toRegex(value).source.replace(/\\b/g, this.regexWordBoundary);
|
|
188
192
|
return this.quote(src);
|
|
189
193
|
} else {
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
190
195
|
return this.quote(value as string);
|
|
191
196
|
}
|
|
192
197
|
} else if (conf.type === Boolean) {
|
|
@@ -194,6 +199,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
194
199
|
} else if (conf.type === Number) {
|
|
195
200
|
return `${value}`;
|
|
196
201
|
} else if (conf.type === Date) {
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
197
203
|
return this.resolveDateValue(ModelQueryUtil.resolveComparator(value) as Date);
|
|
198
204
|
} else if (conf.type === PointImpl && Array.isArray(value)) {
|
|
199
205
|
return `point(${value[0]},${value[1]})`;
|
|
@@ -206,7 +212,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
206
212
|
/**
|
|
207
213
|
* Get column type from field config
|
|
208
214
|
*/
|
|
209
|
-
getColumnType(conf: FieldConfig) {
|
|
215
|
+
getColumnType(conf: FieldConfig): string {
|
|
210
216
|
let type: string = '';
|
|
211
217
|
|
|
212
218
|
if (conf.type === Number) {
|
|
@@ -221,7 +227,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
221
227
|
} else if (digits < 5) {
|
|
222
228
|
type = this.COLUMN_TYPES.SMALLINT;
|
|
223
229
|
} else if (digits < 7) {
|
|
224
|
-
type = this.COLUMN_TYPES.
|
|
230
|
+
type = this.COLUMN_TYPES.MEDIUMINT;
|
|
225
231
|
} else if (digits < 10) {
|
|
226
232
|
type = this.COLUMN_TYPES.INT;
|
|
227
233
|
} else {
|
|
@@ -253,7 +259,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
253
259
|
/**
|
|
254
260
|
* FieldConfig to Column definition
|
|
255
261
|
*/
|
|
256
|
-
getColumnDefinition(conf: FieldConfig) {
|
|
262
|
+
getColumnDefinition(conf: FieldConfig): string | undefined {
|
|
257
263
|
const type = this.getColumnType(conf);
|
|
258
264
|
if (!type) {
|
|
259
265
|
return;
|
|
@@ -264,7 +270,8 @@ export abstract class SQLDialect implements DialectState {
|
|
|
264
270
|
/**
|
|
265
271
|
* Delete query and return count removed
|
|
266
272
|
*/
|
|
267
|
-
async deleteAndGetCount<T>(cls: Class<T>, query: Query<T>) {
|
|
273
|
+
async deleteAndGetCount<T>(cls: Class<T>, query: Query<T>): Promise<number> {
|
|
274
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
268
275
|
const { count } = await this.executeSQL(this.getDeleteSQL(SQLUtil.classToStack(cls), query.where as WhereClause<T>));
|
|
269
276
|
return count;
|
|
270
277
|
}
|
|
@@ -272,7 +279,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
272
279
|
/**
|
|
273
280
|
* Get the count for a given query
|
|
274
281
|
*/
|
|
275
|
-
async getCountForQuery<T>(cls: Class<T>, query: Query<T>) {
|
|
282
|
+
async getCountForQuery<T>(cls: Class<T>, query: Query<T>): Promise<number> {
|
|
276
283
|
const { records } = await this.executeSQL<{ total: number }>(this.getQueryCountSQL(cls, query));
|
|
277
284
|
const [record] = records;
|
|
278
285
|
return Total.from(record).total;
|
|
@@ -281,7 +288,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
281
288
|
/**
|
|
282
289
|
* Remove a sql column
|
|
283
290
|
*/
|
|
284
|
-
getDropColumnSQL(stack: VisitStack[]) {
|
|
291
|
+
getDropColumnSQL(stack: VisitStack[]): string {
|
|
285
292
|
const field = stack[stack.length - 1];
|
|
286
293
|
return `ALTER TABLE ${this.parentTable(stack)} DROP COLUMN ${this.ident(field.name)};`;
|
|
287
294
|
}
|
|
@@ -289,8 +296,9 @@ export abstract class SQLDialect implements DialectState {
|
|
|
289
296
|
/**
|
|
290
297
|
* Add a sql column
|
|
291
298
|
*/
|
|
292
|
-
getAddColumnSQL(stack: VisitStack[]) {
|
|
299
|
+
getAddColumnSQL(stack: VisitStack[]): string {
|
|
293
300
|
const field = stack[stack.length - 1];
|
|
301
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
294
302
|
return `ALTER TABLE ${this.parentTable(stack)} ADD COLUMN ${this.getColumnDefinition(field as FieldConfig)};`;
|
|
295
303
|
}
|
|
296
304
|
|
|
@@ -309,49 +317,49 @@ export abstract class SQLDialect implements DialectState {
|
|
|
309
317
|
/**
|
|
310
318
|
* Determine table/field namespace for a given stack location
|
|
311
319
|
*/
|
|
312
|
-
namespace(stack: VisitStack[]) {
|
|
320
|
+
namespace(stack: VisitStack[]): string {
|
|
313
321
|
return `${this.ns}${SQLUtil.buildTable(stack)}`;
|
|
314
322
|
}
|
|
315
323
|
|
|
316
324
|
/**
|
|
317
325
|
* Determine namespace for a given stack location - 1
|
|
318
326
|
*/
|
|
319
|
-
namespaceParent(stack: VisitStack[]) {
|
|
327
|
+
namespaceParent(stack: VisitStack[]): string {
|
|
320
328
|
return this.namespace(stack.slice(0, stack.length - 1));
|
|
321
329
|
}
|
|
322
330
|
|
|
323
331
|
/**
|
|
324
332
|
* Determine table name for a given stack location
|
|
325
333
|
*/
|
|
326
|
-
table(stack: VisitStack[]) {
|
|
334
|
+
table(stack: VisitStack[]): string {
|
|
327
335
|
return this.ident(this.namespace(stack));
|
|
328
336
|
}
|
|
329
337
|
|
|
330
338
|
/**
|
|
331
339
|
* Determine parent table name for a given stack location
|
|
332
340
|
*/
|
|
333
|
-
parentTable(stack: VisitStack[]) {
|
|
341
|
+
parentTable(stack: VisitStack[]): string {
|
|
334
342
|
return this.table(stack.slice(0, stack.length - 1));
|
|
335
343
|
}
|
|
336
344
|
|
|
337
345
|
/**
|
|
338
346
|
* Get lookup key for cls and name
|
|
339
347
|
*/
|
|
340
|
-
getKey(cls: Class, name: string) {
|
|
348
|
+
getKey(cls: Class, name: string): string {
|
|
341
349
|
return `${cls.name}:${name}`;
|
|
342
350
|
}
|
|
343
351
|
|
|
344
352
|
/**
|
|
345
353
|
* Alias a field for usage
|
|
346
354
|
*/
|
|
347
|
-
alias(field: string | FieldConfig, alias: string = this.rootAlias) {
|
|
355
|
+
alias(field: string | FieldConfig, alias: string = this.rootAlias): string {
|
|
348
356
|
return `${alias}.${this.ident(field)}`;
|
|
349
357
|
}
|
|
350
358
|
|
|
351
359
|
/**
|
|
352
360
|
* Get alias cache for the stack
|
|
353
361
|
*/
|
|
354
|
-
getAliasCache(stack: VisitStack[], resolve: (path: VisitStack[]) => string) {
|
|
362
|
+
getAliasCache(stack: VisitStack[], resolve: (path: VisitStack[]) => string): Map<string, Alias> {
|
|
355
363
|
const cls = stack[0].type;
|
|
356
364
|
|
|
357
365
|
if (this.aliasCache.has(cls)) {
|
|
@@ -402,6 +410,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
402
410
|
const { foreignMap, localMap } = SQLUtil.getFieldsByLocation(stack);
|
|
403
411
|
const SQL_OPS = this.SQL_OPS;
|
|
404
412
|
|
|
413
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
405
414
|
for (const key of Object.keys(o) as ((keyof (typeof o)))[]) {
|
|
406
415
|
const top = o[key];
|
|
407
416
|
const field = localMap[key] ?? foreignMap[key];
|
|
@@ -437,18 +446,19 @@ export abstract class SQLDialect implements DialectState {
|
|
|
437
446
|
const arr = [...new Set(Array.isArray(v) ? v : [v])].map(el => resolve(el));
|
|
438
447
|
const valueTable = this.parentTable(sStack);
|
|
439
448
|
const alias = `_all_${sStack.length}`;
|
|
440
|
-
const
|
|
441
|
-
const
|
|
449
|
+
const pPath = this.ident(this.parentPathField.name);
|
|
450
|
+
const rpPath = this.resolveName([...sStack, field, this.parentPathField]);
|
|
442
451
|
|
|
443
452
|
items.push(`${arr.length} = (
|
|
444
453
|
SELECT COUNT(DISTINCT ${alias}.${this.ident(field.name)})
|
|
445
454
|
FROM ${valueTable} ${alias}
|
|
446
|
-
WHERE ${alias}.${
|
|
455
|
+
WHERE ${alias}.${pPath} = ${rpPath}
|
|
447
456
|
AND ${alias}.${this.ident(field.name)} IN (${arr.join(',')})
|
|
448
457
|
)`);
|
|
449
458
|
break;
|
|
450
459
|
}
|
|
451
460
|
case '$regex': {
|
|
461
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
452
462
|
const re = Util.toRegex(v as string);
|
|
453
463
|
const src = re.source;
|
|
454
464
|
const ins = re.flags && re.flags.includes('i');
|
|
@@ -482,6 +492,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
482
492
|
break;
|
|
483
493
|
}
|
|
484
494
|
case '$lt': case '$gt': case '$gte': case '$lte': {
|
|
495
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
485
496
|
const subItems = (Object.keys(top) as (keyof typeof SQL_OPS)[])
|
|
486
497
|
.map(ssk => `${sPath} ${SQL_OPS[ssk]} ${resolve(top[ssk])}`);
|
|
487
498
|
items.push(subItems.length > 1 ? `(${subItems.join(` ${SQL_OPS.$and} `)})` : subItems[0]);
|
|
@@ -529,6 +540,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
529
540
|
getWhereSQL<T>(cls: Class<T>, where?: WhereClause<T>): string {
|
|
530
541
|
return !where || !Object.keys(where).length ?
|
|
531
542
|
'' :
|
|
543
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
532
544
|
`WHERE ${this.getWhereGroupingSQL(cls, where as WhereClause<T>)}`;
|
|
533
545
|
}
|
|
534
546
|
|
|
@@ -598,16 +610,19 @@ LEFT OUTER JOIN ${from} ON
|
|
|
598
610
|
/**
|
|
599
611
|
* Generate full query
|
|
600
612
|
*/
|
|
601
|
-
getQuerySQL<T>(cls: Class<T>, query: Query<T>) {
|
|
613
|
+
getQuerySQL<T>(cls: Class<T>, query: Query<T>): string {
|
|
602
614
|
const sortFields = !query.sort ?
|
|
603
615
|
'' :
|
|
604
616
|
SQLUtil.orderBy(cls, query.sort)
|
|
605
617
|
.map(x => this.resolveName(x.stack))
|
|
606
618
|
.join(', ');
|
|
619
|
+
|
|
620
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
621
|
+
const where = query.where as WhereClause<T>;
|
|
607
622
|
return `
|
|
608
623
|
${this.getSelectSQL(cls, query.select)}
|
|
609
624
|
${this.getFromSQL(cls)}
|
|
610
|
-
${this.getWhereSQL(cls,
|
|
625
|
+
${this.getWhereSQL(cls, where)}
|
|
611
626
|
${this.getGroupBySQL(cls, query)}${sortFields ? `, ${sortFields}` : ''}
|
|
612
627
|
${this.getOrderBySQL(cls, query.sort)}
|
|
613
628
|
${this.getLimitSQL(cls, query)}`;
|
|
@@ -624,6 +639,7 @@ ${this.getLimitSQL(cls, query)}`;
|
|
|
624
639
|
|
|
625
640
|
const fields = SchemaRegistry.has(config.type) ?
|
|
626
641
|
[...SQLUtil.getFieldsByLocation(stack).local] :
|
|
642
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
627
643
|
(array ? [config as FieldConfig] : []);
|
|
628
644
|
|
|
629
645
|
if (!parent) {
|
|
@@ -660,19 +676,19 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
660
676
|
/**
|
|
661
677
|
* Generate drop SQL
|
|
662
678
|
*/
|
|
663
|
-
getDropTableSQL(stack: VisitStack[]) {
|
|
679
|
+
getDropTableSQL(stack: VisitStack[]): string {
|
|
664
680
|
return `DROP TABLE IF EXISTS ${this.table(stack)}; `;
|
|
665
681
|
}
|
|
666
682
|
|
|
667
683
|
/**
|
|
668
684
|
* Generate truncate SQL
|
|
669
685
|
*/
|
|
670
|
-
getTruncateTableSQL(stack: VisitStack[]) {
|
|
686
|
+
getTruncateTableSQL(stack: VisitStack[]): string {
|
|
671
687
|
return `TRUNCATE ${this.table(stack)}; `;
|
|
672
688
|
}
|
|
673
689
|
|
|
674
690
|
/**
|
|
675
|
-
* Get all table
|
|
691
|
+
* Get all table create queries for a class
|
|
676
692
|
*/
|
|
677
693
|
getCreateAllTablesSQL(cls: Class): string[] {
|
|
678
694
|
const out: string[] = [];
|
|
@@ -697,11 +713,13 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
697
713
|
getCreateIndexSQL<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T>): string {
|
|
698
714
|
const table = this.namespace(SQLUtil.classToStack(cls));
|
|
699
715
|
const fields: [string, boolean][] = idx.fields.map(x => {
|
|
716
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
700
717
|
const key = Object.keys(x)[0] as keyof typeof x;
|
|
701
718
|
const val = x[key];
|
|
702
719
|
if (Util.isPlainObject(val)) {
|
|
703
720
|
throw new Error('Unable to supported nested fields for indices');
|
|
704
721
|
}
|
|
722
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
705
723
|
return [key as string, typeof val === 'number' ? val === 1 : (!!val)];
|
|
706
724
|
});
|
|
707
725
|
const constraint = `idx_${table}_${fields.map(([f]) => f).join('_')}`;
|
|
@@ -751,7 +769,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
751
769
|
const isArray = !!config.array;
|
|
752
770
|
|
|
753
771
|
if (isArray) {
|
|
754
|
-
const newInstances
|
|
772
|
+
const newInstances: typeof instances = [];
|
|
755
773
|
for (const el of instances) {
|
|
756
774
|
if (el.value === null || el.value === undefined) {
|
|
757
775
|
continue;
|
|
@@ -776,6 +794,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
776
794
|
return;
|
|
777
795
|
}
|
|
778
796
|
|
|
797
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
779
798
|
const matrix = instances.map(inst => columns.map(c => this.resolveValue(c, (inst.value as Record<string, string>)[c.name])));
|
|
780
799
|
|
|
781
800
|
columnNames.push(this.pathField.name);
|
|
@@ -812,7 +831,7 @@ ${matrix.map(row => `(${row.join(', ')})`).join(',\n')};`;
|
|
|
812
831
|
*/
|
|
813
832
|
getAllInsertSQL<T extends ModelType>(cls: Class<T>, instance: T): string[] {
|
|
814
833
|
const out: string[] = [];
|
|
815
|
-
const add = (text?: string) => text && out.push(text);
|
|
834
|
+
const add = (text?: string): void => { text && out.push(text); };
|
|
816
835
|
SQLUtil.visitSchemaInstance(cls, instance, {
|
|
817
836
|
onRoot: ({ value, path }) => add(this.getInsertSQL(path, [{ stack: path, value }])),
|
|
818
837
|
onSub: ({ value, path }) => add(this.getInsertSQL(path, [{ stack: path, value }])),
|
|
@@ -824,7 +843,7 @@ ${matrix.map(row => `(${row.join(', ')})`).join(',\n')};`;
|
|
|
824
843
|
/**
|
|
825
844
|
* Simple data base updates
|
|
826
845
|
*/
|
|
827
|
-
getUpdateSQL(stack: VisitStack[], data: Record<string, unknown>, where?: WhereClause<unknown>) {
|
|
846
|
+
getUpdateSQL(stack: VisitStack[], data: Record<string, unknown>, where?: WhereClause<unknown>): string {
|
|
828
847
|
const { type } = stack[stack.length - 1];
|
|
829
848
|
const { localMap } = SQLUtil.getFieldsByLocation(stack);
|
|
830
849
|
return `
|
|
@@ -866,18 +885,21 @@ ${orderBy};`;
|
|
|
866
885
|
/**
|
|
867
886
|
* Get COUNT(1) query
|
|
868
887
|
*/
|
|
869
|
-
getQueryCountSQL<T>(cls: Class<T>, query: Query<T>) {
|
|
888
|
+
getQueryCountSQL<T>(cls: Class<T>, query: Query<T>): string {
|
|
889
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
890
|
+
const where = query.where as WhereClause<T>;
|
|
870
891
|
return `
|
|
871
892
|
SELECT COUNT(DISTINCT ${this.rootAlias}.id) as total
|
|
872
893
|
${this.getFromSQL(cls)}
|
|
873
|
-
${this.getWhereSQL(cls,
|
|
894
|
+
${this.getWhereSQL(cls, where)}`;
|
|
874
895
|
}
|
|
875
896
|
|
|
876
897
|
async fetchDependents<T>(cls: Class<T>, items: T[], select?: SelectClause<T>): Promise<T[]> {
|
|
877
898
|
const stack: Record<string, unknown>[] = [];
|
|
878
899
|
const selectStack: (SelectClause<T> | undefined)[] = [];
|
|
879
900
|
|
|
880
|
-
const buildSet = (children: unknown[], field?: FieldConfig)
|
|
901
|
+
const buildSet = (children: unknown[], field?: FieldConfig): Record<string, unknown> =>
|
|
902
|
+
SQLUtil.collectDependents(this, stack[stack.length - 1], children, field);
|
|
881
903
|
|
|
882
904
|
await SQLUtil.visitSchema(SchemaRegistry.get(cls), {
|
|
883
905
|
onRoot: async (config) => {
|
|
@@ -895,6 +917,7 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
895
917
|
|
|
896
918
|
// See if a selection exists at all
|
|
897
919
|
const sel: FieldConfig[] = subSelectTop ? fields
|
|
920
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
898
921
|
.filter(f => subSelectTop[f.name as keyof SelectClause<T>] === 1)
|
|
899
922
|
: [];
|
|
900
923
|
|
|
@@ -924,7 +947,7 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
924
947
|
}
|
|
925
948
|
}
|
|
926
949
|
},
|
|
927
|
-
onSimple: async ({ config, path }) => {
|
|
950
|
+
onSimple: async ({ config, path }): Promise<void> => {
|
|
928
951
|
const top = stack[stack.length - 1];
|
|
929
952
|
const ids = Object.keys(top);
|
|
930
953
|
if (ids.length) {
|
|
@@ -943,7 +966,7 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
943
966
|
/**
|
|
944
967
|
* Delete all ids
|
|
945
968
|
*/
|
|
946
|
-
async deleteByIds(stack: VisitStack[], ids: string[]) {
|
|
969
|
+
async deleteByIds(stack: VisitStack[], ids: string[]): Promise<number> {
|
|
947
970
|
return this.deleteAndGetCount<ModelType>(stack[stack.length - 1].type, {
|
|
948
971
|
where: {
|
|
949
972
|
[stack.length === 1 ? this.idField.name : this.pathField.name]: {
|
|
@@ -956,10 +979,10 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
956
979
|
/**
|
|
957
980
|
* Do bulk process
|
|
958
981
|
*/
|
|
959
|
-
async bulkProcess(
|
|
982
|
+
async bulkProcess(deletes: DeleteWrapper[], inserts: InsertWrapper[], upserts: InsertWrapper[], updates: InsertWrapper[]): Promise<BulkResponse> {
|
|
960
983
|
const out = {
|
|
961
984
|
counts: {
|
|
962
|
-
delete:
|
|
985
|
+
delete: deletes.reduce((acc, el) => acc + el.ids.length, 0),
|
|
963
986
|
error: 0,
|
|
964
987
|
insert: inserts.filter(x => x.stack.length === 1).reduce((acc, el) => acc + el.records.length, 0),
|
|
965
988
|
update: updates.filter(x => x.stack.length === 1).reduce((acc, el) => acc + el.records.length, 0),
|
|
@@ -970,7 +993,7 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
970
993
|
};
|
|
971
994
|
|
|
972
995
|
// Full removals
|
|
973
|
-
await Promise.all(
|
|
996
|
+
await Promise.all(deletes.map(el => this.deleteByIds(el.stack, el.ids)));
|
|
974
997
|
|
|
975
998
|
// Adding deletes
|
|
976
999
|
if (upserts.length || updates.length) {
|
|
@@ -978,9 +1001,11 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
978
1001
|
|
|
979
1002
|
await Promise.all([
|
|
980
1003
|
...upserts.filter(x => x.stack.length === 1).map(i =>
|
|
1004
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
981
1005
|
this.deleteByIds(i.stack, i.records.map(v => (v.value as Record<string, string>)[idx]))
|
|
982
1006
|
),
|
|
983
1007
|
...updates.filter(x => x.stack.length === 1).map(i =>
|
|
1008
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
984
1009
|
this.deleteByIds(i.stack, i.records.map(v => (v.value as Record<string, string>)[idx]))
|
|
985
1010
|
),
|
|
986
1011
|
]);
|
|
@@ -24,7 +24,7 @@ export class MySQLConnection extends Connection<mysql.PoolConnection> {
|
|
|
24
24
|
this.#config = config;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async init() {
|
|
27
|
+
async init(): Promise<void> {
|
|
28
28
|
this.#pool = mysql.createPool({
|
|
29
29
|
user: this.#config.user,
|
|
30
30
|
password: this.#config.password,
|
|
@@ -43,7 +43,7 @@ export class MySQLConnection extends Connection<mysql.PoolConnection> {
|
|
|
43
43
|
/**
|
|
44
44
|
* Support some basic type support for JSON data
|
|
45
45
|
*/
|
|
46
|
-
typeCast(field: Parameters<Exclude<mysql.TypeCast, boolean>>[0], next: () => unknown) {
|
|
46
|
+
typeCast(field: Parameters<Exclude<mysql.TypeCast, boolean>>[0], next: () => unknown): unknown {
|
|
47
47
|
const res = next();
|
|
48
48
|
if (typeof res === 'string' && (field.type === 'JSON' || field.type === 'BLOB')) {
|
|
49
49
|
if (res.charAt(0) === '{' && res.charAt(res.length - 1) === '}') {
|
|
@@ -67,19 +67,19 @@ export class MySQLConnection extends Connection<mysql.PoolConnection> {
|
|
|
67
67
|
rej(err);
|
|
68
68
|
}
|
|
69
69
|
} else {
|
|
70
|
-
const records = Array.isArray(results) ? [...results].map(v => ({ ...v })) : [{ ...results }]
|
|
70
|
+
const records: T[] = Array.isArray(results) ? [...results].map(v => ({ ...v })) : [{ ...results }];
|
|
71
71
|
res({ records, count: results.affectedRows });
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
acquire() {
|
|
77
|
+
acquire(): Promise<mysql.PoolConnection> {
|
|
78
78
|
return new Promise<mysql.PoolConnection>((res, rej) =>
|
|
79
79
|
this.#pool.getConnection((err, conn) => err ? rej(err) : res(conn)));
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
release(conn: mysql.PoolConnection) {
|
|
82
|
+
release(conn: mysql.PoolConnection): void {
|
|
83
83
|
conn.release();
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -52,36 +52,37 @@ export class MySQLDialect extends SQLDialect {
|
|
|
52
52
|
/**
|
|
53
53
|
* Compute hash
|
|
54
54
|
*/
|
|
55
|
-
hash(value: string) {
|
|
55
|
+
hash(value: string): string {
|
|
56
56
|
return `SHA2('${value}', ${this.KEY_LEN * 4})`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Build identifier
|
|
61
61
|
*/
|
|
62
|
-
ident(field: FieldConfig | string) {
|
|
62
|
+
ident(field: FieldConfig | string): string {
|
|
63
63
|
return `\`${typeof field === 'string' ? field : field.name}\``;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Create table, adding in specific engine options
|
|
68
68
|
*/
|
|
69
|
-
override getCreateTableSQL(stack: VisitStack[]) {
|
|
69
|
+
override getCreateTableSQL(stack: VisitStack[]): string {
|
|
70
70
|
return super.getCreateTableSQL(stack).replace(/;$/, ` ${this.tablePostfix};`);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Define column modification
|
|
75
75
|
*/
|
|
76
|
-
getModifyColumnSQL(stack: VisitStack[]) {
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
getModifyColumnSQL(stack: VisitStack[]): string {
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
78
|
+
const field = stack[stack.length - 1] as FieldConfig;
|
|
79
|
+
return `ALTER TABLE ${this.parentTable(stack)} MODIFY COLUMN ${this.getColumnDefinition(field)};`;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
/**
|
|
82
83
|
* Add root alias to delete clause
|
|
83
84
|
*/
|
|
84
|
-
override getDeleteSQL(stack: VisitStack[], where?: WhereClause<unknown>) {
|
|
85
|
+
override getDeleteSQL(stack: VisitStack[], where?: WhereClause<unknown>): string {
|
|
85
86
|
const sql = super.getDeleteSQL(stack, where);
|
|
86
87
|
return sql.replace(/\bDELETE\b/g, `DELETE ${this.rootAlias}`);
|
|
87
88
|
}
|
|
@@ -28,7 +28,7 @@ export class PostgreSQLConnection extends Connection<pg.PoolClient> {
|
|
|
28
28
|
* Initializes connection and establishes crypto extension for use with hashing
|
|
29
29
|
*/
|
|
30
30
|
@WithAsyncContext()
|
|
31
|
-
async init() {
|
|
31
|
+
async init(): Promise<void> {
|
|
32
32
|
this.#pool = new pg.Pool({
|
|
33
33
|
user: this.#config.user,
|
|
34
34
|
password: this.#config.password,
|
|
@@ -53,9 +53,10 @@ export class PostgreSQLConnection extends Connection<pg.PoolClient> {
|
|
|
53
53
|
console.debug('Executing query', { query });
|
|
54
54
|
try {
|
|
55
55
|
const out = await conn.query(query);
|
|
56
|
-
|
|
56
|
+
const records: T[] = [...out.rows].map(v => ({ ...v }));
|
|
57
|
+
return { count: out.rowCount, records };
|
|
57
58
|
} catch (err) {
|
|
58
|
-
if (err.message.includes('duplicate key value')) {
|
|
59
|
+
if (err instanceof Error && err.message.includes('duplicate key value')) {
|
|
59
60
|
throw new ExistsError('query', query);
|
|
60
61
|
} else {
|
|
61
62
|
throw err;
|
|
@@ -63,11 +64,11 @@ export class PostgreSQLConnection extends Connection<pg.PoolClient> {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
acquire() {
|
|
67
|
+
acquire(): Promise<pg.PoolClient> {
|
|
67
68
|
return this.#pool.connect();
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
release(conn: pg.PoolClient) {
|
|
71
|
+
release(conn: pg.PoolClient): void {
|
|
71
72
|
conn.release();
|
|
72
73
|
}
|
|
73
74
|
}
|
|
@@ -41,20 +41,21 @@ export class PostgreSQLDialect extends SQLDialect {
|
|
|
41
41
|
/**
|
|
42
42
|
* How to hash
|
|
43
43
|
*/
|
|
44
|
-
hash(value: string) {
|
|
44
|
+
hash(value: string): string {
|
|
45
45
|
return `encode(digest('${value}', 'sha1'), 'hex')`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
ident(field: FieldConfig | string) {
|
|
48
|
+
ident(field: FieldConfig | string): string {
|
|
49
49
|
return `"${typeof field === 'string' ? field : field.name}"`;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Define column modification
|
|
54
54
|
*/
|
|
55
|
-
getModifyColumnSQL(stack: VisitStack[]) {
|
|
56
|
-
|
|
57
|
-
const
|
|
55
|
+
getModifyColumnSQL(stack: VisitStack[]): string {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
57
|
+
const field = stack[stack.length - 1] as FieldConfig;
|
|
58
|
+
const type = this.getColumnType(field);
|
|
58
59
|
const ident = this.ident(field.name);
|
|
59
60
|
return `ALTER TABLE ${this.parentTable(stack)} ALTER COLUMN ${ident} TYPE ${type} USING (${ident}::${type});`;
|
|
60
61
|
}
|
|
@@ -29,12 +29,12 @@ export class SqliteConnection extends Connection<sqlite3.Database> {
|
|
|
29
29
|
this.#config = config;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
async #withRetries<T>(op: () => Promise<T>, retries = 10, delay = 250) {
|
|
32
|
+
async #withRetries<T>(op: () => Promise<T>, retries = 10, delay = 250): Promise<T> {
|
|
33
33
|
for (; ;) {
|
|
34
34
|
try {
|
|
35
35
|
return await op();
|
|
36
36
|
} catch (err) {
|
|
37
|
-
if (retries > 1 && err.message.includes('database is locked')) {
|
|
37
|
+
if (err instanceof Error && retries > 1 && err.message.includes('database is locked')) {
|
|
38
38
|
console.error('Failed, and waiting', retries);
|
|
39
39
|
await Util.wait(delay);
|
|
40
40
|
retries -= 1;
|
|
@@ -49,11 +49,11 @@ export class SqliteConnection extends Connection<sqlite3.Database> {
|
|
|
49
49
|
* Initializes connection and establishes crypto extension for use with hashing
|
|
50
50
|
*/
|
|
51
51
|
@WithAsyncContext()
|
|
52
|
-
override async init() {
|
|
52
|
+
override async init(): Promise<void> {
|
|
53
53
|
this.#pool = pool.createPool({
|
|
54
54
|
create: () => this.#withRetries(async () => {
|
|
55
55
|
const db = Db(AppCache.toEntryName('sqlite_db'),
|
|
56
|
-
this.#config.options
|
|
56
|
+
this.#config.options
|
|
57
57
|
);
|
|
58
58
|
await db.pragma('foreign_keys = ON');
|
|
59
59
|
await db.pragma('journal_mode = WAL');
|
|
@@ -73,21 +73,26 @@ export class SqliteConnection extends Connection<sqlite3.Database> {
|
|
|
73
73
|
try {
|
|
74
74
|
const out = await conn.prepare(query)[query.trim().startsWith('SELECT') ? 'all' : 'run']();
|
|
75
75
|
if (Array.isArray(out)) {
|
|
76
|
-
|
|
76
|
+
const records: T[] = [...out].map(v => ({ ...v }));
|
|
77
|
+
return { count: out.length, records };
|
|
77
78
|
} else {
|
|
78
79
|
return { count: out.changes, records: [] };
|
|
79
80
|
}
|
|
80
81
|
} catch (err) {
|
|
81
|
-
|
|
82
|
+
if (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {
|
|
83
|
+
throw new ExistsError('query', query);
|
|
84
|
+
} else {
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
82
87
|
}
|
|
83
88
|
});
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
async acquire() {
|
|
91
|
+
async acquire(): Promise<Db.Database> {
|
|
87
92
|
return this.#pool.acquire();
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
async release(db: sqlite3.Database) {
|
|
95
|
+
async release(db: sqlite3.Database): Promise<void> {
|
|
91
96
|
return this.#pool.release(db);
|
|
92
97
|
}
|
|
93
98
|
}
|
|
@@ -34,30 +34,31 @@ export class SqliteDialect extends SQLDialect {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
override resolveDateValue(value: Date) {
|
|
37
|
+
override resolveDateValue(value: Date): string {
|
|
38
38
|
return `${value.getTime()}`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* How to hash
|
|
43
43
|
*/
|
|
44
|
-
hash(value: string) {
|
|
44
|
+
hash(value: string): string {
|
|
45
45
|
return `hex('${value}')`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Build identifier
|
|
50
50
|
*/
|
|
51
|
-
ident(field: FieldConfig | string) {
|
|
51
|
+
ident(field: FieldConfig | string): string {
|
|
52
52
|
return `\`${typeof field === 'string' ? field : field.name}\``;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Define column modification
|
|
57
57
|
*/
|
|
58
|
-
getModifyColumnSQL(stack: VisitStack[]) {
|
|
59
|
-
|
|
60
|
-
const
|
|
58
|
+
getModifyColumnSQL(stack: VisitStack[]): string {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
60
|
+
const field = stack[stack.length - 1] as FieldConfig;
|
|
61
|
+
const type = this.getColumnType(field);
|
|
61
62
|
const ident = this.ident(field.name);
|
|
62
63
|
return `ALTER TABLE ${this.parentTable(stack)} ALTER COLUMN ${ident} TYPE ${type} USING (${ident}::${type});`;
|
|
63
64
|
}
|
|
@@ -65,7 +66,7 @@ export class SqliteDialect extends SQLDialect {
|
|
|
65
66
|
/**
|
|
66
67
|
* Generate truncate SQL
|
|
67
68
|
*/
|
|
68
|
-
override getTruncateTableSQL(stack: VisitStack[]) {
|
|
69
|
+
override getTruncateTableSQL(stack: VisitStack[]): string {
|
|
69
70
|
return `DELETE FROM ${this.table(stack)};`;
|
|
70
71
|
}
|
|
71
72
|
|
package/src/internal/util.ts
CHANGED
|
@@ -16,17 +16,19 @@ export type VisitStack = {
|
|
|
16
16
|
index?: number;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
type FieldCacheEntry = {
|
|
20
|
+
local: FieldConfig[];
|
|
21
|
+
localMap: Record<string, FieldConfig>;
|
|
22
|
+
foreign: FieldConfig[];
|
|
23
|
+
foreignMap: Record<string, FieldConfig>;
|
|
24
|
+
};
|
|
25
|
+
|
|
19
26
|
/**
|
|
20
27
|
* Utilities for dealing with SQL operations
|
|
21
28
|
*/
|
|
22
29
|
export class SQLUtil {
|
|
23
30
|
|
|
24
|
-
static SCHEMA_FIELDS_CACHE = new Map<Class,
|
|
25
|
-
local: FieldConfig[];
|
|
26
|
-
localMap: Record<string, FieldConfig>;
|
|
27
|
-
foreign: FieldConfig[];
|
|
28
|
-
foreignMap: Record<string, FieldConfig>;
|
|
29
|
-
}>();
|
|
31
|
+
static SCHEMA_FIELDS_CACHE = new Map<Class, FieldCacheEntry>();
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* Creates a new visitation stack with the class as the root
|
|
@@ -44,6 +46,7 @@ export class SQLUtil {
|
|
|
44
46
|
if (Array.isArray(o)) {
|
|
45
47
|
return o.filter(x => x !== null && x !== undefined).map(x => this.cleanResults(dct, x));
|
|
46
48
|
} else if (!Util.isSimple(o)) {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
47
50
|
for (const k of Object.keys(o) as (keyof T)[]) {
|
|
48
51
|
if (o[k] === null || o[k] === undefined || k === dct.parentPathField.name || k === dct.pathField.name || k === dct.idxField.name) {
|
|
49
52
|
delete o[k];
|
|
@@ -51,8 +54,10 @@ export class SQLUtil {
|
|
|
51
54
|
o[k] = this.cleanResults(dct, o[k]);
|
|
52
55
|
}
|
|
53
56
|
}
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
54
58
|
return { ...o } as unknown as U[];
|
|
55
59
|
} else {
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
56
61
|
return o as unknown as U;
|
|
57
62
|
}
|
|
58
63
|
}
|
|
@@ -60,7 +65,7 @@ export class SQLUtil {
|
|
|
60
65
|
/**
|
|
61
66
|
* Get all available fields at current stack path
|
|
62
67
|
*/
|
|
63
|
-
static getFieldsByLocation(stack: VisitStack[]) {
|
|
68
|
+
static getFieldsByLocation(stack: VisitStack[]): FieldCacheEntry {
|
|
64
69
|
const top = stack[stack.length - 1];
|
|
65
70
|
const cls = SchemaRegistry.get(top.type);
|
|
66
71
|
|
|
@@ -69,6 +74,7 @@ export class SQLUtil {
|
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
if (!cls) { // If a simple type, it is it's own field
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
72
78
|
const field = { ...top } as FieldConfig;
|
|
73
79
|
return {
|
|
74
80
|
local: [field], localMap: { [field.name]: field },
|
|
@@ -94,9 +100,9 @@ export class SQLUtil {
|
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
const ret = {
|
|
98
|
-
localMap: {}
|
|
99
|
-
foreignMap: {}
|
|
103
|
+
const ret: FieldCacheEntry = {
|
|
104
|
+
localMap: {},
|
|
105
|
+
foreignMap: {},
|
|
100
106
|
local: fields.filter(x => !SchemaRegistry.has(x.type) && !x.array),
|
|
101
107
|
foreign: fields.filter(x => SchemaRegistry.has(x.type) || x.array)
|
|
102
108
|
};
|
|
@@ -112,11 +118,11 @@ export class SQLUtil {
|
|
|
112
118
|
/**
|
|
113
119
|
* Process a schema structure, synchronously
|
|
114
120
|
*/
|
|
115
|
-
static visitSchemaSync(config: ClassConfig | FieldConfig, handler: VisitHandler<void>, state: VisitState = { path: [] }) {
|
|
121
|
+
static visitSchemaSync(config: ClassConfig | FieldConfig, handler: VisitHandler<void>, state: VisitState = { path: [] }): void {
|
|
116
122
|
const path = 'class' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
117
123
|
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
118
124
|
|
|
119
|
-
const descend = () => {
|
|
125
|
+
const descend = (): void => {
|
|
120
126
|
for (const field of foreign) {
|
|
121
127
|
if (SchemaRegistry.has(field.type)) {
|
|
122
128
|
this.visitSchemaSync(field, handler, { path });
|
|
@@ -140,11 +146,11 @@ export class SQLUtil {
|
|
|
140
146
|
/**
|
|
141
147
|
* Visit a Schema structure
|
|
142
148
|
*/
|
|
143
|
-
static async visitSchema(config: ClassConfig | FieldConfig, handler: VisitHandler<Promise<void>>, state: VisitState = { path: [] }) {
|
|
149
|
+
static async visitSchema(config: ClassConfig | FieldConfig, handler: VisitHandler<Promise<void>>, state: VisitState = { path: [] }): Promise<void> {
|
|
144
150
|
const path = 'class' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
145
151
|
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
146
152
|
|
|
147
|
-
const descend = async () => {
|
|
153
|
+
const descend = async (): Promise<void> => {
|
|
148
154
|
for (const field of foreign) {
|
|
149
155
|
if (SchemaRegistry.has(field.type)) {
|
|
150
156
|
await this.visitSchema(field, handler, { path });
|
|
@@ -168,7 +174,7 @@ export class SQLUtil {
|
|
|
168
174
|
/**
|
|
169
175
|
* Process a schema instance by visiting it synchronously. This is synchronous to prevent concurrent calls from breaking
|
|
170
176
|
*/
|
|
171
|
-
static visitSchemaInstance<T extends ModelType>(cls: Class<T>, instance: T, handler: VisitHandler<unknown, VisitInstanceNode<unknown>>) {
|
|
177
|
+
static visitSchemaInstance<T extends ModelType>(cls: Class<T>, instance: T, handler: VisitHandler<unknown, VisitInstanceNode<unknown>>): void {
|
|
172
178
|
const pathObj: unknown[] = [instance];
|
|
173
179
|
this.visitSchemaSync(SchemaRegistry.get(cls), {
|
|
174
180
|
onRoot: (config) => {
|
|
@@ -179,15 +185,16 @@ export class SQLUtil {
|
|
|
179
185
|
},
|
|
180
186
|
onSub: (config) => {
|
|
181
187
|
const { config: field } = config;
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
182
189
|
const topObj = pathObj[pathObj.length - 1] as Record<string, unknown>;
|
|
183
190
|
const top = config.path[config.path.length - 1];
|
|
184
191
|
|
|
185
192
|
if (field.name in topObj) {
|
|
186
193
|
const value = topObj[field.name];
|
|
187
|
-
const
|
|
194
|
+
const values = Array.isArray(value) ? value : [value];
|
|
188
195
|
|
|
189
196
|
let i = 0;
|
|
190
|
-
for (const val of
|
|
197
|
+
for (const val of values) {
|
|
191
198
|
try {
|
|
192
199
|
pathObj.push(val);
|
|
193
200
|
config.path[config.path.length - 1] = { ...top, index: i++ };
|
|
@@ -202,6 +209,7 @@ export class SQLUtil {
|
|
|
202
209
|
},
|
|
203
210
|
onSimple: (config) => {
|
|
204
211
|
const { config: field } = config;
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
205
213
|
const topObj = pathObj[pathObj.length - 1] as Record<string, unknown>;
|
|
206
214
|
const value = topObj[field.name];
|
|
207
215
|
return handler.onSimple({ ...config, value });
|
|
@@ -214,7 +222,7 @@ export class SQLUtil {
|
|
|
214
222
|
*/
|
|
215
223
|
static select<T>(cls: Class<T>, select?: SelectClause<T>): FieldConfig[] {
|
|
216
224
|
if (!select || Object.keys(select).length === 0) {
|
|
217
|
-
return [{ type: cls, name: '*'
|
|
225
|
+
return [{ type: cls, name: '*', owner: cls, array: false }];
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
const { localMap } = this.getFieldsByLocation(this.classToStack(cls));
|
|
@@ -222,7 +230,9 @@ export class SQLUtil {
|
|
|
222
230
|
let toGet = new Set<string>();
|
|
223
231
|
|
|
224
232
|
for (const [k, v] of Object.entries(select)) {
|
|
225
|
-
|
|
233
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
234
|
+
const typedKey = k as keyof typeof select;
|
|
235
|
+
if (!Util.isPlainObject(select[typedKey]) && localMap[k]) {
|
|
226
236
|
if (!v) {
|
|
227
237
|
if (toGet.size === 0) {
|
|
228
238
|
toGet = new Set(SchemaRegistry.get(cls).views[AllViewⲐ].fields);
|
|
@@ -254,6 +264,7 @@ export class SQLUtil {
|
|
|
254
264
|
} else {
|
|
255
265
|
stack.push(field);
|
|
256
266
|
schema = SchemaRegistry.get(field.type);
|
|
267
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
257
268
|
cl = val as Record<string, unknown>;
|
|
258
269
|
}
|
|
259
270
|
}
|
|
@@ -264,18 +275,24 @@ export class SQLUtil {
|
|
|
264
275
|
/**
|
|
265
276
|
* Find all dependent fields via child tables
|
|
266
277
|
*/
|
|
267
|
-
static collectDependents<T>(dct: DialectState, parent: unknown, v: T[], field?: FieldConfig) {
|
|
278
|
+
static collectDependents<T>(dct: DialectState, parent: unknown, v: T[], field?: FieldConfig): Record<string, T> {
|
|
268
279
|
if (field) {
|
|
269
280
|
const isSimple = SchemaRegistry.has(field.type);
|
|
270
281
|
for (const el of v) {
|
|
271
|
-
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
283
|
+
const parentKey = el[dct.parentPathField.name as keyof T] as unknown as string;
|
|
284
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
285
|
+
const root = (parent as Record<string, Record<string, unknown>>)[parentKey];
|
|
272
286
|
if (field.array) {
|
|
273
287
|
if (!root[field.name]) {
|
|
288
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
274
289
|
root[field.name] = [isSimple ? el : el[field.name as keyof T]];
|
|
275
290
|
} else {
|
|
291
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
276
292
|
(root[field.name] as unknown[]).push(isSimple ? el : el[field.name as keyof T]);
|
|
277
293
|
}
|
|
278
294
|
} else {
|
|
295
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
279
296
|
root[field.name] = isSimple ? el : el[field.name as keyof T];
|
|
280
297
|
}
|
|
281
298
|
}
|
|
@@ -283,6 +300,7 @@ export class SQLUtil {
|
|
|
283
300
|
|
|
284
301
|
const mapping: Record<string, T> = {};
|
|
285
302
|
for (const el of v) {
|
|
303
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
286
304
|
const key = el[dct.pathField.name as keyof T];
|
|
287
305
|
if (typeof key === 'string') {
|
|
288
306
|
mapping[key] = el;
|
|
@@ -294,7 +312,7 @@ export class SQLUtil {
|
|
|
294
312
|
/**
|
|
295
313
|
* Build table name via stack path
|
|
296
314
|
*/
|
|
297
|
-
static buildTable(list: VisitStack[]) {
|
|
315
|
+
static buildTable(list: VisitStack[]): string {
|
|
298
316
|
const top = list[list.length - 1];
|
|
299
317
|
if (!top[TableⲐ]) {
|
|
300
318
|
top[TableⲐ] = list.map((el, i) => i === 0 ? ModelRegistry.getStore(el.type) : el.name).join('_');
|
|
@@ -305,7 +323,7 @@ export class SQLUtil {
|
|
|
305
323
|
/**
|
|
306
324
|
* Build property path for a table/field given the current stack
|
|
307
325
|
*/
|
|
308
|
-
static buildPath(list: VisitStack[]) {
|
|
326
|
+
static buildPath(list: VisitStack[]): string {
|
|
309
327
|
return list.map((el, i) => `${el.name}${el.index ? `[${el.index}]` : ''}`).join('.');
|
|
310
328
|
}
|
|
311
329
|
|
|
@@ -313,9 +331,9 @@ export class SQLUtil {
|
|
|
313
331
|
* Get insert statements for a given class, and its child tables
|
|
314
332
|
*/
|
|
315
333
|
static async getInserts<T extends ModelType>(cls: Class<T>, els: T[]): Promise<InsertWrapper[]> {
|
|
316
|
-
const ins
|
|
334
|
+
const ins: Record<string, InsertWrapper> = {};
|
|
317
335
|
|
|
318
|
-
const track = (stack: VisitStack[], value: unknown) => {
|
|
336
|
+
const track = (stack: VisitStack[], value: unknown): void => {
|
|
319
337
|
const key = this.buildTable(stack);
|
|
320
338
|
(ins[key] = ins[key] ?? { stack, records: [] }).records.push({ stack, value });
|
|
321
339
|
};
|
package/src/service.ts
CHANGED
|
@@ -26,6 +26,8 @@ import { Connected, ConnectedIterator, Transactional } from './connection/decora
|
|
|
26
26
|
import { SQLUtil } from './internal/util';
|
|
27
27
|
import { SQLDialect } from './dialect/base';
|
|
28
28
|
import { TableManager } from './table-manager';
|
|
29
|
+
import { Connection } from './connection/base';
|
|
30
|
+
import { InsertWrapper } from './internal/types';
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Core for SQL Model Source. Should not have any direct queries,
|
|
@@ -45,7 +47,7 @@ export class SQLModelService implements
|
|
|
45
47
|
|
|
46
48
|
readonly config: SQLModelConfig;
|
|
47
49
|
|
|
48
|
-
get client() {
|
|
50
|
+
get client(): SQLDialect {
|
|
49
51
|
return this.#dialect;
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -66,7 +68,7 @@ export class SQLModelService implements
|
|
|
66
68
|
cls: Class<T>,
|
|
67
69
|
addedIds: Map<number, string>,
|
|
68
70
|
toCheck: Map<string, number>
|
|
69
|
-
) {
|
|
71
|
+
): Promise<Map<number, string>> {
|
|
70
72
|
// Get all upsert ids
|
|
71
73
|
const all = toCheck.size ?
|
|
72
74
|
(await this.#exec<ModelType>(
|
|
@@ -86,20 +88,21 @@ export class SQLModelService implements
|
|
|
86
88
|
return addedIds;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
#exec<T = unknown>(sql: string) {
|
|
91
|
+
#exec<T = unknown>(sql: string): Promise<{ records: T[], count: number }> {
|
|
90
92
|
return this.#dialect.executeSQL<T>(sql);
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
async #deleteRaw<T extends ModelType>(cls: Class<T>, id: string, checkExpiry = true) {
|
|
95
|
+
async #deleteRaw<T extends ModelType>(cls: Class<T>, id: string, checkExpiry = true): Promise<void> {
|
|
96
|
+
const where: WhereClauseRaw<ModelType> = { id };
|
|
94
97
|
const count = await this.#dialect.deleteAndGetCount<ModelType>(cls, {
|
|
95
|
-
where: ModelQueryUtil.getWhereClause(cls,
|
|
98
|
+
where: ModelQueryUtil.getWhereClause(cls, where, checkExpiry)
|
|
96
99
|
});
|
|
97
100
|
if (count === 0) {
|
|
98
101
|
throw new NotFoundError(cls, id);
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
async postConstruct() {
|
|
105
|
+
async postConstruct(): Promise<void> {
|
|
103
106
|
if (this.#dialect) {
|
|
104
107
|
if (this.#dialect.conn.init) {
|
|
105
108
|
await this.#dialect.conn.init();
|
|
@@ -110,32 +113,32 @@ export class SQLModelService implements
|
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
get conn() {
|
|
116
|
+
get conn(): Connection {
|
|
114
117
|
return this.#dialect.conn;
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
uuid() {
|
|
120
|
+
uuid(): string {
|
|
118
121
|
return this.#dialect.generateId();
|
|
119
122
|
}
|
|
120
123
|
|
|
121
|
-
async changeSchema(cls: Class, change: SchemaChange) {
|
|
124
|
+
async changeSchema(cls: Class, change: SchemaChange): Promise<void> {
|
|
122
125
|
await this.#manager.changeSchema(cls, change);
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
async createModel(cls: Class) {
|
|
128
|
+
async createModel(cls: Class): Promise<void> {
|
|
126
129
|
await this.#manager.createTables(cls);
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
async deleteModel(cls: Class) {
|
|
132
|
+
async deleteModel(cls: Class): Promise<void> {
|
|
130
133
|
await this.#manager.dropTables(cls);
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
async truncateModel(cls: Class) {
|
|
136
|
+
async truncateModel(cls: Class): Promise<void> {
|
|
134
137
|
await this.#manager.truncateTables(cls);
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
async createStorage() { }
|
|
138
|
-
async deleteStorage() { }
|
|
140
|
+
async createStorage(): Promise<void> { }
|
|
141
|
+
async deleteStorage(): Promise<void> { }
|
|
139
142
|
|
|
140
143
|
@Transactional()
|
|
141
144
|
async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
|
|
@@ -183,7 +186,8 @@ export class SQLModelService implements
|
|
|
183
186
|
|
|
184
187
|
@Connected()
|
|
185
188
|
async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
|
|
186
|
-
const
|
|
189
|
+
const where: WhereClauseRaw<ModelType> = { id };
|
|
190
|
+
const res = await this.query(cls, { where });
|
|
187
191
|
if (res.length === 1) {
|
|
188
192
|
return await ModelCrudUtil.load(cls, res[0]);
|
|
189
193
|
}
|
|
@@ -191,14 +195,14 @@ export class SQLModelService implements
|
|
|
191
195
|
}
|
|
192
196
|
|
|
193
197
|
@ConnectedIterator()
|
|
194
|
-
async * list<T extends ModelType>(cls: Class<T>) {
|
|
198
|
+
async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
|
|
195
199
|
for (const item of await this.query(cls, {})) {
|
|
196
200
|
yield await ModelCrudUtil.load(cls, item);
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
203
|
|
|
200
204
|
@Transactional()
|
|
201
|
-
async delete<T extends ModelType>(cls: Class<T>, id: string) {
|
|
205
|
+
async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
|
|
202
206
|
await this.#deleteRaw(cls, id, false);
|
|
203
207
|
}
|
|
204
208
|
|
|
@@ -214,8 +218,10 @@ export class SQLModelService implements
|
|
|
214
218
|
new Map([...existingUpsertedIds.entries()].map(([k, v]) => [v, k]))
|
|
215
219
|
);
|
|
216
220
|
|
|
217
|
-
const get = (k: keyof BulkOp<T>)
|
|
218
|
-
|
|
221
|
+
const get = (k: keyof BulkOp<T>): T[] =>
|
|
222
|
+
operations.map(x => x[k]).filter((x): x is T => !!x);
|
|
223
|
+
const getStatements = async (k: keyof BulkOp<T>): Promise<InsertWrapper[]> =>
|
|
224
|
+
(await SQLUtil.getInserts(cls, get(k))).filter(x => !!x.records.length);
|
|
219
225
|
|
|
220
226
|
const deletes = [{ stack: SQLUtil.classToStack(cls), ids: get('delete').map(x => x.id) }].filter(x => !!x.ids.length);
|
|
221
227
|
|
|
@@ -232,7 +238,7 @@ export class SQLModelService implements
|
|
|
232
238
|
|
|
233
239
|
// Expiry
|
|
234
240
|
@Transactional()
|
|
235
|
-
async deleteExpired<T extends ModelType>(cls: Class<T>) {
|
|
241
|
+
async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
|
|
236
242
|
return ModelQueryExpiryUtil.deleteExpired(this, cls);
|
|
237
243
|
}
|
|
238
244
|
|
|
@@ -284,12 +290,15 @@ export class SQLModelService implements
|
|
|
284
290
|
async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
|
|
285
291
|
const q = ModelQuerySuggestUtil.getSuggestFieldQuery(cls, field, prefix, query);
|
|
286
292
|
const results = await this.query(cls, q);
|
|
287
|
-
|
|
293
|
+
|
|
294
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
295
|
+
const modelTypeField = field as ValidStringFields<ModelType>;
|
|
296
|
+
return ModelQuerySuggestUtil.combineSuggestResults(cls, modelTypeField, prefix, results, x => x, query?.limit);
|
|
288
297
|
}
|
|
289
298
|
|
|
290
299
|
@Connected()
|
|
291
300
|
async facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<{ key: string, count: number }[]> {
|
|
292
|
-
const col = this.#dialect.ident(field
|
|
301
|
+
const col = this.#dialect.ident(field);
|
|
293
302
|
const ttl = this.#dialect.ident('count');
|
|
294
303
|
const key = this.#dialect.ident('key');
|
|
295
304
|
const q = [
|
package/src/table-manager.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { SchemaChange } from '@travetto/schema';
|
|
|
7
7
|
import { Connected, Transactional } from './connection/decorator';
|
|
8
8
|
import { SQLDialect } from './dialect/base';
|
|
9
9
|
import { SQLUtil, VisitStack } from './internal/util';
|
|
10
|
+
import { Connection } from './connection/base';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Manage creation/updating of all tables
|
|
@@ -22,7 +23,7 @@ export class TableManager {
|
|
|
22
23
|
this.#dialect = dialect;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
#exec<T = unknown>(sql: string) {
|
|
26
|
+
#exec<T = unknown>(sql: string): Promise<{ records: T[], count: number }> {
|
|
26
27
|
return this.#dialect.executeSQL<T>(sql);
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -41,9 +42,12 @@ export class TableManager {
|
|
|
41
42
|
for (const op of this.#dialect.getCreateAllIndicesSQL(cls, indices)) {
|
|
42
43
|
try {
|
|
43
44
|
await this.#exec(op);
|
|
44
|
-
} catch (
|
|
45
|
-
if (
|
|
46
|
-
throw
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (!(err instanceof Error)) {
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
if (!/\bexists|duplicate\b/i.test(err.message)) {
|
|
50
|
+
throw err;
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -78,7 +82,7 @@ export class TableManager {
|
|
|
78
82
|
/**
|
|
79
83
|
* Get a valid connection
|
|
80
84
|
*/
|
|
81
|
-
get conn() {
|
|
85
|
+
get conn(): Connection {
|
|
82
86
|
return this.#dialect.conn;
|
|
83
87
|
}
|
|
84
88
|
|
|
@@ -88,24 +92,24 @@ export class TableManager {
|
|
|
88
92
|
@WithAsyncContext({})
|
|
89
93
|
@Transactional()
|
|
90
94
|
@Connected()
|
|
91
|
-
async changeSchema(cls: Class, change: SchemaChange) {
|
|
95
|
+
async changeSchema(cls: Class, change: SchemaChange): Promise<void> {
|
|
92
96
|
try {
|
|
93
97
|
const rootStack = SQLUtil.classToStack(cls);
|
|
94
98
|
|
|
95
|
-
const changes = change.subs.reduce((acc, v) => {
|
|
99
|
+
const changes = change.subs.reduce<Record<ChangeEvent<unknown>['type'], VisitStack[][]>>((acc, v) => {
|
|
96
100
|
const path = v.path.map(f => ({ ...f }));
|
|
97
101
|
for (const ev of v.fields) {
|
|
98
102
|
acc[ev.type].push([...rootStack, ...path, { ...(ev.type === 'removing' ? ev.prev : ev.curr)! }]);
|
|
99
103
|
}
|
|
100
104
|
return acc;
|
|
101
|
-
}, { added: [], changed: [], removing: [] }
|
|
105
|
+
}, { added: [], changed: [], removing: [] });
|
|
102
106
|
|
|
103
107
|
await Promise.all(changes.added.map(v => this.#dialect.executeSQL(this.#dialect.getAddColumnSQL(v))));
|
|
104
108
|
await Promise.all(changes.changed.map(v => this.#dialect.executeSQL(this.#dialect.getModifyColumnSQL(v))));
|
|
105
109
|
await Promise.all(changes.removing.map(v => this.#dialect.executeSQL(this.#dialect.getDropColumnSQL(v))));
|
|
106
|
-
} catch (
|
|
110
|
+
} catch (err) {
|
|
107
111
|
// Failed to change
|
|
108
|
-
console.error('Unable to change field', { error:
|
|
112
|
+
console.error('Unable to change field', { error: err });
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
}
|