-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbuilder.go
131 lines (116 loc) · 4.06 KB
/
builder.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
package sqld
import (
"fmt"
"github.com/Masterminds/squirrel"
)
// TODO: Add input validation for maximum number of selected columns
// TODO: Add SQL injection protection checks for WHERE values
// TODO: Add validation for LIMIT/OFFSET values
// TODO: Add query timeout configuration
// TODO: Add metrics/logging for query performance monitoring
// buildQuery creates a type-safe query for the given model.
// To achieve safety, it does the following:
// - Validates the select fields against the model metadata
// - Converts JSON field names to actual field names for SELECT
// - Converts JSON field names to actual field names for WHERE
// - Other validations -- TODO
func buildQuery[T Model](req QueryRequest) (squirrel.SelectBuilder, error) {
var model T
metadata, err := getModelMetadata(model)
if err != nil {
return squirrel.SelectBuilder{}, fmt.Errorf("failed to get model metadata: %w", err)
}
// Validate select fields
if len(req.Select) == 0 {
return squirrel.SelectBuilder{}, fmt.Errorf("select fields cannot be empty")
}
// Use Postgres placeholder format ($1, $2, etc)
builder := squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
// Handle special "ALL" value in Select
var selectFields []string
if len(req.Select) == 1 && req.Select[0] == SelectAll {
// When "ALL" is specified, include all fields from the model
selectFields = make([]string, 0, len(metadata.Fields))
for _, field := range metadata.Fields {
selectFields = append(selectFields, field.Name)
}
} else {
// Convert JSON field names to actual field names for SELECT
selectFields = make([]string, len(req.Select))
for i, jsonName := range req.Select {
field, ok := metadata.Fields[jsonName]
if !ok {
return squirrel.SelectBuilder{}, fmt.Errorf("invalid field in select: %s", jsonName)
}
selectFields[i] = field.Name
}
}
// Build query with converted field names
query := builder.Select(selectFields...).
From(model.TableName())
// Build WHERE conditions
if len(req.Where) > 0 {
for _, cond := range req.Where {
field, ok := metadata.Fields[cond.Field]
if !ok {
return squirrel.SelectBuilder{}, fmt.Errorf("invalid field in where clause: %s", cond.Field)
}
switch cond.Operator {
case OpEqual:
query = query.Where(squirrel.Eq{field.Name: cond.Value})
case OpNotEqual:
query = query.Where(squirrel.NotEq{field.Name: cond.Value})
case OpGreaterThan:
query = query.Where(squirrel.Gt{field.Name: cond.Value})
case OpLessThan:
query = query.Where(squirrel.Lt{field.Name: cond.Value})
case OpGreaterThanOrEqual:
query = query.Where(squirrel.GtOrEq{field.Name: cond.Value})
case OpLessThanOrEqual:
query = query.Where(squirrel.LtOrEq{field.Name: cond.Value})
case OpLike, OpILike:
op := string(cond.Operator)
query = query.Where(squirrel.Expr(field.Name+" "+op+" ?", cond.Value))
case OpIn:
query = query.Where(squirrel.Eq{field.Name: cond.Value})
case OpNotIn:
query = query.Where(squirrel.NotEq{field.Name: cond.Value})
case OpIsNull:
query = query.Where(squirrel.Eq{field.Name: nil})
case OpIsNotNull:
query = query.Where(squirrel.NotEq{field.Name: nil})
default:
return squirrel.SelectBuilder{}, fmt.Errorf("unsupported operator: %s", cond.Operator)
}
}
}
// Handle ORDER BY clauses
if len(req.OrderBy) > 0 {
for _, orderBy := range req.OrderBy {
field, ok := metadata.Fields[orderBy.Field]
if !ok {
return squirrel.SelectBuilder{}, fmt.Errorf("invalid field in order by clause: %s", orderBy.Field)
}
if orderBy.Desc {
query = query.OrderBy(field.Name + " DESC")
} else {
query = query.OrderBy(field.Name + " ASC")
}
}
}
// Handle LIMIT and OFFSET
if req.Limit != nil {
if *req.Limit < 0 {
return squirrel.SelectBuilder{}, fmt.Errorf("limit must be non-negative")
}
query = query.Limit(uint64(*req.Limit))
}
if req.Offset != nil {
if *req.Offset < 0 {
return squirrel.SelectBuilder{}, fmt.Errorf("offset must be non-negative")
}
query = query.Offset(uint64(*req.Offset))
}
// TODO: Add support for GROUP BY
return query, nil
}