@travetto/model-sql 2.1.5 → 2.2.2
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 +72 -49
- 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 -26
- 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.2",
|
|
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.2",
|
|
32
|
+
"@travetto/context": "^2.2.2",
|
|
33
|
+
"@travetto/model": "^2.2.2",
|
|
34
|
+
"@travetto/model-query": "2.2.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@travetto/app": "^2.
|
|
37
|
+
"@travetto/app": "^2.2.2"
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SchemaRegistry, FieldConfig, Schema } from '@travetto/schema';
|
|
2
2
|
import { Class, Util, AppError } from '@travetto/base';
|
|
3
|
-
import { SelectClause, Query, SortClause, WhereClause } from '@travetto/model-query';
|
|
3
|
+
import { SelectClause, Query, SortClause, WhereClause, RetainFields } from '@travetto/model-query';
|
|
4
4
|
import { BulkResponse, IndexConfig } from '@travetto/model';
|
|
5
5
|
import { PointImpl } from '@travetto/model-query/src/internal/model/point';
|
|
6
6
|
import { ModelType } from '@travetto/model/src/types/model';
|
|
@@ -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,14 @@ 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
|
-
owner: null,
|
|
31
33
|
type,
|
|
32
34
|
array: false,
|
|
33
35
|
...(required ? { required: { active: true } } : {}),
|
|
34
36
|
...extra
|
|
35
|
-
}
|
|
37
|
+
};
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -81,7 +83,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
81
83
|
BOOLEAN: 'BOOLEAN',
|
|
82
84
|
TINYINT: 'TINYINT',
|
|
83
85
|
SMALLINT: 'SMALLINT',
|
|
84
|
-
|
|
86
|
+
MEDIUMINT: 'MEDIUMINT',
|
|
85
87
|
INT: 'INT',
|
|
86
88
|
BIGINT: 'BIGINT',
|
|
87
89
|
TIMESTAMP: 'TIMESTAMP',
|
|
@@ -91,7 +93,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
91
93
|
/**
|
|
92
94
|
* Column types with inputs
|
|
93
95
|
*/
|
|
94
|
-
PARAMETERIZED_COLUMN_TYPES: Record<'VARCHAR' | 'DECIMAL', (...
|
|
96
|
+
PARAMETERIZED_COLUMN_TYPES: Record<'VARCHAR' | 'DECIMAL', (...values: number[]) => string> = {
|
|
95
97
|
VARCHAR: n => `VARCHAR(${n})`,
|
|
96
98
|
DECIMAL: (d, p) => `DECIMAL(${d},${p})`
|
|
97
99
|
};
|
|
@@ -153,7 +155,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
153
155
|
*/
|
|
154
156
|
abstract hash(inp: string): string;
|
|
155
157
|
|
|
156
|
-
executeSQL<T>(sql: string) {
|
|
158
|
+
executeSQL<T>(sql: string): Promise<{ records: T[], count: number }> {
|
|
157
159
|
return this.conn.execute<T>(this.conn.active, sql);
|
|
158
160
|
}
|
|
159
161
|
|
|
@@ -171,15 +173,15 @@ export abstract class SQLDialect implements DialectState {
|
|
|
171
173
|
* @param value
|
|
172
174
|
* @returns
|
|
173
175
|
*/
|
|
174
|
-
resolveDateValue(value: Date) {
|
|
175
|
-
const [day, time] =
|
|
176
|
+
resolveDateValue(value: Date): string {
|
|
177
|
+
const [day, time] = value.toISOString().split(/[TZ]/);
|
|
176
178
|
return this.quote(`${day} ${time}`);
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
/**
|
|
180
182
|
* Convert value to SQL valid representation
|
|
181
183
|
*/
|
|
182
|
-
resolveValue(conf: FieldConfig, value: unknown) {
|
|
184
|
+
resolveValue(conf: FieldConfig, value: unknown): string {
|
|
183
185
|
if (value === undefined || value === null) {
|
|
184
186
|
return 'NULL';
|
|
185
187
|
} else if (conf.type === String) {
|
|
@@ -187,6 +189,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
187
189
|
const src = Util.toRegex(value).source.replace(/\\b/g, this.regexWordBoundary);
|
|
188
190
|
return this.quote(src);
|
|
189
191
|
} else {
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
190
193
|
return this.quote(value as string);
|
|
191
194
|
}
|
|
192
195
|
} else if (conf.type === Boolean) {
|
|
@@ -194,6 +197,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
194
197
|
} else if (conf.type === Number) {
|
|
195
198
|
return `${value}`;
|
|
196
199
|
} else if (conf.type === Date) {
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
197
201
|
return this.resolveDateValue(ModelQueryUtil.resolveComparator(value) as Date);
|
|
198
202
|
} else if (conf.type === PointImpl && Array.isArray(value)) {
|
|
199
203
|
return `point(${value[0]},${value[1]})`;
|
|
@@ -206,7 +210,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
206
210
|
/**
|
|
207
211
|
* Get column type from field config
|
|
208
212
|
*/
|
|
209
|
-
getColumnType(conf: FieldConfig) {
|
|
213
|
+
getColumnType(conf: FieldConfig): string {
|
|
210
214
|
let type: string = '';
|
|
211
215
|
|
|
212
216
|
if (conf.type === Number) {
|
|
@@ -221,7 +225,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
221
225
|
} else if (digits < 5) {
|
|
222
226
|
type = this.COLUMN_TYPES.SMALLINT;
|
|
223
227
|
} else if (digits < 7) {
|
|
224
|
-
type = this.COLUMN_TYPES.
|
|
228
|
+
type = this.COLUMN_TYPES.MEDIUMINT;
|
|
225
229
|
} else if (digits < 10) {
|
|
226
230
|
type = this.COLUMN_TYPES.INT;
|
|
227
231
|
} else {
|
|
@@ -253,7 +257,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
253
257
|
/**
|
|
254
258
|
* FieldConfig to Column definition
|
|
255
259
|
*/
|
|
256
|
-
getColumnDefinition(conf: FieldConfig) {
|
|
260
|
+
getColumnDefinition(conf: FieldConfig): string | undefined {
|
|
257
261
|
const type = this.getColumnType(conf);
|
|
258
262
|
if (!type) {
|
|
259
263
|
return;
|
|
@@ -264,7 +268,8 @@ export abstract class SQLDialect implements DialectState {
|
|
|
264
268
|
/**
|
|
265
269
|
* Delete query and return count removed
|
|
266
270
|
*/
|
|
267
|
-
async deleteAndGetCount<T>(cls: Class<T>, query: Query<T>) {
|
|
271
|
+
async deleteAndGetCount<T>(cls: Class<T>, query: Query<T>): Promise<number> {
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
268
273
|
const { count } = await this.executeSQL(this.getDeleteSQL(SQLUtil.classToStack(cls), query.where as WhereClause<T>));
|
|
269
274
|
return count;
|
|
270
275
|
}
|
|
@@ -272,7 +277,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
272
277
|
/**
|
|
273
278
|
* Get the count for a given query
|
|
274
279
|
*/
|
|
275
|
-
async getCountForQuery<T>(cls: Class<T>, query: Query<T>) {
|
|
280
|
+
async getCountForQuery<T>(cls: Class<T>, query: Query<T>): Promise<number> {
|
|
276
281
|
const { records } = await this.executeSQL<{ total: number }>(this.getQueryCountSQL(cls, query));
|
|
277
282
|
const [record] = records;
|
|
278
283
|
return Total.from(record).total;
|
|
@@ -281,7 +286,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
281
286
|
/**
|
|
282
287
|
* Remove a sql column
|
|
283
288
|
*/
|
|
284
|
-
getDropColumnSQL(stack: VisitStack[]) {
|
|
289
|
+
getDropColumnSQL(stack: VisitStack[]): string {
|
|
285
290
|
const field = stack[stack.length - 1];
|
|
286
291
|
return `ALTER TABLE ${this.parentTable(stack)} DROP COLUMN ${this.ident(field.name)};`;
|
|
287
292
|
}
|
|
@@ -289,8 +294,9 @@ export abstract class SQLDialect implements DialectState {
|
|
|
289
294
|
/**
|
|
290
295
|
* Add a sql column
|
|
291
296
|
*/
|
|
292
|
-
getAddColumnSQL(stack: VisitStack[]) {
|
|
297
|
+
getAddColumnSQL(stack: VisitStack[]): string {
|
|
293
298
|
const field = stack[stack.length - 1];
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
294
300
|
return `ALTER TABLE ${this.parentTable(stack)} ADD COLUMN ${this.getColumnDefinition(field as FieldConfig)};`;
|
|
295
301
|
}
|
|
296
302
|
|
|
@@ -309,49 +315,49 @@ export abstract class SQLDialect implements DialectState {
|
|
|
309
315
|
/**
|
|
310
316
|
* Determine table/field namespace for a given stack location
|
|
311
317
|
*/
|
|
312
|
-
namespace(stack: VisitStack[]) {
|
|
318
|
+
namespace(stack: VisitStack[]): string {
|
|
313
319
|
return `${this.ns}${SQLUtil.buildTable(stack)}`;
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
/**
|
|
317
323
|
* Determine namespace for a given stack location - 1
|
|
318
324
|
*/
|
|
319
|
-
namespaceParent(stack: VisitStack[]) {
|
|
325
|
+
namespaceParent(stack: VisitStack[]): string {
|
|
320
326
|
return this.namespace(stack.slice(0, stack.length - 1));
|
|
321
327
|
}
|
|
322
328
|
|
|
323
329
|
/**
|
|
324
330
|
* Determine table name for a given stack location
|
|
325
331
|
*/
|
|
326
|
-
table(stack: VisitStack[]) {
|
|
332
|
+
table(stack: VisitStack[]): string {
|
|
327
333
|
return this.ident(this.namespace(stack));
|
|
328
334
|
}
|
|
329
335
|
|
|
330
336
|
/**
|
|
331
337
|
* Determine parent table name for a given stack location
|
|
332
338
|
*/
|
|
333
|
-
parentTable(stack: VisitStack[]) {
|
|
339
|
+
parentTable(stack: VisitStack[]): string {
|
|
334
340
|
return this.table(stack.slice(0, stack.length - 1));
|
|
335
341
|
}
|
|
336
342
|
|
|
337
343
|
/**
|
|
338
344
|
* Get lookup key for cls and name
|
|
339
345
|
*/
|
|
340
|
-
getKey(cls: Class, name: string) {
|
|
346
|
+
getKey(cls: Class, name: string): string {
|
|
341
347
|
return `${cls.name}:${name}`;
|
|
342
348
|
}
|
|
343
349
|
|
|
344
350
|
/**
|
|
345
351
|
* Alias a field for usage
|
|
346
352
|
*/
|
|
347
|
-
alias(field: string | FieldConfig, alias: string = this.rootAlias) {
|
|
353
|
+
alias(field: string | FieldConfig, alias: string = this.rootAlias): string {
|
|
348
354
|
return `${alias}.${this.ident(field)}`;
|
|
349
355
|
}
|
|
350
356
|
|
|
351
357
|
/**
|
|
352
358
|
* Get alias cache for the stack
|
|
353
359
|
*/
|
|
354
|
-
getAliasCache(stack: VisitStack[], resolve: (path: VisitStack[]) => string) {
|
|
360
|
+
getAliasCache(stack: VisitStack[], resolve: (path: VisitStack[]) => string): Map<string, Alias> {
|
|
355
361
|
const cls = stack[0].type;
|
|
356
362
|
|
|
357
363
|
if (this.aliasCache.has(cls)) {
|
|
@@ -402,7 +408,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
402
408
|
const { foreignMap, localMap } = SQLUtil.getFieldsByLocation(stack);
|
|
403
409
|
const SQL_OPS = this.SQL_OPS;
|
|
404
410
|
|
|
405
|
-
for (const key of Object.keys(o)
|
|
411
|
+
for (const key of Object.keys(o)) {
|
|
406
412
|
const top = o[key];
|
|
407
413
|
const field = localMap[key] ?? foreignMap[key];
|
|
408
414
|
if (!field) {
|
|
@@ -437,18 +443,19 @@ export abstract class SQLDialect implements DialectState {
|
|
|
437
443
|
const arr = [...new Set(Array.isArray(v) ? v : [v])].map(el => resolve(el));
|
|
438
444
|
const valueTable = this.parentTable(sStack);
|
|
439
445
|
const alias = `_all_${sStack.length}`;
|
|
440
|
-
const
|
|
441
|
-
const
|
|
446
|
+
const pPath = this.ident(this.parentPathField.name);
|
|
447
|
+
const rpPath = this.resolveName([...sStack, field, this.parentPathField]);
|
|
442
448
|
|
|
443
449
|
items.push(`${arr.length} = (
|
|
444
450
|
SELECT COUNT(DISTINCT ${alias}.${this.ident(field.name)})
|
|
445
451
|
FROM ${valueTable} ${alias}
|
|
446
|
-
WHERE ${alias}.${
|
|
452
|
+
WHERE ${alias}.${pPath} = ${rpPath}
|
|
447
453
|
AND ${alias}.${this.ident(field.name)} IN (${arr.join(',')})
|
|
448
454
|
)`);
|
|
449
455
|
break;
|
|
450
456
|
}
|
|
451
457
|
case '$regex': {
|
|
458
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
452
459
|
const re = Util.toRegex(v as string);
|
|
453
460
|
const src = re.source;
|
|
454
461
|
const ins = re.flags && re.flags.includes('i');
|
|
@@ -482,7 +489,8 @@ export abstract class SQLDialect implements DialectState {
|
|
|
482
489
|
break;
|
|
483
490
|
}
|
|
484
491
|
case '$lt': case '$gt': case '$gte': case '$lte': {
|
|
485
|
-
|
|
492
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
493
|
+
const subItems = Object.keys(top as typeof SQL_OPS)
|
|
486
494
|
.map(ssk => `${sPath} ${SQL_OPS[ssk]} ${resolve(top[ssk])}`);
|
|
487
495
|
items.push(subItems.length > 1 ? `(${subItems.join(` ${SQL_OPS.$and} `)})` : subItems[0]);
|
|
488
496
|
break;
|
|
@@ -529,6 +537,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
529
537
|
getWhereSQL<T>(cls: Class<T>, where?: WhereClause<T>): string {
|
|
530
538
|
return !where || !Object.keys(where).length ?
|
|
531
539
|
'' :
|
|
540
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
532
541
|
`WHERE ${this.getWhereGroupingSQL(cls, where as WhereClause<T>)}`;
|
|
533
542
|
}
|
|
534
543
|
|
|
@@ -598,16 +607,19 @@ LEFT OUTER JOIN ${from} ON
|
|
|
598
607
|
/**
|
|
599
608
|
* Generate full query
|
|
600
609
|
*/
|
|
601
|
-
getQuerySQL<T>(cls: Class<T>, query: Query<T>) {
|
|
610
|
+
getQuerySQL<T>(cls: Class<T>, query: Query<T>): string {
|
|
602
611
|
const sortFields = !query.sort ?
|
|
603
612
|
'' :
|
|
604
613
|
SQLUtil.orderBy(cls, query.sort)
|
|
605
614
|
.map(x => this.resolveName(x.stack))
|
|
606
615
|
.join(', ');
|
|
616
|
+
|
|
617
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
618
|
+
const where = query.where as WhereClause<T>;
|
|
607
619
|
return `
|
|
608
620
|
${this.getSelectSQL(cls, query.select)}
|
|
609
621
|
${this.getFromSQL(cls)}
|
|
610
|
-
${this.getWhereSQL(cls,
|
|
622
|
+
${this.getWhereSQL(cls, where)}
|
|
611
623
|
${this.getGroupBySQL(cls, query)}${sortFields ? `, ${sortFields}` : ''}
|
|
612
624
|
${this.getOrderBySQL(cls, query.sort)}
|
|
613
625
|
${this.getLimitSQL(cls, query)}`;
|
|
@@ -624,6 +636,7 @@ ${this.getLimitSQL(cls, query)}`;
|
|
|
624
636
|
|
|
625
637
|
const fields = SchemaRegistry.has(config.type) ?
|
|
626
638
|
[...SQLUtil.getFieldsByLocation(stack).local] :
|
|
639
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
627
640
|
(array ? [config as FieldConfig] : []);
|
|
628
641
|
|
|
629
642
|
if (!parent) {
|
|
@@ -660,19 +673,19 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
660
673
|
/**
|
|
661
674
|
* Generate drop SQL
|
|
662
675
|
*/
|
|
663
|
-
getDropTableSQL(stack: VisitStack[]) {
|
|
676
|
+
getDropTableSQL(stack: VisitStack[]): string {
|
|
664
677
|
return `DROP TABLE IF EXISTS ${this.table(stack)}; `;
|
|
665
678
|
}
|
|
666
679
|
|
|
667
680
|
/**
|
|
668
681
|
* Generate truncate SQL
|
|
669
682
|
*/
|
|
670
|
-
getTruncateTableSQL(stack: VisitStack[]) {
|
|
683
|
+
getTruncateTableSQL(stack: VisitStack[]): string {
|
|
671
684
|
return `TRUNCATE ${this.table(stack)}; `;
|
|
672
685
|
}
|
|
673
686
|
|
|
674
687
|
/**
|
|
675
|
-
* Get all table
|
|
688
|
+
* Get all table create queries for a class
|
|
676
689
|
*/
|
|
677
690
|
getCreateAllTablesSQL(cls: Class): string[] {
|
|
678
691
|
const out: string[] = [];
|
|
@@ -697,11 +710,12 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
697
710
|
getCreateIndexSQL<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T>): string {
|
|
698
711
|
const table = this.namespace(SQLUtil.classToStack(cls));
|
|
699
712
|
const fields: [string, boolean][] = idx.fields.map(x => {
|
|
700
|
-
const key = Object.keys(x)[0]
|
|
713
|
+
const key = Object.keys(x)[0];
|
|
701
714
|
const val = x[key];
|
|
702
715
|
if (Util.isPlainObject(val)) {
|
|
703
716
|
throw new Error('Unable to supported nested fields for indices');
|
|
704
717
|
}
|
|
718
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
705
719
|
return [key as string, typeof val === 'number' ? val === 1 : (!!val)];
|
|
706
720
|
});
|
|
707
721
|
const constraint = `idx_${table}_${fields.map(([f]) => f).join('_')}`;
|
|
@@ -751,7 +765,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
751
765
|
const isArray = !!config.array;
|
|
752
766
|
|
|
753
767
|
if (isArray) {
|
|
754
|
-
const newInstances
|
|
768
|
+
const newInstances: typeof instances = [];
|
|
755
769
|
for (const el of instances) {
|
|
756
770
|
if (el.value === null || el.value === undefined) {
|
|
757
771
|
continue;
|
|
@@ -776,6 +790,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
776
790
|
return;
|
|
777
791
|
}
|
|
778
792
|
|
|
793
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
779
794
|
const matrix = instances.map(inst => columns.map(c => this.resolveValue(c, (inst.value as Record<string, string>)[c.name])));
|
|
780
795
|
|
|
781
796
|
columnNames.push(this.pathField.name);
|
|
@@ -812,7 +827,7 @@ ${matrix.map(row => `(${row.join(', ')})`).join(',\n')};`;
|
|
|
812
827
|
*/
|
|
813
828
|
getAllInsertSQL<T extends ModelType>(cls: Class<T>, instance: T): string[] {
|
|
814
829
|
const out: string[] = [];
|
|
815
|
-
const add = (text?: string) => text && out.push(text);
|
|
830
|
+
const add = (text?: string): void => { text && out.push(text); };
|
|
816
831
|
SQLUtil.visitSchemaInstance(cls, instance, {
|
|
817
832
|
onRoot: ({ value, path }) => add(this.getInsertSQL(path, [{ stack: path, value }])),
|
|
818
833
|
onSub: ({ value, path }) => add(this.getInsertSQL(path, [{ stack: path, value }])),
|
|
@@ -824,7 +839,7 @@ ${matrix.map(row => `(${row.join(', ')})`).join(',\n')};`;
|
|
|
824
839
|
/**
|
|
825
840
|
* Simple data base updates
|
|
826
841
|
*/
|
|
827
|
-
getUpdateSQL(stack: VisitStack[], data: Record<string, unknown>, where?: WhereClause<unknown>) {
|
|
842
|
+
getUpdateSQL(stack: VisitStack[], data: Record<string, unknown>, where?: WhereClause<unknown>): string {
|
|
828
843
|
const { type } = stack[stack.length - 1];
|
|
829
844
|
const { localMap } = SQLUtil.getFieldsByLocation(stack);
|
|
830
845
|
return `
|
|
@@ -866,18 +881,21 @@ ${orderBy};`;
|
|
|
866
881
|
/**
|
|
867
882
|
* Get COUNT(1) query
|
|
868
883
|
*/
|
|
869
|
-
getQueryCountSQL<T>(cls: Class<T>, query: Query<T>) {
|
|
884
|
+
getQueryCountSQL<T>(cls: Class<T>, query: Query<T>): string {
|
|
885
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
886
|
+
const where = query.where as WhereClause<T>;
|
|
870
887
|
return `
|
|
871
888
|
SELECT COUNT(DISTINCT ${this.rootAlias}.id) as total
|
|
872
889
|
${this.getFromSQL(cls)}
|
|
873
|
-
${this.getWhereSQL(cls,
|
|
890
|
+
${this.getWhereSQL(cls, where)}`;
|
|
874
891
|
}
|
|
875
892
|
|
|
876
893
|
async fetchDependents<T>(cls: Class<T>, items: T[], select?: SelectClause<T>): Promise<T[]> {
|
|
877
894
|
const stack: Record<string, unknown>[] = [];
|
|
878
895
|
const selectStack: (SelectClause<T> | undefined)[] = [];
|
|
879
896
|
|
|
880
|
-
const buildSet = (children: unknown[], field?: FieldConfig)
|
|
897
|
+
const buildSet = (children: unknown[], field?: FieldConfig): Record<string, unknown> =>
|
|
898
|
+
SQLUtil.collectDependents(this, stack[stack.length - 1], children, field);
|
|
881
899
|
|
|
882
900
|
await SQLUtil.visitSchema(SchemaRegistry.get(cls), {
|
|
883
901
|
onRoot: async (config) => {
|
|
@@ -890,12 +908,15 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
890
908
|
const top = stack[stack.length - 1];
|
|
891
909
|
const ids = Object.keys(top);
|
|
892
910
|
const selectTop = selectStack[selectStack.length - 1];
|
|
893
|
-
// @
|
|
894
|
-
const
|
|
911
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
912
|
+
const fieldKey = config.name as keyof RetainFields<T>;
|
|
913
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
914
|
+
const subSelectTop: SelectClause<T> | undefined = selectTop?.[fieldKey] as SelectClause<T> | undefined;
|
|
895
915
|
|
|
896
916
|
// See if a selection exists at all
|
|
897
917
|
const sel: FieldConfig[] = subSelectTop ? fields
|
|
898
|
-
|
|
918
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
919
|
+
.filter(f => typeof subSelectTop === 'object' && subSelectTop[f.name as typeof fieldKey] === 1)
|
|
899
920
|
: [];
|
|
900
921
|
|
|
901
922
|
if (sel.length) {
|
|
@@ -924,7 +945,7 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
924
945
|
}
|
|
925
946
|
}
|
|
926
947
|
},
|
|
927
|
-
onSimple: async ({ config, path }) => {
|
|
948
|
+
onSimple: async ({ config, path }): Promise<void> => {
|
|
928
949
|
const top = stack[stack.length - 1];
|
|
929
950
|
const ids = Object.keys(top);
|
|
930
951
|
if (ids.length) {
|
|
@@ -943,7 +964,7 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
943
964
|
/**
|
|
944
965
|
* Delete all ids
|
|
945
966
|
*/
|
|
946
|
-
async deleteByIds(stack: VisitStack[], ids: string[]) {
|
|
967
|
+
async deleteByIds(stack: VisitStack[], ids: string[]): Promise<number> {
|
|
947
968
|
return this.deleteAndGetCount<ModelType>(stack[stack.length - 1].type, {
|
|
948
969
|
where: {
|
|
949
970
|
[stack.length === 1 ? this.idField.name : this.pathField.name]: {
|
|
@@ -956,10 +977,10 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
956
977
|
/**
|
|
957
978
|
* Do bulk process
|
|
958
979
|
*/
|
|
959
|
-
async bulkProcess(
|
|
980
|
+
async bulkProcess(deletes: DeleteWrapper[], inserts: InsertWrapper[], upserts: InsertWrapper[], updates: InsertWrapper[]): Promise<BulkResponse> {
|
|
960
981
|
const out = {
|
|
961
982
|
counts: {
|
|
962
|
-
delete:
|
|
983
|
+
delete: deletes.reduce((acc, el) => acc + el.ids.length, 0),
|
|
963
984
|
error: 0,
|
|
964
985
|
insert: inserts.filter(x => x.stack.length === 1).reduce((acc, el) => acc + el.records.length, 0),
|
|
965
986
|
update: updates.filter(x => x.stack.length === 1).reduce((acc, el) => acc + el.records.length, 0),
|
|
@@ -970,7 +991,7 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
970
991
|
};
|
|
971
992
|
|
|
972
993
|
// Full removals
|
|
973
|
-
await Promise.all(
|
|
994
|
+
await Promise.all(deletes.map(el => this.deleteByIds(el.stack, el.ids)));
|
|
974
995
|
|
|
975
996
|
// Adding deletes
|
|
976
997
|
if (upserts.length || updates.length) {
|
|
@@ -978,9 +999,11 @@ ${this.getWhereSQL(cls, query.where as WhereClause<T>)}`;
|
|
|
978
999
|
|
|
979
1000
|
await Promise.all([
|
|
980
1001
|
...upserts.filter(x => x.stack.length === 1).map(i =>
|
|
1002
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
981
1003
|
this.deleteByIds(i.stack, i.records.map(v => (v.value as Record<string, string>)[idx]))
|
|
982
1004
|
),
|
|
983
1005
|
...updates.filter(x => x.stack.length === 1).map(i =>
|
|
1006
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
984
1007
|
this.deleteByIds(i.stack, i.records.map(v => (v.value as Record<string, string>)[idx]))
|
|
985
1008
|
),
|
|
986
1009
|
]);
|
|
@@ -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
|
}
|