@an5/adapters 0.2.11 → 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 +133 -2
- package/dist/an5Adapter.d.ts +37 -0
- package/dist/an5Adapter.js +138 -35
- package/dist/base/types.d.ts +14 -0
- 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 +33 -7
- 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 +146 -29
- package/typescript/src/base/types.ts +15 -0
- 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/dotnet/An5Adapter.cs
CHANGED
|
@@ -19,21 +19,34 @@ namespace An5Orm
|
|
|
19
19
|
private readonly int _commandTimeout;
|
|
20
20
|
|
|
21
21
|
public An5Adapter(string connectionString, int commandTimeout = 60)
|
|
22
|
+
: this(connectionString, commandTimeout, null) { }
|
|
23
|
+
|
|
24
|
+
public An5Adapter(string connectionString, int commandTimeout, SqliteVectorSupport vectorSupport)
|
|
22
25
|
{
|
|
23
26
|
ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
|
24
27
|
_commandTimeout = commandTimeout;
|
|
25
28
|
_engine = DialectDetector.Detect(connectionString) switch
|
|
26
29
|
{
|
|
27
30
|
Dialect.Postgres => new PostgresEngine(connectionString, commandTimeout),
|
|
28
|
-
Dialect.Sqlite => new SqliteEngine(connectionString, commandTimeout),
|
|
31
|
+
Dialect.Sqlite => new SqliteEngine(connectionString, commandTimeout, vectorSupport),
|
|
29
32
|
_ => new MssqlEngine(connectionString, commandTimeout)
|
|
30
33
|
};
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
public An5Adapter(An5AdapterOptions options)
|
|
36
|
+
public An5Adapter(An5AdapterOptions options)
|
|
37
|
+
: this(options.ConnectionString, options.CommandTimeout,
|
|
38
|
+
new SqliteVectorSupport { SqliteVecPath = options.SqliteVec, Preference = options.VectorStrategy })
|
|
39
|
+
{ }
|
|
34
40
|
|
|
35
41
|
// ── Raw query execution ────────────────────────────────────────────────
|
|
36
42
|
|
|
43
|
+
/// <summary>
|
|
44
|
+
/// SQLite only: the vector hooks of the configured connection, or null on
|
|
45
|
+
/// the other providers. `VectorSearch` needs them to rank a `VECTOR(n)`
|
|
46
|
+
/// column inside the database.
|
|
47
|
+
/// </summary>
|
|
48
|
+
public SqliteVectorSupport VectorSupport => _engine.VectorSupport;
|
|
49
|
+
|
|
37
50
|
public List<Dictionary<string, object>> QueryRaw(string sql, Dictionary<string, object> parameters = null)
|
|
38
51
|
=> _engine.QueryRaw(sql, parameters);
|
|
39
52
|
|
|
@@ -1006,10 +1019,29 @@ namespace An5Orm
|
|
|
1006
1019
|
string whereClause = null, Dictionary<string, object> parameters = null,
|
|
1007
1020
|
string vectorField = "Embedding", string distanceMetric = "cosine")
|
|
1008
1021
|
{
|
|
1009
|
-
// 1.
|
|
1010
|
-
//
|
|
1011
|
-
//
|
|
1012
|
-
|
|
1022
|
+
// 1. SQLite ranks through sqlite-vec, a driver function or json_each,
|
|
1023
|
+
// whichever this connection supports, so the table never has to
|
|
1024
|
+
// leave the database.
|
|
1025
|
+
if (_dialect == Dialect.Sqlite)
|
|
1026
|
+
{
|
|
1027
|
+
var p = parameters != null ? new Dictionary<string, object>(parameters) : new Dictionary<string, object>();
|
|
1028
|
+
var quotedVectorField = SqlQuote.QuoteName(vectorField, _dialect);
|
|
1029
|
+
var ranked = SqliteVectors.RunSearch(
|
|
1030
|
+
_adapter.QueryRaw,
|
|
1031
|
+
_tableName,
|
|
1032
|
+
_rawTable.Trim('[', ']'),
|
|
1033
|
+
quotedVectorField,
|
|
1034
|
+
vectorField,
|
|
1035
|
+
vector,
|
|
1036
|
+
distanceMetric,
|
|
1037
|
+
take,
|
|
1038
|
+
string.IsNullOrWhiteSpace(whereClause) ? "" : $" WHERE {whereClause}",
|
|
1039
|
+
p,
|
|
1040
|
+
_adapter.VectorSupport);
|
|
1041
|
+
if (ranked != null) return ToRanked(ranked, take);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// 2. Primary path: Native database SQL vector query execution (MSSQL VECTOR_DISTANCE / Postgres pgvector)
|
|
1013
1045
|
if (_dialect != Dialect.Sqlite)
|
|
1014
1046
|
try
|
|
1015
1047
|
{
|
|
@@ -1063,7 +1095,7 @@ namespace An5Orm
|
|
|
1063
1095
|
// Secondary fallback: In-memory similarity computation if DB instance lacks native vector extension
|
|
1064
1096
|
}
|
|
1065
1097
|
|
|
1066
|
-
//
|
|
1098
|
+
// 3. Secondary fallback: In-memory similarity computation
|
|
1067
1099
|
var rows = FindMany(whereClause, parameters);
|
|
1068
1100
|
var results = new List<(T, double)>();
|
|
1069
1101
|
var vectorProp = typeof(T).GetProperty(vectorField, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
|
@@ -1071,33 +1103,59 @@ namespace An5Orm
|
|
|
1071
1103
|
|
|
1072
1104
|
foreach (var row in rows)
|
|
1073
1105
|
{
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
try
|
|
1078
|
-
{
|
|
1079
|
-
if (rawVal is string s) vec = JsonSerializer.Deserialize<List<double>>(s);
|
|
1080
|
-
}
|
|
1081
|
-
catch { continue; }
|
|
1106
|
+
// A float32 BLOB, a legacy JSON text column or an already-decoded
|
|
1107
|
+
// array all decode the same way.
|
|
1108
|
+
var vec = SqliteVectors.DecodeVector(vectorProp.GetValue(row));
|
|
1082
1109
|
if (vec == null || vec.Count != vector.Count) continue;
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
{
|
|
1087
|
-
dot += vector[i] * vec[i];
|
|
1088
|
-
m1 += vector[i] * vector[i];
|
|
1089
|
-
m2 += vec[i] * vec[i];
|
|
1090
|
-
}
|
|
1091
|
-
double cosine = (m1 > 0 && m2 > 0) ? dot / (Math.Sqrt(m1) * Math.Sqrt(m2)) : 0;
|
|
1092
|
-
double dist = distanceMetric.Equals("cosine", StringComparison.OrdinalIgnoreCase) ? 1.0 - cosine :
|
|
1093
|
-
distanceMetric.Equals("dot", StringComparison.OrdinalIgnoreCase) ? -dot : EuclideanDistance(vector, vec);
|
|
1094
|
-
results.Add((row, dist));
|
|
1110
|
+
var dist = SqliteVectors.VectorDistance(vector, vec, distanceMetric);
|
|
1111
|
+
if (!dist.HasValue) continue;
|
|
1112
|
+
results.Add((row, dist.Value));
|
|
1095
1113
|
}
|
|
1096
1114
|
|
|
1097
1115
|
results.Sort((a, b) => a.Item2.CompareTo(b.Item2));
|
|
1098
1116
|
return results.GetRange(0, Math.Min(take, results.Count));
|
|
1099
1117
|
}
|
|
1100
1118
|
|
|
1119
|
+
/// <summary>
|
|
1120
|
+
/// Projects the ranked rows the in-database strategies return into the
|
|
1121
|
+
/// caller's entity type, taking `distance` from the added column.
|
|
1122
|
+
/// </summary>
|
|
1123
|
+
private List<(T, double)> ToRanked(List<Dictionary<string, object>> rows, int take)
|
|
1124
|
+
{
|
|
1125
|
+
var results = new List<(T, double)>();
|
|
1126
|
+
foreach (var row in rows)
|
|
1127
|
+
{
|
|
1128
|
+
var item = Activator.CreateInstance<T>();
|
|
1129
|
+
foreach (var prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
|
1130
|
+
{
|
|
1131
|
+
if (!row.TryGetValue(prop.Name, out var value) || value == null) continue;
|
|
1132
|
+
if (prop.Name.Equals("Distance", StringComparison.OrdinalIgnoreCase))
|
|
1133
|
+
{
|
|
1134
|
+
if (value is double d) prop.SetValue(item, d);
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
// A vector column arrives as float32 bytes; the entity declares
|
|
1138
|
+
// it as float[].
|
|
1139
|
+
if (prop.PropertyType == typeof(float[]))
|
|
1140
|
+
{
|
|
1141
|
+
var vec = SqliteVectors.DecodeVector(value);
|
|
1142
|
+
if (vec != null)
|
|
1143
|
+
{
|
|
1144
|
+
prop.SetValue(item, vec.Select(v => (float)v).ToArray());
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
try { prop.SetValue(item, Convert.ChangeType(value, prop.PropertyType)); } catch { }
|
|
1149
|
+
}
|
|
1150
|
+
double distance = row.TryGetValue("distance", out var raw) && raw != null
|
|
1151
|
+
? Convert.ToDouble(raw)
|
|
1152
|
+
: 0;
|
|
1153
|
+
results.Add((item, distance));
|
|
1154
|
+
if (results.Count >= take) break;
|
|
1155
|
+
}
|
|
1156
|
+
return results;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1101
1159
|
private static double EuclideanDistance(List<double> left, List<double> right)
|
|
1102
1160
|
{
|
|
1103
1161
|
double sum = 0;
|
package/dotnet/Base/Core.cs
CHANGED
|
@@ -18,6 +18,17 @@ public enum Dialect
|
|
|
18
18
|
public string ConnectionString { get; set; }
|
|
19
19
|
public int CommandTimeout { get; set; } = 60;
|
|
20
20
|
public int ConnectRetryCount { get; set; } = 3;
|
|
21
|
+
/// <summary>
|
|
22
|
+
/// SQLite only: path to the sqlite-vec extension binary. Loaded on every
|
|
23
|
+
/// connection so vector search ranks inside the database. Other
|
|
24
|
+
/// providers ignore it.
|
|
25
|
+
/// </summary>
|
|
26
|
+
public string SqliteVec { get; set; }
|
|
27
|
+
/// <summary>
|
|
28
|
+
/// SQLite only: pin one of "sqlite-vec" | "udf" | "sql" | "memory"
|
|
29
|
+
/// instead of probing for the fastest strategy available.
|
|
30
|
+
/// </summary>
|
|
31
|
+
public string VectorStrategy { get; set; }
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
// ─── Query Engine Interface ────────────────────────────────────────────────
|
|
@@ -29,6 +40,9 @@ public enum Dialect
|
|
|
29
40
|
List<T> QueryRaw<T>(string sql, Dictionary<string, object> parameters) where T : new();
|
|
30
41
|
int ExecuteRaw(string sql, Dictionary<string, object> parameters);
|
|
31
42
|
An5TransactionBase BeginTransaction();
|
|
43
|
+
|
|
44
|
+
/// <summary>SQLite only: extension loading and distance-function registration.</summary>
|
|
45
|
+
SqliteVectorSupport VectorSupport { get; }
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
// ─── Transaction base ──────────────────────────────────────────────────────
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
using System;
|
|
2
2
|
using System.Collections.Generic;
|
|
3
3
|
using System.Data;
|
|
4
|
+
using System.Globalization;
|
|
4
5
|
using System.Reflection;
|
|
5
6
|
using Microsoft.Data.SqlClient;
|
|
6
7
|
|
|
@@ -11,6 +12,8 @@ namespace An5Orm
|
|
|
11
12
|
internal class MssqlEngine : IQueryEngine
|
|
12
13
|
{
|
|
13
14
|
public Dialect Dialect => Dialect.Mssql;
|
|
15
|
+
// Only SQLite has a vector hook; this provider has none to offer.
|
|
16
|
+
public SqliteVectorSupport VectorSupport => null;
|
|
14
17
|
private readonly string _connectionString;
|
|
15
18
|
private readonly int _commandTimeout;
|
|
16
19
|
|
|
@@ -32,6 +35,33 @@ namespace An5Orm
|
|
|
32
35
|
return conn;
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
/// <summary>
|
|
39
|
+
/// Converts a value into something SqlClient can bind.
|
|
40
|
+
/// </summary>
|
|
41
|
+
/// <remarks>
|
|
42
|
+
/// A <c>VECTOR</c> column is transmitted as a JSON array over TDS, so a float or
|
|
43
|
+
/// double array is written in that form. Binding the array itself is not an option:
|
|
44
|
+
/// SqlClient has no mapping for it, and the failure names the array rather than the
|
|
45
|
+
/// column. Driver versions before 6.1 have no <c>SqlVector</c> either, so the JSON
|
|
46
|
+
/// form is what every version accepts.
|
|
47
|
+
/// </remarks>
|
|
48
|
+
private static object BindableValue(object value)
|
|
49
|
+
{
|
|
50
|
+
if (value is float[] floats)
|
|
51
|
+
{
|
|
52
|
+
var parts = new string[floats.Length];
|
|
53
|
+
for (int i = 0; i < floats.Length; i++) parts[i] = floats[i].ToString("R", CultureInfo.InvariantCulture);
|
|
54
|
+
return "[" + string.Join(", ", parts) + "]";
|
|
55
|
+
}
|
|
56
|
+
if (value is double[] doubles)
|
|
57
|
+
{
|
|
58
|
+
var parts = new string[doubles.Length];
|
|
59
|
+
for (int i = 0; i < doubles.Length; i++) parts[i] = doubles[i].ToString("R", CultureInfo.InvariantCulture);
|
|
60
|
+
return "[" + string.Join(", ", parts) + "]";
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
|
|
35
65
|
private SqlCommand BuildCommand(
|
|
36
66
|
SqlConnection conn, string sql, Dictionary<string, object> parameters)
|
|
37
67
|
{
|
|
@@ -42,7 +72,7 @@ namespace An5Orm
|
|
|
42
72
|
foreach (var kv in parameters)
|
|
43
73
|
{
|
|
44
74
|
var paramName = kv.Key.StartsWith("@") ? kv.Key : "@" + kv.Key;
|
|
45
|
-
cmd.Parameters.AddWithValue(paramName, kv.Value ?? DBNull.Value);
|
|
75
|
+
cmd.Parameters.AddWithValue(paramName, BindableValue(kv.Value) ?? DBNull.Value);
|
|
46
76
|
}
|
|
47
77
|
}
|
|
48
78
|
return cmd;
|
|
@@ -87,6 +117,19 @@ namespace An5Orm
|
|
|
87
117
|
if (!HasColumn(reader, prop.Name)) continue;
|
|
88
118
|
var val = reader[prop.Name];
|
|
89
119
|
if (val == DBNull.Value) continue;
|
|
120
|
+
// A `VECTOR` column arrives as a JSON array, which no converter
|
|
121
|
+
// bridges to the `float[]` the entity declares.
|
|
122
|
+
if (prop.PropertyType == typeof(float[]))
|
|
123
|
+
{
|
|
124
|
+
var vector = SqliteVectors.DecodeVector(val);
|
|
125
|
+
if (vector != null)
|
|
126
|
+
{
|
|
127
|
+
var floats = new float[vector.Count];
|
|
128
|
+
for (int i = 0; i < vector.Count; i++) floats[i] = (float)vector[i];
|
|
129
|
+
prop.SetValue(item, floats);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
90
133
|
try { prop.SetValue(item, Convert.ChangeType(val, prop.PropertyType)); } catch { }
|
|
91
134
|
}
|
|
92
135
|
results.Add(item);
|
|
@@ -10,6 +10,8 @@ namespace An5Orm
|
|
|
10
10
|
internal class PostgresEngine : IQueryEngine
|
|
11
11
|
{
|
|
12
12
|
public Dialect Dialect => Dialect.Postgres;
|
|
13
|
+
// Only SQLite has a vector hook; this provider has none to offer.
|
|
14
|
+
public SqliteVectorSupport VectorSupport => null;
|
|
13
15
|
private readonly string _connectionString;
|
|
14
16
|
private readonly int _commandTimeout;
|
|
15
17
|
|
|
@@ -18,13 +18,15 @@ namespace An5Orm
|
|
|
18
18
|
internal class SqliteEngine : IQueryEngine
|
|
19
19
|
{
|
|
20
20
|
public Dialect Dialect => Dialect.Sqlite;
|
|
21
|
+
public SqliteVectorSupport VectorSupport { get; }
|
|
21
22
|
private readonly string _connectionString;
|
|
22
23
|
private readonly int _commandTimeout;
|
|
23
24
|
|
|
24
|
-
public SqliteEngine(string connectionString, int commandTimeout)
|
|
25
|
+
public SqliteEngine(string connectionString, int commandTimeout, SqliteVectorSupport vectorSupport = null)
|
|
25
26
|
{
|
|
26
27
|
_connectionString = SqliteConnectionString.Normalize(connectionString);
|
|
27
28
|
_commandTimeout = commandTimeout;
|
|
29
|
+
VectorSupport = vectorSupport ?? new SqliteVectorSupport();
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
// Set while a transaction is open; statements then join it instead of
|
|
@@ -40,9 +42,71 @@ namespace An5Orm
|
|
|
40
42
|
using var pragma = conn.CreateCommand();
|
|
41
43
|
pragma.CommandText = "PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;";
|
|
42
44
|
pragma.ExecuteNonQuery();
|
|
45
|
+
PrepareVectorSearch(conn);
|
|
43
46
|
return conn;
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
/// <summary>
|
|
50
|
+
/// Registers the distance functions and loads sqlite-vec so a `VECTOR(n)`
|
|
51
|
+
/// column is ranked by SQLite instead of by the client. Neither is
|
|
52
|
+
/// required: a connection that refuses both falls back to `json_each`, or
|
|
53
|
+
/// to scoring in C#.
|
|
54
|
+
///
|
|
55
|
+
/// This runs per connection because the engine opens one per statement,
|
|
56
|
+
/// and a user function only exists for the connection that declared it.
|
|
57
|
+
/// </summary>
|
|
58
|
+
private void PrepareVectorSearch(Microsoft.Data.Sqlite.SqliteConnection conn)
|
|
59
|
+
{
|
|
60
|
+
var support = VectorSupport;
|
|
61
|
+
if (support == null) return;
|
|
62
|
+
|
|
63
|
+
var hasVec = false;
|
|
64
|
+
if (!string.IsNullOrWhiteSpace(support.SqliteVecPath))
|
|
65
|
+
{
|
|
66
|
+
try { conn.LoadExtension(support.SqliteVecPath); }
|
|
67
|
+
catch { /* the extension is optional */ }
|
|
68
|
+
}
|
|
69
|
+
if (!hasVec)
|
|
70
|
+
{
|
|
71
|
+
try
|
|
72
|
+
{
|
|
73
|
+
using var probe = conn.CreateCommand();
|
|
74
|
+
probe.CommandText = "SELECT vec_version()";
|
|
75
|
+
probe.ExecuteScalar();
|
|
76
|
+
hasVec = true;
|
|
77
|
+
}
|
|
78
|
+
catch { /* not loaded */ }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var hasUdf = false;
|
|
82
|
+
try {
|
|
83
|
+
using var native = conn.CreateCommand();
|
|
84
|
+
native.CommandText = "SELECT an5_vector_version()";
|
|
85
|
+
native.ExecuteScalar();
|
|
86
|
+
support.Report(false, true, true);
|
|
87
|
+
return;
|
|
88
|
+
} catch { /* no AN5 native extension */ }
|
|
89
|
+
try
|
|
90
|
+
{
|
|
91
|
+
foreach (var metric in SqliteVectors.An5Functions.Keys)
|
|
92
|
+
{
|
|
93
|
+
var name = SqliteVectors.An5Functions[metric];
|
|
94
|
+
var distance = SqliteVectors.MakeDistanceFunction(metric);
|
|
95
|
+
// The metric is baked into `distance`; the empty state is
|
|
96
|
+
// only there to satisfy the delegate shape.
|
|
97
|
+
conn.CreateFunction<object[], object>(
|
|
98
|
+
name,
|
|
99
|
+
Array.Empty<object>(),
|
|
100
|
+
(_state, args) => distance(args),
|
|
101
|
+
true);
|
|
102
|
+
}
|
|
103
|
+
hasUdf = true;
|
|
104
|
+
}
|
|
105
|
+
catch { /* the bundle may not allow user functions */ }
|
|
106
|
+
|
|
107
|
+
support.Report(hasVec, hasUdf, true);
|
|
108
|
+
}
|
|
109
|
+
|
|
46
110
|
private ConnectionScope<Microsoft.Data.Sqlite.SqliteConnection> Acquire()
|
|
47
111
|
=> _txnConn != null
|
|
48
112
|
? new ConnectionScope<Microsoft.Data.Sqlite.SqliteConnection>(_txnConn, owned: false)
|
|
@@ -67,7 +131,9 @@ namespace An5Orm
|
|
|
67
131
|
param.ParameterName = paramName;
|
|
68
132
|
// A JSON vector or a bool needs a type SQLite will store in a
|
|
69
133
|
// column rather than coerce to an integer.
|
|
70
|
-
if (value is
|
|
134
|
+
if (value is float[] || value is double[] || value is List<double>)
|
|
135
|
+
param.Value = SqliteVectors.EncodeVector(SqliteVectors.DecodeVector(value));
|
|
136
|
+
else if (value is bool flag) param.Value = flag ? 1 : 0;
|
|
71
137
|
else param.Value = value;
|
|
72
138
|
cmd.Parameters.Add(param);
|
|
73
139
|
}
|
|
@@ -108,6 +174,20 @@ namespace An5Orm
|
|
|
108
174
|
if (!HasColumn(reader, prop.Name)) continue;
|
|
109
175
|
var val = reader[prop.Name];
|
|
110
176
|
if (val == DBNull.Value) continue;
|
|
177
|
+
// A `VECTOR(n)` column stores a float32 BLOB (and older ones a
|
|
178
|
+
// JSON text column) while the entity declares it as float[],
|
|
179
|
+
// which no converter bridges.
|
|
180
|
+
if (prop.PropertyType == typeof(float[]))
|
|
181
|
+
{
|
|
182
|
+
var vector = SqliteVectors.DecodeVector(val);
|
|
183
|
+
if (vector != null)
|
|
184
|
+
{
|
|
185
|
+
var floats = new float[vector.Count];
|
|
186
|
+
for (int i = 0; i < vector.Count; i++) floats[i] = (float)vector[i];
|
|
187
|
+
prop.SetValue(item, floats);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
111
191
|
try { prop.SetValue(item, Convert.ChangeType(val, prop.PropertyType)); } catch { }
|
|
112
192
|
}
|
|
113
193
|
results.Add(item);
|