Documentation
¶
Overview ¶
SQL Builder: simple SQL query builder. Oriented towards text and writing PLAIN SQL, simplifying parameters, arguments, query interpolation, query composition, and so on. Also provides tools for converting structs into SQL expressions and arguments.
See the sibling library https://github.com/mitranim/gos for scanning SQL rows into structs.
Key Features ¶
• You write plain SQL. There's no DSL in Go.
• Automatically renumerates ordinal parameters such as $1, $2, and so on. In the code, the count always starts at 1.
• Supports named parameters such as :ident, automatically converting them into ordinals.
• Avoids parameter collisions.
• Composable: query objects used as arguments are automatically inserted, combining the arguments and automatically renumerating the parameters.
• Supports converting structs to SQL clauses such as `select a, b, c`, `names (...) values (...)`, etc.
• Supports converting structs to named argument maps.
Examples ¶
See `Query`, `Query.Append`, `Query.AppendNamed` for examples.
Index ¶
- Variables
- func Cols(dest interface{}) string
- func StructMap(input interface{}) map[string]interface{}
- type Err
- type ErrCode
- type IQuery
- type NamedArg
- type NamedArgs
- func (self NamedArgs) Assignments() Query
- func (self NamedArgs) Conditions() Query
- func (self NamedArgs) Every(fun func(NamedArg) bool) bool
- func (self NamedArgs) Names() Query
- func (self NamedArgs) NamesAndValues() Query
- func (self NamedArgs) Some(fun func(NamedArg) bool) bool
- func (self NamedArgs) Values() Query
- type Ord
- type Ords
- func (self *Ords) Append(items ...IQuery)
- func (self Ords) IsEmpty() bool
- func (self Ords) Len() (count int)
- func (self Ords) Or(items ...IQuery) Ords
- func (self *Ords) ParseSlice(vals []string) error
- func (self Ords) QueryAppend(out *Query)
- func (self Ords) RowNumber() Query
- func (self *Ords) UnmarshalJSON(input []byte) error
- type Query
- func (self *Query) Append(src string, args ...interface{})
- func (self *Query) AppendNamed(src string, args map[string]interface{})
- func (self *Query) AppendQuery(query IQuery)
- func (self *Query) Clear()
- func (self Query) QueryAppend(out *Query)
- func (self Query) String() string
- func (self *Query) WrapSelect(exprs string)
- func (self *Query) WrapSelectCols(dest interface{})
- type StrQuery
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var CheckUnused = true
If true (default), unused query parameters cause panics in functions like `Query.Append`. If false, unused parameters are ok. Turning this off can be convenient in development, when changing queries rapidly.
Functions ¶
func Cols ¶
func Cols(dest interface{}) string
Takes a struct and generates a string of column names suitable for inclusion into `select`. Also accepts the following inputs and automatically dereferences them into a struct type:
- Struct pointer.
- Struct slice.
- Struct slice pointer.
Nil slices and pointers are fine, as long as they carry a struct type. Any other input causes a panic.
Should be used in conjunction with `Query`. Also see `Query.WrapSelectCols()`.
Example ¶
package main
import (
"fmt"
"github.com/mitranim/sqlb"
)
func main() {
type Internal struct {
Id string `db:"id"`
Name string `db:"name"`
}
type External struct {
Id string `db:"id"`
Name string `db:"name"`
Internal Internal `db:"internal"`
}
fmt.Println(sqlb.Cols(External{}))
/**
Formatted here for readability:
"id",
"name",
("internal")."id" as "internal.id",
("internal")."name" as "internal.name"
*/
}
func StructMap ¶
func StructMap(input interface{}) map[string]interface{}
Scans a struct, accumulating fields tagged with `db` into a map suitable for `Query.AppendNamed`. The input must be a struct or a struct pointer. A nil pointer is fine and produces an empty non-nil map. Panics on other inputs. Treats embedded structs as part of enclosing structs.
Types ¶
type Err ¶
Type of errors returned by this package.
var ( ErrInvalidInput Err = Err{Code: ErrCodeInvalidInput, Cause: errors.New(`invalid input`)} ErrMissingArgument Err = Err{Code: ErrCodeMissingArgument, Cause: errors.New(`missing argument`)} ErrUnexpectedParameter Err = Err{Code: ErrCodeUnexpectedParameter, Cause: errors.New(`unexpected parameter`)} ErrUnusedArgument Err = Err{Code: ErrCodeUnusedArgument, Cause: errors.New(`unused argument`)} ErrOrdinalOutOfBounds Err = Err{Code: ErrCodeOrdinalOutOfBounds, Cause: errors.New(`ordinal parameter exceeds arguments`)} ErrUnknownField Err = Err{Code: ErrCodeUnknownField, Cause: errors.New(`unknown field`)} ErrInternal Err = Err{Code: ErrCodeInternal, Cause: errors.New(`internal error`)} )
Use blank error variables to detect error types:
if errors.Is(err, sqlb.ErrIndexMismatch) {
// Handle specific error.
}
Note that errors returned by this package can't be compared via `==` because they may include additional details about the circumstances. When compared by `errors.Is`, they compare `.Cause` and fall back on `.Code`.
type ErrCode ¶
type ErrCode string
Error codes. You probably shouldn't use this directly; instead, use the `Err` variables with `errors.Is`.
const ( ErrCodeUnknown ErrCode = "" ErrCodeInvalidInput ErrCode = "InvalidInput" ErrCodeMissingArgument ErrCode = "MissingArgument" ErrCodeUnexpectedParameter ErrCode = "UnexpectedParameter" ErrCodeUnusedArgument ErrCode = "UnusedArgument" ErrCodeOrdinalOutOfBounds ErrCode = "OrdinalOutOfBounds" ErrCodeUnknownField ErrCode = "UnknownField" ErrCodeInternal ErrCode = "Internal" )
type IQuery ¶
type IQuery interface{ QueryAppend(*Query) }
Interface that allows compatibility between different query variants. Subquery insertion / flattening, supported by `Query.Append` and `Query.AppendNamed`, detects instances of this interface, rather than the concrete type `Query`, allowing external code to implement its own variants, wrap `Query`, etc.
WTB better name.
type NamedArg ¶
type NamedArg struct {
Name string
Value interface{}
}
Same as `sql.NamedArg`, with additional methods. See `NamedArgs`.
func (NamedArg) IsNil ¶
Returns true if the value would be equivalent to `null` in SQL. Caution: this is NOT the same as comparing the value to `nil`:
NamedArg{}.Value == nil // true
NamedArg{}.IsNil() // true
NamedArg{Value: (*string)(nil)}.Value == nil // false
NamedArg{Value: (*string)(nil)}.IsNil() // true
type NamedArgs ¶
type NamedArgs []NamedArg
Sequence of named SQL arguments with utility methods for query building. Usually obtained by calling `StructNamedArgs()`.
func StructNamedArgs ¶
func StructNamedArgs(input interface{}) NamedArgs
Scans a struct, converting fields tagged with `db` into a sequence of named `NamedArgs`. The input must be a struct or a struct pointer. A nil pointer is fine and produces a nil result. Panics on other inputs. Treats embedded structs as part of enclosing structs.
func (NamedArgs) Assignments ¶
Returns a query whose string representation is suitable for an SQL `update set` clause, with arguments. Should be included into other queries via `Query.Append` or `Query.AppendNamed`.
For example, this:
val := struct {
One int64 `db:"one"`
Two int64 `db:"two"`
}{
One: 10,
Two: 20,
}
query := StructNamedArgs(val).Assignments()
text := query.String()
args := query.Args
Is equivalent to:
text := `"one" = $1, "two" = $2`
args := []interface{}{10, 20}
Known issue: when empty, this generates an empty query which is invalid SQL. Don't use this when `NamedArgs` is empty.
func (NamedArgs) Conditions ¶
Returns a query whose string representation is suitable for an SQL `where` or `on` clause, with arguments. Should be included into other queries via `Query.Append` or `Query.AppendNamed`.
For example, this:
val := struct {
One int64 `db:"one"`
Two int64 `db:"two"`
Three *int64 `db:"three"`
}{
One: 10,
Two: 20,
}
query := StructNamedArgs(val).Conditions()
text := query.String()
args := query.Args
Is equivalent to:
text := `"one" = $1 and "two" = $2 and "three" is null`
args := []interface{}{10, 20}
func (NamedArgs) Every ¶
Returns true if every argument satisfies the predicate function. Example:
ok := args.Every(NamedArg.IsNil)
func (NamedArgs) Names ¶
Returns a query whose string representation is suitable for an SQL `select` clause. Should be included into other queries via `Query.Append` or `Query.AppendNamed`.
For example, this:
val := struct {
One int64 `db:"one"`
Two int64 `db:"two"`
}{
One: 10,
Two: 20,
}
text := StructNamedArgs(val).Names().String()
Is equivalent to:
text := `"one", "two"`
func (NamedArgs) NamesAndValues ¶
Returns a query whose string representation is suitable for an SQL `insert` clause, with arguments. Should be included into other queries via `Query.Append` or `Query.AppendNamed`.
For example, this:
val := struct {
One int64 `db:"one"`
Two int64 `db:"two"`
}{
One: 10,
Two: 20,
}
query := StructNamedArgs(val).NamesAndValues()
text := query.String()
args := query.Args
Is equivalent to:
text := `("one", "two") values ($1, $2)`
args := []interface{}{10, 20}
func (NamedArgs) Some ¶
Returns true if at least one argument satisfies the predicate function. Example:
ok := args.Some(NamedArg.IsNil)
func (NamedArgs) Values ¶
Returns a query whose string representation is suitable for an SQL `values()` clause, with arguments. Should be included into other queries via `Query.Append` or `Query.AppendNamed`.
For example, this:
val := struct {
One int64 `db:"one"`
Two int64 `db:"two"`
}{
One: 10,
Two: 20,
}
query := StructNamedArgs(val).Values()
text := query.String()
args := query.Args
Is equivalent to:
text := `$1, $2`
args := []interface{}{10, 20}
type Ord ¶ added in v0.1.10
Short for "ordering". Describes an SQL ordering like:
`"some_col" asc`
`("nested")."other_col" desc`
but in a structured format. When encoding for SQL, identifiers are quoted for safety. Identifier case is preserved. Parsing of keyword such as "asc", "desc", "nulls last" is case-insensitive and non-case-preserving since they're converted to bools.
Note on `Desc`: the default value `false` corresponds to "ascending", which is the default in SQL.
Also see `Ords`.
func OrdAscNl ¶ added in v0.1.10
Shortcut:
OrdAscNl(`one`, `two) ≡ Ord{Path: []string{`one`, `two`}, NullsLast: true}
func OrdDesc ¶ added in v0.1.10
Shortcut:
OrdDesc(`one`, `two) ≡ Ord{Path: []string{`one`, `two`}, Desc: true}
func OrdDescNl ¶ added in v0.1.10
Shortcut:
OrdDescNl(`one`, `two) ≡ Ord{Path: []string{`one`, `two`}, Desc: true, NullsLast: true}
func (Ord) AppendBytes ¶ added in v0.1.10
Appends an SQL string to the buffer. See `.String()`.
func (Ord) QueryAppend ¶ added in v0.1.10
Implement `IQuery`, allowing this to be placed in `Ords`.
type Ords ¶ added in v0.1.10
Short for "orderings". Structured representation of an SQL ordering such as:
`order by "some_col" asc` `order by "some_col" asc, "nested"."other_col" desc`
For flexibility, the sequence of `Ords` may include arbitrary SQL expressions expressed as `IQuery` instances. But when decoding external input, every created element is an instance of `Ord`.
When encoding an `Ord` to a string, identifiers are quoted for safety. An ordering with empty `.Items` represents no ordering: "".
`.Type` is used for parsing external input. It must be a struct type. Every field name or path must be found in the struct type, possibly in nested structs. The decoding process will convert every JSON field name into the corresponding DB column name. Identifiers without the corresponding pair of `json` and `db` tags cause a parse error.
`.Lax` allows to ignore unknown fields. Normally, orderings not present in `.Type` generate errors; when `.Lax = true`, they're simply ignored.
Usage for parsing:
input := []byte(`["one asc", "two.three desc"]`)
ords := OrdsFor(SomeStructType{})
err := ords.UnmarshalJSON(input)
panic(err)
The result is equivalent to:
OrdsFrom(OrdAsc(`one`), OrdDesc(`two`, `three`))
Usage for SQL:
text, args := ords.Query()
`Ords` implements `IQuery` and can be directly used as a sub-query:
var query Query query.Append(`select from where $1`, OrdsFrom(OrdAsc(`some_col`)))
func OrdsFor ¶ added in v0.1.10
func OrdsFor(val interface{}) Ords
Shortcut for empty `Ords` intended for parsing. The input is used only as a type carrier. The parsing process will consult the provided type; see `Ords.UnmarshalJSON`.
func (Ords) Or ¶ added in v0.1.10
If empty, replaces items with the provided fallback. Otherwise does nothing.
func (*Ords) ParseSlice ¶ added in v0.1.10
Convenience method for parsing string slices, which may come from URL queries, form-encoded data, and so on.
func (Ords) QueryAppend ¶ added in v0.1.10
Implement `IQuery`, allowing this to be used as a sub-query for `Query`. When used as an argument for `Query.Append` or `Query.AppendNamed`, this will be automatically interpolated.
func (Ords) RowNumber ¶ added in v0.1.10
Returns a query for the Postgres window function `row_number`:
OrdsFrom().RowNumber() -> `0` OrdsFrom(OrdAsc(`col`)).RowNumber() -> `row_number() over (order by "col" asc nulls last)`
As shown above, an empty `Ords` generates a constant `0`. The Postgres query planner should optimize away any ordering by this constant column.
func (*Ords) UnmarshalJSON ¶ added in v0.1.10
Implement decoding from JSON. Consults `.Type` to determine known field paths, and converts them to DB column paths, rejecting unknown identifiers.
type Query ¶
type Query struct {
Text []byte
Args []interface{}
}
Tool for building SQL queries. Makes it easy to append or insert arbitrary SQL code while avoiding common mistakes. Contains both query content (as parsed AST) and arguments.
Automatically renumerates ordinal parameters such as "$1" when appending code, making it easy to avoid mis-numbering. See `.Append()`.
Supports named parameters. See `.AppendNamed()`.
Composable: both `.Append()` and `.AppendNamed()` automatically interpolate sub-queries found in the arguments, combining the arguments and renumerating the parameters as appropriate.
Currently biased towards Postgres-style ordinal parameters such as "$1". The code is always converted to this "canonical" form. This can be rectified if there is enough demand; you can open an issue at https://github.com/mitranim/sqlb/issues.
func QueryNamed ¶ added in v0.1.10
Convenience function for making a `Query` with a single `Query.AppendNamed` invocation.
func QueryOrd ¶ added in v0.1.10
Convenience function for making a `Query` with a single `Query.Append` invocation.
func (*Query) Append ¶
Appends code and arguments. Renumerates ordinal parameters, offsetting them by the previous argument count. The count in the code always starts from `$1`.
Composable: automatically interpolates any instances of `IQuery` found in the arguments, combining the arguments and renumerating the parameters as appropriate.
For example, this:
var query Query query.Append(`where true`) query.Append(`and one = $1`, 10) query.Append(`and two = $1`, 20) // Note the $1. text := query.String() args := query.Args
Is equivalent to this:
text := `where true and one = $1 and two = $2`
args := []interface{}{10, 20}
Panics when: the code is malformed; the code has named parameters; a parameter doesn't have a corresponding argument; an argument doesn't have a corresponding parameter.
func (*Query) AppendNamed ¶
Appends code and named arguments. The code must have named parameters in the form ":identifier". The keys in the arguments map must have the form "identifier", without a leading ":".
Internally, converts named parameters to ordinal parameters of the form `$N`, such as the ones used by `.Append()`.
Composable: automatically interpolates any instances of `IQuery` found in the arguments, combining the arguments and renumerating the parameters as appropriate.
For example, this:
var query Query
query.AppendNamed(
`select col where col = :value`,
map[string]interface{}{"value": 10},
)
text := query.String()
args := query.Args
Is equivalent to this:
text := `select col where col = $1`
args := []interface{}{10}
Panics when: the code is malformed; the code has ordinal parameters; a parameter doesn't have a corresponding argument; an argument doesn't have a corresponding parameter.
func (*Query) AppendQuery ¶
Convenience method, inverse of `IQuery.QueryAppend`. Appends the other query to this one, combining the arguments and renumerating the ordinal parameters as appropriate.
func (*Query) Clear ¶ added in v0.1.3
func (self *Query) Clear()
"Zeroes" the query, keeping any already-allocated capacity. Similar to `query = sqlb.Query{}`, but slightly clearer and marginally more efficient for subsequent query building.
func (Query) QueryAppend ¶ added in v0.1.6
Implement `IQuery`, allowing compatibility between different implementations, wrappers, etc.
func (*Query) WrapSelect ¶
Wraps the query to select only the specified expressions.
For example, this:
var query Query query.Append(`select * from some_table`) query.WrapSelect(`one, two`) text := query.String()
Is equivalent to this:
text := `with _ as (select * from some_table) select one, two from _`
func (*Query) WrapSelectCols ¶
func (self *Query) WrapSelectCols(dest interface{})
Wraps the query to select the fields derived by calling `Cols(dest)`.
For example, this:
var query Query
query.Append(`select * from some_table`)
var out struct{Id int64 `db:"id"`}
query.WrapSelectCols(out)
text := query.String()
Is equivalent to this:
text := `with _ as (select * from some_table) select "id" from _`
type StrQuery ¶ added in v0.1.8
type StrQuery string
Shortcut for interpolating strings into queries.
Because this implements `IQuery`, when used as an ordinal or named parameter ($1 or :named), this will be directly interpolated into the query string and removed from the argument list.
Usage:
var query Query
query.AppendNamed(
`select col where :col = :value`,
map[string]interface{}{"col": StrQuery("rel_id"), "value": 10},
)
func (StrQuery) QueryAppend ¶ added in v0.1.8
Implement `IQuery`.