@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/python/table_client.py
CHANGED
|
@@ -5,8 +5,10 @@ import uuid
|
|
|
5
5
|
from typing import Any, Dict, Generic, List, Optional, TypeVar
|
|
6
6
|
try:
|
|
7
7
|
from .base import DIALECT_MSSQL, DIALECT_POSTGRES, DIALECT_SQLITE, get_fields_for_model, _build_order_by, _parse_where, _quote, _quote_table, _resolve_table, get_relations_for_model
|
|
8
|
+
from .base.vectors import decode_vector, encode_vector, is_vector_field, needs_vector_encoding, run_sqlite_vector_search, vector_distance
|
|
8
9
|
except ImportError:
|
|
9
10
|
from base import DIALECT_MSSQL, DIALECT_POSTGRES, DIALECT_SQLITE, get_fields_for_model, _build_order_by, _parse_where, _quote, _quote_table, _resolve_table, get_relations_for_model
|
|
11
|
+
from base.vectors import decode_vector, encode_vector, is_vector_field, needs_vector_encoding, run_sqlite_vector_search, vector_distance
|
|
10
12
|
|
|
11
13
|
# ─── Select / Include helpers ────────────────────────────────────────────────────────
|
|
12
14
|
|
|
@@ -255,6 +257,50 @@ class AdapterTableClient(Generic[T]):
|
|
|
255
257
|
)
|
|
256
258
|
return fields or []
|
|
257
259
|
|
|
260
|
+
@property
|
|
261
|
+
def _vector_fields(self) -> List[str]:
|
|
262
|
+
"""The `VECTOR(n)` columns of this model, from the generated metadata."""
|
|
263
|
+
return [f["name"] for f in self._fields if is_vector_field(f)]
|
|
264
|
+
|
|
265
|
+
def _encode_vector_fields(self, data: Dict) -> Dict:
|
|
266
|
+
"""Encodes every `VECTOR(n)` value on its way into SQLite.
|
|
267
|
+
|
|
268
|
+
The column holds a float32 BLOB, which is a third of the size of the JSON
|
|
269
|
+
text it replaces and is what the in-database distance functions read.
|
|
270
|
+
Values that already arrived as text or bytes are left alone, so rows
|
|
271
|
+
written by an older version stay readable.
|
|
272
|
+
"""
|
|
273
|
+
if self._dialect != DIALECT_SQLITE:
|
|
274
|
+
return data
|
|
275
|
+
out = dict(data)
|
|
276
|
+
for name in self._vector_fields:
|
|
277
|
+
value = out.get(name)
|
|
278
|
+
if needs_vector_encoding(value):
|
|
279
|
+
parsed = [float(v) for v in value]
|
|
280
|
+
if parsed:
|
|
281
|
+
out[name] = encode_vector(parsed)
|
|
282
|
+
return out
|
|
283
|
+
|
|
284
|
+
def _decode_vector_fields(self, rows: List[Dict]) -> List[Dict]:
|
|
285
|
+
"""Decodes `VECTOR(n)` columns back into lists of numbers after a read."""
|
|
286
|
+
if self._dialect != DIALECT_SQLITE or not rows:
|
|
287
|
+
return rows
|
|
288
|
+
names = self._vector_fields
|
|
289
|
+
if not names:
|
|
290
|
+
return rows
|
|
291
|
+
decoded = []
|
|
292
|
+
for row in rows:
|
|
293
|
+
changed = False
|
|
294
|
+
out = dict(row)
|
|
295
|
+
for name in names:
|
|
296
|
+
if name in out:
|
|
297
|
+
values = decode_vector(out[name])
|
|
298
|
+
if values:
|
|
299
|
+
out[name] = values
|
|
300
|
+
changed = True
|
|
301
|
+
decoded.append(out if changed else row)
|
|
302
|
+
return decoded
|
|
303
|
+
|
|
258
304
|
def _pagination(self, take: Optional[int], skip: int, order_sql: str) -> str:
|
|
259
305
|
if take is None:
|
|
260
306
|
return ""
|
|
@@ -282,7 +328,7 @@ class AdapterTableClient(Generic[T]):
|
|
|
282
328
|
query += f" {order_sql}"
|
|
283
329
|
query += self._pagination(take, skip, order_sql)
|
|
284
330
|
|
|
285
|
-
rows = self._adapter.exec(query, list(params.values()))
|
|
331
|
+
rows = self._decode_vector_fields(self._adapter.exec(query, list(params.values())))
|
|
286
332
|
|
|
287
333
|
if include:
|
|
288
334
|
_resolve_includes(self._model, rows, include, self._adapter)
|
|
@@ -314,6 +360,7 @@ class AdapterTableClient(Generic[T]):
|
|
|
314
360
|
data = {**data, id_field["name"]: str(uuid.uuid4())}
|
|
315
361
|
|
|
316
362
|
data, relation_writes = _split_relation_writes(self._model, data)
|
|
363
|
+
data = self._encode_vector_fields(data)
|
|
317
364
|
|
|
318
365
|
cols = [k for k, v in data.items() if v is not None]
|
|
319
366
|
values = [data[c] for c in cols]
|
|
@@ -362,6 +409,7 @@ class AdapterTableClient(Generic[T]):
|
|
|
362
409
|
params: Dict = {}
|
|
363
410
|
where_sql = _parse_where(self._model, where, params, self._dialect, "w_")
|
|
364
411
|
data, relation_writes = _split_relation_writes(self._model, data)
|
|
412
|
+
data = self._encode_vector_fields(data)
|
|
365
413
|
set_parts: List[str] = []
|
|
366
414
|
set_values: List = []
|
|
367
415
|
for col, val in data.items():
|
|
@@ -405,6 +453,7 @@ class AdapterTableClient(Generic[T]):
|
|
|
405
453
|
def update_many(self, where: Optional[Dict], data: Dict) -> Dict:
|
|
406
454
|
params: Dict = {}
|
|
407
455
|
where_sql = _parse_where(self._model, where, params, self._dialect, "w_")
|
|
456
|
+
data = self._encode_vector_fields(data)
|
|
408
457
|
set_parts: List[str] = []
|
|
409
458
|
set_values: List = []
|
|
410
459
|
for col, val in data.items():
|
|
@@ -514,72 +563,93 @@ class AdapterTableClient(Generic[T]):
|
|
|
514
563
|
|
|
515
564
|
return self._adapter.exec(query, list(params.values()))
|
|
516
565
|
|
|
517
|
-
def vector_search(
|
|
566
|
+
def vector_search(
|
|
567
|
+
self,
|
|
568
|
+
vector: List[float],
|
|
569
|
+
take: int = 10,
|
|
570
|
+
where=None,
|
|
571
|
+
vector_field: str = "embedding",
|
|
572
|
+
distance_metric: str = "cosine",
|
|
573
|
+
) -> List[Dict]:
|
|
518
574
|
dim = len(vector)
|
|
519
575
|
vec_json = json.dumps(vector)
|
|
520
576
|
params: Dict = {}
|
|
521
577
|
where_sql = _parse_where(self._model, where, params, self._dialect)
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
#
|
|
525
|
-
#
|
|
526
|
-
#
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
578
|
+
field_sql = _quote(vector_field, self._dialect)
|
|
579
|
+
|
|
580
|
+
# 1. SQLite ranks through sqlite-vec, a driver function or json_each,
|
|
581
|
+
# whichever this connection supports, so the table never leaves the
|
|
582
|
+
# database unless it has to.
|
|
583
|
+
if self._dialect == DIALECT_SQLITE:
|
|
584
|
+
rows = run_sqlite_vector_search(
|
|
585
|
+
exec_fn=self._adapter.exec,
|
|
586
|
+
table=self._table_sql,
|
|
587
|
+
raw_table=self._table.strip("[]"),
|
|
588
|
+
column=field_sql,
|
|
589
|
+
raw_column=vector_field,
|
|
590
|
+
vector=vector,
|
|
591
|
+
metric=distance_metric,
|
|
592
|
+
take=take,
|
|
593
|
+
tail=f" WHERE {where_sql}" if where_sql else "",
|
|
594
|
+
params=list(params.values()),
|
|
595
|
+
support=self._adapter.vector_support(),
|
|
596
|
+
preference=getattr(self._adapter, "vector_strategy", None),
|
|
597
|
+
)
|
|
598
|
+
if rows is not None:
|
|
599
|
+
return self._decode_vector_fields(rows)
|
|
600
|
+
return self._score_vectors_in_memory(vector, take, where, vector_field, distance_metric)
|
|
601
|
+
|
|
602
|
+
# 2. Native dialect execution (PostgreSQL pgvector / MSSQL VECTOR_DISTANCE).
|
|
603
|
+
try:
|
|
604
|
+
if self._dialect == DIALECT_POSTGRES:
|
|
605
|
+
op = "<=>" if distance_metric == "cosine" else ("<->" if distance_metric == "euclidean" else "<#>")
|
|
606
|
+
query = f"SELECT *, ({field_sql} {op} %s::vector) AS distance FROM {self._table_sql}"
|
|
607
|
+
query_params = [vec_json] + list(params.values())
|
|
608
|
+
if where_sql:
|
|
609
|
+
query += f" WHERE {field_sql} IS NOT NULL AND ({where_sql})"
|
|
540
610
|
else:
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
query += "
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
#
|
|
558
|
-
|
|
611
|
+
query += f" WHERE {field_sql} IS NOT NULL"
|
|
612
|
+
query += f" ORDER BY distance ASC LIMIT {take}"
|
|
613
|
+
else:
|
|
614
|
+
placeholder = "?"
|
|
615
|
+
query = f"SELECT TOP ({take}) *, VECTOR_DISTANCE('{distance_metric}', CAST({field_sql} AS VECTOR({dim}, float32)), CAST({placeholder} AS VECTOR({dim}, float32))) AS distance FROM {self._table_sql}{self._nolock}"
|
|
616
|
+
query_params = [vec_json] + list(params.values())
|
|
617
|
+
if where_sql:
|
|
618
|
+
query += f" WHERE {field_sql} IS NOT NULL AND ({where_sql})"
|
|
619
|
+
else:
|
|
620
|
+
query += f" WHERE {field_sql} IS NOT NULL"
|
|
621
|
+
query += " ORDER BY distance ASC"
|
|
622
|
+
|
|
623
|
+
return self._adapter.exec(query, query_params)
|
|
624
|
+
except Exception:
|
|
625
|
+
pass
|
|
626
|
+
|
|
627
|
+
# 3. Fallback: score the column in Python.
|
|
628
|
+
return self._score_vectors_in_memory(vector, take, where, vector_field, distance_metric)
|
|
629
|
+
|
|
630
|
+
def _score_vectors_in_memory(
|
|
631
|
+
self,
|
|
632
|
+
vector: List[float],
|
|
633
|
+
take: int,
|
|
634
|
+
where,
|
|
635
|
+
vector_field: str,
|
|
636
|
+
distance_metric: str,
|
|
637
|
+
) -> List[Dict]:
|
|
638
|
+
"""Ranks the vector column in Python.
|
|
639
|
+
|
|
640
|
+
The last resort for every provider, and the only option on a SQLite
|
|
641
|
+
connection that offers neither sqlite-vec, a driver function nor JSON1.
|
|
642
|
+
The whole table is loaded, so it is only reasonable for a table small
|
|
643
|
+
enough to fit in memory.
|
|
644
|
+
"""
|
|
559
645
|
scored = []
|
|
560
|
-
for row in
|
|
561
|
-
|
|
562
|
-
if raw is None:
|
|
563
|
-
continue
|
|
564
|
-
try:
|
|
565
|
-
vec = json.loads(raw) if isinstance(raw, str) else list(raw)
|
|
566
|
-
except Exception:
|
|
567
|
-
continue
|
|
646
|
+
for row in self.find_many(where=where):
|
|
647
|
+
vec = decode_vector(row.get(vector_field))
|
|
568
648
|
if not vec or len(vec) != len(vector):
|
|
569
649
|
continue
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
m2 = sum(b ** 2 for b in vec) ** 0.5
|
|
574
|
-
cosine = dot / (m1 * m2) if m1 and m2 else 0.0
|
|
575
|
-
|
|
576
|
-
if distance_metric == "cosine":
|
|
577
|
-
dist = 1.0 - cosine
|
|
578
|
-
elif distance_metric == "dot":
|
|
579
|
-
dist = -dot
|
|
580
|
-
else:
|
|
581
|
-
dist = sum((a - b) ** 2 for a, b in zip(vector, vec)) ** 0.5
|
|
582
|
-
|
|
650
|
+
dist = vector_distance(vector, vec, distance_metric)
|
|
651
|
+
if dist is None:
|
|
652
|
+
continue
|
|
583
653
|
scored.append((row, dist))
|
|
584
654
|
|
|
585
655
|
scored.sort(key=lambda x: x[1])
|