Skip to content

Commit 81f25b5

Browse files
committed
Refactor code, use Expr interface for flexibility
1 parent 7128760 commit 81f25b5

9 files changed

+746
-762
lines changed

clause.go

Lines changed: 184 additions & 163 deletions
Large diffs are not rendered by default.

clausekind_string.go

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Package query provides an extensible SQL query builder for PostgreSQL. It
2+
// works by using first class functions to allow for queries to be built up.
3+
// This does not support the entire dialect for PostgreSQL, only a subset that
4+
// would be necessary for CRUD operations.
5+
//
6+
// Let's look at how a simple SELECT query can be built up using this library,
7+
//
8+
// q := query.Select(
9+
// query.Columns("*"),
10+
// query.From("posts"),
11+
// query.Where("user_id", "=", query.Arg(10)),
12+
// query.OrderDesc("created_at"),
13+
// )
14+
//
15+
// we can then use the above to pass a query to our database driver, along with
16+
// the arguments passed to the query,
17+
//
18+
// rows, err := db.Query(q.Build(), q.Args()...)
19+
//
20+
// Calling Build on the query will build up the query string and correctly set
21+
// the parameter arguments in the query. Args will return an interface slice
22+
// containing the arguments given to the query via query.Arg.
23+
//
24+
// We can do even more complex SELECT queries too,
25+
//
26+
// q := query.Select(
27+
// query.Columns("*"),
28+
// query.From("posts"),
29+
// query.Where("id", "IN",
30+
// query.Select(
31+
// query.Columns("id"),
32+
// query.From("post_tags"),
33+
// query.Where("name", "LIKE", query.Arg("%sql%")),
34+
// ),
35+
// ),
36+
// query.OrderDesc("created_at"),
37+
// )
38+
//
39+
// This library makes use of the type Option which is a first class function,
40+
// which takes the current Query an Option is being applied to and returns it.
41+
//
42+
// type Option func(Query) Query
43+
//
44+
// With this we can define our own Option functions to clean up some of the
45+
// queries we want to build.
46+
//
47+
// func Search(col, pattern string) query.Option {
48+
// return func(q query.Query) query.Query {
49+
// if pattern == "" {
50+
// return q
51+
// }
52+
// return query.Where(col, "LIKE", query.Arg("%" + pattern + "%"))(q)
53+
// }
54+
// }
55+
//
56+
// q := query.Select(
57+
// query.Columns("*"),
58+
// query.From("posts"),
59+
// Search("title", "query builder"),
60+
// query.OrderDesc("created_at"),
61+
// )
62+
package query

expr.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package query
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// callExpr is the expression used for calling functions within PostgreSQL
9+
type callExpr struct {
10+
name string
11+
args []Expr
12+
}
13+
14+
type listExpr struct {
15+
items []string
16+
wrap bool
17+
args []interface{}
18+
}
19+
20+
type identExpr string
21+
22+
type argExpr struct {
23+
val interface{}
24+
}
25+
26+
type litExpr struct {
27+
val interface{}
28+
}
29+
30+
// Expr is an expression that exists within the Query being built. This would
31+
// typically be an identifier, literal, argument, function call, or list
32+
// values in queries.
33+
type Expr interface {
34+
// Args returns the arguments that were given to the Query expression.
35+
Args() []interface{}
36+
37+
// Build returns the built up Query expression.
38+
Build() string
39+
}
40+
41+
var (
42+
_ Expr = (*listExpr)(nil)
43+
_ Expr = (*identExpr)(nil)
44+
_ Expr = (*argExpr)(nil)
45+
_ Expr = (*litExpr)(nil)
46+
_ Expr = (*callExpr)(nil)
47+
)
48+
49+
// Columns returns a list expression of the given column names. This will not
50+
// be wrapped in parentheses when built.
51+
func Columns(cols ...string) listExpr {
52+
return listExpr{
53+
items: cols,
54+
wrap: false,
55+
}
56+
}
57+
58+
// Count returns a call expression for the COUNT function on the given columns.
59+
func Count(cols ...string) callExpr {
60+
exprs := make([]Expr, 0, len(cols))
61+
62+
for _, col := range cols {
63+
exprs = append(exprs, Lit(col))
64+
}
65+
66+
return callExpr{
67+
name: "COUNT",
68+
args: exprs,
69+
}
70+
}
71+
72+
// List returns a list expression of the given values. Each item in the
73+
// given list will use the ? placeholder. This will be wrapped in parentheses
74+
// when built.
75+
func List(vals ...interface{}) listExpr {
76+
items := make([]string, 0, len(vals))
77+
78+
for range vals {
79+
items = append(items, "?")
80+
}
81+
return listExpr{
82+
items: items,
83+
wrap: true,
84+
args: vals,
85+
}
86+
}
87+
88+
// Ident returns an identifier expression for the given string. When built
89+
// this will simply use the initial string that was given.
90+
func Ident(s string) identExpr { return identExpr(s) }
91+
92+
// Arg returns an argument expression for the given value. When built this will
93+
// use ? as the placeholder for the argument value.
94+
func Arg(val interface{}) argExpr {
95+
return argExpr{
96+
val: val,
97+
}
98+
}
99+
100+
// Lit returns a literal expression for the given value. This will place the
101+
// literal value into the built up expression string itself, and not use the ?
102+
// placeholder. For example using Lit like so,
103+
//
104+
// Where("deleted_at", "IS NOT", Lit("NULL"))
105+
//
106+
// would result in a WHERE clause being built up like this,
107+
//
108+
// WHERE (deleted_at IS NOT NULL)
109+
func Lit(val interface{}) litExpr {
110+
return litExpr{
111+
val: val,
112+
}
113+
}
114+
115+
func (e listExpr) Args() []interface{} { return e.args }
116+
117+
func (e listExpr) Build() string {
118+
items := strings.Join(e.items, ", ")
119+
120+
if e.wrap {
121+
return "(" + items + ")"
122+
}
123+
return items
124+
}
125+
126+
func (e identExpr) Args() []interface{} { return nil }
127+
func (e identExpr) Build() string { return string(e) }
128+
129+
func (e argExpr) Args() []interface{} { return []interface{}{e.val} }
130+
func (e argExpr) Build() string { return "?" }
131+
132+
func (e litExpr) Args() []interface{} { return nil }
133+
func (e litExpr) Build() string { return fmt.Sprintf("%v", e.val) }
134+
135+
func (e callExpr) Args() []interface{} {
136+
vals := make([]interface{}, 0)
137+
138+
for _, expr := range e.args {
139+
vals = append(vals, expr.Args()...)
140+
}
141+
return vals
142+
}
143+
144+
func (e callExpr) Build() string {
145+
args := make([]string, 0, len(e.args))
146+
147+
for _, arg := range e.args {
148+
args = append(args, arg.Build())
149+
}
150+
return e.name + "(" + strings.Join(args, ", ") + ")"
151+
}

0 commit comments

Comments
 (0)