@travetto/model-sql 6.0.0 → 7.0.0-rc.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/package.json +7 -7
- package/src/connection/decorator.ts +3 -0
- package/src/dialect/base.ts +55 -44
- package/src/internal/types.ts +9 -9
- package/src/service.ts +2 -2
- package/src/table-manager.ts +5 -4
- package/src/types.ts +1 -1
- package/src/util.ts +43 -44
- package/support/test/query.ts +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-sql",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-rc.0",
|
|
4
4
|
"description": "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sql",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"directory": "module/model-sql"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/cli": "^
|
|
31
|
-
"@travetto/config": "^
|
|
32
|
-
"@travetto/context": "^
|
|
33
|
-
"@travetto/model": "^
|
|
34
|
-
"@travetto/model-query": "^
|
|
30
|
+
"@travetto/cli": "^7.0.0-rc.0",
|
|
31
|
+
"@travetto/config": "^7.0.0-rc.0",
|
|
32
|
+
"@travetto/context": "^7.0.0-rc.0",
|
|
33
|
+
"@travetto/model": "^7.0.0-rc.0",
|
|
34
|
+
"@travetto/model-query": "^7.0.0-rc.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@travetto/test": "^
|
|
37
|
+
"@travetto/test": "^7.0.0-rc.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@travetto/test": {
|
|
@@ -10,6 +10,7 @@ export interface ConnectionAware<C = unknown> {
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Decorator to ensure a method runs with a valid connection
|
|
13
|
+
* @kind decorator
|
|
13
14
|
*/
|
|
14
15
|
export function Connected() {
|
|
15
16
|
return function <T extends { conn?: Connection }>(
|
|
@@ -24,6 +25,7 @@ export function Connected() {
|
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Decorator to ensure a method runs with a valid connection
|
|
28
|
+
* @kind decorator
|
|
27
29
|
*/
|
|
28
30
|
export function ConnectedIterator() {
|
|
29
31
|
return function <T extends { conn?: Connection }>(
|
|
@@ -38,6 +40,7 @@ export function ConnectedIterator() {
|
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* Decorator to ensure a method runs with a valid transaction
|
|
43
|
+
* @kind decorator
|
|
41
44
|
*/
|
|
42
45
|
export function Transactional(mode: TransactionType = 'required') {
|
|
43
46
|
return function <T extends { conn?: Connection }>(
|
package/src/dialect/base.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @stylistic/indent */
|
|
2
|
-
import { DataUtil,
|
|
2
|
+
import { DataUtil, SchemaFieldConfig, Schema, SchemaRegistryIndex, type Point } from '@travetto/schema';
|
|
3
3
|
import { Class, AppError, TypedObject, TimeUtil, castTo, castKey, toConcrete } from '@travetto/runtime';
|
|
4
|
-
import { SelectClause, Query, SortClause, WhereClause,
|
|
4
|
+
import { SelectClause, Query, SortClause, WhereClause, RetainQueryPrimitiveFields, ModelQueryUtil } from '@travetto/model-query';
|
|
5
5
|
import { BulkResponse, IndexConfig, ModelType } from '@travetto/model';
|
|
6
6
|
|
|
7
7
|
import { SQLModelUtil } from '../util.ts';
|
|
@@ -21,9 +21,10 @@ class Total {
|
|
|
21
21
|
total: number;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function makeField(name: string, type: Class, required: boolean, extra: Partial<
|
|
24
|
+
function makeField(name: string, type: Class, required: boolean, extra: Partial<SchemaFieldConfig>): SchemaFieldConfig {
|
|
25
25
|
return {
|
|
26
26
|
name,
|
|
27
|
+
owner: null!,
|
|
27
28
|
type,
|
|
28
29
|
array: false,
|
|
29
30
|
...(required ? { required: { active: true } } : {}),
|
|
@@ -98,6 +99,8 @@ export abstract class SQLDialect implements DialectState {
|
|
|
98
99
|
DECIMAL: (d, p) => `DECIMAL(${d},${p})`
|
|
99
100
|
};
|
|
100
101
|
|
|
102
|
+
ID_AFFIX = '`';
|
|
103
|
+
|
|
101
104
|
/**
|
|
102
105
|
* Generate an id field
|
|
103
106
|
*/
|
|
@@ -160,7 +163,14 @@ export abstract class SQLDialect implements DialectState {
|
|
|
160
163
|
/**
|
|
161
164
|
* Identify a name or field (escape it)
|
|
162
165
|
*/
|
|
163
|
-
|
|
166
|
+
ident(field: SchemaFieldConfig | string | symbol): string {
|
|
167
|
+
if (field === '*') {
|
|
168
|
+
return field;
|
|
169
|
+
} else {
|
|
170
|
+
const name = (typeof field === 'symbol' || typeof field === 'string') ? field : field.name;
|
|
171
|
+
return `${this.ID_AFFIX}${name.toString()}${this.ID_AFFIX}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
164
174
|
|
|
165
175
|
quote(text: string): string {
|
|
166
176
|
return `'${text.replace(/[']/g, "''")}'`;
|
|
@@ -179,7 +189,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
179
189
|
/**
|
|
180
190
|
* Convert value to SQL valid representation
|
|
181
191
|
*/
|
|
182
|
-
resolveValue(conf:
|
|
192
|
+
resolveValue(conf: SchemaFieldConfig, value: unknown): string {
|
|
183
193
|
if (value === undefined || value === null) {
|
|
184
194
|
return 'NULL';
|
|
185
195
|
} else if (conf.type === String) {
|
|
@@ -204,13 +214,13 @@ export abstract class SQLDialect implements DialectState {
|
|
|
204
214
|
} else if (conf.type === Object) {
|
|
205
215
|
return this.quote(JSON.stringify(value).replace(/[']/g, "''"));
|
|
206
216
|
}
|
|
207
|
-
throw new AppError(`Unknown value type for field ${conf.name}, ${value}`, { category: 'data' });
|
|
217
|
+
throw new AppError(`Unknown value type for field ${conf.name.toString()}, ${value}`, { category: 'data' });
|
|
208
218
|
}
|
|
209
219
|
|
|
210
220
|
/**
|
|
211
221
|
* Get column type from field config
|
|
212
222
|
*/
|
|
213
|
-
getColumnType(conf:
|
|
223
|
+
getColumnType(conf: SchemaFieldConfig): string {
|
|
214
224
|
let type: string = '';
|
|
215
225
|
|
|
216
226
|
if (conf.type === Number) {
|
|
@@ -257,12 +267,12 @@ export abstract class SQLDialect implements DialectState {
|
|
|
257
267
|
/**
|
|
258
268
|
* FieldConfig to Column definition
|
|
259
269
|
*/
|
|
260
|
-
getColumnDefinition(conf:
|
|
270
|
+
getColumnDefinition(conf: SchemaFieldConfig): string | undefined {
|
|
261
271
|
const type = this.getColumnType(conf);
|
|
262
272
|
if (!type) {
|
|
263
273
|
return;
|
|
264
274
|
}
|
|
265
|
-
return `${this.ident(conf)} ${type} ${(conf.required
|
|
275
|
+
return `${this.ident(conf)} ${type} ${(conf.required?.active !== false) ? 'NOT NULL' : 'DEFAULT NULL'}`;
|
|
266
276
|
}
|
|
267
277
|
|
|
268
278
|
/**
|
|
@@ -286,7 +296,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
286
296
|
* Remove a sql column
|
|
287
297
|
*/
|
|
288
298
|
getDropColumnSQL(stack: VisitStack[]): string {
|
|
289
|
-
const field = stack
|
|
299
|
+
const field = stack.at(-1)!;
|
|
290
300
|
return `ALTER TABLE ${this.parentTable(stack)} DROP COLUMN ${this.ident(field.name)};`;
|
|
291
301
|
}
|
|
292
302
|
|
|
@@ -294,7 +304,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
294
304
|
* Add a sql column
|
|
295
305
|
*/
|
|
296
306
|
getAddColumnSQL(stack: VisitStack[]): string {
|
|
297
|
-
const field:
|
|
307
|
+
const field: SchemaFieldConfig = castTo(stack.at(-1));
|
|
298
308
|
return `ALTER TABLE ${this.parentTable(stack)} ADD COLUMN ${this.getColumnDefinition(field)};`;
|
|
299
309
|
}
|
|
300
310
|
|
|
@@ -341,7 +351,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
341
351
|
/**
|
|
342
352
|
* Alias a field for usage
|
|
343
353
|
*/
|
|
344
|
-
alias(field: string |
|
|
354
|
+
alias(field: string | symbol | SchemaFieldConfig, alias: string = this.rootAlias): string {
|
|
345
355
|
return `${alias}.${this.ident(field)}`;
|
|
346
356
|
}
|
|
347
357
|
|
|
@@ -358,7 +368,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
358
368
|
const clauses = new Map<string, Alias>();
|
|
359
369
|
let idx = 0;
|
|
360
370
|
|
|
361
|
-
SQLModelUtil.visitSchemaSync(
|
|
371
|
+
SQLModelUtil.visitSchemaSync(SchemaRegistryIndex.getConfig(cls), {
|
|
362
372
|
onRoot: ({ descend, path }) => {
|
|
363
373
|
const table = resolve(path);
|
|
364
374
|
clauses.set(table, { alias: this.rootAlias, path });
|
|
@@ -366,12 +376,12 @@ export abstract class SQLDialect implements DialectState {
|
|
|
366
376
|
},
|
|
367
377
|
onSub: ({ descend, config, path }) => {
|
|
368
378
|
const table = resolve(path);
|
|
369
|
-
clauses.set(table, { alias: `${config.name.charAt(0)}${idx++}`, path });
|
|
379
|
+
clauses.set(table, { alias: `${config.name.toString().charAt(0)}${idx++}`, path });
|
|
370
380
|
return descend();
|
|
371
381
|
},
|
|
372
382
|
onSimple: ({ config, path }) => {
|
|
373
383
|
const table = resolve(path);
|
|
374
|
-
clauses.set(table, { alias: `${config.name.charAt(0)}${idx++}`, path });
|
|
384
|
+
clauses.set(table, { alias: `${config.name.toString().charAt(0)}${idx++}`, path });
|
|
375
385
|
}
|
|
376
386
|
});
|
|
377
387
|
|
|
@@ -385,7 +395,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
385
395
|
*/
|
|
386
396
|
resolveName(stack: VisitStack[]): string {
|
|
387
397
|
const path = this.namespaceParent(stack);
|
|
388
|
-
const name = stack
|
|
398
|
+
const name = stack.at(-1)!.name;
|
|
389
399
|
const cache = this.getAliasCache(stack, this.namespace);
|
|
390
400
|
const base = cache.get(path)!;
|
|
391
401
|
return this.alias(name, base.alias);
|
|
@@ -406,10 +416,11 @@ export abstract class SQLDialect implements DialectState {
|
|
|
406
416
|
throw new Error(`Unknown field: ${key}`);
|
|
407
417
|
}
|
|
408
418
|
const sStack = [...stack, field];
|
|
409
|
-
if (key in foreignMap && field.array && !
|
|
419
|
+
if (key in foreignMap && field.array && !SchemaRegistryIndex.has(field.type)) {
|
|
410
420
|
// If dealing with simple external
|
|
411
421
|
sStack.push({
|
|
412
422
|
name: field.name,
|
|
423
|
+
owner: null!,
|
|
413
424
|
type: field.type
|
|
414
425
|
});
|
|
415
426
|
}
|
|
@@ -630,12 +641,12 @@ ${this.getLimitSQL(cls, query)}`;
|
|
|
630
641
|
}
|
|
631
642
|
|
|
632
643
|
getCreateTableSQL(stack: VisitStack[]): string {
|
|
633
|
-
const config = stack
|
|
644
|
+
const config = stack.at(-1)!;
|
|
634
645
|
const parent = stack.length > 1;
|
|
635
646
|
const array = parent && config.array;
|
|
636
|
-
const fields =
|
|
647
|
+
const fields = SchemaRegistryIndex.has(config.type) ?
|
|
637
648
|
[...SQLModelUtil.getFieldsByLocation(stack).local] :
|
|
638
|
-
(array ? [castTo<
|
|
649
|
+
(array ? [castTo<SchemaFieldConfig>(config)] : []);
|
|
639
650
|
|
|
640
651
|
if (!parent) {
|
|
641
652
|
let idField = fields.find(x => x.name === this.idField.name);
|
|
@@ -688,7 +699,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
688
699
|
*/
|
|
689
700
|
getCreateAllTablesSQL(cls: Class): string[] {
|
|
690
701
|
const out: string[] = [];
|
|
691
|
-
SQLModelUtil.visitSchemaSync(
|
|
702
|
+
SQLModelUtil.visitSchemaSync(SchemaRegistryIndex.getConfig(cls), {
|
|
692
703
|
onRoot: ({ path, descend }) => { out.push(this.getCreateTableSQL(path)); descend(); },
|
|
693
704
|
onSub: ({ path, descend }) => { out.push(this.getCreateTableSQL(path)); descend(); },
|
|
694
705
|
onSimple: ({ path }) => out.push(this.getCreateTableSQL(path))
|
|
@@ -727,7 +738,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
727
738
|
*/
|
|
728
739
|
getDropAllTablesSQL<T extends ModelType>(cls: Class<T>): string[] {
|
|
729
740
|
const out: string[] = [];
|
|
730
|
-
SQLModelUtil.visitSchemaSync(
|
|
741
|
+
SQLModelUtil.visitSchemaSync(SchemaRegistryIndex.getConfig(cls), {
|
|
731
742
|
onRoot: ({ path, descend }) => { descend(); out.push(this.getDropTableSQL(path)); },
|
|
732
743
|
onSub: ({ path, descend }) => { descend(); out.push(this.getDropTableSQL(path)); },
|
|
733
744
|
onSimple: ({ path }) => out.push(this.getDropTableSQL(path))
|
|
@@ -740,7 +751,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
740
751
|
*/
|
|
741
752
|
getTruncateAllTablesSQL<T extends ModelType>(cls: Class<T>): string[] {
|
|
742
753
|
const out: string[] = [];
|
|
743
|
-
SQLModelUtil.visitSchemaSync(
|
|
754
|
+
SQLModelUtil.visitSchemaSync(SchemaRegistryIndex.getConfig(cls), {
|
|
744
755
|
onRoot: ({ path, descend }) => { descend(); out.push(this.getTruncateTableSQL(path)); },
|
|
745
756
|
onSub: ({ path, descend }) => { descend(); out.push(this.getTruncateTableSQL(path)); },
|
|
746
757
|
onSimple: ({ path }) => out.push(this.getTruncateTableSQL(path))
|
|
@@ -752,10 +763,10 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
752
763
|
* Get INSERT sql for a given instance and a specific stack location
|
|
753
764
|
*/
|
|
754
765
|
getInsertSQL(stack: VisitStack[], instances: InsertWrapper['records']): string | undefined {
|
|
755
|
-
const config = stack
|
|
766
|
+
const config = stack.at(-1)!;
|
|
756
767
|
const columns = SQLModelUtil.getFieldsByLocation(stack).local
|
|
757
|
-
.filter(x => !
|
|
758
|
-
.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
768
|
+
.filter(x => !SchemaRegistryIndex.has(x.type))
|
|
769
|
+
.toSorted((a, b) => a.name.toString().localeCompare(b.name.toString()));
|
|
759
770
|
const columnNames = columns.map(c => c.name);
|
|
760
771
|
|
|
761
772
|
const hasParent = stack.length > 1;
|
|
@@ -767,7 +778,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
767
778
|
if (el.value === null || el.value === undefined) {
|
|
768
779
|
continue;
|
|
769
780
|
} else if (Array.isArray(el.value)) {
|
|
770
|
-
const name = el.stack
|
|
781
|
+
const name = el.stack.at(-1)!.name;
|
|
771
782
|
for (const sel of el.value) {
|
|
772
783
|
newInstances.push({
|
|
773
784
|
stack: el.stack,
|
|
@@ -787,7 +798,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
787
798
|
return;
|
|
788
799
|
}
|
|
789
800
|
|
|
790
|
-
const matrix = instances.map(inst => columns.map(c => this.resolveValue(c, castTo<Record<string, unknown>>(inst.value)[c.name])));
|
|
801
|
+
const matrix = instances.map(inst => columns.map(c => this.resolveValue(c, castTo<Record<string | symbol, unknown>>(inst.value)[c.name])));
|
|
791
802
|
|
|
792
803
|
columnNames.push(this.pathField.name);
|
|
793
804
|
if (hasParent) {
|
|
@@ -836,7 +847,7 @@ ${matrix.map(row => `(${row.join(', ')})`).join(',\n')};`;
|
|
|
836
847
|
* Simple data base updates
|
|
837
848
|
*/
|
|
838
849
|
getUpdateSQL(stack: VisitStack[], data: Record<string, unknown>, where?: WhereClause<unknown>): string {
|
|
839
|
-
const { type } = stack
|
|
850
|
+
const { type } = stack.at(-1)!;
|
|
840
851
|
const { localMap } = SQLModelUtil.getFieldsByLocation(stack);
|
|
841
852
|
return `
|
|
842
853
|
UPDATE ${this.table(stack)} ${this.rootAlias}
|
|
@@ -849,7 +860,7 @@ SET
|
|
|
849
860
|
}
|
|
850
861
|
|
|
851
862
|
getDeleteSQL(stack: VisitStack[], where?: WhereClause<unknown>): string {
|
|
852
|
-
const { type } = stack
|
|
863
|
+
const { type } = stack.at(-1)!;
|
|
853
864
|
return `
|
|
854
865
|
DELETE
|
|
855
866
|
FROM ${this.table(stack)} ${this.rootAlias}
|
|
@@ -859,11 +870,11 @@ ${this.getWhereSQL(type, where)};`;
|
|
|
859
870
|
/**
|
|
860
871
|
* Get elements by ids
|
|
861
872
|
*/
|
|
862
|
-
getSelectRowsByIdsSQL(stack: VisitStack[], ids: string[], select:
|
|
863
|
-
const config = stack
|
|
873
|
+
getSelectRowsByIdsSQL(stack: VisitStack[], ids: string[], select: SchemaFieldConfig[] = []): string {
|
|
874
|
+
const config = stack.at(-1)!;
|
|
864
875
|
const orderBy = !config.array ?
|
|
865
876
|
'' :
|
|
866
|
-
`ORDER BY ${this.rootAlias}.${this.idxField.name} ASC`;
|
|
877
|
+
`ORDER BY ${this.rootAlias}.${this.idxField.name.toString()} ASC`;
|
|
867
878
|
|
|
868
879
|
const idField = (stack.length > 1 ? this.parentPathField : this.idField);
|
|
869
880
|
|
|
@@ -888,10 +899,10 @@ ${this.getWhereSQL(cls, where!)}`;
|
|
|
888
899
|
const stack: Record<string, unknown>[] = [];
|
|
889
900
|
const selectStack: (SelectClause<T> | undefined)[] = [];
|
|
890
901
|
|
|
891
|
-
const buildSet = (children: unknown[], field?:
|
|
892
|
-
SQLModelUtil.collectDependents(this, stack
|
|
902
|
+
const buildSet = (children: unknown[], field?: SchemaFieldConfig): Record<string, unknown> =>
|
|
903
|
+
SQLModelUtil.collectDependents(this, stack.at(-1)!, children, field);
|
|
893
904
|
|
|
894
|
-
await SQLModelUtil.visitSchema(
|
|
905
|
+
await SQLModelUtil.visitSchema(SchemaRegistryIndex.getConfig(cls), {
|
|
895
906
|
onRoot: async (config) => {
|
|
896
907
|
const res = buildSet(items); // Already filtered by initial select query
|
|
897
908
|
selectStack.push(select);
|
|
@@ -899,14 +910,14 @@ ${this.getWhereSQL(cls, where!)}`;
|
|
|
899
910
|
await config.descend();
|
|
900
911
|
},
|
|
901
912
|
onSub: async ({ config, descend, fields, path }) => {
|
|
902
|
-
const top = stack
|
|
913
|
+
const top = stack.at(-1)!;
|
|
903
914
|
const ids = Object.keys(top);
|
|
904
|
-
const selectTop = selectStack
|
|
905
|
-
const fieldKey = castKey<
|
|
915
|
+
const selectTop = selectStack.at(-1)!;
|
|
916
|
+
const fieldKey = castKey<RetainQueryPrimitiveFields<T>>(config.name);
|
|
906
917
|
const subSelectTop: SelectClause<T> | undefined = castTo(selectTop?.[fieldKey]);
|
|
907
918
|
|
|
908
919
|
// See if a selection exists at all
|
|
909
|
-
const sel:
|
|
920
|
+
const sel: SchemaFieldConfig[] = subSelectTop ? fields
|
|
910
921
|
.filter(f => typeof subSelectTop === 'object' && subSelectTop[castTo<typeof fieldKey>(f.name)] === 1)
|
|
911
922
|
: [];
|
|
912
923
|
|
|
@@ -937,7 +948,7 @@ ${this.getWhereSQL(cls, where!)}`;
|
|
|
937
948
|
}
|
|
938
949
|
},
|
|
939
950
|
onSimple: async ({ config, path }): Promise<void> => {
|
|
940
|
-
const top = stack
|
|
951
|
+
const top = stack.at(-1)!;
|
|
941
952
|
const ids = Object.keys(top);
|
|
942
953
|
if (ids.length) {
|
|
943
954
|
const { records: matching } = await this.executeSQL(this.getSelectRowsByIdsSQL(
|
|
@@ -956,7 +967,7 @@ ${this.getWhereSQL(cls, where!)}`;
|
|
|
956
967
|
* Delete all ids
|
|
957
968
|
*/
|
|
958
969
|
async deleteByIds(stack: VisitStack[], ids: string[]): Promise<number> {
|
|
959
|
-
return this.deleteAndGetCount<ModelType>(stack
|
|
970
|
+
return this.deleteAndGetCount<ModelType>(stack.at(-1)!.type, {
|
|
960
971
|
where: {
|
|
961
972
|
[stack.length === 1 ? this.idField.name : this.pathField.name]: {
|
|
962
973
|
$in: ids
|
|
@@ -990,10 +1001,10 @@ ${this.getWhereSQL(cls, where!)}`;
|
|
|
990
1001
|
|
|
991
1002
|
await Promise.all([
|
|
992
1003
|
...upserts.filter(x => x.stack.length === 1).map(i =>
|
|
993
|
-
this.deleteByIds(i.stack, i.records.map(v => castTo<Record<string, string>>(v.value)[idx]))
|
|
1004
|
+
this.deleteByIds(i.stack, i.records.map(v => castTo<Record<string | symbol, string>>(v.value)[idx]))
|
|
994
1005
|
),
|
|
995
1006
|
...updates.filter(x => x.stack.length === 1).map(i =>
|
|
996
|
-
this.deleteByIds(i.stack, i.records.map(v => castTo<Record<string, string>>(v.value)[idx]))
|
|
1007
|
+
this.deleteByIds(i.stack, i.records.map(v => castTo<Record<string | symbol, string>>(v.value)[idx]))
|
|
997
1008
|
),
|
|
998
1009
|
]);
|
|
999
1010
|
}
|
package/src/internal/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SchemaFieldConfig, SchemaClassConfig } from '@travetto/schema';
|
|
2
2
|
|
|
3
3
|
import { VisitStack } from '../types.ts';
|
|
4
4
|
|
|
@@ -22,10 +22,10 @@ export interface DeleteWrapper {
|
|
|
22
22
|
* Dialect state
|
|
23
23
|
*/
|
|
24
24
|
export interface DialectState {
|
|
25
|
-
pathField:
|
|
26
|
-
parentPathField:
|
|
27
|
-
idField:
|
|
28
|
-
idxField:
|
|
25
|
+
pathField: SchemaFieldConfig;
|
|
26
|
+
parentPathField: SchemaFieldConfig;
|
|
27
|
+
idField: SchemaFieldConfig;
|
|
28
|
+
idxField: SchemaFieldConfig;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export type VisitState = { path: VisitStack[] };
|
|
@@ -35,7 +35,7 @@ export type VisitState = { path: VisitStack[] };
|
|
|
35
35
|
*/
|
|
36
36
|
export interface VisitNode<R> {
|
|
37
37
|
path: VisitStack[];
|
|
38
|
-
fields:
|
|
38
|
+
fields: SchemaFieldConfig[];
|
|
39
39
|
descend: () => R;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -58,7 +58,7 @@ export interface VisitInstanceNode<R> extends VisitNode<R> {
|
|
|
58
58
|
* Visit handler
|
|
59
59
|
*/
|
|
60
60
|
export interface VisitHandler<R, U extends VisitNode<R> = VisitNode<R>> {
|
|
61
|
-
onRoot(config: U & { config:
|
|
62
|
-
onSub(config: U & { config:
|
|
63
|
-
onSimple(config: Omit<U, 'descend'> & { config:
|
|
61
|
+
onRoot(config: U & { config: SchemaClassConfig }): R;
|
|
62
|
+
onSub(config: U & { config: SchemaFieldConfig }): R;
|
|
63
|
+
onSimple(config: Omit<U, 'descend'> & { config: SchemaFieldConfig }): R;
|
|
64
64
|
}
|
package/src/service.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ModelType,
|
|
3
3
|
BulkOp, BulkResponse, ModelCrudSupport, ModelStorageSupport, ModelBulkSupport,
|
|
4
|
-
NotFoundError,
|
|
4
|
+
NotFoundError, ModelRegistryIndex, ExistsError, OptionalId, ModelIdSource,
|
|
5
5
|
ModelExpiryUtil, ModelCrudUtil, ModelStorageUtil, ModelBulkUtil,
|
|
6
6
|
} from '@travetto/model';
|
|
7
7
|
import { castTo, Class } from '@travetto/runtime';
|
|
@@ -243,7 +243,7 @@ export class SQLModelService implements
|
|
|
243
243
|
async query<T extends ModelType>(cls: Class<T>, query: PageableModelQuery<T>): Promise<T[]> {
|
|
244
244
|
await QueryVerifier.verify(cls, query);
|
|
245
245
|
const { records: res } = await this.#exec<T>(this.#dialect.getQuerySQL(cls, query, ModelQueryUtil.getWhereClause(cls, query.where)));
|
|
246
|
-
if (
|
|
246
|
+
if (ModelRegistryIndex.has(cls)) {
|
|
247
247
|
await this.#dialect.fetchDependents(cls, res, query && query.select);
|
|
248
248
|
}
|
|
249
249
|
|
package/src/table-manager.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { AsyncContext, WithAsyncContext } from '@travetto/context';
|
|
2
|
-
import {
|
|
2
|
+
import { ModelRegistryIndex } from '@travetto/model';
|
|
3
3
|
import { Class } from '@travetto/runtime';
|
|
4
4
|
import { ChangeEvent } from '@travetto/registry';
|
|
5
5
|
import { SchemaChange } from '@travetto/schema';
|
|
6
6
|
|
|
7
7
|
import { Connected, Transactional } from './connection/decorator.ts';
|
|
8
8
|
import { SQLDialect } from './dialect/base.ts';
|
|
9
|
-
import { SQLModelUtil
|
|
9
|
+
import { SQLModelUtil } from './util.ts';
|
|
10
10
|
import { Connection } from './connection/base.ts';
|
|
11
|
+
import { VisitStack } from './types.ts';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Manage creation/updating of all tables
|
|
@@ -34,7 +35,7 @@ export class TableManager {
|
|
|
34
35
|
for (const op of this.#dialect.getCreateAllTablesSQL(cls)) {
|
|
35
36
|
out.push(op);
|
|
36
37
|
}
|
|
37
|
-
const indices =
|
|
38
|
+
const indices = ModelRegistryIndex.getConfig(cls).indices;
|
|
38
39
|
if (indices) {
|
|
39
40
|
for (const op of this.#dialect.getCreateAllIndicesSQL(cls, indices)) {
|
|
40
41
|
out.push(op);
|
|
@@ -53,7 +54,7 @@ export class TableManager {
|
|
|
53
54
|
for (const op of this.#dialect.getCreateAllTablesSQL(cls)) {
|
|
54
55
|
await this.#exec(op);
|
|
55
56
|
}
|
|
56
|
-
const indices =
|
|
57
|
+
const indices = ModelRegistryIndex.getConfig(cls).indices;
|
|
57
58
|
if (indices) {
|
|
58
59
|
for (const op of this.#dialect.getCreateAllIndicesSQL(cls, indices)) {
|
|
59
60
|
try {
|
package/src/types.ts
CHANGED
package/src/util.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { castKey, castTo, Class, TypedObject } from '@travetto/runtime';
|
|
2
2
|
import { SelectClause, SortClause } from '@travetto/model-query';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { ModelRegistryIndex, ModelType, OptionalId } from '@travetto/model';
|
|
4
|
+
import { SchemaClassConfig, SchemaFieldConfig, DataUtil, SchemaRegistryIndex } from '@travetto/schema';
|
|
5
5
|
|
|
6
6
|
import { DialectState, InsertWrapper, VisitHandler, VisitState, VisitInstanceNode, OrderBy } from './internal/types.ts';
|
|
7
7
|
import { TableSymbol, VisitStack } from './types.ts';
|
|
8
8
|
|
|
9
9
|
type FieldCacheEntry = {
|
|
10
|
-
local:
|
|
11
|
-
localMap: Record<string,
|
|
12
|
-
foreign:
|
|
13
|
-
foreignMap: Record<string,
|
|
10
|
+
local: SchemaFieldConfig[];
|
|
11
|
+
localMap: Record<string | symbol, SchemaFieldConfig>;
|
|
12
|
+
foreign: SchemaFieldConfig[];
|
|
13
|
+
foreignMap: Record<string | symbol, SchemaFieldConfig>;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -18,7 +18,7 @@ type FieldCacheEntry = {
|
|
|
18
18
|
*/
|
|
19
19
|
export class SQLModelUtil {
|
|
20
20
|
|
|
21
|
-
static
|
|
21
|
+
static #schemaFieldsCache = new Map<Class, FieldCacheEntry>();
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Creates a new visitation stack with the class as the root
|
|
@@ -53,34 +53,33 @@ export class SQLModelUtil {
|
|
|
53
53
|
* Get all available fields at current stack path
|
|
54
54
|
*/
|
|
55
55
|
static getFieldsByLocation(stack: VisitStack[]): FieldCacheEntry {
|
|
56
|
-
const top = stack
|
|
57
|
-
const
|
|
56
|
+
const top = stack.at(-1)!;
|
|
57
|
+
const conf = SchemaRegistryIndex.has(top.type) ? SchemaRegistryIndex.getConfig(top.type) : undefined;
|
|
58
58
|
|
|
59
|
-
if (
|
|
60
|
-
return this.
|
|
59
|
+
if (conf && this.#schemaFieldsCache.has(conf.class)) {
|
|
60
|
+
return this.#schemaFieldsCache.get(conf.class)!;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
if (!
|
|
64
|
-
const field:
|
|
63
|
+
if (!conf) { // If a simple type, it is it's own field
|
|
64
|
+
const field: SchemaFieldConfig = castTo({ ...top });
|
|
65
65
|
return {
|
|
66
66
|
local: [field], localMap: { [field.name]: field },
|
|
67
67
|
foreign: [], foreignMap: {}
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const fields = conf.fields.map(x => ({ ...conf.schema[x] }));
|
|
71
|
+
const hasModel = ModelRegistryIndex.has(conf.class)!;
|
|
72
|
+
const fields = Object.values(conf.fields).map(field => ({ ...field }));
|
|
74
73
|
|
|
75
74
|
// Polymorphic
|
|
76
|
-
if (
|
|
75
|
+
if (hasModel && conf.discriminatedBase) {
|
|
77
76
|
const fieldMap = new Set(fields.map(f => f.name));
|
|
78
|
-
for (const type of
|
|
79
|
-
const typeConf =
|
|
80
|
-
for (const
|
|
81
|
-
if (!fieldMap.has(
|
|
82
|
-
fieldMap.add(
|
|
83
|
-
fields.push({ ...
|
|
77
|
+
for (const type of SchemaRegistryIndex.getDiscriminatedClasses(conf.class)) {
|
|
78
|
+
const typeConf = SchemaRegistryIndex.getConfig(type);
|
|
79
|
+
for (const [fieldName, field] of Object.entries<SchemaFieldConfig>(typeConf.fields)) {
|
|
80
|
+
if (!fieldMap.has(fieldName)) {
|
|
81
|
+
fieldMap.add(fieldName);
|
|
82
|
+
fields.push({ ...field, required: { active: false } });
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
85
|
}
|
|
@@ -89,14 +88,14 @@ export class SQLModelUtil {
|
|
|
89
88
|
const ret: FieldCacheEntry = {
|
|
90
89
|
localMap: {},
|
|
91
90
|
foreignMap: {},
|
|
92
|
-
local: fields.filter(x => !
|
|
93
|
-
foreign: fields.filter(x =>
|
|
91
|
+
local: fields.filter(x => !SchemaRegistryIndex.has(x.type) && !x.array),
|
|
92
|
+
foreign: fields.filter(x => SchemaRegistryIndex.has(x.type) || x.array)
|
|
94
93
|
};
|
|
95
94
|
|
|
96
95
|
ret.local.reduce((acc, f) => (acc[f.name] = f) && acc, ret.localMap);
|
|
97
96
|
ret.foreign.reduce((acc, f) => (acc[f.name] = f) && acc, ret.foreignMap);
|
|
98
97
|
|
|
99
|
-
this.
|
|
98
|
+
this.#schemaFieldsCache.set(conf.class, ret);
|
|
100
99
|
|
|
101
100
|
return ret;
|
|
102
101
|
}
|
|
@@ -104,13 +103,13 @@ export class SQLModelUtil {
|
|
|
104
103
|
/**
|
|
105
104
|
* Process a schema structure, synchronously
|
|
106
105
|
*/
|
|
107
|
-
static visitSchemaSync(config:
|
|
106
|
+
static visitSchemaSync(config: SchemaClassConfig | SchemaFieldConfig, handler: VisitHandler<void>, state: VisitState = { path: [] }): void {
|
|
108
107
|
const path = 'class' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
109
108
|
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
110
109
|
|
|
111
110
|
const descend = (): void => {
|
|
112
111
|
for (const field of foreign) {
|
|
113
|
-
if (
|
|
112
|
+
if (SchemaRegistryIndex.has(field.type)) {
|
|
114
113
|
this.visitSchemaSync(field, handler, { path });
|
|
115
114
|
} else {
|
|
116
115
|
handler.onSimple({
|
|
@@ -132,13 +131,13 @@ export class SQLModelUtil {
|
|
|
132
131
|
/**
|
|
133
132
|
* Visit a Schema structure
|
|
134
133
|
*/
|
|
135
|
-
static async visitSchema(config:
|
|
134
|
+
static async visitSchema(config: SchemaClassConfig | SchemaFieldConfig, handler: VisitHandler<Promise<void>>, state: VisitState = { path: [] }): Promise<void> {
|
|
136
135
|
const path = 'class' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
137
136
|
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
138
137
|
|
|
139
138
|
const descend = async (): Promise<void> => {
|
|
140
139
|
for (const field of foreign) {
|
|
141
|
-
if (
|
|
140
|
+
if (SchemaRegistryIndex.has(field.type)) {
|
|
142
141
|
await this.visitSchema(field, handler, { path });
|
|
143
142
|
} else {
|
|
144
143
|
await handler.onSimple({
|
|
@@ -162,7 +161,7 @@ export class SQLModelUtil {
|
|
|
162
161
|
*/
|
|
163
162
|
static visitSchemaInstance<T extends ModelType>(cls: Class<T>, instance: T | OptionalId<T>, handler: VisitHandler<unknown, VisitInstanceNode<unknown>>): void {
|
|
164
163
|
const pathObj: unknown[] = [instance];
|
|
165
|
-
this.visitSchemaSync(
|
|
164
|
+
this.visitSchemaSync(SchemaRegistryIndex.getConfig(cls), {
|
|
166
165
|
onRoot: (config) => {
|
|
167
166
|
const { path } = config;
|
|
168
167
|
path[0].name = instance.id!;
|
|
@@ -171,8 +170,8 @@ export class SQLModelUtil {
|
|
|
171
170
|
},
|
|
172
171
|
onSub: (config) => {
|
|
173
172
|
const { config: field } = config;
|
|
174
|
-
const topObj: Record<string, unknown> = castTo(pathObj
|
|
175
|
-
const top = config.path
|
|
173
|
+
const topObj: Record<string | symbol, unknown> = castTo(pathObj.at(-1));
|
|
174
|
+
const top = config.path.at(-1)!;
|
|
176
175
|
|
|
177
176
|
if (field.name in topObj) {
|
|
178
177
|
const value = topObj[field.name];
|
|
@@ -199,7 +198,7 @@ export class SQLModelUtil {
|
|
|
199
198
|
},
|
|
200
199
|
onSimple: (config) => {
|
|
201
200
|
const { config: field } = config;
|
|
202
|
-
const topObj: Record<string, unknown> = castTo(pathObj
|
|
201
|
+
const topObj: Record<string | symbol, unknown> = castTo(pathObj.at(-1));
|
|
203
202
|
const value = topObj[field.name];
|
|
204
203
|
return handler.onSimple({ ...config, value });
|
|
205
204
|
}
|
|
@@ -209,7 +208,7 @@ export class SQLModelUtil {
|
|
|
209
208
|
/**
|
|
210
209
|
* Get list of selected fields
|
|
211
210
|
*/
|
|
212
|
-
static select<T>(cls: Class<T>, select?: SelectClause<T>):
|
|
211
|
+
static select<T>(cls: Class<T>, select?: SelectClause<T>): SchemaFieldConfig[] {
|
|
213
212
|
if (!select || Object.keys(select).length === 0) {
|
|
214
213
|
return [{ type: cls, name: '*', owner: cls, array: false }];
|
|
215
214
|
}
|
|
@@ -222,7 +221,7 @@ export class SQLModelUtil {
|
|
|
222
221
|
if (typeof k === 'string' && !DataUtil.isPlainObject(select[k]) && localMap[k]) {
|
|
223
222
|
if (!v) {
|
|
224
223
|
if (toGet.size === 0) {
|
|
225
|
-
toGet = new Set(
|
|
224
|
+
toGet = new Set(Object.keys(SchemaRegistryIndex.getConfig(cls).fields));
|
|
226
225
|
}
|
|
227
226
|
toGet.delete(k);
|
|
228
227
|
} else {
|
|
@@ -238,19 +237,19 @@ export class SQLModelUtil {
|
|
|
238
237
|
*/
|
|
239
238
|
static orderBy<T>(cls: Class<T>, sort: SortClause<T>[]): OrderBy[] {
|
|
240
239
|
return sort.map((cl: Record<string, unknown>) => {
|
|
241
|
-
let schema:
|
|
240
|
+
let schema: SchemaClassConfig = SchemaRegistryIndex.getConfig(cls);
|
|
242
241
|
const stack = this.classToStack(cls);
|
|
243
242
|
let found: OrderBy | undefined;
|
|
244
243
|
while (!found) {
|
|
245
244
|
const key = Object.keys(cl)[0];
|
|
246
245
|
const val = cl[key];
|
|
247
|
-
const field = { ...schema.
|
|
246
|
+
const field = { ...schema.fields[key] };
|
|
248
247
|
if (DataUtil.isPrimitive(val)) {
|
|
249
248
|
stack.push(field);
|
|
250
249
|
found = { stack, asc: val === 1 };
|
|
251
250
|
} else {
|
|
252
251
|
stack.push(field);
|
|
253
|
-
schema =
|
|
252
|
+
schema = SchemaRegistryIndex.getConfig(field.type);
|
|
254
253
|
cl = castTo(val);
|
|
255
254
|
}
|
|
256
255
|
}
|
|
@@ -261,9 +260,9 @@ export class SQLModelUtil {
|
|
|
261
260
|
/**
|
|
262
261
|
* Find all dependent fields via child tables
|
|
263
262
|
*/
|
|
264
|
-
static collectDependents<T>(dct: DialectState, parent: unknown, v: T[], field?:
|
|
263
|
+
static collectDependents<T>(dct: DialectState, parent: unknown, v: T[], field?: SchemaFieldConfig): Record<string, T> {
|
|
265
264
|
if (field) {
|
|
266
|
-
const isSimple =
|
|
265
|
+
const isSimple = SchemaRegistryIndex.has(field.type);
|
|
267
266
|
for (const el of v) {
|
|
268
267
|
const parentKey: string = castTo(el[castKey<T>(dct.parentPathField.name)]);
|
|
269
268
|
const root = castTo<Record<string, Record<string, unknown>>>(parent)[parentKey];
|
|
@@ -294,9 +293,9 @@ export class SQLModelUtil {
|
|
|
294
293
|
* Build table name via stack path
|
|
295
294
|
*/
|
|
296
295
|
static buildTable(list: VisitStack[]): string {
|
|
297
|
-
const top = list
|
|
296
|
+
const top = list.at(-1)!;
|
|
298
297
|
if (!top[TableSymbol]) {
|
|
299
|
-
top[TableSymbol] = list.map((el, i) => i === 0 ?
|
|
298
|
+
top[TableSymbol] = list.map((el, i) => i === 0 ? ModelRegistryIndex.getStoreName(el.type) : el.name).join('_');
|
|
300
299
|
}
|
|
301
300
|
return top[TableSymbol]!;
|
|
302
301
|
}
|
|
@@ -305,7 +304,7 @@ export class SQLModelUtil {
|
|
|
305
304
|
* Build property path for a table/field given the current stack
|
|
306
305
|
*/
|
|
307
306
|
static buildPath(list: VisitStack[]): string {
|
|
308
|
-
return list.map((el) => `${el.name}${el.index ? `[${el.index}]` : ''}`).join('.');
|
|
307
|
+
return list.map((el) => `${el.name.toString()}${el.index ? `[${el.index}]` : ''}`).join('.');
|
|
309
308
|
}
|
|
310
309
|
|
|
311
310
|
/**
|
package/support/test/query.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
-
import { Schema,
|
|
3
|
+
import { Schema, SchemaFieldConfig } from '@travetto/schema';
|
|
4
4
|
import { Suite, Test } from '@travetto/test';
|
|
5
5
|
import { castTo } from '@travetto/runtime';
|
|
6
6
|
import { BaseModelSuite } from '@travetto/model/support/test/base.ts';
|
|
@@ -67,9 +67,9 @@ export abstract class BaseSQLTest extends BaseModelSuite<SQLModelService> {
|
|
|
67
67
|
|
|
68
68
|
const dct = await this.dialect;
|
|
69
69
|
dct.resolveName = (stack: VisitStack[]) => {
|
|
70
|
-
const field:
|
|
71
|
-
const parent:
|
|
72
|
-
return `${field.owner ? field.owner.name : parent.name}.${field.name}`;
|
|
70
|
+
const field: SchemaFieldConfig = castTo(stack.at(-1));
|
|
71
|
+
const parent: SchemaFieldConfig = castTo(stack.at(-2));
|
|
72
|
+
return `${field.owner ? field.owner.name.toString() : parent.name.toString()}.${field.name.toString()}`;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
const qryStr = dct.getWhereGroupingSQL(WhereType, qry);
|
|
@@ -80,8 +80,8 @@ export abstract class BaseSQLTest extends BaseModelSuite<SQLModelService> {
|
|
|
80
80
|
async testRegEx() {
|
|
81
81
|
const dct = await this.dialect;
|
|
82
82
|
dct.resolveName = (stack: VisitStack[]) => {
|
|
83
|
-
const field:
|
|
84
|
-
return `${field.owner?.name}.${field.name}`;
|
|
83
|
+
const field: SchemaFieldConfig = castTo(stack.at(-1));
|
|
84
|
+
return `${field.owner?.name}.${field.name.toString()}`;
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
const out = dct.getWhereGroupingSQL(User, {
|