-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathquarry.go
More file actions
170 lines (149 loc) · 4.76 KB
/
Copy pathquarry.go
File metadata and controls
170 lines (149 loc) · 4.76 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
// Package quarry provides a small, explicit Go SQL composition toolkit.
//
// Quarry keeps SQL visible, binds values explicitly, and stays close to
// database/sql instead of trying to become an ORM or schema modeler.
package quarry
import (
"errors"
"fmt"
)
// Dialect identifies the SQL dialect Quarry should render for.
type Dialect string
const (
// Postgres renders PostgreSQL placeholders and dialect-specific SQL.
Postgres Dialect = "postgres"
// MySQL renders MySQL-style placeholders and compatible SQL.
MySQL Dialect = "mysql"
// SQLite renders SQLite-style placeholders and compatible SQL.
SQLite Dialect = "sqlite"
)
// Feature identifies a dialect capability Quarry may need to gate.
type Feature string
const (
// FeatureReturning reports whether RETURNING is supported.
FeatureReturning Feature = "returning"
// FeatureILike reports whether ILIKE is supported natively.
FeatureILike Feature = "ilike"
// FeatureAny reports whether = ANY(...) is supported.
FeatureAny Feature = "any"
)
var (
// ErrInvalidIdentifier reports a rejected identifier value.
ErrInvalidIdentifier = errors.New("invalid identifier")
// ErrUnsupportedFeature reports that the active dialect cannot render a feature.
ErrUnsupportedFeature = errors.New("unsupported dialect feature")
// ErrInvalidBuilderState reports a builder that cannot be rendered as configured.
ErrInvalidBuilderState = errors.New("invalid builder state")
// ErrPlaceholderMismatch reports a placeholder / argument count mismatch.
ErrPlaceholderMismatch = errors.New("placeholder mismatch")
)
// SQLer is the shared contract for anything that can render SQL and bound args.
type SQLer interface {
ToSQL() (string, []any, error)
}
// Query keeps the subquery-oriented API readable without adding a second contract.
type Query = SQLer
// Quarry carries the selected dialect and manufactures builders from it.
type Quarry struct {
dialect Dialect
err error
}
// Dialect returns the configured dialect for the receiver.
func (q *Quarry) Dialect() Dialect {
if q == nil {
return ""
}
return q.dialect
}
// Name returns the canonical dialect name.
func (d Dialect) Name() string {
return string(d)
}
// Placeholder renders the dialect's positional placeholder token.
func (d Dialect) Placeholder(n int) string {
switch d {
case Postgres:
return fmt.Sprintf("$%d", n)
case MySQL, SQLite:
return "?"
default:
return ""
}
}
// QuoteIdent returns the dialect-specific quoted identifier.
func (d Dialect) QuoteIdent(ident string) (string, error) {
if err := validateIdentifier(ident); err != nil {
return "", err
}
switch d {
case MySQL:
return "`" + ident + "`", nil
case Postgres, SQLite:
return `"` + ident + `"`, nil
default:
return "", fmt.Errorf("quarry: unsupported dialect %q: %w", d, ErrUnsupportedFeature)
}
}
// Supports reports whether the dialect can render the requested feature.
func (d Dialect) Supports(feature Feature) bool {
switch d {
case Postgres:
switch feature {
case FeatureReturning, FeatureILike, FeatureAny:
return true
}
case SQLite:
switch feature {
case FeatureReturning:
return true
}
case MySQL:
return false
}
return false
}
// New creates a Quarry configured for the supplied dialect.
func New(d Dialect) *Quarry {
q := &Quarry{dialect: d}
if !isSupportedDialect(d) {
// Defer the unsupported-dialect failure until rendering so builder code can stay fluent.
q.err = fmt.Errorf("quarry: unsupported dialect %q: %w", d, ErrUnsupportedFeature)
}
return q
}
// isSupportedDialect reports whether Quarry knows how to render the dialect.
func isSupportedDialect(d Dialect) bool {
switch d {
case Postgres, MySQL, SQLite:
return true
default:
return false
}
}
// errOrNil returns the constructor-time error, if any.
func (q *Quarry) errOrNil() error {
if q == nil {
return fmt.Errorf("quarry: nil quarry")
}
if q.dialect == "" && q.err == nil {
// Treat the zero-value root as invalid so callers must opt into an explicit dialect.
return fmt.Errorf("quarry: zero-value quarry requires quarry.New: %w", ErrInvalidBuilderState)
}
return q.err
}
// Select starts a SELECT builder that inherits the receiver's dialect.
func (q *Quarry) Select(cols ...any) *SelectBuilder {
return &SelectBuilder{q: q, cols: append([]any(nil), cols...)}
}
// InsertInto starts an INSERT builder that inherits the receiver's dialect.
func (q *Quarry) InsertInto(table any) *InsertBuilder {
return &InsertBuilder{q: q, table: table}
}
// Update starts an UPDATE builder that inherits the receiver's dialect.
func (q *Quarry) Update(table any) *UpdateBuilder {
return &UpdateBuilder{q: q, table: table}
}
// DeleteFrom starts a DELETE builder that inherits the receiver's dialect.
func (q *Quarry) DeleteFrom(table any) *DeleteBuilder {
return &DeleteBuilder{q: q, table: table}
}