@travetto/model-sql 8.0.0-alpha.2 → 8.0.0-alpha.21

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-sql",
3
- "version": "8.0.0-alpha.2",
3
+ "version": "8.0.0-alpha.21",
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.2",
32
- "@travetto/context": "^8.0.0-alpha.2",
33
- "@travetto/model": "^8.0.0-alpha.2",
34
- "@travetto/model-query": "^8.0.0-alpha.2"
31
+ "@travetto/config": "^8.0.0-alpha.18",
32
+ "@travetto/context": "^8.0.0-alpha.17",
33
+ "@travetto/model": "^8.0.0-alpha.19",
34
+ "@travetto/model-indexed": "^8.0.0-alpha.21",
35
+ "@travetto/model-query": "^8.0.0-alpha.20"
35
36
  },
36
37
  "peerDependencies": {
37
- "@travetto/cli": "^8.0.0-alpha.3",
38
- "@travetto/test": "^8.0.0-alpha.2"
38
+ "@travetto/cli": "^8.0.0-alpha.24",
39
+ "@travetto/test": "^8.0.0-alpha.17"
39
40
  },
40
41
  "peerDependenciesMeta": {
41
42
  "@travetto/cli": {
@@ -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 { BulkResponse, IndexConfig, ModelType } from '@travetto/model';
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 === 1) {
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<T>[]): string[] {
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<ModelType>): string {
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<T>): string {
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
- const fields: [string, boolean][] = idx.fields.map(field => {
738
- const key = TypedObject.keys(field)[0];
739
- const value = field[key];
740
- if (DataUtil.isPlainObject(value)) {
741
- throw new Error('Unable to supported nested fields for indices');
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
- return [castTo(key), typeof value === 'number' ? value === 1 : (!!value)];
744
- });
745
- const constraint = this.getIndexName(cls, idx);
746
- return `CREATE ${idx.type === 'unique' ? 'UNIQUE ' : ''}INDEX ${constraint} ON ${this.identifier(table)} (${fields
747
- .map(([name, sel]) => `${this.identifier(name)} ${sel ? 'ASC' : 'DESC'}`)
748
- .join(', ')});`;
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<T> | string): string {
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<ModelType>, existing: SQLTableDescription['indices'][number]): boolean {
1078
- let result =
1079
- (existing.is_unique && requested.type !== 'unique')
1080
- || requested.fields.length !== existing.columns.length;
1081
-
1082
- for (let i = 0; i < requested.fields.length && !result; i++) {
1083
- const [[key, value]] = Object.entries(requested.fields[i]);
1084
- const desc = value === -1;
1085
- result ||= key !== existing.columns[i].name && desc !== existing.columns[i].desc;
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
- return result;
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
- NotFoundError, ModelRegistryIndex, ExistsError, type OptionalId, type ModelIdSource,
5
- ModelExpiryUtil, ModelCrudUtil, ModelStorageUtil, ModelBulkUtil,
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 { castTo, type Class } from '@travetto/runtime';
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>): AsyncIterable<T> {
188
- for (const item of await this.query(cls, {})) {
189
- yield await ModelCrudUtil.load(cls, item);
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 suggest<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
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 suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
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 facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]> {
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
  }
@@ -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.getConfig(cls).indices;
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 requestedIndices = new Map((model?.indices ?? []).map(index => [this.#dialect.getIndexName(type, index), index]) ?? []);
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
- sqlCommands.createIndex.push(this.#dialect.getCreateIndexSQL(type, requestedIndices.get(index)!));
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
- sqlCommands.createIndex.push(this.#dialect.getCreateIndexSQL(type, requestedIndices.get(index)!));
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