@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
@@ -0,0 +1,243 @@
1
+ import Foundation
2
+
3
+ /// SQLite vector search: the codec, the strategy plan and the ranking SQL.
4
+ ///
5
+ /// SQLite has no vector type, so a `VECTOR(n)` column stores a BLOB of little-endian
6
+ /// float32 values (see ``SqliteVectors/encode(_:)``) and is ranked in one of four ways, in
7
+ /// this order:
8
+ ///
9
+ /// 1. `sqlite-vec` — native scalar distances, when the extension loads.
10
+ /// 2. `udf` — `an5_vec_cosine` / `an5_vec_l2` / `an5_vec_ip` registered with the driver.
11
+ /// Reads the BLOB and the legacy JSON text.
12
+ /// 3. `sql` — `json_each` brute force in plain SQL. Needs no user function, but only
13
+ /// reaches rows stored as JSON text.
14
+ /// 4. `memory` — the column is loaded and scored in Swift.
15
+ ///
16
+ /// Strategies 1-3 rank inside the database and transfer only the matching rows, which is why
17
+ /// they are preferred over the in-memory path. The bundled `SQLiteDriver` links the system
18
+ /// SQLite and can register the distance functions itself, so it reaches strategies 1-3; a
19
+ /// custom `SQLDriver` normally reaches 3 and 4.
20
+ ///
21
+ /// The docstring in the TypeScript `sqlite/vector.ts` is the shared specification every
22
+ /// runtime implements.
23
+ public enum SqliteVectors {
24
+
25
+ /// The size of one stored value; a column's byte length is its dimension times this.
26
+ public static let bytesPerFloat = 4
27
+
28
+ public static let strategySqliteVec = "sqlite-vec"
29
+ public static let strategyUdf = "udf"
30
+ public static let strategySQL = "sql"
31
+ public static let strategyMemory = "memory"
32
+
33
+ /// The scalar functions the adapter registers itself.
34
+ public static let an5Functions: [String: String] = [
35
+ "cosine": "an5_vec_cosine",
36
+ "euclidean": "an5_vec_l2",
37
+ "dot": "an5_vec_ip",
38
+ ]
39
+
40
+ /// The scalar functions the sqlite-vec extension provides.
41
+ public static let sqliteVecFunctions: [String: String] = [
42
+ "cosine": "vec_distance_cosine",
43
+ "euclidean": "vec_distance_l2",
44
+ "dot": "vec_distance_ip",
45
+ ]
46
+
47
+ // ─── Codec ─────────────────────────────────────────────────────────────────────
48
+
49
+ /// Encodes a vector as the little-endian float32 BLOB the column stores.
50
+ public static func encode(_ vector: [Double]) -> Data {
51
+ var data = Data(capacity: vector.count * bytesPerFloat)
52
+ for value in vector {
53
+ withUnsafeBytes(of: Float(value).bitPattern.littleEndian) { data.append(contentsOf: $0) }
54
+ }
55
+ return data
56
+ }
57
+
58
+ /// True when the value still has to become bytes on its way into a column.
59
+ public static func needsEncoding(_ value: Any?) -> Bool {
60
+ value is [Double] || value is [Float] || value is [NSNumber]
61
+ }
62
+
63
+ /// Decodes a stored vector: a float32 BLOB, a legacy JSON text column, or an array the
64
+ /// driver already decoded.
65
+ ///
66
+ /// - Parameter expectedLength: the query vector's length, or `0` to accept any length.
67
+ public static func decode(_ value: Any?, expectedLength: Int = 0) -> [Double]? {
68
+ if let data = value as? Data {
69
+ return decodeBlob(data, expectedLength: expectedLength)
70
+ }
71
+ if let bytes = value as? [UInt8] {
72
+ return decodeBlob(Data(bytes), expectedLength: expectedLength)
73
+ }
74
+ return Vectors.parse(value, expectedLength: expectedLength)
75
+ }
76
+
77
+ /// Decodes the float32 BLOB a `VECTOR(n)` column stores.
78
+ ///
79
+ /// JSON text is not a multiple of four bytes once encoded and starts with `[`, so the two
80
+ /// are told apart by length and first byte.
81
+ public static func decodeBlob(_ data: Data, expectedLength: Int = 0) -> [Double]? {
82
+ guard !data.isEmpty else { return nil }
83
+ let bytes = [UInt8](data)
84
+ if bytes[0] == UInt8(ascii: "[") || bytes.count % bytesPerFloat != 0 {
85
+ return Vectors.parse(String(decoding: data, as: UTF8.self), expectedLength: expectedLength)
86
+ }
87
+ var values: [Double] = []
88
+ values.reserveCapacity(bytes.count / bytesPerFloat)
89
+ for offset in stride(from: 0, to: bytes.count, by: bytesPerFloat) {
90
+ let raw = UInt32(bytes[offset]) | (UInt32(bytes[offset + 1]) << 8)
91
+ | (UInt32(bytes[offset + 2]) << 16) | (UInt32(bytes[offset + 3]) << 24)
92
+ values.append(Double(Float(bitPattern: raw)))
93
+ }
94
+ guard expectedLength == 0 || values.count == expectedLength else { return nil }
95
+ return values
96
+ }
97
+
98
+ /// True when generated metadata describes a `VECTOR(n)` column.
99
+ public static func isVectorField(_ field: FieldMeta) -> Bool {
100
+ isVectorType(field.sqlType) || isVectorType(field.type)
101
+ }
102
+
103
+ /// True when a declared SQL type names a vector column.
104
+ public static func isVectorType(_ sql: String?) -> Bool {
105
+ guard let sql, !sql.isEmpty else { return false }
106
+ let trimmed = sql.trimmingCharacters(in: .whitespacesAndNewlines)
107
+ if trimmed.contains("[]") { return true }
108
+ let upper = trimmed.uppercased()
109
+ guard upper.hasPrefix("VECTOR") else { return false }
110
+ let rest = String(upper.dropFirst("VECTOR".count))
111
+ return rest.isEmpty || rest.hasPrefix("(") || rest.hasPrefix(" ")
112
+ }
113
+
114
+ // ─── Strategy plan ─────────────────────────────────────────────────────────────
115
+
116
+ /// What a connection can do, probed once and cached by the driver.
117
+ public struct Capabilities: Sendable {
118
+ /// `vec_version()` answered, so the sqlite-vec extension is loaded.
119
+ public var vec: Bool
120
+ /// The adapter's own distance functions are registered.
121
+ public var udf: Bool
122
+ /// `json_each()` is available, which the pure-SQL strategy needs.
123
+ public var json1: Bool
124
+
125
+ public init(vec: Bool = false, udf: Bool = false, json1: Bool = false) {
126
+ self.vec = vec
127
+ self.udf = udf
128
+ self.json1 = json1
129
+ }
130
+ }
131
+
132
+ /// The ordered strategies to try.
133
+ ///
134
+ /// `declaredType` is the column's DDL type: the JSON strategy only reaches rows stored
135
+ /// as text, so a `BLOB` column drops it from the order.
136
+ public static func plan(
137
+ capabilities: Capabilities,
138
+ declaredType: String?,
139
+ preference: String?
140
+ ) -> [String] {
141
+ if let preference, !preference.isEmpty, preference != "auto" {
142
+ // `memory` is the caller's own path, not a query, so it yields no plan.
143
+ return preference == strategyMemory ? [] : [preference]
144
+ }
145
+ var available: [String] = []
146
+ if capabilities.vec { available.append(strategySqliteVec) }
147
+ if capabilities.udf { available.append(strategyUdf) }
148
+ if capabilities.json1 { available.append(strategySQL) }
149
+ let declared = (declaredType ?? "").trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
150
+ let textLike = ["TEXT", "CHAR", "CLOB", "STRING", "JSON"].contains { declared.contains($0) }
151
+ if !declared.isEmpty && !textLike {
152
+ // A BLOB column has no JSON to read, so json_each could only produce NULLs.
153
+ available.removeAll { $0 == strategySQL }
154
+ }
155
+ return available
156
+ }
157
+
158
+ // ─── Query building ─────────────────────────────────────────────────────────────
159
+
160
+ /// `json_valid` fails on a float32 BLOB, so the JSON path needs this guard.
161
+ private static func jsonText(_ column: String) -> String {
162
+ "CASE WHEN json_valid(\(column)) THEN \(column) ELSE '[]' END"
163
+ }
164
+
165
+ private static func jsonDistanceExpr(metric: DistanceMetric, _ column: String) -> String {
166
+ let row = jsonText(column)
167
+ let sameLength = "(SELECT COUNT(*) FROM json_each(\(row))) = (SELECT COUNT(*) FROM q)"
168
+ let terms: String
169
+ switch metric {
170
+ case .euclidean:
171
+ terms = "sqrt((SELECT SUM((je.value - q.v) * (je.value - q.v))"
172
+ + " FROM json_each(\(row)) je JOIN q ON q.k = je.key))"
173
+ case .dot:
174
+ terms = "-(SELECT SUM(je.value * q.v) FROM json_each(\(row)) je JOIN q ON q.k = je.key)"
175
+ case .cosine:
176
+ terms = "1.0 - (SELECT SUM(je.value * q.v) FROM json_each(\(row)) je JOIN q ON q.k = je.key)"
177
+ + " / NULLIF(sqrt((SELECT SUM(q.v * q.v) FROM q))"
178
+ + " * sqrt((SELECT SUM(je.value * je.value) FROM json_each(\(row)) je)), 0)"
179
+ }
180
+ return "CASE WHEN \(sameLength) THEN \(terms) END"
181
+ }
182
+
183
+ /// The ranking SQL for one strategy, with the values its placeholders need appended.
184
+ ///
185
+ /// `sqlite-vec` and `udf` bind the query vector as a float32 BLOB; `sql` binds it as
186
+ /// JSON text because `json_each` reads text.
187
+ ///
188
+ /// A row whose stored vector cannot be scored (a `NULL`, a text column, another
189
+ /// dimension) yields a `NULL` distance; those rows are dropped rather than reported, so
190
+ /// a caller never has to sort them out of the result.
191
+ public static func rankingQuery(
192
+ strategy: String,
193
+ metric: DistanceMetric,
194
+ table: String,
195
+ column: String,
196
+ vector: [Double],
197
+ take: Int,
198
+ tail: String,
199
+ placeholder: String,
200
+ bind: inout [SQLValue]
201
+ ) -> String? {
202
+ let functions = strategy == strategySqliteVec ? sqliteVecFunctions : an5Functions
203
+ var inner: String
204
+ if strategy == strategySqliteVec || strategy == strategyUdf {
205
+ guard let function = functions[metric.rawValue] else { return nil }
206
+ var distance = "\(function)(\(column), \(placeholder))"
207
+ var guardClause = ""
208
+ if strategy == strategySqliteVec {
209
+ // `vec_f32` also accepts legacy JSON text, so the guard is only a
210
+ // dimension check. It joins the caller's WHERE with AND, because
211
+ // appending the tail after it would produce a second WHERE.
212
+ guardClause = "\(column) IS NOT NULL"
213
+ distance = "CASE WHEN vec_length(vec_f32(\(column))) = \(vector.count)"
214
+ + " THEN \(function)(vec_f32(\(column)), \(placeholder)) END"
215
+ bind.append(.blob(encode(vector)))
216
+ } else {
217
+ bind.append(.blob(encode(vector)))
218
+ }
219
+ if guardClause.isEmpty {
220
+ inner = "SELECT *, \(distance) AS distance FROM \(table)\(tail)"
221
+ } else if tail.isEmpty {
222
+ inner = "SELECT *, \(distance) AS distance FROM \(table) WHERE \(guardClause)"
223
+ } else {
224
+ // `tail` starts with " WHERE "; drop that keyword and keep the condition,
225
+ // which then combines with the guard through a single WHERE.
226
+ let condition = tail.trimmingCharacters(in: .whitespacesAndNewlines)
227
+ let trimmed = condition.hasPrefix("WHERE")
228
+ ? String(condition.dropFirst("WHERE".count).drop(while: { $0 == " " }))
229
+ : condition
230
+ inner = "SELECT *, \(distance) AS distance FROM \(table)"
231
+ + " WHERE \(guardClause) AND \(trimmed)"
232
+ }
233
+ } else {
234
+ bind.append(.text(Vectors.format(vector)))
235
+ inner = "SELECT *, \(jsonDistanceExpr(metric: metric, column)) AS distance"
236
+ + " FROM \(table)\(tail)"
237
+ inner = "WITH q AS (SELECT je.key AS k, CAST(je.value AS REAL) AS v"
238
+ + " FROM json_each(\(placeholder)) je) " + inner
239
+ }
240
+ return "SELECT * FROM (\(inner)) AS an5_ranked"
241
+ + " WHERE distance IS NOT NULL ORDER BY distance ASC LIMIT \(take)"
242
+ }
243
+ }