master
go 72 lines 1.67 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package sqlquery
4
5 import (
6 "context"
7 "database/sql"
8 "time"
9 )
10
11 // Queryer is the minimal query interface required by query helpers.
12 type Queryer interface {
13 QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
14 }
15
16 // AssignFunc receives each row value as string and rowEnd=true for the last column.
17 type AssignFunc func(column, value string, rowEnd bool)
18
19 // QueryRows executes query and streams row values through assign.
20 // The returned duration measures query submission latency (QueryContext call).
21 func QueryRows(ctx context.Context, q Queryer, query string, assign AssignFunc, args ...any) (time.Duration, error) {
22 start := time.Now()
23 rows, err := q.QueryContext(ctx, query, args...)
24 queryDuration := time.Since(start)
25 if err != nil {
26 return 0, err
27 }
28 defer func() { _ = rows.Close() }()
29 if err := readRows(rows, assign); err != nil {
30 return queryDuration, err
31 }
32 return queryDuration, nil
33 }
34
35 // readRows scans all rows and invokes assign for every column value.
36 func readRows(rows *sql.Rows, assign AssignFunc) error {
37 if assign == nil {
38 return nil
39 }
40
41 columns, err := rows.Columns()
42 if err != nil {
43 return err
44 }
45
46 values := makeValues(len(columns))
47 for rows.Next() {
48 if err := rows.Scan(values...); err != nil {
49 return err
50 }
51 for i := range values {
52 assign(columns[i], valueToString(values[i]), i == len(values)-1)
53 }
54 }
55 return rows.Err()
56 }
57
58 func valueToString(value any) string {
59 v, ok := value.(*sql.NullString)
60 if !ok || !v.Valid {
61 return ""
62 }
63 return v.String
64 }
65
66 func makeValues(size int) []any {
67 vs := make([]any, size)
68 for i := range vs {
69 vs[i] = &sql.NullString{}
70 }
71 return vs
72 }