@travetto/model-sql 8.0.0-alpha.2 → 8.0.0-alpha.20
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 +8 -7
- package/src/dialect/base.ts +70 -30
- package/src/service.ts +184 -12
- package/src/table-manager.ts +11 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-sql",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -28,14 +28,15 @@
|
|
|
28
28
|
"directory": "module/model-sql"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@travetto/config": "^8.0.0-alpha.
|
|
32
|
-
"@travetto/context": "^8.0.0-alpha.
|
|
33
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
34
|
-
"@travetto/model-
|
|
31
|
+
"@travetto/config": "^8.0.0-alpha.18",
|
|
32
|
+
"@travetto/context": "^8.0.0-alpha.17",
|
|
33
|
+
"@travetto/model": "^8.0.0-alpha.18",
|
|
34
|
+
"@travetto/model-indexed": "^8.0.0-alpha.20",
|
|
35
|
+
"@travetto/model-query": "^8.0.0-alpha.19"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
|
-
"@travetto/cli": "^8.0.0-alpha.
|
|
38
|
-
"@travetto/test": "^8.0.0-alpha.
|
|
38
|
+
"@travetto/cli": "^8.0.0-alpha.23",
|
|
39
|
+
"@travetto/test": "^8.0.0-alpha.17"
|
|
39
40
|
},
|
|
40
41
|
"peerDependenciesMeta": {
|
|
41
42
|
"@travetto/cli": {
|
package/src/dialect/base.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/* eslint-disable @stylistic/indent */
|
|
2
2
|
import { DataUtil, type SchemaFieldConfig, SchemaRegistryIndex, type Point } from '@travetto/schema';
|
|
3
3
|
import { type Class, RuntimeError, TypedObject, TimeUtil, castTo, castKey, toConcrete, JSONUtil } from '@travetto/runtime';
|
|
4
|
-
import { type SelectClause, type Query, type SortClause, type WhereClause, type RetainQueryPrimitiveFields, ModelQueryUtil } from '@travetto/model-query';
|
|
5
|
-
import type
|
|
4
|
+
import { type SelectClause, type Query, type SortClause, type WhereClause, type RetainQueryPrimitiveFields, ModelQueryUtil, isModelQueryIndex } from '@travetto/model-query';
|
|
5
|
+
import { IndexNotSupported, type BulkResponse, type IndexConfig, type ModelType } from '@travetto/model';
|
|
6
|
+
import { isModelIndexedIndex } from '@travetto/model-indexed';
|
|
6
7
|
|
|
7
8
|
import { SQLModelUtil } from '../util.ts';
|
|
8
9
|
import type { DeleteWrapper, InsertWrapper, DialectState } from '../internal/types.ts';
|
|
@@ -538,7 +539,9 @@ export abstract class SQLDialect implements DialectState {
|
|
|
538
539
|
items.push(`${sPath} ${SQL_OPS.$eq} ${this.resolveValue(field, top)}`);
|
|
539
540
|
}
|
|
540
541
|
}
|
|
541
|
-
if (items.length ===
|
|
542
|
+
if (items.length === 0) {
|
|
543
|
+
return 'TRUE';
|
|
544
|
+
} else if (items.length === 1) {
|
|
542
545
|
return items[0];
|
|
543
546
|
} else {
|
|
544
547
|
return `(${items.join(` ${SQL_OPS.$and} `)})`;
|
|
@@ -717,14 +720,14 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
717
720
|
/**
|
|
718
721
|
* Get all create indices need for a given class
|
|
719
722
|
*/
|
|
720
|
-
getCreateAllIndicesSQL<T extends ModelType>(cls: Class<T>, indices: IndexConfig
|
|
721
|
-
return indices.map(idx => this.getCreateIndexSQL(cls, idx));
|
|
723
|
+
getCreateAllIndicesSQL<T extends ModelType>(cls: Class<T>, indices: IndexConfig[]): string[] {
|
|
724
|
+
return indices.map(idx => this.getCreateIndexSQL(cls, idx)).filter((sql): sql is string => !!sql);
|
|
722
725
|
}
|
|
723
726
|
|
|
724
727
|
/**
|
|
725
728
|
* Get index name
|
|
726
729
|
*/
|
|
727
|
-
getIndexName<T extends ModelType>(cls: Class<T>, idx: IndexConfig
|
|
730
|
+
getIndexName<T extends ModelType>(cls: Class<T>, idx: IndexConfig): string {
|
|
728
731
|
const table = this.namespace(SQLModelUtil.classToStack(cls));
|
|
729
732
|
return ['idx', table, idx.name.toLowerCase().replaceAll('-', '_')].join('_');
|
|
730
733
|
}
|
|
@@ -732,26 +735,44 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
732
735
|
/**
|
|
733
736
|
* Get CREATE INDEX sql
|
|
734
737
|
*/
|
|
735
|
-
getCreateIndexSQL<T extends ModelType>(cls: Class<T>, idx: IndexConfig
|
|
738
|
+
getCreateIndexSQL<T extends ModelType>(cls: Class<T>, idx: IndexConfig): string | undefined {
|
|
739
|
+
const constraint = this.getIndexName(cls, idx);
|
|
736
740
|
const table = this.namespace(SQLModelUtil.classToStack(cls));
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
741
|
+
|
|
742
|
+
if (isModelQueryIndex(idx)) {
|
|
743
|
+
const fields: [string, boolean][] = idx.fields.map(field => {
|
|
744
|
+
const key = TypedObject.keys(field)[0];
|
|
745
|
+
const value = field[key];
|
|
746
|
+
if (DataUtil.isPlainObject(value)) {
|
|
747
|
+
throw new IndexNotSupported(cls, idx, 'Only indexed and query indices are supported in SQL');
|
|
748
|
+
}
|
|
749
|
+
return [castTo(key), typeof value === 'number' ? value === 1 : (!!value)];
|
|
750
|
+
});
|
|
751
|
+
return `CREATE ${idx.unique ? 'UNIQUE ' : ''}INDEX ${constraint} ON ${this.identifier(table)} (${fields
|
|
752
|
+
.map(([name, sel]) => `${this.identifier(name)} ${sel ? 'ASC' : 'DESC'}`)
|
|
753
|
+
.join(', ')});`;
|
|
754
|
+
} else if (isModelIndexedIndex(idx)) {
|
|
755
|
+
const all = [...idx.keyTemplate, ...idx.sortTemplate];
|
|
756
|
+
if (all.find(field => field.path.length > 1)) {
|
|
757
|
+
console.debug('Nested fields are not supported in ModelIndexed indices SQL', { index: idx.name });
|
|
758
|
+
return;
|
|
742
759
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
760
|
+
const fields = all
|
|
761
|
+
.map(({ path, value }) => `${this.identifier(path.join('_'))} ${value === -1 ? 'DESC' : 'ASC'}`)
|
|
762
|
+
.join(', ');
|
|
763
|
+
switch (idx.type) {
|
|
764
|
+
case 'indexed:keyed': return `CREATE ${idx.unique ? 'UNIQUE ' : ''}INDEX ${constraint} ON ${this.identifier(table)} (${fields});`;
|
|
765
|
+
case 'indexed:sorted': return `CREATE INDEX ${constraint} ON ${this.identifier(table)} (${fields});`;
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
throw new IndexNotSupported(cls, idx, 'Only indexed and query indices are supported in SQL');
|
|
769
|
+
}
|
|
749
770
|
}
|
|
750
771
|
|
|
751
772
|
/**
|
|
752
773
|
* Get DROP INDEX sql
|
|
753
774
|
*/
|
|
754
|
-
getDropIndexSQL<T extends ModelType>(cls: Class<T>, idx: IndexConfig
|
|
775
|
+
getDropIndexSQL<T extends ModelType>(cls: Class<T>, idx: IndexConfig | string): string {
|
|
755
776
|
const constraint = typeof idx === 'string' ? idx : this.getIndexName(cls, idx);
|
|
756
777
|
return `DROP INDEX ${this.identifier(constraint)} ;`;
|
|
757
778
|
}
|
|
@@ -1074,18 +1095,37 @@ ${this.getWhereSQL(cls, where!)}`;
|
|
|
1074
1095
|
/**
|
|
1075
1096
|
* Determine if an index has changed
|
|
1076
1097
|
*/
|
|
1077
|
-
isIndexChanged(requested: IndexConfig
|
|
1078
|
-
|
|
1079
|
-
(existing.is_unique && requested.
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1098
|
+
isIndexChanged(requested: IndexConfig, existing: SQLTableDescription['indices'][number]): boolean {
|
|
1099
|
+
if (isModelQueryIndex(requested)) {
|
|
1100
|
+
const uniqueChanged = (existing.is_unique && !requested.unique);
|
|
1101
|
+
const columnSizeChanged = requested.fields.length !== existing.columns.length;
|
|
1102
|
+
let result = uniqueChanged || columnSizeChanged;
|
|
1103
|
+
for (let i = 0; i < requested.fields.length && !result; i++) {
|
|
1104
|
+
const [[key, value]] = Object.entries(requested.fields[i]);
|
|
1105
|
+
const desc = value === -1;
|
|
1106
|
+
result ||= key !== existing.columns[i].name && desc !== existing.columns[i].desc;
|
|
1107
|
+
}
|
|
1087
1108
|
|
|
1088
|
-
|
|
1109
|
+
return result;
|
|
1110
|
+
} else if (isModelIndexedIndex(requested)) {
|
|
1111
|
+
const keys = Object.entries(requested.key);
|
|
1112
|
+
const sort = Object.entries(requested.sort);
|
|
1113
|
+
const all = [...keys, ...sort];
|
|
1114
|
+
|
|
1115
|
+
const uniqueChanged = (requested.type === 'indexed:keyed' && existing.is_unique && !requested.unique);
|
|
1116
|
+
const columnSizeChanged = all.length !== existing.columns.length;
|
|
1117
|
+
let result = uniqueChanged || columnSizeChanged;
|
|
1118
|
+
|
|
1119
|
+
for (let i = 0; i < all.length && !result; i++) {
|
|
1120
|
+
const [key, value] = all[i];
|
|
1121
|
+
const desc = value === -1;
|
|
1122
|
+
result ||= key !== existing.columns[i].name && desc !== existing.columns[i].desc;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
return result;
|
|
1126
|
+
} else {
|
|
1127
|
+
throw new IndexNotSupported(requested.class, requested, 'Only indexed and query indices are supported in SQL');
|
|
1128
|
+
}
|
|
1089
1129
|
}
|
|
1090
1130
|
|
|
1091
1131
|
/**
|
package/src/service.ts
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ModelType,
|
|
3
|
-
type BulkOperation, type BulkResponse, type ModelCrudSupport, type ModelStorageSupport, type ModelBulkSupport,
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
type BulkOperation, type BulkResponse, type ModelCrudSupport, type ModelStorageSupport, type ModelBulkSupport, NotFoundError,
|
|
4
|
+
ModelRegistryIndex, ExistsError, type OptionalId, type ModelIdSource, ModelExpiryUtil, ModelCrudUtil, ModelStorageUtil, ModelBulkUtil,
|
|
5
|
+
type ModelListOptions,
|
|
6
6
|
} from '@travetto/model';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type ModelIndexedSupport, type KeyedIndexSelection, type KeyedIndexBody, type ModelPageOptions, ModelIndexedUtil,
|
|
9
|
+
type SingleItemIndex, type SortedIndexSelection, type ModelPageResult, type SortedIndex, type FullKeyedIndexBody,
|
|
10
|
+
type FullKeyedIndexWithPartialBody, ModelIndexedComputedIndex, type ModelIndexedSearchOptions, type SortedIndexSelectionType
|
|
11
|
+
} from '@travetto/model-indexed';
|
|
12
|
+
import { castTo, type Class, JSONUtil } from '@travetto/runtime';
|
|
8
13
|
import { DataUtil } from '@travetto/schema';
|
|
9
14
|
import type { AsyncContext } from '@travetto/context';
|
|
10
15
|
import { Injectable, PostConstruct } from '@travetto/di';
|
|
11
16
|
import {
|
|
12
17
|
type ModelQuery, type ModelQueryCrudSupport, type ModelQueryFacetSupport, type ModelQuerySupport,
|
|
13
18
|
type PageableModelQuery, type ValidStringFields, type WhereClauseRaw, QueryVerifier, type ModelQuerySuggestSupport,
|
|
14
|
-
ModelQueryUtil, ModelQuerySuggestUtil, ModelQueryCrudUtil,
|
|
15
|
-
type ModelQueryFacet,
|
|
19
|
+
ModelQueryUtil, ModelQuerySuggestUtil, ModelQueryCrudUtil, type ModelQueryFacet,
|
|
16
20
|
} from '@travetto/model-query';
|
|
17
21
|
|
|
18
22
|
import type { SQLModelConfig } from './config.ts';
|
|
@@ -33,6 +37,7 @@ export class SQLModelService implements
|
|
|
33
37
|
ModelCrudSupport, ModelStorageSupport,
|
|
34
38
|
ModelBulkSupport, ModelQuerySupport,
|
|
35
39
|
ModelQueryCrudSupport, ModelQueryFacetSupport,
|
|
40
|
+
ModelIndexedSupport,
|
|
36
41
|
ModelQuerySuggestSupport {
|
|
37
42
|
|
|
38
43
|
#manager: TableManager;
|
|
@@ -98,6 +103,32 @@ export class SQLModelService implements
|
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
106
|
+
async * #scanTable<T extends ModelType>(
|
|
107
|
+
cls: Class<T>,
|
|
108
|
+
buildQuery: () => PageableModelQuery<T>,
|
|
109
|
+
options?: ModelListOptions & ModelPageOptions<number>
|
|
110
|
+
): AsyncIterable<{ items: T[], nextOffset?: number }> {
|
|
111
|
+
const batchSize = options?.batchSizeHint ?? 100;
|
|
112
|
+
const maxCount = options?.limit ?? Number.MAX_SAFE_INTEGER;
|
|
113
|
+
let offset = options?.offset ?? 0;
|
|
114
|
+
let lastOffset = -1;
|
|
115
|
+
let produced = 0;
|
|
116
|
+
while (offset !== lastOffset && produced < maxCount && !(options?.abort?.aborted)) {
|
|
117
|
+
const limit = Math.min(batchSize, maxCount - produced);
|
|
118
|
+
lastOffset = offset;
|
|
119
|
+
const items = await this.query<T>(cls, {
|
|
120
|
+
...buildQuery(),
|
|
121
|
+
limit,
|
|
122
|
+
offset
|
|
123
|
+
});
|
|
124
|
+
offset += items.length;
|
|
125
|
+
produced += items.length;
|
|
126
|
+
if (items.length) {
|
|
127
|
+
yield { items, nextOffset: items.length < limit ? undefined : offset };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
101
132
|
@PostConstruct()
|
|
102
133
|
async initializeClient(): Promise<void> {
|
|
103
134
|
await this.#dialect.connection.init?.();
|
|
@@ -184,9 +215,9 @@ export class SQLModelService implements
|
|
|
184
215
|
}
|
|
185
216
|
|
|
186
217
|
@ConnectedIterator()
|
|
187
|
-
async * list<T extends ModelType>(cls: Class<T
|
|
188
|
-
for (const
|
|
189
|
-
yield
|
|
218
|
+
async * list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
|
|
219
|
+
for await (const { items } of this.#scanTable(cls, () => ({}), options)) {
|
|
220
|
+
yield items;
|
|
190
221
|
}
|
|
191
222
|
}
|
|
192
223
|
|
|
@@ -285,7 +316,7 @@ export class SQLModelService implements
|
|
|
285
316
|
}
|
|
286
317
|
|
|
287
318
|
@Connected()
|
|
288
|
-
async
|
|
319
|
+
async suggestByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
|
|
289
320
|
await QueryVerifier.verify(cls, query);
|
|
290
321
|
const resolvedQuery = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
|
|
291
322
|
const results = await this.query<T>(cls, resolvedQuery);
|
|
@@ -293,7 +324,7 @@ export class SQLModelService implements
|
|
|
293
324
|
}
|
|
294
325
|
|
|
295
326
|
@Connected()
|
|
296
|
-
async
|
|
327
|
+
async suggestValuesByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
|
|
297
328
|
await QueryVerifier.verify(cls, query);
|
|
298
329
|
const resolvedQuery = ModelQuerySuggestUtil.getSuggestFieldQuery(cls, field, prefix, query);
|
|
299
330
|
const results = await this.query(cls, resolvedQuery);
|
|
@@ -303,7 +334,7 @@ export class SQLModelService implements
|
|
|
303
334
|
}
|
|
304
335
|
|
|
305
336
|
@Connected()
|
|
306
|
-
async
|
|
337
|
+
async facetByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]> {
|
|
307
338
|
await QueryVerifier.verify(cls, query);
|
|
308
339
|
const col = this.#dialect.identifier(field);
|
|
309
340
|
const ttl = this.#dialect.identifier('count');
|
|
@@ -326,4 +357,145 @@ export class SQLModelService implements
|
|
|
326
357
|
return result;
|
|
327
358
|
});
|
|
328
359
|
}
|
|
360
|
+
|
|
361
|
+
// Indexed support
|
|
362
|
+
@Connected()
|
|
363
|
+
async getByIndex<
|
|
364
|
+
T extends ModelType,
|
|
365
|
+
K extends KeyedIndexSelection<T>,
|
|
366
|
+
S extends SortedIndexSelection<T>
|
|
367
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<T> {
|
|
368
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
|
|
369
|
+
const results = await this.query(cls, castTo({ where: computed.project({ sort: true, includeId: true }) }));
|
|
370
|
+
if (results.length !== 1) {
|
|
371
|
+
throw new NotFoundError(`${cls.name}: ${idx}`, computed.getKey({ sort: true }));
|
|
372
|
+
}
|
|
373
|
+
return results[0];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@Connected()
|
|
377
|
+
@Transactional()
|
|
378
|
+
async deleteByIndex<
|
|
379
|
+
T extends ModelType,
|
|
380
|
+
K extends KeyedIndexSelection<T>,
|
|
381
|
+
S extends SortedIndexSelection<T>
|
|
382
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<void> {
|
|
383
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
|
|
384
|
+
const count = await this.deleteByQuery(cls, castTo({ where: computed.project({ sort: true, includeId: true }) }));
|
|
385
|
+
if (count === 0) {
|
|
386
|
+
throw new NotFoundError(`${cls.name}: ${idx}`, computed.getKey({ sort: true }));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@Connected()
|
|
391
|
+
@Transactional()
|
|
392
|
+
upsertByIndex<
|
|
393
|
+
T extends ModelType,
|
|
394
|
+
K extends KeyedIndexSelection<T>,
|
|
395
|
+
S extends SortedIndexSelection<T>
|
|
396
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: OptionalId<T>): Promise<T> {
|
|
397
|
+
return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
@Connected()
|
|
401
|
+
@Transactional()
|
|
402
|
+
updateByIndex<
|
|
403
|
+
T extends ModelType,
|
|
404
|
+
K extends KeyedIndexSelection<T>,
|
|
405
|
+
S extends SortedIndexSelection<T>
|
|
406
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: T): Promise<T> {
|
|
407
|
+
return ModelIndexedUtil.naiveUpdate(this, cls, idx, body);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@Connected()
|
|
411
|
+
@Transactional()
|
|
412
|
+
async updatePartialByIndex<
|
|
413
|
+
T extends ModelType,
|
|
414
|
+
K extends KeyedIndexSelection<T>,
|
|
415
|
+
S extends SortedIndexSelection<T>
|
|
416
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexWithPartialBody<T, K, S>): Promise<T> {
|
|
417
|
+
const item = await ModelCrudUtil.naivePartialUpdate(cls, () => this.getByIndex(cls, idx, castTo(body)), castTo(body));
|
|
418
|
+
return this.update(cls, item);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@Connected()
|
|
422
|
+
async pageByIndex<
|
|
423
|
+
T extends ModelType,
|
|
424
|
+
K extends KeyedIndexSelection<T>,
|
|
425
|
+
S extends SortedIndexSelection<T>
|
|
426
|
+
>(
|
|
427
|
+
cls: Class<T>,
|
|
428
|
+
idx: SortedIndex<T, K, S>,
|
|
429
|
+
body: KeyedIndexBody<T, K>,
|
|
430
|
+
options?: ModelPageOptions
|
|
431
|
+
): Promise<ModelPageResult<T>> {
|
|
432
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate();
|
|
433
|
+
const offset = options?.offset ? JSONUtil.fromBase64<number>(options.offset) : 0;
|
|
434
|
+
|
|
435
|
+
const baseQuery = castTo<ModelQuery<T>>({
|
|
436
|
+
where: computed.project(),
|
|
437
|
+
sort: idx.sortTemplate.map(part => ({ [part.path.join('.')]: part.value })),
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const items: T[] = [];
|
|
441
|
+
let nextOffset: number | undefined;
|
|
442
|
+
for await (const batch of this.#scanTable<T>(cls, () => baseQuery, { limit: 100, ...options, offset })) {
|
|
443
|
+
items.push(...batch.items);
|
|
444
|
+
nextOffset = batch.nextOffset;
|
|
445
|
+
}
|
|
446
|
+
return { items, nextOffset: nextOffset ? JSONUtil.toBase64(nextOffset) : undefined };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
@ConnectedIterator()
|
|
450
|
+
async * listByIndex<
|
|
451
|
+
T extends ModelType,
|
|
452
|
+
K extends KeyedIndexSelection<T>,
|
|
453
|
+
S extends SortedIndexSelection<T>
|
|
454
|
+
>(
|
|
455
|
+
cls: Class<T>,
|
|
456
|
+
idx: SortedIndex<T, K, S>,
|
|
457
|
+
body: KeyedIndexBody<T, K>,
|
|
458
|
+
options?: ModelListOptions
|
|
459
|
+
): AsyncIterable<T[]> {
|
|
460
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate();
|
|
461
|
+
const baseQuery = castTo<ModelQuery<T>>({
|
|
462
|
+
where: computed.project(),
|
|
463
|
+
sort: idx.sortTemplate.map(part => ({ [part.path.join('.')]: part.value })),
|
|
464
|
+
});
|
|
465
|
+
for await (const { items } of this.#scanTable<T>(cls, () => baseQuery, options)) {
|
|
466
|
+
yield items;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
@Connected()
|
|
471
|
+
async suggestByIndex<
|
|
472
|
+
T extends ModelType,
|
|
473
|
+
S extends SortedIndexSelection<T>,
|
|
474
|
+
K extends KeyedIndexSelection<T>,
|
|
475
|
+
B extends SortedIndexSelectionType<T, S> & string
|
|
476
|
+
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, prefix: B, options?: ModelIndexedSearchOptions): Promise<T[]> {
|
|
477
|
+
const items: T[] = [];
|
|
478
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate();
|
|
479
|
+
const nested: Record<string, unknown> = {};
|
|
480
|
+
let current = nested;
|
|
481
|
+
for (const key of idx.sortTemplate[0].path.slice(0, -1)) {
|
|
482
|
+
current = (current[key] = {});
|
|
483
|
+
}
|
|
484
|
+
current[idx.sortTemplate[0].path.at(-1)!] = { $regex: ModelIndexedUtil.getSuggestRegex(prefix) };
|
|
485
|
+
|
|
486
|
+
const baseQuery = castTo<ModelQuery<T>>({
|
|
487
|
+
where: {
|
|
488
|
+
$and: [
|
|
489
|
+
computed.project(),
|
|
490
|
+
nested
|
|
491
|
+
]
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
for await (const batch of this.#scanTable<T>(cls, () => baseQuery, { limit: 10, ...options })) {
|
|
496
|
+
items.push(...batch.items);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return items;
|
|
500
|
+
}
|
|
329
501
|
}
|
package/src/table-manager.ts
CHANGED
|
@@ -45,7 +45,7 @@ export class TableManager {
|
|
|
45
45
|
for (const command of this.#dialect.getCreateAllTablesSQL(cls)) {
|
|
46
46
|
out.push(command);
|
|
47
47
|
}
|
|
48
|
-
const indices = ModelRegistryIndex.
|
|
48
|
+
const indices = ModelRegistryIndex.getIndices(cls);
|
|
49
49
|
if (indices) {
|
|
50
50
|
for (const command of this.#dialect.getCreateAllIndicesSQL(cls, indices)) {
|
|
51
51
|
out.push(command);
|
|
@@ -64,7 +64,8 @@ export class TableManager {
|
|
|
64
64
|
const existingFields = new Map(found?.columns.map(column => [column.name, column]) ?? []);
|
|
65
65
|
const existingIndices = new Map(found?.indices.map(index => [index.name, index]) ?? []);
|
|
66
66
|
const model = path.length === 1 ? ModelRegistryIndex.getConfig(type) : undefined;
|
|
67
|
-
const
|
|
67
|
+
const indices = model ? ModelRegistryIndex.getIndices(type) : undefined;
|
|
68
|
+
const requestedIndices = new Map((indices ?? []).map(index => [this.#dialect.getIndexName(type, index), index]) ?? []);
|
|
68
69
|
|
|
69
70
|
// Manage fields
|
|
70
71
|
if (!existingFields.size) {
|
|
@@ -98,10 +99,16 @@ export class TableManager {
|
|
|
98
99
|
// Manage indices
|
|
99
100
|
for (const index of requestedIndices.keys()) {
|
|
100
101
|
if (!existingIndices.has(index)) {
|
|
101
|
-
|
|
102
|
+
const sql = this.#dialect.getCreateIndexSQL(type, requestedIndices.get(index)!);
|
|
103
|
+
if (sql) {
|
|
104
|
+
sqlCommands.createIndex.push(sql);
|
|
105
|
+
}
|
|
102
106
|
} else if (this.#dialect.isIndexChanged(requestedIndices.get(index)!, existingIndices.get(index)!)) {
|
|
103
107
|
sqlCommands.dropIndex.push(this.#dialect.getDropIndexSQL(type, existingIndices.get(index)!.name));
|
|
104
|
-
|
|
108
|
+
const sql = this.#dialect.getCreateIndexSQL(type, requestedIndices.get(index)!);
|
|
109
|
+
if (sql) {
|
|
110
|
+
sqlCommands.createIndex.push(sql);
|
|
111
|
+
}
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
|