@an5/adapters 0.2.10 → 0.2.13
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 +163 -2
- package/dist/an5Adapter.d.ts +37 -0
- package/dist/an5Adapter.js +190 -65
- package/dist/base/metadata.d.ts +2 -0
- package/dist/base/metadata.js +10 -0
- package/dist/base/sql.d.ts +2 -0
- package/dist/base/sql.js +24 -25
- package/dist/base/types.d.ts +14 -0
- package/dist/googlesheets/adapter.js +16 -8
- package/dist/googlesheets/config.d.ts +13 -0
- package/dist/googlesheets/config.js +5 -1
- package/dist/googlesheets/oauth.d.ts +3 -0
- package/dist/googlesheets/oauth.js +43 -0
- package/dist/googlesheets/parseConnectionString.js +12 -0
- package/dist/googlesheets/tableClient.js +5 -23
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -1
- package/dist/sqlite/browserEngine.d.ts +13 -0
- package/dist/sqlite/browserEngine.js +13 -0
- package/dist/sqlite/engine.d.ts +5 -0
- package/dist/sqlite/engine.js +10 -0
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/sqlite/vector.d.ts +155 -0
- package/dist/sqlite/vector.js +436 -0
- package/dotnet/An5Adapter.cs +85 -27
- package/dotnet/Base/Core.cs +14 -0
- package/dotnet/Mssql/MssqlEngine.cs +44 -1
- package/dotnet/Postgres/PostgresEngine.cs +2 -0
- package/dotnet/Sqlite/SqliteEngine.cs +82 -2
- package/dotnet/Sqlite/SqliteVectors.cs +401 -0
- package/golang/an5_adapter.go +84 -32
- package/golang/base/vectors.go +263 -0
- package/golang/sqlite/vector.go +248 -0
- package/golang/sqlite_vector_test.go +288 -0
- package/golang/table_client.go +67 -3
- package/java/pom.xml +177 -0
- package/java/src/main/java/an5/adapters/An5Adapter.java +374 -0
- package/java/src/main/java/an5/adapters/An5Aggregate.java +116 -0
- package/java/src/main/java/an5/adapters/An5GroupBy.java +97 -0
- package/java/src/main/java/an5/adapters/An5Query.java +100 -0
- package/java/src/main/java/an5/adapters/An5TableClient.java +1031 -0
- package/java/src/main/java/an5/adapters/An5ViewClient.java +107 -0
- package/java/src/main/java/an5/adapters/base/Dialect.java +103 -0
- package/java/src/main/java/an5/adapters/base/FieldMeta.java +42 -0
- package/java/src/main/java/an5/adapters/base/JdbcUrls.java +201 -0
- package/java/src/main/java/an5/adapters/base/Metadata.java +195 -0
- package/java/src/main/java/an5/adapters/base/RelationDef.java +26 -0
- package/java/src/main/java/an5/adapters/base/SqlBuilder.java +422 -0
- package/java/src/main/java/an5/adapters/base/SqliteVectors.java +375 -0
- package/java/src/main/java/an5/adapters/base/Vectors.java +123 -0
- package/java/src/test/java/An5AdaptersSmoke.java +733 -0
- package/java/src/test/java/Json.java +174 -0
- package/java/src/test/java/QuerySemantics.java +111 -0
- package/kotlin/build.gradle.kts +145 -0
- package/kotlin/settings.gradle.kts +10 -0
- package/kotlin/src/main/kotlin/an5/adapters/Aggregate.kt +89 -0
- package/kotlin/src/main/kotlin/an5/adapters/An5.kt +91 -0
- package/kotlin/src/main/kotlin/an5/adapters/Query.kt +130 -0
- package/kotlin/src/main/kotlin/an5/adapters/Rows.kt +166 -0
- package/kotlin/src/main/kotlin/an5/adapters/TableClient.kt +169 -0
- package/kotlin/src/main/kotlin/an5/adapters/Types.kt +77 -0
- package/kotlin/src/test/kotlin/an5/adapters/An5Smoke.kt +329 -0
- package/native/sqlite/CMakeLists.txt +12 -0
- package/native/sqlite/README.md +117 -0
- package/native/sqlite/an5_vector.c +209 -0
- package/package.json +40 -9
- package/python/an5_adapter.py +37 -6
- package/python/base/__init__.py +15 -0
- package/python/base/vectors.py +434 -0
- package/python/sqlite/__init__.py +2 -2
- package/python/sqlite/provider.py +21 -1
- package/python/table_client.py +129 -59
- package/rust/src/adapter.rs +342 -24
- package/rust/src/base/mod.rs +7 -0
- package/rust/src/base/vectors.rs +293 -0
- package/rust/src/lib.rs +3 -3
- package/scripts/sqlite-native-build.js +32 -0
- package/swift/Package.swift +22 -0
- package/swift/Sources/An5Adapters/An5Adapter.swift +160 -0
- package/swift/Sources/An5Adapters/Metadata.swift +183 -0
- package/swift/Sources/An5Adapters/Query.swift +105 -0
- package/swift/Sources/An5Adapters/Row+Accessors.swift +129 -0
- package/swift/Sources/An5Adapters/SQLBuilder.swift +359 -0
- package/swift/Sources/An5Adapters/SQLiteDriver.swift +301 -0
- package/swift/Sources/An5Adapters/SqliteVectors.swift +243 -0
- package/swift/Sources/An5Adapters/TableClient.swift +671 -0
- package/swift/Sources/An5Adapters/Types.swift +127 -0
- package/swift/Sources/An5Adapters/Vectors.swift +86 -0
- package/swift/Sources/An5Adapters/ViewClient.swift +50 -0
- package/swift/Sources/CSQLite/module.modulemap +8 -0
- package/swift/Sources/CSQLite/shim.h +9 -0
- package/swift/Tests/An5AdaptersTests/QuerySemanticsTests.swift +51 -0
- package/swift/Tests/An5AdaptersTests/RuntimeSmokeTests.swift +488 -0
- package/typescript/src/an5Adapter.ts +200 -58
- package/typescript/src/base/metadata.ts +8 -0
- package/typescript/src/base/sql.ts +23 -16
- package/typescript/src/base/types.ts +15 -0
- package/typescript/src/googlesheets/adapter.ts +17 -8
- package/typescript/src/googlesheets/config.ts +10 -1
- package/typescript/src/googlesheets/oauth.ts +31 -0
- package/typescript/src/googlesheets/parseConnectionString.ts +8 -0
- package/typescript/src/googlesheets/tableClient.ts +5 -19
- package/typescript/src/index.ts +13 -0
- package/typescript/src/sqlite/browserEngine.ts +24 -0
- package/typescript/src/sqlite/engine.ts +13 -0
- package/typescript/src/sqlite/index.ts +1 -1
- package/typescript/src/sqlite/vector.ts +541 -0
- package/golang/sqlite/sqlite.go +0 -14
package/dist/an5Adapter.js
CHANGED
|
@@ -17,6 +17,7 @@ const parseConnectionString_1 = require("./googlesheets/parseConnectionString");
|
|
|
17
17
|
const SQLITE_FILE_SUFFIXES = ['.sqlite', '.sqlite3', '.db'];
|
|
18
18
|
const nbase_1 = require("./nbase");
|
|
19
19
|
const sql_1 = require("./base/sql");
|
|
20
|
+
const vector_1 = require("./sqlite/vector");
|
|
20
21
|
const metadata_1 = require("./base/metadata");
|
|
21
22
|
var metadata_2 = require("./base/metadata");
|
|
22
23
|
Object.defineProperty(exports, "setAdapterMetadata", { enumerable: true, get: function () { return metadata_2.setAdapterMetadata; } });
|
|
@@ -28,38 +29,60 @@ function sanitizeParamName(name) {
|
|
|
28
29
|
const cleaned = String(name).replace(/[^A-Za-z0-9_]/g, '_');
|
|
29
30
|
return /^[A-Za-z]/.test(cleaned) ? cleaned : `p_${cleaned}`;
|
|
30
31
|
}
|
|
32
|
+
/** Match only unique/primary-key violations, never general constraint errors. */
|
|
33
|
+
function isDuplicateKeyError(error, dialect) {
|
|
34
|
+
if (!error || typeof error !== 'object')
|
|
35
|
+
return false;
|
|
36
|
+
const e = error;
|
|
37
|
+
if (dialect === 'postgres')
|
|
38
|
+
return e.code === '23505';
|
|
39
|
+
if (dialect === 'mysql')
|
|
40
|
+
return e.code === 'ER_DUP_ENTRY' || e.errno === 1062;
|
|
41
|
+
if (dialect === 'sqlite') {
|
|
42
|
+
return e.code === 'SQLITE_CONSTRAINT_UNIQUE' || e.code === 'SQLITE_CONSTRAINT_PRIMARYKEY'
|
|
43
|
+
|| e.errcode === 2067 || e.errcode === 1555;
|
|
44
|
+
}
|
|
45
|
+
if (dialect === 'mssql') {
|
|
46
|
+
const number = e.number ?? e.originalError?.info?.number;
|
|
47
|
+
return number === 2601 || number === 2627;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
31
51
|
function appendUpdateSet(sets, params, col, val, dialect) {
|
|
32
52
|
const quoted = (0, sql_1.quote)(col, dialect);
|
|
33
53
|
const safeCol = sanitizeParamName(col);
|
|
34
|
-
|
|
54
|
+
// `TypedArray.prototype.set` is the copy method, not the update operator, so a
|
|
55
|
+
// vector written as a float32 BLOB must not be read as `{ set: ... }`.
|
|
56
|
+
const isLiteral = Array.isArray(val) || ArrayBuffer.isView(val) || val instanceof ArrayBuffer;
|
|
57
|
+
if (val && typeof val === 'object' && !(val instanceof Date) && !isLiteral) {
|
|
35
58
|
if (val.increment !== undefined) {
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
const p = (0, sql_1.bindParam)(params, `s_${safeCol}_inc`, val.increment);
|
|
60
|
+
sets.push(`${quoted} = ${quoted} + @${p}`);
|
|
38
61
|
return;
|
|
39
62
|
}
|
|
40
63
|
if (val.decrement !== undefined) {
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
const p = (0, sql_1.bindParam)(params, `s_${safeCol}_dec`, val.decrement);
|
|
65
|
+
sets.push(`${quoted} = ${quoted} - @${p}`);
|
|
43
66
|
return;
|
|
44
67
|
}
|
|
45
68
|
if (val.multiply !== undefined) {
|
|
46
|
-
|
|
47
|
-
|
|
69
|
+
const p = (0, sql_1.bindParam)(params, `s_${safeCol}_mul`, val.multiply);
|
|
70
|
+
sets.push(`${quoted} = ${quoted} * @${p}`);
|
|
48
71
|
return;
|
|
49
72
|
}
|
|
50
73
|
if (val.divide !== undefined) {
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
const p = (0, sql_1.bindParam)(params, `s_${safeCol}_div`, val.divide);
|
|
75
|
+
sets.push(`${quoted} = ${quoted} / @${p}`);
|
|
53
76
|
return;
|
|
54
77
|
}
|
|
55
78
|
if (val.set !== undefined) {
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
const p = (0, sql_1.bindParam)(params, `s_${safeCol}_set`, val.set);
|
|
80
|
+
sets.push(`${quoted} = @${p}`);
|
|
58
81
|
return;
|
|
59
82
|
}
|
|
60
83
|
}
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
const p = (0, sql_1.bindParam)(params, `s_${safeCol}`, val);
|
|
85
|
+
sets.push(`${quoted} = @${p}`);
|
|
63
86
|
}
|
|
64
87
|
function selectedAggregateFields(fields) {
|
|
65
88
|
if (!fields || typeof fields !== 'object')
|
|
@@ -324,6 +347,21 @@ class An5Adapter {
|
|
|
324
347
|
return this.sheetsAdapter.exec(query, params);
|
|
325
348
|
return (await this.requireEngine()).exec(query, params);
|
|
326
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* The SQLite engine's vector hooks, or null when this is not a SQLite adapter.
|
|
352
|
+
* `vectorSearch` needs them to load sqlite-vec and register the distance
|
|
353
|
+
* functions; a connection with no hooks still searches in memory.
|
|
354
|
+
*/
|
|
355
|
+
async sqliteVectorSupport() {
|
|
356
|
+
if (this.sheetsAdapter)
|
|
357
|
+
return null;
|
|
358
|
+
const engine = await this.requireEngine();
|
|
359
|
+
return engine.dialect === 'sqlite' ? (engine.vectorSupport ?? null) : null;
|
|
360
|
+
}
|
|
361
|
+
/** `vectorStrategy` from the config, so a project can pin one strategy. */
|
|
362
|
+
get vectorStrategy() {
|
|
363
|
+
return this._engineConfig?.vectorStrategy;
|
|
364
|
+
}
|
|
327
365
|
/** INTERNAL: used by AdapterTableClient for DML statements needing row count */
|
|
328
366
|
async _executeRaw(query, params) {
|
|
329
367
|
return (await this.requireEngine()).executeRaw(query, params);
|
|
@@ -636,6 +674,15 @@ class AdapterTableClient {
|
|
|
636
674
|
}
|
|
637
675
|
get dialect() { return this.adapter.dialect; }
|
|
638
676
|
get tableName() {
|
|
677
|
+
const t = this.mappedTableName;
|
|
678
|
+
if (t.startsWith('[') || t.startsWith('"') || t.startsWith('`'))
|
|
679
|
+
return t;
|
|
680
|
+
if (t.includes('.'))
|
|
681
|
+
return t.split('.').map(p => (0, sql_1.quote)(p, this.dialect)).join('.');
|
|
682
|
+
return (0, sql_1.quote)(t, this.dialect);
|
|
683
|
+
}
|
|
684
|
+
/** The table name this model maps to, before any quoting is applied. */
|
|
685
|
+
get mappedTableName() {
|
|
639
686
|
const name = this.modelName;
|
|
640
687
|
let t = name;
|
|
641
688
|
const modelToTable = (0, metadata_1.getModelToTable)();
|
|
@@ -651,11 +698,16 @@ class AdapterTableClient {
|
|
|
651
698
|
t = modelToTable[lower];
|
|
652
699
|
}
|
|
653
700
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
701
|
+
return t;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* The bare table name, for helpers that take a string literal. `@@map` values
|
|
705
|
+
* may be pre-quoted or schema-qualified, neither of which `pragma_table_info`
|
|
706
|
+
* accepts, so the last segment is unwrapped here.
|
|
707
|
+
*/
|
|
708
|
+
get rawTableName() {
|
|
709
|
+
const last = this.mappedTableName.split('.').pop() ?? this.mappedTableName;
|
|
710
|
+
return last.replace(/^[\s]*[\[\]"`]+|[\[\]"`]+[\s]*$/g, '');
|
|
659
711
|
}
|
|
660
712
|
get nolock() {
|
|
661
713
|
return this.dialect === 'mssql' ? ' WITH (NOLOCK)' : '';
|
|
@@ -674,6 +726,58 @@ class AdapterTableClient {
|
|
|
674
726
|
async doExecuteRaw(query, params) {
|
|
675
727
|
return this.adapter._executeRaw(query, params);
|
|
676
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* Encode every `VECTOR(n)` value on its way into SQLite.
|
|
731
|
+
*
|
|
732
|
+
* The column holds a float32 BLOB, which is a third of the size of the JSON
|
|
733
|
+
* text it replaces and is what sqlite-vec reads. Values that already arrived
|
|
734
|
+
* as text or bytes are left alone, so rows written by an older version stay
|
|
735
|
+
* readable.
|
|
736
|
+
*/
|
|
737
|
+
encodeVectorFields(data) {
|
|
738
|
+
if (this.dialect !== 'sqlite')
|
|
739
|
+
return data;
|
|
740
|
+
const fields = (0, metadata_1.getFieldsForModel)(this.modelName);
|
|
741
|
+
const vectorFields = Object.keys(fields).filter((name) => (0, vector_1.isVectorField)(fields[name]));
|
|
742
|
+
if (vectorFields.length === 0)
|
|
743
|
+
return data;
|
|
744
|
+
const { encodeVector } = require('./sqlite/vector');
|
|
745
|
+
const out = { ...data };
|
|
746
|
+
for (const name of vectorFields) {
|
|
747
|
+
const value = out[name];
|
|
748
|
+
if ((0, vector_1.needsVectorEncoding)(value)) {
|
|
749
|
+
const parsed = Array.from(value);
|
|
750
|
+
if (parsed.length > 0)
|
|
751
|
+
out[name] = encodeVector(parsed);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return out;
|
|
755
|
+
}
|
|
756
|
+
/** Decode `VECTOR(n)` columns back into `number[]` after a read. */
|
|
757
|
+
decodeVectorFields(rows) {
|
|
758
|
+
if (this.dialect !== 'sqlite' || !Array.isArray(rows) || rows.length === 0)
|
|
759
|
+
return rows;
|
|
760
|
+
const fields = (0, metadata_1.getFieldsForModel)(this.modelName);
|
|
761
|
+
const vectorFields = Object.keys(fields).filter((name) => (0, vector_1.isVectorField)(fields[name]));
|
|
762
|
+
if (vectorFields.length === 0)
|
|
763
|
+
return rows;
|
|
764
|
+
return rows.map((row) => {
|
|
765
|
+
if (!row || typeof row !== 'object')
|
|
766
|
+
return row;
|
|
767
|
+
let changed = false;
|
|
768
|
+
const out = { ...row };
|
|
769
|
+
for (const name of vectorFields) {
|
|
770
|
+
if (!(name in out))
|
|
771
|
+
continue;
|
|
772
|
+
const decoded = (0, vector_1.decodeVector)(out[name]);
|
|
773
|
+
if (decoded) {
|
|
774
|
+
out[name] = decoded;
|
|
775
|
+
changed = true;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return changed ? out : row;
|
|
779
|
+
});
|
|
780
|
+
}
|
|
677
781
|
async findMany(args) {
|
|
678
782
|
const params = {};
|
|
679
783
|
const whereCtx = this.whereContext();
|
|
@@ -717,7 +821,8 @@ class AdapterTableClient {
|
|
|
717
821
|
query += ` WHERE ${whereSql}`;
|
|
718
822
|
if (orderSql)
|
|
719
823
|
query += ` ${orderSql}`;
|
|
720
|
-
|
|
824
|
+
const limit = take ?? (this.dialect === 'sqlite' ? '-1' : this.dialect === 'mysql' ? '18446744073709551615' : 'ALL');
|
|
825
|
+
query += ` LIMIT ${limit} OFFSET ${skip}`;
|
|
721
826
|
}
|
|
722
827
|
else {
|
|
723
828
|
// mssql requires ORDER BY before OFFSET/FETCH
|
|
@@ -738,7 +843,7 @@ class AdapterTableClient {
|
|
|
738
843
|
if (orderSql)
|
|
739
844
|
query += ` ${orderSql}`;
|
|
740
845
|
}
|
|
741
|
-
const rows = await this.doExec(query, params);
|
|
846
|
+
const rows = this.decodeVectorFields(await this.doExec(query, params));
|
|
742
847
|
if (args?.include) {
|
|
743
848
|
await resolveIncludes(this.modelName, rows, args.include, this.adapter);
|
|
744
849
|
}
|
|
@@ -779,10 +884,8 @@ class AdapterTableClient {
|
|
|
779
884
|
}
|
|
780
885
|
}
|
|
781
886
|
const fields = (0, metadata_1.getFieldsForModel)(this.modelName);
|
|
782
|
-
const idFieldName =
|
|
783
|
-
|
|
784
|
-
: Object.keys(fields).find((name) => name.endsWith('_id') || name.endsWith('Id') || name.toLowerCase() === 'id');
|
|
785
|
-
const data = { ...rawData };
|
|
887
|
+
const idFieldName = (0, metadata_1.resolveIdField)(fields);
|
|
888
|
+
const data = this.encodeVectorFields(rawData);
|
|
786
889
|
if (idFieldName) {
|
|
787
890
|
const fieldDef = fields[idFieldName];
|
|
788
891
|
const rawType = typeof fieldDef === 'string' ? fieldDef : (fieldDef?.ts || fieldDef?.sql || fieldDef?.type || '');
|
|
@@ -836,27 +939,33 @@ class AdapterTableClient {
|
|
|
836
939
|
if (firstRow === undefined)
|
|
837
940
|
return { count: 0 };
|
|
838
941
|
const firstCols = Object.keys(firstRow).filter(k => firstRow[k] !== undefined);
|
|
839
|
-
|
|
840
|
-
|
|
942
|
+
const sameColumns = args.data.every(row => {
|
|
943
|
+
const cols = Object.keys(row).filter(k => row[k] !== undefined);
|
|
944
|
+
return cols.length === firstCols.length && firstCols.every(col => cols.includes(col));
|
|
945
|
+
});
|
|
946
|
+
const fields = (0, metadata_1.getFieldsForModel)(this.modelName);
|
|
947
|
+
const idFieldName = (0, metadata_1.resolveIdField)(fields);
|
|
948
|
+
const idDef = idFieldName ? fields[idFieldName] : undefined;
|
|
949
|
+
const idType = typeof idDef === 'string' ? idDef : (idDef?.ts || idDef?.sql || idDef?.type || '');
|
|
950
|
+
const generatesId = ['string', 'uuid', 'uniqueidentifier', 'nvarchar', 'varchar', 'text'].includes(String(idType).toLowerCase());
|
|
951
|
+
const needsGeneratedId = idFieldName && generatesId && args.data.some(row => !row[idFieldName]);
|
|
952
|
+
// Rows needing generated IDs follow create's default handling.
|
|
953
|
+
if (firstCols.length > 0 && sameColumns && !needsGeneratedId && !args.skipDuplicates) {
|
|
841
954
|
const params = {};
|
|
842
955
|
const rowPlaceholders = [];
|
|
843
956
|
for (let r = 0; r < args.data.length; r++) {
|
|
844
957
|
const row = args.data[r];
|
|
958
|
+
const encoded = this.encodeVectorFields(row);
|
|
845
959
|
const vals = firstCols.map(col => {
|
|
846
960
|
const p = `r${r}_${col}`;
|
|
847
|
-
params[p] =
|
|
961
|
+
params[p] = encoded[col] ?? null;
|
|
848
962
|
return `@${p}`;
|
|
849
963
|
});
|
|
850
964
|
rowPlaceholders.push(`(${vals.join(', ')})`);
|
|
851
965
|
}
|
|
852
966
|
const query = `INSERT INTO ${this.tableName} (${firstCols.map(c => (0, sql_1.quote)(c, this.dialect)).join(', ')}) VALUES ${rowPlaceholders.join(', ')}`;
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
return { count: args.data.length };
|
|
856
|
-
}
|
|
857
|
-
catch {
|
|
858
|
-
// fallback below if bulk fails (e.g. mixed column sets)
|
|
859
|
-
}
|
|
967
|
+
await this.doExecuteRaw(query, params);
|
|
968
|
+
return { count: args.data.length };
|
|
860
969
|
}
|
|
861
970
|
// Row-by-row fallback
|
|
862
971
|
let count = 0;
|
|
@@ -866,7 +975,7 @@ class AdapterTableClient {
|
|
|
866
975
|
count++;
|
|
867
976
|
}
|
|
868
977
|
catch (e) {
|
|
869
|
-
if (!args.skipDuplicates)
|
|
978
|
+
if (!args.skipDuplicates || !isDuplicateKeyError(e, this.dialect))
|
|
870
979
|
throw e;
|
|
871
980
|
}
|
|
872
981
|
}
|
|
@@ -882,13 +991,14 @@ class AdapterTableClient {
|
|
|
882
991
|
delete rawData[key];
|
|
883
992
|
}
|
|
884
993
|
}
|
|
994
|
+
const data = this.encodeVectorFields(rawData);
|
|
885
995
|
const setCols = Object.keys(rawData).filter(k => rawData[k] !== undefined);
|
|
886
996
|
if (setCols.length > 0) {
|
|
887
997
|
const params = {};
|
|
888
998
|
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, 'w_', this.whereContext());
|
|
889
999
|
const sets = [];
|
|
890
1000
|
for (const col of setCols) {
|
|
891
|
-
appendUpdateSet(sets, params, col,
|
|
1001
|
+
appendUpdateSet(sets, params, col, data[col], this.dialect);
|
|
892
1002
|
}
|
|
893
1003
|
if (sets.length > 0) {
|
|
894
1004
|
const query = `UPDATE ${this.tableName} SET ${sets.join(', ')}${whereSql ? ` WHERE ${whereSql}` : ''}`;
|
|
@@ -940,10 +1050,11 @@ class AdapterTableClient {
|
|
|
940
1050
|
async updateMany(args) {
|
|
941
1051
|
const params = {};
|
|
942
1052
|
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, 'w_', this.whereContext());
|
|
1053
|
+
const data = this.encodeVectorFields(args.data);
|
|
943
1054
|
const setCols = Object.keys(args.data).filter(k => args.data[k] !== undefined);
|
|
944
1055
|
const sets = [];
|
|
945
1056
|
for (const col of setCols) {
|
|
946
|
-
appendUpdateSet(sets, params, col,
|
|
1057
|
+
appendUpdateSet(sets, params, col, data[col], this.dialect);
|
|
947
1058
|
}
|
|
948
1059
|
if (sets.length === 0)
|
|
949
1060
|
return { count: 0 };
|
|
@@ -1046,7 +1157,7 @@ class AdapterTableClient {
|
|
|
1046
1157
|
}
|
|
1047
1158
|
async groupBy(args) {
|
|
1048
1159
|
const params = {};
|
|
1049
|
-
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
|
|
1160
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect, '', this.whereContext());
|
|
1050
1161
|
const byFields = normalizeByFields(args?.by);
|
|
1051
1162
|
if (byFields.length === 0)
|
|
1052
1163
|
throw new Error("groupBy requires 'by' fields");
|
|
@@ -1076,9 +1187,8 @@ class AdapterTableClient {
|
|
|
1076
1187
|
const skip = toNonNegativeInt(args?.skip);
|
|
1077
1188
|
const take = toNonNegativeInt(args?.take, 1);
|
|
1078
1189
|
if (this.dialect === 'postgres' || this.dialect === 'mysql' || this.dialect === 'sqlite') {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
query += ` OFFSET ${skip}`;
|
|
1190
|
+
const limit = hasTake ? take : (this.dialect === 'sqlite' ? '-1' : this.dialect === 'mysql' ? '18446744073709551615' : 'ALL');
|
|
1191
|
+
query += ` LIMIT ${limit} OFFSET ${skip}`;
|
|
1082
1192
|
}
|
|
1083
1193
|
else {
|
|
1084
1194
|
query += ` OFFSET ${skip} ROWS`;
|
|
@@ -1285,6 +1395,30 @@ class AdapterTableClient {
|
|
|
1285
1395
|
throw err;
|
|
1286
1396
|
}
|
|
1287
1397
|
}
|
|
1398
|
+
else if (this.dialect === 'sqlite') {
|
|
1399
|
+
// SQLite has no vector type, so the ranking runs through sqlite-vec, a
|
|
1400
|
+
// driver function, or json_each — whichever this connection supports.
|
|
1401
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, '', this.whereContext());
|
|
1402
|
+
const support = await this.adapter.sqliteVectorSupport?.() ?? null;
|
|
1403
|
+
const ranked = await (0, vector_1.runSqliteVectorSearch)({
|
|
1404
|
+
exec: (sql, p) => this.doExec(sql, p),
|
|
1405
|
+
vectorSupport: support,
|
|
1406
|
+
table: this.tableName,
|
|
1407
|
+
rawTable: this.rawTableName,
|
|
1408
|
+
column: col,
|
|
1409
|
+
rawColumn: vectorField,
|
|
1410
|
+
vector: args.vector,
|
|
1411
|
+
metric,
|
|
1412
|
+
take,
|
|
1413
|
+
tail: whereSql ? ` WHERE ${whereSql}` : '',
|
|
1414
|
+
params,
|
|
1415
|
+
preference: this.adapter.vectorStrategy,
|
|
1416
|
+
});
|
|
1417
|
+
if (ranked)
|
|
1418
|
+
return this.decodeVectorFields(ranked.rows);
|
|
1419
|
+
// No in-database strategy: score the column in the client instead.
|
|
1420
|
+
return this.scoreVectorsInMemory(args, metric, take, vectorField);
|
|
1421
|
+
}
|
|
1288
1422
|
else if (this.dialect === 'mssql') {
|
|
1289
1423
|
const distFn = metric === 'cosine' ? 'cosine' : metric === 'euclidean' ? 'euclidean' : 'dot';
|
|
1290
1424
|
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, '', this.whereContext());
|
|
@@ -1311,35 +1445,26 @@ class AdapterTableClient {
|
|
|
1311
1445
|
}
|
|
1312
1446
|
}
|
|
1313
1447
|
// 2. Fallback: in-memory similarity computation when native vector support is unavailable
|
|
1448
|
+
return this.scoreVectorsInMemory(args, metric, take, vectorField);
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Rank the vector column in the client. This is the last resort for every
|
|
1452
|
+
* provider, and the only option on SQLite connections that offer neither
|
|
1453
|
+
* sqlite-vec, a driver function nor JSON1.
|
|
1454
|
+
*
|
|
1455
|
+
* The whole table is loaded, so it is only reasonable for a table small
|
|
1456
|
+
* enough to fit in memory.
|
|
1457
|
+
*/
|
|
1458
|
+
async scoreVectorsInMemory(args, metric, take, vectorField = 'embedding') {
|
|
1314
1459
|
const rows = await this.findMany({ where: args.where });
|
|
1315
1460
|
const scored = [];
|
|
1316
1461
|
for (const row of rows) {
|
|
1317
|
-
const
|
|
1318
|
-
if (!
|
|
1319
|
-
continue;
|
|
1320
|
-
let vec = [];
|
|
1321
|
-
try {
|
|
1322
|
-
vec = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
1323
|
-
}
|
|
1324
|
-
catch {
|
|
1462
|
+
const vec = (0, vector_1.decodeVector)(row[vectorField]);
|
|
1463
|
+
if (!vec)
|
|
1325
1464
|
continue;
|
|
1326
|
-
|
|
1327
|
-
if (
|
|
1465
|
+
const dist = (0, vector_1.vectorDistance)(args.vector, vec, metric);
|
|
1466
|
+
if (dist === null)
|
|
1328
1467
|
continue;
|
|
1329
|
-
let dot = 0, m1 = 0, m2 = 0;
|
|
1330
|
-
for (let i = 0; i < args.vector.length; i++) {
|
|
1331
|
-
const av = args.vector[i] ?? 0;
|
|
1332
|
-
const bv = vec[i] ?? 0;
|
|
1333
|
-
dot += av * bv;
|
|
1334
|
-
m1 += av ** 2;
|
|
1335
|
-
m2 += bv ** 2;
|
|
1336
|
-
}
|
|
1337
|
-
const cosine = m1 && m2 ? dot / (Math.sqrt(m1) * Math.sqrt(m2)) : 0;
|
|
1338
|
-
const dist = metric === 'cosine'
|
|
1339
|
-
? 1 - cosine
|
|
1340
|
-
: metric === 'dot'
|
|
1341
|
-
? -dot
|
|
1342
|
-
: Math.sqrt(args.vector.reduce((s, v, i) => s + (v - (vec[i] ?? 0)) ** 2, 0));
|
|
1343
1468
|
scored.push({ row, dist });
|
|
1344
1469
|
}
|
|
1345
1470
|
scored.sort((a, b) => a.dist - b.dist);
|
package/dist/base/metadata.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface ModelFieldMeta {
|
|
|
11
11
|
isId?: boolean;
|
|
12
12
|
[key: string]: any;
|
|
13
13
|
}
|
|
14
|
+
/** Prefer schema primary-key metadata; retain name inference for legacy clients. */
|
|
15
|
+
export declare function resolveIdField(fields: Record<string, any>): string | undefined;
|
|
14
16
|
export interface AdapterMetadata {
|
|
15
17
|
modelToTable?: Record<string, string>;
|
|
16
18
|
modelFields?: Record<string, any>;
|
package/dist/base/metadata.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveIdField = resolveIdField;
|
|
3
4
|
exports.setAdapterMetadata = setAdapterMetadata;
|
|
4
5
|
exports.getModelToTable = getModelToTable;
|
|
5
6
|
exports.getFieldsForModel = getFieldsForModel;
|
|
6
7
|
exports.getRelationMap = getRelationMap;
|
|
7
8
|
exports.getRelationsForModel = getRelationsForModel;
|
|
8
9
|
exports.createAdapterProxy = createAdapterProxy;
|
|
10
|
+
/** Prefer schema primary-key metadata; retain name inference for legacy clients. */
|
|
11
|
+
function resolveIdField(fields) {
|
|
12
|
+
const primaryKey = Object.keys(fields).find(name => fields[name]?.isId === true);
|
|
13
|
+
if (primaryKey)
|
|
14
|
+
return primaryKey;
|
|
15
|
+
if (Object.prototype.hasOwnProperty.call(fields, 'id'))
|
|
16
|
+
return 'id';
|
|
17
|
+
return Object.keys(fields).find(name => name.endsWith('_id') || name.endsWith('Id') || name.toLowerCase() === 'id');
|
|
18
|
+
}
|
|
9
19
|
let modelToTable = {};
|
|
10
20
|
let modelFields = {};
|
|
11
21
|
let relationMap = {};
|
package/dist/base/sql.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type { RelationDef } from './metadata';
|
|
|
3
3
|
export declare function quote(name: string, dialect: Dialect): string;
|
|
4
4
|
/** Quote a full table reference (may already be quoted, and may contain schema parts). */
|
|
5
5
|
export declare function quoteTable(name: string, dialect: Dialect): string;
|
|
6
|
+
/** Allocate parameters without overwriting values from sibling clauses. */
|
|
7
|
+
export declare function bindParam(params: Record<string, any>, name: string, value: any): string;
|
|
6
8
|
export interface WhereContext {
|
|
7
9
|
/** Relations of the current model keyed by relation name. */
|
|
8
10
|
relationMap?: Record<string, Record<string, RelationDef>>;
|
package/dist/base/sql.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.quote = quote;
|
|
4
4
|
exports.quoteTable = quoteTable;
|
|
5
|
+
exports.bindParam = bindParam;
|
|
5
6
|
exports.parseWhere = parseWhere;
|
|
6
7
|
exports.buildOrderBy = buildOrderBy;
|
|
7
8
|
// ─── Quoting ───────────────────────────────────────────────────────────────────────
|
|
@@ -38,6 +39,15 @@ function sanitizeParamName(name) {
|
|
|
38
39
|
const cleaned = String(name).replace(/[^A-Za-z0-9_]/g, '_');
|
|
39
40
|
return /^[A-Za-z]/.test(cleaned) ? cleaned : `p_${cleaned}`;
|
|
40
41
|
}
|
|
42
|
+
/** Allocate parameters without overwriting values from sibling clauses. */
|
|
43
|
+
function bindParam(params, name, value) {
|
|
44
|
+
let candidate = name;
|
|
45
|
+
let suffix = 1;
|
|
46
|
+
while (Object.prototype.hasOwnProperty.call(params, candidate))
|
|
47
|
+
candidate = `${name}_${suffix++}`;
|
|
48
|
+
params[candidate] = value;
|
|
49
|
+
return candidate;
|
|
50
|
+
}
|
|
41
51
|
function normalizeSortDirection(dir) {
|
|
42
52
|
return typeof dir === 'string' && dir.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
|
|
43
53
|
}
|
|
@@ -119,15 +129,12 @@ function parseWhere(modelName, where, params, dialect, prefix = '', ctx) {
|
|
|
119
129
|
conditions.push(`${col} IS NOT NULL`);
|
|
120
130
|
}
|
|
121
131
|
else if (v.not && typeof v.not === 'object' && !(v.not instanceof Date) && !Array.isArray(v.not)) {
|
|
122
|
-
const
|
|
123
|
-
const nestedSql = parseWhere(modelName, { [key]: v.not }, nestedParams, dialect, `${prefix}${key}_not_`, ctx);
|
|
124
|
-
Object.assign(params, nestedParams);
|
|
132
|
+
const nestedSql = parseWhere(modelName, { [key]: v.not }, params, dialect, `${prefix}${key}_not_`, ctx);
|
|
125
133
|
if (nestedSql)
|
|
126
134
|
conditions.push(`NOT (${nestedSql})`);
|
|
127
135
|
}
|
|
128
136
|
else {
|
|
129
|
-
const p = `${pname}_not
|
|
130
|
-
params[p] = v.not;
|
|
137
|
+
const p = bindParam(params, `${pname}_not`, v.not);
|
|
131
138
|
conditions.push(`${col} <> @${p}`);
|
|
132
139
|
}
|
|
133
140
|
}
|
|
@@ -136,49 +143,41 @@ function parseWhere(modelName, where, params, dialect, prefix = '', ctx) {
|
|
|
136
143
|
conditions.push(`${col} IS NULL`);
|
|
137
144
|
}
|
|
138
145
|
else {
|
|
139
|
-
const p = `${pname}_eq
|
|
140
|
-
params[p] = v.equals;
|
|
146
|
+
const p = bindParam(params, `${pname}_eq`, v.equals);
|
|
141
147
|
conditions.push(`${col} = @${p}`);
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
150
|
if (v.contains !== undefined) {
|
|
145
|
-
const p = `${pname}_co
|
|
146
|
-
params[p] = `%${v.contains}%`;
|
|
151
|
+
const p = bindParam(params, `${pname}_co`, `%${v.contains}%`);
|
|
147
152
|
conditions.push(`${col} LIKE @${p}`);
|
|
148
153
|
}
|
|
149
154
|
if (v.startsWith !== undefined) {
|
|
150
|
-
const p = `${pname}_sw
|
|
151
|
-
params[p] = `${v.startsWith}%`;
|
|
155
|
+
const p = bindParam(params, `${pname}_sw`, `${v.startsWith}%`);
|
|
152
156
|
conditions.push(`${col} LIKE @${p}`);
|
|
153
157
|
}
|
|
154
158
|
if (v.endsWith !== undefined) {
|
|
155
|
-
const p = `${pname}_ew
|
|
156
|
-
params[p] = `%${v.endsWith}`;
|
|
159
|
+
const p = bindParam(params, `${pname}_ew`, `%${v.endsWith}`);
|
|
157
160
|
conditions.push(`${col} LIKE @${p}`);
|
|
158
161
|
}
|
|
159
162
|
if (v.gte !== undefined) {
|
|
160
|
-
const p = `${pname}_gte
|
|
161
|
-
params[p] = v.gte;
|
|
163
|
+
const p = bindParam(params, `${pname}_gte`, v.gte);
|
|
162
164
|
conditions.push(`${col} >= @${p}`);
|
|
163
165
|
}
|
|
164
166
|
if (v.lte !== undefined) {
|
|
165
|
-
const p = `${pname}_lte
|
|
166
|
-
params[p] = v.lte;
|
|
167
|
+
const p = bindParam(params, `${pname}_lte`, v.lte);
|
|
167
168
|
conditions.push(`${col} <= @${p}`);
|
|
168
169
|
}
|
|
169
170
|
if (v.gt !== undefined) {
|
|
170
|
-
const p = `${pname}_gt
|
|
171
|
-
params[p] = v.gt;
|
|
171
|
+
const p = bindParam(params, `${pname}_gt`, v.gt);
|
|
172
172
|
conditions.push(`${col} > @${p}`);
|
|
173
173
|
}
|
|
174
174
|
if (v.lt !== undefined) {
|
|
175
|
-
const p = `${pname}_lt
|
|
176
|
-
params[p] = v.lt;
|
|
175
|
+
const p = bindParam(params, `${pname}_lt`, v.lt);
|
|
177
176
|
conditions.push(`${col} < @${p}`);
|
|
178
177
|
}
|
|
179
178
|
if (v.in !== undefined) {
|
|
180
179
|
if (Array.isArray(v.in) && v.in.length > 0) {
|
|
181
|
-
const ps = v.in.map((x, i) => { const p = `${pname}_in${i}
|
|
180
|
+
const ps = v.in.map((x, i) => { const p = bindParam(params, `${pname}_in${i}`, x); return `@${p}`; });
|
|
182
181
|
conditions.push(`${col} IN (${ps.join(', ')})`);
|
|
183
182
|
}
|
|
184
183
|
else {
|
|
@@ -187,7 +186,7 @@ function parseWhere(modelName, where, params, dialect, prefix = '', ctx) {
|
|
|
187
186
|
}
|
|
188
187
|
if (v.notIn !== undefined) {
|
|
189
188
|
if (Array.isArray(v.notIn) && v.notIn.length > 0) {
|
|
190
|
-
const ps = v.notIn.map((x, i) => { const p = `${pname}_notin${i}
|
|
189
|
+
const ps = v.notIn.map((x, i) => { const p = bindParam(params, `${pname}_notin${i}`, x); return `@${p}`; });
|
|
191
190
|
conditions.push(`${col} NOT IN (${ps.join(', ')})`);
|
|
192
191
|
}
|
|
193
192
|
else {
|
|
@@ -196,8 +195,8 @@ function parseWhere(modelName, where, params, dialect, prefix = '', ctx) {
|
|
|
196
195
|
}
|
|
197
196
|
}
|
|
198
197
|
else {
|
|
199
|
-
params
|
|
200
|
-
conditions.push(`${col} = @${
|
|
198
|
+
const p = bindParam(params, pname, value);
|
|
199
|
+
conditions.push(`${col} = @${p}`);
|
|
201
200
|
}
|
|
202
201
|
}
|
|
203
202
|
}
|
package/dist/base/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SqliteVectorSupport, VectorStrategyPreference } from '../sqlite/vector';
|
|
1
2
|
export type Dialect = 'mssql' | 'postgres' | 'mysql' | 'sqlite' | 'googlesheets';
|
|
2
3
|
export interface An5AdapterConfig {
|
|
3
4
|
connectionString?: string;
|
|
@@ -7,6 +8,17 @@ export interface An5AdapterConfig {
|
|
|
7
8
|
* run in NBase while the rows stay in `connectionString`'s database.
|
|
8
9
|
*/
|
|
9
10
|
nbase?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Path to sqlite-vec or the AN5 native vector extension. On SQLite it is loaded before the
|
|
13
|
+
* vector capabilities are probed, so `vectorSearch` ranks inside the database
|
|
14
|
+
* instead of loading the table into memory. Other providers ignore it.
|
|
15
|
+
*/
|
|
16
|
+
sqliteVec?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Pin the SQLite vector search strategy. `auto` (the default) probes for
|
|
19
|
+
* sqlite-vec, a driver function and JSON1, and uses the first that works.
|
|
20
|
+
*/
|
|
21
|
+
vectorStrategy?: VectorStrategyPreference;
|
|
10
22
|
engine?: QueryEngine;
|
|
11
23
|
db?: any;
|
|
12
24
|
driver?: any;
|
|
@@ -27,4 +39,6 @@ export interface QueryEngine {
|
|
|
27
39
|
connect(): Promise<void>;
|
|
28
40
|
disconnect(): Promise<void>;
|
|
29
41
|
beginTransaction(): Promise<TransactionHandle>;
|
|
42
|
+
/** SQLite only: extension loading and distance-function registration. */
|
|
43
|
+
vectorSupport?: SqliteVectorSupport;
|
|
30
44
|
}
|