@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.
Files changed (109) hide show
  1. package/README.md +163 -2
  2. package/dist/an5Adapter.d.ts +37 -0
  3. package/dist/an5Adapter.js +190 -65
  4. package/dist/base/metadata.d.ts +2 -0
  5. package/dist/base/metadata.js +10 -0
  6. package/dist/base/sql.d.ts +2 -0
  7. package/dist/base/sql.js +24 -25
  8. package/dist/base/types.d.ts +14 -0
  9. package/dist/googlesheets/adapter.js +16 -8
  10. package/dist/googlesheets/config.d.ts +13 -0
  11. package/dist/googlesheets/config.js +5 -1
  12. package/dist/googlesheets/oauth.d.ts +3 -0
  13. package/dist/googlesheets/oauth.js +43 -0
  14. package/dist/googlesheets/parseConnectionString.js +12 -0
  15. package/dist/googlesheets/tableClient.js +5 -23
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.js +8 -1
  18. package/dist/sqlite/browserEngine.d.ts +13 -0
  19. package/dist/sqlite/browserEngine.js +13 -0
  20. package/dist/sqlite/engine.d.ts +5 -0
  21. package/dist/sqlite/engine.js +10 -0
  22. package/dist/sqlite/index.d.ts +1 -0
  23. package/dist/sqlite/index.js +1 -0
  24. package/dist/sqlite/vector.d.ts +155 -0
  25. package/dist/sqlite/vector.js +436 -0
  26. package/dotnet/An5Adapter.cs +85 -27
  27. package/dotnet/Base/Core.cs +14 -0
  28. package/dotnet/Mssql/MssqlEngine.cs +44 -1
  29. package/dotnet/Postgres/PostgresEngine.cs +2 -0
  30. package/dotnet/Sqlite/SqliteEngine.cs +82 -2
  31. package/dotnet/Sqlite/SqliteVectors.cs +401 -0
  32. package/golang/an5_adapter.go +84 -32
  33. package/golang/base/vectors.go +263 -0
  34. package/golang/sqlite/vector.go +248 -0
  35. package/golang/sqlite_vector_test.go +288 -0
  36. package/golang/table_client.go +67 -3
  37. package/java/pom.xml +177 -0
  38. package/java/src/main/java/an5/adapters/An5Adapter.java +374 -0
  39. package/java/src/main/java/an5/adapters/An5Aggregate.java +116 -0
  40. package/java/src/main/java/an5/adapters/An5GroupBy.java +97 -0
  41. package/java/src/main/java/an5/adapters/An5Query.java +100 -0
  42. package/java/src/main/java/an5/adapters/An5TableClient.java +1031 -0
  43. package/java/src/main/java/an5/adapters/An5ViewClient.java +107 -0
  44. package/java/src/main/java/an5/adapters/base/Dialect.java +103 -0
  45. package/java/src/main/java/an5/adapters/base/FieldMeta.java +42 -0
  46. package/java/src/main/java/an5/adapters/base/JdbcUrls.java +201 -0
  47. package/java/src/main/java/an5/adapters/base/Metadata.java +195 -0
  48. package/java/src/main/java/an5/adapters/base/RelationDef.java +26 -0
  49. package/java/src/main/java/an5/adapters/base/SqlBuilder.java +422 -0
  50. package/java/src/main/java/an5/adapters/base/SqliteVectors.java +375 -0
  51. package/java/src/main/java/an5/adapters/base/Vectors.java +123 -0
  52. package/java/src/test/java/An5AdaptersSmoke.java +733 -0
  53. package/java/src/test/java/Json.java +174 -0
  54. package/java/src/test/java/QuerySemantics.java +111 -0
  55. package/kotlin/build.gradle.kts +145 -0
  56. package/kotlin/settings.gradle.kts +10 -0
  57. package/kotlin/src/main/kotlin/an5/adapters/Aggregate.kt +89 -0
  58. package/kotlin/src/main/kotlin/an5/adapters/An5.kt +91 -0
  59. package/kotlin/src/main/kotlin/an5/adapters/Query.kt +130 -0
  60. package/kotlin/src/main/kotlin/an5/adapters/Rows.kt +166 -0
  61. package/kotlin/src/main/kotlin/an5/adapters/TableClient.kt +169 -0
  62. package/kotlin/src/main/kotlin/an5/adapters/Types.kt +77 -0
  63. package/kotlin/src/test/kotlin/an5/adapters/An5Smoke.kt +329 -0
  64. package/native/sqlite/CMakeLists.txt +12 -0
  65. package/native/sqlite/README.md +117 -0
  66. package/native/sqlite/an5_vector.c +209 -0
  67. package/package.json +40 -9
  68. package/python/an5_adapter.py +37 -6
  69. package/python/base/__init__.py +15 -0
  70. package/python/base/vectors.py +434 -0
  71. package/python/sqlite/__init__.py +2 -2
  72. package/python/sqlite/provider.py +21 -1
  73. package/python/table_client.py +129 -59
  74. package/rust/src/adapter.rs +342 -24
  75. package/rust/src/base/mod.rs +7 -0
  76. package/rust/src/base/vectors.rs +293 -0
  77. package/rust/src/lib.rs +3 -3
  78. package/scripts/sqlite-native-build.js +32 -0
  79. package/swift/Package.swift +22 -0
  80. package/swift/Sources/An5Adapters/An5Adapter.swift +160 -0
  81. package/swift/Sources/An5Adapters/Metadata.swift +183 -0
  82. package/swift/Sources/An5Adapters/Query.swift +105 -0
  83. package/swift/Sources/An5Adapters/Row+Accessors.swift +129 -0
  84. package/swift/Sources/An5Adapters/SQLBuilder.swift +359 -0
  85. package/swift/Sources/An5Adapters/SQLiteDriver.swift +301 -0
  86. package/swift/Sources/An5Adapters/SqliteVectors.swift +243 -0
  87. package/swift/Sources/An5Adapters/TableClient.swift +671 -0
  88. package/swift/Sources/An5Adapters/Types.swift +127 -0
  89. package/swift/Sources/An5Adapters/Vectors.swift +86 -0
  90. package/swift/Sources/An5Adapters/ViewClient.swift +50 -0
  91. package/swift/Sources/CSQLite/module.modulemap +8 -0
  92. package/swift/Sources/CSQLite/shim.h +9 -0
  93. package/swift/Tests/An5AdaptersTests/QuerySemanticsTests.swift +51 -0
  94. package/swift/Tests/An5AdaptersTests/RuntimeSmokeTests.swift +488 -0
  95. package/typescript/src/an5Adapter.ts +200 -58
  96. package/typescript/src/base/metadata.ts +8 -0
  97. package/typescript/src/base/sql.ts +23 -16
  98. package/typescript/src/base/types.ts +15 -0
  99. package/typescript/src/googlesheets/adapter.ts +17 -8
  100. package/typescript/src/googlesheets/config.ts +10 -1
  101. package/typescript/src/googlesheets/oauth.ts +31 -0
  102. package/typescript/src/googlesheets/parseConnectionString.ts +8 -0
  103. package/typescript/src/googlesheets/tableClient.ts +5 -19
  104. package/typescript/src/index.ts +13 -0
  105. package/typescript/src/sqlite/browserEngine.ts +24 -0
  106. package/typescript/src/sqlite/engine.ts +13 -0
  107. package/typescript/src/sqlite/index.ts +1 -1
  108. package/typescript/src/sqlite/vector.ts +541 -0
  109. package/golang/sqlite/sqlite.go +0 -14
@@ -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(self, vector: List[float], take: int = 10, where=None, vector_field: str = "embedding", distance_metric: str = "cosine") -> List[Dict]:
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
- # 1. Primary path: Native database SQL vector query execution (MSSQL VECTOR_DISTANCE / Postgres pgvector)
524
- # SQLite has no vector operators at all, so skip it entirely: building
525
- # the SQL and catching the error would cost work and still end up on the
526
- # in-memory path in step 2.
527
- native_rows = None
528
- if self._dialect != DIALECT_SQLITE:
529
- try:
530
- field_sql = _quote(vector_field, self._dialect)
531
- if self._dialect == DIALECT_POSTGRES:
532
- op = "<=>" if distance_metric == "cosine" else ("<->" if distance_metric == "euclidean" else "<#>")
533
- query = f"SELECT *, ({field_sql} {op} %s::vector) AS distance FROM {self._table_sql}"
534
- query_params = [vec_json] + list(params.values())
535
- if where_sql:
536
- query += f" WHERE {field_sql} IS NOT NULL AND ({where_sql})"
537
- else:
538
- query += f" WHERE {field_sql} IS NOT NULL"
539
- query += f" ORDER BY distance ASC LIMIT {take}"
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
- placeholder = "?"
542
- 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}"
543
- query_params = [vec_json] + list(params.values())
544
- if where_sql:
545
- query += f" WHERE {field_sql} IS NOT NULL AND ({where_sql})"
546
- else:
547
- query += f" WHERE {field_sql} IS NOT NULL"
548
- query += " ORDER BY distance ASC"
549
-
550
- native_rows = self._adapter.exec(query, query_params)
551
-
552
- if native_rows is not None:
553
- return native_rows
554
- except Exception:
555
- pass
556
-
557
- # 2. Secondary fallback: In-memory similarity computation if DB engine lacks native vector extension
558
- rows = self.find_many(where=where)
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 rows:
561
- raw = row.get(vector_field)
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
- dot = sum(a * b for a, b in zip(vector, vec))
572
- m1 = sum(a ** 2 for a in vector) ** 0.5
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])