@atscript/db-sqlite 0.1.142 → 0.1.144

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/dist/index.cjs CHANGED
@@ -1319,13 +1319,21 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1319
1319
  for (const idx of this._getFulltextIndexes()) indexes.push({
1320
1320
  name: idx.name,
1321
1321
  description: `FTS5 index (${idx.fields.map((f) => f.name).join(", ")})`,
1322
- type: "text"
1323
- });
1324
- for (const [field, vec] of this._vectorFields) indexes.push({
1325
- name: vec.indexName,
1326
- description: `vec0 index on ${field} (${vec.dimensions}, ${vec.similarity})`,
1327
- type: "vector"
1322
+ type: "text",
1323
+ fields: this._indexLogicalPaths(idx),
1324
+ isDefault: indexes.length === 0
1328
1325
  });
1326
+ let firstVector = true;
1327
+ for (const [field, vec] of this._vectorFields) {
1328
+ indexes.push({
1329
+ name: vec.indexName,
1330
+ description: `vec0 index on ${field} (${vec.dimensions}, ${vec.similarity})`,
1331
+ type: "vector",
1332
+ fields: [field],
1333
+ isDefault: firstVector
1334
+ });
1335
+ firstVector = false;
1336
+ }
1329
1337
  return indexes;
1330
1338
  }
1331
1339
  async search(text, query, indexName) {
@@ -1380,10 +1388,10 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1380
1388
  /** Resolves a fulltext index by name, or returns the first available. */
1381
1389
  _resolveFtsIndex(indexName) {
1382
1390
  const ftIndexes = this._getFulltextIndexes();
1383
- if (ftIndexes.length === 0) throw new Error("No search index available");
1391
+ if (ftIndexes.length === 0) throw new Error((0, _atscript_db.searchIndexNotFoundMessage)());
1384
1392
  if (indexName) {
1385
1393
  const found = ftIndexes.find((idx) => idx.name === indexName);
1386
- if (!found) throw new Error(`Search index "${indexName}" not found`);
1394
+ if (!found) throw new Error((0, _atscript_db.searchIndexNotFoundMessage)(indexName));
1387
1395
  return found;
1388
1396
  }
1389
1397
  return ftIndexes[0];
@@ -1494,23 +1502,18 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1494
1502
  async vectorSearchWithCount(vector, query, indexName) {
1495
1503
  if (!this._detectVectorSupport()) throw new Error("Vector search requires the sqlite-vec extension. Construct BetterSqlite3Driver with { vector: true }.");
1496
1504
  const base = this._buildVectorSearchBase(vector, query, indexName);
1497
- const countSql = `SELECT COUNT(*) AS cnt ${base.fromWhere}`;
1505
+ const count = (0, _atscript_db_sql_tools.buildVectorSearchCount)(sqliteDialect, base.source, base);
1498
1506
  return this._stmt(() => {
1499
1507
  const data = this._runVectorSearch(base);
1500
- this._log(countSql, base.params);
1508
+ this._log(count.sql, count.params);
1501
1509
  return {
1502
1510
  data,
1503
- count: this.driver.get(countSql, base.params)?.cnt ?? 0
1511
+ count: this.driver.get(count.sql, count.params)?.cnt ?? 0
1504
1512
  };
1505
1513
  });
1506
1514
  }
1507
1515
  _runVectorSearch(base) {
1508
- let sql = `SELECT * ${base.fromWhere} ORDER BY _distance ASC LIMIT ?`;
1509
- const params = [...base.params, base.limit];
1510
- if (base.skip > 0) {
1511
- sql += ` OFFSET ?`;
1512
- params.push(base.skip);
1513
- }
1516
+ const { sql, params } = (0, _atscript_db_sql_tools.buildVectorSearchSelect)(sqliteDialect, base.source, base);
1514
1517
  this._log(sql, params);
1515
1518
  return this.driver.all(sql, params);
1516
1519
  }
@@ -1522,7 +1525,7 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1522
1525
  entry = [f, v];
1523
1526
  break;
1524
1527
  }
1525
- if (!entry) throw new Error(`Vector index "${indexName}" not found`);
1528
+ if (!entry) throw new Error((0, _atscript_db.vectorIndexNotFoundMessage)(indexName));
1526
1529
  } else {
1527
1530
  const first = this._vectorFields.entries().next();
1528
1531
  if (first.done) throw new Error("No vector fields defined");
@@ -1568,9 +1571,10 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1568
1571
  };
1569
1572
  }
1570
1573
  /**
1571
- * Builds the shared FROM+WHERE fragment for vec0 KNN queries (without ORDER/LIMIT).
1572
- * Both `vectorSearch` and `vectorSearchWithCount` reuse this — the former appends
1573
- * ORDER BY + LIMIT/OFFSET, the latter wraps it in a COUNT(*).
1574
+ * Builds the shared parts of a vec0 KNN query: the row source (the vec0
1575
+ * index joined to the table), the threshold, the residual filter and the
1576
+ * page. `vectorSearch` pages it (`buildVectorSearchSelect`),
1577
+ * `vectorSearchWithCount` also counts it (`buildVectorSearchCount`).
1574
1578
  */
1575
1579
  _buildVectorSearchBase(vector, query, indexName) {
1576
1580
  const { vec, partitionPhysicalNames } = this._resolveVectorIndex(indexName);
@@ -1580,35 +1584,29 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1580
1584
  const limit = controls.$limit ?? 20;
1581
1585
  const skip = controls.$skip ?? 0;
1582
1586
  const threshold = this._resolveVectorThreshold(controls, vec.indexName);
1583
- const { partition, residual } = this._splitVectorFilter(query.filter, partitionPhysicalNames);
1584
- const residualWhere = buildPrefixedWhere("_vs", residual);
1585
- const hasResidual = residualWhere.sql !== "1=1";
1587
+ const split = this._splitVectorFilter(query.filter, partitionPhysicalNames);
1588
+ const residual = buildPrefixedWhere("_v", split.residual);
1589
+ const hasResidual = residual.sql !== "1=1";
1586
1590
  const hasThreshold = threshold !== void 0;
1587
1591
  const k = (limit + skip) * (hasResidual || hasThreshold ? SqliteAdapter._RESIDUAL_OVERFETCH : 1);
1588
1592
  const tableName = this.resolveTableName();
1589
1593
  const vecTable = this._vecTableName(vec.indexName);
1590
1594
  const innerWhereParts = ["v.embedding MATCH ?", "v.k = ?"];
1591
1595
  const params = [vecBuf, k];
1592
- for (const p of partition) {
1596
+ for (const p of split.partition) {
1593
1597
  innerWhereParts.push(`v."${esc(p.name)}" = ?`);
1594
1598
  params.push(p.value);
1595
1599
  }
1596
- let fromWhere = `FROM (${`SELECT t.*, v.distance AS _distance FROM "${esc(vecTable)}" v JOIN "${esc(tableName)}" t ON t.rowid = v.rowid WHERE ${innerWhereParts.join(" AND ")}`}) _vs`;
1597
- const outerWhereParts = [];
1598
- if (hasThreshold) {
1599
- outerWhereParts.push(`_distance <= ?`);
1600
- params.push(thresholdToVecDistance(threshold, vec.similarity));
1601
- }
1602
- if (hasResidual) {
1603
- outerWhereParts.push(`(${residualWhere.sql})`);
1604
- params.push(...residualWhere.params);
1605
- }
1606
- if (outerWhereParts.length > 0) fromWhere += ` WHERE ${outerWhereParts.join(" AND ")}`;
1607
1600
  return {
1608
- fromWhere,
1609
- params,
1601
+ source: {
1602
+ sql: `SELECT t.*, v.distance AS _distance FROM "${esc(vecTable)}" v JOIN "${esc(tableName)}" t ON t.rowid = v.rowid WHERE ${innerWhereParts.join(" AND ")}`,
1603
+ params
1604
+ },
1610
1605
  limit,
1611
- skip
1606
+ skip,
1607
+ maxDistance: hasThreshold ? thresholdToVecDistance(threshold, vec.similarity) : void 0,
1608
+ residual,
1609
+ select: query.controls?.$select
1612
1610
  };
1613
1611
  }
1614
1612
  /**
@@ -1667,10 +1665,7 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1667
1665
  };
1668
1666
  }
1669
1667
  _buildGeoSearchSelect(ctx) {
1670
- return (0, _atscript_db_sql_tools.buildGeoSearchSelect)(sqliteDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window, {
1671
- $limit: ctx.controls.$limit,
1672
- $skip: ctx.controls.$skip
1673
- });
1668
+ return (0, _atscript_db_sql_tools.buildGeoSearchSelect)(sqliteDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window, ctx.controls);
1674
1669
  }
1675
1670
  /** Builds vec0 shadow table name from index name: `<table>__vec__<indexName>`. */
1676
1671
  _vecTableName(indexName) {
package/dist/index.d.cts CHANGED
@@ -448,9 +448,10 @@ declare class SqliteAdapter extends BaseDbAdapter {
448
448
  */
449
449
  private _splitVectorFilter;
450
450
  /**
451
- * Builds the shared FROM+WHERE fragment for vec0 KNN queries (without ORDER/LIMIT).
452
- * Both `vectorSearch` and `vectorSearchWithCount` reuse this — the former appends
453
- * ORDER BY + LIMIT/OFFSET, the latter wraps it in a COUNT(*).
451
+ * Builds the shared parts of a vec0 KNN query: the row source (the vec0
452
+ * index joined to the table), the threshold, the residual filter and the
453
+ * page. `vectorSearch` pages it (`buildVectorSearchSelect`),
454
+ * `vectorSearchWithCount` also counts it (`buildVectorSearchCount`).
454
455
  */
455
456
  private _buildVectorSearchBase;
456
457
  /**
package/dist/index.d.mts CHANGED
@@ -448,9 +448,10 @@ declare class SqliteAdapter extends BaseDbAdapter {
448
448
  */
449
449
  private _splitVectorFilter;
450
450
  /**
451
- * Builds the shared FROM+WHERE fragment for vec0 KNN queries (without ORDER/LIMIT).
452
- * Both `vectorSearch` and `vectorSearchWithCount` reuse this — the former appends
453
- * ORDER BY + LIMIT/OFFSET, the latter wraps it in a COUNT(*).
451
+ * Builds the shared parts of a vec0 KNN query: the row source (the vec0
452
+ * index joined to the table), the threshold, the residual filter and the
453
+ * page. `vectorSearch` pages it (`buildVectorSearchSelect`),
454
+ * `vectorSearchWithCount` also counts it (`buildVectorSearchCount`).
454
455
  */
455
456
  private _buildVectorSearchBase;
456
457
  /**
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
- import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
2
+ import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, searchIndexNotFoundMessage, vectorIndexNotFoundMessage } from "@atscript/db";
3
3
  import { resolveAggregateSearch } from "@atscript/db/agg";
4
- import { EMPTY_AND, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, geoWindowFromControls, jsonDollarPath, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral, toSqlValue } from "@atscript/db-sql-tools";
4
+ import { EMPTY_AND, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, geoWindowFromControls, jsonDollarPath, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral, toSqlValue } from "@atscript/db-sql-tools";
5
5
  import { bucketer } from "@uniqu/core";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
7
7
  //#region src/better-sqlite3-driver.ts
@@ -1318,13 +1318,21 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1318
1318
  for (const idx of this._getFulltextIndexes()) indexes.push({
1319
1319
  name: idx.name,
1320
1320
  description: `FTS5 index (${idx.fields.map((f) => f.name).join(", ")})`,
1321
- type: "text"
1322
- });
1323
- for (const [field, vec] of this._vectorFields) indexes.push({
1324
- name: vec.indexName,
1325
- description: `vec0 index on ${field} (${vec.dimensions}, ${vec.similarity})`,
1326
- type: "vector"
1321
+ type: "text",
1322
+ fields: this._indexLogicalPaths(idx),
1323
+ isDefault: indexes.length === 0
1327
1324
  });
1325
+ let firstVector = true;
1326
+ for (const [field, vec] of this._vectorFields) {
1327
+ indexes.push({
1328
+ name: vec.indexName,
1329
+ description: `vec0 index on ${field} (${vec.dimensions}, ${vec.similarity})`,
1330
+ type: "vector",
1331
+ fields: [field],
1332
+ isDefault: firstVector
1333
+ });
1334
+ firstVector = false;
1335
+ }
1328
1336
  return indexes;
1329
1337
  }
1330
1338
  async search(text, query, indexName) {
@@ -1379,10 +1387,10 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1379
1387
  /** Resolves a fulltext index by name, or returns the first available. */
1380
1388
  _resolveFtsIndex(indexName) {
1381
1389
  const ftIndexes = this._getFulltextIndexes();
1382
- if (ftIndexes.length === 0) throw new Error("No search index available");
1390
+ if (ftIndexes.length === 0) throw new Error(searchIndexNotFoundMessage());
1383
1391
  if (indexName) {
1384
1392
  const found = ftIndexes.find((idx) => idx.name === indexName);
1385
- if (!found) throw new Error(`Search index "${indexName}" not found`);
1393
+ if (!found) throw new Error(searchIndexNotFoundMessage(indexName));
1386
1394
  return found;
1387
1395
  }
1388
1396
  return ftIndexes[0];
@@ -1493,23 +1501,18 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1493
1501
  async vectorSearchWithCount(vector, query, indexName) {
1494
1502
  if (!this._detectVectorSupport()) throw new Error("Vector search requires the sqlite-vec extension. Construct BetterSqlite3Driver with { vector: true }.");
1495
1503
  const base = this._buildVectorSearchBase(vector, query, indexName);
1496
- const countSql = `SELECT COUNT(*) AS cnt ${base.fromWhere}`;
1504
+ const count = buildVectorSearchCount(sqliteDialect, base.source, base);
1497
1505
  return this._stmt(() => {
1498
1506
  const data = this._runVectorSearch(base);
1499
- this._log(countSql, base.params);
1507
+ this._log(count.sql, count.params);
1500
1508
  return {
1501
1509
  data,
1502
- count: this.driver.get(countSql, base.params)?.cnt ?? 0
1510
+ count: this.driver.get(count.sql, count.params)?.cnt ?? 0
1503
1511
  };
1504
1512
  });
1505
1513
  }
1506
1514
  _runVectorSearch(base) {
1507
- let sql = `SELECT * ${base.fromWhere} ORDER BY _distance ASC LIMIT ?`;
1508
- const params = [...base.params, base.limit];
1509
- if (base.skip > 0) {
1510
- sql += ` OFFSET ?`;
1511
- params.push(base.skip);
1512
- }
1515
+ const { sql, params } = buildVectorSearchSelect(sqliteDialect, base.source, base);
1513
1516
  this._log(sql, params);
1514
1517
  return this.driver.all(sql, params);
1515
1518
  }
@@ -1521,7 +1524,7 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1521
1524
  entry = [f, v];
1522
1525
  break;
1523
1526
  }
1524
- if (!entry) throw new Error(`Vector index "${indexName}" not found`);
1527
+ if (!entry) throw new Error(vectorIndexNotFoundMessage(indexName));
1525
1528
  } else {
1526
1529
  const first = this._vectorFields.entries().next();
1527
1530
  if (first.done) throw new Error("No vector fields defined");
@@ -1567,9 +1570,10 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1567
1570
  };
1568
1571
  }
1569
1572
  /**
1570
- * Builds the shared FROM+WHERE fragment for vec0 KNN queries (without ORDER/LIMIT).
1571
- * Both `vectorSearch` and `vectorSearchWithCount` reuse this — the former appends
1572
- * ORDER BY + LIMIT/OFFSET, the latter wraps it in a COUNT(*).
1573
+ * Builds the shared parts of a vec0 KNN query: the row source (the vec0
1574
+ * index joined to the table), the threshold, the residual filter and the
1575
+ * page. `vectorSearch` pages it (`buildVectorSearchSelect`),
1576
+ * `vectorSearchWithCount` also counts it (`buildVectorSearchCount`).
1573
1577
  */
1574
1578
  _buildVectorSearchBase(vector, query, indexName) {
1575
1579
  const { vec, partitionPhysicalNames } = this._resolveVectorIndex(indexName);
@@ -1579,35 +1583,29 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1579
1583
  const limit = controls.$limit ?? 20;
1580
1584
  const skip = controls.$skip ?? 0;
1581
1585
  const threshold = this._resolveVectorThreshold(controls, vec.indexName);
1582
- const { partition, residual } = this._splitVectorFilter(query.filter, partitionPhysicalNames);
1583
- const residualWhere = buildPrefixedWhere("_vs", residual);
1584
- const hasResidual = residualWhere.sql !== "1=1";
1586
+ const split = this._splitVectorFilter(query.filter, partitionPhysicalNames);
1587
+ const residual = buildPrefixedWhere("_v", split.residual);
1588
+ const hasResidual = residual.sql !== "1=1";
1585
1589
  const hasThreshold = threshold !== void 0;
1586
1590
  const k = (limit + skip) * (hasResidual || hasThreshold ? SqliteAdapter._RESIDUAL_OVERFETCH : 1);
1587
1591
  const tableName = this.resolveTableName();
1588
1592
  const vecTable = this._vecTableName(vec.indexName);
1589
1593
  const innerWhereParts = ["v.embedding MATCH ?", "v.k = ?"];
1590
1594
  const params = [vecBuf, k];
1591
- for (const p of partition) {
1595
+ for (const p of split.partition) {
1592
1596
  innerWhereParts.push(`v."${esc(p.name)}" = ?`);
1593
1597
  params.push(p.value);
1594
1598
  }
1595
- let fromWhere = `FROM (${`SELECT t.*, v.distance AS _distance FROM "${esc(vecTable)}" v JOIN "${esc(tableName)}" t ON t.rowid = v.rowid WHERE ${innerWhereParts.join(" AND ")}`}) _vs`;
1596
- const outerWhereParts = [];
1597
- if (hasThreshold) {
1598
- outerWhereParts.push(`_distance <= ?`);
1599
- params.push(thresholdToVecDistance(threshold, vec.similarity));
1600
- }
1601
- if (hasResidual) {
1602
- outerWhereParts.push(`(${residualWhere.sql})`);
1603
- params.push(...residualWhere.params);
1604
- }
1605
- if (outerWhereParts.length > 0) fromWhere += ` WHERE ${outerWhereParts.join(" AND ")}`;
1606
1599
  return {
1607
- fromWhere,
1608
- params,
1600
+ source: {
1601
+ sql: `SELECT t.*, v.distance AS _distance FROM "${esc(vecTable)}" v JOIN "${esc(tableName)}" t ON t.rowid = v.rowid WHERE ${innerWhereParts.join(" AND ")}`,
1602
+ params
1603
+ },
1609
1604
  limit,
1610
- skip
1605
+ skip,
1606
+ maxDistance: hasThreshold ? thresholdToVecDistance(threshold, vec.similarity) : void 0,
1607
+ residual,
1608
+ select: query.controls?.$select
1611
1609
  };
1612
1610
  }
1613
1611
  /**
@@ -1666,10 +1664,7 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1666
1664
  };
1667
1665
  }
1668
1666
  _buildGeoSearchSelect(ctx) {
1669
- return buildGeoSearchSelect(sqliteDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window, {
1670
- $limit: ctx.controls.$limit,
1671
- $skip: ctx.controls.$skip
1672
- });
1667
+ return buildGeoSearchSelect(sqliteDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window, ctx.controls);
1673
1668
  }
1674
1669
  /** Builds vec0 shadow table name from index name: `<table>__vec__<indexName>`. */
1675
1670
  _vecTableName(indexName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-sqlite",
3
- "version": "0.1.142",
3
+ "version": "0.1.144",
4
4
  "description": "SQLite adapter for @atscript/db with swappable driver support.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -36,21 +36,21 @@
36
36
  "access": "public"
37
37
  },
38
38
  "devDependencies": {
39
- "@atscript/core": "^0.1.95",
40
- "@atscript/typescript": "^0.1.95",
39
+ "@atscript/core": "^0.1.96",
40
+ "@atscript/typescript": "^0.1.96",
41
41
  "@types/better-sqlite3": "^7.6.13",
42
42
  "@uniqu/core": "^0.1.11",
43
43
  "better-sqlite3": "^12.6.2",
44
44
  "sqlite-vec": "^0.1.9",
45
- "unplugin-atscript": "^0.1.95"
45
+ "unplugin-atscript": "^0.1.96"
46
46
  },
47
47
  "peerDependencies": {
48
- "@atscript/core": "^0.1.95",
49
- "@atscript/typescript": "^0.1.95",
48
+ "@atscript/core": "^0.1.96",
49
+ "@atscript/typescript": "^0.1.96",
50
50
  "@uniqu/core": "^0.1.11",
51
51
  "better-sqlite3": ">=11.0.0",
52
- "@atscript/db": "^0.1.142",
53
- "@atscript/db-sql-tools": "^0.1.142"
52
+ "@atscript/db": "^0.1.144",
53
+ "@atscript/db-sql-tools": "^0.1.144"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "better-sqlite3": {