@an5/adapters 0.2.11 → 0.2.14

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 (91) hide show
  1. package/README.md +137 -5
  2. package/dist/an5Adapter.d.ts +42 -1
  3. package/dist/an5Adapter.js +146 -38
  4. package/dist/base/types.d.ts +14 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +8 -1
  7. package/dist/sqlite/browserEngine.d.ts +13 -0
  8. package/dist/sqlite/browserEngine.js +13 -0
  9. package/dist/sqlite/engine.d.ts +5 -0
  10. package/dist/sqlite/engine.js +10 -0
  11. package/dist/sqlite/index.d.ts +1 -0
  12. package/dist/sqlite/index.js +1 -0
  13. package/dist/sqlite/vector.d.ts +155 -0
  14. package/dist/sqlite/vector.js +432 -0
  15. package/dotnet/An5Adapter.cs +99 -28
  16. package/dotnet/Base/Core.cs +14 -0
  17. package/dotnet/Mssql/MssqlEngine.cs +44 -1
  18. package/dotnet/Postgres/PostgresEngine.cs +2 -0
  19. package/dotnet/Sqlite/SqliteEngine.cs +82 -2
  20. package/dotnet/Sqlite/SqliteVectors.cs +401 -0
  21. package/golang/an5_adapter.go +84 -32
  22. package/golang/base/vectors.go +263 -0
  23. package/golang/sqlite/vector.go +248 -0
  24. package/golang/sqlite_vector_test.go +288 -0
  25. package/golang/table_client.go +67 -3
  26. package/java/pom.xml +177 -0
  27. package/java/src/main/java/an5/adapters/An5Adapter.java +374 -0
  28. package/java/src/main/java/an5/adapters/An5Aggregate.java +116 -0
  29. package/java/src/main/java/an5/adapters/An5GroupBy.java +97 -0
  30. package/java/src/main/java/an5/adapters/An5Query.java +100 -0
  31. package/java/src/main/java/an5/adapters/An5TableClient.java +1030 -0
  32. package/java/src/main/java/an5/adapters/An5ViewClient.java +107 -0
  33. package/java/src/main/java/an5/adapters/base/Dialect.java +103 -0
  34. package/java/src/main/java/an5/adapters/base/FieldMeta.java +42 -0
  35. package/java/src/main/java/an5/adapters/base/JdbcUrls.java +201 -0
  36. package/java/src/main/java/an5/adapters/base/Metadata.java +195 -0
  37. package/java/src/main/java/an5/adapters/base/RelationDef.java +26 -0
  38. package/java/src/main/java/an5/adapters/base/SqlBuilder.java +422 -0
  39. package/java/src/main/java/an5/adapters/base/SqliteVectors.java +375 -0
  40. package/java/src/main/java/an5/adapters/base/Vectors.java +123 -0
  41. package/java/src/test/java/An5AdaptersSmoke.java +733 -0
  42. package/java/src/test/java/Json.java +174 -0
  43. package/java/src/test/java/QuerySemantics.java +111 -0
  44. package/kotlin/build.gradle.kts +145 -0
  45. package/kotlin/settings.gradle.kts +10 -0
  46. package/kotlin/src/main/kotlin/an5/adapters/Aggregate.kt +89 -0
  47. package/kotlin/src/main/kotlin/an5/adapters/An5.kt +91 -0
  48. package/kotlin/src/main/kotlin/an5/adapters/Query.kt +130 -0
  49. package/kotlin/src/main/kotlin/an5/adapters/Rows.kt +166 -0
  50. package/kotlin/src/main/kotlin/an5/adapters/TableClient.kt +169 -0
  51. package/kotlin/src/main/kotlin/an5/adapters/Types.kt +77 -0
  52. package/kotlin/src/test/kotlin/an5/adapters/An5Smoke.kt +329 -0
  53. package/native/sqlite/CMakeLists.txt +12 -0
  54. package/native/sqlite/README.md +117 -0
  55. package/native/sqlite/an5_vector.c +209 -0
  56. package/package.json +33 -7
  57. package/python/an5_adapter.py +37 -6
  58. package/python/base/__init__.py +15 -0
  59. package/python/base/vectors.py +431 -0
  60. package/python/sqlite/__init__.py +2 -2
  61. package/python/sqlite/provider.py +21 -1
  62. package/python/table_client.py +129 -59
  63. package/rust/src/adapter.rs +342 -24
  64. package/rust/src/base/mod.rs +7 -0
  65. package/rust/src/base/vectors.rs +293 -0
  66. package/rust/src/lib.rs +3 -3
  67. package/scripts/sqlite-native-build.js +32 -0
  68. package/swift/Package.swift +22 -0
  69. package/swift/Sources/An5Adapters/An5Adapter.swift +160 -0
  70. package/swift/Sources/An5Adapters/Metadata.swift +183 -0
  71. package/swift/Sources/An5Adapters/Query.swift +105 -0
  72. package/swift/Sources/An5Adapters/Row+Accessors.swift +129 -0
  73. package/swift/Sources/An5Adapters/SQLBuilder.swift +359 -0
  74. package/swift/Sources/An5Adapters/SQLiteDriver.swift +301 -0
  75. package/swift/Sources/An5Adapters/SqliteVectors.swift +243 -0
  76. package/swift/Sources/An5Adapters/TableClient.swift +671 -0
  77. package/swift/Sources/An5Adapters/Types.swift +127 -0
  78. package/swift/Sources/An5Adapters/Vectors.swift +86 -0
  79. package/swift/Sources/An5Adapters/ViewClient.swift +50 -0
  80. package/swift/Sources/CSQLite/module.modulemap +8 -0
  81. package/swift/Sources/CSQLite/shim.h +9 -0
  82. package/swift/Tests/An5AdaptersTests/QuerySemanticsTests.swift +51 -0
  83. package/swift/Tests/An5AdaptersTests/RuntimeSmokeTests.swift +488 -0
  84. package/typescript/src/an5Adapter.ts +155 -31
  85. package/typescript/src/base/types.ts +15 -0
  86. package/typescript/src/index.ts +13 -0
  87. package/typescript/src/sqlite/browserEngine.ts +24 -0
  88. package/typescript/src/sqlite/engine.ts +13 -0
  89. package/typescript/src/sqlite/index.ts +1 -1
  90. package/typescript/src/sqlite/vector.ts +537 -0
  91. package/golang/sqlite/sqlite.go +0 -14
@@ -10,8 +10,12 @@ use sqlx::{Any, AnyConnection, AnyPool, Row};
10
10
  use std::collections::HashMap;
11
11
 
12
12
  use crate::base::{
13
- build_order_by, build_where, get_fields_for_model, resolve_table, set_adapter_metadata,
14
- vector_distance, AdapterMetadata, Dialect, RelationDef, Where,
13
+ build_order_by, build_where, decode_vector, encode_vector, get_fields_for_model, is_vector_field,
14
+ needs_vector_encoding, resolve_table, set_adapter_metadata, vector_distance, AdapterMetadata,
15
+ Dialect, RelationDef, Where,
16
+ };
17
+ use crate::base::vectors::{
18
+ distance_function, normalize_metric, plan_vector_strategies, STRATEGY_SQLITE_VEC, STRATEGY_UDF,
15
19
  };
16
20
 
17
21
  /// A database row as a plain JSON object.
@@ -213,6 +217,9 @@ pub struct An5Adapter {
213
217
  pool: AnyPool,
214
218
  dialect: Dialect,
215
219
  connection_string: String,
220
+ /// SQLite only: pins one of `sqlite-vec`, `udf`, `sql` or `memory` instead of
221
+ /// probing for the fastest strategy available. Empty probes.
222
+ pub vector_strategy: Option<String>,
216
223
  }
217
224
 
218
225
  impl An5Adapter {
@@ -227,6 +234,7 @@ impl An5Adapter {
227
234
  dialect: Dialect::detect(connection_string),
228
235
  pool,
229
236
  connection_string: connection_string.to_string(),
237
+ vector_strategy: None,
230
238
  })
231
239
  }
232
240
 
@@ -237,6 +245,7 @@ impl An5Adapter {
237
245
  dialect: Dialect::detect(connection_string),
238
246
  pool,
239
247
  connection_string: connection_string.to_string(),
248
+ vector_strategy: None,
240
249
  }
241
250
  }
242
251
 
@@ -307,6 +316,45 @@ impl An5Adapter {
307
316
  rows_to_maps(&rows)
308
317
  }
309
318
 
319
+ /// What this SQLite connection can do for vector search, probed with cheap
320
+ /// `SELECT`s.
321
+ ///
322
+ /// `sqlx` connects through a pooled `AnyPool` and exposes no way to register a
323
+ /// SQL function or load an extension, so a stock adapter normally reports only
324
+ /// JSON1. That is enough to rank with `json_each`; the sqlite-vec and UDF
325
+ /// strategies need a driver built for them.
326
+ pub async fn sqlite_capabilities(&self) -> HashMap<&'static str, bool> {
327
+ let mut capabilities = HashMap::from([
328
+ ("vec", self.probe("SELECT vec_version()").await),
329
+ ("udf", self.probe("SELECT an5_vec_cosine(zeroblob(4), zeroblob(4))").await),
330
+ ("json1", self.probe("SELECT json_valid('[1]')").await),
331
+ ]);
332
+ // The keys are static strings, so the map can be handed out directly.
333
+ capabilities.shrink_to_fit();
334
+ capabilities
335
+ }
336
+
337
+ /// A vector column's declared DDL type, which is what decides whether the
338
+ /// `json_each` strategy can reach the rows in it.
339
+ pub async fn sqlite_column_type(&self, table: &str, column: &str) -> Option<String> {
340
+ let rows = self
341
+ .query_raw(
342
+ "SELECT type FROM pragma_table_info(?) WHERE name = ?",
343
+ &[Value::from(table), Value::from(column)],
344
+ )
345
+ .await
346
+ .ok()?;
347
+ rows.first()
348
+ .and_then(|row| row.get("type"))
349
+ .and_then(Value::as_str)
350
+ .map(str::to_string)
351
+ }
352
+
353
+ /// True when a probe statement ran, whatever it returned.
354
+ async fn probe(&self, sql: &str) -> bool {
355
+ self.query_raw(sql, &[]).await.is_ok()
356
+ }
357
+
310
358
  /// Execute a non-SELECT statement and return the affected row count.
311
359
  pub async fn execute_raw(&self, sql: &str, args: &[Value]) -> Result<u64> {
312
360
  let mut q = sqlx::query(sql);
@@ -360,6 +408,9 @@ fn bind_value<'q>(
360
408
  }
361
409
  }
362
410
  Value::String(s) => q.bind(s.clone()),
411
+ // A vector is bound as the float32 BLOB a `VECTOR(n)` column stores; any
412
+ // other structure has no SQLite representation.
413
+ Value::Object(_) if blob_bytes(v).is_some() => q.bind(blob_bytes(v)),
363
414
  other => q.bind(other.to_string()),
364
415
  }
365
416
  }
@@ -384,15 +435,55 @@ fn column_to_value(row: &sqlx::any::AnyRow, idx: usize) -> Result<Value> {
384
435
  probe!(i64, Value::from);
385
436
  probe!(f64, Value::from);
386
437
  probe!(String, Value::String);
387
- probe!(Vec<u8>, |b: Vec<u8>| {
388
- Value::String(String::from_utf8_lossy(&b).into_owned())
389
- });
438
+ probe!(Vec<u8>, |b: Vec<u8>| bytes_to_value(&b));
390
439
 
391
440
  // An undecodable column (driver-specific type) becomes null rather than
392
441
  // failing the whole query.
393
442
  Ok(Value::Null)
394
443
  }
395
444
 
445
+ /// The member name marking a value as raw bytes rather than text.
446
+ const BLOB_KEY: &str = "__an5Blob";
447
+
448
+ /// Prefix kept for rows written before the marker object, so they still read back.
449
+ const BLOB_PREFIX: &str = "\u{0}an5:blob:";
450
+
451
+ /// Carries bytes through the JSON row model.
452
+ ///
453
+ /// SQLite has no vector type, so a `VECTOR(n)` column is a BLOB of float32 values,
454
+ /// and the row model is a JSON object with no byte type. A value from `blob_value`
455
+ /// is bound and read as raw bytes; text columns stay plain strings.
456
+ pub fn blob_value(bytes: &[u8]) -> Value {
457
+ let mut map = RowMap::new();
458
+ let mut hex = String::with_capacity(bytes.len() * 2);
459
+ for byte in bytes {
460
+ hex.push_str(&format!("{:02x}", byte));
461
+ }
462
+ map.insert(BLOB_KEY.to_string(), Value::String(hex));
463
+ Value::Object(map)
464
+ }
465
+
466
+ /// Recovers the bytes behind a value from [`blob_value`].
467
+ pub fn blob_bytes(value: &Value) -> Option<Vec<u8>> {
468
+ let hex = match value {
469
+ Value::Object(map) => map.get(BLOB_KEY)?.as_str()?.to_string(),
470
+ Value::String(text) => text.strip_prefix(BLOB_PREFIX)?.to_string(),
471
+ _ => return None,
472
+ };
473
+ if hex.len() % 2 != 0 {
474
+ return None;
475
+ }
476
+ (0..hex.len())
477
+ .step_by(2)
478
+ .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).ok())
479
+ .collect()
480
+ }
481
+
482
+ /// A text BLOB stays text; anything binary becomes a [`blob_value`].
483
+ fn bytes_to_value(bytes: &[u8]) -> Value {
484
+ blob_value(bytes)
485
+ }
486
+
396
487
  fn rows_to_maps(rows: &[sqlx::any::AnyRow]) -> Result<Vec<RowMap>> {
397
488
  let mut out = Vec::with_capacity(rows.len());
398
489
  for row in rows {
@@ -525,7 +616,7 @@ impl TableClient {
525
616
  if let Some(inc) = &args.include {
526
617
  self.resolve_includes(&mut rows, inc).await?;
527
618
  }
528
- Ok(rows)
619
+ Ok(self.decode_vector_rows(rows))
529
620
  }
530
621
 
531
622
  /// First row matching the args, or `None`.
@@ -571,7 +662,7 @@ impl TableClient {
571
662
  }
572
663
  let mut data = data.clone();
573
664
  apply_generated_primary_key(&self.table_name, &mut data);
574
- let data = &data;
665
+ let data = &self.encode_vector_fields(&data);
575
666
  let (scalars, _relations) = split_relation_writes(&self.table_name, data);
576
667
 
577
668
  let mut cols: Vec<String> = Vec::new();
@@ -596,7 +687,14 @@ impl TableClient {
596
687
  );
597
688
 
598
689
  match self.adapter.query_raw(&sql, &args).await {
599
- Ok(rows) if !rows.is_empty() => Ok(rows.into_iter().next().unwrap()),
690
+ Ok(rows) if !rows.is_empty() => {
691
+ // The inserted row comes back with the vector still in its stored form,
692
+ // so it needs the same decoding a `find` would apply. Without it the
693
+ // caller receives the internal blob envelope where the vector should be.
694
+ Ok(self
695
+ .decode_vector_rows(vec![rows.into_iter().next().unwrap()])
696
+ .remove(0))
697
+ }
600
698
  // The engine rejected the returning clause; insert without it.
601
699
  _ => {
602
700
  let sql = format!(
@@ -814,6 +912,198 @@ impl TableClient {
814
912
  self.adapter.query_raw(&sql, &w.args).await
815
913
  }
816
914
 
915
+ /// The `VECTOR(n)` columns of this model, from the generated metadata.
916
+ fn vector_fields(&self) -> Vec<String> {
917
+ match get_fields_for_model(&self.table_name) {
918
+ Some(Value::Object(fields)) => fields
919
+ .iter()
920
+ .filter(|(_, definition)| is_vector_field(definition))
921
+ .map(|(name, _)| name.clone())
922
+ .collect(),
923
+ _ => Vec::new(),
924
+ }
925
+ }
926
+
927
+ /// Encodes every `VECTOR(n)` value on its way into SQLite.
928
+ ///
929
+ /// The column holds a float32 BLOB, which is a third of the size of the JSON
930
+ /// text it replaces. Values that already arrived as text or bytes are left
931
+ /// alone, so rows written by an older version stay readable.
932
+ fn encode_vector_fields(&self, data: &RowMap) -> RowMap {
933
+ if self.adapter.dialect != Dialect::Sqlite {
934
+ return data.clone();
935
+ }
936
+ let names = self.vector_fields();
937
+ if names.is_empty() {
938
+ return data.clone();
939
+ }
940
+ let mut out = data.clone();
941
+ for name in names {
942
+ if let Some(value) = out.get(&name) {
943
+ if needs_vector_encoding(value) {
944
+ if let Some(values) = decode_vector(value) {
945
+ out.insert(name, blob_value(&encode_vector(&values)));
946
+ }
947
+ }
948
+ }
949
+ }
950
+ out
951
+ }
952
+
953
+ /// Decodes `VECTOR(n)` columns back into arrays of numbers after a read.
954
+ fn decode_vector_rows(&self, rows: Vec<RowMap>) -> Vec<RowMap> {
955
+ if self.adapter.dialect != Dialect::Sqlite || rows.is_empty() {
956
+ return rows;
957
+ }
958
+ let names = self.vector_fields();
959
+ if names.is_empty() {
960
+ return rows;
961
+ }
962
+ let mut out = rows;
963
+ for row in &mut out {
964
+ for name in &names {
965
+ if let Some(value) = row.get(name) {
966
+ if let Some(values) = decode_vector(value) {
967
+ row.insert(name.clone(), Value::from(values));
968
+ }
969
+ }
970
+ }
971
+ }
972
+ out
973
+ }
974
+
975
+ /// SQLite ranks a `VECTOR(n)` column inside the database.
976
+ ///
977
+ /// Returns `None` when the connection offers no strategy, so the caller falls
978
+ /// back to scoring in Rust.
979
+ async fn sqlite_vector_search(
980
+ &self,
981
+ args: &VectorSearchArgs,
982
+ field: &str,
983
+ metric: &str,
984
+ take: i64,
985
+ w: &Where,
986
+ ) -> Result<Option<Vec<RowMap>>> {
987
+ if args.vector.is_empty() {
988
+ return Ok(Some(Vec::new()));
989
+ }
990
+ let metric = normalize_metric(metric);
991
+ let column = self.adapter.dialect.quote_identifier(field);
992
+ let table = self.quoted_table();
993
+
994
+ let capabilities = self.adapter.sqlite_capabilities().await;
995
+ let declared = self
996
+ .adapter
997
+ .sqlite_column_type(&self.physical_table, field)
998
+ .await;
999
+ let tail = if w.is_empty() {
1000
+ String::new()
1001
+ } else {
1002
+ format!(" WHERE {}", w.clause)
1003
+ };
1004
+
1005
+ for strategy in plan_vector_strategies(
1006
+ &capabilities,
1007
+ declared.as_deref(),
1008
+ self.adapter.vector_strategy.as_deref(),
1009
+ ) {
1010
+ if strategy == crate::base::STRATEGY_SQL {
1011
+ let clause = if tail.is_empty() { " WHERE ".to_string() } else { format!("{tail} AND ") };
1012
+ let binary = self.adapter.query_raw(&format!("SELECT 1 FROM {table}{clause}typeof({column}) = 'blob' LIMIT 1"), &w.args).await?;
1013
+ if !binary.is_empty() { continue; }
1014
+ }
1015
+ let mut bind: Vec<Value> = Vec::new();
1016
+ let sql = match strategy {
1017
+ STRATEGY_SQLITE_VEC | STRATEGY_UDF => {
1018
+ let Some(function) = distance_function(strategy, metric) else {
1019
+ continue;
1020
+ };
1021
+ bind.push(blob_value(&encode_vector(&args.vector)));
1022
+ let guard = match strategy {
1023
+ // sqlite-vec only understands float32 BLOB operands, so rows
1024
+ // stored any other way are excluded rather than aborting the
1025
+ // query.
1026
+ STRATEGY_SQLITE_VEC => format!("{column} IS NOT NULL"),
1027
+ _ => String::new(),
1028
+ };
1029
+ let distance = if strategy == STRATEGY_SQLITE_VEC {
1030
+ format!("CASE WHEN vec_length(vec_f32({column})) = {} THEN {function}(vec_f32({column}), ?) END", args.vector.len())
1031
+ } else { format!("{function}({column}, ?)") };
1032
+ // The guard and the caller's WHERE are one clause: appending the
1033
+ // tail after it would produce a second WHERE.
1034
+ let clause = match (guard.is_empty(), tail.is_empty()) {
1035
+ (true, _) => tail.clone(),
1036
+ (false, true) => format!(" WHERE {guard}"),
1037
+ (false, false) => {
1038
+ format!(" WHERE {guard} AND{}", tail.replacen(" WHERE ", " ", 1))
1039
+ }
1040
+ };
1041
+ format!("SELECT *, {distance} AS distance FROM {table}{clause}")
1042
+ }
1043
+ _ => {
1044
+ // json_each reads the query vector as JSON text.
1045
+ bind.push(Value::from(
1046
+ serde_json::to_string(&args.vector).unwrap_or_else(|_| "[]".to_string()),
1047
+ ));
1048
+ format!(
1049
+ "WITH q AS (SELECT je.key AS k, CAST(je.value AS REAL) AS v FROM json_each(?) je) SELECT *, {} AS distance FROM {table}{tail}",
1050
+ json_distance_expr(metric, &column)
1051
+ )
1052
+ }
1053
+ };
1054
+
1055
+ // A row whose stored vector cannot be scored yields a NULL distance;
1056
+ // those rows are dropped rather than handed to the caller.
1057
+ let sql = format!(
1058
+ "SELECT * FROM ({sql}) AS an5_ranked WHERE distance IS NOT NULL ORDER BY distance ASC LIMIT {take}"
1059
+ );
1060
+ bind.extend(w.args.clone());
1061
+ if let Ok(rows) = self.adapter.query_raw(&sql, &bind).await {
1062
+ return Ok(Some(self.decode_vector_rows(rows)));
1063
+ }
1064
+ // The connection advertised the capability but the query failed, e.g.
1065
+ // an extension that did not really load. The next strategy is cheaper
1066
+ // than reporting the failure.
1067
+ }
1068
+ Ok(None)
1069
+ }
1070
+
1071
+ /// Ranks the vector column in Rust.
1072
+ ///
1073
+ /// The last resort for every provider, and the only option on a SQLite
1074
+ /// connection that offers no in-database strategy. The whole table is loaded,
1075
+ /// so it is only reasonable for a table small enough to fit in memory.
1076
+ async fn score_vectors_in_memory(
1077
+ &self,
1078
+ args: &VectorSearchArgs,
1079
+ field: &str,
1080
+ metric: &str,
1081
+ take: i64,
1082
+ ) -> Result<Vec<RowMap>> {
1083
+ let rows = self
1084
+ .find_many(&FindManyArgs {
1085
+ r#where: args.r#where.clone(),
1086
+ ..Default::default()
1087
+ })
1088
+ .await?;
1089
+
1090
+ let mut scored: Vec<(RowMap, f64)> = Vec::new();
1091
+ for mut row in rows {
1092
+ let Some(row_vec) = row.get(field).and_then(decode_vector) else {
1093
+ continue;
1094
+ };
1095
+ if row_vec.len() != args.vector.len() {
1096
+ continue;
1097
+ }
1098
+ let distance = vector_distance(&args.vector, &row_vec, metric);
1099
+ row.insert("distance".to_string(), Value::from(distance));
1100
+ scored.push((row, distance));
1101
+ }
1102
+ scored.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
1103
+ scored.truncate(take.max(0) as usize);
1104
+ Ok(scored.into_iter().map(|(row, _)| row).collect())
1105
+ }
1106
+
817
1107
  /// Vector similarity search.
818
1108
  ///
819
1109
  /// Tries the database's native distance function first and falls back to
@@ -859,6 +1149,20 @@ impl TableClient {
859
1149
  Dialect::Sqlite => None,
860
1150
  };
861
1151
 
1152
+ // SQLite has no vector operator, so the ranking is built from the
1153
+ // strategies this connection supports: sqlite-vec, a registered distance
1154
+ // function, or json_each in plain SQL. `sqlx` reaches none of the first two
1155
+ // through an `AnyPool`, so json_each is normally the one that runs.
1156
+ if self.adapter.dialect == Dialect::Sqlite {
1157
+ if let Some(rows) = self
1158
+ .sqlite_vector_search(args, field, metric, take, &w)
1159
+ .await?
1160
+ {
1161
+ return Ok(rows);
1162
+ }
1163
+ return self.score_vectors_in_memory(args, field, metric, take).await;
1164
+ }
1165
+
862
1166
  if let Some(sql) = native_sql {
863
1167
  let mut bind: Vec<Value> = Vec::new();
864
1168
  if !w.is_empty() {
@@ -888,23 +1192,13 @@ impl TableClient {
888
1192
 
889
1193
  let mut scored: Vec<(RowMap, f64)> = Vec::new();
890
1194
  for mut row in rows {
891
- let raw = row.get(field).cloned();
892
- let row_vec = match raw {
893
- Some(Value::String(s)) => parse_vector(&s),
894
- Some(Value::Array(items)) => Some(
895
- items
896
- .iter()
897
- .filter_map(|v| v.as_f64())
898
- .collect::<Vec<f64>>(),
899
- ),
900
- _ => None,
1195
+ let Some(rv) = row.get(field).and_then(decode_vector) else {
1196
+ continue;
901
1197
  };
902
- if let Some(rv) = row_vec {
903
- if rv.len() == args.vector.len() {
904
- let d = vector_distance(&args.vector, &rv, metric);
905
- row.insert("distance".to_string(), Value::from(d));
906
- scored.push((row, d));
907
- }
1198
+ if rv.len() == args.vector.len() {
1199
+ let d = vector_distance(&args.vector, &rv, metric);
1200
+ row.insert("distance".to_string(), Value::from(d));
1201
+ scored.push((row, d));
908
1202
  }
909
1203
  }
910
1204
  scored.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
@@ -1185,6 +1479,30 @@ fn build_set_clause(data: &Value, ph: &dyn Fn(usize) -> String) -> Result<(Strin
1185
1479
  }
1186
1480
 
1187
1481
  /// Parses a JSON array string into a vector.
1482
+ /// The distance expression for the `json_each` strategy.
1483
+ ///
1484
+ /// `json_valid` fails on a float32 BLOB, so the column is guarded before it is
1485
+ /// walked, and a row whose stored vector has another dimension yields NULL.
1486
+ fn json_distance_expr(metric: &str, column: &str) -> String {
1487
+ let row = format!(
1488
+ "CASE WHEN json_valid({column}) THEN {column} ELSE '[]' END"
1489
+ );
1490
+ let same_length =
1491
+ format!("(SELECT COUNT(*) FROM json_each({row})) = (SELECT COUNT(*) FROM q)");
1492
+ let terms = match normalize_metric(metric) {
1493
+ "euclidean" => format!(
1494
+ "sqrt((SELECT SUM((je.value - q.v) * (je.value - q.v)) FROM json_each({row}) je JOIN q ON q.k = je.key))"
1495
+ ),
1496
+ "dot" => format!(
1497
+ "-(SELECT SUM(je.value * q.v) FROM json_each({row}) je JOIN q ON q.k = je.key)"
1498
+ ),
1499
+ _ => format!(
1500
+ "1.0 - (SELECT SUM(je.value * q.v) FROM json_each({row}) je JOIN q ON q.k = je.key) / NULLIF(sqrt((SELECT SUM(q.v * q.v) FROM q)) * sqrt((SELECT SUM(je.value * je.value) FROM json_each({row}) je)), 0)"
1501
+ ),
1502
+ };
1503
+ format!("CASE WHEN {same_length} THEN {terms} END")
1504
+ }
1505
+
1188
1506
  fn parse_vector(raw: &str) -> Option<Vec<f64>> {
1189
1507
  let trimmed = raw.trim().trim_start_matches('[').trim_end_matches(']');
1190
1508
  if trimmed.trim().is_empty() {
@@ -5,6 +5,7 @@
5
5
 
6
6
  pub mod dialect;
7
7
  pub mod metadata;
8
+ pub mod vectors;
8
9
  pub mod where_builder;
9
10
 
10
11
  pub use dialect::{
@@ -14,4 +15,10 @@ pub use metadata::{
14
15
  add_table_override, get_fields_for_model, get_model_to_table, get_relations_for_model,
15
16
  resolve_table, set_adapter_metadata, AdapterMetadata, RelationDef,
16
17
  };
18
+ pub use vectors::{
19
+ decode_vector, decode_vector_bytes, distance_function, encode_vector, is_vector_field,
20
+ needs_vector_encoding, normalize_metric, parse_vector, plan_vector_strategies,
21
+ AN5_VECTOR_FUNCTIONS, SQLITE_VEC_FUNCTIONS, STRATEGY_MEMORY, STRATEGY_SQL, STRATEGY_SQLITE_VEC,
22
+ STRATEGY_UDF,
23
+ };
17
24
  pub use where_builder::{build_order_by, build_where, SqlArg, Where};