• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bokwoon95 / sq / 5049698939

pending completion
5049698939

push

github

bokwoon
support for dynamically mapping rows (fixes #5)

1617 of 1617 new or added lines in 9 files covered. (100.0%)

5371 of 6224 relevant lines covered (86.29%)

49.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.16
/fetch_exec.go
1
package sq
2

3
import (
4
        "bytes"
5
        "context"
6
        "database/sql"
7
        "fmt"
8
        "reflect"
9
        "runtime"
10
        "strconv"
11
        "strings"
12
        "sync/atomic"
13
        "time"
14
)
15

16
var (
17
        errMixedCalls       = fmt.Errorf("rowmapper cannot mix calls to row.Values()/row.Columns()/row.ColumnTypes() with the other row methods")
18
        errNoFieldsAccessed = fmt.Errorf("rowmapper did not access any fields, unable to determine fields to insert into query")
19
        errForbiddenCalls   = fmt.Errorf("rowmapper can only contain calls to row.Values()/row.Columns()/row.ColumnTypes() because query's SELECT clause is not dynamic")
20
)
21

22
// A Cursor represents a database cursor.
23
type Cursor[T any] struct {
24
        ctx           context.Context
25
        row           *Row
26
        rowmapper     func(*Row) T
27
        queryStats    QueryStats
28
        logSettings   LogSettings
29
        logger        SqLogger
30
        logged        int32
31
        fieldNames    []string
32
        resultsBuffer *bytes.Buffer
33
}
34

35
// FetchCursor returns a new cursor.
36
func FetchCursor[T any](db DB, query Query, rowmapper func(*Row) T) (*Cursor[T], error) {
×
37
        return fetchCursor(context.Background(), db, query, rowmapper, 1)
×
38
}
×
39

40
// FetchCursorContext is like FetchCursor but additionally requires a context.Context.
41
func FetchCursorContext[T any](ctx context.Context, db DB, query Query, rowmapper func(*Row) T) (*Cursor[T], error) {
×
42
        return fetchCursor(ctx, db, query, rowmapper, 1)
×
43
}
×
44

45
func fetchCursor[T any](ctx context.Context, db DB, query Query, rowmapper func(*Row) T, skip int) (cursor *Cursor[T], err error) {
40✔
46
        if db == nil {
40✔
47
                return nil, fmt.Errorf("db is nil")
×
48
        }
×
49
        if query == nil {
40✔
50
                return nil, fmt.Errorf("query is nil")
×
51
        }
×
52
        if rowmapper == nil {
40✔
53
                return nil, fmt.Errorf("rowmapper is nil")
×
54
        }
×
55
        dialect := query.GetDialect()
40✔
56
        cursor = &Cursor[T]{
40✔
57
                ctx:       ctx,
40✔
58
                rowmapper: rowmapper,
40✔
59
                row: &Row{
40✔
60
                        dialect: dialect,
40✔
61
                },
40✔
62
                queryStats: QueryStats{
40✔
63
                        Dialect:  dialect,
40✔
64
                        RowCount: sql.NullInt64{Valid: true},
40✔
65
                        Params:   make(map[string][]int),
40✔
66
                },
40✔
67
        }
40✔
68

40✔
69
        // Call the rowmapper to populate row.fields and row.scanDest.
40✔
70
        defer mapperFunctionPanicked(&err)
40✔
71
        _ = cursor.rowmapper(cursor.row)
40✔
72
        var ok bool
40✔
73
        if cursor.row.rawSQLMode && len(cursor.row.fields) > 0 {
44✔
74
                return nil, errMixedCalls
4✔
75
        }
4✔
76

77
        // Insert the fields into the query.
78
        query, ok = query.SetFetchableFields(cursor.row.fields)
36✔
79
        if ok && len(cursor.row.fields) == 0 {
40✔
80
                return nil, errNoFieldsAccessed
4✔
81
        }
4✔
82
        if !ok && len(cursor.row.fields) > 0 {
36✔
83
                return nil, errForbiddenCalls
4✔
84
        }
4✔
85

86
        // Build query.
87
        buf := bufpool.Get().(*bytes.Buffer)
28✔
88
        buf.Reset()
28✔
89
        defer bufpool.Put(buf)
28✔
90
        err = query.WriteSQL(ctx, dialect, buf, &cursor.queryStats.Args, cursor.queryStats.Params)
28✔
91
        cursor.queryStats.Query = buf.String()
28✔
92
        if err != nil {
28✔
93
                return nil, err
×
94
        }
×
95

96
        // Setup logger.
97
        if cursor.logger, ok = db.(SqLogger); ok {
36✔
98
                cursor.logger.SqLogSettings(ctx, &cursor.logSettings)
8✔
99
                if cursor.logSettings.IncludeCaller {
16✔
100
                        cursor.queryStats.CallerFile, cursor.queryStats.CallerLine, cursor.queryStats.CallerFunction = caller(skip + 1)
8✔
101
                }
8✔
102
        }
103

104
        // Run query.
105
        if cursor.logSettings.IncludeTime {
36✔
106
                cursor.queryStats.StartedAt = time.Now()
8✔
107
        }
8✔
108
        cursor.row.sqlRows, cursor.queryStats.Err = db.QueryContext(ctx, cursor.queryStats.Query, cursor.queryStats.Args...)
28✔
109
        if cursor.logSettings.IncludeTime {
36✔
110
                cursor.queryStats.TimeTaken = time.Since(cursor.queryStats.StartedAt)
8✔
111
        }
8✔
112
        if cursor.queryStats.Err != nil {
28✔
113
                cursor.log()
×
114
                return nil, cursor.queryStats.Err
×
115
        }
×
116

117
        // Allocate the resultsBuffer.
118
        if cursor.logSettings.IncludeResults > 0 {
34✔
119
                cursor.resultsBuffer = bufpool.Get().(*bytes.Buffer)
6✔
120
                cursor.resultsBuffer.Reset()
6✔
121
        }
6✔
122
        return cursor, nil
28✔
123
}
124

125
// Next advances the cursor to the next result.
126
func (cursor *Cursor[T]) Next() bool {
102✔
127
        hasNext := cursor.row.sqlRows.Next()
102✔
128
        if hasNext {
186✔
129
                cursor.queryStats.RowCount.Int64++
84✔
130
        } else {
102✔
131
                cursor.log()
18✔
132
        }
18✔
133
        return hasNext
102✔
134
}
135

136
// RowCount returns the current row number so far.
137
func (cursor *Cursor[T]) RowCount() int64 { return cursor.queryStats.RowCount.Int64 }
18✔
138

139
// Result returns the cursor result.
140
func (cursor *Cursor[T]) Result() (result T, err error) {
84✔
141
        if !cursor.row.rawSQLMode {
134✔
142
                err = cursor.row.sqlRows.Scan(cursor.row.scanDest...)
50✔
143
                if err != nil {
50✔
144
                        cursor.log()
×
145
                        fieldMappings := getFieldMappings(cursor.queryStats.Dialect, cursor.row.fields, cursor.row.scanDest)
×
146
                        return result, fmt.Errorf("please check if your mapper function is correct:%s\n%w", fieldMappings, err)
×
147
                }
×
148
        }
149
        // If results should be logged, write the row into the resultsBuffer.
150
        if cursor.resultsBuffer != nil && cursor.queryStats.RowCount.Int64 <= int64(cursor.logSettings.IncludeResults) {
126✔
151
                if len(cursor.fieldNames) == 0 {
52✔
152
                        cursor.fieldNames = getFieldNames(cursor.ctx, cursor.row)
10✔
153
                }
10✔
154
                cursor.resultsBuffer.WriteString("\n----[ Row " + strconv.FormatInt(cursor.queryStats.RowCount.Int64, 10) + " ]----")
42✔
155
                for i := range cursor.row.scanDest {
366✔
156
                        cursor.resultsBuffer.WriteString("\n")
324✔
157
                        if i < len(cursor.fieldNames) {
648✔
158
                                cursor.resultsBuffer.WriteString(cursor.fieldNames[i])
324✔
159
                        }
324✔
160
                        cursor.resultsBuffer.WriteString(": ")
324✔
161
                        scanDest := cursor.row.scanDest[i]
324✔
162
                        rhs, err := Sprint(cursor.queryStats.Dialect, scanDest)
324✔
163
                        if err != nil {
324✔
164
                                cursor.resultsBuffer.WriteString("%!(error=" + err.Error() + ")")
×
165
                                continue
×
166
                        }
167
                        cursor.resultsBuffer.WriteString(rhs)
324✔
168
                }
169
        }
170
        cursor.row.index = 0
84✔
171
        defer mapperFunctionPanicked(&err)
84✔
172
        result = cursor.rowmapper(cursor.row)
84✔
173
        return result, nil
84✔
174
}
175

176
func (cursor *Cursor[T]) log() {
90✔
177
        if !atomic.CompareAndSwapInt32(&cursor.logged, 0, 1) {
144✔
178
                return
54✔
179
        }
54✔
180
        if cursor.resultsBuffer != nil {
46✔
181
                cursor.queryStats.Results = cursor.resultsBuffer.String()
10✔
182
                bufpool.Put(cursor.resultsBuffer)
10✔
183
        }
10✔
184
        if cursor.logger == nil {
56✔
185
                return
20✔
186
        }
20✔
187
        if cursor.logSettings.LogAsynchronously {
16✔
188
                go cursor.logger.SqLogQuery(cursor.ctx, cursor.queryStats)
×
189
        } else {
16✔
190
                cursor.logger.SqLogQuery(cursor.ctx, cursor.queryStats)
16✔
191
        }
16✔
192
}
193

194
// Close closes the cursor.
195
func (cursor *Cursor[T]) Close() error {
72✔
196
        cursor.log()
72✔
197
        if err := cursor.row.sqlRows.Close(); err != nil {
72✔
198
                return err
×
199
        }
×
200
        if err := cursor.row.sqlRows.Err(); err != nil {
72✔
201
                return err
×
202
        }
×
203
        return nil
72✔
204
}
205

206
// FetchOne returns the first result from running the given Query on the given
207
// DB.
208
func FetchOne[T any](db DB, query Query, rowmapper func(*Row) T) (T, error) {
14✔
209
        cursor, err := fetchCursor(context.Background(), db, query, rowmapper, 1)
14✔
210
        if err != nil {
14✔
211
                return *new(T), err
×
212
        }
×
213
        defer cursor.Close()
14✔
214
        return cursorResult(cursor)
14✔
215
}
216

217
// FetchOneContext is like FetchOne but additionally requires a context.Context.
218
func FetchOneContext[T any](ctx context.Context, db DB, query Query, rowmapper func(*Row) T) (T, error) {
×
219
        cursor, err := fetchCursor(ctx, db, query, rowmapper, 1)
×
220
        if err != nil {
×
221
                return *new(T), err
×
222
        }
×
223
        defer cursor.Close()
×
224
        return cursorResult(cursor)
×
225
}
226

227
// FetchAll returns all results from running the given Query on the given DB.
228
func FetchAll[T any](db DB, query Query, rowmapper func(*Row) T) ([]T, error) {
26✔
229
        cursor, err := fetchCursor(context.Background(), db, query, rowmapper, 1)
26✔
230
        if err != nil {
38✔
231
                return nil, err
12✔
232
        }
12✔
233
        defer cursor.Close()
14✔
234
        return cursorResults(cursor)
14✔
235
}
236

237
// FetchAllContext is like FetchAll but additionally requires a context.Context.
238
func FetchAllContext[T any](ctx context.Context, db DB, query Query, rowmapper func(*Row) T) ([]T, error) {
×
239
        cursor, err := fetchCursor(ctx, db, query, rowmapper, 1)
×
240
        if err != nil {
×
241
                return nil, err
×
242
        }
×
243
        defer cursor.Close()
×
244
        return cursorResults(cursor)
×
245
}
246

247
// CompiledFetch is the result of compiling a Query down into a query string
248
// and args slice. A CompiledFetch can be safely executed in parallel.
249
type CompiledFetch[T any] struct {
250
        dialect   string
251
        query     string
252
        args      []any
253
        params    map[string][]int
254
        rowmapper func(*Row) T
255
}
256

257
// NewCompiledFetch returns a new CompiledFetch.
258
func NewCompiledFetch[T any](dialect string, query string, args []any, params map[string][]int, rowmapper func(*Row) T) *CompiledFetch[T] {
4✔
259
        return &CompiledFetch[T]{
4✔
260
                dialect:   dialect,
4✔
261
                query:     query,
4✔
262
                args:      args,
4✔
263
                params:    params,
4✔
264
                rowmapper: rowmapper,
4✔
265
        }
4✔
266
}
4✔
267

268
// CompileFetch returns a new CompileFetch.
269
func CompileFetch[T any](q Query, rowmapper func(*Row) T) (*CompiledFetch[T], error) {
4✔
270
        return CompileFetchContext(context.Background(), q, rowmapper)
4✔
271
}
4✔
272

273
// CompileFetchContext is like CompileFetch but accepts a context.Context.
274
func CompileFetchContext[T any](ctx context.Context, query Query, rowmapper func(*Row) T) (compiledFetch *CompiledFetch[T], err error) {
8✔
275
        if query == nil {
8✔
276
                return nil, fmt.Errorf("query is nil")
×
277
        }
×
278
        if rowmapper == nil {
8✔
279
                return nil, fmt.Errorf("rowmapper is nil")
×
280
        }
×
281
        dialect := query.GetDialect()
8✔
282
        compiledFetch = &CompiledFetch[T]{
8✔
283
                dialect:   dialect,
8✔
284
                params:    make(map[string][]int),
8✔
285
                rowmapper: rowmapper,
8✔
286
        }
8✔
287
        row := &Row{
8✔
288
                dialect: dialect,
8✔
289
        }
8✔
290

8✔
291
        // Call the rowmapper to populate row.fields.
8✔
292
        defer mapperFunctionPanicked(&err)
8✔
293
        _ = rowmapper(row)
8✔
294
        var ok bool
8✔
295
        if row.rawSQLMode && len(row.fields) > 0 {
8✔
296
                return nil, errMixedCalls
×
297
        }
×
298

299
        // Insert the fields into the query.
300
        query, ok = query.SetFetchableFields(row.fields)
8✔
301
        if ok && len(row.fields) == 0 {
8✔
302
                return nil, errNoFieldsAccessed
×
303
        }
×
304
        if !ok && len(row.fields) > 0 {
8✔
305
                return nil, errForbiddenCalls
×
306
        }
×
307

308
        // Build query.
309
        buf := bufpool.Get().(*bytes.Buffer)
8✔
310
        buf.Reset()
8✔
311
        defer bufpool.Put(buf)
8✔
312
        err = query.WriteSQL(ctx, dialect, buf, &compiledFetch.args, compiledFetch.params)
8✔
313
        compiledFetch.query = buf.String()
8✔
314
        if err != nil {
8✔
315
                return nil, err
×
316
        }
×
317
        return compiledFetch, nil
8✔
318
}
319

320
// FetchCursor returns a new cursor.
321
func (compiledFetch *CompiledFetch[T]) FetchCursor(db DB, params Params) (*Cursor[T], error) {
×
322
        return compiledFetch.fetchCursor(context.Background(), db, params, 1)
×
323
}
×
324

325
// FetchCursorContext is like FetchCursor but additionally requires a context.Context.
326
func (compiledFetch *CompiledFetch[T]) FetchCursorContext(ctx context.Context, db DB, params Params) (*Cursor[T], error) {
×
327
        return compiledFetch.fetchCursor(ctx, db, params, 1)
×
328
}
×
329

330
func (compiledFetch *CompiledFetch[T]) fetchCursor(ctx context.Context, db DB, params Params, skip int) (cursor *Cursor[T], err error) {
4✔
331
        if db == nil {
4✔
332
                return nil, fmt.Errorf("db is nil")
×
333
        }
×
334
        cursor = &Cursor[T]{
4✔
335
                ctx:       ctx,
4✔
336
                rowmapper: compiledFetch.rowmapper,
4✔
337
                row: &Row{
4✔
338
                        dialect: compiledFetch.dialect,
4✔
339
                },
4✔
340
                queryStats: QueryStats{
4✔
341
                        Dialect: compiledFetch.dialect,
4✔
342
                        Query:   compiledFetch.query,
4✔
343
                        Args:    compiledFetch.args,
4✔
344
                        Params:  compiledFetch.params,
4✔
345
                },
4✔
346
        }
4✔
347

4✔
348
        // Call the rowmapper to populate row.scanDest.
4✔
349
        defer mapperFunctionPanicked(&err)
4✔
350
        _ = cursor.rowmapper(cursor.row)
4✔
351
        if err != nil {
4✔
352
                return nil, err
×
353
        }
×
354

355
        // Substitute params.
356
        cursor.queryStats.Args, err = substituteParams(cursor.queryStats.Dialect, cursor.queryStats.Args, cursor.queryStats.Params, params)
4✔
357
        if err != nil {
4✔
358
                return nil, err
×
359
        }
×
360

361
        // Setup logger.
362
        var ok bool
4✔
363
        cursor.queryStats.RowCount.Valid = true
4✔
364
        if cursor.logger, ok = db.(SqLogger); ok {
8✔
365
                cursor.logger.SqLogSettings(ctx, &cursor.logSettings)
4✔
366
                if cursor.logSettings.IncludeCaller {
8✔
367
                        cursor.queryStats.CallerFile, cursor.queryStats.CallerLine, cursor.queryStats.CallerFunction = caller(skip + 1)
4✔
368
                }
4✔
369
        }
370

371
        // Run query.
372
        if cursor.logSettings.IncludeTime {
8✔
373
                cursor.queryStats.StartedAt = time.Now()
4✔
374
        }
4✔
375
        cursor.row.sqlRows, cursor.queryStats.Err = db.QueryContext(ctx, cursor.queryStats.Query, cursor.queryStats.Args...)
4✔
376
        if cursor.logSettings.IncludeTime {
8✔
377
                cursor.queryStats.TimeTaken = time.Since(cursor.queryStats.StartedAt)
4✔
378
        }
4✔
379
        if cursor.queryStats.Err != nil {
4✔
380
                return nil, cursor.queryStats.Err
×
381
        }
×
382

383
        // Allocate the resultsBuffer.
384
        if cursor.logSettings.IncludeResults > 0 {
6✔
385
                cursor.resultsBuffer = bufpool.Get().(*bytes.Buffer)
2✔
386
                cursor.resultsBuffer.Reset()
2✔
387
        }
2✔
388
        return cursor, nil
4✔
389
}
390

391
// FetchOne returns the first result from running the CompiledFetch on the
392
// given DB with the give params.
393
func (compiledFetch *CompiledFetch[T]) FetchOne(db DB, params Params) (T, error) {
2✔
394
        cursor, err := compiledFetch.fetchCursor(context.Background(), db, params, 1)
2✔
395
        if err != nil {
2✔
396
                return *new(T), err
×
397
        }
×
398
        defer cursor.Close()
2✔
399
        return cursorResult(cursor)
2✔
400
}
401

402
// FetchOneContext is like FetchOne but additionally requires a context.Context.
403
func (compiledFetch *CompiledFetch[T]) FetchOneContext(ctx context.Context, db DB, params Params) (T, error) {
×
404
        cursor, err := compiledFetch.fetchCursor(ctx, db, params, 1)
×
405
        if err != nil {
×
406
                return *new(T), err
×
407
        }
×
408
        defer cursor.Close()
×
409
        return cursorResult(cursor)
×
410
}
411

412
// FetchAll returns all the results from running the CompiledFetch on the given
413
// DB with the give params.
414
func (compiledFetch *CompiledFetch[T]) FetchAll(db DB, params Params) ([]T, error) {
2✔
415
        cursor, err := compiledFetch.fetchCursor(context.Background(), db, params, 1)
2✔
416
        if err != nil {
2✔
417
                return nil, err
×
418
        }
×
419
        defer cursor.Close()
2✔
420
        return cursorResults(cursor)
2✔
421
}
422

423
// FetchAllContext is like FetchAll but additionally requires a context.Context.
424
func (compiledFetch *CompiledFetch[T]) FetchAllContext(ctx context.Context, db DB, params Params) ([]T, error) {
×
425
        cursor, err := compiledFetch.fetchCursor(ctx, db, params, 1)
×
426
        if err != nil {
×
427
                return nil, err
×
428
        }
×
429
        defer cursor.Close()
×
430
        return cursorResults(cursor)
×
431
}
432

433
// GetSQL returns a copy of the dialect, query, args, params and rowmapper that
434
// make up the CompiledFetch.
435
func (compiledFetch *CompiledFetch[T]) GetSQL() (dialect string, query string, args []any, params map[string][]int, rowmapper func(*Row) T) {
4✔
436
        dialect = compiledFetch.dialect
4✔
437
        query = compiledFetch.query
4✔
438
        args = make([]any, len(compiledFetch.args))
4✔
439
        params = make(map[string][]int)
4✔
440
        copy(args, compiledFetch.args)
4✔
441
        for name, indexes := range compiledFetch.params {
6✔
442
                indexes2 := make([]int, len(indexes))
2✔
443
                copy(indexes2, indexes)
2✔
444
                params[name] = indexes2
2✔
445
        }
2✔
446
        return dialect, query, args, params, compiledFetch.rowmapper
4✔
447
}
448

449
// Prepare creates a PreparedFetch from a CompiledFetch by preparing it on
450
// the given DB.
451
func (compiledFetch *CompiledFetch[T]) Prepare(db DB) (*PreparedFetch[T], error) {
×
452
        return compiledFetch.PrepareContext(context.Background(), db)
×
453
}
×
454

455
// PrepareContext is like Prepare but additionally requires a context.Context.
456
func (compiledFetch *CompiledFetch[T]) PrepareContext(ctx context.Context, db DB) (*PreparedFetch[T], error) {
4✔
457
        var err error
4✔
458
        preparedFetch := &PreparedFetch[T]{
4✔
459
                compiledFetch: NewCompiledFetch(compiledFetch.GetSQL()),
4✔
460
        }
4✔
461
        if db == nil {
4✔
462
                return nil, fmt.Errorf("db is nil")
×
463
        }
×
464
        preparedFetch.stmt, err = db.PrepareContext(ctx, compiledFetch.query)
4✔
465
        if err != nil {
4✔
466
                return nil, err
×
467
        }
×
468
        preparedFetch.logger, _ = db.(SqLogger)
4✔
469
        return preparedFetch, nil
4✔
470
}
471

472
// PreparedFetch is the result of preparing a CompiledFetch on a DB.
473
type PreparedFetch[T any] struct {
474
        compiledFetch *CompiledFetch[T]
475
        stmt          *sql.Stmt
476
        logger        SqLogger
477
}
478

479
// PrepareFetch returns a new PreparedFetch.
480
func PrepareFetch[T any](db DB, q Query, rowmapper func(*Row) T) (*PreparedFetch[T], error) {
4✔
481
        return PrepareFetchContext(context.Background(), db, q, rowmapper)
4✔
482
}
4✔
483

484
// PrepareFetchContext is like PrepareFetch but additionally requires a context.Context.
485
func PrepareFetchContext[T any](ctx context.Context, db DB, q Query, rowmapper func(*Row) T) (*PreparedFetch[T], error) {
4✔
486
        compiledFetch, err := CompileFetchContext(ctx, q, rowmapper)
4✔
487
        if err != nil {
4✔
488
                return nil, err
×
489
        }
×
490
        return compiledFetch.PrepareContext(ctx, db)
4✔
491
}
492

493
// FetchCursor returns a new cursor.
494
func (preparedFetch PreparedFetch[T]) FetchCursor(params Params) (*Cursor[T], error) {
×
495
        return preparedFetch.fetchCursor(context.Background(), params, 1)
×
496
}
×
497

498
// FetchCursorContext is like FetchCursor but additionally requires a context.Context.
499
func (preparedFetch PreparedFetch[T]) FetchCursorContext(ctx context.Context, params Params) (*Cursor[T], error) {
×
500
        return preparedFetch.fetchCursor(ctx, params, 1)
×
501
}
×
502

503
func (preparedFetch *PreparedFetch[T]) fetchCursor(ctx context.Context, params Params, skip int) (cursor *Cursor[T], err error) {
4✔
504
        cursor = &Cursor[T]{
4✔
505
                ctx:       ctx,
4✔
506
                rowmapper: preparedFetch.compiledFetch.rowmapper,
4✔
507
                row: &Row{
4✔
508
                        dialect: preparedFetch.compiledFetch.dialect,
4✔
509
                },
4✔
510
                queryStats: QueryStats{
4✔
511
                        Dialect:  preparedFetch.compiledFetch.dialect,
4✔
512
                        Query:    preparedFetch.compiledFetch.query,
4✔
513
                        Args:     preparedFetch.compiledFetch.args,
4✔
514
                        Params:   preparedFetch.compiledFetch.params,
4✔
515
                        RowCount: sql.NullInt64{Valid: true},
4✔
516
                },
4✔
517
                logger: preparedFetch.logger,
4✔
518
        }
4✔
519

4✔
520
        // Call the rowmapper to populate row.scanDest.
4✔
521
        defer mapperFunctionPanicked(&err)
4✔
522
        _ = cursor.rowmapper(cursor.row)
4✔
523
        if err != nil {
4✔
524
                return nil, err
×
525
        }
×
526

527
        // Substitute params.
528
        cursor.queryStats.Args, err = substituteParams(cursor.queryStats.Dialect, cursor.queryStats.Args, cursor.queryStats.Params, params)
4✔
529
        if err != nil {
4✔
530
                return nil, err
×
531
        }
×
532

533
        // Setup logger.
534
        if cursor.logger != nil {
8✔
535
                cursor.logger.SqLogSettings(ctx, &cursor.logSettings)
4✔
536
                if cursor.logSettings.IncludeCaller {
8✔
537
                        cursor.queryStats.CallerFile, cursor.queryStats.CallerLine, cursor.queryStats.CallerFunction = caller(skip + 1)
4✔
538
                }
4✔
539
        }
540

541
        // Run query.
542
        if cursor.logSettings.IncludeTime {
8✔
543
                cursor.queryStats.StartedAt = time.Now()
4✔
544
        }
4✔
545
        cursor.row.sqlRows, cursor.queryStats.Err = preparedFetch.stmt.QueryContext(ctx, cursor.queryStats.Args...)
4✔
546
        if cursor.logSettings.IncludeTime {
8✔
547
                cursor.queryStats.TimeTaken = time.Since(cursor.queryStats.StartedAt)
4✔
548
        }
4✔
549
        if cursor.queryStats.Err != nil {
4✔
550
                return nil, cursor.queryStats.Err
×
551
        }
×
552

553
        // Allocate the resultsBuffer.
554
        if cursor.logSettings.IncludeResults > 0 {
6✔
555
                cursor.resultsBuffer = bufpool.Get().(*bytes.Buffer)
2✔
556
                cursor.resultsBuffer.Reset()
2✔
557
        }
2✔
558
        return cursor, nil
4✔
559
}
560

561
// FetchOne returns the first result from running the PreparedFetch with the
562
// give params.
563
func (preparedFetch *PreparedFetch[T]) FetchOne(params Params) (T, error) {
2✔
564
        cursor, err := preparedFetch.fetchCursor(context.Background(), params, 1)
2✔
565
        if err != nil {
2✔
566
                return *new(T), err
×
567
        }
×
568
        defer cursor.Close()
2✔
569
        return cursorResult(cursor)
2✔
570
}
571

572
// FetchOneContext is like FetchOne but additionally requires a context.Context.
573
func (preparedFetch *PreparedFetch[T]) FetchOneContext(ctx context.Context, params Params) (T, error) {
×
574
        cursor, err := preparedFetch.fetchCursor(ctx, params, 1)
×
575
        if err != nil {
×
576
                return *new(T), err
×
577
        }
×
578
        defer cursor.Close()
×
579
        return cursorResult(cursor)
×
580
}
581

582
// FetchAll returns all the results from running the PreparedFetch with the
583
// give params.
584
func (preparedFetch *PreparedFetch[T]) FetchAll(params Params) ([]T, error) {
2✔
585
        cursor, err := preparedFetch.fetchCursor(context.Background(), params, 1)
2✔
586
        if err != nil {
2✔
587
                return nil, err
×
588
        }
×
589
        defer cursor.Close()
2✔
590
        return cursorResults(cursor)
2✔
591
}
592

593
// FetchAllContext is like FetchAll but additionally requires a context.Context.
594
func (preparedFetch *PreparedFetch[T]) FetchAllContext(ctx context.Context, params Params) ([]T, error) {
×
595
        cursor, err := preparedFetch.fetchCursor(ctx, params, 1)
×
596
        if err != nil {
×
597
                return nil, err
×
598
        }
×
599
        defer cursor.Close()
×
600
        return cursorResults(cursor)
×
601
}
602

603
// GetCompiled returns a copy of the underlying CompiledFetch.
604
func (preparedFetch *PreparedFetch[T]) GetCompiled() *CompiledFetch[T] {
×
605
        return NewCompiledFetch(preparedFetch.compiledFetch.GetSQL())
×
606
}
×
607

608
// Close closes the PreparedFetch.
609
func (preparedFetch *PreparedFetch[T]) Close() error {
×
610
        if preparedFetch.stmt == nil {
×
611
                return nil
×
612
        }
×
613
        return preparedFetch.stmt.Close()
×
614
}
615

616
// Exec executes the given Query on the given DB.
617
func Exec(db DB, query Query) (Result, error) {
9✔
618
        return exec(context.Background(), db, query, 1)
9✔
619
}
9✔
620

621
// ExecContext is like Exec but additionally requires a context.Context.
622
func ExecContext(ctx context.Context, db DB, query Query) (Result, error) {
×
623
        return exec(ctx, db, query, 1)
×
624
}
×
625

626
func exec(ctx context.Context, db DB, query Query, skip int) (result Result, err error) {
9✔
627
        if db == nil {
9✔
628
                return result, fmt.Errorf("db is nil")
×
629
        }
×
630
        if query == nil {
9✔
631
                return result, fmt.Errorf("query is nil")
×
632
        }
×
633
        dialect := query.GetDialect()
9✔
634
        queryStats := QueryStats{
9✔
635
                Dialect: dialect,
9✔
636
                Params:  make(map[string][]int),
9✔
637
        }
9✔
638

9✔
639
        // Build query.
9✔
640
        buf := bufpool.Get().(*bytes.Buffer)
9✔
641
        buf.Reset()
9✔
642
        defer bufpool.Put(buf)
9✔
643
        err = query.WriteSQL(ctx, dialect, buf, &queryStats.Args, queryStats.Params)
9✔
644
        queryStats.Query = buf.String()
9✔
645
        if err != nil {
9✔
646
                return result, err
×
647
        }
×
648

649
        // Setup logger.
650
        var logSettings LogSettings
9✔
651
        if logger, ok := db.(SqLogger); ok {
18✔
652
                logger.SqLogSettings(ctx, &logSettings)
9✔
653
                if logSettings.IncludeCaller {
18✔
654
                        queryStats.CallerFile, queryStats.CallerLine, queryStats.CallerFunction = caller(skip + 1)
9✔
655
                }
9✔
656
                defer func() {
18✔
657
                        if logSettings.LogAsynchronously {
9✔
658
                                go logger.SqLogQuery(ctx, queryStats)
×
659
                        } else {
9✔
660
                                logger.SqLogQuery(ctx, queryStats)
9✔
661
                        }
9✔
662
                }()
663
        }
664

665
        // Run query.
666
        if logSettings.IncludeTime {
18✔
667
                queryStats.StartedAt = time.Now()
9✔
668
        }
9✔
669
        var sqlResult sql.Result
9✔
670
        sqlResult, queryStats.Err = db.ExecContext(ctx, queryStats.Query, queryStats.Args...)
9✔
671
        if logSettings.IncludeTime {
18✔
672
                queryStats.TimeTaken = time.Since(queryStats.StartedAt)
9✔
673
        }
9✔
674
        if queryStats.Err != nil {
9✔
675
                return result, queryStats.Err
×
676
        }
×
677
        return execResult(sqlResult, &queryStats)
9✔
678
}
679

680
// CompiledExec is the result of compiling a Query down into a query string and
681
// args slice. A CompiledExec can be safely executed in parallel.
682
type CompiledExec struct {
683
        dialect string
684
        query   string
685
        args    []any
686
        params  map[string][]int
687
}
688

689
// NewCompiledExec returns a new CompiledExec.
690
func NewCompiledExec(dialect string, query string, args []any, params map[string][]int) *CompiledExec {
1✔
691
        return &CompiledExec{
1✔
692
                dialect: dialect,
1✔
693
                query:   query,
1✔
694
                args:    args,
1✔
695
                params:  params,
1✔
696
        }
1✔
697
}
1✔
698

699
// CompileExec returns a new CompiledExec.
700
func CompileExec(query Query) (*CompiledExec, error) {
1✔
701
        return CompileExecContext(context.Background(), query)
1✔
702
}
1✔
703

704
// CompileExecContext is like CompileExec but additionally requires a context.Context.
705
func CompileExecContext(ctx context.Context, query Query) (*CompiledExec, error) {
2✔
706
        if query == nil {
2✔
707
                return nil, fmt.Errorf("query is nil")
×
708
        }
×
709
        dialect := query.GetDialect()
2✔
710
        compiledExec := &CompiledExec{
2✔
711
                dialect: dialect,
2✔
712
                params:  make(map[string][]int),
2✔
713
        }
2✔
714

2✔
715
        // Build query.
2✔
716
        buf := bufpool.Get().(*bytes.Buffer)
2✔
717
        buf.Reset()
2✔
718
        defer bufpool.Put(buf)
2✔
719
        err := query.WriteSQL(ctx, dialect, buf, &compiledExec.args, compiledExec.params)
2✔
720
        compiledExec.query = buf.String()
2✔
721
        if err != nil {
2✔
722
                return nil, err
×
723
        }
×
724
        return compiledExec, nil
2✔
725
}
726

727
// Exec executes the CompiledExec on the given DB with the given params.
728
func (compiledExec *CompiledExec) Exec(db DB, params Params) (Result, error) {
5✔
729
        return compiledExec.exec(context.Background(), db, params, 1)
5✔
730
}
5✔
731

732
// ExecContext is like Exec but additionally requires a context.Context.
733
func (compiledExec *CompiledExec) ExecContext(ctx context.Context, db DB, params Params) (Result, error) {
×
734
        return compiledExec.exec(ctx, db, params, 1)
×
735
}
×
736

737
func (compiledExec *CompiledExec) exec(ctx context.Context, db DB, params Params, skip int) (result Result, err error) {
5✔
738
        if db == nil {
5✔
739
                return result, fmt.Errorf("db is nil")
×
740
        }
×
741
        queryStats := QueryStats{
5✔
742
                Dialect: compiledExec.dialect,
5✔
743
                Query:   compiledExec.query,
5✔
744
                Args:    compiledExec.args,
5✔
745
                Params:  compiledExec.params,
5✔
746
        }
5✔
747

5✔
748
        // Setup logger.
5✔
749
        var logSettings LogSettings
5✔
750
        if logger, ok := db.(SqLogger); ok {
10✔
751
                logger.SqLogSettings(ctx, &logSettings)
5✔
752
                if logSettings.IncludeCaller {
10✔
753
                        queryStats.CallerFile, queryStats.CallerLine, queryStats.CallerFunction = caller(skip + 1)
5✔
754
                }
5✔
755
                defer func() {
10✔
756
                        if logSettings.LogAsynchronously {
5✔
757
                                go logger.SqLogQuery(ctx, queryStats)
×
758
                        } else {
5✔
759
                                logger.SqLogQuery(ctx, queryStats)
5✔
760
                        }
5✔
761
                }()
762
        }
763

764
        // Substitute params.
765
        queryStats.Args, err = substituteParams(queryStats.Dialect, queryStats.Args, queryStats.Params, params)
5✔
766
        if err != nil {
5✔
767
                return result, err
×
768
        }
×
769

770
        // Run query.
771
        if logSettings.IncludeTime {
10✔
772
                queryStats.StartedAt = time.Now()
5✔
773
        }
5✔
774
        var sqlResult sql.Result
5✔
775
        sqlResult, queryStats.Err = db.ExecContext(ctx, queryStats.Query, queryStats.Args...)
5✔
776
        if logSettings.IncludeTime {
10✔
777
                queryStats.TimeTaken = time.Since(queryStats.StartedAt)
5✔
778
        }
5✔
779
        if queryStats.Err != nil {
5✔
780
                return result, queryStats.Err
×
781
        }
×
782
        return execResult(sqlResult, &queryStats)
5✔
783
}
784

785
// GetSQL returns a copy of the dialect, query, args, params and rowmapper that
786
// make up the CompiledExec.
787
func (compiledExec *CompiledExec) GetSQL() (dialect string, query string, args []any, params map[string][]int) {
1✔
788
        dialect = compiledExec.dialect
1✔
789
        query = compiledExec.query
1✔
790
        args = make([]any, len(compiledExec.args))
1✔
791
        params = make(map[string][]int)
1✔
792
        copy(args, compiledExec.args)
1✔
793
        for name, indexes := range compiledExec.params {
5✔
794
                indexes2 := make([]int, len(indexes))
4✔
795
                copy(indexes2, indexes)
4✔
796
                params[name] = indexes2
4✔
797
        }
4✔
798
        return dialect, query, args, params
1✔
799
}
800

801
// Prepare creates a PreparedExec from a CompiledExec by preparing it on the
802
// given DB.
803
func (compiledExec *CompiledExec) Prepare(db DB) (*PreparedExec, error) {
×
804
        return compiledExec.PrepareContext(context.Background(), db)
×
805
}
×
806

807
// PrepareContext is like Prepare but additionally requires a context.Context.
808
func (compiledExec *CompiledExec) PrepareContext(ctx context.Context, db DB) (*PreparedExec, error) {
1✔
809
        var err error
1✔
810
        preparedExec := &PreparedExec{
1✔
811
                compiledExec: NewCompiledExec(compiledExec.GetSQL()),
1✔
812
        }
1✔
813
        preparedExec.stmt, err = db.PrepareContext(ctx, compiledExec.query)
1✔
814
        if err != nil {
1✔
815
                return nil, err
×
816
        }
×
817
        preparedExec.logger, _ = db.(SqLogger)
1✔
818
        return preparedExec, nil
1✔
819
}
820

821
// PrepareExec is the result of preparing a CompiledExec on a DB.
822
type PreparedExec struct {
823
        compiledExec *CompiledExec
824
        stmt         *sql.Stmt
825
        logger       SqLogger
826
}
827

828
// PrepareExec returns a new PreparedExec.
829
func PrepareExec(db DB, q Query) (*PreparedExec, error) {
1✔
830
        return PrepareExecContext(context.Background(), db, q)
1✔
831
}
1✔
832

833
// PrepareExecContext is like PrepareExec but additionally requires a
834
// context.Context.
835
func PrepareExecContext(ctx context.Context, db DB, q Query) (*PreparedExec, error) {
1✔
836
        compiledExec, err := CompileExecContext(ctx, q)
1✔
837
        if err != nil {
1✔
838
                return nil, err
×
839
        }
×
840
        return compiledExec.PrepareContext(ctx, db)
1✔
841
}
842

843
// Close closes the PreparedExec.
844
func (preparedExec *PreparedExec) Close() error {
×
845
        if preparedExec.stmt == nil {
×
846
                return nil
×
847
        }
×
848
        return preparedExec.stmt.Close()
×
849
}
850

851
// Exec executes the PreparedExec with the given params.
852
func (preparedExec *PreparedExec) Exec(params Params) (Result, error) {
5✔
853
        return preparedExec.exec(context.Background(), params, 1)
5✔
854
}
5✔
855

856
// ExecContext is like Exec but additionally requires a context.Context.
857
func (preparedExec *PreparedExec) ExecContext(ctx context.Context, params Params) (Result, error) {
×
858
        return preparedExec.exec(ctx, params, 1)
×
859
}
×
860

861
func (preparedExec *PreparedExec) exec(ctx context.Context, params Params, skip int) (result Result, err error) {
5✔
862
        queryStats := QueryStats{
5✔
863
                Dialect: preparedExec.compiledExec.dialect,
5✔
864
                Query:   preparedExec.compiledExec.query,
5✔
865
                Args:    preparedExec.compiledExec.args,
5✔
866
                Params:  preparedExec.compiledExec.params,
5✔
867
        }
5✔
868

5✔
869
        // Setup logger.
5✔
870
        var logSettings LogSettings
5✔
871
        if preparedExec.logger != nil {
10✔
872
                preparedExec.logger.SqLogSettings(ctx, &logSettings)
5✔
873
                if logSettings.IncludeCaller {
10✔
874
                        queryStats.CallerFile, queryStats.CallerLine, queryStats.CallerFunction = caller(skip + 1)
5✔
875
                }
5✔
876
                defer func() {
10✔
877
                        if logSettings.LogAsynchronously {
5✔
878
                                go preparedExec.logger.SqLogQuery(ctx, queryStats)
×
879
                        } else {
5✔
880
                                preparedExec.logger.SqLogQuery(ctx, queryStats)
5✔
881
                        }
5✔
882
                }()
883
        }
884

885
        // Substitute params.
886
        queryStats.Args, err = substituteParams(queryStats.Dialect, queryStats.Args, queryStats.Params, params)
5✔
887
        if err != nil {
5✔
888
                return result, err
×
889
        }
×
890

891
        // Run query.
892
        if logSettings.IncludeTime {
10✔
893
                queryStats.StartedAt = time.Now()
5✔
894
        }
5✔
895
        var sqlResult sql.Result
5✔
896
        sqlResult, queryStats.Err = preparedExec.stmt.ExecContext(ctx, queryStats.Args...)
5✔
897
        if logSettings.IncludeTime {
10✔
898
                queryStats.TimeTaken = time.Since(queryStats.StartedAt)
5✔
899
        }
5✔
900
        if queryStats.Err != nil {
5✔
901
                return result, queryStats.Err
×
902
        }
×
903
        return execResult(sqlResult, &queryStats)
5✔
904
}
905

906
func getFieldNames(ctx context.Context, row *Row) []string {
10✔
907
        if len(row.fields) == 0 {
13✔
908
                columns, _ := row.sqlRows.Columns()
3✔
909
                return columns
3✔
910
        }
3✔
911
        buf := bufpool.Get().(*bytes.Buffer)
7✔
912
        buf.Reset()
7✔
913
        defer bufpool.Put(buf)
7✔
914
        var args []any
7✔
915
        fieldNames := make([]string, 0, len(row.fields))
7✔
916
        for _, field := range row.fields {
91✔
917
                if alias := getAlias(field); alias != "" {
84✔
918
                        fieldNames = append(fieldNames, alias)
×
919
                        continue
×
920
                }
921
                buf.Reset()
84✔
922
                args = args[:0]
84✔
923
                err := field.WriteSQL(ctx, row.dialect, buf, &args, nil)
84✔
924
                if err != nil {
84✔
925
                        fieldNames = append(fieldNames, "%!(error="+err.Error()+")")
×
926
                        continue
×
927
                }
928
                fieldName, err := Sprintf(row.dialect, buf.String(), args)
84✔
929
                if err != nil {
84✔
930
                        fieldNames = append(fieldNames, "%!(error="+err.Error()+")")
×
931
                        continue
×
932
                }
933
                fieldNames = append(fieldNames, fieldName)
84✔
934
        }
935
        return fieldNames
7✔
936
}
937

938
func getFieldMappings(dialect string, fields []Field, scanDest []any) string {
2✔
939
        var buf bytes.Buffer
2✔
940
        var args []any
2✔
941
        var b strings.Builder
2✔
942
        for i, field := range fields {
5✔
943
                b.WriteString(fmt.Sprintf("\n %02d. ", i+1))
3✔
944
                buf.Reset()
3✔
945
                args = args[:0]
3✔
946
                err := field.WriteSQL(context.Background(), dialect, &buf, &args, nil)
3✔
947
                if err != nil {
3✔
948
                        buf.WriteString("%!(error=" + err.Error() + ")")
×
949
                        continue
×
950
                }
951
                fieldName, err := Sprintf(dialect, buf.String(), args)
3✔
952
                if err != nil {
3✔
953
                        b.WriteString("%!(error=" + err.Error() + ")")
×
954
                        continue
×
955
                }
956
                b.WriteString(fieldName + " => " + reflect.TypeOf(scanDest[i]).String())
3✔
957
        }
958
        return b.String()
2✔
959
}
960

961
func cursorResult[T any](cursor *Cursor[T]) (result T, err error) {
18✔
962
        for cursor.Next() {
36✔
963
                result, err = cursor.Result()
18✔
964
                if err != nil {
18✔
965
                        return result, err
×
966
                }
×
967
                break
18✔
968
        }
969
        if cursor.RowCount() == 0 {
18✔
970
                return result, sql.ErrNoRows
×
971
        }
×
972
        return result, cursor.Close()
18✔
973
}
974

975
func cursorResults[T any](cursor *Cursor[T]) (results []T, err error) {
18✔
976
        var result T
18✔
977
        for cursor.Next() {
84✔
978
                result, err = cursor.Result()
66✔
979
                if err != nil {
66✔
980
                        return results, err
×
981
                }
×
982
                results = append(results, result)
66✔
983
        }
984
        return results, cursor.Close()
18✔
985
}
986

987
func execResult(sqlResult sql.Result, queryStats *QueryStats) (Result, error) {
19✔
988
        var err error
19✔
989
        var result Result
19✔
990
        if queryStats.Dialect == DialectSQLite || queryStats.Dialect == DialectMySQL {
34✔
991
                result.LastInsertId, err = sqlResult.LastInsertId()
15✔
992
                if err != nil {
15✔
993
                        return result, err
×
994
                }
×
995
                queryStats.LastInsertId.Valid = true
15✔
996
                queryStats.LastInsertId.Int64 = result.LastInsertId
15✔
997
        }
998
        result.RowsAffected, err = sqlResult.RowsAffected()
19✔
999
        if err != nil {
19✔
1000
                return result, err
×
1001
        }
×
1002
        queryStats.RowsAffected.Valid = true
19✔
1003
        queryStats.RowsAffected.Int64 = result.RowsAffected
19✔
1004
        return result, nil
19✔
1005
}
1006

1007
// FetchExists returns a boolean indicating if running the given Query on the
1008
// given DB returned any results.
1009
func FetchExists(db DB, query Query) (exists bool, err error) {
4✔
1010
        return fetchExists(context.Background(), db, query, 1)
4✔
1011
}
4✔
1012

1013
// FetchExistsContext is like FetchExists but additionally requires a
1014
// context.Context.
1015
func FetchExistsContext(ctx context.Context, db DB, query Query) (exists bool, err error) {
×
1016
        return fetchExists(ctx, db, query, 1)
×
1017
}
×
1018

1019
func fetchExists(ctx context.Context, db DB, query Query, skip int) (exists bool, err error) {
4✔
1020
        dialect := query.GetDialect()
4✔
1021
        queryStats := QueryStats{
4✔
1022
                Dialect: dialect,
4✔
1023
                Exists:  sql.NullBool{Valid: true},
4✔
1024
                Params:  make(map[string][]int),
4✔
1025
        }
4✔
1026

4✔
1027
        // Build query.
4✔
1028
        buf := bufpool.Get().(*bytes.Buffer)
4✔
1029
        buf.Reset()
4✔
1030
        defer bufpool.Put(buf)
4✔
1031
        if dialect == DialectSQLServer {
5✔
1032
                query = Queryf("SELECT CASE WHEN EXISTS ({}) THEN 1 ELSE 0 END", query)
1✔
1033
        } else {
4✔
1034
                query = Queryf("SELECT EXISTS ({})", query)
3✔
1035
        }
3✔
1036
        err = query.WriteSQL(ctx, dialect, buf, &queryStats.Args, queryStats.Params)
4✔
1037
        queryStats.Query = buf.String()
4✔
1038
        if err != nil {
4✔
1039
                return false, err
×
1040
        }
×
1041

1042
        // Setup logger.
1043
        var logSettings LogSettings
4✔
1044
        if logger, ok := db.(SqLogger); ok {
8✔
1045
                logger.SqLogSettings(ctx, &logSettings)
4✔
1046
                if logSettings.IncludeCaller {
8✔
1047
                        queryStats.CallerFile, queryStats.CallerLine, queryStats.CallerFunction = caller(skip + 1)
4✔
1048
                }
4✔
1049
                defer func() {
8✔
1050
                        if logSettings.LogAsynchronously {
4✔
1051
                                go logger.SqLogQuery(ctx, queryStats)
×
1052
                        } else {
4✔
1053
                                logger.SqLogQuery(ctx, queryStats)
4✔
1054
                        }
4✔
1055
                }()
1056
        }
1057

1058
        // Run query.
1059
        if logSettings.IncludeTime {
8✔
1060
                queryStats.StartedAt = time.Now()
4✔
1061
        }
4✔
1062
        var sqlRows *sql.Rows
4✔
1063
        sqlRows, queryStats.Err = db.QueryContext(ctx, queryStats.Query, queryStats.Args...)
4✔
1064
        if logSettings.IncludeTime {
8✔
1065
                queryStats.TimeTaken = time.Since(queryStats.StartedAt)
4✔
1066
        }
4✔
1067
        if queryStats.Err != nil {
4✔
1068
                return false, queryStats.Err
×
1069
        }
×
1070

1071
        for sqlRows.Next() {
8✔
1072
                err = sqlRows.Scan(&exists)
4✔
1073
                if err != nil {
4✔
1074
                        return false, err
×
1075
                }
×
1076
                break
4✔
1077
        }
1078
        queryStats.Exists.Bool = exists
4✔
1079

4✔
1080
        if err := sqlRows.Close(); err != nil {
4✔
1081
                return exists, err
×
1082
        }
×
1083
        if err := sqlRows.Err(); err != nil {
4✔
1084
                return exists, err
×
1085
        }
×
1086
        return exists, nil
4✔
1087
}
1088

1089
// substituteParams will return a new args slice by substituting values from
1090
// the given paramValues. The input args slice is untouched.
1091
func substituteParams(dialect string, args []any, paramIndexes map[string][]int, paramValues map[string]any) ([]any, error) {
21✔
1092
        if len(paramValues) == 0 {
26✔
1093
                return args, nil
5✔
1094
        }
5✔
1095
        newArgs := make([]any, len(args))
16✔
1096
        copy(newArgs, args)
16✔
1097
        var err error
16✔
1098
        for name, value := range paramValues {
65✔
1099
                indexes := paramIndexes[name]
49✔
1100
                for _, index := range indexes {
98✔
1101
                        switch arg := newArgs[index].(type) {
49✔
1102
                        case sql.NamedArg:
46✔
1103
                                arg.Value, err = preprocessValue(dialect, value)
46✔
1104
                                if err != nil {
46✔
1105
                                        return nil, err
×
1106
                                }
×
1107
                                newArgs[index] = arg
46✔
1108
                        default:
3✔
1109
                                value, err = preprocessValue(dialect, value)
3✔
1110
                                if err != nil {
3✔
1111
                                        return nil, err
×
1112
                                }
×
1113
                                newArgs[index] = value
3✔
1114
                        }
1115
                }
1116
        }
1117
        return newArgs, nil
16✔
1118
}
1119

1120
func caller(skip int) (file string, line int, function string) {
39✔
1121
        pc, file, line, _ := runtime.Caller(skip + 1)
39✔
1122
        fn := runtime.FuncForPC(pc)
39✔
1123
        function = fn.Name()
39✔
1124
        return file, line, function
39✔
1125
}
39✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc