-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdatabase.go
More file actions
223 lines (189 loc) · 7.08 KB
/
database.go
File metadata and controls
223 lines (189 loc) · 7.08 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
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package limen
import (
"context"
"strings"
)
type DatabaseAdapter interface {
Create(ctx context.Context, tableName SchemaTableName, data map[string]any) error
FindOne(ctx context.Context, tableName SchemaTableName, conditions []Where, orderBy []OrderBy) (map[string]any, error)
FindMany(ctx context.Context, tableName SchemaTableName, conditions []Where, options *QueryOptions) ([]map[string]any, error)
Update(ctx context.Context, tableName SchemaTableName, conditions []Where, updates map[string]any) error
Delete(ctx context.Context, tableName SchemaTableName, conditions []Where) error
Exists(ctx context.Context, tableName SchemaTableName, conditions []Where) (bool, error)
Count(ctx context.Context, tableName SchemaTableName, conditions []Where) (int64, error)
}
// DatabaseTx represents a database transaction
type DatabaseTx interface {
DatabaseAdapter
Commit() error
Rollback() error
}
// TransactionalAdapter is implemented by adapters that support transactions
type TransactionalAdapter interface {
BeginTx(ctx context.Context) (DatabaseTx, error)
}
// Where represents a typed condition for database queries
type Where struct {
Column string `json:"column"`
Operator Operator `json:"operator"` // "eq" by default
Value any `json:"value"` // string | number | boolean | []string | []number | time.Time | nil
Connector Connector `json:"connector"` // "AND" by default, "OR" for multiple conditions
}
// Operator defines the comparison operation
type Operator string
const (
OpEq Operator = "eq" // equals
OpNe Operator = "ne" // not equals
OpLt Operator = "lt" // less than
OpLte Operator = "lte" // less than or equal
OpGt Operator = "gt" // greater than
OpGte Operator = "gte" // greater than or equal
OpIn Operator = "in" // in array
OpNotIn Operator = "not_in" // not in array
OpContains Operator = "contains" // contains substring
OpStartsWith Operator = "starts_with" // starts with
OpEndsWith Operator = "ends_with" // ends with
OpIsNull Operator = "is_null" // is null
OpIsNotNull Operator = "is_not_null" // is not null
)
// Connector defines how conditions are joined
type Connector string
const (
ConnectorAnd Connector = "AND"
ConnectorOr Connector = "OR"
)
// GroupConditionsByConnector splits conditions into groups: each group is either a single
// condition (AND) or a run of conditions connected by a connector (AND or OR). Groups are AND'd in order.
// E.g. [A, B.Or(), C, D] → [[A], [B,C], [D]] meaning (A) AND (B OR C) AND (D).
// Adapters can use this to build WHERE clauses with consistent OR precedence.
func GroupConditionsByConnector(conditions []Where) [][]Where {
if len(conditions) == 0 {
return nil
}
var groups [][]Where
var current []Where
for i, c := range conditions {
if i == 0 {
current = []Where{c}
continue
}
if c.Connector == ConnectorOr {
current = append(current, c)
continue
}
groups = append(groups, current)
current = []Where{c}
}
if len(current) > 0 {
groups = append(groups, current)
}
return groups
}
// BuildGroupClause builds a single connector clause from a group of conditions.
// buildCondition is adapter-specific (e.g. column quoting, placeholder style). Returns combined
// clause and args; caller may wrap in parens when len(group) > 1 (e.g. for SQL).
func BuildGroupClause(group []Where, buildCondition func(Where) (string, []any)) (clause string, args []any) {
var clauses []string
for _, c := range group {
part, partArgs := buildCondition(c)
if part == "" {
continue
}
clauses = append(clauses, part)
args = append(args, partArgs...)
}
if len(clauses) == 0 {
return "", nil
}
if len(clauses) == 1 {
return clauses[0], args
}
return strings.Join(clauses, " OR "), args
}
// BuildWhereFromGroups builds a full WHERE expression by joining group clauses with AND.
// buildGroup turns each group into (clause, args); empty clauses are skipped.
func BuildWhereFromGroups(groups [][]Where, buildGroup func([]Where) (string, []any)) (clause string, args []any) {
var parts []string
for _, group := range groups {
c, groupArgs := buildGroup(group)
if c == "" {
continue
}
parts = append(parts, c)
args = append(args, groupArgs...)
}
return strings.Join(parts, " AND "), args
}
type OrderByDirection string
const (
OrderByAsc OrderByDirection = "ASC" // order by ascending i.e oldest at top
OrderByDesc OrderByDirection = "DESC" // order by descending i.e newest at top
)
type OrderBy struct {
Column string
Direction OrderByDirection
}
// QueryOptions for additional query parameters
type QueryOptions struct {
Limit int
Offset int
OrderBy []OrderBy
}
// Helper functions for building conditions
// Eq creates an equality condition
func Eq(column string, value any) Where {
return Where{Column: column, Operator: OpEq, Value: value, Connector: ConnectorAnd}
}
// Ne creates a not-equals condition
func Ne(column string, value any) Where {
return Where{Column: column, Operator: OpNe, Value: value, Connector: ConnectorAnd}
}
// Lt creates a less-than condition
func Lt(column string, value any) Where {
return Where{Column: column, Operator: OpLt, Value: value, Connector: ConnectorAnd}
}
// Lte creates a less-than-or-equal condition
func Lte(column string, value any) Where {
return Where{Column: column, Operator: OpLte, Value: value, Connector: ConnectorAnd}
}
// Gt creates a greater-than condition
func Gt(column string, value any) Where {
return Where{Column: column, Operator: OpGt, Value: value, Connector: ConnectorAnd}
}
// Gte creates a greater-than-or-equal condition
func Gte(column string, value any) Where {
return Where{Column: column, Operator: OpGte, Value: value, Connector: ConnectorAnd}
}
// In creates an IN condition
func In(column string, values []any) Where {
return Where{Column: column, Operator: OpIn, Value: values, Connector: ConnectorAnd}
}
// NotIn creates a NOT IN condition
func NotIn(column string, values []any) Where {
return Where{Column: column, Operator: OpNotIn, Value: values, Connector: ConnectorAnd}
}
// Contains creates a contains substring condition
func Contains(column, value string) Where {
return Where{Column: column, Operator: OpContains, Value: value, Connector: ConnectorAnd}
}
// StartsWith creates a starts-with condition
func StartsWith(column, value string) Where {
return Where{Column: column, Operator: OpStartsWith, Value: value, Connector: ConnectorAnd}
}
// EndsWith creates an ends-with condition
func EndsWith(column, value string) Where {
return Where{Column: column, Operator: OpEndsWith, Value: value, Connector: ConnectorAnd}
}
// IsNull creates an IS NULL condition
func IsNull(column string) Where {
return Where{Column: column, Operator: OpIsNull, Value: nil, Connector: ConnectorAnd}
}
// IsNotNull creates an IS NOT NULL condition
func IsNotNull(column string) Where {
return Where{Column: column, Operator: OpIsNotNull, Value: nil, Connector: ConnectorAnd}
}
// Or modifier to change connector to OR
func (w Where) Or() Where {
w.Connector = ConnectorOr
return w
}