uql-orm 0.21.0 → 0.22.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 (142) hide show
  1. package/README.md +5 -4
  2. package/dist/bunSql/bunSqlCockroachDialect.d.ts +2 -2
  3. package/dist/bunSql/bunSqlCockroachDialect.js +4 -10
  4. package/dist/bunSql/bunSqlPostgresDialect.d.ts +2 -2
  5. package/dist/bunSql/bunSqlPostgresDialect.js +4 -10
  6. package/dist/cockroachdb/cockroachDialect.d.ts +20 -4
  7. package/dist/cockroachdb/cockroachDialect.js +33 -18
  8. package/dist/d1/d1Querier.d.ts +4 -3
  9. package/dist/d1/d1Querier.js +0 -3
  10. package/dist/d1/d1SqliteDialect.d.ts +7 -0
  11. package/dist/d1/d1SqliteDialect.js +9 -0
  12. package/dist/dialect/abstractDialect.d.ts +8 -3
  13. package/dist/dialect/abstractDialect.js +9 -4
  14. package/dist/dialect/abstractSqlDialect.d.ts +35 -7
  15. package/dist/dialect/abstractSqlDialect.js +76 -35
  16. package/dist/dialect/index.d.ts +1 -0
  17. package/dist/dialect/index.js +1 -0
  18. package/dist/dialect/indexSqlDialect.d.ts +57 -0
  19. package/dist/dialect/indexSqlDialect.js +101 -0
  20. package/dist/dialect/mysqlLikeSqlDialect.d.ts +16 -1
  21. package/dist/dialect/mysqlLikeSqlDialect.js +32 -1
  22. package/dist/dialect/pgLikeSqlDialect.d.ts +33 -15
  23. package/dist/dialect/pgLikeSqlDialect.js +90 -56
  24. package/dist/dialect/vectorCast.d.ts +21 -0
  25. package/dist/dialect/vectorCast.js +28 -0
  26. package/dist/dialect/vectorSqlDialect.d.ts +10 -3
  27. package/dist/dialect/vectorSqlDialect.js +19 -8
  28. package/dist/entity/decorator/index-decorator.d.ts +2 -9
  29. package/dist/entity/metadata/definition.d.ts +5 -4
  30. package/dist/entity/metadata/definition.js +4 -3
  31. package/dist/libsql/libsqlDialect.d.ts +12 -2
  32. package/dist/libsql/libsqlDialect.js +14 -2
  33. package/dist/libsql/libsqlQuerier.d.ts +9 -22
  34. package/dist/libsql/libsqlQuerier.js +9 -85
  35. package/dist/libsql/libsqlQuerierPool.d.ts +10 -8
  36. package/dist/libsql/libsqlQuerierPool.js +17 -15
  37. package/dist/maria/mariaDialect.d.ts +26 -4
  38. package/dist/maria/mariaDialect.js +54 -15
  39. package/dist/migrate/builder/migrationBuilder.d.ts +9 -29
  40. package/dist/migrate/builder/migrationBuilder.js +58 -76
  41. package/dist/migrate/builder/splitSqlStatements.d.ts +0 -2
  42. package/dist/migrate/builder/splitSqlStatements.js +0 -2
  43. package/dist/migrate/builder/tableBuilder.d.ts +12 -2
  44. package/dist/migrate/builder/tableBuilder.js +71 -119
  45. package/dist/migrate/builder/types.d.ts +30 -41
  46. package/dist/migrate/cli.d.ts +3 -2
  47. package/dist/migrate/cli.js +21 -48
  48. package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -4
  49. package/dist/migrate/codegen/entityCodeGenerator.js +5 -63
  50. package/dist/migrate/codegen/fieldOptionsSource.d.ts +10 -0
  51. package/dist/migrate/codegen/fieldOptionsSource.js +55 -0
  52. package/dist/migrate/codegen/index.d.ts +0 -2
  53. package/dist/migrate/codegen/index.js +0 -4
  54. package/dist/migrate/generator/indexNodeToSchema.d.ts +9 -0
  55. package/dist/migrate/generator/indexNodeToSchema.js +14 -0
  56. package/dist/migrate/generator/mongoCommand.d.ts +57 -0
  57. package/dist/migrate/generator/mongoCommand.js +26 -0
  58. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +9 -20
  59. package/dist/migrate/generator/mongoSchemaGenerator.js +40 -82
  60. package/dist/migrate/index.d.ts +0 -1
  61. package/dist/migrate/index.js +0 -1
  62. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +25 -10
  63. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +46 -32
  64. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +0 -1
  65. package/dist/migrate/introspection/baseSqlIntrospector.js +3 -12
  66. package/dist/migrate/introspection/mongoIntrospector.js +2 -2
  67. package/dist/migrate/introspection/mysqlIntrospector.d.ts +5 -8
  68. package/dist/migrate/introspection/mysqlIntrospector.js +4 -10
  69. package/dist/migrate/introspection/postgresIntrospector.d.ts +5 -8
  70. package/dist/migrate/introspection/postgresIntrospector.js +4 -10
  71. package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -5
  72. package/dist/migrate/introspection/sqliteIntrospector.js +26 -18
  73. package/dist/migrate/migrator.d.ts +23 -3
  74. package/dist/migrate/migrator.js +57 -69
  75. package/dist/migrate/schemaGenerator.d.ts +26 -37
  76. package/dist/migrate/schemaGenerator.js +69 -174
  77. package/dist/migrate/schemaGeneratorAsync.d.ts +2 -2
  78. package/dist/migrate/schemaGeneratorAsync.js +3 -3
  79. package/dist/migrate/storage/databaseStorage.js +4 -4
  80. package/dist/mongo/mongoDialect.d.ts +2 -0
  81. package/dist/mongo/mongoDialect.js +14 -3
  82. package/dist/mysql/mysqlDialect.d.ts +8 -0
  83. package/dist/mysql/mysqlDialect.js +13 -0
  84. package/dist/schema/canonicalType.d.ts +14 -2
  85. package/dist/schema/canonicalType.js +41 -31
  86. package/dist/schema/index.d.ts +1 -1
  87. package/dist/schema/schemaASTBuilder.d.ts +8 -1
  88. package/dist/schema/schemaASTBuilder.js +56 -47
  89. package/dist/schema/types.d.ts +10 -37
  90. package/dist/sqlite/abstractSqliteQuerier.d.ts +39 -0
  91. package/dist/sqlite/abstractSqliteQuerier.js +44 -0
  92. package/dist/sqlite/bunSqliteAdapter.bun.d.ts +26 -0
  93. package/dist/sqlite/bunSqliteAdapter.bun.js +25 -0
  94. package/dist/sqlite/hranaQuerier.d.ts +54 -0
  95. package/dist/sqlite/hranaQuerier.js +90 -0
  96. package/dist/sqlite/hranaQuerierPool.d.ts +21 -0
  97. package/dist/sqlite/hranaQuerierPool.js +25 -0
  98. package/dist/sqlite/index.d.ts +2 -1
  99. package/dist/sqlite/index.js +2 -1
  100. package/dist/sqlite/sqliteDialect.d.ts +18 -4
  101. package/dist/sqlite/sqliteDialect.js +24 -24
  102. package/dist/sqlite/sqliteQuerier.d.ts +16 -9
  103. package/dist/sqlite/sqliteQuerier.js +4 -27
  104. package/dist/sqlite/sqliteQuerierPool.d.ts +14 -4
  105. package/dist/sqlite/sqliteQuerierPool.js +16 -5
  106. package/dist/turso/index.d.ts +3 -0
  107. package/dist/turso/index.js +3 -0
  108. package/dist/turso/local.d.ts +3 -0
  109. package/dist/turso/local.js +3 -0
  110. package/dist/turso/tursoDialect.d.ts +15 -0
  111. package/dist/turso/tursoDialect.js +18 -0
  112. package/dist/turso/tursoLocalQuerier.d.ts +25 -0
  113. package/dist/turso/tursoLocalQuerier.js +20 -0
  114. package/dist/turso/tursoLocalQuerierPool.d.ts +32 -0
  115. package/dist/turso/tursoLocalQuerierPool.js +39 -0
  116. package/dist/turso/tursoQuerier.d.ts +10 -0
  117. package/dist/turso/tursoQuerier.js +10 -0
  118. package/dist/turso/tursoQuerierPool.d.ts +39 -0
  119. package/dist/turso/tursoQuerierPool.js +38 -0
  120. package/dist/type/config.d.ts +0 -6
  121. package/dist/type/dialect.d.ts +12 -4
  122. package/dist/type/dialect.js +7 -1
  123. package/dist/type/entity.d.ts +66 -5
  124. package/dist/type/migration.d.ts +32 -23
  125. package/dist/type/vector.d.ts +5 -2
  126. package/dist/util/index.d.ts +1 -0
  127. package/dist/util/index.js +1 -0
  128. package/dist/util/indexColumn.util.d.ts +6 -0
  129. package/dist/util/indexColumn.util.js +26 -0
  130. package/package.json +16 -3
  131. package/dist/migrate/codegen/entityMerger.d.ts +0 -114
  132. package/dist/migrate/codegen/entityMerger.js +0 -293
  133. package/dist/migrate/codegen/migrationCodeGenerator.d.ts +0 -61
  134. package/dist/migrate/codegen/migrationCodeGenerator.js +0 -355
  135. package/dist/migrate/generator/index.d.ts +0 -2
  136. package/dist/migrate/generator/index.js +0 -2
  137. package/dist/migrate/sync/index.d.ts +0 -6
  138. package/dist/migrate/sync/index.js +0 -6
  139. package/dist/migrate/sync/schemaSync.d.ts +0 -131
  140. package/dist/migrate/sync/schemaSync.js +0 -259
  141. package/dist/sqlite/betterSqlite3Dialect.d.ts +0 -9
  142. package/dist/sqlite/betterSqlite3Dialect.js +0 -9
@@ -4,11 +4,7 @@ import { areTypesEqual, canonicalToSql, fieldOptionsToCanonical, isVectorCategor
4
4
  import { SchemaASTBuilder } from '../schema/schemaASTBuilder.js';
5
5
  import { escapeSqlId, getKeys, isAutoIncrement } from '../util/index.js';
6
6
  import { formatDefaultValue } from './builder/expressions.js';
7
- /** Maps UQL distance metric names to inline DDL keywords (e.g. MariaDB `DISTANCE=euclidean`). */
8
- const INLINE_VECTOR_DISTANCE_MAP = new Map([
9
- ['cosine', 'cosine'],
10
- ['l2', 'euclidean'],
11
- ]);
7
+ import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
12
8
  /**
13
9
  * Unified SQL schema generator.
14
10
  * Parameterized by dialect to handle Postgres, MySQL, MariaDB, and SQLite.
@@ -53,15 +49,7 @@ export class SqlSchemaGenerator {
53
49
  getCanonicalType(field, fieldType) {
54
50
  return fieldOptionsToCanonical(field, fieldType);
55
51
  }
56
- /**
57
- * Convert CanonicalType to SQL type string for this dialect.
58
- * Also handles legacy string types for backward compatibility.
59
- */
60
52
  canonicalTypeToSql(type) {
61
- // Handle legacy string types
62
- if (typeof type === 'string') {
63
- return type;
64
- }
65
53
  return canonicalToSql(type, this.dialect);
66
54
  }
67
55
  // ============================================================================
@@ -73,10 +61,10 @@ export class SqlSchemaGenerator {
73
61
  const tableNode = ast.getTables()[0];
74
62
  return this.generateCreateTableFromNode(tableNode, options);
75
63
  }
76
- generateDropTable(entity) {
77
- const meta = getMeta(entity);
78
- const tableName = this.dialect.resolveTableName(entity, meta);
79
- return `DROP TABLE IF EXISTS ${this.escapeId(tableName)};`;
64
+ generateDropTable(tableName, options = {}) {
65
+ const ifExists = options.ifExists ? 'IF EXISTS ' : '';
66
+ const cascade = options.cascade && this.features.dropTableCascade ? ' CASCADE' : '';
67
+ return `DROP TABLE ${ifExists}${this.escapeId(tableName)}${cascade};`;
80
68
  }
81
69
  generateAlterTable(diff) {
82
70
  const statements = [];
@@ -145,48 +133,7 @@ export class SqlSchemaGenerator {
145
133
  return statements;
146
134
  }
147
135
  generateCreateIndex(tableName, index, options = {}) {
148
- const unique = index.unique ? 'UNIQUE ' : '';
149
- const ifNotExists = (options.ifNotExists ?? this.features.indexIfNotExists) ? 'IF NOT EXISTS ' : '';
150
- const opsClassMap = this.dialect.vectorOpsClass;
151
- // CockroachDB's native vector index has one algorithm (no hnsw/ivfflat choice), so `type: 'vector'`
152
- // is its trigger - same generic value MariaDB's inline style already uses (see `isInlineVectorIdx`).
153
- const isNativeVectorIndex = this.features.vectorIndexStyle === 'native' && index.type === 'vector';
154
- const hasOpsClass = opsClassMap && (isNativeVectorIndex || index.type === 'hnsw' || index.type === 'ivfflat');
155
- // For pgvector/CockroachDB vector indexes: append operator class to the column. Throws rather
156
- // than silently omitting the operator class for an unsupported distance - e.g. a bare
157
- // `CREATE VECTOR INDEX ... (col)` on CockroachDB would build using its default distance
158
- // metric instead of the one actually requested, with no error to signal the mismatch.
159
- const columns = index.columns
160
- .map((c) => {
161
- const escaped = this.escapeId(c);
162
- if (!opsClassMap || !hasOpsClass || !index.distance) {
163
- return escaped;
164
- }
165
- const opsClass = opsClassMap.get(index.distance);
166
- if (!opsClass) {
167
- throw new TypeError(`${this.dialect.dialectName} does not support vector distance metric: ${index.distance} (index "${index.name}")`);
168
- }
169
- return `${escaped} ${opsClass}`;
170
- })
171
- .join(', ');
172
- // CockroachDB has no access-method keyword: `CREATE VECTOR INDEX ... (col)`, not
173
- // `CREATE INDEX ... USING vector (col)`.
174
- const indexKeyword = isNativeVectorIndex ? 'VECTOR INDEX' : 'INDEX';
175
- const using = !isNativeVectorIndex && index.type ? ` USING ${index.type}` : '';
176
- // Build WITH params for pgvector indexes with operator class support. CockroachDB's native
177
- // vector index has its own, differently-named tuning knobs - not generated here.
178
- let withClause = '';
179
- if (hasOpsClass && !isNativeVectorIndex) {
180
- const withParams = [];
181
- if (index.m !== undefined)
182
- withParams.push(`m = ${index.m}`);
183
- if (index.efConstruction !== undefined)
184
- withParams.push(`ef_construction = ${index.efConstruction}`);
185
- if (index.lists !== undefined)
186
- withParams.push(`lists = ${index.lists}`);
187
- withClause = withParams.length > 0 ? ` WITH (${withParams.join(', ')})` : '';
188
- }
189
- return `CREATE ${unique}${indexKeyword} ${ifNotExists}${this.escapeId(index.name)} ON ${this.escapeId(tableName)}${using} (${columns})${withClause};`;
136
+ return this.dialect.getCreateIndexStatement(tableName, index, options);
190
137
  }
191
138
  generateDropIndex(tableName, indexName) {
192
139
  if (this.dialect.dropIndexSyntax === 'on-table') {
@@ -195,19 +142,15 @@ export class SqlSchemaGenerator {
195
142
  return `DROP INDEX IF EXISTS ${this.escapeId(indexName)};`;
196
143
  }
197
144
  /**
198
- * Generate column definition from a ColumnSchema object
145
+ * Generate a column definition from a {@link ColumnSchema}, whose type is the engine's own spelling
146
+ * and may already carry its precision (or even `PRIMARY KEY`, for a serial).
199
147
  */
200
148
  generateColumnDefinitionFromSchema(column, options = {}) {
201
149
  const { includePrimaryKey = true, includeUnique = true } = options;
202
150
  let type = column.type;
203
151
  if (!type.includes('(')) {
204
152
  if (column.precision !== undefined) {
205
- if (column.scale !== undefined) {
206
- type += `(${column.precision}, ${column.scale})`;
207
- }
208
- else {
209
- type += `(${column.precision})`;
210
- }
153
+ type += column.scale === undefined ? `(${column.precision})` : `(${column.precision}, ${column.scale})`;
211
154
  }
212
155
  else if (column.length !== undefined) {
213
156
  type += `(${column.length})`;
@@ -216,23 +159,38 @@ export class SqlSchemaGenerator {
216
159
  if (!includePrimaryKey) {
217
160
  type = type.replace(/\s+PRIMARY\s+KEY/i, '');
218
161
  }
219
- let definition = `${this.escapeId(column.name)} ${type}`;
220
- if (includePrimaryKey && column.isPrimaryKey && !type.includes('PRIMARY KEY')) {
221
- definition += ' PRIMARY KEY';
162
+ // `includePrimaryKey: false` suppresses the keyword, not the fact: the column is still the primary
163
+ // key, so it must not pick up `NOT NULL` (implied) or `UNIQUE` (redundant) on the way out.
164
+ return this.renderColumn({
165
+ ...column,
166
+ type,
167
+ isUnique: column.isUnique && includeUnique,
168
+ declaresPrimaryKey: includePrimaryKey && column.isPrimaryKey,
169
+ });
170
+ }
171
+ /**
172
+ * The one place a column definition is spelled. Both callers reach it - the `ColumnSchema` path above
173
+ * and the `ColumnNode` path below - because the clause order and the "no NOT NULL on a primary key"
174
+ * rules are the same everywhere, and having them written twice is how the two paths drifted.
175
+ */
176
+ renderColumn(column) {
177
+ let def = `${this.escapeId(column.name)} ${column.type}`;
178
+ if (column.declaresPrimaryKey && !column.type.includes('PRIMARY KEY')) {
179
+ def += ' PRIMARY KEY';
222
180
  }
223
181
  if (!column.nullable && !column.isPrimaryKey) {
224
- definition += ' NOT NULL';
182
+ def += ' NOT NULL';
225
183
  }
226
- if (includeUnique && column.isUnique && !column.isPrimaryKey) {
227
- definition += ' UNIQUE';
184
+ if (column.isUnique && !column.isPrimaryKey) {
185
+ def += ' UNIQUE';
228
186
  }
229
187
  if (column.defaultValue !== undefined) {
230
- definition += ` DEFAULT ${this.formatDefaultValue(column.defaultValue)}`;
188
+ def += ` DEFAULT ${this.formatDefaultValue(column.defaultValue)}`;
231
189
  }
232
190
  if (column.comment) {
233
- definition += this.generateColumnComment(column.name, column.comment);
191
+ def += this.generateColumnComment(column.name, column.comment);
234
192
  }
235
- return definition;
193
+ return def;
236
194
  }
237
195
  getSqlType(field, fieldType) {
238
196
  // If field has a reference, inherit type from the target primary key
@@ -250,12 +208,6 @@ export class SqlSchemaGenerator {
250
208
  }
251
209
  return this.canonicalTypeToSql(canonical);
252
210
  }
253
- /**
254
- * Get the boolean type for this database
255
- */
256
- getBooleanType() {
257
- return this.canonicalTypeToSql({ category: 'boolean' });
258
- }
259
211
  /**
260
212
  * Generate ALTER COLUMN statements (database-specific)
261
213
  */
@@ -286,12 +238,6 @@ export class SqlSchemaGenerator {
286
238
  }
287
239
  return [`ALTER TABLE ${table} ${this.dialect.alterColumnSyntax} ${newDefinition};`];
288
240
  }
289
- /**
290
- * Get table options (e.g., ENGINE for MySQL)
291
- */
292
- getTableOptions(_meta) {
293
- return this.dialect.tableOptions ? ` ${this.dialect.tableOptions}` : '';
294
- }
295
241
  /**
296
242
  * Generate column comment clause (if supported)
297
243
  */
@@ -372,7 +318,8 @@ export class SqlSchemaGenerator {
372
318
  };
373
319
  }
374
320
  /**
375
- * Convert field options to ColumnSchema
321
+ * Convert field options to ColumnSchema. Both sides of a diff are the engine's SQL spelling: what it
322
+ * would create for this field, against what it reported for the existing column.
376
323
  */
377
324
  fieldToColumnSchema(fieldKey, field, meta) {
378
325
  const isPrimaryKey = field.isId === true && meta.id === fieldKey;
@@ -410,12 +357,16 @@ export class SqlSchemaGenerator {
410
357
  return false;
411
358
  }
412
359
  /**
413
- * Compare two column types for equality using the canonical type system
360
+ * Whether two column types are the same *as this engine stores them*.
361
+ *
362
+ * Both sides are SQL spellings, parsed back to canonical so that `INT` and `INTEGER`, or `DATETIME`
363
+ * and `TIMESTAMP`, do not read as a change. Do not "simplify" this into comparing the entity's
364
+ * canonical type against the column's: several canonical types share one storage type per engine
365
+ * (`boolean` is `TINYINT(1)` on MySQL and `INTEGER` on SQLite), so that comparison reports an
366
+ * alteration for those columns on every single sync.
414
367
  */
415
368
  isTypeEqual(current, desired) {
416
- const typeA = sqlToCanonical(current.type);
417
- const typeB = sqlToCanonical(desired.type);
418
- return areTypesEqual(typeA, typeB);
369
+ return areTypesEqual(sqlToCanonical(current.type), sqlToCanonical(desired.type));
419
370
  }
420
371
  /**
421
372
  * Compare two default values for equality
@@ -445,8 +396,14 @@ export class SqlSchemaGenerator {
445
396
  generateCreateTableFromNode(table, options = {}) {
446
397
  const columns = [];
447
398
  const constraints = [];
399
+ const isInlineVectorIdx = this.features.inlineVectorIndex;
400
+ const vectorIndexes = isInlineVectorIdx ? table.indexes.filter((idx) => idx.type === 'vector') : [];
401
+ const regularIndexes = isInlineVectorIdx ? table.indexes.filter((idx) => idx.type !== 'vector') : table.indexes;
402
+ // MariaDB rejects a `VECTOR INDEX` whose column is nullable ("All parts of a VECTOR index must
403
+ // be NOT NULL"), so being indexed decides it rather than the entity's own nullability.
404
+ const indexedVectorColumns = new Set(vectorIndexes.flatMap((idx) => idx.columns.map((col) => col.name)));
448
405
  for (const col of table.columns.values()) {
449
- const colDef = this.generateColumnFromNode(col);
406
+ const colDef = this.generateColumnFromNode(indexedVectorColumns.has(col.name) ? { ...col, nullable: false } : col);
450
407
  columns.push(colDef);
451
408
  }
452
409
  if (table.primaryKey.length > 1) {
@@ -469,12 +426,11 @@ export class SqlSchemaGenerator {
469
426
  createSql += ',\n';
470
427
  createSql += constraints.map((c) => ` ${c}`).join(',\n');
471
428
  }
472
- const isInlineVectorIdx = this.features.vectorIndexStyle === 'inline';
473
- const vectorIndexes = isInlineVectorIdx ? table.indexes.filter((idx) => idx.type === 'vector') : [];
474
- const regularIndexes = isInlineVectorIdx ? table.indexes.filter((idx) => idx.type !== 'vector') : table.indexes;
475
429
  if (vectorIndexes.length > 0) {
476
430
  createSql += ',\n';
477
- createSql += vectorIndexes.map((idx) => ` ${this.generateInlineVectorIndex(idx)}`).join(',\n');
431
+ createSql += vectorIndexes
432
+ .map((idx) => ` ${this.dialect.getInlineVectorIndexDeclaration(indexNodeToSchema(idx))}`)
433
+ .join(',\n');
478
434
  }
479
435
  createSql += '\n)';
480
436
  if (this.dialect.tableOptions) {
@@ -495,73 +451,22 @@ export class SqlSchemaGenerator {
495
451
  return statements;
496
452
  }
497
453
  /**
498
- * Generate a column definition from a ColumnNode.
454
+ * Generate a column definition from a ColumnNode. A composite key is declared as a table constraint,
455
+ * so only a lone primary key column carries `PRIMARY KEY` inline.
499
456
  */
500
457
  generateColumnFromNode(col) {
501
- const colName = this.escapeId(col.name);
502
- let sqlType = this.canonicalTypeToSql(col.type);
503
- if (col.isPrimaryKey && col.isAutoIncrement) {
504
- sqlType = this.serialPrimaryKeyType;
505
- }
506
- let def = `${colName} ${sqlType}`;
507
- if (!col.nullable && !col.isPrimaryKey) {
508
- def += ' NOT NULL';
509
- }
510
- if (col.isPrimaryKey && col.table.primaryKey.length === 1 && !sqlType.includes('PRIMARY KEY')) {
511
- def += ' PRIMARY KEY';
512
- }
513
- if (col.isUnique && !col.isPrimaryKey) {
514
- def += ' UNIQUE';
515
- }
516
- if (col.defaultValue !== undefined) {
517
- def += ` DEFAULT ${this.formatDefaultValue(col.defaultValue)}`;
518
- }
519
- if (col.comment) {
520
- def += this.generateColumnComment(col.name, col.comment);
521
- }
522
- return def;
523
- }
524
- /**
525
- * Generate an inline VECTOR INDEX clause for use inside CREATE TABLE.
526
- * Syntax: `VECTOR INDEX (col) M=n DISTANCE=metric`
527
- */
528
- generateInlineVectorIndex(index) {
529
- const columns = index.columns.map((c) => this.escapeId(c.name)).join(', ');
530
- let clause = `VECTOR INDEX (${columns})`;
531
- if (index.m !== undefined) {
532
- clause += ` M=${index.m}`;
533
- }
534
- if (index.distance) {
535
- const metric = INLINE_VECTOR_DISTANCE_MAP.get(index.distance);
536
- if (!metric) {
537
- throw new TypeError(`${this.dialect.dialectName} does not support vector distance metric: ${index.distance} (index "${index.name}")`);
538
- }
539
- clause += ` DISTANCE=${metric}`;
540
- }
541
- return clause;
458
+ return this.renderColumn({
459
+ ...col,
460
+ type: col.isPrimaryKey && col.isAutoIncrement ? this.serialPrimaryKeyType : this.canonicalTypeToSql(col.type),
461
+ declaresPrimaryKey: col.isPrimaryKey && col.table.primaryKey.length === 1,
462
+ });
542
463
  }
543
464
  /**
544
465
  * Generate CREATE INDEX SQL from an IndexNode.
545
466
  * Delegates to `generateCreateIndex` for unified SQL assembly.
546
467
  */
547
468
  generateCreateIndexFromNode(index, options = { ifNotExists: false }) {
548
- return this.generateCreateIndex(index.table.name, {
549
- name: index.name,
550
- columns: index.columns.map((c) => c.name),
551
- unique: index.unique,
552
- type: index.type,
553
- distance: index.distance,
554
- m: index.m,
555
- efConstruction: index.efConstruction,
556
- lists: index.lists,
557
- }, options);
558
- }
559
- /**
560
- * Generate DROP TABLE SQL from a TableNode.
561
- */
562
- generateDropTableFromNode(table, options = {}) {
563
- const ifExists = options.ifExists ? 'IF EXISTS ' : '';
564
- return `DROP TABLE ${ifExists}${this.escapeId(table.name)};`;
469
+ return this.generateCreateIndex(index.table.name, indexNodeToSchema(index), options);
565
470
  }
566
471
  // ============================================================================
567
472
  // Phase 3: Builder Operation Methods (Moved forward for unification)
@@ -570,12 +475,6 @@ export class SqlSchemaGenerator {
570
475
  const tableNode = this.tableDefinitionToNode(table);
571
476
  return this.generateCreateTableFromNode(tableNode, options);
572
477
  }
573
- generateDropTableSql(tableName, options) {
574
- const ifExists = options?.ifExists ? 'IF EXISTS ' : '';
575
- // Use dialect-specific cascade support from config
576
- const cascade = options?.cascade && this.features.dropTableCascade ? ' CASCADE' : '';
577
- return `DROP TABLE ${ifExists}${this.escapeId(tableName)}${cascade};`;
578
- }
579
478
  generateRenameTableSql(oldName, newName) {
580
479
  if (this.dialect.renameTableSyntax === 'rename-table') {
581
480
  return `RENAME TABLE ${this.escapeId(oldName)} TO ${this.escapeId(newName)};`;
@@ -587,8 +486,8 @@ export class SqlSchemaGenerator {
587
486
  return `ALTER TABLE ${this.escapeId(tableName)} ADD COLUMN ${colSql};`;
588
487
  }
589
488
  generateAlterColumnSql(tableName, columnName, column) {
590
- const colSql = this.generateColumnFromNode(this.fullColumnDefinitionToNode(column, tableName));
591
- return this.generateAlterColumnStatements(tableName, { name: columnName, type: '' }, colSql).join('\n');
489
+ const node = this.fullColumnDefinitionToNode(column, tableName);
490
+ return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node)).join('\n');
592
491
  }
593
492
  generateDropColumnSql(tableName, columnName) {
594
493
  return `ALTER TABLE ${this.escapeId(tableName)} DROP COLUMN ${this.escapeId(columnName)};`;
@@ -596,12 +495,6 @@ export class SqlSchemaGenerator {
596
495
  generateRenameColumnSql(tableName, oldName, newName) {
597
496
  return `ALTER TABLE ${this.escapeId(tableName)} RENAME COLUMN ${this.escapeId(oldName)} TO ${this.escapeId(newName)};`;
598
497
  }
599
- generateCreateIndexSql(tableName, index) {
600
- return this.generateCreateIndex(tableName, index);
601
- }
602
- generateDropIndexSql(tableName, indexName) {
603
- return this.generateDropIndex(tableName, indexName);
604
- }
605
498
  generateAddForeignKeySql(tableName, foreignKey) {
606
499
  const fkCols = foreignKey.columns.map((c) => this.escapeId(c)).join(', ');
607
500
  const refCols = foreignKey.referencesColumns.map((c) => this.escapeId(c)).join(', ');
@@ -645,10 +538,12 @@ export class SqlSchemaGenerator {
645
538
  table.primaryKey = finalPrimaryKey;
646
539
  for (const idxDef of def.indexes) {
647
540
  const indexNode = {
648
- name: idxDef.name,
541
+ ...idxDef,
649
542
  table,
650
- columns: idxDef.columns.map((name) => columns.get(name)).filter((c) => c !== undefined),
651
- unique: idxDef.unique,
543
+ columns: idxDef.columns
544
+ .map((entry) => (entry.expression ? undefined : columns.get(entry.column)))
545
+ .filter((c) => c !== undefined),
546
+ entries: idxDef.columns,
652
547
  };
653
548
  table.indexes.push(indexNode);
654
549
  }
@@ -703,7 +598,7 @@ export class SqlSchemaGenerator {
703
598
  * Synchronous factory for SQL schema generators only.
704
599
  * For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
705
600
  */
706
- export function createSchemaGenerator(dialect, namingStrategy, defaultForeignKeyAction) {
601
+ export function createSchemaGenerator(dialect, defaultForeignKeyAction) {
707
602
  if (!(dialect instanceof AbstractSqlDialect)) {
708
603
  return undefined;
709
604
  }
@@ -1,8 +1,8 @@
1
1
  import type { AbstractDialect } from '../dialect/abstractDialect.js';
2
2
  import type { ForeignKeyAction } from '../schema/types.js';
3
- import type { NamingStrategy, SchemaGenerator } from '../type/index.js';
3
+ import type { SchemaGenerator } from '../type/index.js';
4
4
  /**
5
5
  * Async factory for schema generators. Use this for MongoDB so the optional peer
6
6
  * `mongodb` is only loaded when this path runs. SQL dialects delegate to {@link createSchemaGenerator}.
7
7
  */
8
- export declare function createSchemaGeneratorAsync(dialect: AbstractDialect, namingStrategy?: NamingStrategy, defaultForeignKeyAction?: ForeignKeyAction): Promise<SchemaGenerator | undefined>;
8
+ export declare function createSchemaGeneratorAsync(dialect: AbstractDialect, defaultForeignKeyAction?: ForeignKeyAction): Promise<SchemaGenerator | undefined>;
@@ -3,10 +3,10 @@ import { createSchemaGenerator } from './schemaGenerator.js';
3
3
  * Async factory for schema generators. Use this for MongoDB so the optional peer
4
4
  * `mongodb` is only loaded when this path runs. SQL dialects delegate to {@link createSchemaGenerator}.
5
5
  */
6
- export async function createSchemaGeneratorAsync(dialect, namingStrategy, defaultForeignKeyAction) {
6
+ export async function createSchemaGeneratorAsync(dialect, defaultForeignKeyAction) {
7
7
  if (dialect.dialectName === 'mongodb') {
8
8
  const { MongoSchemaGenerator } = await import('./generator/mongoSchemaGenerator.js');
9
- return new MongoSchemaGenerator(namingStrategy, defaultForeignKeyAction);
9
+ return new MongoSchemaGenerator(dialect.namingStrategy, defaultForeignKeyAction);
10
10
  }
11
- return createSchemaGenerator(dialect, namingStrategy, defaultForeignKeyAction);
11
+ return createSchemaGenerator(dialect, defaultForeignKeyAction);
12
12
  }
@@ -31,7 +31,7 @@ export class DatabaseMigrationStorage {
31
31
  }
32
32
  async createTableIfNotExists(querier) {
33
33
  const { dialect } = querier;
34
- const sql = `
34
+ const sql = /*sql*/ `
35
35
  CREATE TABLE IF NOT EXISTS ${dialect.escapeId(this.tableName)} (
36
36
  ${dialect.escapeId('name')} VARCHAR(255) PRIMARY KEY,
37
37
  ${dialect.escapeId('executed_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP
@@ -48,7 +48,7 @@ export class DatabaseMigrationStorage {
48
48
  }
49
49
  try {
50
50
  const { dialect } = querier;
51
- const sql = `SELECT ${dialect.escapeId('name')} FROM ${dialect.escapeId(this.tableName)} ORDER BY ${dialect.escapeId('name')} ASC`;
51
+ const sql = /*sql*/ `SELECT ${dialect.escapeId('name')} FROM ${dialect.escapeId(this.tableName)} ORDER BY ${dialect.escapeId('name')} ASC`;
52
52
  const results = await querier.all(sql);
53
53
  return results.map((r) => r.name);
54
54
  }
@@ -62,7 +62,7 @@ export class DatabaseMigrationStorage {
62
62
  async logWithQuerier(querier, migrationName) {
63
63
  await this.ensureStorage();
64
64
  const { dialect } = querier;
65
- const sql = `INSERT INTO ${dialect.escapeId(this.tableName)} (${dialect.escapeId('name')}) VALUES (${dialect.placeholder(1)})`;
65
+ const sql = /*sql*/ `INSERT INTO ${dialect.escapeId(this.tableName)} (${dialect.escapeId('name')}) VALUES (${dialect.placeholder(1)})`;
66
66
  await querier.run(sql, [migrationName]);
67
67
  }
68
68
  /**
@@ -71,7 +71,7 @@ export class DatabaseMigrationStorage {
71
71
  async unlogWithQuerier(querier, migrationName) {
72
72
  await this.ensureStorage();
73
73
  const { dialect } = querier;
74
- const sql = `DELETE FROM ${dialect.escapeId(this.tableName)} WHERE ${dialect.escapeId('name')} = ${dialect.placeholder(1)}`;
74
+ const sql = /*sql*/ `DELETE FROM ${dialect.escapeId(this.tableName)} WHERE ${dialect.escapeId('name')} = ${dialect.placeholder(1)}`;
75
75
  await querier.run(sql, [migrationName]);
76
76
  }
77
77
  }
@@ -15,6 +15,8 @@ export declare class MongoDialect extends AbstractDialect {
15
15
  private static readonly REL_COUNT_KEY;
16
16
  private static readonly REL_NESTED_KEY;
17
17
  private static readonly VECTOR_INDEX_TYPES;
18
+ /** Atlas rejects a `$vectorSearch` asking for more candidates than this. */
19
+ private static readonly MAX_NUM_CANDIDATES;
18
20
  private static readonly AGGREGATE_OP_MAP;
19
21
  /**
20
22
  * MongoDB stores the primary key as `_id`; everything else resolves as usual. Projections, sorts,
@@ -14,7 +14,7 @@ export const mongoDialectFeatures = {
14
14
  renameColumn: false,
15
15
  foreignKeyAlter: false,
16
16
  columnComment: false,
17
- vectorIndexStyle: 'create',
17
+ inlineVectorIndex: false,
18
18
  vectorSupportsLength: false,
19
19
  supportsTimestamptz: false,
20
20
  defaultStringAsText: false,
@@ -30,6 +30,8 @@ export class MongoDialect extends AbstractDialect {
30
30
  static REL_COUNT_KEY = 'n';
31
31
  static REL_NESTED_KEY = '__uql_target';
32
32
  static VECTOR_INDEX_TYPES = new Set(['vectorSearch', 'hnsw', 'ivfflat', 'vector']);
33
+ /** Atlas rejects a `$vectorSearch` asking for more candidates than this. */
34
+ static MAX_NUM_CANDIDATES = 10_000;
33
35
  // Direct field aggregates → MongoDB accumulator. `$count` is handled separately (COUNT(*) vs
34
36
  // COUNT(field) differ), so it is not listed here.
35
37
  static AGGREGATE_OP_MAP = new Map([
@@ -98,6 +100,11 @@ export class MongoDialect extends AbstractDialect {
98
100
  return this.renderFilter(entity, filterIt, opts, lookups);
99
101
  });
100
102
  }
103
+ else if (key === '$text') {
104
+ // MongoDB's text index declares which fields it covers, so `$fields` cannot narrow the search
105
+ // the way it does elsewhere - the same shape as `$distance` being index-defined here.
106
+ filter['$text'] = { $search: val.$value };
107
+ }
101
108
  else if (meta.relations[key]) {
102
109
  this.assertNoRaw(val);
103
110
  if (!lookups) {
@@ -744,13 +751,17 @@ export class MongoDialect extends AbstractDialect {
744
751
  }
745
752
  const colName = this.resolveColumnName(key, field);
746
753
  // Resolve index name from @Index metadata, or fall back to convention
747
- const indexMeta = meta.indexes?.find((idx) => idx.columns.includes(key) && MongoDialect.VECTOR_INDEX_TYPES.has(idx.type));
754
+ const indexMeta = meta.indexes?.find((idx) => idx.columns.some((entry) => entry.column === key) && MongoDialect.VECTOR_INDEX_TYPES.has(idx.type));
748
755
  const indexName = indexMeta?.name ?? `${colName}_index`;
756
+ if (!limit) {
757
+ throw new TypeError(`$vectorSearch requires $limit (vector sort on '${key}' of '${meta.name}')`);
758
+ }
749
759
  const stage = {
750
760
  index: indexName,
751
761
  path: colName,
752
762
  queryVector: [...search.$vector],
753
- numCandidates: limit * 10,
763
+ // Atlas caps `numCandidates` at 10000 and wants roughly 10x the limit below that.
764
+ numCandidates: Math.min(limit * 10, MongoDialect.MAX_NUM_CANDIDATES),
754
765
  limit,
755
766
  };
756
767
  // Pre-filter: merge $where into $vectorSearch.filter
@@ -1,4 +1,12 @@
1
1
  import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
2
+ import type { IndexSchema } from '../type/index.js';
2
3
  export declare class MySqlDialect extends MysqlLikeSqlDialect {
3
4
  readonly dialectName = "mysql";
5
+ /**
6
+ * MySQL has no vector index of any kind, so one is refused here rather than compiled to DDL the
7
+ * server rejects: `USING hnsw` is a syntax error, and MariaDB's inline `VECTOR INDEX` is not MySQL
8
+ * syntax either. Verified against MySQL 9.7, which does have `VECTOR` columns and
9
+ * `STRING_TO_VECTOR`, but no distance function outside HeatWave - hence nothing to index for.
10
+ */
11
+ protected indexAccessMethod(index: IndexSchema): string;
4
12
  }
@@ -1,4 +1,17 @@
1
1
  import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
2
2
  export class MySqlDialect extends MysqlLikeSqlDialect {
3
3
  dialectName = 'mysql';
4
+ /**
5
+ * MySQL has no vector index of any kind, so one is refused here rather than compiled to DDL the
6
+ * server rejects: `USING hnsw` is a syntax error, and MariaDB's inline `VECTOR INDEX` is not MySQL
7
+ * syntax either. Verified against MySQL 9.7, which does have `VECTOR` columns and
8
+ * `STRING_TO_VECTOR`, but no distance function outside HeatWave - hence nothing to index for.
9
+ */
10
+ indexAccessMethod(index) {
11
+ if (index.type === 'vector' || index.type === 'hnsw' || index.type === 'ivfflat') {
12
+ throw new TypeError(`${this.dialectName} has no vector index (index "${index.name}" declares type "${index.type}"). ` +
13
+ 'Vector search on MySQL needs HeatWave.');
14
+ }
15
+ return super.indexAccessMethod(index);
16
+ }
4
17
  }
@@ -7,15 +7,27 @@
7
7
  * - TypeScript types (for entity generation)
8
8
  */
9
9
  import type { AbstractDialect } from '../dialect/abstractDialect.js';
10
+ import type { VectorCast } from '../dialect/vectorCast.js';
10
11
  import type { ColumnType, FieldOptions } from '../type/entity.js';
11
12
  import type { CanonicalType, TypeCategory } from './types.js';
12
- /** Type guard for all vector-family categories. */
13
- export declare function isVectorCategory(category: TypeCategory): boolean;
13
+ /** Whether a category is one of the vector types, narrowing it to the cast pgvector names use. */
14
+ export declare function isVectorCategory(category: TypeCategory | undefined): category is VectorCast;
14
15
  /**
15
16
  * Parse a SQL type string into a canonical type.
16
17
  * Handles complex types like VARCHAR(255), DECIMAL(10,2), etc.
17
18
  */
18
19
  export declare function sqlToCanonical(sqlType: string): CanonicalType;
20
+ /**
21
+ * The canonical type for a column an engine reported, merging the metadata columns it reports beside
22
+ * the type name (`character_maximum_length` and friends) over whatever the name itself carried. This is
23
+ * the one place a SQL type string is parsed: everything downstream compares and renders canonical
24
+ * types, so no consumer has to know that `TINYINT(1)` means boolean on MySQL.
25
+ */
26
+ export declare function canonicalColumnType(sqlType: string, reported?: {
27
+ length?: number;
28
+ precision?: number;
29
+ scale?: number;
30
+ }): CanonicalType;
19
31
  /**
20
32
  * Convert a canonical type to a SQL type string for a specific dialect instance.
21
33
  */