bqb

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2025 License: MIT Imports: 9 Imported by: 0

README

Basic Query Builder

Go Reference

Hard fork of bqb with a lot of breaking changes.

Why

  1. Simple, lightweight, and fast
  2. Supports any and all syntax by the nature of how it works
  3. Doesn't require learning special syntax or operators

Basic

q := bqb.New("SELECT * FROM places WHERE id = ?", 1234)
sql, params, err := q.ToSQL()

Produces:

SELECT * FROM places WHERE id = ?
PARAMS: [1234]
Escaping ?

Use the double question mark ?? value to escape the ? in Postgres queries. For example:

q := bqb.New("SELECT * FROM places WHERE json_obj_column ?? 'key'")
sql, params, err := q.ToPgSQL()

This query uses the ? operator for jsonb types in Postgres to test an object for the presence of a key. It should not be interpreted as an escaped value by bqb.

SELECT * FROM places WHERE json_obj_column ? 'key'
PARAMS: []

Dialects

bqb supports following dialects to build queries with:

  • SQL — uses '?' placeholders, expands arrays (default)
  • SQLite — uses '?NNN' placeholders, expands arrays
  • PostgreSQL — uses '$NNN' placeholders, doesn't expand arrays

It is possible to define and use custom dialect:

var Custom = Dialect{
	Placeholder: func(w *bytes.Buffer, i int) {
		w.WriteByte('@')
		w.WriteString(strconv.Itoa(i))
	},
	ExpandArrays: true,
}
q := bqb.New("SELECT * FROM pets WHERE name = ?", "garfield")
sel, params, err := q.Build(&Custom)

Produces:

SELECT * FROM pets WHERE name = @1
PARAMS: ["garfield"]

Postgres - ToPgSQL()

The ToPgSQL method wraps Build(&bqb.PostgreSQL):

q := bqb.New("DELETE FROM users").
	Space("WHERE id = ? OR name = ANY(?)", 7, []string{"delete", "remove"}).
	Space("LIMIT ?", 5)
sql, params, err := q.ToPgSQL()

Produces:

DELETE FROM users WHERE id = $1 OR name = ANY($2) LIMIT $3
PARAMS: [7, "delete", "remove", 5]

SQLite - ToSQLite()

The ToSQLite method wraps Build(&bqb.SQLite)

q := bqb.New("INSERT INTO animals (id, name) VALUES").
	Space("(?, ?)", 1, "dolphin").
	Comma("(?, ?)", 2, "duck").
	Comma("(?, ?)", 3, "elephant")
sql, params, err := q.ToSQLite()

Produces:

INSERT INTO animals (id, name) VALUES (?1, ?2), (?3, ?4), (?5, ?6)
PARAMS: [1, "dolphin", 2, "duck", 3, "elephant"]

Types

driver.Valuer

The driver.Valuer interface is supported for types that are able to convert themselves to a sql driver value.

Embedder

bqb provides an Embedder interface for directly replacing ? with a string returned by the RawValue method on the Embedder implementation.

This can be useful for changing sort direction or embedding table and column names.

Since this is a raw value, special attention should be paid to ensure user-input is checked and sanitized.

Query IN

Array and slice arguments are automatically expanded for Dialects with ExpandArrays: true.

q := bqb.New("SELECT * FROM animals WHERE name IN (?)", []string{"dolphin", "duck", "elephant"})
sql, params, _ := q.ToSQL()

Produces:

SELECT * FROM animals WHERE name IN (?, ?, ?)
PARAMS: ["dolphin", "duck", "elephant"]

JSON Arguments

There are two helper structs, JSONMap and JSONList to make JSON conversion a little simpler.

sql, err := bqb.New("INSERT INTO my_table (json_map, json_list) VALUES (?, ?)",
	bqb.JsonMap{"a": 1, "b": []string{"a","b","c"}},
	bqb.JsonList{"string",1,true,nil},
).ToSQL()

Produces:

INSERT INTO my_table (json_map, json_list) VALUES (?, ?)
PARAMS: [`{"a": 1, "b": ["a","b","c"]}`, `["string",1,true,null]`]

Query Building

Since queries are built in an additive way by reference rather than value, it's easy to mutate a query without having to reassign the result.

Basic Example
sel := bqb.New("SELECT")
// later
sel.Space("id")
// even later
sel.Comma("age").Comma("email")

Produces:

SELECT id, age, email
Advanced Example

The Q() function returns an empty Query, that resolves to an empty string if no query parts have been added via methods on the query instance, and becomes the next query part otherwise. For example q := Q() will resolve to an empty string unless parts have been added by one of the methods, but q.Comma("name") would make q.ToSQL() resolve to name.

The Optional(string) functions acts the same way as the Q() function, except that it joins with a space to the next query part if some parts have been added by one of the methods. For example q := Optional("SELECT") will resolve to an empty string if there are not query parts, but q.Space("* FROM my_table") would make q.ToSQL() resolve to SELECT * FROM my_table.

targets := bqb.Q()
if getName {
	targets.Comma("name")
}
if getId {
	targets.Comma("id")
}
if !getName && !getId {
	targets.Comma("*")
}

where := bqb.Optional("WHERE")
if filterAdult {
	adultCond := bqb.New("name = ?", "adult")
	if ageCheck {
		adultCond.And("age > ?", 20)
	}
	where.And("(?)", adultCond)
}
if filterChild {
	where.Or("(name = ? AND age < ?)", "youth", 21)
}

q := bqb.New("SELECT ? FROM my_table ?", targets, where).Space("LIMIT ?", 10)

Assuming all values are true, the query would look like:

SELECT name, id FROM my_table WHERE (name = 'adult' AND age > 20) OR (name = 'youth' AND age < 21) LIMIT 10

If getName and getId are false, the query would be:

SELECT * FROM my_table WHERE (name = 'adult' AND age > 20) OR (name = 'youth' AND age < 21) LIMIT 10

If filterAdult is false, the query would be:

SELECT name, id FROM my_table WHERE (name = 'youth' AND age < 21) LIMIT 10

If all values are false, the query would be:

SELECT * FROM my_table LIMIT 10

Documentation

Overview

Lightweight and easy to use query builder that works with any SQL dialect.

Index

Constants

This section is empty.

Variables

View Source
var (
	// SQL is the default dialect.
	// Uses '?' placeholders, expands arrays.
	SQL = Dialect{
		Placeholder: func(w *bytes.Buffer, i int) {
			w.WriteByte('?')
		},
		ExpandArrays: true,
	}

	// SQLite is the dialect for SQLite.
	// Uses '?NNN' placeholders, expands arrays.
	SQLite = Dialect{
		Placeholder: func(w *bytes.Buffer, i int) {
			w.WriteByte('?')
			w.WriteString(strconv.Itoa(i))
		},
		ExpandArrays: true,
	}

	// PostgreSQL is the dialect for PostgreSQL.
	// Uses '$NNN' placeholders, doesn't expand arrays.
	PostgreSQL = Dialect{
		Placeholder: func(w *bytes.Buffer, i int) {
			w.WriteByte('$')
			w.WriteString(strconv.Itoa(i))
		},
		ExpandArrays: false,
	}
)

Functions

This section is empty.

Types

type Dialect

type Dialect struct {
	Placeholder  func(w *bytes.Buffer, i int)
	ExpandArrays bool
}

Dialect represents SQL dialect.

type Embedded

type Embedded string

Embedded is a string type that is directly embedded into the query. Note: Like Embedder, this is not to be used for untrusted input.

type Embedder

type Embedder interface {
	RawValue() string
}

Embedder embeds a value directly into a query string. Note: Since this is embedded and not bound, attention must be paid to sanitizing this input.

type JSONList

type JSONList []any

JSONList is a type that tells bqb to convert the parameter to a JSON list without requiring reflection.

type JSONMap

type JSONMap map[string]any

JSONMap is a custom type which tells bqb to convert the parameter to a JSON object without requiring reflection.

type Query

type Query struct {
	Parts          []QueryPart
	OptionalPrefix string
}

Query contains all the QueryParts for the query and is the primary struct of the bqb package.

func New

func New(text string, args ...any) *Query

New returns an instance of Query with a single QueryPart.

func Optional

func Optional(prefix string) *Query

Optional returns a query object that has a conditional prefix which only resolves when at least one QueryPart has been added.

func Q

func Q() *Query

Q returns a new empty Query.

func (*Query) And

func (q *Query) And(text string, args ...any) *Query

And joins the current QueryPart to the previous QueryPart with ' AND '.

func (*Query) Build

func (q *Query) Build(dialect *Dialect) (stmt string, args []any, err error)

Build builds the query into a SQL string and bound args using the given dialect.

func (*Query) Comma

func (q *Query) Comma(text string, args ...any) *Query

Comma joins the current QueryPart to the previous QueryPart with a comma.

func (*Query) Concat

func (q *Query) Concat(text string, args ...any) *Query

Concat concatenates the current QueryPart to the previous QueryPart with a zero space string.

func (*Query) Empty

func (q *Query) Empty() bool

Empty returns true if the Query is nil or has a length > 0.

func (*Query) Join

func (q *Query) Join(sep, text string, args ...any) *Query

Join joins the current QueryPart to the previous QueryPart with `sep`.

func (*Query) Len

func (q *Query) Len() int

Len returns the length of Query.Parts.

func (*Query) Or

func (q *Query) Or(text string, args ...any) *Query

Or joins the current QueryPart to the previous QueryPart with ' OR '.

func (*Query) Space

func (q *Query) Space(text string, args ...any) *Query

Space joins the current QueryPart to the previous QueryPart with a space.

func (*Query) ToPgSQL

func (q *Query) ToPgSQL() (stmt string, args []any, err error)

ToPgSQL builds the query into a SQL string and bound args using PostgreSQL dialect.

func (*Query) ToSQL

func (q *Query) ToSQL() (stmt string, args []any, err error)

ToSQL builds the query into a SQL string and bound args using SQL dialect.

func (*Query) ToSQLite

func (q *Query) ToSQLite() (stmt string, args []any, err error)

ToSQLite builds the query into a SQL string and bound args using SQLite dialect.

type QueryPart

type QueryPart struct {
	Text   string
	Params []any
}

QueryPart holds a section of a Query.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL