uql-orm 0.70.0 → 0.72.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 +2 -0
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +3 -3
- package/dist/cockroachdb/cockroachDialect.d.ts +6 -0
- package/dist/cockroachdb/cockroachDialect.js +8 -0
- package/dist/d1/d1SqliteDialect.d.ts +3 -0
- package/dist/d1/d1SqliteDialect.js +3 -1
- package/dist/dialect/abstractSqlDialect.d.ts +14 -2
- package/dist/dialect/abstractSqlDialect.js +48 -16
- package/dist/dialect/aliases.d.ts +2 -0
- package/dist/dialect/aliases.js +2 -0
- package/dist/dialect/hydrateColumn.d.ts +3 -2
- package/dist/dialect/hydrateColumn.js +10 -1
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +2 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +6 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +21 -5
- package/dist/dialect/pgLikeSqlDialect.js +48 -15
- package/dist/dialect/queryJoins.d.ts +14 -4
- package/dist/dialect/queryJoins.js +31 -11
- package/dist/dialect/vectorCast.d.ts +2 -0
- package/dist/dialect/vectorCast.js +7 -0
- package/dist/dialect/vectorSqlDialect.d.ts +7 -3
- package/dist/dialect/vectorSqlDialect.js +15 -10
- package/dist/libsql/libsqlDialect.d.ts +1 -1
- package/dist/libsql/libsqlDialect.js +3 -3
- package/dist/maria/mariaDialect.d.ts +3 -9
- package/dist/maria/mariaDialect.js +5 -15
- package/dist/maria/mariadbQuerier.js +9 -3
- package/dist/migrate/ddl/index.d.ts +1 -0
- package/dist/migrate/ddl/index.js +4 -2
- package/dist/migrate/ddl/indexDdl.d.ts +2 -0
- package/dist/migrate/ddl/indexDdl.js +10 -2
- package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -4
- package/dist/migrate/ddl/pgIndexDdl.js +11 -11
- package/dist/migrate/ddl/sqliteIndexDdl.d.ts +11 -0
- package/dist/migrate/ddl/sqliteIndexDdl.js +38 -0
- package/dist/migrate/generator/mongoCommand.d.ts +28 -0
- package/dist/migrate/generator/mongoCommand.js +8 -0
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +2 -0
- package/dist/migrate/generator/mongoSchemaGenerator.js +50 -6
- package/dist/migrate/introspection/mongoIntrospector.js +33 -7
- package/dist/migrate/introspection/postgresIntrospector.js +19 -0
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +29 -3
- package/dist/mongo/mongoDialect.d.ts +5 -3
- package/dist/mongo/mongoDialect.js +40 -17
- package/dist/mongo/mongodbQuerier.js +4 -5
- package/dist/mssql/mssqlDialect.js +1 -0
- package/dist/schema/canonicalType.js +10 -4
- package/dist/schema/indexDifferences.d.ts +5 -2
- package/dist/schema/indexDifferences.js +5 -0
- package/dist/schema/schemaASTBuilder.js +3 -2
- package/dist/sqlite/localSqliteQuerierPool.d.ts +10 -11
- package/dist/sqlite/localSqliteQuerierPool.js +8 -11
- package/dist/sqlite/nodeSqliteQuerierPool.d.ts +4 -2
- package/dist/sqlite/nodeSqliteQuerierPool.js +7 -4
- package/dist/sqlite/sqliteDialect.d.ts +11 -1
- package/dist/sqlite/sqliteDialect.js +46 -3
- package/dist/sqlite/sqliteQuerierPool.d.ts +6 -5
- package/dist/sqlite/sqliteQuerierPool.js +10 -7
- package/dist/turso/tursoLocalDialect.d.ts +1 -1
- package/dist/turso/tursoLocalDialect.js +1 -1
- package/dist/turso/tursoLocalQuerierPool.d.ts +4 -5
- package/dist/turso/tursoLocalQuerierPool.js +3 -10
- package/dist/type/dialect.d.ts +5 -0
- package/dist/type/entity.d.ts +27 -13
- package/dist/type/migration.d.ts +7 -9
- package/dist/type/query.d.ts +11 -4
- package/dist/type/queryAggregate.d.ts +5 -16
- package/dist/type/utility.d.ts +13 -0
- package/dist/type/vector.d.ts +17 -0
- package/dist/type/vector.js +6 -0
- package/dist/util/ddlExpression.util.d.ts +2 -0
- package/dist/util/ddlExpression.util.js +5 -0
- package/dist/util/dialect.util.d.ts +21 -2
- package/dist/util/dialect.util.js +42 -2
- package/package.json +4 -3
- package/skills/uql-orm/SKILL.md +146 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb';
|
|
2
2
|
import { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
3
|
-
import { AGGREGATE_VALUE_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, SUM_COUNT_ALIAS, sortCountField, } from '../dialect/aliases.js';
|
|
3
|
+
import { AGGREGATE_VALUE_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, SUM_COUNT_ALIAS, sortCountField, TEXT_SCORE_ALIAS, } from '../dialect/aliases.js';
|
|
4
4
|
import { groupPathField, resolveGroupJoins, resolveQueryJoins, resolveSortableJoin, } from '../dialect/queryJoins.js';
|
|
5
5
|
import { assertSoleId, fieldOf, getMeta, relationOf, soleIdOf } from '../entity/index.js';
|
|
6
6
|
import { COUNT_RESULT_KEY } from '../type/query.js';
|
|
7
7
|
import { QueryRaw } from '../type/queryRaw.js';
|
|
8
|
-
import { aggregateOf, asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, countedRelations, entityName, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorObject, isRecord, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationAtKey, parseRelationSize, parseSortByCount, someKey, targetKeyColumns, } from '../util/index.js';
|
|
8
|
+
import { aggregateOf, asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, countedRelations, entityName, fieldUpdateOf, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isFieldUpdateOp, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorObject, isRecord, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationAtKey, parseRelationSize, parseSortByCount, rankedTextSearch, someKey, targetKeyColumns, } from '../util/index.js';
|
|
9
|
+
/** A scalar field's operator as the aggregation operator computing it. */
|
|
10
|
+
const MONGO_ARITHMETIC = { $inc: '$add', $mul: '$multiply' };
|
|
9
11
|
/** Default {@link DialectFeatures} for MongoDB. */
|
|
10
12
|
export const mongoDialectFeatures = {
|
|
11
13
|
ifNotExists: false,
|
|
@@ -18,6 +20,7 @@ export const mongoDialectFeatures = {
|
|
|
18
20
|
commentSyntax: 'none',
|
|
19
21
|
vectorIndexRequiresNotNull: false,
|
|
20
22
|
vectorSupportsLength: false,
|
|
23
|
+
vectorBytes: false,
|
|
21
24
|
supportsTimestamptz: false,
|
|
22
25
|
stringSizing: 'bounded-text',
|
|
23
26
|
supportsUnsigned: false,
|
|
@@ -453,9 +456,13 @@ export class MongoDialect extends AbstractDialect {
|
|
|
453
456
|
* means a *populated* one, at every level of the path: a lookup adds a field to the result, so one
|
|
454
457
|
* added for the sort alone would change what the caller gets back.
|
|
455
458
|
*/
|
|
456
|
-
sort(entity, sort, populate) {
|
|
459
|
+
sort(entity, { $sort: sort, $populate: populate, $where: where }) {
|
|
457
460
|
const meta = getMeta(entity);
|
|
458
461
|
const normalized = {};
|
|
462
|
+
// Refused as the SQL dialects refuse it, before MongoDB answers a missing score with its own error.
|
|
463
|
+
if (sort?.$text) {
|
|
464
|
+
rankedTextSearch(where);
|
|
465
|
+
}
|
|
459
466
|
// The same join set the lookups are built from, so what an ordering may address and what the
|
|
460
467
|
// pipeline actually produces cannot drift apart - `$sort` contributes its own to-one joins here
|
|
461
468
|
// exactly as it does on the SQL dialects.
|
|
@@ -466,6 +473,13 @@ export class MongoDialect extends AbstractDialect {
|
|
|
466
473
|
collectSort(meta, sort, joins, path, out) {
|
|
467
474
|
for (const [key, value] of Object.entries(sort ?? {})) {
|
|
468
475
|
const relation = meta.relations[key];
|
|
476
|
+
if (key === '$text') {
|
|
477
|
+
if (path) {
|
|
478
|
+
throw new TypeError(`$sort by $text is only supported on the queried entity, not on relation '${path.slice(0, -1)}'`);
|
|
479
|
+
}
|
|
480
|
+
out[TEXT_SCORE_ALIAS] = { $meta: 'textScore' };
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
469
483
|
if (!relation) {
|
|
470
484
|
// The queried entity's own vector search is lifted out before this walk, so one reaching it
|
|
471
485
|
// sits under a relation, which a `$lookup` brings in one row at a time - there is nothing to
|
|
@@ -552,7 +566,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
552
566
|
const relOpts = relationOf(meta, spec.relation);
|
|
553
567
|
const query = spec.query ?? {};
|
|
554
568
|
const tail = [
|
|
555
|
-
...(query.$sort ? [{ $sort: this.sort(relOpts.entity(), query
|
|
569
|
+
...(query.$sort ? [{ $sort: this.sort(relOpts.entity(), query) }] : []),
|
|
556
570
|
...this.pagerStages(query),
|
|
557
571
|
spec.field
|
|
558
572
|
? {
|
|
@@ -668,7 +682,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
668
682
|
return [
|
|
669
683
|
...this.matchStages(entity, q.$where, opts, this.aggregateKeys(entity, q)),
|
|
670
684
|
...this.readStages(entity, q, {
|
|
671
|
-
sort: this.sort(entity, q
|
|
685
|
+
sort: this.sort(entity, q),
|
|
672
686
|
pager: this.pagerStages(q),
|
|
673
687
|
}),
|
|
674
688
|
];
|
|
@@ -911,8 +925,14 @@ export class MongoDialect extends AbstractDialect {
|
|
|
911
925
|
const set = {};
|
|
912
926
|
const push = {};
|
|
913
927
|
const pull = {};
|
|
928
|
+
const arithmetic = {};
|
|
914
929
|
const unset = new Set();
|
|
915
930
|
for (const [key, value] of Object.entries(persistable)) {
|
|
931
|
+
if (isFieldUpdateOp(value)) {
|
|
932
|
+
const [op, operand] = fieldUpdateOf(value);
|
|
933
|
+
arithmetic[key] = { [MONGO_ARITHMETIC[op]]: [{ $ifNull: [`$${key}`, 0] }, { $literal: operand }] };
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
916
936
|
if (!isJsonUpdateOp(value)) {
|
|
917
937
|
set[key] = value;
|
|
918
938
|
continue;
|
|
@@ -930,7 +950,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
930
950
|
pull[`${key}.${path}`] = v;
|
|
931
951
|
}
|
|
932
952
|
}
|
|
933
|
-
return { set, push, pull, unset };
|
|
953
|
+
return { set, push, pull, arithmetic, unset };
|
|
934
954
|
}
|
|
935
955
|
/**
|
|
936
956
|
* Turn a persistable payload into a MongoDB update, mapping UQL's JSON operators onto their native
|
|
@@ -939,12 +959,12 @@ export class MongoDialect extends AbstractDialect {
|
|
|
939
959
|
*/
|
|
940
960
|
getUpdateFilter(persistable) {
|
|
941
961
|
const groups = this.groupUpdateOperators(persistable);
|
|
942
|
-
const { set, push, pull, unset } = groups;
|
|
962
|
+
const { set, push, pull, arithmetic, unset } = groups;
|
|
943
963
|
const exprKeys = [...Object.keys(pull), ...Object.keys(set), ...Object.keys(push)];
|
|
944
|
-
// Native `$pull` fails on a value that is no array,
|
|
945
|
-
// path in a single update document:
|
|
964
|
+
// Native `$pull` fails on a value that is no array, native `$inc`/`$mul` on a `null`, and MongoDB
|
|
965
|
+
// rejects two operators targeting one path in a single update document: each forces the pipeline form.
|
|
946
966
|
const allPaths = [...exprKeys, ...unset];
|
|
947
|
-
if (hasKeys(pull) || new Set(allPaths).size < allPaths.length) {
|
|
967
|
+
if (hasKeys(pull) || hasKeys(arithmetic) || new Set(allPaths).size < allPaths.length) {
|
|
948
968
|
return this.getUpdatePipeline(groups, new Set(exprKeys));
|
|
949
969
|
}
|
|
950
970
|
return {
|
|
@@ -954,11 +974,11 @@ export class MongoDialect extends AbstractDialect {
|
|
|
954
974
|
};
|
|
955
975
|
}
|
|
956
976
|
/**
|
|
957
|
-
*
|
|
977
|
+
* An update as one pipeline, since MongoDB refuses two operators on one path: each path composed as
|
|
958
978
|
* `$pull`, `$set`, `$push`, then `$unset`, as SQL does, with values as `$literal` so `$x` stays data.
|
|
959
979
|
*/
|
|
960
|
-
getUpdatePipeline({ set, push, pull, unset }, exprPaths) {
|
|
961
|
-
const assignments = {};
|
|
980
|
+
getUpdatePipeline({ set, push, pull, arithmetic, unset }, exprPaths) {
|
|
981
|
+
const assignments = { ...arithmetic };
|
|
962
982
|
for (const path of exprPaths) {
|
|
963
983
|
let expr = { $ifNull: [`$${path}`, []] };
|
|
964
984
|
if (path in pull) {
|
|
@@ -1016,10 +1036,10 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1016
1036
|
*/
|
|
1017
1037
|
buildAggregateStages(entity, q, opts) {
|
|
1018
1038
|
const meta = getMeta(entity);
|
|
1019
|
-
const joins = resolveGroupJoins(meta, q
|
|
1039
|
+
const { joins, where } = resolveGroupJoins(meta, q);
|
|
1020
1040
|
const { groupId, accumulators, columns, named } = this.buildGroupSpec(meta, parseGroupMap(q.$group, q.$select), joins);
|
|
1021
1041
|
const pipeline = [
|
|
1022
|
-
...this.matchStages(entity,
|
|
1042
|
+
...this.matchStages(entity, where, opts, named),
|
|
1023
1043
|
...this.lookupStages(meta, joins),
|
|
1024
1044
|
{ $group: { _id: hasKeys(groupId) ? groupId : null, ...accumulators } },
|
|
1025
1045
|
// `$group` answers with `_id` even when grouping by nothing, and with what `columns` read to the end.
|
|
@@ -1220,6 +1240,10 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1220
1240
|
}
|
|
1221
1241
|
return { vectorKey: found.key, vectorSearch: found.search, regularSort: regularSort };
|
|
1222
1242
|
}
|
|
1243
|
+
/** The Atlas index a `$vectorSearch` over `column` reads, and migrations create: its declared name, else `<column>_index`. */
|
|
1244
|
+
vectorSearchIndexName(name, column) {
|
|
1245
|
+
return name ?? `${column}_index`;
|
|
1246
|
+
}
|
|
1223
1247
|
/**
|
|
1224
1248
|
* Build a `$vectorSearch` aggregation pipeline stage.
|
|
1225
1249
|
* Merges `$where` into `$vectorSearch.filter` for optimal pre-filtering.
|
|
@@ -1231,8 +1255,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1231
1255
|
throw new TypeError(`Field '${key}' not found in entity '${meta.name}'`);
|
|
1232
1256
|
}
|
|
1233
1257
|
const colName = this.resolveColumnName(key, field);
|
|
1234
|
-
|
|
1235
|
-
const indexName = findVectorIndex(meta, key)?.name ?? `${colName}_index`;
|
|
1258
|
+
const indexName = this.vectorSearchIndexName(findVectorIndex(meta, key)?.name, colName);
|
|
1236
1259
|
if (!limit) {
|
|
1237
1260
|
throw new TypeError(`$vectorSearch requires $limit (vector sort on '${key}' of '${meta.name}')`);
|
|
1238
1261
|
}
|
|
@@ -2,7 +2,7 @@ import { AGGREGATE_VALUE_ALIAS } from '../dialect/aliases.js';
|
|
|
2
2
|
import { hasRequiredJoin } from '../dialect/queryJoins.js';
|
|
3
3
|
import { fieldOf, getMeta, namesKey, soleIdOf } from '../entity/index.js';
|
|
4
4
|
import { AbstractQuerier, enrichError } from '../querier/index.js';
|
|
5
|
-
import { clone, getKeys, getSoftDeleteValue, hasKeys, populatesRelations, throwNoPendingTransaction, throwPendingTransaction, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
|
+
import { clone, getKeys, getSoftDeleteValue, hasKeys, populatesRelations, throwNoPendingTransaction, throwPendingTransaction, vectorCandidates, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
6
6
|
/**
|
|
7
7
|
* `$limit: 0` asks for no rows, the way it does on every SQL dialect - but MongoDB reads `limit(0)`
|
|
8
8
|
* as *unlimited*, so a read that passed it straight to the driver came back with the whole
|
|
@@ -93,7 +93,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
93
93
|
if (hasKeys(select)) {
|
|
94
94
|
cursor.project(select);
|
|
95
95
|
}
|
|
96
|
-
const sort = this.dialect.sort(entity, q
|
|
96
|
+
const sort = this.dialect.sort(entity, q);
|
|
97
97
|
if (hasKeys(sort)) {
|
|
98
98
|
cursor.sort(sort);
|
|
99
99
|
}
|
|
@@ -113,13 +113,13 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
113
113
|
buildVectorPipeline(entity, q, vectorSort, opts) {
|
|
114
114
|
const scoreAlias = vectorSort.vectorSearch.$project;
|
|
115
115
|
return [
|
|
116
|
-
this.dialect.buildVectorSearchStage(entity, vectorSort.vectorKey, vectorSort.vectorSearch, q.$where, q.$limit ?? 10, opts, q
|
|
116
|
+
this.dialect.buildVectorSearchStage(entity, vectorSort.vectorKey, vectorSort.vectorSearch, q.$where, q.$limit ?? 10, opts, vectorCandidates(q)),
|
|
117
117
|
// The score becomes a real field before anything reads it, so the lookups and the projection
|
|
118
118
|
// that follow treat it like any other - and a query with no projection keeps its own columns.
|
|
119
119
|
...(scoreAlias ? [{ $addFields: { [scoreAlias]: { $meta: 'vectorSearchScore' } } }] : []),
|
|
120
120
|
// `$vectorSearch` has already applied `$limit`, so the pager is its own.
|
|
121
121
|
...this.dialect.readStages(entity, q, {
|
|
122
|
-
sort: this.dialect.sort(entity, vectorSort.regularSort
|
|
122
|
+
sort: this.dialect.sort(entity, { ...q, $sort: vectorSort.regularSort }),
|
|
123
123
|
project: scoreAlias ? { [scoreAlias]: 1 } : undefined,
|
|
124
124
|
}),
|
|
125
125
|
];
|
|
@@ -189,7 +189,6 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
189
189
|
return this.timed('internalUpdateMany', undefined, async () => {
|
|
190
190
|
const persistable = this.dialect.getPersistable(getMeta(entity), payload, 'onUpdate');
|
|
191
191
|
const filter = this.dialect.where(entity, qm.$where, opts);
|
|
192
|
-
// Maps JSON operators ($set/$unset/$push/$pull) onto their native MongoDB equivalents.
|
|
193
192
|
const update = this.dialect.getUpdateFilter(persistable);
|
|
194
193
|
const { matchedCount } = await this.execute((session) => this.collection(entity).updateMany(filter, update, { session }));
|
|
195
194
|
return matchedCount;
|
|
@@ -80,6 +80,7 @@ const SQL_TO_CANONICAL = {
|
|
|
80
80
|
image: { category: 'blob', size: 'big' },
|
|
81
81
|
// === Vector (for AI/embeddings) ===
|
|
82
82
|
vector: { category: 'vector' },
|
|
83
|
+
f32_blob: { category: 'vector' },
|
|
83
84
|
halfvec: { category: 'halfvec' },
|
|
84
85
|
sparsevec: { category: 'sparsevec' },
|
|
85
86
|
};
|
|
@@ -204,8 +205,8 @@ const ENGINE_TYPES = {
|
|
|
204
205
|
sizes: MYSQL_SIZES,
|
|
205
206
|
decimal: { precision: 10, scale: 2 },
|
|
206
207
|
},
|
|
207
|
-
// SQLite uses affinity, so no size variants.
|
|
208
|
-
sqlite: { scalars: withVectorType(SQLITE_SCALAR_MAP, '
|
|
208
|
+
// SQLite uses affinity, so no size variants. `F32_BLOB` is libSQL's vector type; elsewhere just a name of BLOB affinity.
|
|
209
|
+
sqlite: { scalars: withVectorType(SQLITE_SCALAR_MAP, 'F32_BLOB') },
|
|
209
210
|
// 2025 and up; below that the server refuses the type rather than storing it as text.
|
|
210
211
|
mssql: { scalars: withVectorType(MSSQL_SCALAR_MAP, 'VECTOR'), sizes: MSSQL_SIZES },
|
|
211
212
|
mongodb: { scalars: withVectorType(MONGO_SCALAR_MAP, 'array') },
|
|
@@ -238,7 +239,7 @@ export function sqlToCanonical(sqlType) {
|
|
|
238
239
|
const unsigned = normalized.includes('unsigned');
|
|
239
240
|
const withoutUnsigned = normalized.replace(/\s*unsigned\s*/i, ' ').trim();
|
|
240
241
|
// Extract base type and parameters: "VARCHAR(255)" -> ["varchar", "255"]
|
|
241
|
-
const match = withoutUnsigned.match(/^([a-z][a-z0-
|
|
242
|
+
const match = withoutUnsigned.match(/^([a-z][a-z0-9_ ]*?)(?:\(([^)]+)\))?$/);
|
|
242
243
|
const base = match ? SQL_TO_CANONICAL[match[1]] : undefined;
|
|
243
244
|
if (!match || !base) {
|
|
244
245
|
return { category: 'string', raw: sqlType };
|
|
@@ -332,8 +333,13 @@ export function canonicalToTypeScript(type) {
|
|
|
332
333
|
* the engine settles an unstated bound. Migrations and drift both compare through it.
|
|
333
334
|
*/
|
|
334
335
|
export function engineType(dialect) {
|
|
335
|
-
return (type) =>
|
|
336
|
+
return (type) => {
|
|
337
|
+
const stored = sqlToCanonical(canonicalToSql(type, dialect));
|
|
338
|
+
// SQLite keeps a vector in any column, so one created as `TEXT` before vectors were blobs stays as it is.
|
|
339
|
+
return dialect.dialectName === 'sqlite' && isVectorCategory(stored.category) ? SQLITE_TEXT : stored;
|
|
340
|
+
};
|
|
336
341
|
}
|
|
342
|
+
const SQLITE_TEXT = { category: 'string', size: 'small' };
|
|
337
343
|
/**
|
|
338
344
|
* Convert UQL FieldOptions to a canonical type.
|
|
339
345
|
*/
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { IndexNode } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* What an introspector reports about an index, and so all a diff may compare; apart from `IndexFeature`, what an engine emits.
|
|
4
|
+
* `vector` is whether it is a vector index at all, for an engine with one vector index whatever type declared it.
|
|
5
|
+
*/
|
|
6
|
+
export type IndexFacet = 'order' | 'nulls' | 'opsClass' | 'accessMethod' | 'include' | 'vector';
|
|
4
7
|
/**
|
|
5
8
|
* Whether the table has this index already, by shape rather than name, uniqueness included. An index
|
|
6
9
|
* over an expression, whose text the engine reprints, falls back to its name.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isVectorIndexType } from '../type/vector.js';
|
|
1
2
|
/**
|
|
2
3
|
* Whether the table has this index already, by shape rather than name, uniqueness included. An index
|
|
3
4
|
* over an expression, whose text the engine reprints, falls back to its name.
|
|
@@ -45,6 +46,10 @@ export function describeIndexDifferences(source, target, facets) {
|
|
|
45
46
|
if (facets.has('accessMethod') && (source.type ?? 'btree') !== (target.type ?? 'btree')) {
|
|
46
47
|
differences.push(`type: ${target.type ?? 'btree'} -> ${source.type ?? 'btree'}`);
|
|
47
48
|
}
|
|
49
|
+
if (facets.has('vector') && isVectorIndexType(source.type) !== isVectorIndexType(target.type)) {
|
|
50
|
+
const [expected, actual] = [source, target].map((index) => (isVectorIndexType(index.type) ? 'yes' : 'no'));
|
|
51
|
+
differences.push(`vector index: ${actual} -> ${expected}`);
|
|
52
|
+
}
|
|
48
53
|
if (facets.has('include')) {
|
|
49
54
|
// Order carries no meaning in an `INCLUDE` list, so it is compared as a set.
|
|
50
55
|
const [sourceInclude, targetInclude] = [source.include ?? [], target.include ?? []].map((columns) => [...columns].sort().join(', '));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fieldOf, foreignKeysOf, getMeta, soleIdOf } from '../entity/metadata/definition.js';
|
|
2
|
-
import { declaredIndexes,
|
|
2
|
+
import { declaredIndexes, declaredIndexName, renderIndexColumn } from '../util/ddlExpression.util.js';
|
|
3
3
|
import { isInlinedExpression } from '../util/field.util.js';
|
|
4
4
|
import { isSoleIdField } from '../util/field.util.js';
|
|
5
5
|
import { isAutoIncrement } from '../util/field.util.js';
|
|
@@ -198,7 +198,7 @@ function addCompositeIndex(ctx, table, meta, idxMeta) {
|
|
|
198
198
|
});
|
|
199
199
|
if (!resolved.length)
|
|
200
200
|
return;
|
|
201
|
-
const name = idxMeta.name
|
|
201
|
+
const name = declaredIndexName(idxMeta.name, table.name, resolved);
|
|
202
202
|
ctx.ast.addIndex({
|
|
203
203
|
name,
|
|
204
204
|
table,
|
|
@@ -211,5 +211,6 @@ function addCompositeIndex(ctx, table, meta, idxMeta) {
|
|
|
211
211
|
m: idxMeta.m,
|
|
212
212
|
efConstruction: idxMeta.efConstruction,
|
|
213
213
|
lists: idxMeta.lists,
|
|
214
|
+
config: idxMeta.config,
|
|
214
215
|
});
|
|
215
216
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import { type SqlitePreparedStatement, SqliteQuerier } from './sqliteQuerier.js';
|
|
2
|
+
import type { SqliteDialect } from './sqliteDialect.js';
|
|
3
|
+
import { type SqliteDatabase, type SqlitePreparedStatement, SqliteQuerier } from './sqliteQuerier.js';
|
|
5
4
|
/** What every local SQLite pool accepts on top of its driver's own options. */
|
|
6
5
|
export type LocalSqlitePoolOptions = {
|
|
7
6
|
/**
|
|
@@ -27,12 +26,12 @@ export type LocalSqliteDatabase = {
|
|
|
27
26
|
export declare function adaptSqlite<S extends Omit<SqlitePreparedStatement, 'reader'>>(db: Omit<LocalSqliteDatabase, 'prepare'> & {
|
|
28
27
|
prepare(sql: string): S;
|
|
29
28
|
}, reads: (stmt: S) => boolean): LocalSqliteDatabase;
|
|
30
|
-
/**
|
|
31
|
-
export declare
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/** Opens the driver's database,
|
|
35
|
-
protected abstract createDb(): Promise<
|
|
36
|
-
protected openDb(): Promise<
|
|
37
|
-
protected buildQuerier(db:
|
|
29
|
+
/** `db` with each loadable extension installed, which `node:sqlite` refuses unless opened to allow them. */
|
|
30
|
+
export declare function loadExtensions(db: LocalSqliteDatabase, extensions?: readonly string[]): LocalSqliteDatabase;
|
|
31
|
+
/** A pool for a database file opened in this process, configured the same way whichever driver's {@link createDb} opens it. */
|
|
32
|
+
export declare abstract class AbstractLocalSqliteQuerierPool<DB extends SqliteDatabase, D extends SqliteDialect> extends AbstractSharedHandleQuerierPool<DB, SqliteQuerier, D> {
|
|
33
|
+
/** Opens the driver's database, reading integers as `bigint`, which the querier decodes exactly past 2^53. */
|
|
34
|
+
protected abstract createDb(): Promise<DB>;
|
|
35
|
+
protected openDb(): Promise<DB>;
|
|
36
|
+
protected buildQuerier(db: DB): SqliteQuerier;
|
|
38
37
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
2
1
|
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
3
|
-
import { SqliteDialect } from './sqliteDialect.js';
|
|
4
2
|
import { applySqlitePragmas } from './sqlitePragmas.js';
|
|
5
3
|
import { SqliteQuerier } from './sqliteQuerier.js';
|
|
6
4
|
/**
|
|
@@ -22,19 +20,18 @@ export function adaptSqlite(db, reads) {
|
|
|
22
20
|
close: () => db.close(),
|
|
23
21
|
};
|
|
24
22
|
}
|
|
25
|
-
/**
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
super(new SqliteDialect(dialectOptionsFrom(extra)), extra);
|
|
30
|
-
this.opts = opts;
|
|
23
|
+
/** `db` with each loadable extension installed, which `node:sqlite` refuses unless opened to allow them. */
|
|
24
|
+
export function loadExtensions(db, extensions = []) {
|
|
25
|
+
for (const extension of extensions) {
|
|
26
|
+
db.loadExtension(extension);
|
|
31
27
|
}
|
|
28
|
+
return db;
|
|
29
|
+
}
|
|
30
|
+
/** A pool for a database file opened in this process, configured the same way whichever driver's {@link createDb} opens it. */
|
|
31
|
+
export class AbstractLocalSqliteQuerierPool extends AbstractSharedHandleQuerierPool {
|
|
32
32
|
async openDb() {
|
|
33
33
|
const db = await this.createDb();
|
|
34
34
|
await applySqlitePragmas(db);
|
|
35
|
-
for (const extension of this.opts?.extensions ?? []) {
|
|
36
|
-
db.loadExtension(extension);
|
|
37
|
-
}
|
|
38
35
|
return db;
|
|
39
36
|
}
|
|
40
37
|
buildQuerier(db) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtraOptions } from '../type/index.js';
|
|
2
2
|
import { AbstractLocalSqliteQuerierPool, type LocalSqliteDatabase, type LocalSqlitePoolOptions } from './localSqliteQuerierPool.js';
|
|
3
|
+
import { SqliteDialect } from './sqliteDialect.js';
|
|
3
4
|
/**
|
|
4
5
|
* The `DatabaseSync` options worth surfacing, plus the loadable extensions to install. Declared here
|
|
5
6
|
* rather than imported from `node:sqlite` so this module needs no ambient Node types; unknown keys
|
|
@@ -15,8 +16,9 @@ export type NodeSqlitePoolOptions = LocalSqlitePoolOptions & {
|
|
|
15
16
|
* A pool over Node's built-in `node:sqlite`, needing no dependency at all. {@link Sqlite3QuerierPool} is the
|
|
16
17
|
* faster choice for read-heavy work, and the one on Bun.
|
|
17
18
|
*/
|
|
18
|
-
export declare class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool<
|
|
19
|
+
export declare class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool<LocalSqliteDatabase, SqliteDialect> {
|
|
19
20
|
readonly filename: string;
|
|
20
|
-
|
|
21
|
+
readonly opts?: NodeSqlitePoolOptions | undefined;
|
|
22
|
+
constructor(filename?: string, opts?: NodeSqlitePoolOptions | undefined, extra?: ExtraOptions);
|
|
21
23
|
protected createDb(): Promise<LocalSqliteDatabase>;
|
|
22
24
|
}
|
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
2
|
+
import { AbstractLocalSqliteQuerierPool, adaptSqlite, loadExtensions, } from './localSqliteQuerierPool.js';
|
|
3
|
+
import { SqliteDialect } from './sqliteDialect.js';
|
|
2
4
|
/**
|
|
3
5
|
* A pool over Node's built-in `node:sqlite`, needing no dependency at all. {@link Sqlite3QuerierPool} is the
|
|
4
6
|
* faster choice for read-heavy work, and the one on Bun.
|
|
5
7
|
*/
|
|
6
8
|
export class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool {
|
|
7
9
|
filename;
|
|
10
|
+
opts;
|
|
8
11
|
constructor(filename = ':memory:', opts, extra) {
|
|
9
|
-
super(
|
|
12
|
+
super(new SqliteDialect(dialectOptionsFrom(extra)), extra);
|
|
10
13
|
this.filename = filename;
|
|
14
|
+
this.opts = opts;
|
|
11
15
|
}
|
|
12
16
|
async createDb() {
|
|
13
17
|
const { DatabaseSync } = await import('node:sqlite');
|
|
14
18
|
const { extensions, ...driverOpts } = this.opts ?? {};
|
|
15
19
|
const nodeDb = new DatabaseSync(this.filename, {
|
|
16
20
|
...driverOpts,
|
|
17
|
-
// Integers as `bigint`, which the querier decodes exactly past 2^53.
|
|
18
21
|
readBigInts: true,
|
|
19
22
|
// `node:sqlite` refuses `loadExtension` unless the database was opened with this on.
|
|
20
23
|
...(extensions?.length ? { allowExtension: true } : undefined),
|
|
21
24
|
});
|
|
22
|
-
return adaptSqlite(nodeDb, (stmt) => stmt.columns().length > 0);
|
|
25
|
+
return loadExtensions(adaptSqlite(nodeDb, (stmt) => stmt.columns().length > 0), extensions);
|
|
23
26
|
}
|
|
24
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AbstractSqlDialect, type DerivedRelation, type HydrateKind, type RelationRows } from '../dialect/abstractSqlDialect.js';
|
|
2
2
|
import { type JsonAccessMode, type JsonSlot } from '../dialect/jsonSql.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type EntityMeta, type FieldOptions, type Query, type QueryContext, type QueryPager, type QueryTextSearchOptions, type QueryWhere, type SqlDialectFeatures, type Type, type VectorDistance, type VectorMetric } from '../type/index.js';
|
|
4
4
|
/** What SQLite and the engines derived from it have. */
|
|
5
5
|
export declare const SQLITE_FEATURES: SqlDialectFeatures;
|
|
6
6
|
export declare class SqliteDialect extends AbstractSqlDialect {
|
|
@@ -24,6 +24,12 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
24
24
|
* their own vector functions instead, so `LibsqlDialect` overrides this.
|
|
25
25
|
*/
|
|
26
26
|
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorMetric>;
|
|
27
|
+
/**
|
|
28
|
+
* A read ranked by the metric its field's vector index measures, and paged, narrowed to the rowids of
|
|
29
|
+
* that index's nearest rows, which libSQL's `vector_top_k` answers: `$candidates` of them, else as many
|
|
30
|
+
* as the page reaches. Their exact distance still orders them. Unchanged on an engine with no such index.
|
|
31
|
+
*/
|
|
32
|
+
protected rankedWhere<E>(meta: EntityMeta<E>, q: Query<E>, prefix: string | undefined): QueryWhere<E> | undefined;
|
|
27
33
|
/**
|
|
28
34
|
* SQLite does not support the `DEFAULT` keyword inside `VALUES`. Inline the metadata default
|
|
29
35
|
* when declared, else `NULL` (which is also how SQLite auto-generates INTEGER PRIMARY KEYs).
|
|
@@ -51,7 +57,9 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
51
57
|
protected readonly carriedFields: {
|
|
52
58
|
numeric: (expr: string, field: FieldOptions) => string;
|
|
53
59
|
blob: (expr: string) => string;
|
|
60
|
+
vector: (expr: string) => string;
|
|
54
61
|
};
|
|
62
|
+
private bytesAsText;
|
|
55
63
|
/** A date reads back as SQLite stored it, a number or text, which JSON carries unchanged. */
|
|
56
64
|
protected hydrateKind(field: FieldOptions | undefined): HydrateKind | undefined;
|
|
57
65
|
/**
|
|
@@ -59,6 +67,8 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
59
67
|
* FTS5 virtual table (UQL does not create those; declare it outside your entities).
|
|
60
68
|
*/
|
|
61
69
|
protected appendTextSearch<E>(ctx: QueryContext, entity: Type<E>, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
|
|
70
|
+
/** FTS5's `BM25` of the match, lower for a better one, so negated to rank as every other engine does. */
|
|
71
|
+
protected appendTextRank<E>(ctx: QueryContext, meta: EntityMeta<E>): void;
|
|
62
72
|
protected jsonLength(slot: JsonSlot): string;
|
|
63
73
|
/** `JSON_EACH` walks the array at the path itself, each element's `fullkey` naming it from the column. */
|
|
64
74
|
protected jsonElemFrom(slot: JsonSlot, alias: string): string;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { AbstractSqlDialect, } from '../dialect/abstractSqlDialect.js';
|
|
2
2
|
import { BYTES_PREFIX } from '../dialect/hydrateColumn.js';
|
|
3
3
|
import { chainedCall, groupsPerCall, jsonSetCall, jsonPath, jsonArraySlotArgs, jsonSlotArgs, jsonRemoveCall, jsonSetTarget, } from '../dialect/jsonSql.js';
|
|
4
|
-
import {
|
|
4
|
+
import { QueryRaw, } from '../type/index.js';
|
|
5
|
+
import { indexDistance, isVectorIndexType } from '../type/vector.js';
|
|
6
|
+
import { declaredIndexName } from '../util/ddlExpression.util.js';
|
|
7
|
+
import { findVectorIndex, findVectorSort, textSearchFields, vectorCandidates } from '../util/dialect.util.js';
|
|
5
8
|
import { columnFamily, isIntegerColumn } from '../util/field.util.js';
|
|
6
9
|
/** What SQLite and the engines derived from it have. */
|
|
7
10
|
export const SQLITE_FEATURES = {
|
|
@@ -14,7 +17,8 @@ export const SQLITE_FEATURES = {
|
|
|
14
17
|
generatedColumnAdd: false, // accepted in a CREATE TABLE, rejected in an ALTER
|
|
15
18
|
commentSyntax: 'none',
|
|
16
19
|
vectorIndexRequiresNotNull: false,
|
|
17
|
-
vectorSupportsLength:
|
|
20
|
+
vectorSupportsLength: true,
|
|
21
|
+
vectorBytes: true,
|
|
18
22
|
supportsTimestamptz: false,
|
|
19
23
|
stringSizing: 'text',
|
|
20
24
|
supportsUnsigned: false,
|
|
@@ -55,6 +59,37 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
55
59
|
['l2', { fn: 'vec_distance_L2' }],
|
|
56
60
|
['l1', { fn: 'vec_distance_L1' }],
|
|
57
61
|
]);
|
|
62
|
+
/**
|
|
63
|
+
* A read ranked by the metric its field's vector index measures, and paged, narrowed to the rowids of
|
|
64
|
+
* that index's nearest rows, which libSQL's `vector_top_k` answers: `$candidates` of them, else as many
|
|
65
|
+
* as the page reaches. Their exact distance still orders them. Unchanged on an engine with no such index.
|
|
66
|
+
*/
|
|
67
|
+
rankedWhere(meta, q, prefix) {
|
|
68
|
+
const ranked = findVectorSort(q.$sort);
|
|
69
|
+
const k = vectorCandidates(q) ?? (q.$limit === undefined ? undefined : (q.$skip ?? 0) + q.$limit);
|
|
70
|
+
const index = ranked && findVectorIndex(meta, ranked.key);
|
|
71
|
+
if (!ranked || !index || !isVectorIndexType(index.type) || k === undefined) {
|
|
72
|
+
return q.$where;
|
|
73
|
+
}
|
|
74
|
+
const { colName, distance, field } = this.resolveVectorDistance(meta, ranked.key, ranked.search);
|
|
75
|
+
if (indexDistance(index) !== distance || !this.vectorMetrics.get(distance)?.index) {
|
|
76
|
+
return q.$where;
|
|
77
|
+
}
|
|
78
|
+
const name = declaredIndexName(index.name, this.resolveTableName(meta), [{ column: colName }]);
|
|
79
|
+
const table = this.escapeId(prefix ?? this.resolveTableAlias(meta), true, true);
|
|
80
|
+
const nearest = new QueryRaw(({ ctx }) => {
|
|
81
|
+
ctx.append(`${table}rowid IN (SELECT id FROM vector_top_k(`);
|
|
82
|
+
ctx.addValue(name);
|
|
83
|
+
ctx.append(', ');
|
|
84
|
+
this.appendVectorValue(ctx, ranked.search.$vector, field);
|
|
85
|
+
ctx.append(', ');
|
|
86
|
+
ctx.addValue(k);
|
|
87
|
+
ctx.append('))');
|
|
88
|
+
});
|
|
89
|
+
const where = {};
|
|
90
|
+
where.$and = q.$where ? [q.$where, nearest] : [nearest];
|
|
91
|
+
return where;
|
|
92
|
+
}
|
|
58
93
|
/**
|
|
59
94
|
* SQLite does not support the `DEFAULT` keyword inside `VALUES`. Inline the metadata default
|
|
60
95
|
* when declared, else `NULL` (which is also how SQLite auto-generates INTEGER PRIMARY KEYs).
|
|
@@ -109,8 +144,12 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
109
144
|
*/
|
|
110
145
|
carriedFields = {
|
|
111
146
|
numeric: (expr, field) => (isIntegerColumn(field) ? `CAST(${expr} AS TEXT)` : expr),
|
|
112
|
-
blob: (expr) =>
|
|
147
|
+
blob: (expr) => this.bytesAsText(expr),
|
|
148
|
+
vector: (expr) => `CASE WHEN typeof(${expr}) = 'blob' THEN ${this.bytesAsText(expr)} ELSE ${expr} END`,
|
|
113
149
|
};
|
|
150
|
+
bytesAsText(expr) {
|
|
151
|
+
return `${this.escape(BYTES_PREFIX)} || hex(${expr})`;
|
|
152
|
+
}
|
|
114
153
|
/** A date reads back as SQLite stored it, a number or text, which JSON carries unchanged. */
|
|
115
154
|
hydrateKind(field) {
|
|
116
155
|
return columnFamily(field?.type) === 'date' ? undefined : super.hydrateKind(field);
|
|
@@ -124,6 +163,10 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
124
163
|
ctx.append(`${this.escapedTableName(meta)} MATCH {${columns.join(' ')}} : `);
|
|
125
164
|
ctx.addValue(search.$value);
|
|
126
165
|
}
|
|
166
|
+
/** FTS5's `BM25` of the match, lower for a better one, so negated to rank as every other engine does. */
|
|
167
|
+
appendTextRank(ctx, meta) {
|
|
168
|
+
ctx.append(`-BM25(${this.escapedTableName(meta)})`);
|
|
169
|
+
}
|
|
127
170
|
jsonLength(slot) {
|
|
128
171
|
return `JSON_ARRAY_LENGTH(${jsonArraySlotArgs(slot, this.jsonIsArray(slot))})`;
|
|
129
172
|
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import type { Options } from 'better-sqlite3';
|
|
2
2
|
import type { ExtraOptions } from '../type/index.js';
|
|
3
3
|
import { AbstractLocalSqliteQuerierPool, type LocalSqliteDatabase, type LocalSqlitePoolOptions } from './localSqliteQuerierPool.js';
|
|
4
|
+
import { SqliteDialect } from './sqliteDialect.js';
|
|
4
5
|
/** Driver options, plus the loadable extensions to install on the connection. */
|
|
5
6
|
export type Sqlite3PoolOptions = Options & LocalSqlitePoolOptions;
|
|
6
7
|
/**
|
|
7
8
|
* Pool for `better-sqlite3`, or `bun:sqlite` when running under Bun - the same file, through whichever
|
|
8
9
|
* driver the runtime provides.
|
|
9
10
|
*/
|
|
10
|
-
export declare class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool<
|
|
11
|
+
export declare class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool<LocalSqliteDatabase, SqliteDialect> {
|
|
11
12
|
readonly filename: string | Buffer;
|
|
12
|
-
|
|
13
|
+
readonly opts?: Sqlite3PoolOptions | undefined;
|
|
14
|
+
constructor(filename?: string | Buffer, opts?: Sqlite3PoolOptions | undefined, extra?: ExtraOptions);
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* serialized database - which better-sqlite3 takes as its filename - is deserialized there instead.
|
|
16
|
+
* `bun:sqlite` rejects option keys it does not know, so `extensions` is stripped out, and opens only a
|
|
17
|
+
* path, so a serialized database - which better-sqlite3 takes as its filename - is deserialized there instead.
|
|
17
18
|
*/
|
|
18
19
|
protected createDb(): Promise<LocalSqliteDatabase>;
|
|
19
20
|
}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
2
|
+
import { AbstractLocalSqliteQuerierPool, adaptSqlite, loadExtensions, } from './localSqliteQuerierPool.js';
|
|
3
|
+
import { SqliteDialect } from './sqliteDialect.js';
|
|
2
4
|
/**
|
|
3
5
|
* Pool for `better-sqlite3`, or `bun:sqlite` when running under Bun - the same file, through whichever
|
|
4
6
|
* driver the runtime provides.
|
|
5
7
|
*/
|
|
6
8
|
export class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool {
|
|
7
9
|
filename;
|
|
10
|
+
opts;
|
|
8
11
|
constructor(filename = ':memory:', opts, extra) {
|
|
9
|
-
super(
|
|
12
|
+
super(new SqliteDialect(dialectOptionsFrom(extra)), extra);
|
|
10
13
|
this.filename = filename;
|
|
14
|
+
this.opts = opts;
|
|
11
15
|
}
|
|
12
16
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* serialized database - which better-sqlite3 takes as its filename - is deserialized there instead.
|
|
17
|
+
* `bun:sqlite` rejects option keys it does not know, so `extensions` is stripped out, and opens only a
|
|
18
|
+
* path, so a serialized database - which better-sqlite3 takes as its filename - is deserialized there instead.
|
|
16
19
|
*/
|
|
17
20
|
async createDb() {
|
|
18
21
|
const { extensions, ...driverOpts } = this.opts ?? {};
|
|
@@ -22,9 +25,9 @@ export class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool {
|
|
|
22
25
|
const bunDb = typeof this.filename === 'string'
|
|
23
26
|
? new Database(this.filename, bunOpts)
|
|
24
27
|
: Database.deserialize(this.filename, bunOpts);
|
|
25
|
-
return adaptSqlite(bunDb, (stmt) => stmt.columnNames.length > 0);
|
|
28
|
+
return loadExtensions(adaptSqlite(bunDb, (stmt) => stmt.columnNames.length > 0), extensions);
|
|
26
29
|
}
|
|
27
30
|
const { default: BetterSqlite3 } = await import('better-sqlite3');
|
|
28
|
-
return new BetterSqlite3(this.filename, driverOpts).defaultSafeIntegers(true);
|
|
31
|
+
return loadExtensions(new BetterSqlite3(this.filename, driverOpts).defaultSafeIntegers(true), extensions);
|
|
29
32
|
}
|
|
30
33
|
}
|
|
@@ -2,7 +2,7 @@ import type { VectorDistance, VectorMetric } from '../type/index.js';
|
|
|
2
2
|
import { TursoDialect } from './tursoDialect.js';
|
|
3
3
|
/**
|
|
4
4
|
* SQLite Dialect specialization for the embedded Turso engine, the Rust engine alone: it adds a
|
|
5
|
-
* dot-product distance to libSQL's cosine and L2,
|
|
5
|
+
* dot-product distance to libSQL's cosine and L2, caps no function call, and has no vector index.
|
|
6
6
|
*/
|
|
7
7
|
export declare class TursoLocalDialect extends TursoDialect {
|
|
8
8
|
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorMetric>;
|