-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtx_inserter.go
130 lines (109 loc) · 3.44 KB
/
tx_inserter.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
package dal
import (
"context"
"fmt"
)
// Inserter defines a function to insert a single record into a database
type Inserter interface {
// Insert inserts a single record into a database
Insert(ctx context.Context, record Record, opts ...InsertOption) error
}
// MultiInserter defines a function to insert multiple records into a database
type MultiInserter interface {
// InsertMulti inserts multiple record into a database at once if possible, or fallback to batch of single inserts
InsertMulti(ctx context.Context, records []Record, opts ...InsertOption) error
}
// IDGenerator defines a contract for ID generator function
type IDGenerator = func(ctx context.Context, record Record) error
// InsertOptions defines interface for insert options
type InsertOptions interface {
IDGenerator() IDGenerator
}
type insertOptions struct {
idGenerator IDGenerator
}
func (v insertOptions) IDGenerator() IDGenerator {
return v.idGenerator
}
var _ InsertOptions = (*insertOptions)(nil)
// NewInsertOptions creates insert options
func NewInsertOptions(opts ...InsertOption) InsertOptions {
var options insertOptions
for _, o := range opts {
o(&options)
}
return options
}
// InsertOption defines a contract for an insert option
type InsertOption func(options *insertOptions)
type randomStringOptions struct {
length int
prefix string
}
// Length returns a predefined length for a random string. Default is DefaultRandomStringIDLength
func (v randomStringOptions) Length() int {
if v.length == 0 {
return DefaultRandomStringIDLength
}
return v.length
}
// Prefix returns a predefined prefix for a random string
func (v randomStringOptions) Prefix() string {
return v.prefix
}
// RandomStringOptions defines settings for random string
type RandomStringOptions interface {
Prefix() string
Length() int
}
// Prefix sets prefix for a random string
func Prefix(prefix string) func(options *randomStringOptions) {
return func(options *randomStringOptions) {
options.prefix = prefix
}
}
// RandomLength sets prefix for a random string
func RandomLength(length int) func(options *randomStringOptions) {
return func(options *randomStringOptions) {
options.length = length
}
}
type randomStringOption func(opts *randomStringOptions)
// WithIDGenerator sets ID generator for a random string (usually random)
func WithIDGenerator(ctx context.Context, g IDGenerator) KeyOption {
return func(key *Key) error {
if key.ID != nil {
panic("an attempt to set ID generator for a child that already have an ID value")
}
return g(ctx, &record{key: key})
}
}
// InsertWithRandomID inserts a record with a random ID
func InsertWithRandomID(
ctx context.Context,
r Record,
generateID IDGenerator,
attempts int,
exists func(*Key) error,
insert func(Record) error,
) error {
key := r.Key()
// We need a temp record to make sure we do not overwrite data during exists() check
tmp := &record{key: key}
for i := 1; i <= attempts; i++ {
if err := generateID(ctx, tmp); err != nil {
return fmt.Errorf("failed to generate random value: %w", err)
}
if err := exists(key); err == nil {
continue
} else if IsNotFound(err) {
return insert(r) // r shares child with tmp
} else {
r.Key().ID = nil
return fmt.Errorf("failed to check if record exists: %w", err)
}
}
r.Key().ID = nil
return fmt.Errorf("not able to generate unique id: %w: %d", ErrExceedsMaxNumberOfAttempts, attempts)
}
var ErrExceedsMaxNumberOfAttempts = fmt.Errorf("exceeds max number of attempts")