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.
- package/README.md +5 -4
- package/dist/bunSql/bunSqlCockroachDialect.d.ts +2 -2
- package/dist/bunSql/bunSqlCockroachDialect.js +4 -10
- package/dist/bunSql/bunSqlPostgresDialect.d.ts +2 -2
- package/dist/bunSql/bunSqlPostgresDialect.js +4 -10
- package/dist/cockroachdb/cockroachDialect.d.ts +20 -4
- package/dist/cockroachdb/cockroachDialect.js +33 -18
- package/dist/d1/d1Querier.d.ts +4 -3
- package/dist/d1/d1Querier.js +0 -3
- package/dist/d1/d1SqliteDialect.d.ts +7 -0
- package/dist/d1/d1SqliteDialect.js +9 -0
- package/dist/dialect/abstractDialect.d.ts +8 -3
- package/dist/dialect/abstractDialect.js +9 -4
- package/dist/dialect/abstractSqlDialect.d.ts +35 -7
- package/dist/dialect/abstractSqlDialect.js +76 -35
- package/dist/dialect/index.d.ts +1 -0
- package/dist/dialect/index.js +1 -0
- package/dist/dialect/indexSqlDialect.d.ts +57 -0
- package/dist/dialect/indexSqlDialect.js +101 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +16 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +32 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +33 -15
- package/dist/dialect/pgLikeSqlDialect.js +90 -56
- package/dist/dialect/vectorCast.d.ts +21 -0
- package/dist/dialect/vectorCast.js +28 -0
- package/dist/dialect/vectorSqlDialect.d.ts +10 -3
- package/dist/dialect/vectorSqlDialect.js +19 -8
- package/dist/entity/decorator/index-decorator.d.ts +2 -9
- package/dist/entity/metadata/definition.d.ts +5 -4
- package/dist/entity/metadata/definition.js +4 -3
- package/dist/libsql/libsqlDialect.d.ts +12 -2
- package/dist/libsql/libsqlDialect.js +14 -2
- package/dist/libsql/libsqlQuerier.d.ts +9 -22
- package/dist/libsql/libsqlQuerier.js +9 -85
- package/dist/libsql/libsqlQuerierPool.d.ts +10 -8
- package/dist/libsql/libsqlQuerierPool.js +17 -15
- package/dist/maria/mariaDialect.d.ts +26 -4
- package/dist/maria/mariaDialect.js +54 -15
- package/dist/migrate/builder/migrationBuilder.d.ts +9 -29
- package/dist/migrate/builder/migrationBuilder.js +58 -76
- package/dist/migrate/builder/splitSqlStatements.d.ts +0 -2
- package/dist/migrate/builder/splitSqlStatements.js +0 -2
- package/dist/migrate/builder/tableBuilder.d.ts +12 -2
- package/dist/migrate/builder/tableBuilder.js +71 -119
- package/dist/migrate/builder/types.d.ts +30 -41
- package/dist/migrate/cli.d.ts +3 -2
- package/dist/migrate/cli.js +21 -48
- package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -4
- package/dist/migrate/codegen/entityCodeGenerator.js +5 -63
- package/dist/migrate/codegen/fieldOptionsSource.d.ts +10 -0
- package/dist/migrate/codegen/fieldOptionsSource.js +55 -0
- package/dist/migrate/codegen/index.d.ts +0 -2
- package/dist/migrate/codegen/index.js +0 -4
- package/dist/migrate/generator/indexNodeToSchema.d.ts +9 -0
- package/dist/migrate/generator/indexNodeToSchema.js +14 -0
- package/dist/migrate/generator/mongoCommand.d.ts +57 -0
- package/dist/migrate/generator/mongoCommand.js +26 -0
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +9 -20
- package/dist/migrate/generator/mongoSchemaGenerator.js +40 -82
- package/dist/migrate/index.d.ts +0 -1
- package/dist/migrate/index.js +0 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +25 -10
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +46 -32
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +0 -1
- package/dist/migrate/introspection/baseSqlIntrospector.js +3 -12
- package/dist/migrate/introspection/mongoIntrospector.js +2 -2
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +5 -8
- package/dist/migrate/introspection/mysqlIntrospector.js +4 -10
- package/dist/migrate/introspection/postgresIntrospector.d.ts +5 -8
- package/dist/migrate/introspection/postgresIntrospector.js +4 -10
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -5
- package/dist/migrate/introspection/sqliteIntrospector.js +26 -18
- package/dist/migrate/migrator.d.ts +23 -3
- package/dist/migrate/migrator.js +57 -69
- package/dist/migrate/schemaGenerator.d.ts +26 -37
- package/dist/migrate/schemaGenerator.js +69 -174
- package/dist/migrate/schemaGeneratorAsync.d.ts +2 -2
- package/dist/migrate/schemaGeneratorAsync.js +3 -3
- package/dist/migrate/storage/databaseStorage.js +4 -4
- package/dist/mongo/mongoDialect.d.ts +2 -0
- package/dist/mongo/mongoDialect.js +14 -3
- package/dist/mysql/mysqlDialect.d.ts +8 -0
- package/dist/mysql/mysqlDialect.js +13 -0
- package/dist/schema/canonicalType.d.ts +14 -2
- package/dist/schema/canonicalType.js +41 -31
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/schemaASTBuilder.d.ts +8 -1
- package/dist/schema/schemaASTBuilder.js +56 -47
- package/dist/schema/types.d.ts +10 -37
- package/dist/sqlite/abstractSqliteQuerier.d.ts +39 -0
- package/dist/sqlite/abstractSqliteQuerier.js +44 -0
- package/dist/sqlite/bunSqliteAdapter.bun.d.ts +26 -0
- package/dist/sqlite/bunSqliteAdapter.bun.js +25 -0
- package/dist/sqlite/hranaQuerier.d.ts +54 -0
- package/dist/sqlite/hranaQuerier.js +90 -0
- package/dist/sqlite/hranaQuerierPool.d.ts +21 -0
- package/dist/sqlite/hranaQuerierPool.js +25 -0
- package/dist/sqlite/index.d.ts +2 -1
- package/dist/sqlite/index.js +2 -1
- package/dist/sqlite/sqliteDialect.d.ts +18 -4
- package/dist/sqlite/sqliteDialect.js +24 -24
- package/dist/sqlite/sqliteQuerier.d.ts +16 -9
- package/dist/sqlite/sqliteQuerier.js +4 -27
- package/dist/sqlite/sqliteQuerierPool.d.ts +14 -4
- package/dist/sqlite/sqliteQuerierPool.js +16 -5
- package/dist/turso/index.d.ts +3 -0
- package/dist/turso/index.js +3 -0
- package/dist/turso/local.d.ts +3 -0
- package/dist/turso/local.js +3 -0
- package/dist/turso/tursoDialect.d.ts +15 -0
- package/dist/turso/tursoDialect.js +18 -0
- package/dist/turso/tursoLocalQuerier.d.ts +25 -0
- package/dist/turso/tursoLocalQuerier.js +20 -0
- package/dist/turso/tursoLocalQuerierPool.d.ts +32 -0
- package/dist/turso/tursoLocalQuerierPool.js +39 -0
- package/dist/turso/tursoQuerier.d.ts +10 -0
- package/dist/turso/tursoQuerier.js +10 -0
- package/dist/turso/tursoQuerierPool.d.ts +39 -0
- package/dist/turso/tursoQuerierPool.js +38 -0
- package/dist/type/config.d.ts +0 -6
- package/dist/type/dialect.d.ts +12 -4
- package/dist/type/dialect.js +7 -1
- package/dist/type/entity.d.ts +66 -5
- package/dist/type/migration.d.ts +32 -23
- package/dist/type/vector.d.ts +5 -2
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/indexColumn.util.d.ts +6 -0
- package/dist/util/indexColumn.util.js +26 -0
- package/package.json +16 -3
- package/dist/migrate/codegen/entityMerger.d.ts +0 -114
- package/dist/migrate/codegen/entityMerger.js +0 -293
- package/dist/migrate/codegen/migrationCodeGenerator.d.ts +0 -61
- package/dist/migrate/codegen/migrationCodeGenerator.js +0 -355
- package/dist/migrate/generator/index.d.ts +0 -2
- package/dist/migrate/generator/index.js +0 -2
- package/dist/migrate/sync/index.d.ts +0 -6
- package/dist/migrate/sync/index.js +0 -6
- package/dist/migrate/sync/schemaSync.d.ts +0 -131
- package/dist/migrate/sync/schemaSync.js +0 -259
- package/dist/sqlite/betterSqlite3Dialect.d.ts +0 -9
- 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
|
-
|
|
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(
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
return `DROP TABLE
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
182
|
+
def += ' NOT NULL';
|
|
225
183
|
}
|
|
226
|
-
if (
|
|
227
|
-
|
|
184
|
+
if (column.isUnique && !column.isPrimaryKey) {
|
|
185
|
+
def += ' UNIQUE';
|
|
228
186
|
}
|
|
229
187
|
if (column.defaultValue !== undefined) {
|
|
230
|
-
|
|
188
|
+
def += ` DEFAULT ${this.formatDefaultValue(column.defaultValue)}`;
|
|
231
189
|
}
|
|
232
190
|
if (column.comment) {
|
|
233
|
-
|
|
191
|
+
def += this.generateColumnComment(column.name, column.comment);
|
|
234
192
|
}
|
|
235
|
-
return
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
|
591
|
-
return this.generateAlterColumnStatements(tableName, { name: columnName
|
|
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
|
-
|
|
541
|
+
...idxDef,
|
|
649
542
|
table,
|
|
650
|
-
columns: idxDef.columns
|
|
651
|
-
|
|
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,
|
|
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 {
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
/**
|
|
13
|
-
export declare function isVectorCategory(category: TypeCategory):
|
|
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
|
*/
|