@travetto/model-sql 8.0.0-alpha.24 → 8.0.0-alpha.25
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/__index__.ts +2 -2
- package/package.json +8 -8
- package/src/config.ts +1 -1
- package/src/connection/base.ts +8 -7
- package/src/connection/decorator.ts +5 -10
- package/src/dialect/base.ts +152 -112
- package/src/internal/types.ts +2 -2
- package/src/service.ts +139 -116
- package/src/table-manager.ts +20 -12
- package/src/types.ts +1 -1
- package/src/util.ts +43 -22
- package/support/test/query.ts +8 -5
package/src/service.ts
CHANGED
|
@@ -1,31 +1,64 @@
|
|
|
1
|
+
import type { AsyncContext } from '@travetto/context';
|
|
2
|
+
import { Injectable, PostConstruct } from '@travetto/di';
|
|
1
3
|
import {
|
|
2
|
-
type
|
|
3
|
-
type
|
|
4
|
-
|
|
4
|
+
type BulkOperation,
|
|
5
|
+
type BulkResponse,
|
|
6
|
+
ExistsError,
|
|
7
|
+
type ModelBulkSupport,
|
|
8
|
+
ModelBulkUtil,
|
|
9
|
+
type ModelCrudSupport,
|
|
10
|
+
ModelCrudUtil,
|
|
11
|
+
ModelExpiryUtil,
|
|
12
|
+
type ModelIdSource,
|
|
5
13
|
type ModelListOptions,
|
|
14
|
+
ModelRegistryIndex,
|
|
15
|
+
type ModelStorageSupport,
|
|
16
|
+
ModelStorageUtil,
|
|
17
|
+
type ModelType,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
type OptionalId
|
|
6
20
|
} from '@travetto/model';
|
|
7
21
|
import {
|
|
8
|
-
type
|
|
9
|
-
type
|
|
10
|
-
type
|
|
22
|
+
type FullKeyedIndexBody,
|
|
23
|
+
type FullKeyedIndexWithPartialBody,
|
|
24
|
+
type KeyedIndexBody,
|
|
25
|
+
type KeyedIndexSelection,
|
|
26
|
+
ModelIndexedComputedIndex,
|
|
27
|
+
type ModelIndexedSearchOptions,
|
|
28
|
+
type ModelIndexedSupport,
|
|
29
|
+
ModelIndexedUtil,
|
|
30
|
+
type ModelPageOptions,
|
|
31
|
+
type ModelPageResult,
|
|
32
|
+
type SingleItemIndex,
|
|
33
|
+
type SortedIndex,
|
|
34
|
+
type SortedIndexSelection,
|
|
35
|
+
type SortedIndexSelectionType
|
|
11
36
|
} from '@travetto/model-indexed';
|
|
12
|
-
import { castTo, type Class, JSONUtil } from '@travetto/runtime';
|
|
13
|
-
import { DataUtil } from '@travetto/schema';
|
|
14
|
-
import type { AsyncContext } from '@travetto/context';
|
|
15
|
-
import { Injectable, PostConstruct } from '@travetto/di';
|
|
16
37
|
import {
|
|
17
|
-
type ModelQuery,
|
|
18
|
-
type
|
|
19
|
-
|
|
38
|
+
type ModelQuery,
|
|
39
|
+
type ModelQueryCrudSupport,
|
|
40
|
+
ModelQueryCrudUtil,
|
|
41
|
+
type ModelQueryFacet,
|
|
42
|
+
type ModelQueryFacetSupport,
|
|
43
|
+
type ModelQuerySuggestSupport,
|
|
44
|
+
ModelQuerySuggestUtil,
|
|
45
|
+
type ModelQuerySupport,
|
|
46
|
+
ModelQueryUtil,
|
|
47
|
+
type PageableModelQuery,
|
|
48
|
+
QueryVerifier,
|
|
49
|
+
type ValidStringFields,
|
|
50
|
+
type WhereClauseRaw
|
|
20
51
|
} from '@travetto/model-query';
|
|
52
|
+
import { type Class, castTo, JSONUtil } from '@travetto/runtime';
|
|
53
|
+
import { DataUtil } from '@travetto/schema';
|
|
21
54
|
|
|
22
55
|
import type { SQLModelConfig } from './config.ts';
|
|
56
|
+
import type { Connection } from './connection/base.ts';
|
|
23
57
|
import { Connected, ConnectedIterator, Transactional } from './connection/decorator.ts';
|
|
24
|
-
import { SQLModelUtil } from './util.ts';
|
|
25
58
|
import type { SQLDialect } from './dialect/base.ts';
|
|
26
|
-
import { TableManager } from './table-manager.ts';
|
|
27
|
-
import type { Connection } from './connection/base.ts';
|
|
28
59
|
import type { InsertWrapper } from './internal/types.ts';
|
|
60
|
+
import { TableManager } from './table-manager.ts';
|
|
61
|
+
import { SQLModelUtil } from './util.ts';
|
|
29
62
|
|
|
30
63
|
/**
|
|
31
64
|
* Core for SQL Model Source. Should not have any direct queries,
|
|
@@ -33,13 +66,17 @@ import type { InsertWrapper } from './internal/types.ts';
|
|
|
33
66
|
* as needed.
|
|
34
67
|
*/
|
|
35
68
|
@Injectable()
|
|
36
|
-
export class SQLModelService
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
export class SQLModelService
|
|
70
|
+
implements
|
|
71
|
+
ModelCrudSupport,
|
|
72
|
+
ModelStorageSupport,
|
|
73
|
+
ModelBulkSupport,
|
|
74
|
+
ModelQuerySupport,
|
|
75
|
+
ModelQueryCrudSupport,
|
|
76
|
+
ModelQueryFacetSupport,
|
|
77
|
+
ModelIndexedSupport,
|
|
78
|
+
ModelQuerySuggestSupport
|
|
79
|
+
{
|
|
43
80
|
#manager: TableManager;
|
|
44
81
|
#context: AsyncContext;
|
|
45
82
|
#dialect: SQLDialect;
|
|
@@ -51,11 +88,7 @@ export class SQLModelService implements
|
|
|
51
88
|
return this.#dialect;
|
|
52
89
|
}
|
|
53
90
|
|
|
54
|
-
constructor(
|
|
55
|
-
context: AsyncContext,
|
|
56
|
-
config: SQLModelConfig,
|
|
57
|
-
dialect: SQLDialect
|
|
58
|
-
) {
|
|
91
|
+
constructor(context: AsyncContext, config: SQLModelConfig, dialect: SQLDialect) {
|
|
59
92
|
this.#context = context;
|
|
60
93
|
this.#dialect = dialect;
|
|
61
94
|
this.config = config;
|
|
@@ -70,17 +103,19 @@ export class SQLModelService implements
|
|
|
70
103
|
toCheck: Map<string, number>
|
|
71
104
|
): Promise<Map<number, string>> {
|
|
72
105
|
// Get all upsert ids
|
|
73
|
-
const all = toCheck.size
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
106
|
+
const all = toCheck.size
|
|
107
|
+
? (
|
|
108
|
+
await this.#exec<ModelType>(
|
|
109
|
+
this.#dialect.getSelectRowsByIdsSQL(SQLModelUtil.classToStack(cls), [...toCheck.keys()], [this.#dialect.idField])
|
|
110
|
+
)
|
|
111
|
+
).records
|
|
112
|
+
: [];
|
|
79
113
|
|
|
80
114
|
const allIds = new Set(all.map(type => type.id));
|
|
81
115
|
|
|
82
116
|
for (const [id, idx] of toCheck.entries()) {
|
|
83
|
-
if (!allIds.has(id)) {
|
|
117
|
+
if (!allIds.has(id)) {
|
|
118
|
+
// If not found
|
|
84
119
|
addedIds.set(idx, id);
|
|
85
120
|
}
|
|
86
121
|
}
|
|
@@ -88,12 +123,12 @@ export class SQLModelService implements
|
|
|
88
123
|
return addedIds;
|
|
89
124
|
}
|
|
90
125
|
|
|
91
|
-
#exec<T = unknown>(sql: string): Promise<{ records: T[]
|
|
126
|
+
#exec<T = unknown>(sql: string): Promise<{ records: T[]; count: number }> {
|
|
92
127
|
return this.#dialect.executeSQL<T>(sql);
|
|
93
128
|
}
|
|
94
129
|
|
|
95
130
|
async #deleteRaw<T extends ModelType>(cls: Class<T>, id: string, where?: WhereClauseRaw<T>, checkExpiry = true): Promise<void> {
|
|
96
|
-
castTo<WhereClauseRaw<ModelType>>(where ??= {}).id = id;
|
|
131
|
+
castTo<WhereClauseRaw<ModelType>>((where ??= {})).id = id;
|
|
97
132
|
|
|
98
133
|
const count = await this.#dialect.deleteAndGetCount<ModelType>(cls, {
|
|
99
134
|
where: ModelQueryUtil.getWhereClause(cls, where, checkExpiry)
|
|
@@ -103,17 +138,17 @@ export class SQLModelService implements
|
|
|
103
138
|
}
|
|
104
139
|
}
|
|
105
140
|
|
|
106
|
-
async
|
|
141
|
+
async *#scanTable<T extends ModelType>(
|
|
107
142
|
cls: Class<T>,
|
|
108
143
|
buildQuery: () => PageableModelQuery<T>,
|
|
109
144
|
options?: ModelListOptions & ModelPageOptions<number>
|
|
110
|
-
): AsyncIterable<{ items: T[]
|
|
145
|
+
): AsyncIterable<{ items: T[]; nextOffset?: number }> {
|
|
111
146
|
const batchSize = options?.batchSizeHint ?? 100;
|
|
112
147
|
const maxCount = options?.limit ?? Number.MAX_SAFE_INTEGER;
|
|
113
148
|
let offset = options?.offset ?? 0;
|
|
114
149
|
let lastOffset = -1;
|
|
115
150
|
let produced = 0;
|
|
116
|
-
while (offset !== lastOffset && produced < maxCount && !
|
|
151
|
+
while (offset !== lastOffset && produced < maxCount && !options?.abort?.aborted) {
|
|
117
152
|
const limit = Math.min(batchSize, maxCount - produced);
|
|
118
153
|
lastOffset = offset;
|
|
119
154
|
const items = await this.query<T>(cls, {
|
|
@@ -158,8 +193,8 @@ export class SQLModelService implements
|
|
|
158
193
|
await this.#manager.truncateTables(cls);
|
|
159
194
|
}
|
|
160
195
|
|
|
161
|
-
async createStorage(): Promise<void> {
|
|
162
|
-
async deleteStorage(): Promise<void> {
|
|
196
|
+
async createStorage(): Promise<void> {}
|
|
197
|
+
async deleteStorage(): Promise<void> {}
|
|
163
198
|
|
|
164
199
|
@Transactional()
|
|
165
200
|
async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
|
|
@@ -215,7 +250,7 @@ export class SQLModelService implements
|
|
|
215
250
|
}
|
|
216
251
|
|
|
217
252
|
@ConnectedIterator()
|
|
218
|
-
async *
|
|
253
|
+
async *list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
|
|
219
254
|
for await (const { items } of this.#scanTable(cls, () => ({}), options)) {
|
|
220
255
|
yield items;
|
|
221
256
|
}
|
|
@@ -228,15 +263,11 @@ export class SQLModelService implements
|
|
|
228
263
|
|
|
229
264
|
@Transactional()
|
|
230
265
|
async processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOperation<T>[]): Promise<BulkResponse> {
|
|
231
|
-
|
|
232
266
|
const { insertedIds, upsertedIds, existingUpsertedIds } = await ModelBulkUtil.preStore(cls, operations, this);
|
|
233
267
|
|
|
234
268
|
const addedIds = new Map([...insertedIds.entries(), ...upsertedIds.entries()]);
|
|
235
269
|
|
|
236
|
-
await this.#checkUpsertedIds(cls,
|
|
237
|
-
addedIds,
|
|
238
|
-
new Map([...existingUpsertedIds.entries()].map(([key, value]) => [value, key]))
|
|
239
|
-
);
|
|
270
|
+
await this.#checkUpsertedIds(cls, addedIds, new Map([...existingUpsertedIds.entries()].map(([key, value]) => [value, key])));
|
|
240
271
|
|
|
241
272
|
const get = <K extends keyof BulkOperation<T>>(key: K): Required<BulkOperation<T>>[K][] =>
|
|
242
273
|
operations.map(item => item[key]).filter((item): item is Required<BulkOperation<T>>[K] => !!item);
|
|
@@ -244,14 +275,11 @@ export class SQLModelService implements
|
|
|
244
275
|
const getStatements = async (key: keyof BulkOperation<T>): Promise<InsertWrapper[]> =>
|
|
245
276
|
(await SQLModelUtil.getInserts(cls, get(key))).filter(wrapper => !!wrapper.records.length);
|
|
246
277
|
|
|
247
|
-
const deletes = [{ stack: SQLModelUtil.classToStack(cls), ids: get('delete').map(wrapper => wrapper.id) }]
|
|
248
|
-
|
|
278
|
+
const deletes = [{ stack: SQLModelUtil.classToStack(cls), ids: get('delete').map(wrapper => wrapper.id) }].filter(
|
|
279
|
+
wrapper => !!wrapper.ids.length
|
|
280
|
+
);
|
|
249
281
|
|
|
250
|
-
const [inserts, upserts, updates] = await Promise.all([
|
|
251
|
-
getStatements('insert'),
|
|
252
|
-
getStatements('upsert'),
|
|
253
|
-
getStatements('update')
|
|
254
|
-
]);
|
|
282
|
+
const [inserts, upserts, updates] = await Promise.all([getStatements('insert'), getStatements('upsert'), getStatements('update')]);
|
|
255
283
|
|
|
256
284
|
const result = await this.#dialect.bulkProcess(deletes, inserts, upserts, updates);
|
|
257
285
|
result.insertedIds = addedIds;
|
|
@@ -269,7 +297,7 @@ export class SQLModelService implements
|
|
|
269
297
|
await QueryVerifier.verify(cls, query);
|
|
270
298
|
const { records } = await this.#exec<T>(this.#dialect.getQuerySQL(cls, query, ModelQueryUtil.getWhereClause(cls, query.where)));
|
|
271
299
|
if (ModelRegistryIndex.has(cls)) {
|
|
272
|
-
await this.#dialect.fetchDependents(cls, records, query
|
|
300
|
+
await this.#dialect.fetchDependents(cls, records, query?.select);
|
|
273
301
|
}
|
|
274
302
|
|
|
275
303
|
const cleaned = SQLModelUtil.cleanResults<T>(this.#dialect, records);
|
|
@@ -303,7 +331,9 @@ export class SQLModelService implements
|
|
|
303
331
|
async updatePartialByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, data: Partial<T>): Promise<number> {
|
|
304
332
|
await QueryVerifier.verify(cls, query);
|
|
305
333
|
const item = await ModelCrudUtil.prePartialUpdate(cls, data);
|
|
306
|
-
const { count } = await this.#exec(
|
|
334
|
+
const { count } = await this.#exec(
|
|
335
|
+
this.#dialect.getUpdateSQL(SQLModelUtil.classToStack(cls), item, ModelQueryUtil.getWhereClause(cls, query.where))
|
|
336
|
+
);
|
|
307
337
|
return count;
|
|
308
338
|
}
|
|
309
339
|
|
|
@@ -311,12 +341,19 @@ export class SQLModelService implements
|
|
|
311
341
|
@Transactional()
|
|
312
342
|
async deleteByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number> {
|
|
313
343
|
await QueryVerifier.verify(cls, query);
|
|
314
|
-
const { count } = await this.#exec(
|
|
344
|
+
const { count } = await this.#exec(
|
|
345
|
+
this.#dialect.getDeleteSQL(SQLModelUtil.classToStack(cls), ModelQueryUtil.getWhereClause(cls, query.where, false))
|
|
346
|
+
);
|
|
315
347
|
return count;
|
|
316
348
|
}
|
|
317
349
|
|
|
318
350
|
@Connected()
|
|
319
|
-
async suggestByQuery<T extends ModelType>(
|
|
351
|
+
async suggestByQuery<T extends ModelType>(
|
|
352
|
+
cls: Class<T>,
|
|
353
|
+
field: ValidStringFields<T>,
|
|
354
|
+
prefix?: string,
|
|
355
|
+
query?: PageableModelQuery<T>
|
|
356
|
+
): Promise<T[]> {
|
|
320
357
|
await QueryVerifier.verify(cls, query);
|
|
321
358
|
const resolvedQuery = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
|
|
322
359
|
const results = await this.query<T>(cls, resolvedQuery);
|
|
@@ -324,7 +361,12 @@ export class SQLModelService implements
|
|
|
324
361
|
}
|
|
325
362
|
|
|
326
363
|
@Connected()
|
|
327
|
-
async suggestValuesByQuery<T extends ModelType>(
|
|
364
|
+
async suggestValuesByQuery<T extends ModelType>(
|
|
365
|
+
cls: Class<T>,
|
|
366
|
+
field: ValidStringFields<T>,
|
|
367
|
+
prefix?: string,
|
|
368
|
+
query?: PageableModelQuery<T>
|
|
369
|
+
): Promise<string[]> {
|
|
328
370
|
await QueryVerifier.verify(cls, query);
|
|
329
371
|
const resolvedQuery = ModelQuerySuggestUtil.getSuggestFieldQuery(cls, field, prefix, query);
|
|
330
372
|
const results = await this.query(cls, resolvedQuery);
|
|
@@ -339,19 +381,11 @@ export class SQLModelService implements
|
|
|
339
381
|
const col = this.#dialect.identifier(field);
|
|
340
382
|
const ttl = this.#dialect.identifier('count');
|
|
341
383
|
const key = this.#dialect.identifier('key');
|
|
342
|
-
const sql = [
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
];
|
|
346
|
-
sql.push(
|
|
347
|
-
this.#dialect.getWhereSQL(cls, ModelQueryUtil.getWhereClause(cls, query?.where))
|
|
348
|
-
);
|
|
349
|
-
sql.push(
|
|
350
|
-
`GROUP BY ${col}`,
|
|
351
|
-
`ORDER BY ${ttl} DESC`
|
|
352
|
-
);
|
|
384
|
+
const sql = [`SELECT ${col} as ${key}, COUNT(${col}) as ${ttl}`, this.#dialect.getFromSQL(cls)];
|
|
385
|
+
sql.push(this.#dialect.getWhereSQL(cls, ModelQueryUtil.getWhereClause(cls, query?.where)));
|
|
386
|
+
sql.push(`GROUP BY ${col}`, `ORDER BY ${ttl} DESC`);
|
|
353
387
|
|
|
354
|
-
const results = await this.#exec<{ key: string
|
|
388
|
+
const results = await this.#exec<{ key: string; count: number }>(sql.join('\n'));
|
|
355
389
|
return results.records.map(result => {
|
|
356
390
|
result.count = DataUtil.coerceType(result.count, Number);
|
|
357
391
|
return result;
|
|
@@ -360,11 +394,11 @@ export class SQLModelService implements
|
|
|
360
394
|
|
|
361
395
|
// Indexed support
|
|
362
396
|
@Connected()
|
|
363
|
-
async getByIndex<
|
|
364
|
-
T
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
397
|
+
async getByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
398
|
+
cls: Class<T>,
|
|
399
|
+
idx: SingleItemIndex<T, K, S>,
|
|
400
|
+
body: FullKeyedIndexBody<T, K, S>
|
|
401
|
+
): Promise<T> {
|
|
368
402
|
const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
|
|
369
403
|
const results = await this.query(cls, castTo({ where: computed.project({ sort: true, includeId: true }) }));
|
|
370
404
|
if (results.length !== 1) {
|
|
@@ -375,11 +409,11 @@ export class SQLModelService implements
|
|
|
375
409
|
|
|
376
410
|
@Connected()
|
|
377
411
|
@Transactional()
|
|
378
|
-
async deleteByIndex<
|
|
379
|
-
T
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
412
|
+
async deleteByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
413
|
+
cls: Class<T>,
|
|
414
|
+
idx: SingleItemIndex<T, K, S>,
|
|
415
|
+
body: FullKeyedIndexBody<T, K, S>
|
|
416
|
+
): Promise<void> {
|
|
383
417
|
const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
|
|
384
418
|
const count = await this.deleteByQuery(cls, castTo({ where: computed.project({ sort: true, includeId: true }) }));
|
|
385
419
|
if (count === 0) {
|
|
@@ -389,41 +423,37 @@ export class SQLModelService implements
|
|
|
389
423
|
|
|
390
424
|
@Connected()
|
|
391
425
|
@Transactional()
|
|
392
|
-
upsertByIndex<
|
|
393
|
-
T
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
426
|
+
upsertByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
427
|
+
cls: Class<T>,
|
|
428
|
+
idx: SingleItemIndex<T, K, S>,
|
|
429
|
+
body: OptionalId<T>
|
|
430
|
+
): Promise<T> {
|
|
397
431
|
return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
|
|
398
432
|
}
|
|
399
433
|
|
|
400
434
|
@Connected()
|
|
401
435
|
@Transactional()
|
|
402
|
-
updateByIndex<
|
|
403
|
-
T
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
436
|
+
updateByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
437
|
+
cls: Class<T>,
|
|
438
|
+
idx: SingleItemIndex<T, K, S>,
|
|
439
|
+
body: T
|
|
440
|
+
): Promise<T> {
|
|
407
441
|
return ModelIndexedUtil.naiveUpdate(this, cls, idx, body);
|
|
408
442
|
}
|
|
409
443
|
|
|
410
444
|
@Connected()
|
|
411
445
|
@Transactional()
|
|
412
|
-
async updatePartialByIndex<
|
|
413
|
-
T
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
446
|
+
async updatePartialByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
447
|
+
cls: Class<T>,
|
|
448
|
+
idx: SingleItemIndex<T, K, S>,
|
|
449
|
+
body: FullKeyedIndexWithPartialBody<T, K, S>
|
|
450
|
+
): Promise<T> {
|
|
417
451
|
const item = await ModelCrudUtil.naivePartialUpdate(cls, () => this.getByIndex(cls, idx, castTo(body)), castTo(body));
|
|
418
452
|
return this.update(cls, item);
|
|
419
453
|
}
|
|
420
454
|
|
|
421
455
|
@Connected()
|
|
422
|
-
async pageByIndex<
|
|
423
|
-
T extends ModelType,
|
|
424
|
-
K extends KeyedIndexSelection<T>,
|
|
425
|
-
S extends SortedIndexSelection<T>
|
|
426
|
-
>(
|
|
456
|
+
async pageByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
427
457
|
cls: Class<T>,
|
|
428
458
|
idx: SortedIndex<T, K, S>,
|
|
429
459
|
body: KeyedIndexBody<T, K>,
|
|
@@ -434,7 +464,7 @@ export class SQLModelService implements
|
|
|
434
464
|
|
|
435
465
|
const baseQuery = castTo<ModelQuery<T>>({
|
|
436
466
|
where: computed.project(),
|
|
437
|
-
sort: idx.sortTemplate.map(part => ({ [part.path.join('.')]: part.value }))
|
|
467
|
+
sort: idx.sortTemplate.map(part => ({ [part.path.join('.')]: part.value }))
|
|
438
468
|
});
|
|
439
469
|
|
|
440
470
|
const items: T[] = [];
|
|
@@ -447,11 +477,7 @@ export class SQLModelService implements
|
|
|
447
477
|
}
|
|
448
478
|
|
|
449
479
|
@ConnectedIterator()
|
|
450
|
-
async *
|
|
451
|
-
T extends ModelType,
|
|
452
|
-
K extends KeyedIndexSelection<T>,
|
|
453
|
-
S extends SortedIndexSelection<T>
|
|
454
|
-
>(
|
|
480
|
+
async *listByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
455
481
|
cls: Class<T>,
|
|
456
482
|
idx: SortedIndex<T, K, S>,
|
|
457
483
|
body: KeyedIndexBody<T, K>,
|
|
@@ -460,7 +486,7 @@ export class SQLModelService implements
|
|
|
460
486
|
const computed = ModelIndexedComputedIndex.get(idx, body).validate();
|
|
461
487
|
const baseQuery = castTo<ModelQuery<T>>({
|
|
462
488
|
where: computed.project(),
|
|
463
|
-
sort: idx.sortTemplate.map(part => ({ [part.path.join('.')]: part.value }))
|
|
489
|
+
sort: idx.sortTemplate.map(part => ({ [part.path.join('.')]: part.value }))
|
|
464
490
|
});
|
|
465
491
|
for await (const { items } of this.#scanTable<T>(cls, () => baseQuery, options)) {
|
|
466
492
|
yield items;
|
|
@@ -479,17 +505,14 @@ export class SQLModelService implements
|
|
|
479
505
|
const nested: Record<string, unknown> = {};
|
|
480
506
|
let current = nested;
|
|
481
507
|
for (const key of idx.sortTemplate[0].path.slice(0, -1)) {
|
|
482
|
-
current =
|
|
508
|
+
current = current[key] = {};
|
|
483
509
|
}
|
|
484
510
|
current[idx.sortTemplate[0].path.at(-1)!] = { $regex: ModelIndexedUtil.getSuggestRegex(prefix) };
|
|
485
511
|
|
|
486
512
|
const baseQuery = castTo<ModelQuery<T>>({
|
|
487
513
|
where: {
|
|
488
|
-
$and: [
|
|
489
|
-
|
|
490
|
-
nested
|
|
491
|
-
]
|
|
492
|
-
},
|
|
514
|
+
$and: [computed.project(), nested]
|
|
515
|
+
}
|
|
493
516
|
});
|
|
494
517
|
|
|
495
518
|
for await (const batch of this.#scanTable<T>(cls, () => baseQuery, { limit: 10, ...options })) {
|
|
@@ -498,4 +521,4 @@ export class SQLModelService implements
|
|
|
498
521
|
|
|
499
522
|
return items;
|
|
500
523
|
}
|
|
501
|
-
}
|
|
524
|
+
}
|
package/src/table-manager.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { type AsyncContext, WithAsyncContext } from '@travetto/context';
|
|
2
2
|
import { ModelRegistryIndex } from '@travetto/model';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import type { Class } from '@travetto/runtime';
|
|
4
|
+
import { type SchemaFieldConfig, SchemaRegistryIndex } from '@travetto/schema';
|
|
5
5
|
|
|
6
|
+
import type { Connection } from './connection/base.ts';
|
|
6
7
|
import { Connected, Transactional } from './connection/decorator.ts';
|
|
7
8
|
import type { SQLDialect } from './dialect/base.ts';
|
|
8
|
-
import { SQLModelUtil } from './util.ts';
|
|
9
|
-
import type { Connection } from './connection/base.ts';
|
|
10
9
|
import type { VisitStack } from './types.ts';
|
|
10
|
+
import { SQLModelUtil } from './util.ts';
|
|
11
11
|
|
|
12
|
-
type UpsertStructure = { dropIndex: string[]
|
|
12
|
+
type UpsertStructure = { dropIndex: string[]; createIndex: string[]; table: string[] };
|
|
13
13
|
const isSimpleField = (input: VisitStack | undefined): input is SchemaFieldConfig =>
|
|
14
14
|
!!input && (!('type' in input) || (input.type && !SchemaRegistryIndex.has(input.type)));
|
|
15
15
|
|
|
@@ -17,7 +17,6 @@ const isSimpleField = (input: VisitStack | undefined): input is SchemaFieldConfi
|
|
|
17
17
|
* Manage creation/updating of all tables
|
|
18
18
|
*/
|
|
19
19
|
export class TableManager {
|
|
20
|
-
|
|
21
20
|
#dialect: SQLDialect;
|
|
22
21
|
context: AsyncContext;
|
|
23
22
|
|
|
@@ -26,7 +25,7 @@ export class TableManager {
|
|
|
26
25
|
this.context = context;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
#exec<T = unknown>(sql: string): Promise<{ records: T[]
|
|
28
|
+
#exec<T = unknown>(sql: string): Promise<{ records: T[]; count: number }> {
|
|
30
29
|
return this.#dialect.executeSQL<T>(sql);
|
|
31
30
|
}
|
|
32
31
|
|
|
@@ -70,7 +69,8 @@ export class TableManager {
|
|
|
70
69
|
// Manage fields
|
|
71
70
|
if (!existingFields.size) {
|
|
72
71
|
sqlCommands.table.push(this.#dialect.getCreateTableSQL(path));
|
|
73
|
-
} else {
|
|
72
|
+
} else {
|
|
73
|
+
// Existing
|
|
74
74
|
// Fields
|
|
75
75
|
const requestedFields = new Map(fields.map(field => [field.name, field]));
|
|
76
76
|
const top = path.at(-1);
|
|
@@ -121,9 +121,17 @@ export class TableManager {
|
|
|
121
121
|
|
|
122
122
|
const schema = SchemaRegistryIndex.getConfig(cls);
|
|
123
123
|
await SQLModelUtil.visitSchema(schema, {
|
|
124
|
-
onRoot: async ({ config, path, fields, descend }) => {
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
onRoot: async ({ config, path, fields, descend }) => {
|
|
125
|
+
await onVisit(config.class, fields, path);
|
|
126
|
+
return descend();
|
|
127
|
+
},
|
|
128
|
+
onSub: async ({ config, path, fields, descend }) => {
|
|
129
|
+
await onVisit(config.type, fields, path);
|
|
130
|
+
return descend();
|
|
131
|
+
},
|
|
132
|
+
onSimple: async ({ config, path, fields }) => {
|
|
133
|
+
await onVisit(config.type, fields, path);
|
|
134
|
+
}
|
|
127
135
|
});
|
|
128
136
|
return sqlCommands;
|
|
129
137
|
}
|
|
@@ -166,4 +174,4 @@ export class TableManager {
|
|
|
166
174
|
await this.#exec(command);
|
|
167
175
|
}
|
|
168
176
|
}
|
|
169
|
-
}
|
|
177
|
+
}
|
package/src/types.ts
CHANGED