@travetto/model-sql 7.0.0-rc.0 → 7.0.0-rc.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/package.json +7 -7
- package/src/connection/base.ts +30 -30
- package/src/connection/decorator.ts +17 -17
- package/src/dialect/base.ts +155 -150
- package/src/service.ts +47 -46
- package/src/table-manager.ts +29 -29
- package/src/types.ts +1 -1
- package/src/util.ts +70 -70
package/src/service.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ModelType,
|
|
3
|
-
|
|
3
|
+
BulkOperation, BulkResponse, ModelCrudSupport, ModelStorageSupport, ModelBulkSupport,
|
|
4
4
|
NotFoundError, ModelRegistryIndex, ExistsError, OptionalId, ModelIdSource,
|
|
5
5
|
ModelExpiryUtil, ModelCrudUtil, ModelStorageUtil, ModelBulkUtil,
|
|
6
6
|
} from '@travetto/model';
|
|
@@ -72,11 +72,11 @@ export class SQLModelService implements
|
|
|
72
72
|
)
|
|
73
73
|
)).records : [];
|
|
74
74
|
|
|
75
|
-
const allIds = new Set(all.map(
|
|
75
|
+
const allIds = new Set(all.map(type => type.id));
|
|
76
76
|
|
|
77
|
-
for (const [
|
|
78
|
-
if (!allIds.has(
|
|
79
|
-
addedIds.set(idx,
|
|
77
|
+
for (const [id, idx] of toCheck.entries()) {
|
|
78
|
+
if (!allIds.has(id)) { // If not found
|
|
79
|
+
addedIds.set(idx, id);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -100,22 +100,22 @@ export class SQLModelService implements
|
|
|
100
100
|
|
|
101
101
|
async postConstruct(): Promise<void> {
|
|
102
102
|
if (this.#dialect) {
|
|
103
|
-
if (this.#dialect.
|
|
104
|
-
await this.#dialect.
|
|
103
|
+
if (this.#dialect.connection.init) {
|
|
104
|
+
await this.#dialect.connection.init();
|
|
105
105
|
}
|
|
106
|
-
this.idSource = ModelCrudUtil.uuidSource(this.#dialect.
|
|
106
|
+
this.idSource = ModelCrudUtil.uuidSource(this.#dialect.ID_LENGTH);
|
|
107
107
|
this.#manager = new TableManager(this.#context, this.#dialect);
|
|
108
108
|
await ModelStorageUtil.registerModelChangeListener(this);
|
|
109
109
|
ModelExpiryUtil.registerCull(this);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
get
|
|
114
|
-
return this.#dialect.
|
|
113
|
+
get connection(): Connection {
|
|
114
|
+
return this.#dialect.connection;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
async exportModel<T extends ModelType>(
|
|
118
|
-
return (await this.#manager.exportTables(
|
|
117
|
+
async exportModel<T extends ModelType>(cls: Class<T>): Promise<string> {
|
|
118
|
+
return (await this.#manager.exportTables(cls)).join('\n');
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
async changeSchema(cls: Class, change: SchemaChange): Promise<void> {
|
|
@@ -144,11 +144,11 @@ export class SQLModelService implements
|
|
|
144
144
|
for (const ins of this.#dialect.getAllInsertSQL(cls, prepped)) {
|
|
145
145
|
await this.#exec(ins);
|
|
146
146
|
}
|
|
147
|
-
} catch (
|
|
148
|
-
if (
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (error instanceof ExistsError) {
|
|
149
149
|
throw new ExistsError(cls, prepped.id);
|
|
150
150
|
} else {
|
|
151
|
-
throw
|
|
151
|
+
throw error;
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
return prepped;
|
|
@@ -166,9 +166,9 @@ export class SQLModelService implements
|
|
|
166
166
|
if (item.id) {
|
|
167
167
|
await this.#deleteRaw(cls, item.id, {}, false);
|
|
168
168
|
}
|
|
169
|
-
} catch (
|
|
170
|
-
if (!(
|
|
171
|
-
throw
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (!(error instanceof NotFoundError)) {
|
|
171
|
+
throw error;
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
return await this.create(cls, item);
|
|
@@ -203,7 +203,7 @@ export class SQLModelService implements
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
@Transactional()
|
|
206
|
-
async processBulk<T extends ModelType>(cls: Class<T>, operations:
|
|
206
|
+
async processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOperation<T>[]): Promise<BulkResponse> {
|
|
207
207
|
|
|
208
208
|
const { insertedIds, upsertedIds, existingUpsertedIds } = await ModelBulkUtil.preStore(cls, operations, this);
|
|
209
209
|
|
|
@@ -211,16 +211,17 @@ export class SQLModelService implements
|
|
|
211
211
|
|
|
212
212
|
await this.#checkUpsertedIds(cls,
|
|
213
213
|
addedIds,
|
|
214
|
-
new Map([...existingUpsertedIds.entries()].map(([
|
|
214
|
+
new Map([...existingUpsertedIds.entries()].map(([key, value]) => [value, key]))
|
|
215
215
|
);
|
|
216
216
|
|
|
217
|
-
const get = <K extends keyof
|
|
218
|
-
operations.map(
|
|
217
|
+
const get = <K extends keyof BulkOperation<T>>(key: K): Required<BulkOperation<T>>[K][] =>
|
|
218
|
+
operations.map(item => item[key]).filter((item): item is Required<BulkOperation<T>>[K] => !!item);
|
|
219
219
|
|
|
220
|
-
const getStatements = async (
|
|
221
|
-
(await SQLModelUtil.getInserts(cls, get(
|
|
220
|
+
const getStatements = async (key: keyof BulkOperation<T>): Promise<InsertWrapper[]> =>
|
|
221
|
+
(await SQLModelUtil.getInserts(cls, get(key))).filter(wrapper => !!wrapper.records.length);
|
|
222
222
|
|
|
223
|
-
const deletes = [{ stack: SQLModelUtil.classToStack(cls), ids: get('delete').map(
|
|
223
|
+
const deletes = [{ stack: SQLModelUtil.classToStack(cls), ids: get('delete').map(wrapper => wrapper.id) }]
|
|
224
|
+
.filter(wrapper => !!wrapper.ids.length);
|
|
224
225
|
|
|
225
226
|
const [inserts, upserts, updates] = await Promise.all([
|
|
226
227
|
getStatements('insert'),
|
|
@@ -242,19 +243,19 @@ export class SQLModelService implements
|
|
|
242
243
|
@Connected()
|
|
243
244
|
async query<T extends ModelType>(cls: Class<T>, query: PageableModelQuery<T>): Promise<T[]> {
|
|
244
245
|
await QueryVerifier.verify(cls, query);
|
|
245
|
-
const { records
|
|
246
|
+
const { records } = await this.#exec<T>(this.#dialect.getQuerySQL(cls, query, ModelQueryUtil.getWhereClause(cls, query.where)));
|
|
246
247
|
if (ModelRegistryIndex.has(cls)) {
|
|
247
|
-
await this.#dialect.fetchDependents(cls,
|
|
248
|
+
await this.#dialect.fetchDependents(cls, records, query && query.select);
|
|
248
249
|
}
|
|
249
250
|
|
|
250
|
-
const cleaned = SQLModelUtil.cleanResults<T>(this.#dialect,
|
|
251
|
-
return await Promise.all(cleaned.map(
|
|
251
|
+
const cleaned = SQLModelUtil.cleanResults<T>(this.#dialect, records);
|
|
252
|
+
return await Promise.all(cleaned.map(item => ModelCrudUtil.load(cls, item)));
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
@Connected()
|
|
255
256
|
async queryOne<T extends ModelType>(cls: Class<T>, builder: ModelQuery<T>, failOnMany = true): Promise<T> {
|
|
256
|
-
const
|
|
257
|
-
return ModelQueryUtil.verifyGetSingleCounts<T>(cls, failOnMany,
|
|
257
|
+
const results = await this.query<T>(cls, { ...builder, limit: failOnMany ? 2 : 1 });
|
|
258
|
+
return ModelQueryUtil.verifyGetSingleCounts<T>(cls, failOnMany, results, builder.where);
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
@Connected()
|
|
@@ -294,43 +295,43 @@ export class SQLModelService implements
|
|
|
294
295
|
@Connected()
|
|
295
296
|
async suggest<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
|
|
296
297
|
await QueryVerifier.verify(cls, query);
|
|
297
|
-
const
|
|
298
|
-
const results = await this.query<T>(cls,
|
|
298
|
+
const resolvedQuery = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
|
|
299
|
+
const results = await this.query<T>(cls, resolvedQuery);
|
|
299
300
|
return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, results, (a, b) => b, query?.limit);
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
@Connected()
|
|
303
304
|
async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
|
|
304
305
|
await QueryVerifier.verify(cls, query);
|
|
305
|
-
const
|
|
306
|
-
const results = await this.query(cls,
|
|
306
|
+
const resolvedQuery = ModelQuerySuggestUtil.getSuggestFieldQuery(cls, field, prefix, query);
|
|
307
|
+
const results = await this.query(cls, resolvedQuery);
|
|
307
308
|
|
|
308
309
|
const modelTypeField: ValidStringFields<ModelType> = castTo(field);
|
|
309
|
-
return ModelQuerySuggestUtil.combineSuggestResults(cls, modelTypeField, prefix, results,
|
|
310
|
+
return ModelQuerySuggestUtil.combineSuggestResults(cls, modelTypeField, prefix, results, result => result, query?.limit);
|
|
310
311
|
}
|
|
311
312
|
|
|
312
313
|
@Connected()
|
|
313
314
|
async facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]> {
|
|
314
315
|
await QueryVerifier.verify(cls, query);
|
|
315
|
-
const col = this.#dialect.
|
|
316
|
-
const ttl = this.#dialect.
|
|
317
|
-
const key = this.#dialect.
|
|
318
|
-
const
|
|
316
|
+
const col = this.#dialect.identifier(field);
|
|
317
|
+
const ttl = this.#dialect.identifier('count');
|
|
318
|
+
const key = this.#dialect.identifier('key');
|
|
319
|
+
const sql = [
|
|
319
320
|
`SELECT ${col} as ${key}, COUNT(${col}) as ${ttl}`,
|
|
320
321
|
this.#dialect.getFromSQL(cls),
|
|
321
322
|
];
|
|
322
|
-
|
|
323
|
+
sql.push(
|
|
323
324
|
this.#dialect.getWhereSQL(cls, ModelQueryUtil.getWhereClause(cls, query?.where))
|
|
324
325
|
);
|
|
325
|
-
|
|
326
|
+
sql.push(
|
|
326
327
|
`GROUP BY ${col}`,
|
|
327
328
|
`ORDER BY ${ttl} DESC`
|
|
328
329
|
);
|
|
329
330
|
|
|
330
|
-
const results = await this.#exec<{ key: string, count: number }>(
|
|
331
|
-
return results.records.map(
|
|
332
|
-
|
|
333
|
-
return
|
|
331
|
+
const results = await this.#exec<{ key: string, count: number }>(sql.join('\n'));
|
|
332
|
+
return results.records.map(result => {
|
|
333
|
+
result.count = DataUtil.coerceType(result.count, Number);
|
|
334
|
+
return result;
|
|
334
335
|
});
|
|
335
336
|
}
|
|
336
337
|
}
|
package/src/table-manager.ts
CHANGED
|
@@ -32,13 +32,13 @@ export class TableManager {
|
|
|
32
32
|
*/
|
|
33
33
|
async exportTables(cls: Class): Promise<string[]> {
|
|
34
34
|
const out: string[] = [];
|
|
35
|
-
for (const
|
|
36
|
-
out.push(
|
|
35
|
+
for (const command of this.#dialect.getCreateAllTablesSQL(cls)) {
|
|
36
|
+
out.push(command);
|
|
37
37
|
}
|
|
38
38
|
const indices = ModelRegistryIndex.getConfig(cls).indices;
|
|
39
39
|
if (indices) {
|
|
40
|
-
for (const
|
|
41
|
-
out.push(
|
|
40
|
+
for (const command of this.#dialect.getCreateAllIndicesSQL(cls, indices)) {
|
|
41
|
+
out.push(command);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
return out;
|
|
@@ -51,20 +51,20 @@ export class TableManager {
|
|
|
51
51
|
@Connected()
|
|
52
52
|
@Transactional()
|
|
53
53
|
async createTables(cls: Class): Promise<void> {
|
|
54
|
-
for (const
|
|
55
|
-
await this.#exec(
|
|
54
|
+
for (const command of this.#dialect.getCreateAllTablesSQL(cls)) {
|
|
55
|
+
await this.#exec(command);
|
|
56
56
|
}
|
|
57
57
|
const indices = ModelRegistryIndex.getConfig(cls).indices;
|
|
58
58
|
if (indices) {
|
|
59
|
-
for (const
|
|
59
|
+
for (const command of this.#dialect.getCreateAllIndicesSQL(cls, indices)) {
|
|
60
60
|
try {
|
|
61
|
-
await this.#exec(
|
|
62
|
-
} catch (
|
|
63
|
-
if (!(
|
|
64
|
-
throw
|
|
61
|
+
await this.#exec(command);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (!(error instanceof Error)) {
|
|
64
|
+
throw error;
|
|
65
65
|
}
|
|
66
|
-
if (!/\bexists|duplicate\b/i.test(
|
|
67
|
-
throw
|
|
66
|
+
if (!/\bexists|duplicate\b/i.test(error.message)) {
|
|
67
|
+
throw error;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -78,8 +78,8 @@ export class TableManager {
|
|
|
78
78
|
@Connected()
|
|
79
79
|
@Transactional()
|
|
80
80
|
async dropTables(cls: Class): Promise<void> {
|
|
81
|
-
for (const
|
|
82
|
-
await this.#exec(
|
|
81
|
+
for (const command of this.#dialect.getDropAllTablesSQL(cls)) {
|
|
82
|
+
await this.#exec(command);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -90,16 +90,16 @@ export class TableManager {
|
|
|
90
90
|
@Connected()
|
|
91
91
|
@Transactional()
|
|
92
92
|
async truncateTables(cls: Class): Promise<void> {
|
|
93
|
-
for (const
|
|
94
|
-
await this.#exec(
|
|
93
|
+
for (const command of this.#dialect.getTruncateAllTablesSQL(cls)) {
|
|
94
|
+
await this.#exec(command);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* Get a valid connection
|
|
100
100
|
*/
|
|
101
|
-
get
|
|
102
|
-
return this.#dialect.
|
|
101
|
+
get connection(): Connection {
|
|
102
|
+
return this.#dialect.connection;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
/**
|
|
@@ -112,20 +112,20 @@ export class TableManager {
|
|
|
112
112
|
try {
|
|
113
113
|
const rootStack = SQLModelUtil.classToStack(cls);
|
|
114
114
|
|
|
115
|
-
const changes = change.subs.reduce<Record<ChangeEvent<unknown>['type'], VisitStack[][]>>((
|
|
116
|
-
const path =
|
|
117
|
-
for (const
|
|
118
|
-
|
|
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
119
|
}
|
|
120
|
-
return
|
|
120
|
+
return result;
|
|
121
121
|
}, { added: [], changed: [], removing: [] });
|
|
122
122
|
|
|
123
|
-
await Promise.all(changes.added.map(
|
|
124
|
-
await Promise.all(changes.changed.map(
|
|
125
|
-
await Promise.all(changes.removing.map(
|
|
126
|
-
} catch (
|
|
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
127
|
// Failed to change
|
|
128
|
-
console.error('Unable to change field', { error
|
|
128
|
+
console.error('Unable to change field', { error });
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
package/src/types.ts
CHANGED
package/src/util.ts
CHANGED
|
@@ -8,9 +8,9 @@ import { TableSymbol, VisitStack } from './types.ts';
|
|
|
8
8
|
|
|
9
9
|
type FieldCacheEntry = {
|
|
10
10
|
local: SchemaFieldConfig[];
|
|
11
|
-
localMap: Record<string
|
|
11
|
+
localMap: Record<string, SchemaFieldConfig>;
|
|
12
12
|
foreign: SchemaFieldConfig[];
|
|
13
|
-
foreignMap: Record<string
|
|
13
|
+
foreignMap: Record<string, SchemaFieldConfig>;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -30,22 +30,22 @@ export class SQLModelUtil {
|
|
|
30
30
|
/**
|
|
31
31
|
* Clean results from db, by dropping internal fields
|
|
32
32
|
*/
|
|
33
|
-
static cleanResults<T, U = T>(
|
|
34
|
-
static cleanResults<T, U = T>(
|
|
35
|
-
static cleanResults<T, U = T>(
|
|
36
|
-
if (Array.isArray(
|
|
37
|
-
return
|
|
38
|
-
} else if (!DataUtil.isSimpleValue(
|
|
39
|
-
for (const
|
|
40
|
-
if (
|
|
41
|
-
delete
|
|
33
|
+
static cleanResults<T, U = T>(state: DialectState, item: T[]): U[];
|
|
34
|
+
static cleanResults<T, U = T>(state: DialectState, item: T): U;
|
|
35
|
+
static cleanResults<T, U = T>(state: DialectState, item: T | T[]): U | U[] {
|
|
36
|
+
if (Array.isArray(item)) {
|
|
37
|
+
return item.filter(value => value !== null && value !== undefined).map(value => this.cleanResults(state, value));
|
|
38
|
+
} else if (!DataUtil.isSimpleValue(item)) {
|
|
39
|
+
for (const key of TypedObject.keys(item)) {
|
|
40
|
+
if (item[key] === null || item[key] === undefined || key === state.parentPathField.name || key === state.pathField.name || key === state.idxField.name) {
|
|
41
|
+
delete item[key];
|
|
42
42
|
} else {
|
|
43
|
-
|
|
43
|
+
item[key] = this.cleanResults(state, item[key]);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
return castTo({ ...
|
|
46
|
+
return castTo({ ...item });
|
|
47
47
|
} else {
|
|
48
|
-
return castTo(
|
|
48
|
+
return castTo(item);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -54,13 +54,13 @@ export class SQLModelUtil {
|
|
|
54
54
|
*/
|
|
55
55
|
static getFieldsByLocation(stack: VisitStack[]): FieldCacheEntry {
|
|
56
56
|
const top = stack.at(-1)!;
|
|
57
|
-
const
|
|
57
|
+
const config = SchemaRegistryIndex.getOptional(top.type)?.get();
|
|
58
58
|
|
|
59
|
-
if (
|
|
60
|
-
return this.#schemaFieldsCache.get(
|
|
59
|
+
if (config && this.#schemaFieldsCache.has(config.class)) {
|
|
60
|
+
return this.#schemaFieldsCache.get(config.class)!;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
if (!
|
|
63
|
+
if (!config) { // If a simple type, it is it's own field
|
|
64
64
|
const field: SchemaFieldConfig = castTo({ ...top });
|
|
65
65
|
return {
|
|
66
66
|
local: [field], localMap: { [field.name]: field },
|
|
@@ -68,15 +68,15 @@ export class SQLModelUtil {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const hasModel = ModelRegistryIndex.has(
|
|
72
|
-
const fields = Object.values(
|
|
71
|
+
const hasModel = ModelRegistryIndex.has(config.class)!;
|
|
72
|
+
const fields = Object.values(config.fields).map(field => ({ ...field }));
|
|
73
73
|
|
|
74
74
|
// Polymorphic
|
|
75
|
-
if (hasModel &&
|
|
76
|
-
const fieldMap = new Set(fields.map(
|
|
77
|
-
for (const type of SchemaRegistryIndex.getDiscriminatedClasses(
|
|
78
|
-
const
|
|
79
|
-
for (const [fieldName, field] of Object.entries<SchemaFieldConfig>(
|
|
75
|
+
if (hasModel && config.discriminatedBase) {
|
|
76
|
+
const fieldMap = new Set(fields.map(field => field.name));
|
|
77
|
+
for (const type of SchemaRegistryIndex.getDiscriminatedClasses(config.class)) {
|
|
78
|
+
const typeConfig = SchemaRegistryIndex.getConfig(type);
|
|
79
|
+
for (const [fieldName, field] of Object.entries<SchemaFieldConfig>(typeConfig.fields)) {
|
|
80
80
|
if (!fieldMap.has(fieldName)) {
|
|
81
81
|
fieldMap.add(fieldName);
|
|
82
82
|
fields.push({ ...field, required: { active: false } });
|
|
@@ -85,19 +85,19 @@ export class SQLModelUtil {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const
|
|
88
|
+
const entry: FieldCacheEntry = {
|
|
89
89
|
localMap: {},
|
|
90
90
|
foreignMap: {},
|
|
91
|
-
local: fields.filter(
|
|
92
|
-
foreign: fields.filter(
|
|
91
|
+
local: fields.filter(field => !SchemaRegistryIndex.has(field.type) && !field.array),
|
|
92
|
+
foreign: fields.filter(field => SchemaRegistryIndex.has(field.type) || field.array)
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
entry.local.reduce((map, field) => (map[field.name] = field) && map, entry.localMap);
|
|
96
|
+
entry.foreign.reduce((map, field) => (map[field.name] = field) && map, entry.foreignMap);
|
|
97
97
|
|
|
98
|
-
this.#schemaFieldsCache.set(
|
|
98
|
+
this.#schemaFieldsCache.set(config.class, entry);
|
|
99
99
|
|
|
100
|
-
return
|
|
100
|
+
return entry;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
@@ -160,7 +160,7 @@ export class SQLModelUtil {
|
|
|
160
160
|
* Process a schema instance by visiting it synchronously. This is synchronous to prevent concurrent calls from breaking
|
|
161
161
|
*/
|
|
162
162
|
static visitSchemaInstance<T extends ModelType>(cls: Class<T>, instance: T | OptionalId<T>, handler: VisitHandler<unknown, VisitInstanceNode<unknown>>): void {
|
|
163
|
-
const
|
|
163
|
+
const pathStack: unknown[] = [instance];
|
|
164
164
|
this.visitSchemaSync(SchemaRegistryIndex.getConfig(cls), {
|
|
165
165
|
onRoot: (config) => {
|
|
166
166
|
const { path } = config;
|
|
@@ -170,24 +170,24 @@ export class SQLModelUtil {
|
|
|
170
170
|
},
|
|
171
171
|
onSub: (config) => {
|
|
172
172
|
const { config: field } = config;
|
|
173
|
-
const
|
|
173
|
+
const topObject: Record<string, unknown> = castTo(pathStack.at(-1));
|
|
174
174
|
const top = config.path.at(-1)!;
|
|
175
175
|
|
|
176
|
-
if (field.name in
|
|
177
|
-
const
|
|
178
|
-
const values = Array.isArray(
|
|
176
|
+
if (field.name in topObject) {
|
|
177
|
+
const valuesInput = topObject[field.name];
|
|
178
|
+
const values = Array.isArray(valuesInput) ? valuesInput : [valuesInput];
|
|
179
179
|
|
|
180
180
|
let i = 0;
|
|
181
|
-
for (const
|
|
181
|
+
for (const value of values) {
|
|
182
182
|
try {
|
|
183
|
-
|
|
183
|
+
pathStack.push(value);
|
|
184
184
|
config.path[config.path.length - 1] = { ...top, index: i++ };
|
|
185
|
-
handler.onSub({ ...config, value
|
|
185
|
+
handler.onSub({ ...config, value });
|
|
186
186
|
if (!field.array) {
|
|
187
187
|
config.descend();
|
|
188
188
|
}
|
|
189
189
|
} finally {
|
|
190
|
-
|
|
190
|
+
pathStack.pop();
|
|
191
191
|
}
|
|
192
192
|
i += 1;
|
|
193
193
|
}
|
|
@@ -198,8 +198,8 @@ export class SQLModelUtil {
|
|
|
198
198
|
},
|
|
199
199
|
onSimple: (config) => {
|
|
200
200
|
const { config: field } = config;
|
|
201
|
-
const
|
|
202
|
-
const value =
|
|
201
|
+
const topObject: Record<string, unknown> = castTo(pathStack.at(-1));
|
|
202
|
+
const value = topObject[field.name];
|
|
203
203
|
return handler.onSimple({ ...config, value });
|
|
204
204
|
}
|
|
205
205
|
});
|
|
@@ -217,19 +217,19 @@ export class SQLModelUtil {
|
|
|
217
217
|
|
|
218
218
|
let toGet = new Set<string>();
|
|
219
219
|
|
|
220
|
-
for (const [
|
|
221
|
-
if (typeof
|
|
222
|
-
if (!
|
|
220
|
+
for (const [key, value] of TypedObject.entries(select)) {
|
|
221
|
+
if (typeof key === 'string' && !DataUtil.isPlainObject(select[key]) && localMap[key]) {
|
|
222
|
+
if (!value) {
|
|
223
223
|
if (toGet.size === 0) {
|
|
224
224
|
toGet = new Set(Object.keys(SchemaRegistryIndex.getConfig(cls).fields));
|
|
225
225
|
}
|
|
226
|
-
toGet.delete(
|
|
226
|
+
toGet.delete(key);
|
|
227
227
|
} else {
|
|
228
|
-
toGet.add(
|
|
228
|
+
toGet.add(key);
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
-
return [...toGet].map(
|
|
232
|
+
return [...toGet].map(field => localMap[field]);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
@@ -242,15 +242,15 @@ export class SQLModelUtil {
|
|
|
242
242
|
let found: OrderBy | undefined;
|
|
243
243
|
while (!found) {
|
|
244
244
|
const key = Object.keys(cl)[0];
|
|
245
|
-
const
|
|
245
|
+
const value = cl[key];
|
|
246
246
|
const field = { ...schema.fields[key] };
|
|
247
|
-
if (DataUtil.isPrimitive(
|
|
247
|
+
if (DataUtil.isPrimitive(value)) {
|
|
248
248
|
stack.push(field);
|
|
249
|
-
found = { stack, asc:
|
|
249
|
+
found = { stack, asc: value === 1 };
|
|
250
250
|
} else {
|
|
251
251
|
stack.push(field);
|
|
252
252
|
schema = SchemaRegistryIndex.getConfig(field.type);
|
|
253
|
-
cl = castTo(
|
|
253
|
+
cl = castTo(value);
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
return found;
|
|
@@ -260,30 +260,30 @@ export class SQLModelUtil {
|
|
|
260
260
|
/**
|
|
261
261
|
* Find all dependent fields via child tables
|
|
262
262
|
*/
|
|
263
|
-
static collectDependents<T>(
|
|
263
|
+
static collectDependents<T>(state: DialectState, parent: unknown, items: T[], field?: SchemaFieldConfig): Record<string, T> {
|
|
264
264
|
if (field) {
|
|
265
265
|
const isSimple = SchemaRegistryIndex.has(field.type);
|
|
266
|
-
for (const
|
|
267
|
-
const parentKey: string = castTo(
|
|
266
|
+
for (const item of items) {
|
|
267
|
+
const parentKey: string = castTo(item[castKey<T>(state.parentPathField.name)]);
|
|
268
268
|
const root = castTo<Record<string, Record<string, unknown>>>(parent)[parentKey];
|
|
269
269
|
const fieldKey = castKey<(typeof root) | T>(field.name);
|
|
270
270
|
if (field.array) {
|
|
271
271
|
if (!root[fieldKey]) {
|
|
272
|
-
root[fieldKey] = [isSimple ?
|
|
272
|
+
root[fieldKey] = [isSimple ? item : item[fieldKey]];
|
|
273
273
|
} else if (Array.isArray(root[fieldKey])) {
|
|
274
|
-
root[fieldKey].push(isSimple ?
|
|
274
|
+
root[fieldKey].push(isSimple ? item : item[fieldKey]);
|
|
275
275
|
}
|
|
276
276
|
} else {
|
|
277
|
-
root[fieldKey] = isSimple ?
|
|
277
|
+
root[fieldKey] = isSimple ? item : item[fieldKey];
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
const mapping: Record<string, T> = {};
|
|
283
|
-
for (const
|
|
284
|
-
const key =
|
|
283
|
+
for (const item of items) {
|
|
284
|
+
const key = item[castKey<T>(state.pathField.name)];
|
|
285
285
|
if (typeof key === 'string') {
|
|
286
|
-
mapping[key] =
|
|
286
|
+
mapping[key] = item;
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
return mapping;
|
|
@@ -295,7 +295,7 @@ export class SQLModelUtil {
|
|
|
295
295
|
static buildTable(list: VisitStack[]): string {
|
|
296
296
|
const top = list.at(-1)!;
|
|
297
297
|
if (!top[TableSymbol]) {
|
|
298
|
-
top[TableSymbol] = list.map((
|
|
298
|
+
top[TableSymbol] = list.map((item, i) => i === 0 ? ModelRegistryIndex.getStoreName(item.type) : item.name).join('_');
|
|
299
299
|
}
|
|
300
300
|
return top[TableSymbol]!;
|
|
301
301
|
}
|
|
@@ -304,22 +304,22 @@ export class SQLModelUtil {
|
|
|
304
304
|
* Build property path for a table/field given the current stack
|
|
305
305
|
*/
|
|
306
306
|
static buildPath(list: VisitStack[]): string {
|
|
307
|
-
return list.map((
|
|
307
|
+
return list.map((item) => `${item.name}${item.index ? `[${item.index}]` : ''}`).join('.');
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
/**
|
|
311
311
|
* Get insert statements for a given class, and its child tables
|
|
312
312
|
*/
|
|
313
|
-
static async getInserts<T extends ModelType>(cls: Class<T>,
|
|
314
|
-
const
|
|
313
|
+
static async getInserts<T extends ModelType>(cls: Class<T>, items: (T | OptionalId<T>)[]): Promise<InsertWrapper[]> {
|
|
314
|
+
const wrappers: Record<string, InsertWrapper> = {};
|
|
315
315
|
|
|
316
316
|
const track = (stack: VisitStack[], value: unknown): void => {
|
|
317
317
|
const key = this.buildTable(stack);
|
|
318
|
-
(
|
|
318
|
+
(wrappers[key] ??= { stack, records: [] }).records.push({ stack, value });
|
|
319
319
|
};
|
|
320
320
|
|
|
321
|
-
const all =
|
|
322
|
-
this.visitSchemaInstance(cls,
|
|
321
|
+
const all = items.map(item =>
|
|
322
|
+
this.visitSchemaInstance(cls, item, {
|
|
323
323
|
onRoot: ({ path, value }) => track(path, value),
|
|
324
324
|
onSub: ({ path, value }) => track(path, value),
|
|
325
325
|
onSimple: ({ path, value }) => track(path, value)
|
|
@@ -327,7 +327,7 @@ export class SQLModelUtil {
|
|
|
327
327
|
|
|
328
328
|
await Promise.all(all);
|
|
329
329
|
|
|
330
|
-
const result = [...Object.values(
|
|
330
|
+
const result = [...Object.values(wrappers)].toSorted((a, b) => a.stack.length - b.stack.length);
|
|
331
331
|
return result;
|
|
332
332
|
}
|
|
333
333
|
}
|