package zdb
import "zgo.at/zdb"
Package zdb provides a nice API to interact with SQL databases in Go.
All query functions (Exec, NumRows, InsertID Get, Select, Query) use named parameters (:name)used if params contains a map or struct; positional parameters (? or $1) are used if it doesn't. You can add multiple structs or maps, but mixing named and positional parameters is not allowed.
Everything between {{:name ..}} is parsed as a conditional; for example {{:foo query}} will only be added if "foo" from params is true or not a zero type. Conditionals only work with named parameters.
If the query starts with "load:" then it's loaded from the filesystem or embedded files; see Load() for details.
Additional DumpArgs can be added to "dump" information to stderr for testing and debugging:
DumpLocation Show location of Dump call. DumpQuery Show the query DumpExplain Show query plain (WILL RUN QUERY TWICE!) DumpResult Show the query result (WILL RUN QUERY TWICE!) DumpVertical Show results in vertical format. DumpCSV Print query result as CSV. DumpJSON Print query result as JSON. DumpHTML Print query result as a HTML table. DumpAll Dump all we can.
Running the query twice for a select is usually safe (just slower), but running insert, update, or delete twice may cause problems.
Index ¶
- Variables
- func ApplyParams(query string, params ...any) string
- func BeginIsolation(level sql.IsolationLevel) beginOpt
- func BeginReadOnly() beginOpt
- func Create(db DB, files fs.FS) error
- func DBSQL(ctx context.Context) (*sql.DB, *sql.Tx)
- func Diff(out, want string) string
- func DriverConnection(db DB) any
- func Dump(ctx context.Context, out io.Writer, query string, params ...any)
- func DumpString(ctx context.Context, query string, params ...any) string
- func ErrMissingField(err error) bool
- func ErrNoRows(err error) bool
- func ErrUnique(err error) bool
- func Exec(ctx context.Context, query string, params ...any) error
- func Get(ctx context.Context, dest any, query string, params ...any) error
- func InsertID(ctx context.Context, idColumn, query string, params ...any) (int64, error)
- func Load(db DB, name string) (string, bool, error)
- func NumRows(ctx context.Context, query string, params ...any) (int64, error)
- func RunTest(t *testing.T, f func(*testing.T, context.Context), opts ...drivers.TestOptions)
- func Select(ctx context.Context, dest any, query string, params ...any) error
- func TX(ctx context.Context, fn func(context.Context) error) error
- func Template(dialect Dialect, tpl string, params ...any) ([]byte, error)
- func TestQueries(t *testing.T, files fs.FS)
- func WithDB(ctx context.Context, db DB) context.Context
- type BulkInsert
- func NewBulkInsert(ctx context.Context, table string, columns []string) BulkInsert
- func (m BulkInsert) Errors() error
- func (m *BulkInsert) Finish() error
- func (m *BulkInsert) OnConflict(c string)
- func (m *BulkInsert) Returned() [][]any
- func (m *BulkInsert) Returning(columns ...string)
- func (m *BulkInsert) Values(values ...any)
- type ConnectOptions
- type DB
- func Begin(ctx context.Context, opts ...beginOpt) (context.Context, DB, error)
- func Connect(ctx context.Context, opt ConnectOptions) (DB, error)
- func GetDB(ctx context.Context) (DB, bool)
- func MustGetDB(ctx context.Context) DB
- func NewLogDB(db DB, out io.Writer, logWhat DumpArg, filter string) DB
- func NewMetricsDB(db DB, recorder MetricRecorder) DB
- func Unwrap(db DB) DB
- type Dialect
- type DumpArg
- type L
- type MetricRecorder
- type MetricsMemory
- func NewMetricsMemory(max int) *MetricsMemory
- func (m *MetricsMemory) Queries() []struct { Query string Times ztime.Durations }
- func (m *MetricsMemory) Record(d time.Duration, query string, params []any)
- func (m *MetricsMemory) Reset()
- func (m *MetricsMemory) String() string
- type Migrate
- func NewMigrate(db DB, files fs.FS, gomig map[string]func(context.Context) error) (*Migrate, error)
- func (m Migrate) Check() error
- func (m Migrate) List() (haveMig, ranMig []string, err error)
- func (m *Migrate) Log(f func(name string))
- func (m Migrate) Run(which ...string) error
- func (m Migrate) Schema(name string) (string, error)
- func (m *Migrate) Show(v bool)
- func (m *Migrate) Test(t bool)
- type P
- type PendingMigrationsError
- type Rows
- func Query(ctx context.Context, query string, params ...any) (*Rows, error)
- func (r *Rows) Close() error
- func (r *Rows) ColumnTypes() ([]*sql.ColumnType, error)
- func (r *Rows) Columns() ([]string, error)
- func (r *Rows) Err() error
- func (r *Rows) Next() bool
- func (r *Rows) Scan(dest ...any) error
- type SQL
- type ServerInfo
- type ServerVersion
Variables ¶
ErrTransactionStarted is returned when a transaction is already started; this can often be treated as a non-fatal error.
TXRollback can be returned from TX to roll back the transaction without error.
TemplateFuncMap are additional template functions for Template(). Existing functions may be overridden.
var TestDrivers []string
TestDrivers is a list of driver names to run tests against. These drivers must be imported. The default is to run against all registered drivers, which is not always desired.
Functions ¶
func ApplyParams ¶
ApplyParams replaces parameter placeholders in query with the values.
This is ONLY for printf-debugging, and NOT for actual usage. Security was NOT a consideration when writing this. Parameters in SQL are sent separately over the write and are not interpolated, so it's very different.
This supports ? placeholders and $1 placeholders *in order* ($\d+ is simply replace with ?).
func BeginIsolation ¶
func BeginIsolation(level sql.IsolationLevel) beginOpt
func BeginReadOnly ¶
func BeginReadOnly() beginOpt
func Create ¶
Create tables based on db/schema.{sql,gotxt}
func DBSQL ¶
DBSQL gets the database and transaction. The sql.DB is always set, but sql.Tx is nil if this is not a transaction.
func Diff ¶
Diff two strings, ignoring whitespace at the start of a line.
This is useful in tests in combination with zdb.Dump():
got := DumpString(ctx, `select * from factions`) want := ` faction_id name 1 Peacekeepers 2 Moya` if d := Diff(got, want); d != "" { t.Error(d) }
It normalizes the leading whitespace in want, making "does my database match with what's expected?" fairly easy to test.
func DriverConnection ¶
DriverConnection gets the driver connection; for example *pgx.Pool. May be nil.
func Dump ¶
Dump the results of a query to a writer in an aligned table. This is a convenience function intended mostly for testing/debugging.
Combined with ztest.Diff() it can be an easy way to test the database state.
You can add some special sentinel values in the params to control the output (they're not sent as parameters to the DB):
DumpAll DumpLocation Show location of the Dump() cal. DumpQuery Show the query with placeholders substituted. DumpExplain Show the results of EXPLAIN (or EXPLAIN ANALYZE for PostgreSQL). DumpResult Show the query result ( DumpVertical Show vertical output instead of horizontal columns. DumpCSV Show as CSV. DumpNul Separate columns with NUL (0x00) bytes; useful to feed output to another printer. DumpJSON Show as an array of JSON objects. DumpHTML Show as a HTML table.
func DumpString ¶
DumpString is like Dump(), but returns the result as a string.
func ErrMissingField ¶
ErrMissingFields reports if this error is because not all columns could be scanned due to missing struct fields.
All other columns are scanned; you can choose to ignore this error safely.
func ErrNoRows ¶
ErrNoRows reports if this error is sql.ErrNoRows.
func ErrUnique ¶
ErrUnique reports if this error reports a UNIQUE constraint violation.
func Exec ¶
Exec executes a query without returning the result.
func Get ¶
Get one row, returning sql.ErrNoRows if there are no rows.
func InsertID ¶
InsertID runs a INSERT query and returns the ID column idColumn.
If multiple rows are inserted it will return the ID of the last inserted row.
func Load ¶
Load a query from the filesystem or embeded files.
Queries are loaded from the "db/query/" directory, as "{name}-{driver}.sql" or "db/query/{name}.sql".
To allow identifying queries in logging and statistics such as pg_stat_statements every query will have the file name inserted in the first line; for example for "db/query/select-x.sql" the query sent to the database:
/* select-x */ select x from y;
Typical usage with Query() is to use "load:name", instead of calling this directly:
zdb.QueryGet(ctx, "load:select-x", &foo, zdb.P{ "param": "foo", })
func NumRows ¶
NumRows executes a query and returns the number of affected rows.
func RunTest ¶
RunTest runs tests against all registered zdb SQL drivers.
func Select ¶
Select zero or more rows; dest needs to be a pointer to a slice of:
- a struct to scan in to
- map[string]any
- []any
Returns nil (and no error) if there are no rows.
func TX ¶
TX runs the given function in a transaction.
The context passed to the callback has the DB replaced with a transaction. The transaction is committed if the fn returns nil, or will be rolled back if it's not. The error is propegated up unless it's TXRollback.
Multiple TX() calls can be nested, but they all run the same transaction and are comitted only if the outermost transaction returns true.
This is just a more convenient wrapper for Begin().
func Template ¶
Template runs text/template on SQL to make writing compatible schemas a bit easier.
Available template functions:
{{sqlite "str"}} Include the string only for the given SQL dialect. {{psql "str"}} {{maria "str"}}
Column types:
{{"auto_increment [large]}} Auto-incrementing column; the optional [large] boolean can be used to make a bigserial/bigint. {{json}} JSON column type (jsonb, json, varchar) {{blob}} Binary column type (bytea, blob, binary)
These only produce output for SQLite:
{{check_timestamp "colname"}} Check constraint for timestamp {{check_date Check constraint for date
You can set additional functions (or override any of the above) by adding functions to TemplateFuncMap.
func TestQueries ¶
TestQueries tests queries in the db/query directory.
for every .sql file you can create a _test.sql file, similar to how Go's testing works; the following special comments are recognized:
-- params Parameters for the query. -- want Expected result.
Everything before the first special comment is run as a "setup". The "-- params" and "-- want" comments can be repeated for multiple tests.
Example:
db/query/select-sites.sql: select * from sites where site_id = :site and created_at > :start db/query/select-sites_test.sql insert into sites () values (...) -- params site_id: 1 created_at: 2020-01-01 -- want 1 -- params -- want
func WithDB ¶
WithDB returns a copy of the context with the DB instance.
Types ¶
type BulkInsert ¶
type BulkInsert struct { Limit uint16 // contains filtered or unexported fields }
BulkInsert inserts as many rows as possible per query we send to the server.
func NewBulkInsert ¶
func NewBulkInsert(ctx context.Context, table string, columns []string) BulkInsert
NewBulkInsert makes a new BulkInsert builder.
func (BulkInsert) Errors ¶
func (m BulkInsert) Errors() error
Errors returns all errors that have been encountered.
func (*BulkInsert) Finish ¶
func (m *BulkInsert) Finish() error
Finish the operation, returning any errors.
This can be called more than once, in cases where you want to have some fine-grained control over when actual SQL is sent to the server.
func (*BulkInsert) OnConflict ¶
func (m *BulkInsert) OnConflict(c string)
OnConflict sets the "on conflict [..]" part of the query. This needs to include the "on conflict" itself.
func (*BulkInsert) Returned ¶
func (m *BulkInsert) Returned() [][]any
Returned returns any rows that were returned; only useful of [Returning] was set.
This will only return values once, for example:
Values(...) // Inserts 3 rows ... Returned() // Return the 3 rows Values(..) // Inserts 1 row Returned() // Returns the 1 row
func (*BulkInsert) Returning ¶
func (m *BulkInsert) Returning(columns ...string)
Returning sets a column name in the "returning" part of the query.
The values can be fetched with [Returned].
func (*BulkInsert) Values ¶
func (m *BulkInsert) Values(values ...any)
Values adds a set of values.
type ConnectOptions ¶
type ConnectOptions struct { Connect string // Connect string. Create bool // Create database if it doesn't exist yet. Migrate []string // Migrations to run; nil for none, "all" for all, or a migration name. MigrateLog func(name string) // Called for every migration that gets run. // Set the maximum number of open and idle connections. // // The default for MaxOpenConns is 16, and the default for MaxIdleConns is // 4, instead of Go's default of 0 and 2. Use a value <0 to skip the // default. // // This can also be changed at runtime with: // // d, _ := db.DBSQL() // d.SetMaxOpenConns(100) MaxOpenConns int MaxIdleConns int // In addition to migrations from .sql files, you can run migrations from Go // functions. See the documentation on Migrate for details. GoMigrations map[string]func(context.Context) error // Database files; the following layout is assumed: // // Schema schema-{dialect}.sql, schema.sql, or schema.gotxt // Migrations migrate/{name}-{dialect}.sql, migrate/{name}.sql, or migrate/{name}.gotxt // Queries query/{name}-{dialect}.sql, query/{name}.sql, or query/{name}.gotxt // // It's okay if files are missing; e.g. no migrate directory simply means // that it won't attempt to run migrations. Files fs.FS }
ConnectOptions are options for Connect().
type DB ¶
type DB interface { DBSQL() (*sql.DB, *sql.Tx) SQLDialect() Dialect Info(ctx context.Context) (ServerInfo, error) Close() error Exec(ctx context.Context, query string, params ...any) error NumRows(ctx context.Context, query string, params ...any) (int64, error) InsertID(ctx context.Context, idColumn, query string, params ...any) (int64, error) Get(ctx context.Context, dest any, query string, params ...any) error Select(ctx context.Context, dest any, query string, params ...any) error Query(ctx context.Context, query string, params ...any) (*Rows, error) TX(ctx context.Context, fb func(context.Context) error) error Begin(ctx context.Context, opts ...beginOpt) (context.Context, DB, error) Rollback() error Commit() error }
DB is an interface to the database; this can be a regular connection, a transaction, or a wrapped connection to add features such as logging.
If this is not a transaction, then Commit() and Rollback() will always return an error. If this is a transaction, then Begin() is a no-op, and Close() will rollback the transaction and close the database connection.
See documentation on the top-level functions for more details on the methods.
func Begin ¶
Begin a new transaction.
The returned context is a copy of the original with the DB replaced with a transaction. The same transaction is also returned directly.
Nested transactions return the original transaction together with ErrTransactionStarted (which is not a fatal error).
func Connect ¶
func Connect(ctx context.Context, opt ConnectOptions) (DB, error)
Connect to a database.
To connect to a database you need to register a driver for it first. While zdb uses database/sql, it needs a zdb-specific driver which contains additional information. Several drivers are included in the zgo.at/zdb/drivers package. To register a driver simply import it:
import _ "zgo.at/zdb/drivers/pq"
The connect string has the following layout (minus spaces):
dialect Use default connection parameters for this driver. dialect + connect Pass driver-specific connection string. driverName + connect Use a SQL driver name, instead of SQL dialect. dialect / driverName + connect Specify both.
The connectString is driver-specific; see the documentation of the driver for details. The dialect is the "SQL dialect"; currently recognized dialects are:
postgresql aliases: postgres psql pgsql sqlite aliases: sqlite3 mysql aliases: mariadb
For example, "postgresql+dbname=mydb", "pq+dbname=mydb", and "postgresql/pq+dbname=mydb" are all identical, assuming pq is the registered driver.
If multiple drivers are registered for the same dialect then it will use the first one.
If Create is set it will try to automatically create a database if it doesn't exist yet. If Files is given it will also look for the following files to set up the database if it doesn't exist or is empty:
schema.gotxt Run zdb.Template first. schema-{dialect}.sql Schema for this SQL dialect. schema.sql
Migrate and GoMigrate are migrations to run, see the documentation of Migrate for details.
func GetDB ¶
GetDB gets the DB from the context.
func MustGetDB ¶
MustGet gets the DB from the context, panicking if there is none.
func NewLogDB ¶
NewLogDB returns a DB wrapper to log queries, query plans, and query results.
If Filter is not an empty string then only queries containing the text are logged. Use an empty string to log everything.
If LogQuery is enabled then the query text will be logged, if LogExplain is enabled then the query plan will be logged, and if LogResult is enabled then the query result will be logged.
Only LogQuery will be set if opts is nil,
WARNING: printing the result means the query will be run twice, which is a significant performance impact and DATA MODIFICATION STATEMENTS ARE ALSO RUN TWICE. Some SQL engines may also run the query on EXPLAIN (e.g. PostgreSQL does).
func NewMetricsDB ¶
func NewMetricsDB(db DB, recorder MetricRecorder) DB
NewMetricsDB returns a DB wrapper which records query performance metrics.
For every query recorder.Record is called.
func Unwrap ¶
Unwrap this database, removing all zdb wrappers and returning the underlying database (which may be a transaction).
To wrap a zdb.DB object embed the zdb.DB interface, which contains the parent DB connection. The Unwrap() method is expected to return the parent DB.
Then implement whatever you want; usually you will want to implement the dbImpl interface, which contains the methods that actually interact with the database. All the DB methods call this under the hood. This way you don't have to wrap all the methods on DB, but just five.
In Begin() you will want to return a new wrapped DB instance with the transaction attached.
See logDB and metricDB in log.go and metric.go for examples.
TODO: document wrapping a bit better.
type Dialect ¶
type Dialect uint8
Dialect is an SQL dialect. This can be represented by multiple drivers; for example for PostgreSQL "pq" and "pgx" are both DialectPostgreSQL.
SQL dialects.
func SQLDialect ¶
SQLDialect gets the SQL dialect.
func (Dialect) String ¶
type DumpArg ¶
type DumpArg int32
const ( DumpLocation DumpArg // Show location of Dump call. DumpQuery // Show the query with placeholders substituted. DumpExplain // Show the results of EXPLAIN (or EXPLAIN ANALYZE for PostgreSQL). DumpResult // Show the query result. DumpVertical // Print query result in vertical columns instead of horizontal. DumpCSV // Print query result as CSV. DumpNul // Print query result with columns separated by NUL (0x00) bytes. DumpJSON // Print query result as JSON. DumpHTML // Print query result as a HTML table. DumpAll // Dump all we can. )
type L ¶
type L []any
L ("list") is an alias for []any.
Deprecated: []any{..} is now a lot shorter and less ugly than []interface{}{..}
type MetricRecorder ¶
type MetricsMemory ¶
type MetricsMemory struct {
// contains filtered or unexported fields
}
MetricsMemory records metrics in memory.
func NewMetricsMemory ¶
func NewMetricsMemory(max int) *MetricsMemory
NewMetricsMemory creates a new MetricsMemory, up to "max" metrics per query.
func (*MetricsMemory) Queries ¶
func (m *MetricsMemory) Queries() []struct { Query string Times ztime.Durations }
Queries gets a list of queries sorted by the total run time.
func (*MetricsMemory) Record ¶
func (m *MetricsMemory) Record(d time.Duration, query string, params []any)
Record this query.
func (*MetricsMemory) Reset ¶
func (m *MetricsMemory) Reset()
Reset the contents.
func (*MetricsMemory) String ¶
func (m *MetricsMemory) String() string
type Migrate ¶
type Migrate struct {
// contains filtered or unexported fields
}
Migrate allows running database migrations.
func NewMigrate ¶
NewMigrate creates a new migration instance.
Migrations are loaded from the filesystem, as described in ConnectOptions.
You can optionally pass a list of Go functions to run as a "migration".
Every migration is automatically run in a transaction; and an entry in the version table is inserted.
func (Migrate) Check ¶
Check if there are pending migrations; will return the (non-fatal) PendingMigrationsError if there are.
func (Migrate) List ¶
List all migrations we know about, and all migrations that have already been run.
func (*Migrate) Log ¶
Log sets a log function for migrations; this gets called for every migration that gets run.
This only gets called if the migration was run successfully.
func (Migrate) Run ¶
Run a migration, or all of then if which contains "all" or "auto".
func (Migrate) Schema ¶
Schema of a migration by name.
func (*Migrate) Show ¶
Show sets the "show" flag; it won't run anything, just print the queries it would run to stdout.
func (*Migrate) Test ¶
Test sets the "test" flag: it won't commit any transactions.
This will work correctly for SQLite and PostgreSQL, but not MariaDB as most ALTER and CREATE commands will automatically imply COMMIT. See: https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit/
type P ¶
P ("params") is an alias for map[string]any, just because it's less typing and looks less noisy 🙃
Deprecated: map[string]any{..} is now a lot shorter and less ugly than map[string]interface{}{...}
type PendingMigrationsError ¶
type PendingMigrationsError struct{ Pending []string }
PendingMigrationsError is a non-fatal error used to indicate there are migrations that have not yet been run.
func (PendingMigrationsError) Error ¶
func (err PendingMigrationsError) Error() string
type Rows ¶
type Rows struct {
// contains filtered or unexported fields
}
TODO: document.
func Query ¶
Query the database without immediately loading the result.
This gives more flexibility over Select(), and won't load the entire result in memory to allow fetching the result one row at a time.
This won't return an error if there are no rows.
TODO: will it return nil or Rows which just does nothing? Make sure this is tested and documented.
func (*Rows) Close ¶
func (*Rows) ColumnTypes ¶
func (r *Rows) ColumnTypes() ([]*sql.ColumnType, error)
func (*Rows) Columns ¶
func (*Rows) Err ¶
func (*Rows) Next ¶
func (*Rows) Scan ¶
type SQL ¶
type SQL string
SQL represents a safe SQL string that will be directly inserted in the query without any modification, rather than passed as a parameter.
Use with wisdom! Careless use of this can open you to SQL injections. Generally speaking you rarely want to use this, except in some rare cases where 1) parameters won't work, and 2) you're really sure this value is safe.
type ServerInfo ¶
type ServerInfo struct { Version ServerVersion DriverName string Dialect Dialect Connect string }
ServerInfo contains information about the SQL server.
func Info ¶
func Info(ctx context.Context) (ServerInfo, error)
Info gets information about the SQL server.
type ServerVersion ¶
type ServerVersion string
ServerVersion represents a database version.
func (ServerVersion) AtLeast ¶
func (v ServerVersion) AtLeast(want ServerVersion) bool
AtLeast reports if this version is at least version want.
Source Files ¶
bulk.go connect.go log.go metric.go migrate.go prepare.go test.go tpl.go zdb.go zdb_impl.go
Directories ¶
Path | Synopsis |
---|---|
drivers | |
drivers/go-sqlite3 | Package sqlite3 provides a zdb driver for SQLite. |
drivers/mariadb | Package mariadb provides a zdb driver for MariaDB. |
drivers/pgx | Package pgx provides a zdb driver for PostgreSQL. |
drivers/pq | Package pq provides a zdb driver for PostgreSQL. |
drivers/test | |
internal |
- Version
- v0.0.0-20241030193854-e38684ed772c (latest)
- Published
- Oct 30, 2024
- Platform
- linux/amd64
- Imports
- 34 packages
- Last checked
- 1 week ago –
Tools for package owners.