@@ -5,30 +5,32 @@ import (
5
5
"database/sql"
6
6
"errors"
7
7
"fmt"
8
+ "iter"
9
+ "runtime/debug"
8
10
"strings"
9
11
)
10
12
11
- type db struct {
12
- handle Handle
13
- logger Logger
13
+ type defaultHandle struct {
14
+ pool Pool
15
+ logger logger
14
16
threshold int
15
17
counts map [string ]int
16
18
prepared map [string ]* sql.Stmt
17
19
}
18
20
19
- func New (handle Handle , options ... option ) DB {
21
+ func New (handle Pool , options ... option ) Handle {
20
22
var config configuration
21
23
Options .apply (options ... )(& config )
22
- return & db {
23
- handle : handle ,
24
+ return & defaultHandle {
25
+ pool : handle ,
24
26
logger : config .logger ,
25
27
threshold : config .threshold ,
26
28
counts : make (map [string ]int ),
27
29
prepared : make (map [string ]* sql.Stmt ),
28
30
}
29
31
}
30
32
31
- func (this * db ) prepare (ctx context.Context , rawStatement string ) (* sql.Stmt , error ) {
33
+ func (this * defaultHandle ) prepare (ctx context.Context , rawStatement string ) (* sql.Stmt , error ) {
32
34
if this .threshold < 0 {
33
35
return nil , nil
34
36
}
@@ -40,16 +42,16 @@ func (this *db) prepare(ctx context.Context, rawStatement string) (*sql.Stmt, er
40
42
if ok {
41
43
return statement , nil
42
44
}
43
- statement , err := this .handle .PrepareContext (ctx , rawStatement )
45
+ statement , err := this .pool .PrepareContext (ctx , rawStatement )
44
46
if err != nil {
45
47
return nil , err
46
48
}
47
49
this .prepared [rawStatement ] = statement
48
50
return statement , nil
49
51
}
50
52
51
- func (this * db ) Execute (ctx context.Context , script Script ) (err error ) {
52
- defer func () { err = NormalizeErr (err ) }()
53
+ func (this * defaultHandle ) Execute (ctx context.Context , script Script ) (err error ) {
54
+ defer func () { err = normalizeErr (err ) }()
53
55
statements := script .Statements ()
54
56
parameters := script .Parameters ()
55
57
placeholderCount := strings .Count (statements , "?" )
@@ -64,57 +66,89 @@ func (this *db) Execute(ctx context.Context, script Script) (err error) {
64
66
if prepared != nil {
65
67
_ , err = prepared .ExecContext (ctx , params ... )
66
68
} else {
67
- _ , err = this .handle .ExecContext (ctx , statement , params ... )
69
+ _ , err = this .pool .ExecContext (ctx , statement , params ... )
68
70
}
69
71
if err != nil {
70
72
return err
71
73
}
72
74
}
73
75
return nil
74
76
}
75
- func (this * db ) QueryRow (ctx context.Context , query Query ) (err error ) {
76
- defer func () { err = NormalizeErr (err ) }()
77
+ func (this * defaultHandle ) Populate (ctx context.Context , query Query ) (err error ) {
78
+ defer func () { err = normalizeErr (err ) }()
77
79
statement := query .Statement ()
78
80
prepared , err := this .prepare (ctx , statement )
79
81
if err != nil {
80
82
return err
81
83
}
82
84
parameters := query .Parameters ()
83
- var row * sql.Row
85
+ var rows * sql.Rows
84
86
if prepared != nil {
85
- row = prepared .QueryRowContext (ctx , parameters ... )
87
+ rows , err = prepared .QueryContext (ctx , parameters ... )
86
88
} else {
87
- row = this .handle . QueryRowContext (ctx , statement , parameters ... )
89
+ rows , err = this .pool . QueryContext (ctx , statement , parameters ... )
88
90
}
89
- err = query .Scan (row )
90
- if errors .Is (err , sql .ErrNoRows ) {
91
- return nil
91
+ if err != nil {
92
+ return err
92
93
}
93
- return err
94
+ defer func () { _ = rows .Close () }()
95
+ for rows .Next () {
96
+ err = query .Scan (rows )
97
+ if err != nil {
98
+ return err
99
+ }
100
+ }
101
+ return rows .Err ()
94
102
}
95
- func (this * db ) Query (ctx context.Context , query Query ) (err error ) {
96
- defer func () { err = NormalizeErr (err ) }()
103
+ func (this * defaultHandle ) PopulateRow (ctx context.Context , query Query ) (err error ) {
104
+ defer func () { err = normalizeErr (err ) }()
97
105
statement := query .Statement ()
98
106
prepared , err := this .prepare (ctx , statement )
99
107
if err != nil {
100
108
return err
101
109
}
102
110
parameters := query .Parameters ()
103
- var rows * sql.Rows
111
+ var row * sql.Row
104
112
if prepared != nil {
105
- rows , err = prepared .QueryContext (ctx , parameters ... )
113
+ row = prepared .QueryRowContext (ctx , parameters ... )
106
114
} else {
107
- rows , err = this .handle . QueryContext (ctx , statement , parameters ... )
115
+ row = this .pool . QueryRowContext (ctx , statement , parameters ... )
108
116
}
109
- if err != nil {
110
- return err
117
+ err = query .Scan (row )
118
+ if errors .Is (err , sql .ErrNoRows ) {
119
+ return nil
111
120
}
112
- defer func () { _ = rows .Close () }()
113
- for rows .Next () {
114
- err = query .Scan (rows )
115
- if err != nil {
116
- return err
121
+ return err
122
+ }
123
+
124
+ // interleaveParameters splits the statements (on ';') and pairs each one with its corresponding parameters.
125
+ func interleaveParameters (statements string , parameters ... any ) iter.Seq2 [string , []any ] {
126
+ return func (yield func (string , []any ) bool ) {
127
+ index := 0
128
+ for statement := range strings .SplitSeq (statements , ";" ) {
129
+ if len (strings .TrimSpace (statement )) == 0 {
130
+ continue
131
+ }
132
+ statement += ";" // terminate the statement
133
+ indexOffset := strings .Count (statement , "?" )
134
+ params := parameters [index : index + indexOffset ]
135
+ index += indexOffset
136
+ if ! yield (statement , params ) {
137
+ return
138
+ }
117
139
}
118
140
}
119
- return rows .Err ()
141
+ }
142
+
143
+ // normalizeErr attaches a stack trace to non-nil errors and also normalizes errors that are
144
+ // semantically equal to context.Canceled. At present we are unaware whether this is still a
145
+ // commonly encountered scenario.
146
+ func normalizeErr (err error ) error {
147
+ if err == nil {
148
+ return nil
149
+ }
150
+ if strings .Contains (err .Error (), "operation was canceled" ) {
151
+ return fmt .Errorf ("%w: %w" , context .Canceled , err )
152
+ }
153
+ return fmt .Errorf ("%w\n Stack Trace:\n %s" , err , string (debug .Stack ()))
120
154
}
0 commit comments