-
Notifications
You must be signed in to change notification settings - Fork 4
/
mapper.go
168 lines (140 loc) · 4.2 KB
/
mapper.go
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package scan
import (
"context"
"fmt"
"reflect"
"strconv"
)
type (
cols = []string
visited map[reflect.Type]int
)
func (v visited) copy() visited {
v2 := make(visited, len(v))
for t, c := range v {
v2[t] = c
}
return v2
}
type mapinfo struct {
name string
position []int
init [][]int
isPointer bool
}
type mapping []mapinfo
func (m mapping) cols() []string {
cols := make([]string, len(m))
for i, info := range m {
cols[i] = info.name
}
return cols
}
// Mapper is a function that return the mapping functions.
// Any expensive operation, like reflection should be done outside the returned
// function.
// It is called with the columns from the query to get the mapping functions
// which is then used to map every row.
//
// The Mapper does not return an error itself to make it less cumbersome
// It is recommended to instead return a function that returns an error
// the [ErrorMapper] is provider for this
type Mapper[T any] func(context.Context, cols) (before BeforeFunc, after func(any) (T, error))
// BeforeFunc is returned by a mapper and is called before a row is scanned
// Scans should be scheduled with either
// the [*Row.ScheduleScan] or [*Row.ScheduleScanx] methods
type BeforeFunc = func(*Row) (link any, err error)
// The generator function does not return an error itself to make it less cumbersome
// so we return a function that only returns an error instead
// This function makes it easy to return this error
func ErrorMapper[T any](err error, meta ...string) (func(*Row) (any, error), func(any) (T, error)) {
err = createError(err, meta...)
return func(v *Row) (any, error) {
return nil, err
}, func(any) (T, error) {
var t T
return t, err
}
}
// Returns a [MappingError] with some optional metadata
func createError(err error, meta ...string) error {
if me, ok := err.(*MappingError); ok && len(meta) == 0 {
return me
}
return &MappingError{cause: err, meta: meta}
}
// MappingError wraps another error and holds some additional metadata
type MappingError struct {
meta []string // easy compare
cause error
}
// Unwrap returns the wrapped error
func (m *MappingError) Unwrap() error {
return m.cause
}
// Error implements the error interface
func (m *MappingError) Error() string {
if m.cause == nil {
return ""
}
return m.cause.Error()
}
// For queries that return only one column
// throws an error if there is more than one column
func SingleColumnMapper[T any](ctx context.Context, c cols) (before func(*Row) (any, error), after func(any) (T, error)) {
if len(c) != 1 {
err := fmt.Errorf("Expected 1 column but got %d columns", len(c))
return ErrorMapper[T](err, "wrong column count", "1", strconv.Itoa(len(c)))
}
return func(v *Row) (any, error) {
var t T
v.ScheduleScan(c[0], &t)
return &t, nil
}, func(v any) (T, error) {
return *(v.(*T)), nil
}
}
// Map a column by name.
func ColumnMapper[T any](name string) func(ctx context.Context, c cols) (before func(*Row) (any, error), after func(any) (T, error)) {
return func(ctx context.Context, c cols) (before func(*Row) (any, error), after func(any) (T, error)) {
return func(v *Row) (any, error) {
var t T
v.ScheduleScan(name, &t)
return &t, nil
}, func(v any) (T, error) {
return *(v.(*T)), nil
}
}
}
// Maps each row into []any in the order
func SliceMapper[T any](ctx context.Context, c cols) (before func(*Row) (any, error), after func(any) ([]T, error)) {
return func(v *Row) (any, error) {
row := make([]T, len(c))
for index, name := range c {
v.ScheduleScan(name, &row[index])
}
return row, nil
}, func(v any) ([]T, error) {
return v.([]T), nil
}
}
// Maps all rows into map[string]T
// Most likely used with interface{} to get a map[string]interface{}
func MapMapper[T any](ctx context.Context, c cols) (before func(*Row) (any, error), after func(any) (map[string]T, error)) {
return func(v *Row) (any, error) {
row := make([]*T, len(c))
for index, name := range c {
var t T
v.ScheduleScan(name, &t)
row[index] = &t
}
return row, nil
}, func(v any) (map[string]T, error) {
row := make(map[string]T, len(c))
slice := v.([]*T)
for index, name := range c {
row[name] = *slice[index]
}
return row, nil
}
}