-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathrow.go
More file actions
127 lines (107 loc) · 3.39 KB
/
Copy pathrow.go
File metadata and controls
127 lines (107 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package scan
import (
"fmt"
"reflect"
)
var zeroValue reflect.Value
func wrapRows(r Rows, allowUnknown bool) (*Row, error) {
cols, err := r.Columns()
if err != nil {
return nil, err
}
return &Row{
r: r,
columns: cols,
scanDestinations: make([]reflect.Value, len(cols)),
scanTargets: make([]any, len(cols)),
allowUnknown: allowUnknown,
}, nil
}
// Row represents a single row from the query and is passed to the [BeforeFunc]
// when sent to a mapper's before function, scans should be scheduled
// with any of the following ScheduleScan methods
// - [*Row.ScheduleScanByName]
// - [*Row.ScheduleScanByNameX]
// - [*Row.ScheduleScanByIndex]
// - [*Row.ScheduleScanByIndexX]
type Row struct {
r Rows
columns []string
scanDestinations []reflect.Value
scanTargets []any
unknownDestinations []string
allowUnknown bool
}
// ScheduleScanByName schedules a scan for the column name into the given value
// val should be a pointer
func (r *Row) ScheduleScanByName(colName string, val any) {
r.ScheduleScanByNameX(colName, reflect.ValueOf(val))
}
// ScheduleScanByNameX schedules a scan for the column name into the given reflect.Value
// val.Kind() should be reflect.Pointer
func (r *Row) ScheduleScanByNameX(colName string, val reflect.Value) {
for i, n := range r.columns {
if n == colName {
r.scanDestinations[i] = val
return
}
}
r.unknownDestinations = append(r.unknownDestinations, colName)
}
// ScheduleScanByIndex schedules a scan for the column number into the given value
// val should be a pointer
func (r *Row) ScheduleScanByIndex(colIndex int, val any) {
r.scanDestinations[colIndex] = reflect.ValueOf(val)
}
// ScheduleScanByIndex schedules a scan for the column number into the given reflect.Value
// val should be a pointer
func (r *Row) ScheduleScanByIndexX(colIndex int, val reflect.Value) {
r.scanDestinations[colIndex] = val
}
// To get a copy of the columns to pass to mapper generators
// since modifing the map can have unintended side effects.
// Ideally, a generator should only call this once
func (r *Row) columnsCopy() []string {
m := make([]string, len(r.columns))
copy(m, r.columns)
return m
}
func (r *Row) scanCurrentRow() error {
if len(r.unknownDestinations) > 0 {
return createError(fmt.Errorf("unknown columns to map to: %v", r.unknownDestinations), r.unknownDestinations...)
}
targets, err := r.createTargets()
if err != nil {
return err
}
err = r.r.Scan(targets...)
if err != nil {
return err
}
// Reset scan destinations in place for the next row. The before hook
// re-populates the same indices deterministically, so zeroing is enough
// (go.mod is go 1.18, so no clear() builtin).
for i := range r.scanDestinations {
r.scanDestinations[i] = zeroValue
}
return nil
}
func (r *Row) createTargets() ([]any, error) {
targets := r.scanTargets
for i, name := range r.columns {
dest := r.scanDestinations[i]
if dest != zeroValue {
targets[i] = dest.Interface()
continue
}
if !r.allowUnknown {
err := fmt.Errorf("no destination for column %s", name)
return nil, createError(err, "no destination", name)
}
// See https://github.com/golang/go/issues/41607
// Some drivers cannot work with nil values, so valid pointers should be
// used for all column targets, even if they are discarded afterwards.
targets[i] = new(any)
}
return targets, nil
}