uql-orm 0.37.1 → 0.39.0

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.
Files changed (85) hide show
  1. package/dist/browser/uql-browser.min.js +2 -2
  2. package/dist/browser/uql-browser.min.js.map +5 -5
  3. package/dist/cockroachdb/cockroachDialect.d.ts +5 -24
  4. package/dist/cockroachdb/cockroachDialect.js +3 -29
  5. package/dist/dialect/abstractSqlDialect.d.ts +42 -12
  6. package/dist/dialect/abstractSqlDialect.js +117 -48
  7. package/dist/dialect/aliases.d.ts +6 -0
  8. package/dist/dialect/aliases.js +6 -0
  9. package/dist/dialect/index.d.ts +0 -5
  10. package/dist/dialect/index.js +2 -5
  11. package/dist/dialect/jsonSql.d.ts +17 -2
  12. package/dist/dialect/jsonSql.js +15 -0
  13. package/dist/dialect/mysqlLikeSqlDialect.d.ts +13 -14
  14. package/dist/dialect/mysqlLikeSqlDialect.js +16 -20
  15. package/dist/dialect/pgLikeSqlDialect.d.ts +17 -20
  16. package/dist/dialect/pgLikeSqlDialect.js +30 -62
  17. package/dist/dialect/vectorCast.d.ts +0 -9
  18. package/dist/dialect/vectorCast.js +0 -8
  19. package/dist/dialect/vectorSqlDialect.d.ts +47 -17
  20. package/dist/dialect/vectorSqlDialect.js +78 -30
  21. package/dist/entity/decorator/entity.d.ts +1 -1
  22. package/dist/entity/metadata/definition.d.ts +1 -1
  23. package/dist/entity/metadata/definition.js +4 -3
  24. package/dist/http/query.js +3 -2
  25. package/dist/libsql/libsqlDialect.d.ts +2 -2
  26. package/dist/libsql/libsqlDialect.js +3 -3
  27. package/dist/maria/mariaDialect.d.ts +12 -17
  28. package/dist/maria/mariaDialect.js +21 -36
  29. package/dist/maria/mariaVectorMetrics.d.ts +8 -0
  30. package/dist/maria/mariaVectorMetrics.js +10 -0
  31. package/dist/maria/mariadbQuerier.d.ts +5 -0
  32. package/dist/maria/mariadbQuerier.js +5 -0
  33. package/dist/migrate/builder/migrationBuilder.d.ts +8 -17
  34. package/dist/migrate/builder/migrationBuilder.js +48 -136
  35. package/dist/migrate/builder/types.d.ts +0 -2
  36. package/dist/migrate/ddl/index.d.ts +11 -0
  37. package/dist/migrate/ddl/index.js +34 -0
  38. package/dist/{dialect/indexSqlDialect.d.ts → migrate/ddl/indexDdl.d.ts} +23 -19
  39. package/dist/migrate/ddl/indexDdl.js +126 -0
  40. package/dist/migrate/ddl/mysqlIndexDdl.d.ts +52 -0
  41. package/dist/migrate/ddl/mysqlIndexDdl.js +125 -0
  42. package/dist/migrate/ddl/pgIndexDdl.d.ts +36 -0
  43. package/dist/migrate/ddl/pgIndexDdl.js +87 -0
  44. package/dist/migrate/drift/driftDetector.js +6 -1
  45. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
  46. package/dist/migrate/index.d.ts +1 -0
  47. package/dist/migrate/index.js +2 -0
  48. package/dist/migrate/introspection/mysqlIntrospector.d.ts +9 -3
  49. package/dist/migrate/introspection/mysqlIntrospector.js +27 -5
  50. package/dist/migrate/migrator.js +3 -2
  51. package/dist/migrate/schemaGenerator.d.ts +6 -6
  52. package/dist/migrate/schemaGenerator.js +13 -22
  53. package/dist/mongo/mongoDialect.d.ts +1 -2
  54. package/dist/mongo/mongoDialect.js +29 -22
  55. package/dist/mongo/mongodbQuerier.js +1 -1
  56. package/dist/mysql/mysqlDialect.d.ts +3 -6
  57. package/dist/mysql/mysqlDialect.js +4 -11
  58. package/dist/postgres/postgresDialect.d.ts +2 -0
  59. package/dist/postgres/postgresDialect.js +2 -0
  60. package/dist/querier/abstractSqlQuerier.d.ts +11 -0
  61. package/dist/querier/abstractSqlQuerier.js +35 -0
  62. package/dist/schema/canonicalType.js +35 -36
  63. package/dist/schema/indexDifferences.d.ts +2 -1
  64. package/dist/schema/indexDifferences.js +3 -2
  65. package/dist/sqlite/sqliteDialect.d.ts +2 -2
  66. package/dist/sqlite/sqliteDialect.js +5 -5
  67. package/dist/turso/tursoDialect.d.ts +2 -2
  68. package/dist/turso/tursoDialect.js +4 -4
  69. package/dist/type/dialect.d.ts +21 -7
  70. package/dist/type/dialect.js +17 -0
  71. package/dist/type/entity.d.ts +117 -8
  72. package/dist/type/entity.js +7 -0
  73. package/dist/type/query.d.ts +18 -0
  74. package/dist/type/query.js +6 -0
  75. package/dist/type/queryWhere.d.ts +46 -2
  76. package/dist/type/vector.d.ts +45 -7
  77. package/dist/type/vector.js +16 -1
  78. package/dist/util/dialect.util.d.ts +20 -1
  79. package/dist/util/dialect.util.js +49 -4
  80. package/dist/util/object.util.d.ts +7 -1
  81. package/dist/util/object.util.js +8 -0
  82. package/dist/util/relationQuery.util.d.ts +3 -1
  83. package/dist/util/relationQuery.util.js +8 -3
  84. package/package.json +1 -1
  85. package/dist/dialect/indexSqlDialect.js +0 -103
@@ -1,24 +1,23 @@
1
1
  import { jsonPath } from '../dialect/jsonSql.js';
2
2
  import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
3
3
  import { isVectorFieldType } from '../dialect/vectorCast.js';
4
+ import { getMeta } from '../entity/index.js';
5
+ import { MARIA_VECTOR_METRICS } from './mariaVectorMetrics.js';
4
6
  export class MariaDialect extends MysqlLikeSqlDialect {
5
7
  dialectName = 'mariadb';
6
- // MariaDB 10.5+ supports `INSERT ... RETURNING` (see `insert` below), so IDs are exact per row.
8
+ // MariaDB 10.5+ has `INSERT ... RETURNING`, so ids come back exact per row - the upsert's too.
7
9
  insertIdSource = 'returning';
8
- /**
9
- * MariaDB has no functional indexes: `CREATE INDEX ... ((lower(col)))` is a syntax error even on
10
- * 12.3, where the documented workaround is a generated column. So it keeps the prefix lengths the
11
- * family shares and drops expressions.
12
- */
13
- indexFeatures = new Set(['prefixLength']);
14
10
  /** MariaDB has no `FOR ... OF`, so a lock cannot be narrowed to one table of a join. */
15
11
  supportsLockOf = false;
16
- /** Unlike MySQL: `VECTOR(n)` takes its dimension, and its vector index is declared inline. */
12
+ /**
13
+ * Unlike MySQL: `VECTOR(n)` takes its dimension, every column of a vector index has to be NOT NULL,
14
+ * and `CREATE INDEX` takes `IF NOT EXISTS` - which MySQL's grammar has no place for.
15
+ */
17
16
  featureOverrides = {
18
17
  vectorSupportsLength: true,
19
- inlineVectorIndex: true,
18
+ vectorIndexRequiresNotNull: true,
19
+ indexIfNotExists: true,
20
20
  };
21
- /** MariaDB 10.5+ supports `INSERT ... RETURNING`, so the ids are exact per row. */
22
21
  upsertReturning(entity) {
23
22
  return ` ${this.returningId(entity)}`;
24
23
  }
@@ -47,16 +46,8 @@ export class MariaDialect extends MysqlLikeSqlDialect {
47
46
  jsonPullKeep(alias, operand) {
48
47
  return `NOT JSON_EQUALS(${alias}.v, ${operand})`;
49
48
  }
50
- /** MariaDB's own names for the metrics its vector index accepts. */
51
- static INLINE_VECTOR_METRICS = new Map([
52
- ['cosine', 'cosine'],
53
- ['l2', 'euclidean'],
54
- ]);
55
- /** MariaDB 11.7+ vector distance functions. */
56
- vectorDistanceFns = new Map([
57
- ['cosine', 'VEC_DISTANCE_COSINE'],
58
- ['l2', 'VEC_DISTANCE_EUCLIDEAN'],
59
- ]);
49
+ /** `VEC_DISTANCE_COSINE`/`VEC_DISTANCE_EUCLIDEAN`, 11.7+: the metric's own name, uppercased. */
50
+ vectorMetrics = new Map([...MARIA_VECTOR_METRICS].map(([metric, name]) => [metric, { fn: `VEC_DISTANCE_${name.toUpperCase()}` }]));
60
51
  /**
61
52
  * A `VECTOR` column holds a packed little-endian float32 blob, and MariaDB refuses text where one
62
53
  * belongs: inserting `'[1,2,3]'` fails with `Incorrect vector value`, and passing it to
@@ -69,24 +60,18 @@ export class MariaDialect extends MysqlLikeSqlDialect {
69
60
  ctx.append(')');
70
61
  }
71
62
  /**
72
- * MariaDB declares a vector index inside `CREATE TABLE`: `VECTOR INDEX (col) M=n DISTANCE=metric`.
73
- * Its metric names are its own (`euclidean`, not `l2`), and an unsupported one throws rather than
74
- * being dropped, which would silently build the index on cosine instead.
63
+ * `SET STATEMENT mhnsw_ef_search=N FOR SELECT ...` - MariaDB scopes a variable to one statement, so
64
+ * the tuning needs neither a transaction nor a restore afterwards, and cannot leak to the next
65
+ * query on this pooled connection. That is why it prefixes the SQL here instead of coming back
66
+ * from `vectorTuningStatements`, which is Postgres's `SET LOCAL` shape.
75
67
  */
76
- getInlineVectorIndexDeclaration(index) {
77
- const columns = index.entries.map((entry) => this.indexColumnTarget(entry)).join(', ');
78
- let clause = `VECTOR INDEX (${columns})`;
79
- if (index.m !== undefined) {
80
- clause += ` M=${index.m}`;
81
- }
82
- if (index.distance) {
83
- const metric = MariaDialect.INLINE_VECTOR_METRICS.get(index.distance);
84
- if (!metric) {
85
- throw new TypeError(`${this.dialectName} does not support vector distance metric: ${index.distance} (index "${index.name}")`);
86
- }
87
- clause += ` DISTANCE=${metric}`;
68
+ find(ctx, entity, q = {}, opts, totalAlias) {
69
+ // `$candidates` first: `getMeta` would otherwise be resolved on every read, to discover that
70
+ // almost none of them tune anything.
71
+ if (q.$candidates !== undefined && this.tunedVectorIndex(getMeta(entity), q)) {
72
+ ctx.append(`SET STATEMENT mhnsw_ef_search=${q.$candidates} FOR `);
88
73
  }
89
- return clause;
74
+ super.find(ctx, entity, q, opts, totalAlias);
90
75
  }
91
76
  /** The reverse: selecting a `VECTOR` column raw yields that blob, so it is read back as text. */
92
77
  selectFieldExpr(escapedColumn, field) {
@@ -0,0 +1,8 @@
1
+ import type { VectorDistance } from '../type/index.js';
2
+ /**
3
+ * MariaDB's own name for each metric it supports (`euclidean`, not `l2`), which its index clause and
4
+ * its distance function are both spelled from - one list, so a metric can never be searchable and
5
+ * unindexable or the reverse. Its own module because the two ends now live apart: the distance
6
+ * function on the dialect, the `DISTANCE=` clause in the migrator's index DDL.
7
+ */
8
+ export declare const MARIA_VECTOR_METRICS: Map<VectorDistance, string>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MariaDB's own name for each metric it supports (`euclidean`, not `l2`), which its index clause and
3
+ * its distance function are both spelled from - one list, so a metric can never be searchable and
4
+ * unindexable or the reverse. Its own module because the two ends now live apart: the distance
5
+ * function on the dialect, the `DISTANCE=` clause in the migrator's index DDL.
6
+ */
7
+ export const MARIA_VECTOR_METRICS = new Map([
8
+ ['cosine', 'cosine'],
9
+ ['l2', 'euclidean'],
10
+ ]);
@@ -7,5 +7,10 @@ export declare class MariadbQuerier extends AbstractPoolQuerier<PoolConnection>
7
7
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
8
8
  internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
9
9
  internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, unknown>;
10
+ /**
11
+ * No `discard` branch, unlike mysql2's: this driver resets the connection on release, so one taken
12
+ * back mid-transaction rolls back rather than handing the next caller an open one. Verified on
13
+ * 12.3 - same `threadId` on reacquire, `@@in_transaction` 0, the uncommitted row gone.
14
+ */
10
15
  protected releaseConn(conn: PoolConnection): Promise<void>;
11
16
  }
@@ -24,6 +24,11 @@ export class MariadbQuerier extends AbstractPoolQuerier {
24
24
  stream.destroy();
25
25
  }
26
26
  }
27
+ /**
28
+ * No `discard` branch, unlike mysql2's: this driver resets the connection on release, so one taken
29
+ * back mid-transaction rolls back rather than handing the next caller an open one. Verified on
30
+ * 12.3 - same `threadId` on reacquire, `@@in_transaction` 0, the uncommitted row gone.
31
+ */
27
32
  async releaseConn(conn) {
28
33
  await conn.release();
29
34
  }
@@ -24,6 +24,12 @@ type ForeignKeyOptions = {
24
24
  */
25
25
  export declare class OperationRecorder implements IMigrationBuilder {
26
26
  protected readonly operations: AnyMigrationOperation[];
27
+ /**
28
+ * Where every operation this class builds lands, and the one thing {@link MigrationBuilder}
29
+ * overrides: it records and then runs. Each operation is spelled once, here, rather than once per
30
+ * class, which is what let the two drift into recording different shapes of the same change.
31
+ */
32
+ protected record(operation: AnyMigrationOperation): Promise<void>;
27
33
  createTable(name: string, callback: (table: ITableBuilder) => void): Promise<void>;
28
34
  dropTable(name: string, options?: {
29
35
  ifExists?: boolean;
@@ -41,7 +47,6 @@ export declare class OperationRecorder implements IMigrationBuilder {
41
47
  dropForeignKey(tableName: string, constraintName: string): Promise<void>;
42
48
  raw(sql: string): Promise<void>;
43
49
  getOperations(): AnyMigrationOperation[];
44
- recordOperationSync(operation: AnyMigrationOperation): void;
45
50
  }
46
51
  /**
47
52
  * Executes DDL operations via a SQL querier.
@@ -62,22 +67,8 @@ export declare class MigrationBuilder extends OperationRecorder {
62
67
  private readonly querier;
63
68
  private readonly sqlGenerator;
64
69
  constructor(querier: SqlQuerier);
65
- recordOperationSync(operation: AnyMigrationOperation): void;
66
- raw(sql: string): Promise<void>;
67
- createTable(name: string, callback: (table: ITableBuilder) => void): Promise<void>;
68
- dropTable(name: string, options?: {
69
- ifExists?: boolean;
70
- cascade?: boolean;
71
- }): Promise<void>;
72
- renameTable(oldName: string, newName: string): Promise<void>;
73
- addColumn(tableName: string, callback: (columns: IColumnFactory) => IColumnBuilder): Promise<void>;
74
- dropColumn(tableName: string, columnName: string): Promise<void>;
75
- alterColumn(tableName: string, callback: (columns: IColumnFactory) => IColumnBuilder): Promise<void>;
76
- renameColumn(tableName: string, oldName: string, newName: string): Promise<void>;
77
- createIndex(tableName: string, columns: readonly IndexColumnInput[], options?: IndexOptions): Promise<void>;
78
- dropIndex(tableName: string, indexName: string): Promise<void>;
79
- addForeignKey(tableName: string, columns: string[], target: ForeignKeyTarget, options?: ForeignKeyOptions): Promise<void>;
80
- dropForeignKey(tableName: string, constraintName: string): Promise<void>;
70
+ /** The recorder's sink, plus the statements the operation turns into. */
71
+ protected record(operation: AnyMigrationOperation): Promise<void>;
81
72
  private getCreateTableStatements;
82
73
  private execute;
83
74
  private operationToSql;
@@ -63,15 +63,21 @@ function addForeignKeyOperation(tableName, columns, target, options = {}) {
63
63
  function buildOneColumn(callback) {
64
64
  return callback(new TableBuilder('')).build();
65
65
  }
66
+ /**
67
+ * Collects the operations one `alterTable` callback declares, in the order it declared them.
68
+ *
69
+ * Collected rather than dispatched to the parent as they are made: the chaining methods are
70
+ * synchronous by contract, so a builder that executes could only fire and forget, which returned from
71
+ * `alterTable` with the statements still in flight and turned a failure into an unhandled rejection.
72
+ */
66
73
  class AlterTableBuilder {
67
74
  tableName;
68
- parentBuilder;
69
- constructor(tableName, parentBuilder) {
75
+ operations = [];
76
+ constructor(tableName) {
70
77
  this.tableName = tableName;
71
- this.parentBuilder = parentBuilder;
72
78
  }
73
79
  addColumn(callback) {
74
- this.parentBuilder.recordOperationSync({
80
+ this.operations.push({
75
81
  type: 'addColumn',
76
82
  tableName: this.tableName,
77
83
  column: buildOneColumn(callback),
@@ -79,7 +85,7 @@ class AlterTableBuilder {
79
85
  return this;
80
86
  }
81
87
  dropColumn(name) {
82
- this.parentBuilder.recordOperationSync({
88
+ this.operations.push({
83
89
  type: 'dropColumn',
84
90
  tableName: this.tableName,
85
91
  columnName: name,
@@ -87,7 +93,7 @@ class AlterTableBuilder {
87
93
  return this;
88
94
  }
89
95
  renameColumn(oldName, newName) {
90
- this.parentBuilder.recordOperationSync({
96
+ this.operations.push({
91
97
  type: 'renameColumn',
92
98
  tableName: this.tableName,
93
99
  oldName,
@@ -97,7 +103,7 @@ class AlterTableBuilder {
97
103
  }
98
104
  alterColumn(callback) {
99
105
  const column = buildOneColumn(callback);
100
- this.parentBuilder.recordOperationSync({
106
+ this.operations.push({
101
107
  type: 'alterColumn',
102
108
  tableName: this.tableName,
103
109
  columnName: column.name,
@@ -106,11 +112,11 @@ class AlterTableBuilder {
106
112
  return this;
107
113
  }
108
114
  addIndex(columns, options) {
109
- this.parentBuilder.recordOperationSync(createIndexOperation(this.tableName, columns, options));
115
+ this.operations.push(createIndexOperation(this.tableName, columns, options));
110
116
  return this;
111
117
  }
112
118
  dropIndex(name) {
113
- this.parentBuilder.recordOperationSync({
119
+ this.operations.push({
114
120
  type: 'dropIndex',
115
121
  tableName: this.tableName,
116
122
  indexName: name,
@@ -118,11 +124,11 @@ class AlterTableBuilder {
118
124
  return this;
119
125
  }
120
126
  addForeignKey(columns, target, options) {
121
- this.parentBuilder.recordOperationSync(addForeignKeyOperation(this.tableName, columns, target, options));
127
+ this.operations.push(addForeignKeyOperation(this.tableName, columns, target, options));
122
128
  return this;
123
129
  }
124
130
  dropForeignKey(name) {
125
- this.parentBuilder.recordOperationSync({
131
+ this.operations.push({
126
132
  type: 'dropForeignKey',
127
133
  tableName: this.tableName,
128
134
  constraintName: name,
@@ -130,22 +136,35 @@ class AlterTableBuilder {
130
136
  return this;
131
137
  }
132
138
  }
139
+ function collectAlterOperations(tableName, callback) {
140
+ const builder = new AlterTableBuilder(tableName);
141
+ callback(builder);
142
+ return builder.operations;
143
+ }
133
144
  /**
134
145
  * Records migration operations without executing them.
135
146
  * Use for migration code generation and dry-run scenarios.
136
147
  */
137
148
  export class OperationRecorder {
138
149
  operations = [];
150
+ /**
151
+ * Where every operation this class builds lands, and the one thing {@link MigrationBuilder}
152
+ * overrides: it records and then runs. Each operation is spelled once, here, rather than once per
153
+ * class, which is what let the two drift into recording different shapes of the same change.
154
+ */
155
+ async record(operation) {
156
+ this.operations.push(operation);
157
+ }
139
158
  async createTable(name, callback) {
140
159
  const builder = new TableBuilder(name);
141
160
  callback(builder);
142
- this.recordOperationSync({
161
+ await this.record({
143
162
  type: 'createTable',
144
163
  table: builder.build(),
145
164
  });
146
165
  }
147
166
  async dropTable(name, options = {}) {
148
- this.recordOperationSync({
167
+ await this.record({
149
168
  type: 'dropTable',
150
169
  tableName: name,
151
170
  ifExists: options.ifExists,
@@ -153,25 +172,26 @@ export class OperationRecorder {
153
172
  });
154
173
  }
155
174
  async renameTable(oldName, newName) {
156
- this.recordOperationSync({
175
+ await this.record({
157
176
  type: 'renameTable',
158
177
  oldName,
159
178
  newName,
160
179
  });
161
180
  }
162
181
  async alterTable(name, callback) {
163
- const builder = new AlterTableBuilder(name, this);
164
- callback(builder);
182
+ for (const operation of collectAlterOperations(name, callback)) {
183
+ await this.record(operation);
184
+ }
165
185
  }
166
186
  async addColumn(tableName, callback) {
167
- this.recordOperationSync({
187
+ await this.record({
168
188
  type: 'addColumn',
169
189
  tableName,
170
190
  column: buildOneColumn(callback),
171
191
  });
172
192
  }
173
193
  async dropColumn(tableName, columnName) {
174
- this.recordOperationSync({
194
+ await this.record({
175
195
  type: 'dropColumn',
176
196
  tableName,
177
197
  columnName,
@@ -179,7 +199,7 @@ export class OperationRecorder {
179
199
  }
180
200
  async alterColumn(tableName, callback) {
181
201
  const column = buildOneColumn(callback);
182
- this.recordOperationSync({
202
+ await this.record({
183
203
  type: 'alterColumn',
184
204
  tableName,
185
205
  columnName: column.name,
@@ -187,7 +207,7 @@ export class OperationRecorder {
187
207
  });
188
208
  }
189
209
  async renameColumn(tableName, oldName, newName) {
190
- this.recordOperationSync({
210
+ await this.record({
191
211
  type: 'renameColumn',
192
212
  tableName,
193
213
  oldName,
@@ -195,27 +215,27 @@ export class OperationRecorder {
195
215
  });
196
216
  }
197
217
  async createIndex(tableName, columns, options) {
198
- this.recordOperationSync(createIndexOperation(tableName, columns, options));
218
+ await this.record(createIndexOperation(tableName, columns, options));
199
219
  }
200
220
  async dropIndex(tableName, indexName) {
201
- this.recordOperationSync({
221
+ await this.record({
202
222
  type: 'dropIndex',
203
223
  tableName,
204
224
  indexName,
205
225
  });
206
226
  }
207
227
  async addForeignKey(tableName, columns, target, options = {}) {
208
- this.recordOperationSync(addForeignKeyOperation(tableName, columns, target, options));
228
+ await this.record(addForeignKeyOperation(tableName, columns, target, options));
209
229
  }
210
230
  async dropForeignKey(tableName, constraintName) {
211
- this.recordOperationSync({
231
+ await this.record({
212
232
  type: 'dropForeignKey',
213
233
  tableName,
214
234
  constraintName,
215
235
  });
216
236
  }
217
237
  async raw(sql) {
218
- this.recordOperationSync({
238
+ await this.record({
219
239
  type: 'raw',
220
240
  sql,
221
241
  });
@@ -223,9 +243,6 @@ export class OperationRecorder {
223
243
  getOperations() {
224
244
  return [...this.operations];
225
245
  }
226
- recordOperationSync(operation) {
227
- this.operations.push(operation);
228
- }
229
246
  }
230
247
  /**
231
248
  * Executes DDL operations via a SQL querier.
@@ -254,114 +271,9 @@ export class MigrationBuilder extends OperationRecorder {
254
271
  }
255
272
  this.sqlGenerator = generator;
256
273
  }
257
- recordOperationSync(operation) {
258
- super.recordOperationSync(operation);
259
- // Fire and forget execution - for sync contexts (AlterTableBuilder)
260
- void this.execute(operation);
261
- }
262
- async raw(sql) {
263
- const operation = {
264
- type: 'raw',
265
- sql,
266
- };
267
- this.operations.push(operation);
268
- await this.querier.run(sql);
269
- }
270
- // Override async methods to execute immediately
271
- async createTable(name, callback) {
272
- const builder = new TableBuilder(name);
273
- callback(builder);
274
- const operation = {
275
- type: 'createTable',
276
- table: builder.build(),
277
- };
278
- this.operations.push(operation);
279
- await this.execute(operation);
280
- }
281
- async dropTable(name, options = {}) {
282
- const operation = {
283
- type: 'dropTable',
284
- tableName: name,
285
- ifExists: options.ifExists,
286
- cascade: options.cascade,
287
- };
288
- this.operations.push(operation);
289
- await this.execute(operation);
290
- }
291
- async renameTable(oldName, newName) {
292
- const operation = {
293
- type: 'renameTable',
294
- oldName,
295
- newName,
296
- };
297
- this.operations.push(operation);
298
- await this.execute(operation);
299
- }
300
- async addColumn(tableName, callback) {
301
- const operation = {
302
- type: 'addColumn',
303
- tableName,
304
- column: buildOneColumn(callback),
305
- };
306
- this.operations.push(operation);
307
- await this.execute(operation);
308
- }
309
- async dropColumn(tableName, columnName) {
310
- const operation = {
311
- type: 'dropColumn',
312
- tableName,
313
- columnName,
314
- };
315
- this.operations.push(operation);
316
- await this.execute(operation);
317
- }
318
- async alterColumn(tableName, callback) {
319
- const column = buildOneColumn(callback);
320
- const operation = {
321
- type: 'alterColumn',
322
- tableName,
323
- columnName: column.name,
324
- changes: column,
325
- };
326
- this.operations.push(operation);
327
- await this.execute(operation);
328
- }
329
- async renameColumn(tableName, oldName, newName) {
330
- const operation = {
331
- type: 'renameColumn',
332
- tableName,
333
- oldName,
334
- newName,
335
- };
336
- this.operations.push(operation);
337
- await this.execute(operation);
338
- }
339
- async createIndex(tableName, columns, options) {
340
- const operation = createIndexOperation(tableName, columns, options);
341
- this.operations.push(operation);
342
- await this.execute(operation);
343
- }
344
- async dropIndex(tableName, indexName) {
345
- const operation = {
346
- type: 'dropIndex',
347
- tableName,
348
- indexName,
349
- };
350
- this.operations.push(operation);
351
- await this.execute(operation);
352
- }
353
- async addForeignKey(tableName, columns, target, options = {}) {
354
- const operation = addForeignKeyOperation(tableName, columns, target, options);
355
- this.operations.push(operation);
356
- await this.execute(operation);
357
- }
358
- async dropForeignKey(tableName, constraintName) {
359
- const operation = {
360
- type: 'dropForeignKey',
361
- tableName,
362
- constraintName,
363
- };
364
- this.operations.push(operation);
274
+ /** The recorder's sink, plus the statements the operation turns into. */
275
+ async record(operation) {
276
+ await super.record(operation);
365
277
  await this.execute(operation);
366
278
  }
367
279
  getCreateTableStatements(operation) {
@@ -449,6 +449,4 @@ export interface IMigrationBuilder {
449
449
  raw(sql: string): Promise<void>;
450
450
  /** Get all recorded operations */
451
451
  getOperations(): AnyMigrationOperation[];
452
- /** Record an operation synchronously */
453
- recordOperationSync(operation: AnyMigrationOperation): void;
454
452
  }
@@ -0,0 +1,11 @@
1
+ import type { AbstractSqlDialect } from '../../dialect/abstractSqlDialect.js';
2
+ import { IndexDdl } from './indexDdl.js';
3
+ export { IndexDdl } from './indexDdl.js';
4
+ export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl.js';
5
+ export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
6
+ /**
7
+ * The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName` so a
8
+ * dialect subclassed by a user keeps its family's DDL, which is what overriding gave it while this
9
+ * lived on the dialect itself. Anything else gets the portable form, which is SQLite's.
10
+ */
11
+ export declare function indexDdlFor(dialect: AbstractSqlDialect): IndexDdl;
@@ -0,0 +1,34 @@
1
+ import { CockroachDialect } from '../../cockroachdb/cockroachDialect.js';
2
+ import { MysqlLikeSqlDialect } from '../../dialect/mysqlLikeSqlDialect.js';
3
+ import { PgLikeSqlDialect } from '../../dialect/pgLikeSqlDialect.js';
4
+ import { MariaDialect } from '../../maria/mariaDialect.js';
5
+ import { MySqlDialect } from '../../mysql/mysqlDialect.js';
6
+ import { IndexDdl } from './indexDdl.js';
7
+ import { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl.js';
8
+ import { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
9
+ export { IndexDdl } from './indexDdl.js';
10
+ export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl.js';
11
+ export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
12
+ /**
13
+ * The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName` so a
14
+ * dialect subclassed by a user keeps its family's DDL, which is what overriding gave it while this
15
+ * lived on the dialect itself. Anything else gets the portable form, which is SQLite's.
16
+ */
17
+ export function indexDdlFor(dialect) {
18
+ if (dialect instanceof CockroachDialect) {
19
+ return new CockroachIndexDdl(dialect);
20
+ }
21
+ if (dialect instanceof PgLikeSqlDialect) {
22
+ return new PgIndexDdl(dialect);
23
+ }
24
+ if (dialect instanceof MySqlDialect) {
25
+ return new MySqlIndexDdl(dialect);
26
+ }
27
+ if (dialect instanceof MariaDialect) {
28
+ return new MariaIndexDdl(dialect);
29
+ }
30
+ if (dialect instanceof MysqlLikeSqlDialect) {
31
+ return new MysqlLikeIndexDdl(dialect);
32
+ }
33
+ return new IndexDdl(dialect);
34
+ }
@@ -1,19 +1,16 @@
1
- import { type IndexColumnSchema, type IndexFeature, type IndexSchema } from '../type/index.js';
2
- import { VectorSqlDialect } from './vectorSqlDialect.js';
1
+ import type { AbstractSqlDialect } from '../../dialect/abstractSqlDialect.js';
2
+ import type { IndexType } from '../../schema/types.js';
3
+ import { type IndexColumnSchema, type IndexFeature, type IndexJsonArray, type IndexSchema } from '../../type/index.js';
3
4
  /**
4
5
  * `CREATE INDEX` for SQL dialects: the statement and the fragments each engine spells differently.
5
- *
6
- * A layer of its own for the same reason as {@link VectorSqlDialect} below it - it needs only
7
- * `escapeId` and `features.indexIfNotExists` from the SQL dialect above, so keeping it here spares
8
- * that 2000-line class thirteen more members. What each engine can express at all is data
9
- * ({@link indexFeatures}), validated once, rather than a throw per feature per dialect.
6
+ * The migrator's rather than the dialect's, since {@link SqlSchemaGenerator} is the only thing that
7
+ * emits DDL - which is what keeps a `CAST(... ARRAY)` table out of every runtime consumer's entry.
8
+ * The form here is the portable one, which SQLite (and so libSQL, Turso and D1) takes verbatim: no
9
+ * access-method clause, no operator classes, no tuning parameters.
10
10
  */
11
- export declare abstract class IndexSqlDialect extends VectorSqlDialect {
12
- /**
13
- * The `CREATE INDEX` statement for this dialect. The form here is the portable one, which SQLite
14
- * (and so libSQL, Turso and D1) takes verbatim: no access-method clause, no operator classes, no
15
- * tuning parameters. Dialects that have those override the fragments below rather than this.
16
- */
11
+ export declare class IndexDdl<D extends AbstractSqlDialect = AbstractSqlDialect> {
12
+ protected readonly dialect: D;
13
+ constructor(dialect: D);
17
14
  getCreateIndexStatement(tableName: string, index: IndexSchema, opts?: {
18
15
  ifNotExists?: boolean;
19
16
  }): string;
@@ -25,20 +22,27 @@ export declare abstract class IndexSqlDialect extends VectorSqlDialect {
25
22
  protected readonly indexFeatures: ReadonlySet<IndexFeature>;
26
23
  private assertIndexFeatures;
27
24
  /**
28
- * The column-level declaration for a vector index that lives inside `CREATE TABLE` rather than in
29
- * its own statement, which the `inlineVectorIndex` feature flags. Only MariaDB has one.
25
+ * Index types this dialect spells as a keyword of their own (`FULLTEXT INDEX`, `VECTOR INDEX`)
26
+ * rather than as an access method after the table. One table drives both, so a type that is a
27
+ * keyword here can never also leak out as a ` USING` clause the engine has no word for.
30
28
  */
31
- getInlineVectorIndexDeclaration(index: IndexSchema): string;
32
- /** Only CockroachDB's native vector index replaces the `INDEX` keyword. */
33
- protected indexKeyword(_index: IndexSchema): string;
29
+ protected readonly indexTypeKeywords: ReadonlyMap<IndexType, string>;
30
+ /** The keyword an index type replaces `INDEX` with, or `INDEX` for the types that do not. */
31
+ protected indexKeyword(index: IndexSchema): string;
34
32
  /** One index entry: what is indexed, its operator class if any, then its stored order. */
35
33
  protected indexColumn(entry: IndexColumnSchema, index: IndexSchema): string;
36
34
  /**
37
35
  * A quoted column, optionally prefix-limited, or an expression in its own parentheses - the form
38
36
  * `((lower("email")))` that MySQL requires and Postgres, CockroachDB and SQLite all accept, so one
39
- * rendering serves every engine that has expression indexes.
37
+ * rendering serves every engine that has expression indexes. A JSON entry is one of those too, its
38
+ * expression compiled from the path rather than written out by the caller.
40
39
  */
41
40
  protected indexColumnTarget(entry: IndexColumnSchema): string;
41
+ /**
42
+ * One key per *element* of the JSON array, which is MySQL's multi-valued index and nothing else's -
43
+ * every other dialect refuses `jsonArray` in {@link assertIndexFeatures} and never reaches this.
44
+ */
45
+ protected jsonArrayIndexExpr(_escapedColumn: string, _json: IndexJsonArray): string;
42
46
  /** Postgres-wire dialects put a vector or user-declared operator class here. */
43
47
  protected indexColumnOpsClass(_entry: IndexColumnSchema, _index: IndexSchema): string;
44
48
  /** `ASC` is every engine's default, so only `DESC` is worth emitting. */