Documentation ¶
Overview ¶
Package sqlf focuses only on building SQL queries by combining fragments. Low reusability and scalability are the main challenges we face when writing SQL, the package is designed to solve these problems.
Fragment ¶
Unlike any other sql builder or ORMs, Fragment is the only concept you need to learn.
Fragment is usually a part of a SQL query, for example, combining main fragment and any number of condition fragments, we can get a complete query.
query, args, _ := (&sqlf.Fragment{ Raw: `SELECT * FROM foo WHERE #join('#fragment', ' AND ')`, Fragments: []*sqlf.Fragment{ sqlf.Fa(`bar IN (#join('#argDollar', ', '))`, 1, 2, 3), sqlf.Fa(`baz = $1`, true), }, }).Build() fmt.Println(query) fmt.Println(args) // Output: // SELECT * FROM foo WHERE bar IN ($1, $2, $3) AND baz = $4 // [1 2 3 true]
Explanation:
- We pay attention only to the references inside the fragment, for example, use $1 to refer Fragment.Args[0], or ? to refer Fragment.Args in order.
- #join, #fragment, etc., are preprocessing functions, which will be explained later.
Preprocessing Functions ¶
- c, column: Fragment.Columns at index, e.g. #c1
- t, table: Fragment.Tables at index, e.g. #t1
- fragment: Fragment.Fragments at index, e.g. #fragment1
- builder: Fragment.Builders at index, e.g. #builder1
- argDollar: Fragment.Args at index with style $, usually used in #join().
- argQuestion: Fragment.Args at index with style ?, usually used in #join().
- join: Join the template with separator, e.g. #join('#column', ', '), #join('#argDollar', ',', 3), #join('#argDollar', ',', 3, 6)
Note:
- #c1 is equivalent to #c(1), which is a special syntax to call preprocessing functions when an integer (usually an index) is the only argument.
- Expressions in the #join template are functions, not function calls.
- You can register custom functions to the build context, see Context.Funcs.
Example (Basic) ¶
query, args, _ := (&sqlf.Fragment{ Raw: `SELECT * FROM foo WHERE #join('#fragment', ' AND ')`, Fragments: []*sqlf.Fragment{ sqlf.Fa(`bar IN (#join('#argDollar', ', '))`, 1, 2, 3), sqlf.Fa(`baz = $1`, true), }, }).Build() fmt.Println(query) fmt.Println(args)
Output: SELECT * FROM foo WHERE bar IN ($1, $2, $3) AND baz = $4 [1 2 3 true]
Example (Select) ¶
selects := &sqlf.Fragment{ Raw: "SELECT #join('#column', ', ')", } from := &sqlf.Fragment{ Raw: "FROM #t1", } where := &sqlf.Fragment{ Prefix: "WHERE", Raw: "#join('#fragment', ' AND ')", } builder := sqlf.Ff( "#join('#fragment', ' ')", selects, from, where, ) var users sqlf.Table = "users" selects.WithColumns(users.Expressions("id", "name", "email")...) from.WithTables(users) where.AppendFragments(&sqlf.Fragment{ Raw: "#c1 IN (#join('#argDollar', ', '))", Columns: users.Expressions("id"), Args: []any{1, 2, 3}, }) where.AppendFragments(&sqlf.Fragment{ Raw: "#c1 = $1", Columns: users.Expressions("active"), Args: []any{true}, }) query, args, err := builder.Build() if err != nil { panic(err) } fmt.Println(query) fmt.Println(args)
Output: SELECT id, name, email FROM users WHERE id IN ($1, $2, $3) AND active = $4 [1 2 3 true]
Example (ShortcutFuncs) ¶
query, args, _ := sqlf.Ff( `#join('#fragment', ' UNION ')`, sqlf.Fa(`SELECT 1`), sqlf.Fa(`SELECT 2`), ).Build() fmt.Println(query) fmt.Println(args)
Output: SELECT 1 UNION SELECT 2 []
Example (UnalignedJoin) ¶
// this example demonstrates how #join() works between unaligned properties. // it leaves the extra property items (.Args[2:] here) unused, which leads to an error. // to make it work, we use #noUnusedError() to suppress the error. ctx := sqlf.NewContext() ctx.Funcs(sqlf.FuncMap{ "noUnusedError": func(ctx *sqlf.FragmentContext) { for i := 3; i <= ctx.Args.Count(); i++ { ctx.Args.ReportUsed(i) } }, }) foo := sqlf.Table("foo") b := &sqlf.Fragment{ Raw: "#noUnusedError() UPDATE #t1 SET #join('#c=#argDollar', ', ')", Tables: []sqlf.Table{foo}, Columns: foo.Expressions("bar", "baz"), Args: []any{1, 2, 3, true, false}, } query, err := b.BuildContext(ctx) if err != nil { panic(err) } fmt.Println(query) fmt.Println(ctx.Args())
Output: UPDATE foo SET bar=$1, baz=$2 [1 2]
Example (Update) ¶
update := &sqlf.Fragment{ Raw: "UPDATE #t1 SET #join('#c=#argDollar', ', ')", } where := &sqlf.Fragment{ Prefix: "WHERE", Raw: "#join('#fragment', ' AND ')", } // consider wrapping it with your own builder // to provide a more friendly APIs builder := &sqlf.Fragment{ Raw: "#join('#fragment', ' ')", Fragments: []*sqlf.Fragment{ update, where, }, } var users sqlf.Table = "users" update.WithTables(users) update.WithColumns(users.Expressions("name", "email")...) update.WithArgs("alice", "alice@example.org") where.AppendFragments(&sqlf.Fragment{ Raw: "#c1=$1", Columns: users.Expressions("id"), Args: []any{1}, }) query, args, err := builder.Build() if err != nil { panic(err) } fmt.Println(query) fmt.Println(args)
Output: UPDATE users SET name=$1, email=$2 WHERE id=$3 [alice alice@example.org 1]
Index ¶
- Variables
- type ArgsProperty
- type Builder
- type BuildersProperty
- type Column
- type ColumnsProperty
- type Context
- type Fragment
- func (f *Fragment) AppendArgs(args ...any)
- func (f *Fragment) AppendBuilders(builders ...Builder)
- func (f *Fragment) AppendColumns(columns ...*Column)
- func (f *Fragment) AppendFragments(fragments ...*Fragment)
- func (f *Fragment) AppendTables(tables ...Table)
- func (f *Fragment) Build() (query string, args []any, err error)
- func (f *Fragment) BuildContext(ctx *Context) (string, error)
- func (f *Fragment) WithArgs(args ...any)
- func (f *Fragment) WithBuilders(builders ...Builder)
- func (f *Fragment) WithColumns(columns ...*Column)
- func (f *Fragment) WithFragments(fragments ...*Fragment)
- func (f *Fragment) WithTables(tables ...Table)
- type FragmentContext
- type FragmentsProperty
- type FuncMap
- type Table
- type TablesProperty
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidIndex is returned when the reference index is invalid. ErrInvalidIndex = errors.New("invalid index") )
Functions ¶
This section is empty.
Types ¶
type ArgsProperty ¶
type ArgsProperty struct {
// contains filtered or unexported fields
}
ArgsProperty is the args builder
func NewArgsProperty ¶
func NewArgsProperty(args ...any) *ArgsProperty
NewArgsProperty returns a new ArgsBuilder.
func (*ArgsProperty) Build ¶
func (b *ArgsProperty) Build(ctx *Context, index int, defaultStyle syntax.BindVarStyle) (string, error)
Build builds the arg at index.
func (ArgsProperty) CheckUsage ¶
func (b ArgsProperty) CheckUsage() error
CheckUsage checks if all items are used.
func (ArgsProperty) ReportUsed ¶
func (b ArgsProperty) ReportUsed(index int)
ReportUsed reports the item at index is used, starting from 1.
type Builder ¶
type Builder interface { // Build builds and returns the query and args. Build() (query string, args []any, err error) // BuildContext builds the query with the context. // The built args should be committed to the context, which can be // retrieved after building. BuildContext(ctx *Context) (query string, err error) }
Builder is the interface for sql builders.
type BuildersProperty ¶
type BuildersProperty struct {
// contains filtered or unexported fields
}
BuildersProperty is the Builders property
func NewBuildersProperty ¶
func NewBuildersProperty(builders ...Builder) *BuildersProperty
NewBuildersProperty returns a new BuildersProperty.
func (*BuildersProperty) Build ¶
func (b *BuildersProperty) Build(ctx *Context, index int) (string, error)
Build builds the builder at index.
func (BuildersProperty) CheckUsage ¶
func (b BuildersProperty) CheckUsage() error
CheckUsage checks if all items are used.
func (BuildersProperty) Count ¶
func (b BuildersProperty) Count() int
Count returns the count of items.
func (BuildersProperty) ReportUsed ¶
func (b BuildersProperty) ReportUsed(index int)
ReportUsed reports the item at index is used, starting from 1.
type ColumnsProperty ¶
type ColumnsProperty struct {
// contains filtered or unexported fields
}
ColumnsProperty is the columns property
func NewColumnsProperty ¶
func NewColumnsProperty(columns ...*Column) *ColumnsProperty
NewColumnsProperty returns a new ColumnsProperty.
func (*ColumnsProperty) Build ¶
func (b *ColumnsProperty) Build(ctx *Context, index int) (string, error)
Build builds the column at index.
func (ColumnsProperty) CheckUsage ¶
func (b ColumnsProperty) CheckUsage() error
CheckUsage checks if all items are used.
func (ColumnsProperty) Count ¶
func (b ColumnsProperty) Count() int
Count returns the count of items.
func (ColumnsProperty) ReportUsed ¶
func (b ColumnsProperty) ReportUsed(index int)
ReportUsed reports the item at index is used, starting from 1.
type Context ¶
type Context struct { // BindVarStyle overrides bindvar styles of all fragments. // if not set, the first bindvar style encountered when // building is applied. BindVarStyle syntax.BindVarStyle // contains filtered or unexported fields }
Context is the global context shared between all fragments building.
func (*Context) CommitArg ¶
func (c *Context) CommitArg(arg any, defaultStyle syntax.BindVarStyle) string
CommitArg commits an built arg to the context and returns the built bindvar. defaultStyle is used only when no style is set in the context and no style is seen before.
func (*Context) Funcs ¶
Funcs adds the preprocessing functions to the context.
The function name is case sensitive, only letters and underscore are allowed.
Allowed function signatures:
func(/* args... */) (string, error) func(/* args... */) string func(/* args... */)
Allowed argument types:
- number types: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64,float32, float64
- string
- bool
- *sqlf.FragmentContext: allowed only as the first argument
Here are examples of legal names and function signatures:
funcs := sqlf.FuncMap{ // #number1, #join('#number', ', ') "number": func(i int) (string, error) {/* ... */}, // #myBuilder1, #join('#myBuilder', ', ') "myBuilder": func(ctx *sqlf.FragmentContext, i int) (string, error) {/* ... */}, // #string('string') "string": func(str string) (string, error) {/* ... */}, // #numbers(1,2) "numbers": func(ctx *sqlf.FragmentContext, a, b int) string {/* ... */}, }
Example ¶
// this example shows how to use Global Args by using // *sqlf.ArgsProperty and custom function, so that we // don't have to put Args into every fragment, which leads // to a list of redundant args. ctx := sqlf.NewContext() ids := sqlf.NewArgsProperty(1, 2, 3) err := ctx.Funcs(sqlf.FuncMap{ "_id": func(i int) (string, error) { return ids.Build(ctx, i, syntax.Dollar) }, }) if err != nil { panic(err) } fragment := &sqlf.Fragment{ Raw: "#join('#fragment', '\nUNION\n')", Fragments: []*sqlf.Fragment{ {Raw: "SELECT id, 'foo' typ, count FROM foo WHERE id IN (#join('#_id', ', '))"}, {Raw: "SELECT id, 'bar' typ, count FROM bar WHERE id IN (#join('#_id', ', '))"}, }, } query, err := fragment.BuildContext(ctx) if err != nil { panic(err) } args := ctx.Args() fmt.Println(query) fmt.Println(args)
Output: SELECT id, 'foo' typ, count FROM foo WHERE id IN ($1, $2, $3) UNION SELECT id, 'bar' typ, count FROM bar WHERE id IN ($1, $2, $3) [1 2 3]
type Fragment ¶
type Fragment struct { Raw string // Raw string support bind vars and preprocessing functions. Args []any // Args to be referred by the Raw, e.g.: ?, $1 Columns []*Column // Columns to be referred by the Raw, e.g.: #c1, #column2 Tables []Table // Table names / alias to be referred by the Raw, e.g.: #t1, #table2 Fragments []*Fragment // Fragments to be referred by the Raw, e.g.: #fragment1, #fragment2 Builders []Builder // Builders to be referred by the Raw, e.g.: #builder1, #builder2 Prefix string // Prefix is added before the built fragment only if which is not empty. Suffix string // Suffix is added after the built fragment only if which is not empty. }
Fragment is the builder for a part of or even the full query, it allows you to write and combine fragments with freedom.
func (*Fragment) AppendArgs ¶
AppendArgs appends args to the fragment. Args are used to be referred by the Raw, e.g.: ?, $1
func (*Fragment) AppendBuilders ¶
AppendBuilders appends builders to the fragment. Builders are used to be referred by the Raw, e.g.: #builder1, #builder2
func (*Fragment) AppendColumns ¶
AppendColumns appends columns to the fragment. Columns are used to be referred by the Raw, e.g.: #c1, #column2
func (*Fragment) AppendFragments ¶
AppendFragments appends fragments to the fragment. Fragments are used to be referred by the Raw, e.g.: #fragment1, #fragment2
func (*Fragment) AppendTables ¶
AppendTables appends tables to the fragment. Tables are used to be referred by the Raw, e.g.: #t1, #table2
func (*Fragment) BuildContext ¶
BuildContext builds the fragment with context.
func (*Fragment) WithArgs ¶
WithArgs replace f.Args with the args Args are used to be referred by the Raw, e.g.: ?, $1
func (*Fragment) WithBuilders ¶
WithBuilders replace f.Builders with the builders Builders are used to be referred by the Raw, e.g.: #builder1, #builder2
func (*Fragment) WithColumns ¶
WithColumns replace f.Columns with the columns Columns are used to be referred by the Raw, e.g.: #c1, #column2
func (*Fragment) WithFragments ¶
WithFragments replace f.Fragments with the fragments Fragments are used to be referred by the Raw, e.g.: #fragment1, #fragment2
func (*Fragment) WithTables ¶
WithTables replace f.Tables with the tables Tables are used to be referred by the Raw, e.g.: #t1, #table2
type FragmentContext ¶
type FragmentContext struct { Global *Context // global context Raw string Args *ArgsProperty Columns *ColumnsProperty Tables *TablesProperty Fragments *FragmentsProperty Builders *BuildersProperty }
FragmentContext is the FragmentContext for current fragment building.
func (*FragmentContext) CheckUsage ¶
func (c *FragmentContext) CheckUsage() error
CheckUsage checks if all properties are used.
type FragmentsProperty ¶
type FragmentsProperty struct {
// contains filtered or unexported fields
}
FragmentsProperty is the fragments property
func NewFragmentsProperty ¶
func NewFragmentsProperty(fragments ...*Fragment) *FragmentsProperty
NewFragmentsProperty returns a new FragmentsProperty.
func (*FragmentsProperty) Build ¶
func (b *FragmentsProperty) Build(ctx *Context, index int) (string, error)
Build builds the fragment at index.
func (FragmentsProperty) CheckUsage ¶
func (b FragmentsProperty) CheckUsage() error
CheckUsage checks if all items are used.
func (FragmentsProperty) Count ¶
func (b FragmentsProperty) Count() int
Count returns the count of items.
func (FragmentsProperty) ReportUsed ¶
func (b FragmentsProperty) ReportUsed(index int)
ReportUsed reports the item at index is used, starting from 1.
type Table ¶
type Table string
Table is a table identifier, it can be a table name or an alias.
func (Table) Column ¶
Column returns a column of the table. It adds table prefix to the column name, e.g.: "id" -> "t.id".
For example:
t := Table("t") // these two are equivalent t.Column("id") // "t.id" t.Expression("#t1.id") // "t.id"
If you want to use the column name directly, try:
t.Expressions("id") // "id"
func (Table) Columns ¶
Columns returns columns of the table from names. It adds table prefix to the column name, e.g.: "id" -> "t.id".
For example:
t := Table("t") // these two are equivalent t.Columns("id", "name") // "t.id", "t.name" t.Expressions("#t1.id", "#t1.name") // "t.id", "t.name"
If you want to use the column name directly, try:
t.Expressions("id", "name") // "id", "name"
func (Table) Expression ¶
Expression returns a column of the table from the expression it accepts bindvars and the preprocessor #t1 which is implicit in t.
For example:
t := Table("t") t.Expression("id") // "id" t.Expression("#t1.id") // "table.id" t.Expression("#t1.id") // "t.id" t.Expression("COALESCE(#t1.id,0)") // "COALESCE(t.id,0)" t.Expression("#t1.deteled_at > $1", 1) // "t.deteled_at > $1"
func (Table) Expressions ¶
Expressions returns columns of the table from the expression, it accepts bindvars and the preprocessor #t1 which is implicit in t.
For example:
t := Table("t") t.Expressions("id", "deteled_at") // "id", "deteled_at" t.Expressions("#t1.id", "#t1.deteled_at") // "table.id", "table.deteled_at" t.Expressions("#t1.id", "#t1.deteled_at") // "t.id", "t.deteled_at" t.Expressions("COALESCE(#t1.id,0)") // "COALESCE(t.id,0)"
type TablesProperty ¶
type TablesProperty struct {
// contains filtered or unexported fields
}
TablesProperty is the tables property
func NewTablesProperty ¶
func NewTablesProperty(tables ...Table) *TablesProperty
NewTablesProperty returns a new TablesProperty.
func (*TablesProperty) Build ¶
func (b *TablesProperty) Build(ctx *Context, index int) (string, error)
Build builds the table at index.
func (TablesProperty) CheckUsage ¶
func (b TablesProperty) CheckUsage() error
CheckUsage checks if all items are used.
func (TablesProperty) ReportUsed ¶
func (b TablesProperty) ReportUsed(index int)
ReportUsed reports the item at index is used, starting from 1.