@an5/adapters 0.2.5 → 0.2.9

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 (99) hide show
  1. package/README.md +283 -305
  2. package/dist/an5Adapter.d.ts +142 -7
  3. package/dist/an5Adapter.js +998 -68
  4. package/dist/base/metadata.d.ts +10 -0
  5. package/dist/base/metadata.js +107 -0
  6. package/dist/base/sql.d.ts +19 -1
  7. package/dist/base/sql.js +152 -24
  8. package/dist/base/types.d.ts +10 -1
  9. package/dist/browser.d.ts +4 -1
  10. package/dist/browser.js +9 -2
  11. package/dist/googlesheets/adapter.d.ts +1 -0
  12. package/dist/googlesheets/adapter.js +9 -2
  13. package/dist/googlesheets/config.d.ts +4 -4
  14. package/dist/googlesheets/helpers.js +2 -0
  15. package/dist/googlesheets/parseConnectionString.js +5 -2
  16. package/dist/googlesheets/sqlExecutor.js +29 -24
  17. package/dist/googlesheets/tableClient.js +14 -5
  18. package/dist/index.d.ts +5 -1
  19. package/dist/index.js +15 -1
  20. package/dist/mssql/engine.js +8 -4
  21. package/dist/mysql/engine.js +1 -1
  22. package/dist/nbase/client.d.ts +92 -0
  23. package/dist/nbase/client.js +147 -0
  24. package/dist/nbase/index.d.ts +47 -0
  25. package/dist/nbase/index.js +80 -0
  26. package/dist/postgres/engine.js +1 -1
  27. package/dist/sqlite/browserEngine.d.ts +27 -0
  28. package/dist/sqlite/browserEngine.js +161 -0
  29. package/dist/sqlite/engine.js +15 -3
  30. package/dist/sqlite/index.d.ts +1 -0
  31. package/dist/sqlite/index.js +1 -0
  32. package/dist/typed.d.ts +5 -0
  33. package/dist/typed.js +2 -0
  34. package/dotnet/An5Adapter.cs +1112 -275
  35. package/dotnet/Base/Core.cs +380 -82
  36. package/dotnet/Mssql/MssqlEngine.cs +145 -144
  37. package/dotnet/Postgres/PostgresEngine.cs +149 -121
  38. package/dotnet/Sqlite/SqliteEngine.cs +214 -0
  39. package/golang/an5_adapter.go +314 -0
  40. package/golang/base/base.go +100 -0
  41. package/golang/base/metadata.go +116 -0
  42. package/golang/base/where.go +311 -0
  43. package/golang/go.mod +3 -0
  44. package/golang/mssql/mssql.go +32 -0
  45. package/golang/postgres/postgres.go +37 -0
  46. package/golang/sqlite/sqlite.go +14 -0
  47. package/golang/sqlite_test.go +42 -0
  48. package/golang/table_client.go +1329 -0
  49. package/package.json +26 -9
  50. package/python/an5_adapter.py +184 -97
  51. package/python/base/__init__.py +3 -3
  52. package/python/base/dialects.py +11 -8
  53. package/python/base/metadata.py +59 -10
  54. package/python/base/sql.py +202 -121
  55. package/python/mssql/__init__.py +1 -1
  56. package/python/mssql/provider.py +62 -62
  57. package/python/postgres/__init__.py +1 -1
  58. package/python/postgres/provider.py +15 -15
  59. package/python/sqlite/__init__.py +3 -0
  60. package/python/sqlite/provider.py +83 -0
  61. package/python/table_client.py +636 -216
  62. package/rust/Cargo.toml +31 -0
  63. package/rust/README.md +71 -0
  64. package/rust/src/adapter.rs +1312 -0
  65. package/rust/src/base/dialect.rs +172 -0
  66. package/rust/src/base/metadata.rs +134 -0
  67. package/rust/src/base/mod.rs +17 -0
  68. package/rust/src/base/where_builder.rs +405 -0
  69. package/rust/src/lib.rs +75 -0
  70. package/typescript/src/an5Adapter.ts +1515 -505
  71. package/typescript/src/base/index.ts +3 -3
  72. package/typescript/src/base/metadata.ts +148 -28
  73. package/typescript/src/base/sql.ts +264 -98
  74. package/typescript/src/base/types.ts +33 -24
  75. package/typescript/src/browser.ts +10 -6
  76. package/typescript/src/config.ts +95 -95
  77. package/typescript/src/googlesheets/adapter.ts +269 -259
  78. package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
  79. package/typescript/src/googlesheets/config.ts +47 -47
  80. package/typescript/src/googlesheets/helpers.ts +144 -143
  81. package/typescript/src/googlesheets/index.ts +3 -3
  82. package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
  83. package/typescript/src/googlesheets/retry.ts +126 -126
  84. package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
  85. package/typescript/src/googlesheets/tableClient.ts +461 -455
  86. package/typescript/src/googlesheets/types.ts +6 -6
  87. package/typescript/src/index.ts +48 -27
  88. package/typescript/src/mssql/engine.ts +134 -132
  89. package/typescript/src/mssql/index.ts +1 -1
  90. package/typescript/src/mysql/engine.ts +80 -80
  91. package/typescript/src/mysql/index.ts +1 -1
  92. package/typescript/src/nbase/client.ts +214 -0
  93. package/typescript/src/nbase/index.ts +104 -0
  94. package/typescript/src/postgres/engine.ts +87 -87
  95. package/typescript/src/postgres/index.ts +1 -1
  96. package/typescript/src/sqlite/browserEngine.ts +192 -0
  97. package/typescript/src/sqlite/engine.ts +66 -55
  98. package/typescript/src/sqlite/index.ts +3 -1
  99. package/typescript/src/typed.ts +52 -0
@@ -0,0 +1,116 @@
1
+ // Package base provides core dialect types, quoting utilities, vector math,
2
+ // and adapter metadata for AN5 Go Adapters.
3
+ package base
4
+
5
+ import (
6
+ "strings"
7
+ "sync"
8
+ )
9
+
10
+ // RelationDef describes a model relation for eager-loading (include) support.
11
+ type RelationDef struct {
12
+ ModelName string
13
+ RelationType string // "many" | "one"
14
+ ForeignKey string
15
+ LocalKey string
16
+ }
17
+
18
+ // AdapterMetadata holds model-to-table, field mapping, and relation mapping injected by generated clients.
19
+ type AdapterMetadata struct {
20
+ ModelToTable map[string]string
21
+ ModelFields map[string]interface{}
22
+ RelationMap map[string]map[string]RelationDef
23
+ }
24
+
25
+ var (
26
+ metaMu sync.RWMutex
27
+ modelToTable = map[string]string{}
28
+ modelFields = map[string]interface{}{}
29
+ relationMap = map[string]map[string]RelationDef{}
30
+ )
31
+
32
+ // SetAdapterMetadata stores metadata provided by the generated an5Client.
33
+ // Call this once at application startup after initializing the DB context.
34
+ func SetAdapterMetadata(meta AdapterMetadata) {
35
+ metaMu.Lock()
36
+ defer metaMu.Unlock()
37
+ modelToTable = make(map[string]string, len(meta.ModelToTable))
38
+ for k, v := range meta.ModelToTable {
39
+ modelToTable[k] = v
40
+ }
41
+ modelFields = make(map[string]interface{}, len(meta.ModelFields))
42
+ for k, v := range meta.ModelFields {
43
+ modelFields[k] = v
44
+ }
45
+ relationMap = make(map[string]map[string]RelationDef, len(meta.RelationMap))
46
+ for model, rels := range meta.RelationMap {
47
+ relationMap[model] = make(map[string]RelationDef, len(rels))
48
+ for key, def := range rels {
49
+ relationMap[model][key] = def
50
+ }
51
+ }
52
+ }
53
+
54
+ // GetModelToTable returns the model-to-table mapping (read-only snapshot).
55
+ func GetModelToTable() map[string]string {
56
+ metaMu.RLock()
57
+ defer metaMu.RUnlock()
58
+ out := make(map[string]string, len(modelToTable))
59
+ for k, v := range modelToTable {
60
+ out[k] = v
61
+ }
62
+ return out
63
+ }
64
+
65
+ // GetFieldsForModel returns field metadata for the given model name.
66
+ func GetFieldsForModel(modelName string) interface{} {
67
+ metaMu.RLock()
68
+ defer metaMu.RUnlock()
69
+ return modelFields[modelName]
70
+ }
71
+
72
+ // GetRelationsForModel returns the relation map for the given model name (read-only snapshot).
73
+ func GetRelationsForModel(modelName string) map[string]RelationDef {
74
+ metaMu.RLock()
75
+ defer metaMu.RUnlock()
76
+ rels := relationMap[modelName]
77
+ if rels == nil {
78
+ return map[string]RelationDef{}
79
+ }
80
+ out := make(map[string]RelationDef, len(rels))
81
+ for k, v := range rels {
82
+ out[k] = v
83
+ }
84
+ return out
85
+ }
86
+
87
+ // GetRelationMap returns the full model-to-relations map (read-only snapshot).
88
+ func GetRelationMap() map[string]map[string]RelationDef {
89
+ metaMu.RLock()
90
+ defer metaMu.RUnlock()
91
+ out := make(map[string]map[string]RelationDef, len(relationMap))
92
+ for model, rels := range relationMap {
93
+ out[model] = make(map[string]RelationDef, len(rels))
94
+ for k, v := range rels {
95
+ out[model][k] = v
96
+ }
97
+ }
98
+ return out
99
+ }
100
+
101
+ // ResolveTable looks up the physical table name for a model name (case-insensitive fallback).
102
+ func ResolveTable(modelName string) string {
103
+ metaMu.RLock()
104
+ defer metaMu.RUnlock()
105
+ if t, ok := modelToTable[modelName]; ok {
106
+ return t
107
+ }
108
+ // lowercase fallback
109
+ lower := strings.ToLower(modelName)
110
+ for k, v := range modelToTable {
111
+ if strings.ToLower(k) == lower {
112
+ return v
113
+ }
114
+ }
115
+ return modelName
116
+ }
@@ -0,0 +1,311 @@
1
+ // Package base provides dialect-aware WHERE and ORDER BY builders for the AN5 Go adapter.
2
+ package base
3
+
4
+ import (
5
+ "fmt"
6
+ "sort"
7
+ "strings"
8
+ )
9
+
10
+ // operatorKeys are the supported comparison operators inside a filter object.
11
+ var operatorKeys = []string{"equals", "in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"}
12
+
13
+ // isOperatorMap reports whether v is a filter dictionary containing ORM operators.
14
+ func isOperatorMap(v interface{}) bool {
15
+ m, ok := v.(map[string]interface{})
16
+ if !ok {
17
+ return false
18
+ }
19
+ for _, op := range operatorKeys {
20
+ if _, has := m[op]; has {
21
+ return true
22
+ }
23
+ }
24
+ return false
25
+ }
26
+
27
+ // isPlainMap reports whether v is a non-nil map.
28
+ func isPlainMap(v interface{}) (map[string]interface{}, bool) {
29
+ m, ok := v.(map[string]interface{})
30
+ if !ok || m == nil {
31
+ return nil, false
32
+ }
33
+ return m, true
34
+ }
35
+
36
+ // cleanWhere flattens relation-style nested where objects (key contains '_' with a plain-map value).
37
+ func cleanWhere(where map[string]interface{}) map[string]interface{} {
38
+ clean := map[string]interface{}{}
39
+ for k, v := range where {
40
+ if strings.Contains(k, "_") {
41
+ if m, ok := isPlainMap(v); ok && !isOperatorMap(v) {
42
+ for nk, nv := range m {
43
+ clean[nk] = nv
44
+ }
45
+ } else {
46
+ clean[k] = v
47
+ }
48
+ } else {
49
+ clean[k] = v
50
+ }
51
+ }
52
+ return clean
53
+ }
54
+
55
+ // sortedKeys returns the map keys in a stable sorted order for deterministic SQL.
56
+ func sortedKeys(m map[string]interface{}) []string {
57
+ keys := make([]string, 0, len(m))
58
+ for k := range m {
59
+ keys = append(keys, k)
60
+ }
61
+ sort.Strings(keys)
62
+ return keys
63
+ }
64
+
65
+ // BuildWhere converts an ORM-style where object into a SQL clause with ordered placeholders.
66
+ // ph returns the placeholder for arg position n (1-based).
67
+ func BuildWhere(where map[string]interface{}, dialect Dialect, ph func(n int) string) (string, []interface{}) {
68
+ args := make([]interface{}, 0, 8)
69
+ clause := buildWhereRec(cleanWhere(where), dialect, &args, ph, "")
70
+ return clause, args
71
+ }
72
+
73
+ func buildWhereRec(where map[string]interface{}, dialect Dialect, args *[]interface{}, ph func(n int) string, prefix string) string {
74
+ if where == nil || len(where) == 0 {
75
+ return ""
76
+ }
77
+ var conditions []string
78
+
79
+ for _, key := range sortedKeys(where) {
80
+ value := where[key]
81
+
82
+ switch key {
83
+ case "OR":
84
+ if list, ok := toSlice(value); ok && len(list) > 0 {
85
+ var subs []string
86
+ for i, item := range list {
87
+ if m, ok := item.(map[string]interface{}); ok && m != nil {
88
+ if s := buildWhereRec(m, dialect, args, ph, fmt.Sprintf("%sor%d_", prefix, i)); s != "" {
89
+ subs = append(subs, s)
90
+ }
91
+ }
92
+ }
93
+ if len(subs) > 0 {
94
+ conditions = append(conditions, "("+strings.Join(subs, " OR ")+")")
95
+ }
96
+ continue
97
+ }
98
+ case "AND":
99
+ if list, ok := toSlice(value); ok && len(list) > 0 {
100
+ var subs []string
101
+ for i, item := range list {
102
+ if m, ok := item.(map[string]interface{}); ok && m != nil {
103
+ if s := buildWhereRec(m, dialect, args, ph, fmt.Sprintf("%sand%d_", prefix, i)); s != "" {
104
+ subs = append(subs, s)
105
+ }
106
+ }
107
+ }
108
+ if len(subs) > 0 {
109
+ conditions = append(conditions, "("+strings.Join(subs, " AND ")+")")
110
+ }
111
+ continue
112
+ }
113
+ case "NOT":
114
+ items := value
115
+ if m, ok := value.(map[string]interface{}); ok {
116
+ items = []interface{}{m}
117
+ }
118
+ if list, ok := toSlice(items); ok && len(list) > 0 {
119
+ var subs []string
120
+ for i, item := range list {
121
+ if m, ok := item.(map[string]interface{}); ok && m != nil {
122
+ if s := buildWhereRec(m, dialect, args, ph, fmt.Sprintf("%snot%d_", prefix, i)); s != "" {
123
+ subs = append(subs, s)
124
+ }
125
+ }
126
+ }
127
+ if len(subs) > 0 {
128
+ conditions = append(conditions, "NOT ("+strings.Join(subs, " AND ")+")")
129
+ }
130
+ continue
131
+ }
132
+ }
133
+
134
+ conditions = append(conditions, buildFieldCondition(key, value, dialect, args, ph, prefix)...)
135
+ }
136
+
137
+ return strings.Join(conditions, " AND ")
138
+ }
139
+
140
+ func buildFieldCondition(key string, value interface{}, dialect Dialect, args *[]interface{}, ph func(n int) string, prefix string) []string {
141
+ col := QuoteIdentifier(key, dialect)
142
+
143
+ if value == nil {
144
+ return []string{col + " IS NULL"}
145
+ }
146
+
147
+ m, isMap := isPlainMap(value)
148
+ if !isMap {
149
+ n := len(*args) + 1
150
+ *args = append(*args, value)
151
+ return []string{fmt.Sprintf("%s = %s", col, ph(n))}
152
+ }
153
+
154
+ var parts []string
155
+ addArg := func(val interface{}) string {
156
+ n := len(*args) + 1
157
+ *args = append(*args, val)
158
+ return ph(n)
159
+ }
160
+
161
+ // Determine property order deterministically.
162
+ var opKeys []string
163
+ for k := range m {
164
+ opKeys = append(opKeys, k)
165
+ }
166
+ sort.Strings(opKeys)
167
+
168
+ for _, op := range opKeys {
169
+ val := m[op]
170
+ switch op {
171
+ case "not":
172
+ if val == nil {
173
+ parts = append(parts, col+" IS NOT NULL")
174
+ } else if nested, ok := val.(map[string]interface{}); ok && !isOperatorMap(val) {
175
+ if nestedArgs := len(*args); true {
176
+ nestedCond := buildWhereRec(map[string]interface{}{key: nested}, dialect, args, ph, key+"_not_")
177
+ _ = nestedArgs
178
+ if nestedCond != "" {
179
+ parts = append(parts, "NOT ("+nestedCond+")")
180
+ }
181
+ }
182
+ } else {
183
+ parts = append(parts, fmt.Sprintf("%s <> %s", col, addArg(val)))
184
+ }
185
+ case "equals":
186
+ if val == nil {
187
+ parts = append(parts, col+" IS NULL")
188
+ } else {
189
+ parts = append(parts, fmt.Sprintf("%s = %s", col, addArg(val)))
190
+ }
191
+ case "contains":
192
+ parts = append(parts, fmt.Sprintf("%s LIKE %s", col, addArg("%"+toString(val)+"%")))
193
+ case "startsWith":
194
+ parts = append(parts, fmt.Sprintf("%s LIKE %s", col, addArg(toString(val)+"%")))
195
+ case "endsWith":
196
+ parts = append(parts, fmt.Sprintf("%s LIKE %s", col, addArg("%"+toString(val))))
197
+ case "gte":
198
+ parts = append(parts, fmt.Sprintf("%s >= %s", col, addArg(val)))
199
+ case "lte":
200
+ parts = append(parts, fmt.Sprintf("%s <= %s", col, addArg(val)))
201
+ case "gt":
202
+ parts = append(parts, fmt.Sprintf("%s > %s", col, addArg(val)))
203
+ case "lt":
204
+ parts = append(parts, fmt.Sprintf("%s < %s", col, addArg(val)))
205
+ case "in":
206
+ if list, ok := toSlice(val); ok && len(list) > 0 {
207
+ var phs []string
208
+ for _, item := range list {
209
+ phs = append(phs, addArg(item))
210
+ }
211
+ parts = append(parts, fmt.Sprintf("%s IN (%s)", col, strings.Join(phs, ", ")))
212
+ } else {
213
+ parts = append(parts, "1=0")
214
+ }
215
+ case "notIn":
216
+ if list, ok := toSlice(val); ok && len(list) > 0 {
217
+ var phs []string
218
+ for _, item := range list {
219
+ phs = append(phs, addArg(item))
220
+ }
221
+ parts = append(parts, fmt.Sprintf("%s NOT IN (%s)", col, strings.Join(phs, ", ")))
222
+ } else {
223
+ parts = append(parts, "1=1")
224
+ }
225
+ }
226
+ }
227
+ return parts
228
+ }
229
+
230
+ // toSlice attempts to interpret value as []interface{} (also accepting []string and []int).
231
+ func toSlice(value interface{}) ([]interface{}, bool) {
232
+ switch v := value.(type) {
233
+ case []interface{}:
234
+ return v, true
235
+ case []string:
236
+ out := make([]interface{}, len(v))
237
+ for i, s := range v {
238
+ out[i] = s
239
+ }
240
+ return out, true
241
+ case []int:
242
+ out := make([]interface{}, len(v))
243
+ for i, s := range v {
244
+ out[i] = s
245
+ }
246
+ return out, true
247
+ case []float64:
248
+ out := make([]interface{}, len(v))
249
+ for i, s := range v {
250
+ out[i] = s
251
+ }
252
+ return out, true
253
+ default:
254
+ return nil, false
255
+ }
256
+ }
257
+
258
+ func toString(v interface{}) string {
259
+ if s, ok := v.(string); ok {
260
+ return s
261
+ }
262
+ return fmt.Sprintf("%v", v)
263
+ }
264
+
265
+ // normalizeSortDirection normalizes a direction to ASC or DESC.
266
+ func normalizeSortDirection(dir interface{}) string {
267
+ if s, ok := dir.(string); ok && strings.EqualFold(s, "DESC") {
268
+ return "DESC"
269
+ }
270
+ return "ASC"
271
+ }
272
+
273
+ // BuildOrderBy converts an ORM-style orderBy object (map or list of maps) into a SQL ORDER BY clause.
274
+ // Supports map[string]string, map[string]interface{}, []interface{} and a plain string.
275
+ func BuildOrderBy(orderBy interface{}, dialect Dialect) string {
276
+ var entries []map[string]interface{}
277
+
278
+ switch v := orderBy.(type) {
279
+ case string:
280
+ if strings.TrimSpace(v) == "" {
281
+ return ""
282
+ }
283
+ return "ORDER BY " + v
284
+ case []interface{}:
285
+ for _, item := range v {
286
+ if m, ok := item.(map[string]interface{}); ok {
287
+ entries = append(entries, m)
288
+ }
289
+ }
290
+ case []map[string]interface{}:
291
+ entries = v
292
+ case map[string]interface{}:
293
+ entries = []map[string]interface{}{v}
294
+ default:
295
+ return ""
296
+ }
297
+
298
+ if len(entries) == 0 {
299
+ return ""
300
+ }
301
+ var parts []string
302
+ for _, entry := range entries {
303
+ for _, key := range sortedKeys(entry) {
304
+ parts = append(parts, fmt.Sprintf("%s %s", QuoteIdentifier(key, dialect), normalizeSortDirection(entry[key])))
305
+ }
306
+ }
307
+ if len(parts) == 0 {
308
+ return ""
309
+ }
310
+ return "ORDER BY " + strings.Join(parts, ", ")
311
+ }
package/golang/go.mod ADDED
@@ -0,0 +1,3 @@
1
+ module an5adapters
2
+
3
+ go 1.18
@@ -0,0 +1,32 @@
1
+ // Package mssql provides SQL Server specific dialect execution and query generation for AN5 Go Adapters.
2
+ package mssql
3
+
4
+ import (
5
+ "an5adapters/base"
6
+ "fmt"
7
+ "strings"
8
+ )
9
+
10
+ // BuildVectorSearchQuery constructs native SQL Server VECTOR_DISTANCE query.
11
+ func BuildVectorSearchQuery(table, field, metric string, dim, take int, where string) string {
12
+ if field == "" {
13
+ field = "embedding"
14
+ }
15
+ if metric == "" {
16
+ metric = "cosine"
17
+ }
18
+ tableSql := base.QuoteTable(table, base.DialectMssql)
19
+ fieldSql := base.QuoteIdentifier(field, base.DialectMssql)
20
+
21
+ query := fmt.Sprintf(
22
+ "SELECT TOP (%d) *, VECTOR_DISTANCE('%s', CAST(%s AS VECTOR(%d, float32)), CAST(? AS VECTOR(%d, float32))) AS distance FROM %s WITH (NOLOCK)",
23
+ take, metric, fieldSql, dim, dim, tableSql,
24
+ )
25
+ if strings.TrimSpace(where) != "" {
26
+ query += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", fieldSql, where)
27
+ } else {
28
+ query += fmt.Sprintf(" WHERE %s IS NOT NULL", fieldSql)
29
+ }
30
+ query += " ORDER BY distance ASC"
31
+ return query
32
+ }
@@ -0,0 +1,37 @@
1
+ // Package postgres provides PostgreSQL specific dialect execution and pgvector query generation for AN5 Go Adapters.
2
+ // pgvector operators: <=> (cosine), <-> (euclidean), <#> (dot product).
3
+ package postgres
4
+
5
+ import (
6
+ "an5adapters/base"
7
+ "fmt"
8
+ "strings"
9
+ )
10
+
11
+ // BuildVectorSearchQuery constructs native PostgreSQL pgvector query.
12
+ func BuildVectorSearchQuery(table, field, metric string, dim, take int, where string) string {
13
+ if field == "" {
14
+ field = "embedding"
15
+ }
16
+ if metric == "" {
17
+ metric = "cosine"
18
+ }
19
+ tableSql := base.QuoteTable(table, base.DialectPostgres)
20
+ fieldSql := base.QuoteIdentifier(field, base.DialectPostgres)
21
+
22
+ op := "<=>"
23
+ if strings.EqualFold(metric, "euclidean") {
24
+ op = "<->"
25
+ } else if strings.EqualFold(metric, "dot") {
26
+ op = "<#>"
27
+ }
28
+
29
+ query := fmt.Sprintf("SELECT *, (%s %s $1::vector) AS distance FROM %s", fieldSql, op, tableSql)
30
+ if strings.TrimSpace(where) != "" {
31
+ query += fmt.Sprintf(" WHERE %s IS NOT NULL AND (%s)", fieldSql, where)
32
+ } else {
33
+ query += fmt.Sprintf(" WHERE %s IS NOT NULL", fieldSql)
34
+ }
35
+ query += fmt.Sprintf(" ORDER BY distance ASC LIMIT %d", take)
36
+ return query
37
+ }
@@ -0,0 +1,14 @@
1
+ // Package sqlite provides SQLite specific dialect execution and query generation for AN5 Go Adapters.
2
+ package sqlite
3
+
4
+ import (
5
+ "an5adapters/base"
6
+ )
7
+
8
+ // BuildVectorSearchQuery returns empty string as SQLite uses in-memory vector fallback by default.
9
+ func BuildVectorSearchQuery(table, field, metric string, dim, take int, where string) string {
10
+ _ = base.QuoteTable(table, base.DialectSqlite)
11
+ _ = base.QuoteIdentifier(field, base.DialectSqlite)
12
+ // Native vector extension not present in standard sqlite; in-memory fallback is used.
13
+ return ""
14
+ }
@@ -0,0 +1,42 @@
1
+ package an5adapters_test
2
+
3
+ import (
4
+ "testing"
5
+
6
+ an5adapters "an5adapters"
7
+ "an5adapters/base"
8
+ )
9
+
10
+ func TestSqliteDialectDetection(t *testing.T) {
11
+ tests := []struct {
12
+ connStr string
13
+ expected base.Dialect
14
+ }{
15
+ {"sqlite://data/test.db", base.DialectSqlite},
16
+ {"sqlite:memory:", base.DialectSqlite},
17
+ {"file:mydb.sqlite3", base.DialectSqlite},
18
+ {"app.db", base.DialectSqlite},
19
+ {":memory:", base.DialectSqlite},
20
+ {"postgres://user:pass@localhost:5432/db", base.DialectPostgres},
21
+ {"Server=localhost;Database=master;", base.DialectMssql},
22
+ }
23
+
24
+ for _, tt := range tests {
25
+ got := an5adapters.DetectDialect(tt.connStr)
26
+ if got != tt.expected {
27
+ t.Errorf("DetectDialect(%q) = %v, want %v", tt.connStr, got, tt.expected)
28
+ }
29
+ }
30
+ }
31
+
32
+ func TestSqliteQuoting(t *testing.T) {
33
+ id := an5adapters.QuoteIdentifier("user_name", base.DialectSqlite)
34
+ if id != `"user_name"` {
35
+ t.Errorf("QuoteIdentifier() = %v, want %v", id, `"user_name"`)
36
+ }
37
+
38
+ tbl := an5adapters.QuoteTable("users", base.DialectSqlite)
39
+ if tbl != `"users"` {
40
+ t.Errorf("QuoteTable() = %v, want %v", tbl, `"users"`)
41
+ }
42
+ }