@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,127 @@
1
+ import Foundation
2
+
3
+ /// One row, keyed by column name.
4
+ ///
5
+ /// A name-to-value dictionary rather than a positional array because a generated model
6
+ /// reads its fields by name, and because a missing column and a `NULL` column are
7
+ /// different things: the first is absent from the dictionary, the second is present with a
8
+ /// `nil` value.
9
+ public typealias Row = [String: Any?]
10
+
11
+ /// A filter tree: scalar values, operator maps, and nested `AND`/`OR`/`NOT` groups.
12
+ public typealias Where = [String: Any?]
13
+
14
+ /// A write payload: column values, plus any relation writes the metadata declares.
15
+ ///
16
+ /// Named `Values` rather than `Data` because `Data` is Foundation's byte buffer, and a
17
+ /// typealias that shadows it makes every `Data(bytes:count:)` in a driver fail to compile.
18
+ public typealias Values = [String: Any?]
19
+
20
+ /// An eager-load description: relation name to `true`, or to a nested description.
21
+ public typealias Include = [String: Any?]
22
+
23
+ /// A parameter value bound into a statement.
24
+ ///
25
+ /// Limited to what a SQL driver can bind. `Date`, `UUID`, `Data` and the numeric types all
26
+ /// have a defined encoding, which is what lets a generated model pass a `UUID` for an
27
+ /// `@id` column without converting it by hand.
28
+ public enum SQLValue {
29
+ case null
30
+ case integer(Int64)
31
+ case real(Double)
32
+ case text(String)
33
+ case blob(Data)
34
+ case bool(Bool)
35
+ case date(Date)
36
+ case uuid(UUID)
37
+
38
+ /// The value to hand back as an untyped Swift value.
39
+ ///
40
+ /// A `NULL` becomes `nil`, and everything else the natural Swift type, so an
41
+ /// already-encoded value can travel through the untyped `[Any?]` entry points without
42
+ /// being encoded a second time.
43
+ public var unwrapped: Any? {
44
+ switch self {
45
+ case .null: return nil
46
+ case .bool(let value): return value
47
+ case .integer(let value): return value
48
+ case .real(let value): return value
49
+ case .text(let value): return value
50
+ case .blob(let value): return value
51
+ case .date(let value): return value
52
+ case .uuid(let value): return value
53
+ }
54
+ }
55
+
56
+ /// Converts a Swift value into something bindable, or `nil` when there is no encoding.
57
+ ///
58
+ /// `Optional` is unwrapped rather than rejected so a generated model can pass a nullable
59
+ /// column straight through; `NSNull` is accepted as an explicit null because that is
60
+ /// what a JSON decoder produces for a missing value.
61
+ public static func encode(_ value: Any?) -> SQLValue? {
62
+ switch value {
63
+ case nil, is NSNull:
64
+ return .null
65
+ case let already as SQLValue:
66
+ return already
67
+ case let bool as Bool:
68
+ return .bool(bool)
69
+ case let number as Int:
70
+ return .integer(Int64(number))
71
+ case let number as Int64:
72
+ return .integer(number)
73
+ case let number as Double:
74
+ return .real(number)
75
+ case let number as Float:
76
+ return .real(Double(number))
77
+ case let decimal as Decimal:
78
+ return .real(NSDecimalNumber(decimal: decimal).doubleValue)
79
+ case let number as NSNumber:
80
+ // An NSNumber wraps either a floating-point or an integral value, and the only
81
+ // portable way to tell is whether it survives the round trip through int64 —
82
+ // CFNumberIsFloatType is not available on every platform Swift builds for.
83
+ let whole = number.doubleValue
84
+ return whole == Double(number.int64Value) ? .integer(number.int64Value) : .real(whole)
85
+ case let text as String:
86
+ return .text(text)
87
+ case let data as Data:
88
+ return .blob(data)
89
+ case let date as Date:
90
+ return .date(date)
91
+ case let uuid as UUID:
92
+ return .uuid(uuid)
93
+ default:
94
+ return nil
95
+ }
96
+ }
97
+ }
98
+
99
+ /// Everything the runtime can fail with.
100
+ ///
101
+ /// One error type with a reason, rather than a family of types: a caller catching an AN5
102
+ /// failure almost always wants to show the message, and a switch over cases that never
103
+ /// differ in handling is noise.
104
+ public enum An5Error: Error, CustomStringConvertible {
105
+ case unsupported(String)
106
+ case configuration(String)
107
+ case invalidQuery(String)
108
+ case driver(String)
109
+ case readOnly(String)
110
+
111
+ public var description: String {
112
+ switch self {
113
+ case .unsupported(let detail):
114
+ return "Unsupported: \(detail)"
115
+ case .configuration(let detail):
116
+ return "Configuration error: \(detail)"
117
+ case .invalidQuery(let detail):
118
+ return "Invalid query: \(detail)"
119
+ case .driver(let detail):
120
+ return "Database error: \(detail)"
121
+ case .readOnly(let detail):
122
+ return "\(detail) is read-only"
123
+ }
124
+ }
125
+
126
+ public var localizedDescription: String { description }
127
+ }
@@ -0,0 +1,86 @@
1
+ import Foundation
2
+
3
+ /// The distance metrics a vector search understands, and the math behind them.
4
+ ///
5
+ /// Each metric is turned into a distance before sorting, so ascending order always means
6
+ /// "closer": cosine similarity and dot product would otherwise have to be sorted descending,
7
+ /// which is the kind of detail no caller should have to remember.
8
+ public enum DistanceMetric: String, Sendable {
9
+ case cosine
10
+ case euclidean
11
+ case dot
12
+
13
+ public static func distance(_ metric: DistanceMetric, _ left: [Double], _ right: [Double]) -> Double {
14
+ switch metric {
15
+ case .cosine: return 1 - Vectors.cosineSimilarity(left, right)
16
+ case .euclidean: return Vectors.euclideanDistance(left, right)
17
+ case .dot: return -Vectors.dotProduct(left, right)
18
+ }
19
+ }
20
+ }
21
+
22
+ public enum Vectors {
23
+
24
+ /// Cosine similarity in `-1...1`; `0` when the lengths differ or either side is empty.
25
+ public static func cosineSimilarity(_ left: [Double], _ right: [Double]) -> Double {
26
+ guard left.count == right.count, !left.isEmpty else { return 0 }
27
+ var dot = 0.0
28
+ var leftMagnitude = 0.0
29
+ var rightMagnitude = 0.0
30
+ for index in left.indices {
31
+ dot += left[index] * right[index]
32
+ leftMagnitude += left[index] * left[index]
33
+ rightMagnitude += right[index] * right[index]
34
+ }
35
+ guard leftMagnitude != 0, rightMagnitude != 0 else { return 0 }
36
+ return dot / (leftMagnitude.squareRoot() * rightMagnitude.squareRoot())
37
+ }
38
+
39
+ /// L2 distance; `0` when the lengths differ or either side is empty.
40
+ public static func euclideanDistance(_ left: [Double], _ right: [Double]) -> Double {
41
+ guard left.count == right.count, !left.isEmpty else { return 0 }
42
+ var sum = 0.0
43
+ for index in left.indices {
44
+ let difference = left[index] - right[index]
45
+ sum += difference * difference
46
+ }
47
+ return sum.squareRoot()
48
+ }
49
+
50
+ /// Inner product; `0` when the lengths differ.
51
+ public static func dotProduct(_ left: [Double], _ right: [Double]) -> Double {
52
+ guard left.count == right.count else { return 0 }
53
+ return zip(left, right).reduce(0) { $0 + $1.0 * $1.1 }
54
+ }
55
+
56
+ /// Reads a stored vector back out of a cell.
57
+ ///
58
+ /// All three vector stores AN5 targets hand the column back as text — SQL Server's
59
+ /// `VECTOR`, PostgreSQL's `vector`, and SQLite's JSON blob — so the in-memory fallback
60
+ /// parses the same `[0.1, 0.2]` form. A row whose length does not match the query vector
61
+ /// yields `nil` rather than a silently wrong score.
62
+ ///
63
+ /// - Parameter expectedLength: the query vector's length, or `0` to accept any length.
64
+ public static func parse(_ value: Any?, expectedLength: Int) -> [Double]? {
65
+ guard let value, !(value is NSNull) else { return nil }
66
+ if let numbers = value as? [Double] {
67
+ return expectedLength > 0 && numbers.count != expectedLength ? nil : numbers
68
+ }
69
+ let text = "\(value)".trimmingCharacters(in: .whitespacesAndNewlines)
70
+ guard let open = text.firstIndex(of: "["), let close = text.lastIndex(of: "]"), close > open else {
71
+ return nil
72
+ }
73
+ let body = text[text.index(after: open)..<close]
74
+ .trimmingCharacters(in: .whitespacesAndNewlines)
75
+ if body.isEmpty { return nil }
76
+ let parsed = body.split(separator: ",").compactMap { Double($0.trimmingCharacters(in: .whitespaces)) }
77
+ guard parsed.count == body.split(separator: ",").count else { return nil }
78
+ guard !parsed.isEmpty, expectedLength == 0 || parsed.count == expectedLength else { return nil }
79
+ return parsed
80
+ }
81
+
82
+ /// Renders a vector the way the vector stores expect it, as `[0.1, 0.2]`.
83
+ public static func format(_ vector: [Double]) -> String {
84
+ "[" + vector.map { "\($0)" }.joined(separator: ", ") + "]"
85
+ }
86
+ }
@@ -0,0 +1,50 @@
1
+ import Foundation
2
+
3
+ /// A read-only client for a database view.
4
+ ///
5
+ /// Reads go straight to the underlying table client, which already knows how to select from
6
+ /// an arbitrary name. Every mutation is refused rather than attempted: a view has no rows to
7
+ /// write, and the databases disagree about whether the statement is even legal, so the error
8
+ /// is raised here where it can name the reason.
9
+ public struct ViewClient {
10
+
11
+ public let adapter: An5Adapter
12
+ public let view: String
13
+
14
+ private var client: TableClient { adapter.table(view) }
15
+
16
+ public init(adapter: An5Adapter, view: String) {
17
+ self.adapter = adapter
18
+ self.view = view
19
+ }
20
+
21
+ public func findMany() throws -> [Row] { try client.findMany() }
22
+ public func findMany(_ query: Query) throws -> [Row] { try client.findMany(query) }
23
+ public func findMany(filter: Where) throws -> [Row] { try client.findMany(filter: filter) }
24
+ public func findFirst(filter: Where) throws -> Row? { try client.findFirst(filter: filter) }
25
+ public func findUnique(filter: Where) throws -> Row? { try client.findUnique(filter: filter) }
26
+ public func count(_ filter: Where? = nil) throws -> Int { try client.count(filter) }
27
+ public func aggregate(_ aggregate: Aggregate) throws -> Row { try client.aggregate(aggregate) }
28
+ public func groupBy(_ aggregate: Aggregate) throws -> [Row] { try client.groupBy(aggregate) }
29
+
30
+ public func vectorSearch(
31
+ _ vector: [Double],
32
+ take: Int = 10,
33
+ filter: Where? = nil,
34
+ vectorField: String = "embedding",
35
+ metric: DistanceMetric = .cosine
36
+ ) throws -> [Row] {
37
+ try client.vectorSearch(vector, take: take, filter: filter, vectorField: vectorField, metric: metric)
38
+ }
39
+
40
+ public func create(_ data: Values) throws -> Row { throw readOnly("create") }
41
+ public func update(filter: Where, data: Values) throws -> Row? { throw readOnly("update") }
42
+ public func updateMany(filter: Where?, data: Values) throws -> Int { throw readOnly("updateMany") }
43
+ public func delete(filter: Where) throws -> Row? { throw readOnly("delete") }
44
+ public func deleteMany(filter: Where? = nil) throws -> Int { throw readOnly("deleteMany") }
45
+ public func upsert(filter: Where, create: Values, update: Values) throws -> Row { throw readOnly("upsert") }
46
+
47
+ private func readOnly(_ operation: String) -> An5Error {
48
+ .readOnly("view '\(view)' — \(operation) is not allowed on it")
49
+ }
50
+ }
@@ -0,0 +1,8 @@
1
+ module CSQLite [system] {
2
+ // The real header, not a hand-written copy of the declarations: a copied subset drifts
3
+ // from the library it claims to describe, and the compiler then accepts calls with the
4
+ // wrong signatures.
5
+ header "shim.h"
6
+ link "sqlite3"
7
+ export *
8
+ }
@@ -0,0 +1,9 @@
1
+ #ifndef AN5_CSQLITE_SHIM_H
2
+ #define AN5_CSQLITE_SHIM_H
3
+
4
+ /* The system SQLite's own header. A module map needs one header to anchor the module to;
5
+ re-declaring the handful of functions AN5 calls here would let the compiler accept a
6
+ signature the installed library does not have. */
7
+ #include <sqlite3.h>
8
+
9
+ #endif /* AN5_CSQLITE_SHIM_H */
@@ -0,0 +1,51 @@
1
+ import Foundation
2
+ import XCTest
3
+ @testable import An5Adapters
4
+
5
+ /// Runs the shared query contract in `test/fixtures/query-semantics.json` through the Swift
6
+ /// SQL builder and a real SQLite database.
7
+ ///
8
+ /// The same fixture gates the TypeScript, Python, Go and Java builders, which is the point: a
9
+ /// where tree that means one thing in one client and another in the next is a bug in the
10
+ /// client, and only a shared fixture catches it.
11
+ final class QuerySemanticsTests: XCTestCase {
12
+
13
+ private static let fixtureURL = URL(fileURLWithPath: #filePath)
14
+ .deletingLastPathComponent() // An5AdaptersTests
15
+ .deletingLastPathComponent() // Tests
16
+ .deletingLastPathComponent() // swift
17
+ .deletingLastPathComponent() // an5Adapters
18
+ .appendingPathComponent("test/fixtures/query-semantics.json")
19
+
20
+ func testSharedContract() throws {
21
+ let data = try Data(contentsOf: Self.fixtureURL)
22
+ let fixture = try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any])
23
+ let rows = try XCTUnwrap(fixture["rows"] as? [[String: Any]])
24
+ let cases = try XCTUnwrap(fixture["cases"] as? [[String: Any]])
25
+
26
+ let driver = try SQLiteDriver(path: ":memory:")
27
+ let adapter = An5Adapter(driver: driver, connectionString: "sqlite::memory:")
28
+ try adapter.execute(
29
+ "CREATE TABLE users (id INTEGER, score INTEGER)"
30
+ )
31
+ for row in rows {
32
+ try adapter.execute(
33
+ "INSERT INTO users VALUES (?, ?)",
34
+ [row["id"] as Any, row["score"] as Any]
35
+ )
36
+ }
37
+
38
+ for testCase in cases {
39
+ let name = testCase["name"] as? String ?? "<unnamed>"
40
+ let filter = testCase["where"] as? [String: Any?] ?? [:]
41
+ let expected = (testCase["ids"] as? [Int]) ?? []
42
+
43
+ let clause = SQLBuilder.whereClause(filter, dialect: .sqlite)
44
+ let sql = "SELECT id FROM users" + (clause.isEmpty ? "" : " WHERE \(clause.sql)") + " ORDER BY id"
45
+ let found = try adapter.query(sql, clause.parameters.map { $0 as Any? })
46
+ let actual = found.compactMap { ($0["id"] as? NSNumber)?.intValue }
47
+
48
+ XCTAssertEqual(actual, expected, name)
49
+ }
50
+ }
51
+ }