Skip to content

Commit 9657405

Browse files
authored
dbutil: add finalizer for RowIter to panic on dropped rows (#16)
1 parent 60145b5 commit 9657405

File tree

1 file changed

+34
-5
lines changed

1 file changed

+34
-5
lines changed

dbutil/iter.go

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66

77
package dbutil
88

9-
import "errors"
9+
import (
10+
"errors"
11+
"fmt"
12+
"runtime"
13+
14+
"go.mau.fi/util/exzerolog"
15+
)
1016

1117
var ErrAlreadyIterated = errors.New("this iterator has been already iterated")
1218

@@ -46,17 +52,31 @@ type rowIterImpl[T any] struct {
4652
Rows
4753
ConvertRow ConvertRowFn[T]
4854

49-
err error
55+
iterated bool
56+
caller string
57+
err error
5058
}
5159

5260
// NewRowIter creates a new RowIter from the given Rows and scanner function.
5361
func NewRowIter[T any](rows Rows, convertFn ConvertRowFn[T]) RowIter[T] {
54-
return &rowIterImpl[T]{Rows: rows, ConvertRow: convertFn}
62+
return newRowIterWithError(rows, convertFn, nil)
5563
}
5664

5765
// NewRowIterWithError creates a new RowIter from the given Rows and scanner function with default error. If not nil, it will be returned without calling iterator function.
5866
func NewRowIterWithError[T any](rows Rows, convertFn ConvertRowFn[T], err error) RowIter[T] {
59-
return &rowIterImpl[T]{Rows: rows, ConvertRow: convertFn, err: err}
67+
return newRowIterWithError(rows, convertFn, err)
68+
}
69+
70+
func newRowIterWithError[T any](rows Rows, convertFn ConvertRowFn[T], err error) RowIter[T] {
71+
ri := &rowIterImpl[T]{Rows: rows, ConvertRow: convertFn, err: err}
72+
if err == nil {
73+
callerSkip := 2
74+
if pc, file, line, ok := runtime.Caller(callerSkip); ok {
75+
ri.caller = exzerolog.CallerWithFunctionName(pc, file, line)
76+
}
77+
runtime.SetFinalizer(ri, (*rowIterImpl[T]).destroy)
78+
}
79+
return ri
6080
}
6181

6282
func ScanSingleColumn[T any](rows Scannable) (val T, err error) {
@@ -74,13 +94,22 @@ func ScanDataStruct[T NewableDataStruct[T]](rows Scannable) (T, error) {
7494
return val.New().Scan(rows)
7595
}
7696

97+
func (i *rowIterImpl[T]) destroy() {
98+
if !i.iterated {
99+
panic(fmt.Errorf("RowIter created at %s wasn't iterated", i.caller))
100+
}
101+
}
102+
77103
func (i *rowIterImpl[T]) Iter(fn func(T) (bool, error)) error {
78104
if i == nil {
79105
return nil
80106
} else if i.Rows == nil || i.err != nil {
81107
return i.err
82108
}
83-
defer i.Rows.Close()
109+
defer func() {
110+
_ = i.Rows.Close()
111+
i.iterated = true
112+
}()
84113

85114
for i.Rows.Next() {
86115
if item, err := i.ConvertRow(i.Rows); err != nil {

0 commit comments

Comments
 (0)