@travetto/model-sql 7.0.0-rc.2 → 7.0.0-rc.3
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/config.ts +5 -5
- package/src/dialect/base.ts +84 -21
- package/src/service.ts +8 -16
- package/src/table-manager.ts +84 -54
- package/src/util.ts +5 -5
- package/support/test/query.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-sql",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.3",
|
|
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": "^7.0.0-rc.
|
|
31
|
-
"@travetto/config": "^7.0.0-rc.
|
|
32
|
-
"@travetto/context": "^7.0.0-rc.
|
|
33
|
-
"@travetto/model": "^7.0.0-rc.
|
|
34
|
-
"@travetto/model-query": "^7.0.0-rc.
|
|
30
|
+
"@travetto/cli": "^7.0.0-rc.3",
|
|
31
|
+
"@travetto/config": "^7.0.0-rc.3",
|
|
32
|
+
"@travetto/context": "^7.0.0-rc.3",
|
|
33
|
+
"@travetto/model": "^7.0.0-rc.3",
|
|
34
|
+
"@travetto/model-query": "^7.0.0-rc.3"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@travetto/test": "^7.0.0-rc.
|
|
37
|
+
"@travetto/test": "^7.0.0-rc.3"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@travetto/test": {
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Config } from '@travetto/config';
|
|
2
|
-
import { asFull } from '@travetto/runtime';
|
|
2
|
+
import { asFull, Runtime } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* SQL Model Config
|
|
@@ -17,11 +17,11 @@ export class SQLModelConfig<T extends {} = {}> {
|
|
|
17
17
|
/**
|
|
18
18
|
* Username
|
|
19
19
|
*/
|
|
20
|
-
user = '';
|
|
20
|
+
user = Runtime.production ? '' : 'travetto';
|
|
21
21
|
/**
|
|
22
22
|
* Password
|
|
23
23
|
*/
|
|
24
|
-
password = '';
|
|
24
|
+
password = Runtime.production ? '' : 'travetto';
|
|
25
25
|
/**
|
|
26
26
|
* Table prefix
|
|
27
27
|
*/
|
|
@@ -31,9 +31,9 @@ export class SQLModelConfig<T extends {} = {}> {
|
|
|
31
31
|
*/
|
|
32
32
|
database = 'app';
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Allow storage modification at runtime
|
|
35
35
|
*/
|
|
36
|
-
|
|
36
|
+
modifyStorage?: boolean;
|
|
37
37
|
/**
|
|
38
38
|
* Db version
|
|
39
39
|
*/
|
package/src/dialect/base.ts
CHANGED
|
@@ -16,6 +16,12 @@ interface Alias {
|
|
|
16
16
|
path: VisitStack[];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export type SQLTableDescription = {
|
|
20
|
+
columns: { name: string, type: string, is_notnull: boolean }[];
|
|
21
|
+
foreignKeys: { name: string, from_column: string, to_column: string, to_table: string }[];
|
|
22
|
+
indices: { name: string, columns: { name: string, desc: boolean }[], is_unique: boolean }[];
|
|
23
|
+
};
|
|
24
|
+
|
|
19
25
|
@Schema()
|
|
20
26
|
class Total {
|
|
21
27
|
total: number;
|
|
@@ -24,7 +30,7 @@ class Total {
|
|
|
24
30
|
function makeField(name: string, type: Class, required: boolean, extra: Partial<SchemaFieldConfig>): SchemaFieldConfig {
|
|
25
31
|
return {
|
|
26
32
|
name,
|
|
27
|
-
|
|
33
|
+
class: null!,
|
|
28
34
|
type,
|
|
29
35
|
array: false,
|
|
30
36
|
...(required ? { required: { active: true } } : {}),
|
|
@@ -105,8 +111,8 @@ export abstract class SQLDialect implements DialectState {
|
|
|
105
111
|
* Generate an id field
|
|
106
112
|
*/
|
|
107
113
|
idField = makeField('id', String, true, {
|
|
108
|
-
maxlength: {
|
|
109
|
-
minlength: {
|
|
114
|
+
maxlength: { limit: this.ID_LENGTH },
|
|
115
|
+
minlength: { limit: this.ID_LENGTH }
|
|
110
116
|
});
|
|
111
117
|
|
|
112
118
|
/**
|
|
@@ -118,8 +124,8 @@ export abstract class SQLDialect implements DialectState {
|
|
|
118
124
|
* Parent path reference
|
|
119
125
|
*/
|
|
120
126
|
parentPathField = makeField('__parent_path', String, true, {
|
|
121
|
-
maxlength: {
|
|
122
|
-
minlength: {
|
|
127
|
+
maxlength: { limit: this.HASH_LENGTH },
|
|
128
|
+
minlength: { limit: this.HASH_LENGTH },
|
|
123
129
|
required: { active: true }
|
|
124
130
|
});
|
|
125
131
|
|
|
@@ -127,8 +133,8 @@ export abstract class SQLDialect implements DialectState {
|
|
|
127
133
|
* Path reference
|
|
128
134
|
*/
|
|
129
135
|
pathField = makeField('__path', String, true, {
|
|
130
|
-
maxlength: {
|
|
131
|
-
minlength: {
|
|
136
|
+
maxlength: { limit: this.HASH_LENGTH },
|
|
137
|
+
minlength: { limit: this.HASH_LENGTH },
|
|
132
138
|
required: { active: true }
|
|
133
139
|
});
|
|
134
140
|
|
|
@@ -156,6 +162,11 @@ export abstract class SQLDialect implements DialectState {
|
|
|
156
162
|
*/
|
|
157
163
|
abstract hash(input: string): string;
|
|
158
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Describe a table structure
|
|
167
|
+
*/
|
|
168
|
+
abstract describeTable(table: string): Promise<SQLTableDescription | undefined>;
|
|
169
|
+
|
|
159
170
|
executeSQL<T>(sql: string): Promise<{ records: T[], count: number }> {
|
|
160
171
|
return this.connection.execute<T>(this.connection.active, sql);
|
|
161
172
|
}
|
|
@@ -253,7 +264,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
253
264
|
if (config.specifiers?.includes('text')) {
|
|
254
265
|
type = this.COLUMN_TYPES.TEXT;
|
|
255
266
|
} else {
|
|
256
|
-
type = this.PARAMETERIZED_COLUMN_TYPES.VARCHAR(config.maxlength
|
|
267
|
+
type = this.PARAMETERIZED_COLUMN_TYPES.VARCHAR(config.maxlength?.limit ?? this.DEFAULT_STRING_LENGTH);
|
|
257
268
|
}
|
|
258
269
|
} else if (config.type === PointConcrete) {
|
|
259
270
|
type = this.COLUMN_TYPES.POINT;
|
|
@@ -267,12 +278,13 @@ export abstract class SQLDialect implements DialectState {
|
|
|
267
278
|
/**
|
|
268
279
|
* FieldConfig to Column definition
|
|
269
280
|
*/
|
|
270
|
-
getColumnDefinition(config: SchemaFieldConfig): string | undefined {
|
|
281
|
+
getColumnDefinition(config: SchemaFieldConfig, overrideRequired?: boolean): string | undefined {
|
|
271
282
|
const type = this.getColumnType(config);
|
|
272
283
|
if (!type) {
|
|
273
284
|
return;
|
|
274
285
|
}
|
|
275
|
-
|
|
286
|
+
const required = overrideRequired ? true : (config.required?.active ?? false);
|
|
287
|
+
return `${this.identifier(config)} ${type} ${required ? 'NOT NULL' : ''}`;
|
|
276
288
|
}
|
|
277
289
|
|
|
278
290
|
/**
|
|
@@ -420,7 +432,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
420
432
|
// If dealing with simple external
|
|
421
433
|
sStack.push({
|
|
422
434
|
name: field.name,
|
|
423
|
-
|
|
435
|
+
class: null!,
|
|
424
436
|
type: field.type
|
|
425
437
|
});
|
|
426
438
|
}
|
|
@@ -652,17 +664,11 @@ ${this.getLimitSQL(cls, query)}`;
|
|
|
652
664
|
let idField = fields.find(field => field.name === this.idField.name);
|
|
653
665
|
if (!idField) {
|
|
654
666
|
fields.push(idField = this.idField);
|
|
655
|
-
} else {
|
|
656
|
-
idField.maxlength = { n: this.ID_LENGTH };
|
|
657
667
|
}
|
|
658
668
|
}
|
|
659
669
|
|
|
660
670
|
const fieldSql = fields
|
|
661
|
-
.map(field =>
|
|
662
|
-
const def = this.getColumnDefinition(field) || '';
|
|
663
|
-
return field.name === this.idField.name && !parent ?
|
|
664
|
-
def.replace('DEFAULT NULL', 'NOT NULL') : def;
|
|
665
|
-
})
|
|
671
|
+
.map(field => this.getColumnDefinition(field, field.name === this.idField.name && !parent) || '')
|
|
666
672
|
.filter(line => !!line.trim())
|
|
667
673
|
.join(',\n ');
|
|
668
674
|
|
|
@@ -714,6 +720,14 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
714
720
|
return indices.map(idx => this.getCreateIndexSQL(cls, idx));
|
|
715
721
|
}
|
|
716
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Get index name
|
|
725
|
+
*/
|
|
726
|
+
getIndexName<T extends ModelType>(cls: Class<T>, idx: IndexConfig<ModelType>): string {
|
|
727
|
+
const table = this.namespace(SQLModelUtil.classToStack(cls));
|
|
728
|
+
return ['idx', table, idx.name.toLowerCase().replaceAll('-', '_')].join('_');
|
|
729
|
+
}
|
|
730
|
+
|
|
717
731
|
/**
|
|
718
732
|
* Get CREATE INDEX sql
|
|
719
733
|
*/
|
|
@@ -727,12 +741,20 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
727
741
|
}
|
|
728
742
|
return [castTo(key), typeof value === 'number' ? value === 1 : (!!value)];
|
|
729
743
|
});
|
|
730
|
-
const constraint =
|
|
744
|
+
const constraint = this.getIndexName(cls, idx);
|
|
731
745
|
return `CREATE ${idx.type === 'unique' ? 'UNIQUE ' : ''}INDEX ${constraint} ON ${this.identifier(table)} (${fields
|
|
732
746
|
.map(([name, sel]) => `${this.identifier(name)} ${sel ? 'ASC' : 'DESC'}`)
|
|
733
747
|
.join(', ')});`;
|
|
734
748
|
}
|
|
735
749
|
|
|
750
|
+
/**
|
|
751
|
+
* Get DROP INDEX sql
|
|
752
|
+
*/
|
|
753
|
+
getDropIndexSQL<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string): string {
|
|
754
|
+
const constraint = typeof idx === 'string' ? idx : this.getIndexName(cls, idx);
|
|
755
|
+
return `DROP INDEX ${this.identifier(constraint)} ;`;
|
|
756
|
+
}
|
|
757
|
+
|
|
736
758
|
/**
|
|
737
759
|
* Drop all tables for a given class
|
|
738
760
|
*/
|
|
@@ -869,8 +891,8 @@ ${this.getWhereSQL(type, where)};`;
|
|
|
869
891
|
}
|
|
870
892
|
|
|
871
893
|
/**
|
|
872
|
-
|
|
873
|
-
|
|
894
|
+
* Get elements by ids
|
|
895
|
+
*/
|
|
874
896
|
getSelectRowsByIdsSQL(stack: VisitStack[], ids: string[], select: SchemaFieldConfig[] = []): string {
|
|
875
897
|
const config = stack.at(-1)!;
|
|
876
898
|
const orderBy = !config.array ?
|
|
@@ -1035,4 +1057,45 @@ ${this.getWhereSQL(cls, where!)}`;
|
|
|
1035
1057
|
|
|
1036
1058
|
return out;
|
|
1037
1059
|
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Determine if a column has changed
|
|
1063
|
+
*/
|
|
1064
|
+
isColumnChanged(requested: SchemaFieldConfig, existing: SQLTableDescription['columns'][number],): boolean {
|
|
1065
|
+
const requestedColumnType = this.getColumnType(requested);
|
|
1066
|
+
const result =
|
|
1067
|
+
(requested.name !== this.idField.name && !!requested.required?.active !== !!existing.is_notnull)
|
|
1068
|
+
|| (requestedColumnType.toUpperCase() !== existing.type.toUpperCase());
|
|
1069
|
+
|
|
1070
|
+
return result;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Determine if an index has changed
|
|
1075
|
+
*/
|
|
1076
|
+
isIndexChanged(requested: IndexConfig<ModelType>, existing: SQLTableDescription['indices'][number]): boolean {
|
|
1077
|
+
let result =
|
|
1078
|
+
(existing.is_unique && requested.type !== 'unique')
|
|
1079
|
+
|| requested.fields.length !== existing.columns.length;
|
|
1080
|
+
|
|
1081
|
+
for (let i = 0; i < requested.fields.length && !result; i++) {
|
|
1082
|
+
const [[key, value]] = Object.entries(requested.fields[i]);
|
|
1083
|
+
const desc = value === -1;
|
|
1084
|
+
result ||= key !== existing.columns[i].name && desc !== existing.columns[i].desc;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
return result;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Enforce the dialect specific id length
|
|
1092
|
+
*/
|
|
1093
|
+
enforceIdLength(cls: Class<ModelType>): void {
|
|
1094
|
+
const config = SchemaRegistryIndex.getConfig(cls);
|
|
1095
|
+
const idField = config.fields[this.idField.name];
|
|
1096
|
+
if (idField) {
|
|
1097
|
+
idField.maxlength = { limit: this.ID_LENGTH };
|
|
1098
|
+
idField.minlength = { limit: this.ID_LENGTH };
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1038
1101
|
}
|
package/src/service.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
ModelExpiryUtil, ModelCrudUtil, ModelStorageUtil, ModelBulkUtil,
|
|
6
6
|
} from '@travetto/model';
|
|
7
7
|
import { castTo, Class } from '@travetto/runtime';
|
|
8
|
-
import { DataUtil
|
|
8
|
+
import { DataUtil } from '@travetto/schema';
|
|
9
9
|
import { AsyncContext } from '@travetto/context';
|
|
10
10
|
import { Injectable } from '@travetto/di';
|
|
11
11
|
import {
|
|
@@ -99,15 +99,11 @@ export class SQLModelService implements
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
async postConstruct(): Promise<void> {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.#manager = new TableManager(this.#context, this.#dialect);
|
|
108
|
-
await ModelStorageUtil.registerModelChangeListener(this);
|
|
109
|
-
ModelExpiryUtil.registerCull(this);
|
|
110
|
-
}
|
|
102
|
+
await this.#dialect.connection.init?.();
|
|
103
|
+
this.idSource = ModelCrudUtil.uuidSource(this.#dialect.ID_LENGTH);
|
|
104
|
+
this.#manager = new TableManager(this.#context, this.#dialect);
|
|
105
|
+
await ModelStorageUtil.storageInitialization(this);
|
|
106
|
+
ModelExpiryUtil.registerCull(this);
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
get connection(): Connection {
|
|
@@ -118,12 +114,8 @@ export class SQLModelService implements
|
|
|
118
114
|
return (await this.#manager.exportTables(cls)).join('\n');
|
|
119
115
|
}
|
|
120
116
|
|
|
121
|
-
async
|
|
122
|
-
await this.#manager.
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async createModel(cls: Class): Promise<void> {
|
|
126
|
-
await this.#manager.createTables(cls);
|
|
117
|
+
async upsertModel(cls: Class): Promise<void> {
|
|
118
|
+
await this.#manager.upsertTables(cls);
|
|
127
119
|
}
|
|
128
120
|
|
|
129
121
|
async deleteModel(cls: Class): Promise<void> {
|
package/src/table-manager.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { AsyncContext, WithAsyncContext } from '@travetto/context';
|
|
2
2
|
import { ModelRegistryIndex } from '@travetto/model';
|
|
3
3
|
import { Class } from '@travetto/runtime';
|
|
4
|
-
import {
|
|
5
|
-
import { SchemaChange } from '@travetto/schema';
|
|
4
|
+
import { SchemaRegistryIndex, type SchemaFieldConfig } from '@travetto/schema';
|
|
6
5
|
|
|
7
6
|
import { Connected, Transactional } from './connection/decorator.ts';
|
|
8
7
|
import { SQLDialect } from './dialect/base.ts';
|
|
@@ -10,6 +9,10 @@ import { SQLModelUtil } from './util.ts';
|
|
|
10
9
|
import { Connection } from './connection/base.ts';
|
|
11
10
|
import { VisitStack } from './types.ts';
|
|
12
11
|
|
|
12
|
+
type UpsertStructure = { dropIndex: string[], createIndex: string[], table: string[] };
|
|
13
|
+
const isSimpleField = (input: VisitStack | undefined): input is SchemaFieldConfig =>
|
|
14
|
+
!!input && (!('type' in input) || (input.type && !SchemaRegistryIndex.has(input.type)));
|
|
15
|
+
|
|
13
16
|
/**
|
|
14
17
|
* Manage creation/updating of all tables
|
|
15
18
|
*/
|
|
@@ -27,6 +30,13 @@ export class TableManager {
|
|
|
27
30
|
return this.#dialect.executeSQL<T>(sql);
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Get a valid connection
|
|
35
|
+
*/
|
|
36
|
+
get connection(): Connection {
|
|
37
|
+
return this.#dialect.connection;
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
/**
|
|
31
41
|
* Create all needed tables for a given class
|
|
32
42
|
*/
|
|
@@ -44,30 +54,84 @@ export class TableManager {
|
|
|
44
54
|
return out;
|
|
45
55
|
}
|
|
46
56
|
|
|
47
|
-
/**
|
|
48
|
-
* Create all needed tables for a given class
|
|
49
|
-
*/
|
|
50
57
|
@WithAsyncContext()
|
|
51
58
|
@Connected()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
async getUpsertTablesSQL(cls: Class): Promise<UpsertStructure> {
|
|
60
|
+
const sqlCommands: UpsertStructure = { dropIndex: [], createIndex: [], table: [] };
|
|
61
|
+
|
|
62
|
+
const onVisit = async (type: Class, fields: SchemaFieldConfig[], path: VisitStack[]): Promise<void> => {
|
|
63
|
+
const found = await this.#dialect.describeTable(this.#dialect.namespace(path));
|
|
64
|
+
const existingFields = new Map(found?.columns.map(column => [column.name, column]) ?? []);
|
|
65
|
+
const existingIndices = new Map(found?.indices.map(index => [index.name, index]) ?? []);
|
|
66
|
+
const model = path.length === 1 ? ModelRegistryIndex.getConfig(type) : undefined;
|
|
67
|
+
const requestedIndices = new Map((model?.indices ?? []).map(index => [this.#dialect.getIndexName(type, index), index]) ?? []);
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
// Manage fields
|
|
71
|
+
if (!existingFields.size) {
|
|
72
|
+
sqlCommands.table.push(this.#dialect.getCreateTableSQL(path));
|
|
73
|
+
} else { // Existing
|
|
74
|
+
// Fields
|
|
75
|
+
const requestedFields = new Map(fields.map(field => [field.name, field]));
|
|
76
|
+
const top = path.at(-1);
|
|
77
|
+
|
|
78
|
+
if (isSimpleField(top)) {
|
|
79
|
+
requestedFields.set(top.name, top);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const [column, field] of requestedFields.entries()) {
|
|
83
|
+
if (!existingFields.has(column)) {
|
|
84
|
+
sqlCommands.table.push(this.#dialect.getAddColumnSQL([...path, field]));
|
|
85
|
+
} else if (this.#dialect.isColumnChanged(field, existingFields.get(column)!)) {
|
|
86
|
+
sqlCommands.table.push(this.#dialect.getModifyColumnSQL([...path, field]));
|
|
65
87
|
}
|
|
66
|
-
|
|
67
|
-
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// TODO: Handle dropping tables that are FK'd when no longer in use
|
|
91
|
+
|
|
92
|
+
for (const column of existingFields.keys()) {
|
|
93
|
+
if (!requestedFields.has(column)) {
|
|
94
|
+
sqlCommands.table.push(this.#dialect.getDropColumnSQL([...path, { name: column, type: undefined!, array: false }]));
|
|
68
95
|
}
|
|
69
96
|
}
|
|
70
97
|
}
|
|
98
|
+
|
|
99
|
+
// Manage indices
|
|
100
|
+
for (const index of requestedIndices.keys()) {
|
|
101
|
+
if (!existingIndices.has(index)) {
|
|
102
|
+
sqlCommands.createIndex.push(this.#dialect.getCreateIndexSQL(type, requestedIndices.get(index)!));
|
|
103
|
+
} else if (this.#dialect.isIndexChanged(requestedIndices.get(index)!, existingIndices.get(index)!)) {
|
|
104
|
+
sqlCommands.dropIndex.push(this.#dialect.getDropIndexSQL(type, existingIndices.get(index)!.name));
|
|
105
|
+
sqlCommands.createIndex.push(this.#dialect.getCreateIndexSQL(type, requestedIndices.get(index)!));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const index of existingIndices.keys()) {
|
|
110
|
+
if (!requestedIndices.has(index)) {
|
|
111
|
+
sqlCommands.dropIndex.push(this.#dialect.getDropIndexSQL(type, existingIndices.get(index)!.name));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const schema = SchemaRegistryIndex.getConfig(cls);
|
|
117
|
+
await SQLModelUtil.visitSchema(schema, {
|
|
118
|
+
onRoot: async ({ config, path, fields, descend }) => { await onVisit(config.class, fields, path); return descend(); },
|
|
119
|
+
onSub: async ({ config, path, fields, descend }) => { await onVisit(config.type, fields, path); return descend(); },
|
|
120
|
+
onSimple: async ({ config, path, fields }) => { await onVisit(config.type, fields, path); }
|
|
121
|
+
});
|
|
122
|
+
return sqlCommands;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@WithAsyncContext()
|
|
126
|
+
@Connected()
|
|
127
|
+
@Transactional()
|
|
128
|
+
async upsertTables(cls: Class): Promise<void> {
|
|
129
|
+
// Enforce id length
|
|
130
|
+
this.#dialect.enforceIdLength(cls);
|
|
131
|
+
|
|
132
|
+
const sqlCommands = await this.getUpsertTablesSQL(cls);
|
|
133
|
+
for (const key of ['dropIndex', 'table', 'createIndex'] as const) {
|
|
134
|
+
await Promise.all(sqlCommands[key].map(command => this.#exec(command)));
|
|
71
135
|
}
|
|
72
136
|
}
|
|
73
137
|
|
|
@@ -94,38 +158,4 @@ export class TableManager {
|
|
|
94
158
|
await this.#exec(command);
|
|
95
159
|
}
|
|
96
160
|
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Get a valid connection
|
|
100
|
-
*/
|
|
101
|
-
get connection(): Connection {
|
|
102
|
-
return this.#dialect.connection;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* When the schema changes, update SQL
|
|
107
|
-
*/
|
|
108
|
-
@WithAsyncContext()
|
|
109
|
-
@Transactional()
|
|
110
|
-
@Connected()
|
|
111
|
-
async changeSchema(cls: Class, change: SchemaChange): Promise<void> {
|
|
112
|
-
try {
|
|
113
|
-
const rootStack = SQLModelUtil.classToStack(cls);
|
|
114
|
-
|
|
115
|
-
const changes = change.subs.reduce<Record<ChangeEvent<unknown>['type'], VisitStack[][]>>((result, value) => {
|
|
116
|
-
const path = value.path.map(field => ({ ...field }));
|
|
117
|
-
for (const event of value.fields) {
|
|
118
|
-
result[event.type].push([...rootStack, ...path, { ...(event.type === 'removing' ? event.previous : event.current)! }]);
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
}, { added: [], changed: [], removing: [] });
|
|
122
|
-
|
|
123
|
-
await Promise.all(changes.added.map(value => this.#dialect.executeSQL(this.#dialect.getAddColumnSQL(value))));
|
|
124
|
-
await Promise.all(changes.changed.map(value => this.#dialect.executeSQL(this.#dialect.getModifyColumnSQL(value))));
|
|
125
|
-
await Promise.all(changes.removing.map(value => this.#dialect.executeSQL(this.#dialect.getDropColumnSQL(value))));
|
|
126
|
-
} catch (error) {
|
|
127
|
-
// Failed to change
|
|
128
|
-
console.error('Unable to change field', { error });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
161
|
}
|
package/src/util.ts
CHANGED
|
@@ -104,7 +104,7 @@ export class SQLModelUtil {
|
|
|
104
104
|
* Process a schema structure, synchronously
|
|
105
105
|
*/
|
|
106
106
|
static visitSchemaSync(config: SchemaClassConfig | SchemaFieldConfig, handler: VisitHandler<void>, state: VisitState = { path: [] }): void {
|
|
107
|
-
const path = '
|
|
107
|
+
const path = 'fields' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
108
108
|
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
109
109
|
|
|
110
110
|
const descend = (): void => {
|
|
@@ -121,7 +121,7 @@ export class SQLModelUtil {
|
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
if ('
|
|
124
|
+
if ('fields' in config) {
|
|
125
125
|
return handler.onRoot({ config, fields, descend, path });
|
|
126
126
|
} else {
|
|
127
127
|
return handler.onSub({ config, fields, descend, path });
|
|
@@ -132,7 +132,7 @@ export class SQLModelUtil {
|
|
|
132
132
|
* Visit a Schema structure
|
|
133
133
|
*/
|
|
134
134
|
static async visitSchema(config: SchemaClassConfig | SchemaFieldConfig, handler: VisitHandler<Promise<void>>, state: VisitState = { path: [] }): Promise<void> {
|
|
135
|
-
const path = '
|
|
135
|
+
const path = 'fields' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
136
136
|
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
137
137
|
|
|
138
138
|
const descend = async (): Promise<void> => {
|
|
@@ -149,7 +149,7 @@ export class SQLModelUtil {
|
|
|
149
149
|
}
|
|
150
150
|
};
|
|
151
151
|
|
|
152
|
-
if ('
|
|
152
|
+
if ('fields' in config) {
|
|
153
153
|
return handler.onRoot({ config, fields, descend, path });
|
|
154
154
|
} else {
|
|
155
155
|
return handler.onSub({ config, fields, descend, path });
|
|
@@ -210,7 +210,7 @@ export class SQLModelUtil {
|
|
|
210
210
|
*/
|
|
211
211
|
static select<T>(cls: Class<T>, select?: SelectClause<T>): SchemaFieldConfig[] {
|
|
212
212
|
if (!select || Object.keys(select).length === 0) {
|
|
213
|
-
return [{ type: cls, name: '*',
|
|
213
|
+
return [{ type: cls, name: '*', class: cls, array: false }];
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
const { localMap } = this.getFieldsByLocation(this.classToStack(cls));
|
package/support/test/query.ts
CHANGED
|
@@ -69,7 +69,7 @@ export abstract class BaseSQLTest extends BaseModelSuite<SQLModelService> {
|
|
|
69
69
|
dct.resolveName = (stack: VisitStack[]) => {
|
|
70
70
|
const field: SchemaFieldConfig = castTo(stack.at(-1));
|
|
71
71
|
const parent: SchemaFieldConfig = castTo(stack.at(-2));
|
|
72
|
-
return `${field.
|
|
72
|
+
return `${field.class ? field.class.name.toString() : parent.name.toString()}.${field.name.toString()}`;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
const qryStr = dct.getWhereGroupingSQL(WhereType, qry);
|
|
@@ -81,7 +81,7 @@ export abstract class BaseSQLTest extends BaseModelSuite<SQLModelService> {
|
|
|
81
81
|
const dct = await this.dialect;
|
|
82
82
|
dct.resolveName = (stack: VisitStack[]) => {
|
|
83
83
|
const field: SchemaFieldConfig = castTo(stack.at(-1));
|
|
84
|
-
return `${field.
|
|
84
|
+
return `${field.class?.name}.${field.name.toString()}`;
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
const out = dct.getWhereGroupingSQL(User, {
|