@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.
- package/README.md +283 -305
- package/dist/an5Adapter.d.ts +142 -7
- package/dist/an5Adapter.js +998 -68
- package/dist/base/metadata.d.ts +10 -0
- package/dist/base/metadata.js +107 -0
- package/dist/base/sql.d.ts +19 -1
- package/dist/base/sql.js +152 -24
- package/dist/base/types.d.ts +10 -1
- package/dist/browser.d.ts +4 -1
- package/dist/browser.js +9 -2
- package/dist/googlesheets/adapter.d.ts +1 -0
- package/dist/googlesheets/adapter.js +9 -2
- package/dist/googlesheets/config.d.ts +4 -4
- package/dist/googlesheets/helpers.js +2 -0
- package/dist/googlesheets/parseConnectionString.js +5 -2
- package/dist/googlesheets/sqlExecutor.js +29 -24
- package/dist/googlesheets/tableClient.js +14 -5
- package/dist/index.d.ts +5 -1
- package/dist/index.js +15 -1
- package/dist/mssql/engine.js +8 -4
- package/dist/mysql/engine.js +1 -1
- package/dist/nbase/client.d.ts +92 -0
- package/dist/nbase/client.js +147 -0
- package/dist/nbase/index.d.ts +47 -0
- package/dist/nbase/index.js +80 -0
- package/dist/postgres/engine.js +1 -1
- package/dist/sqlite/browserEngine.d.ts +27 -0
- package/dist/sqlite/browserEngine.js +161 -0
- package/dist/sqlite/engine.js +15 -3
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/typed.d.ts +5 -0
- package/dist/typed.js +2 -0
- package/dotnet/An5Adapter.cs +1112 -275
- package/dotnet/Base/Core.cs +380 -82
- package/dotnet/Mssql/MssqlEngine.cs +145 -144
- package/dotnet/Postgres/PostgresEngine.cs +149 -121
- package/dotnet/Sqlite/SqliteEngine.cs +214 -0
- package/golang/an5_adapter.go +314 -0
- package/golang/base/base.go +100 -0
- package/golang/base/metadata.go +116 -0
- package/golang/base/where.go +311 -0
- package/golang/go.mod +3 -0
- package/golang/mssql/mssql.go +32 -0
- package/golang/postgres/postgres.go +37 -0
- package/golang/sqlite/sqlite.go +14 -0
- package/golang/sqlite_test.go +42 -0
- package/golang/table_client.go +1329 -0
- package/package.json +26 -9
- package/python/an5_adapter.py +184 -97
- package/python/base/__init__.py +3 -3
- package/python/base/dialects.py +11 -8
- package/python/base/metadata.py +59 -10
- package/python/base/sql.py +202 -121
- package/python/mssql/__init__.py +1 -1
- package/python/mssql/provider.py +62 -62
- package/python/postgres/__init__.py +1 -1
- package/python/postgres/provider.py +15 -15
- package/python/sqlite/__init__.py +3 -0
- package/python/sqlite/provider.py +83 -0
- package/python/table_client.py +636 -216
- package/rust/Cargo.toml +31 -0
- package/rust/README.md +71 -0
- package/rust/src/adapter.rs +1312 -0
- package/rust/src/base/dialect.rs +172 -0
- package/rust/src/base/metadata.rs +134 -0
- package/rust/src/base/mod.rs +17 -0
- package/rust/src/base/where_builder.rs +405 -0
- package/rust/src/lib.rs +75 -0
- package/typescript/src/an5Adapter.ts +1515 -505
- package/typescript/src/base/index.ts +3 -3
- package/typescript/src/base/metadata.ts +148 -28
- package/typescript/src/base/sql.ts +264 -98
- package/typescript/src/base/types.ts +33 -24
- package/typescript/src/browser.ts +10 -6
- package/typescript/src/config.ts +95 -95
- package/typescript/src/googlesheets/adapter.ts +269 -259
- package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
- package/typescript/src/googlesheets/config.ts +47 -47
- package/typescript/src/googlesheets/helpers.ts +144 -143
- package/typescript/src/googlesheets/index.ts +3 -3
- package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
- package/typescript/src/googlesheets/retry.ts +126 -126
- package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
- package/typescript/src/googlesheets/tableClient.ts +461 -455
- package/typescript/src/googlesheets/types.ts +6 -6
- package/typescript/src/index.ts +48 -27
- package/typescript/src/mssql/engine.ts +134 -132
- package/typescript/src/mssql/index.ts +1 -1
- package/typescript/src/mysql/engine.ts +80 -80
- package/typescript/src/mysql/index.ts +1 -1
- package/typescript/src/nbase/client.ts +214 -0
- package/typescript/src/nbase/index.ts +104 -0
- package/typescript/src/postgres/engine.ts +87 -87
- package/typescript/src/postgres/index.ts +1 -1
- package/typescript/src/sqlite/browserEngine.ts +192 -0
- package/typescript/src/sqlite/engine.ts +66 -55
- package/typescript/src/sqlite/index.ts +3 -1
- package/typescript/src/typed.ts +52 -0
|
@@ -0,0 +1,1329 @@
|
|
|
1
|
+
// Package an5adapters provides TableClient for dynamic map-based table queries.
|
|
2
|
+
package an5adapters
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"context"
|
|
6
|
+
"crypto/rand"
|
|
7
|
+
"encoding/hex"
|
|
8
|
+
"fmt"
|
|
9
|
+
"sort"
|
|
10
|
+
"strings"
|
|
11
|
+
|
|
12
|
+
"an5adapters/base"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// TableClient provides table-scoped CRUD, aggregate, and vector search operations for dynamic map rows.
|
|
16
|
+
type TableClient struct {
|
|
17
|
+
Adapter *An5Adapter
|
|
18
|
+
TableName string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Table returns a TableClient for the specified table name.
|
|
22
|
+
func (a *An5Adapter) Table(tableName string) *TableClient {
|
|
23
|
+
return &TableClient{
|
|
24
|
+
Adapter: a,
|
|
25
|
+
TableName: tableName,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ViewClient provides read-only view operations.
|
|
30
|
+
type ViewClient struct {
|
|
31
|
+
table *TableClient
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// View returns a ViewClient for the specified view name.
|
|
35
|
+
func (a *An5Adapter) View(viewName string) *ViewClient {
|
|
36
|
+
return &ViewClient{
|
|
37
|
+
table: a.Table(viewName),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func (v *ViewClient) FindMany(ctx context.Context, args *FindManyArgs) ([]map[string]interface{}, error) {
|
|
42
|
+
return v.table.FindMany(ctx, args)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func (v *ViewClient) FindFirst(ctx context.Context, args *FindManyArgs) (map[string]interface{}, error) {
|
|
46
|
+
return v.table.FindFirst(ctx, args)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func (v *ViewClient) FindUnique(ctx context.Context, args *FindManyArgs) (map[string]interface{}, error) {
|
|
50
|
+
return v.table.FindUnique(ctx, args)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func (v *ViewClient) Count(ctx context.Context, args *CountArgs) (int64, error) {
|
|
54
|
+
return v.table.Count(ctx, args)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func (v *ViewClient) Aggregate(ctx context.Context, args *AggregateArgs) (map[string]interface{}, error) {
|
|
58
|
+
return v.table.Aggregate(ctx, args)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func (v *ViewClient) GroupBy(ctx context.Context, args *GroupByArgs) ([]map[string]interface{}, error) {
|
|
62
|
+
return v.table.GroupBy(ctx, args)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func (v *ViewClient) VectorSearch(ctx context.Context, args *VectorSearchArgs) ([]map[string]interface{}, error) {
|
|
66
|
+
return v.table.VectorSearch(ctx, args)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Args types (ORM-standard structured args, mirrors TypeScript AdapterTableClient) ──
|
|
70
|
+
|
|
71
|
+
// FindManyArgs configures a FindMany / FindFirst / FindUnique query.
|
|
72
|
+
type FindManyArgs struct {
|
|
73
|
+
Where interface{} // map[string]interface{} ORM-style where object
|
|
74
|
+
OrderBy interface{} // map[string]interface{}, []interface{}, []map[string]interface{} or string
|
|
75
|
+
Skip int
|
|
76
|
+
Take int
|
|
77
|
+
Select interface{} // []string or map[string]bool
|
|
78
|
+
Include interface{} // map[string]interface{} relation keys to eager-load
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// CountArgs configures a Count query.
|
|
82
|
+
type CountArgs struct {
|
|
83
|
+
Where interface{}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// DeleteManyArgs configures a DeleteMany query.
|
|
87
|
+
type DeleteManyArgs struct {
|
|
88
|
+
Where interface{}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// CreateArgs configures a Create query.
|
|
92
|
+
type CreateArgs struct {
|
|
93
|
+
Data map[string]interface{}
|
|
94
|
+
Include interface{}
|
|
95
|
+
Select interface{}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// CreateManyArgs configures a CreateMany query.
|
|
99
|
+
type CreateManyArgs struct {
|
|
100
|
+
Data []map[string]interface{}
|
|
101
|
+
SkipDuplicates bool
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// UpdateArgs configures an Update query.
|
|
105
|
+
type UpdateArgs struct {
|
|
106
|
+
Where interface{}
|
|
107
|
+
Data map[string]interface{} // supports {increment,decrement,multiply,divide,set} field ops
|
|
108
|
+
Include interface{}
|
|
109
|
+
Select interface{}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// UpdateManyArgs configures an UpdateMany query.
|
|
113
|
+
type UpdateManyArgs struct {
|
|
114
|
+
Where interface{}
|
|
115
|
+
Data map[string]interface{}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// DeleteArgs configures a Delete query.
|
|
119
|
+
type DeleteArgs struct {
|
|
120
|
+
Where interface{}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// UpsertArgs configures an Upsert query.
|
|
124
|
+
type UpsertArgs struct {
|
|
125
|
+
Where interface{}
|
|
126
|
+
Create map[string]interface{}
|
|
127
|
+
Update map[string]interface{}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// AggregateArgs configures an Aggregate query.
|
|
131
|
+
type AggregateArgs struct {
|
|
132
|
+
Where interface{}
|
|
133
|
+
Count interface{} // true, or object/list/array of fields to count
|
|
134
|
+
Sum interface{}
|
|
135
|
+
Avg interface{}
|
|
136
|
+
Min interface{}
|
|
137
|
+
Max interface{}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// GroupByArgs configures a GroupBy query.
|
|
141
|
+
type GroupByArgs struct {
|
|
142
|
+
By interface{} // string or []string
|
|
143
|
+
Where interface{}
|
|
144
|
+
OrderBy interface{}
|
|
145
|
+
Skip int
|
|
146
|
+
Take int
|
|
147
|
+
Sum interface{}
|
|
148
|
+
Avg interface{}
|
|
149
|
+
Min interface{}
|
|
150
|
+
Max interface{}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// VectorSearchArgs configures a VectorSearch query.
|
|
154
|
+
type VectorSearchArgs struct {
|
|
155
|
+
Vector []float64
|
|
156
|
+
Take int
|
|
157
|
+
Where interface{}
|
|
158
|
+
VectorField string
|
|
159
|
+
DistanceMetric string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func (t *TableClient) quotedTable() string {
|
|
163
|
+
return base.QuoteTable(t.TableName, t.Adapter.Dialect)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func (t *TableClient) nolock() string {
|
|
167
|
+
if t.Adapter.Dialect == base.DialectMssql {
|
|
168
|
+
return " WITH (NOLOCK)"
|
|
169
|
+
}
|
|
170
|
+
return ""
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func (t *TableClient) placeholders(count int) string {
|
|
174
|
+
phs := make([]string, count)
|
|
175
|
+
offset := 0
|
|
176
|
+
for i := 0; i < count; i++ {
|
|
177
|
+
phs[i] = placeholder(t.Adapter.Dialect, i+offset+1)
|
|
178
|
+
}
|
|
179
|
+
return strings.Join(phs, ", ")
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// buildWhere converts an ORM-style where object into SQL clause + bound args.
|
|
183
|
+
func (t *TableClient) buildWhere(where interface{}) (string, []interface{}) {
|
|
184
|
+
if where == nil {
|
|
185
|
+
return "", nil
|
|
186
|
+
}
|
|
187
|
+
if m, ok := where.(map[string]interface{}); ok && m != nil {
|
|
188
|
+
clause, args := base.BuildWhere(m, t.Adapter.Dialect, func(n int) string {
|
|
189
|
+
return placeholder(t.Adapter.Dialect, n)
|
|
190
|
+
})
|
|
191
|
+
return clause, args
|
|
192
|
+
}
|
|
193
|
+
return "", nil
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func (t *TableClient) orderSql(orderBy interface{}) string {
|
|
197
|
+
return base.BuildOrderBy(orderBy, t.Adapter.Dialect)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// selectedFields extracts scalar column names from a select object ([]string or map with truthy values).
|
|
201
|
+
func selectedFields(selectVal interface{}) (keys []string, hasRelationSelect bool) {
|
|
202
|
+
switch v := selectVal.(type) {
|
|
203
|
+
case nil:
|
|
204
|
+
return nil, false
|
|
205
|
+
case []string:
|
|
206
|
+
return append([]string{}, v...), false
|
|
207
|
+
case []interface{}:
|
|
208
|
+
for _, item := range v {
|
|
209
|
+
if s, ok := item.(string); ok {
|
|
210
|
+
keys = append(keys, s)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return keys, false
|
|
214
|
+
case map[string]interface{}:
|
|
215
|
+
for k, val := range v {
|
|
216
|
+
if b, ok := val.(bool); ok && b {
|
|
217
|
+
if k == "_count" || strings.Contains(k, "_") {
|
|
218
|
+
hasRelationSelect = true
|
|
219
|
+
}
|
|
220
|
+
keys = append(keys, k)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return keys, hasRelationSelect
|
|
224
|
+
case map[string]bool:
|
|
225
|
+
for k, val := range v {
|
|
226
|
+
if val {
|
|
227
|
+
if k == "_count" || strings.Contains(k, "_") {
|
|
228
|
+
hasRelationSelect = true
|
|
229
|
+
}
|
|
230
|
+
keys = append(keys, k)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return keys, hasRelationSelect
|
|
234
|
+
}
|
|
235
|
+
return nil, false
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// projectFields keeps only selected fields on a row (mirrors TypeScript projectFields).
|
|
239
|
+
func projectFields(row map[string]interface{}, selectVal interface{}) map[string]interface{} {
|
|
240
|
+
if row == nil || selectVal == nil {
|
|
241
|
+
return row
|
|
242
|
+
}
|
|
243
|
+
projected := map[string]interface{}{}
|
|
244
|
+
truthy := func(k string, v interface{}) bool {
|
|
245
|
+
switch b := v.(type) {
|
|
246
|
+
case bool:
|
|
247
|
+
return b
|
|
248
|
+
case interface{}:
|
|
249
|
+
return b != nil
|
|
250
|
+
}
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
switch sel := selectVal.(type) {
|
|
254
|
+
case []string:
|
|
255
|
+
for _, k := range sel {
|
|
256
|
+
if v, ok := row[k]; ok {
|
|
257
|
+
projected[k] = v
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
case []interface{}:
|
|
261
|
+
for _, item := range sel {
|
|
262
|
+
if k, ok := item.(string); ok {
|
|
263
|
+
if v, had := row[k]; had {
|
|
264
|
+
projected[k] = v
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
case map[string]interface{}:
|
|
269
|
+
for k, v := range sel {
|
|
270
|
+
if truthy(k, v) {
|
|
271
|
+
projected[k] = row[k]
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
case map[string]bool:
|
|
275
|
+
for k, v := range sel {
|
|
276
|
+
if v {
|
|
277
|
+
projected[k] = row[k]
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
default:
|
|
281
|
+
return row
|
|
282
|
+
}
|
|
283
|
+
if rcount, ok := row["_count"]; ok {
|
|
284
|
+
projected["_count"] = rcount
|
|
285
|
+
}
|
|
286
|
+
return projected
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// FindMany fetches rows matching structured ORM args (where/orderBy/skip/take/select/include).
|
|
290
|
+
func (t *TableClient) FindMany(ctx context.Context, args *FindManyArgs) ([]map[string]interface{}, error) {
|
|
291
|
+
whereCond, queryArgs := t.buildWhere(args.Where)
|
|
292
|
+
order := t.orderSql(args.OrderBy)
|
|
293
|
+
take := args.Take
|
|
294
|
+
skip := args.Skip
|
|
295
|
+
hasSkip := skip != 0
|
|
296
|
+
|
|
297
|
+
cols := "*"
|
|
298
|
+
if keys, hasRel := selectedFields(args.Select); len(keys) > 0 && !hasRel {
|
|
299
|
+
quoted := make([]string, len(keys))
|
|
300
|
+
for i, k := range keys {
|
|
301
|
+
quoted[i] = base.QuoteIdentifier(k, t.Adapter.Dialect)
|
|
302
|
+
}
|
|
303
|
+
cols = strings.Join(quoted, ", ")
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
var query string
|
|
307
|
+
switch {
|
|
308
|
+
case take > 0 && !hasSkip:
|
|
309
|
+
if t.Adapter.Dialect == base.DialectPostgres || t.Adapter.Dialect == base.DialectSqlite {
|
|
310
|
+
query = fmt.Sprintf("SELECT %s FROM %s", cols, t.quotedTable())
|
|
311
|
+
if whereCond != "" {
|
|
312
|
+
query += " WHERE " + whereCond
|
|
313
|
+
}
|
|
314
|
+
if order != "" {
|
|
315
|
+
query += " " + order
|
|
316
|
+
}
|
|
317
|
+
query += fmt.Sprintf(" LIMIT %d", take)
|
|
318
|
+
} else {
|
|
319
|
+
query = fmt.Sprintf("SELECT TOP (%d) %s FROM %s%s", take, cols, t.quotedTable(), t.nolock())
|
|
320
|
+
if whereCond != "" {
|
|
321
|
+
query += " WHERE " + whereCond
|
|
322
|
+
}
|
|
323
|
+
if order != "" {
|
|
324
|
+
query += " " + order
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
case hasSkip:
|
|
328
|
+
if t.Adapter.Dialect == base.DialectPostgres || t.Adapter.Dialect == base.DialectSqlite {
|
|
329
|
+
query = fmt.Sprintf("SELECT %s FROM %s", cols, t.quotedTable())
|
|
330
|
+
if whereCond != "" {
|
|
331
|
+
query += " WHERE " + whereCond
|
|
332
|
+
}
|
|
333
|
+
if order != "" {
|
|
334
|
+
query += " " + order
|
|
335
|
+
}
|
|
336
|
+
if take > 0 {
|
|
337
|
+
query += fmt.Sprintf(" LIMIT %d", take)
|
|
338
|
+
} else {
|
|
339
|
+
query += " LIMIT ALL"
|
|
340
|
+
}
|
|
341
|
+
query += fmt.Sprintf(" OFFSET %d", skip)
|
|
342
|
+
} else {
|
|
343
|
+
query = fmt.Sprintf("SELECT %s FROM %s%s", cols, t.quotedTable(), t.nolock())
|
|
344
|
+
if whereCond != "" {
|
|
345
|
+
query += " WHERE " + whereCond
|
|
346
|
+
}
|
|
347
|
+
if order != "" {
|
|
348
|
+
query += " " + order
|
|
349
|
+
} else {
|
|
350
|
+
query += " ORDER BY (SELECT NULL)"
|
|
351
|
+
}
|
|
352
|
+
query += fmt.Sprintf(" OFFSET %d ROWS", skip)
|
|
353
|
+
if take > 0 {
|
|
354
|
+
query += fmt.Sprintf(" FETCH NEXT %d ROWS ONLY", take)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
default:
|
|
358
|
+
query = fmt.Sprintf("SELECT %s FROM %s%s", cols, t.quotedTable(), t.nolock())
|
|
359
|
+
if whereCond != "" {
|
|
360
|
+
query += " WHERE " + whereCond
|
|
361
|
+
}
|
|
362
|
+
if order != "" {
|
|
363
|
+
query += " " + order
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
rows, err := t.Adapter.QueryRaw(ctx, query, queryArgs...)
|
|
368
|
+
if err != nil {
|
|
369
|
+
return nil, err
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if args.Include != nil {
|
|
373
|
+
if err := resolveIncludes(ctx, t.TableName, rows, args.Include, t.Adapter); err != nil {
|
|
374
|
+
return nil, err
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if args.Select != nil {
|
|
378
|
+
if _, hasRel := selectedFields(args.Select); hasRel {
|
|
379
|
+
if err := resolveIncludes(ctx, t.TableName, rows, args.Select, t.Adapter); err != nil {
|
|
380
|
+
return nil, err
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
for i := range rows {
|
|
384
|
+
rows[i] = projectFields(rows[i], args.Select)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return rows, nil
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// FindFirst fetches the first row matching structured ORM args.
|
|
391
|
+
func (t *TableClient) FindFirst(ctx context.Context, args *FindManyArgs) (map[string]interface{}, error) {
|
|
392
|
+
if args == nil {
|
|
393
|
+
args = &FindManyArgs{}
|
|
394
|
+
}
|
|
395
|
+
copied := *args
|
|
396
|
+
copied.Take = 1
|
|
397
|
+
rows, err := t.FindMany(ctx, &copied)
|
|
398
|
+
if err != nil || len(rows) == 0 {
|
|
399
|
+
return nil, err
|
|
400
|
+
}
|
|
401
|
+
return rows[0], nil
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// FindUnique fetches a single row by its unique where criteria.
|
|
405
|
+
func (t *TableClient) FindUnique(ctx context.Context, args *FindManyArgs) (map[string]interface{}, error) {
|
|
406
|
+
return t.FindFirst(ctx, args)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Count returns total row count matching structured where args.
|
|
410
|
+
func (t *TableClient) Count(ctx context.Context, args *CountArgs) (int64, error) {
|
|
411
|
+
var whereCond string
|
|
412
|
+
var queryArgs []interface{}
|
|
413
|
+
if args != nil {
|
|
414
|
+
whereCond, queryArgs = t.buildWhere(args.Where)
|
|
415
|
+
}
|
|
416
|
+
query := fmt.Sprintf("SELECT COUNT(*) AS cnt FROM %s%s", t.quotedTable(), t.nolock())
|
|
417
|
+
if whereCond != "" {
|
|
418
|
+
query += " WHERE " + whereCond
|
|
419
|
+
}
|
|
420
|
+
rows, err := t.Adapter.QueryRaw(ctx, query, queryArgs...)
|
|
421
|
+
if err != nil || len(rows) == 0 {
|
|
422
|
+
return 0, err
|
|
423
|
+
}
|
|
424
|
+
if cnt, ok := rows[0]["cnt"].(int64); ok {
|
|
425
|
+
return cnt, nil
|
|
426
|
+
}
|
|
427
|
+
if f, ok := rows[0]["cnt"].(float64); ok {
|
|
428
|
+
return int64(f), nil
|
|
429
|
+
}
|
|
430
|
+
return 0, nil
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// generateUUID returns a random UUID v4 string.
|
|
434
|
+
func generateUUID() string {
|
|
435
|
+
b := make([]byte, 16)
|
|
436
|
+
_, _ = rand.Read(b)
|
|
437
|
+
b[6] = (b[6] & 0x0f) | 0x40
|
|
438
|
+
b[8] = (b[8] & 0x3f) | 0x80
|
|
439
|
+
h := hex.EncodeToString(b)
|
|
440
|
+
return h[0:8] + "-" + h[8:12] + "-" + h[12:16] + "-" + h[16:20] + "-" + h[20:32]
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// splitRelationWrites separates relation-write objects from scalar data using model metadata.
|
|
444
|
+
func splitRelationWrites(modelName string, data map[string]interface{}) (scalars map[string]interface{}, rels map[string]interface{}) {
|
|
445
|
+
scalars = map[string]interface{}{}
|
|
446
|
+
rels = map[string]interface{}{}
|
|
447
|
+
relations := base.GetRelationsForModel(modelName)
|
|
448
|
+
for k, v := range data {
|
|
449
|
+
if _, isRel := relations[k]; isRel {
|
|
450
|
+
rels[k] = v
|
|
451
|
+
} else {
|
|
452
|
+
scalars[k] = v
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return scalars, rels
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Create inserts one map row and returns the created record.
|
|
459
|
+
func (t *TableClient) Create(ctx context.Context, args *CreateArgs) (map[string]interface{}, error) {
|
|
460
|
+
if args == nil || len(args.Data) == 0 {
|
|
461
|
+
return nil, fmt.Errorf("create requires at least one field")
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
fields, _ := base.GetFieldsForModel(t.TableName).(map[string]interface{})
|
|
465
|
+
idFieldName := ""
|
|
466
|
+
if fields != nil {
|
|
467
|
+
if _, has := fields["id"]; has {
|
|
468
|
+
idFieldName = "id"
|
|
469
|
+
} else {
|
|
470
|
+
for name := range fields {
|
|
471
|
+
if strings.HasSuffix(name, "_id") || strings.HasSuffix(name, "Id") || strings.ToLower(name) == "id" {
|
|
472
|
+
idFieldName = name
|
|
473
|
+
break
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if fdef, has := fields[idFieldName]; has && fields != nil {
|
|
479
|
+
rawType := ""
|
|
480
|
+
switch ft := fdef.(type) {
|
|
481
|
+
case string:
|
|
482
|
+
rawType = ft
|
|
483
|
+
case map[string]interface{}:
|
|
484
|
+
for _, key := range []string{"ts", "sql", "type"} {
|
|
485
|
+
if s, ok := ft[key].(string); ok {
|
|
486
|
+
rawType = s
|
|
487
|
+
break
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
normalized := strings.ToLower(rawType)
|
|
492
|
+
isStringType := normalized == "" || strings.Contains(normalized, "string") || strings.Contains(normalized, "uuid") || strings.Contains(normalized, "uniqueidentifier") || strings.Contains(normalized, "nvarchar") || strings.Contains(normalized, "varchar") || strings.Contains(normalized, "text")
|
|
493
|
+
if idFieldName != "" && isStringType {
|
|
494
|
+
if _, present := args.Data[idFieldName]; !present || args.Data[idFieldName] == nil {
|
|
495
|
+
args.Data[idFieldName] = generateUUID()
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
data, relationWrites := splitRelationWrites(t.TableName, args.Data)
|
|
501
|
+
|
|
502
|
+
var cols []string
|
|
503
|
+
var vals []string
|
|
504
|
+
var values []interface{}
|
|
505
|
+
for col, val := range data {
|
|
506
|
+
cols = append(cols, base.QuoteIdentifier(col, t.Adapter.Dialect))
|
|
507
|
+
vals = append(vals, placeholder(t.Adapter.Dialect, len(values)+1))
|
|
508
|
+
values = append(values, val)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if len(cols) > 0 {
|
|
512
|
+
query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)",
|
|
513
|
+
t.quotedTable(), strings.Join(cols, ", "), strings.Join(vals, ", "))
|
|
514
|
+
if _, err := t.Adapter.ExecuteRaw(ctx, query, values...); err != nil {
|
|
515
|
+
return nil, err
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
where := map[string]interface{}{}
|
|
520
|
+
if idFieldName != "" && args.Data[idFieldName] != nil {
|
|
521
|
+
where[idFieldName] = args.Data[idFieldName]
|
|
522
|
+
} else {
|
|
523
|
+
where = data
|
|
524
|
+
}
|
|
525
|
+
created, err := t.FindFirst(ctx, &FindManyArgs{Where: where})
|
|
526
|
+
if err != nil || created == nil {
|
|
527
|
+
return data, err
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
for relKey, relWrite := range relationWrites {
|
|
531
|
+
relations := base.GetRelationsForModel(t.TableName)
|
|
532
|
+
relation, ok := relations[relKey]
|
|
533
|
+
if !ok {
|
|
534
|
+
continue
|
|
535
|
+
}
|
|
536
|
+
rw, ok := relWrite.(map[string]interface{})
|
|
537
|
+
if !ok {
|
|
538
|
+
continue
|
|
539
|
+
}
|
|
540
|
+
if createVal, has := rw["create"]; has {
|
|
541
|
+
childClient := t.Adapter.Table(relation.ModelName)
|
|
542
|
+
items := normalizeItemList(createVal)
|
|
543
|
+
for _, item := range items {
|
|
544
|
+
if m, ok := item.(map[string]interface{}); ok && m != nil {
|
|
545
|
+
merged := map[string]interface{}{}
|
|
546
|
+
for k, v := range m {
|
|
547
|
+
merged[k] = v
|
|
548
|
+
}
|
|
549
|
+
if local, ok := created[relation.LocalKey]; ok {
|
|
550
|
+
merged[relation.ForeignKey] = local
|
|
551
|
+
}
|
|
552
|
+
if _, err := childClient.Create(ctx, &CreateArgs{Data: merged}); err != nil {
|
|
553
|
+
return nil, err
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if args.Include != nil {
|
|
561
|
+
if err := resolveIncludes(ctx, t.TableName, []map[string]interface{}{created}, args.Include, t.Adapter); err != nil {
|
|
562
|
+
return nil, err
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if args.Select != nil {
|
|
566
|
+
if err := resolveIncludes(ctx, t.TableName, []map[string]interface{}{created}, args.Select, t.Adapter); err != nil {
|
|
567
|
+
return nil, err
|
|
568
|
+
}
|
|
569
|
+
return projectFields(created, args.Select), nil
|
|
570
|
+
}
|
|
571
|
+
return created, nil
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// normalizeItemList accepts a single map or []interface{} and returns a flat []interface{}.
|
|
575
|
+
func normalizeItemList(value interface{}) []interface{} {
|
|
576
|
+
switch v := value.(type) {
|
|
577
|
+
case []interface{}:
|
|
578
|
+
return v
|
|
579
|
+
case []map[string]interface{}:
|
|
580
|
+
out := make([]interface{}, len(v))
|
|
581
|
+
for i, m := range v {
|
|
582
|
+
out[i] = m
|
|
583
|
+
}
|
|
584
|
+
return out
|
|
585
|
+
case map[string]interface{}:
|
|
586
|
+
return []interface{}{v}
|
|
587
|
+
}
|
|
588
|
+
return nil
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// CreateMany inserts rows one by one and optionally skips duplicate errors.
|
|
592
|
+
func (t *TableClient) CreateMany(ctx context.Context, args *CreateManyArgs) (map[string]interface{}, error) {
|
|
593
|
+
if args == nil {
|
|
594
|
+
args = &CreateManyArgs{}
|
|
595
|
+
}
|
|
596
|
+
var count int64
|
|
597
|
+
for _, row := range args.Data {
|
|
598
|
+
_, err := t.Create(ctx, &CreateArgs{Data: row})
|
|
599
|
+
if err != nil {
|
|
600
|
+
if args.SkipDuplicates {
|
|
601
|
+
continue
|
|
602
|
+
}
|
|
603
|
+
return map[string]interface{}{"count": count}, err
|
|
604
|
+
}
|
|
605
|
+
count++
|
|
606
|
+
}
|
|
607
|
+
return map[string]interface{}{"count": count}, nil
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// appendUpdateSet handles set expressions including {increment,decrement,multiply,divide,set} field ops.
|
|
611
|
+
func appendUpdateSet(setParts *[]string, values *[]interface{}, col string, val interface{}, dialect base.Dialect) {
|
|
612
|
+
quoted := base.QuoteIdentifier(col, dialect)
|
|
613
|
+
if m, ok := val.(map[string]interface{}); ok {
|
|
614
|
+
for _, op := range []string{"increment", "decrement", "multiply", "divide", "set"} {
|
|
615
|
+
if opval, has := m[op]; has {
|
|
616
|
+
*values = append(*values, opval)
|
|
617
|
+
p := placeholder(dialect, len(*values))
|
|
618
|
+
switch op {
|
|
619
|
+
case "increment":
|
|
620
|
+
*setParts = append(*setParts, fmt.Sprintf("%s = %s + %s", quoted, quoted, p))
|
|
621
|
+
case "decrement":
|
|
622
|
+
*setParts = append(*setParts, fmt.Sprintf("%s = %s - %s", quoted, quoted, p))
|
|
623
|
+
case "multiply":
|
|
624
|
+
*setParts = append(*setParts, fmt.Sprintf("%s = %s * %s", quoted, quoted, p))
|
|
625
|
+
case "divide":
|
|
626
|
+
*setParts = append(*setParts, fmt.Sprintf("%s = %s / %s", quoted, quoted, p))
|
|
627
|
+
case "set":
|
|
628
|
+
*setParts = append(*setParts, fmt.Sprintf("%s = %s", quoted, p))
|
|
629
|
+
}
|
|
630
|
+
return
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
*values = append(*values, val)
|
|
635
|
+
*setParts = append(*setParts, fmt.Sprintf("%s = %s", quoted, placeholder(dialect, len(*values))))
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Update updates rows matching where criteria and returns the first updated record.
|
|
639
|
+
func (t *TableClient) Update(ctx context.Context, args *UpdateArgs) (map[string]interface{}, error) {
|
|
640
|
+
if args == nil {
|
|
641
|
+
return nil, fmt.Errorf("update requires args")
|
|
642
|
+
}
|
|
643
|
+
data, relationWrites := splitRelationWrites(t.TableName, args.Data)
|
|
644
|
+
|
|
645
|
+
whereCond, whereArgs := t.buildWhere(args.Where)
|
|
646
|
+
setParts := []string{}
|
|
647
|
+
setValues := []interface{}{}
|
|
648
|
+
for col, val := range data {
|
|
649
|
+
appendUpdateSet(&setParts, &setValues, col, val, t.Adapter.Dialect)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if len(setParts) > 0 {
|
|
653
|
+
query := fmt.Sprintf("UPDATE %s SET %s", t.quotedTable(), strings.Join(setParts, ", "))
|
|
654
|
+
if whereCond != "" {
|
|
655
|
+
query += " WHERE " + whereCond
|
|
656
|
+
}
|
|
657
|
+
allValues := append(setValues, whereArgs...)
|
|
658
|
+
if _, err := t.Adapter.ExecuteRaw(ctx, query, allValues...); err != nil {
|
|
659
|
+
return nil, err
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
updated, err := t.FindFirst(ctx, &FindManyArgs{Where: args.Where})
|
|
664
|
+
if err != nil || updated == nil {
|
|
665
|
+
return nil, err
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
for relKey, relWrite := range relationWrites {
|
|
669
|
+
relations := base.GetRelationsForModel(t.TableName)
|
|
670
|
+
relation, ok := relations[relKey]
|
|
671
|
+
if !ok {
|
|
672
|
+
continue
|
|
673
|
+
}
|
|
674
|
+
rw, ok := relWrite.(map[string]interface{})
|
|
675
|
+
if !ok {
|
|
676
|
+
continue
|
|
677
|
+
}
|
|
678
|
+
childClient := t.Adapter.Table(relation.ModelName)
|
|
679
|
+
if createVal, has := rw["create"]; has {
|
|
680
|
+
for _, item := range normalizeItemList(createVal) {
|
|
681
|
+
if m, ok := item.(map[string]interface{}); ok && m != nil {
|
|
682
|
+
merged := map[string]interface{}{}
|
|
683
|
+
for k, v := range m {
|
|
684
|
+
merged[k] = v
|
|
685
|
+
}
|
|
686
|
+
if local, ok := updated[relation.LocalKey]; ok {
|
|
687
|
+
merged[relation.ForeignKey] = local
|
|
688
|
+
}
|
|
689
|
+
if _, err := childClient.Create(ctx, &CreateArgs{Data: merged}); err != nil {
|
|
690
|
+
return nil, err
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if updateVal, has := rw["update"]; has {
|
|
696
|
+
if m, ok := updateVal.(map[string]interface{}); ok && m != nil {
|
|
697
|
+
if _, err := childClient.Update(ctx, &UpdateArgs{Where: m["where"], Data: m["data"].(map[string]interface{})}); err != nil {
|
|
698
|
+
return nil, err
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if disconnectVal, has := rw["disconnect"]; has {
|
|
703
|
+
if m, ok := disconnectVal.(map[string]interface{}); ok && m != nil {
|
|
704
|
+
if _, err := childClient.UpdateMany(ctx, &UpdateManyArgs{Where: m, Data: map[string]interface{}{relation.ForeignKey: nil}}); err != nil {
|
|
705
|
+
return nil, err
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if args.Include != nil {
|
|
712
|
+
if err := resolveIncludes(ctx, t.TableName, []map[string]interface{}{updated}, args.Include, t.Adapter); err != nil {
|
|
713
|
+
return nil, err
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if args.Select != nil {
|
|
717
|
+
if err := resolveIncludes(ctx, t.TableName, []map[string]interface{}{updated}, args.Select, t.Adapter); err != nil {
|
|
718
|
+
return nil, err
|
|
719
|
+
}
|
|
720
|
+
return projectFields(updated, args.Select), nil
|
|
721
|
+
}
|
|
722
|
+
return updated, nil
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// UpdateMany updates rows matching where criteria and returns affected count.
|
|
726
|
+
func (t *TableClient) UpdateMany(ctx context.Context, args *UpdateManyArgs) (map[string]interface{}, error) {
|
|
727
|
+
if args == nil || len(args.Data) == 0 {
|
|
728
|
+
return map[string]interface{}{"count": 0}, nil
|
|
729
|
+
}
|
|
730
|
+
whereCond, whereArgs := t.buildWhere(args.Where)
|
|
731
|
+
setParts := []string{}
|
|
732
|
+
setValues := []interface{}{}
|
|
733
|
+
for col, val := range args.Data {
|
|
734
|
+
appendUpdateSet(&setParts, &setValues, col, val, t.Adapter.Dialect)
|
|
735
|
+
}
|
|
736
|
+
if len(setParts) == 0 {
|
|
737
|
+
return map[string]interface{}{"count": 0}, nil
|
|
738
|
+
}
|
|
739
|
+
query := fmt.Sprintf("UPDATE %s SET %s", t.quotedTable(), strings.Join(setParts, ", "))
|
|
740
|
+
if whereCond != "" {
|
|
741
|
+
query += " WHERE " + whereCond
|
|
742
|
+
}
|
|
743
|
+
allValues := append(setValues, whereArgs...)
|
|
744
|
+
affected, err := t.Adapter.ExecuteRaw(ctx, query, allValues...)
|
|
745
|
+
if err != nil {
|
|
746
|
+
return nil, err
|
|
747
|
+
}
|
|
748
|
+
return map[string]interface{}{"count": affected}, nil
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// DeleteMany deletes rows matching where criteria and returns affected count.
|
|
752
|
+
func (t *TableClient) DeleteMany(ctx context.Context, args *DeleteManyArgs) (map[string]interface{}, error) {
|
|
753
|
+
whereCond, queryArgs := t.buildWhere(args.Where)
|
|
754
|
+
query := fmt.Sprintf("DELETE FROM %s", t.quotedTable())
|
|
755
|
+
if whereCond != "" {
|
|
756
|
+
query += " WHERE " + whereCond
|
|
757
|
+
}
|
|
758
|
+
affected, err := t.Adapter.ExecuteRaw(ctx, query, queryArgs...)
|
|
759
|
+
if err != nil {
|
|
760
|
+
return nil, err
|
|
761
|
+
}
|
|
762
|
+
return map[string]interface{}{"count": affected}, nil
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Delete deletes one record matching where criteria and returns the deleted record.
|
|
766
|
+
func (t *TableClient) Delete(ctx context.Context, args *DeleteArgs) (map[string]interface{}, error) {
|
|
767
|
+
if args == nil {
|
|
768
|
+
return nil, fmt.Errorf("delete requires where")
|
|
769
|
+
}
|
|
770
|
+
existing, err := t.FindFirst(ctx, &FindManyArgs{Where: args.Where})
|
|
771
|
+
if err != nil {
|
|
772
|
+
return nil, err
|
|
773
|
+
}
|
|
774
|
+
whereCond, queryArgs := t.buildWhere(args.Where)
|
|
775
|
+
query := fmt.Sprintf("DELETE FROM %s WHERE %s", t.quotedTable(), whereCond)
|
|
776
|
+
affected, err := t.Adapter.ExecuteRaw(ctx, query, queryArgs...)
|
|
777
|
+
if err != nil {
|
|
778
|
+
return nil, err
|
|
779
|
+
}
|
|
780
|
+
boolish := affected > 0
|
|
781
|
+
if !boolish {
|
|
782
|
+
return nil, nil
|
|
783
|
+
}
|
|
784
|
+
return existing, nil
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Upsert creates a record if none matches where, otherwise updates it.
|
|
788
|
+
func (t *TableClient) Upsert(ctx context.Context, args *UpsertArgs) (map[string]interface{}, error) {
|
|
789
|
+
if args == nil {
|
|
790
|
+
return nil, fmt.Errorf("upsert requires args")
|
|
791
|
+
}
|
|
792
|
+
existing, err := t.FindFirst(ctx, &FindManyArgs{Where: args.Where})
|
|
793
|
+
if err != nil {
|
|
794
|
+
return nil, err
|
|
795
|
+
}
|
|
796
|
+
if existing != nil {
|
|
797
|
+
return t.Update(ctx, &UpdateArgs{Where: args.Where, Data: args.Update})
|
|
798
|
+
}
|
|
799
|
+
return t.Create(ctx, &CreateArgs{Data: args.Create})
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// AggregateOptions defines aggregation expressions keyed by output alias.
|
|
803
|
+
// Deprecated: use AggregateArgs with _count/_sum/_avg/_min/_max keys.
|
|
804
|
+
type AggregateOptions map[string]string
|
|
805
|
+
|
|
806
|
+
// Aggregate executes aggregation queries (SUM, AVG, MIN, MAX, COUNT) and returns a single row result.
|
|
807
|
+
func (t *TableClient) Aggregate(ctx context.Context, args *AggregateArgs) (map[string]interface{}, error) {
|
|
808
|
+
if args == nil {
|
|
809
|
+
args = &AggregateArgs{}
|
|
810
|
+
}
|
|
811
|
+
whereCond, queryArgs := t.buildWhere(args.Where)
|
|
812
|
+
var aggs []string
|
|
813
|
+
|
|
814
|
+
if args.Count != nil {
|
|
815
|
+
countFields := selectedAggregateFields(args.Count)
|
|
816
|
+
if len(countFields) == 0 {
|
|
817
|
+
aggs = append(aggs, "COUNT(*) AS cnt_all")
|
|
818
|
+
} else {
|
|
819
|
+
for _, f := range countFields {
|
|
820
|
+
if f != "_all" {
|
|
821
|
+
aggs = append(aggs, fmt.Sprintf("COUNT(%s) AS cnt_%s", base.QuoteIdentifier(f, t.Adapter.Dialect), sanitizeParamName(f)))
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
for _, aggTuple := range []struct {
|
|
827
|
+
kind string
|
|
828
|
+
value interface{}
|
|
829
|
+
}{
|
|
830
|
+
{"SUM", args.Sum},
|
|
831
|
+
{"AVG", args.Avg},
|
|
832
|
+
{"MIN", args.Min},
|
|
833
|
+
{"MAX", args.Max},
|
|
834
|
+
} {
|
|
835
|
+
for _, f := range selectedAggregateFields(aggTuple.value) {
|
|
836
|
+
aggs = append(aggs, fmt.Sprintf("%s(%s) AS %s_%s", aggTuple.kind, base.QuoteIdentifier(f, t.Adapter.Dialect), strings.ToLower(aggTuple.kind), sanitizeParamName(f)))
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if len(aggs) == 0 {
|
|
841
|
+
return nil, fmt.Errorf("aggregate requires at least one aggregator field")
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
query := fmt.Sprintf("SELECT %s FROM %s%s", strings.Join(aggs, ", "), t.quotedTable(), t.nolock())
|
|
845
|
+
if whereCond != "" {
|
|
846
|
+
query += " WHERE " + whereCond
|
|
847
|
+
}
|
|
848
|
+
rows, err := t.Adapter.QueryRaw(ctx, query, queryArgs...)
|
|
849
|
+
if err != nil {
|
|
850
|
+
return nil, err
|
|
851
|
+
}
|
|
852
|
+
if len(rows) == 0 {
|
|
853
|
+
return map[string]interface{}{}, nil
|
|
854
|
+
}
|
|
855
|
+
row := rows[0]
|
|
856
|
+
|
|
857
|
+
result := map[string]interface{}{}
|
|
858
|
+
if args.Count != nil {
|
|
859
|
+
countFields := selectedAggregateFields(args.Count)
|
|
860
|
+
allCount := numberOrZero(row["cnt_all"], row["_count"])
|
|
861
|
+
result["_count"] = map[string]interface{}{"_all": allCount}
|
|
862
|
+
for _, f := range countFields {
|
|
863
|
+
if f == "_all" {
|
|
864
|
+
continue
|
|
865
|
+
}
|
|
866
|
+
key := "cnt_" + sanitizeParamName(f)
|
|
867
|
+
if v, ok := row[key]; ok && v != nil {
|
|
868
|
+
result["_count"].(map[string]interface{})[f] = numberOrZero(v)
|
|
869
|
+
} else {
|
|
870
|
+
result["_count"].(map[string]interface{})[f] = allCount
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
for _, aggTuple := range []struct {
|
|
875
|
+
kind string
|
|
876
|
+
value interface{}
|
|
877
|
+
}{
|
|
878
|
+
{"sum", args.Sum},
|
|
879
|
+
{"avg", args.Avg},
|
|
880
|
+
{"min", args.Min},
|
|
881
|
+
{"max", args.Max},
|
|
882
|
+
} {
|
|
883
|
+
fields := selectedAggregateFields(aggTuple.value)
|
|
884
|
+
if len(fields) == 0 {
|
|
885
|
+
continue
|
|
886
|
+
}
|
|
887
|
+
sub := map[string]interface{}{}
|
|
888
|
+
for _, f := range fields {
|
|
889
|
+
key := aggTuple.kind + "_" + sanitizeParamName(f)
|
|
890
|
+
if v, ok := row[key]; ok && v != nil {
|
|
891
|
+
sub[f] = numberOrNil(v)
|
|
892
|
+
} else {
|
|
893
|
+
sub[f] = nil
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
result["_"+aggTuple.kind] = sub
|
|
897
|
+
}
|
|
898
|
+
return result, nil
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// GroupBy executes a GROUP BY query and returns grouped result rows.
|
|
902
|
+
func (t *TableClient) GroupBy(ctx context.Context, args *GroupByArgs) ([]map[string]interface{}, error) {
|
|
903
|
+
if args == nil {
|
|
904
|
+
args = &GroupByArgs{}
|
|
905
|
+
}
|
|
906
|
+
byFields := normalizeByFields(args.By)
|
|
907
|
+
if len(byFields) == 0 {
|
|
908
|
+
return nil, fmt.Errorf("groupBy requires at least one group field")
|
|
909
|
+
}
|
|
910
|
+
whereCond, queryArgs := t.buildWhere(args.Where)
|
|
911
|
+
|
|
912
|
+
quotedGroups := make([]string, len(byFields))
|
|
913
|
+
for i, f := range byFields {
|
|
914
|
+
quotedGroups[i] = base.QuoteIdentifier(f, t.Adapter.Dialect)
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
aggs := []string{"COUNT(*) AS _count"}
|
|
918
|
+
for _, aggTuple := range []struct {
|
|
919
|
+
kind string
|
|
920
|
+
value interface{}
|
|
921
|
+
}{
|
|
922
|
+
{"SUM", args.Sum},
|
|
923
|
+
{"AVG", args.Avg},
|
|
924
|
+
{"MIN", args.Min},
|
|
925
|
+
{"MAX", args.Max},
|
|
926
|
+
} {
|
|
927
|
+
for _, f := range selectedAggregateFields(aggTuple.value) {
|
|
928
|
+
aggs = append(aggs, fmt.Sprintf("%s(%s) AS %s_%s", aggTuple.kind, base.QuoteIdentifier(f, t.Adapter.Dialect), strings.ToLower(aggTuple.kind), sanitizeParamName(f)))
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
query := fmt.Sprintf("SELECT %s, %s FROM %s",
|
|
933
|
+
strings.Join(quotedGroups, ", "), strings.Join(aggs, ", "), t.quotedTable())
|
|
934
|
+
if whereCond != "" {
|
|
935
|
+
query += " WHERE " + whereCond
|
|
936
|
+
}
|
|
937
|
+
query += " GROUP BY " + strings.Join(quotedGroups, ", ")
|
|
938
|
+
|
|
939
|
+
order := t.orderSql(args.OrderBy)
|
|
940
|
+
hasSkip := args.Skip != 0
|
|
941
|
+
hasTake := args.Take > 0
|
|
942
|
+
if order != "" {
|
|
943
|
+
query += " " + order
|
|
944
|
+
} else if hasSkip || hasTake {
|
|
945
|
+
query += " ORDER BY " + strings.Join(quotedGroups, ", ")
|
|
946
|
+
}
|
|
947
|
+
if hasSkip || hasTake {
|
|
948
|
+
skip := args.Skip
|
|
949
|
+
take := args.Take
|
|
950
|
+
if take <= 0 {
|
|
951
|
+
take = 1
|
|
952
|
+
}
|
|
953
|
+
if t.Adapter.Dialect == base.DialectPostgres || t.Adapter.Dialect == base.DialectSqlite {
|
|
954
|
+
if hasTake {
|
|
955
|
+
query += fmt.Sprintf(" LIMIT %d", take)
|
|
956
|
+
}
|
|
957
|
+
query += fmt.Sprintf(" OFFSET %d", skip)
|
|
958
|
+
} else {
|
|
959
|
+
query += fmt.Sprintf(" OFFSET %d ROWS", skip)
|
|
960
|
+
if hasTake {
|
|
961
|
+
query += fmt.Sprintf(" FETCH NEXT %d ROWS ONLY", take)
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
return t.Adapter.QueryRaw(ctx, query, queryArgs...)
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// VectorSearch ranks table rows by vector distance using native SQL or fallback.
|
|
970
|
+
func (t *TableClient) VectorSearch(ctx context.Context, args *VectorSearchArgs) ([]map[string]interface{}, error) {
|
|
971
|
+
if args == nil {
|
|
972
|
+
args = &VectorSearchArgs{}
|
|
973
|
+
}
|
|
974
|
+
take := args.Take
|
|
975
|
+
if take <= 0 {
|
|
976
|
+
take = 10
|
|
977
|
+
}
|
|
978
|
+
vectorField := args.VectorField
|
|
979
|
+
if vectorField == "" {
|
|
980
|
+
vectorField = "embedding"
|
|
981
|
+
}
|
|
982
|
+
distanceMetric := args.DistanceMetric
|
|
983
|
+
if distanceMetric == "" {
|
|
984
|
+
distanceMetric = "cosine"
|
|
985
|
+
}
|
|
986
|
+
whereCond, queryArgs := t.buildWhere(args.Where)
|
|
987
|
+
_ = queryArgs
|
|
988
|
+
|
|
989
|
+
clause := ""
|
|
990
|
+
if whereCond != "" {
|
|
991
|
+
clause = whereCond
|
|
992
|
+
}
|
|
993
|
+
return t.Adapter.VectorSearch(ctx, t.TableName, args.Vector, take, clause, vectorField, distanceMetric, queryArgs...)
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// rename function resolves include object against a set of rows using relation metadata.
|
|
997
|
+
func resolveIncludes(ctx context.Context, modelName string, rows []map[string]interface{}, include interface{}, adapter *An5Adapter) error {
|
|
998
|
+
if len(rows) == 0 || include == nil {
|
|
999
|
+
return nil
|
|
1000
|
+
}
|
|
1001
|
+
modelRelations := base.GetRelationsForModel(modelName)
|
|
1002
|
+
|
|
1003
|
+
includeMap := include
|
|
1004
|
+
switch v := include.(type) {
|
|
1005
|
+
case map[string]interface{}:
|
|
1006
|
+
includeMap = v
|
|
1007
|
+
case map[string]bool:
|
|
1008
|
+
m := map[string]interface{}{}
|
|
1009
|
+
for k, b := range v {
|
|
1010
|
+
m[k] = b
|
|
1011
|
+
}
|
|
1012
|
+
includeMap = m
|
|
1013
|
+
default:
|
|
1014
|
+
return nil
|
|
1015
|
+
}
|
|
1016
|
+
im := includeMap.(map[string]interface{})
|
|
1017
|
+
|
|
1018
|
+
keys := make([]string, 0, len(im))
|
|
1019
|
+
for k := range im {
|
|
1020
|
+
keys = append(keys, k)
|
|
1021
|
+
}
|
|
1022
|
+
sort.Strings(keys)
|
|
1023
|
+
|
|
1024
|
+
for _, key := range keys {
|
|
1025
|
+
val := im[key]
|
|
1026
|
+
if val == nil {
|
|
1027
|
+
continue
|
|
1028
|
+
}
|
|
1029
|
+
if b, ok := val.(bool); ok && !b {
|
|
1030
|
+
continue
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if key == "_count" {
|
|
1034
|
+
for _, row := range rows {
|
|
1035
|
+
if row["_count"] == nil {
|
|
1036
|
+
row["_count"] = map[string]interface{}{}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
for relKey, relation := range modelRelations {
|
|
1040
|
+
if relation.RelationType != "many" {
|
|
1041
|
+
continue
|
|
1042
|
+
}
|
|
1043
|
+
uniqueKeys := uniqueSet(rows, relation.LocalKey)
|
|
1044
|
+
if len(uniqueKeys) > 0 {
|
|
1045
|
+
relClient := adapter.Table(relation.ModelName)
|
|
1046
|
+
related, err := relClient.FindMany(ctx, &FindManyArgs{
|
|
1047
|
+
Where: map[string]interface{}{relation.ForeignKey: map[string]interface{}{"in": uniqueKeys}},
|
|
1048
|
+
})
|
|
1049
|
+
if err != nil {
|
|
1050
|
+
return err
|
|
1051
|
+
}
|
|
1052
|
+
countMap := map[interface{}]int{}
|
|
1053
|
+
for _, r := range related {
|
|
1054
|
+
k := r[relation.ForeignKey]
|
|
1055
|
+
countMap[k]++
|
|
1056
|
+
}
|
|
1057
|
+
for _, row := range rows {
|
|
1058
|
+
k := row[relation.LocalKey]
|
|
1059
|
+
c := 0
|
|
1060
|
+
if n, ok := countMap[k]; ok {
|
|
1061
|
+
c = n
|
|
1062
|
+
}
|
|
1063
|
+
row["_count"].(map[string]interface{})[relKey] = c
|
|
1064
|
+
}
|
|
1065
|
+
} else {
|
|
1066
|
+
for _, row := range rows {
|
|
1067
|
+
row["_count"].(map[string]interface{})[relKey] = 0
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
continue
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
relation, ok := modelRelations[key]
|
|
1075
|
+
if !ok {
|
|
1076
|
+
continue
|
|
1077
|
+
}
|
|
1078
|
+
isMany := relation.RelationType == "many"
|
|
1079
|
+
joinKey := relation.LocalKey
|
|
1080
|
+
matchKey := relation.ForeignKey
|
|
1081
|
+
if isMany {
|
|
1082
|
+
joinKey = relation.LocalKey
|
|
1083
|
+
matchKey = relation.ForeignKey
|
|
1084
|
+
} else {
|
|
1085
|
+
joinKey = relation.ForeignKey
|
|
1086
|
+
matchKey = relation.LocalKey
|
|
1087
|
+
}
|
|
1088
|
+
uniqueKeys := uniqueSet(rows, joinKey)
|
|
1089
|
+
if len(uniqueKeys) == 0 {
|
|
1090
|
+
for _, row := range rows {
|
|
1091
|
+
if isMany {
|
|
1092
|
+
row[key] = []map[string]interface{}{}
|
|
1093
|
+
} else {
|
|
1094
|
+
row[key] = nil
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
continue
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
subArgs := map[string]interface{}{}
|
|
1101
|
+
if m, ok := val.(map[string]interface{}); ok && m != nil {
|
|
1102
|
+
subArgs = m
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
subWhere := map[string]interface{}{
|
|
1106
|
+
matchKey: map[string]interface{}{"in": uniqueKeys},
|
|
1107
|
+
}
|
|
1108
|
+
if w, has := subArgs["where"]; has {
|
|
1109
|
+
if wm, ok := w.(map[string]interface{}); ok {
|
|
1110
|
+
for k, v := range wm {
|
|
1111
|
+
subWhere[k] = v
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
relClient := adapter.Table(relation.ModelName)
|
|
1117
|
+
related, err := relClient.FindMany(ctx, &FindManyArgs{
|
|
1118
|
+
Where: subWhere,
|
|
1119
|
+
OrderBy: subArgs["orderBy"],
|
|
1120
|
+
Take: toPositiveInt(subArgs["take"]),
|
|
1121
|
+
Skip: toPositiveInt(subArgs["skip"]),
|
|
1122
|
+
})
|
|
1123
|
+
if err != nil {
|
|
1124
|
+
return err
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
if nested, has := subArgs["include"]; has {
|
|
1128
|
+
if err := resolveIncludes(ctx, relation.ModelName, related, nested, adapter); err != nil {
|
|
1129
|
+
return err
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
outputRows := related
|
|
1134
|
+
if sel, has := subArgs["select"]; has {
|
|
1135
|
+
outputRows = make([]map[string]interface{}, len(related))
|
|
1136
|
+
for i, r := range related {
|
|
1137
|
+
outputRows[i] = projectFields(r, sel)
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
groupMap := map[interface{}][]map[string]interface{}{}
|
|
1142
|
+
for i, r := range related {
|
|
1143
|
+
var k interface{}
|
|
1144
|
+
if isMany {
|
|
1145
|
+
k = r[relation.ForeignKey]
|
|
1146
|
+
} else {
|
|
1147
|
+
k = r[relation.LocalKey]
|
|
1148
|
+
}
|
|
1149
|
+
groupMap[k] = append(groupMap[k], outputRows[i])
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
for _, row := range rows {
|
|
1153
|
+
var k interface{}
|
|
1154
|
+
if isMany {
|
|
1155
|
+
k = row[relation.LocalKey]
|
|
1156
|
+
} else {
|
|
1157
|
+
k = row[relation.ForeignKey]
|
|
1158
|
+
}
|
|
1159
|
+
matches := groupMap[k]
|
|
1160
|
+
if matches == nil {
|
|
1161
|
+
matches = []map[string]interface{}{}
|
|
1162
|
+
}
|
|
1163
|
+
if isMany {
|
|
1164
|
+
row[key] = matches
|
|
1165
|
+
} else if len(matches) > 0 {
|
|
1166
|
+
row[key] = matches[0]
|
|
1167
|
+
} else {
|
|
1168
|
+
row[key] = nil
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return nil
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
func uniqueSet(rows []map[string]interface{}, key string) []interface{} {
|
|
1176
|
+
seen := map[interface{}]bool{}
|
|
1177
|
+
var out []interface{}
|
|
1178
|
+
for _, r := range rows {
|
|
1179
|
+
v := r[key]
|
|
1180
|
+
if v == nil {
|
|
1181
|
+
continue
|
|
1182
|
+
}
|
|
1183
|
+
if !seen[v] {
|
|
1184
|
+
seen[v] = true
|
|
1185
|
+
out = append(out, v)
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
return out
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
func toPositiveInt(v interface{}) int {
|
|
1192
|
+
switch n := v.(type) {
|
|
1193
|
+
case int:
|
|
1194
|
+
if n > 0 {
|
|
1195
|
+
return n
|
|
1196
|
+
}
|
|
1197
|
+
case int64:
|
|
1198
|
+
if n > 0 {
|
|
1199
|
+
return int(n)
|
|
1200
|
+
}
|
|
1201
|
+
case float64:
|
|
1202
|
+
if n > 0 {
|
|
1203
|
+
return int(n)
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return 0
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
func sanitizeParamName(name string) string {
|
|
1210
|
+
var b strings.Builder
|
|
1211
|
+
for _, r := range name {
|
|
1212
|
+
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' {
|
|
1213
|
+
b.WriteRune(r)
|
|
1214
|
+
} else {
|
|
1215
|
+
b.WriteRune('_')
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
out := b.String()
|
|
1219
|
+
if !strings.HasPrefix(out, "_") && (out == "" || !(out[0] >= 'a' && out[0] <= 'z') && !(out[0] >= 'A' && out[0] <= 'Z')) {
|
|
1220
|
+
out = "p_" + out
|
|
1221
|
+
}
|
|
1222
|
+
return out
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
func selectedAggregateFields(fields interface{}) []string {
|
|
1226
|
+
switch v := fields.(type) {
|
|
1227
|
+
case nil:
|
|
1228
|
+
return nil
|
|
1229
|
+
case string:
|
|
1230
|
+
if v != "" {
|
|
1231
|
+
return []string{v}
|
|
1232
|
+
}
|
|
1233
|
+
case bool:
|
|
1234
|
+
return nil
|
|
1235
|
+
case []string:
|
|
1236
|
+
return append([]string{}, v...)
|
|
1237
|
+
case []interface{}:
|
|
1238
|
+
var out []string
|
|
1239
|
+
for _, item := range v {
|
|
1240
|
+
if s, ok := item.(string); ok && s != "" {
|
|
1241
|
+
out = append(out, s)
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
return out
|
|
1245
|
+
case map[string]interface{}:
|
|
1246
|
+
var out []string
|
|
1247
|
+
for f, enabled := range v {
|
|
1248
|
+
if b, ok := enabled.(bool); ok && b {
|
|
1249
|
+
out = append(out, f)
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
sort.Strings(out)
|
|
1253
|
+
return out
|
|
1254
|
+
case map[string]bool:
|
|
1255
|
+
var out []string
|
|
1256
|
+
for f, enabled := range v {
|
|
1257
|
+
if enabled {
|
|
1258
|
+
out = append(out, f)
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
sort.Strings(out)
|
|
1262
|
+
return out
|
|
1263
|
+
}
|
|
1264
|
+
return nil
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
func normalizeByFields(by interface{}) []string {
|
|
1268
|
+
switch v := by.(type) {
|
|
1269
|
+
case string:
|
|
1270
|
+
if v != "" {
|
|
1271
|
+
return []string{v}
|
|
1272
|
+
}
|
|
1273
|
+
case []string:
|
|
1274
|
+
var out []string
|
|
1275
|
+
for _, f := range v {
|
|
1276
|
+
if f != "" {
|
|
1277
|
+
out = append(out, f)
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return out
|
|
1281
|
+
case []interface{}:
|
|
1282
|
+
var out []string
|
|
1283
|
+
for _, item := range v {
|
|
1284
|
+
if s, ok := item.(string); ok && s != "" {
|
|
1285
|
+
out = append(out, s)
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
return out
|
|
1289
|
+
}
|
|
1290
|
+
return nil
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
func numberOrZero(values ...interface{}) float64 {
|
|
1294
|
+
for _, v := range values {
|
|
1295
|
+
switch n := v.(type) {
|
|
1296
|
+
case int64:
|
|
1297
|
+
return float64(n)
|
|
1298
|
+
case int:
|
|
1299
|
+
return float64(n)
|
|
1300
|
+
case float64:
|
|
1301
|
+
return n
|
|
1302
|
+
case []byte:
|
|
1303
|
+
var f float64
|
|
1304
|
+
if _, err := fmt.Sscanf(string(n), "%f", &f); err == nil {
|
|
1305
|
+
return f
|
|
1306
|
+
}
|
|
1307
|
+
case string:
|
|
1308
|
+
var f float64
|
|
1309
|
+
if _, err := fmt.Sscanf(n, "%f", &f); err == nil {
|
|
1310
|
+
return f
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return 0
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
func numberOrNil(v interface{}) interface{} {
|
|
1318
|
+
if v == nil {
|
|
1319
|
+
return nil
|
|
1320
|
+
}
|
|
1321
|
+
return numberOrZero(v)
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
func placeholder(dialect base.Dialect, index int) string {
|
|
1325
|
+
if dialect == base.DialectPostgres {
|
|
1326
|
+
return fmt.Sprintf("$%d", index)
|
|
1327
|
+
}
|
|
1328
|
+
return "?"
|
|
1329
|
+
}
|