Documentation
¶
Overview ¶
Package dalgo2postgres is the PostgreSQL-specific DALgo driver.
It composes github.com/dal-go/dalgo2sql for the dal.DB read/write surface (transactions, recordset reader, Get/Set/Insert/Delete) and adds PostgreSQL-native implementations of:
- structured queries, which dalgo2sql compiles with its typed PostgreSQL compiler (every value a bound argument, every name quoted) and which run filtering, ordering, grouping, aggregation and joins on the server; the constructors force the dialect, so the legacy text emitter is never reached, and *Database declares what the dialect runs on the server (dal.QueryCapabilitiesProvider, dal.NativeJoinProvider and dal.JoinFieldsProvider)
- dbschema.SchemaReader for schema introspection via information_schema and pg_indexes
- ddl.SchemaModifier for PostgreSQL-flavored CREATE / DROP / ALTER
- dal.ConcurrencyAware returning true (PostgreSQL supports concurrent connections from multiple goroutines and processes)
- IsAlreadyExists, a dalgo2sql.DbOptions.IsAlreadyExists classifier that reports a PostgreSQL unique-key violation (SQLSTATE 23505) so Insert rejects a duplicate key with record.IsAlreadyExists
Index ¶
- Constants
- Variables
- func IsAlreadyExists(err error) bool
- type ConnectionError
- type Database
- func (d *Database) Adapter() dal.Adapter
- func (d *Database) AlterCollection(ctx context.Context, name string, ops ...ddl.AlterOp) error
- func (d *Database) CanExecuteJoin(ctx context.Context, q dal.StructuredQuery) error
- func (d *Database) Close() error
- func (d *Database) CreateCollection(ctx context.Context, c dbschema.CollectionDef, opts ...ddl.Option) error
- func (d *Database) Delete(ctx context.Context, key *dalrecord.Key) error
- func (d *Database) DeleteMulti(ctx context.Context, keys []*dalrecord.Key) error
- func (d *Database) DescribeCollection(ctx context.Context, ref *dal.CollectionRef) (*dbschema.CollectionDef, error)
- func (d *Database) DropCollection(ctx context.Context, name string, opts ...ddl.Option) error
- func (d *Database) ExecuteQueryToRecordsReader(ctx context.Context, query dal.Query) (dal.RecordsReader, error)
- func (d *Database) ExecuteQueryToRecordsetReader(ctx context.Context, query dal.Query, opts ...recordset.Option) (dal.RecordsetReader, error)
- func (d *Database) Exists(ctx context.Context, key *dalrecord.Key) (bool, error)
- func (d *Database) Get(ctx context.Context, record dalrecord.Record) error
- func (d *Database) GetMulti(ctx context.Context, records []dalrecord.Record) error
- func (d *Database) ID() string
- func (d *Database) Insert(ctx context.Context, record dalrecord.Record, opts ...dal.InsertOption) error
- func (d *Database) JoinFields(ctx context.Context, source dal.RecordsetSource) ([]string, error)
- func (d *Database) ListCollections(ctx context.Context, parent *dalrecord.Key) ([]dal.CollectionRef, error)
- func (d *Database) ListConstraints(ctx context.Context, ref *dal.CollectionRef) ([]dbschema.ConstraintDef, error)
- func (d *Database) ListIndexes(ctx context.Context, ref *dal.CollectionRef) ([]dbschema.IndexDef, error)
- func (d *Database) ListReferrers(ctx context.Context, ref *dal.CollectionRef) ([]dbschema.Referrer, error)
- func (d *Database) NonDeterministicTextColumns(ctx context.Context, ref *dal.CollectionRef) ([]string, error)
- func (d *Database) QueryCapabilities() dal.QueryCapabilities
- func (d *Database) RunReadonlyTransaction(ctx context.Context, f dal.ROTxWorker, opts ...dal.TransactionOption) error
- func (d *Database) RunReadwriteTransaction(ctx context.Context, f dal.RWTxWorker, opts ...dal.TransactionOption) error
- func (d *Database) Schema() dal.Schema
- func (d *Database) Set(ctx context.Context, record dalrecord.Record) error
- func (d *Database) SetMulti(ctx context.Context, records []dalrecord.Record) error
- func (d *Database) SupportsConcurrentConnections() bool
- func (d *Database) SupportsTransactionalDDL() bool
- func (d *Database) Update(ctx context.Context, key *dalrecord.Key, updates []update.Update, ...) error
- func (d *Database) UpdateMulti(ctx context.Context, keys []*dalrecord.Key, updates []update.Update, ...) error
- func (d *Database) UpdateRecord(ctx context.Context, record dalrecord.Record, updates []update.Update, ...) error
- func (d *Database) Upsert(ctx context.Context, record dalrecord.Record) error
- type FailureKind
- type IdentifierMode
- type Option
Constants ¶
const DefaultSchema = "public"
DefaultSchema is the PostgreSQL schema the schema reader inspects when WithSchema is not used.
Variables ¶
var Version = resolveVersion(debug.ReadBuildInfo)
Version is the module version recorded in the consuming binary's build information (a tag or a pseudo-version), or "devel" when none is recorded, for example in a local-path replace or a working-tree build. Treat it as read-only.
Functions ¶
func IsAlreadyExists ¶ added in v0.2.1
IsAlreadyExists is a dalgo2sql.DbOptions.IsAlreadyExists classifier for PostgreSQL via github.com/jackc/pgx/v5 (the driver this package uses — see the "pgx" import in database.go). It reports whether err represents a unique-key violation: the driver reports these as a *pgconn.PgError whose Code is the SQLSTATE "23505" (unique_violation).
Detection matches the SQLSTATE code via errors.As, never the error message — messages are not a stable contract and can vary by locale or server version. A sibling class-23 code such as "23503" (foreign_key_violation) or "23514" (check_violation) is a different kind of constraint failure, not a duplicate key, and is deliberately reported as false here.
NewDatabaseWithOptions (and therefore NewDatabase) wires this in automatically whenever the caller's dalgo2sql.DbOptions.IsAlreadyExists is nil. It is exported so callers who build a dalgo2sql.DbOptions directly — e.g. calling dalgo2sql.NewDatabase themselves rather than going through this package's constructors — can reuse it instead of rewriting the same *pgconn.PgError check.
Types ¶
type ConnectionError ¶ added in v0.3.1
type ConnectionError struct {
// Kind is what failed.
Kind FailureKind
// SQLState is the five-character SQLSTATE code of a server error (Kind is
// [FailureServer]), for example "28P01" for a rejected password.
SQLState string
// Host is the server's host name or address, named only when the connection
// string names exactly one server and the name passes the check.
Host string
// Port is the server's port, under the same conditions as Host.
Port string
// Database is the database name, named only when it passes the check.
Database string
// contains filtered or unexported fields
}
ConnectionError is the error NewDatabase and NewDatabaseWithOptions return when the connection to PostgreSQL cannot be opened or verified.
Its text is built only from a fixed sentence chosen by Kind and from the parts below that passed a strict check. Nothing of the connection string but those parts, and nothing from the driver's or the server's own message, is ever copied into it: those hold the user name, the password and other settings, and callers print errors. For the same reason the driver's error is not reachable through errors.Unwrap, errors.Is or errors.As; branch on Kind and SQLState instead.
A part that is empty was not known, or could not be named safely.
func (*ConnectionError) Error ¶ added in v0.3.1
func (e *ConnectionError) Error() string
Error returns the text, built from the fixed sentence, the SQLSTATE code and the named parts only.
type Database ¶
type Database struct {
dal.ConcurrencyAvailable // SupportsConcurrentConnections() = true
dal.DB // delegate for the dal.DB surface
// contains filtered or unexported fields
}
Database is the dalgo2postgres driver instance. It implements dal.DB by embedding a dal.DB obtained from dalgo2sql.NewDatabase, and adds PostgreSQL-specific dbschema, ddl, and concurrency surfaces.
The embedded dal.DB (rather than a named field) is what lets Database satisfy dal.DB itself: dal.DB is sealed by an unexported marker method, and embedding is the only way for that method to be promoted onto a decorating type — see dal.NewDB's doc comment.
Construct via NewDatabase. Database values are safe for concurrent use — the underlying PostgreSQL server and pgx connection pool both support concurrent connections from multiple goroutines.
func NewDatabase ¶
NewDatabase opens a connection to the PostgreSQL server identified by dsn using github.com/jackc/pgx/v5/stdlib (pure Go, CGO_ENABLED=0), pings to surface connectivity errors at construction time, wraps the *sql.DB via dalgo2sql.NewDatabase for the dal.DB surface, and returns a *Database that satisfies dal.DB + dal.ConcurrencyAware.
A failure to open or reach the server is a *ConnectionError; its text holds nothing but the host, port and database name the driver parsed (each only when it passes a strict check), and no other text of dsn or of the driver.
Use NewDatabaseWithOptions when you need to supply per-collection primary-key metadata (required for Insert/Get/Delete with map[string]any data). The optional Option values choose the schema the schema reader inspects and how names are matched and written (IdentifierMode: lower-cased by default).
func NewDatabaseWithOptions ¶
func NewDatabaseWithOptions(dsn string, schema dal.Schema, opts dalgo2sql.DbOptions, options ...Option) (*Database, error)
NewDatabaseWithOptions is like NewDatabase but accepts a dal.Schema and dalgo2sql.DbOptions so callers can configure per-collection primary-key mappings required by Insert/Get/Delete operations.
Example — open a DB whose "widgets" table has "id" as its primary key:
db, err := dalgo2postgres.NewDatabaseWithOptions(dsn, dal.NewSchema(nil, nil),
dalgo2sql.DbOptions{
Recordsets: map[string]*dalgo2sql.Recordset{
"widgets": dalgo2sql.NewRecordset("widgets", dalgo2sql.Table,
[]dal.FieldRef{dal.Field("id")}),
},
})
The optional Option values choose the PostgreSQL schema the schema reader inspects (WithSchema) and whether names are matched by the reader and written by structured queries exactly or lower-cased (WithIdentifierMode); the mode may also be given as opts.IdentifierCase, but not as two different values. A nil option is ignored; an unknown IdentifierMode or IdentifierCase, and two that disagree, are an error, returned before any connection is attempted.
Three fields of opts are replaced whatever the caller set: Placeholder (always dollar markers), StructuredQueryDialect (always "postgres", so no structured query reaches dalgo2sql's legacy text emitter) and IdentifierCase (resolved from the mode above). IsAlreadyExists is defaulted only when nil. A caller's own compiler is not accepted beside the dialect: NativeStructuredQueryCompiler, NativeJoinEligibility and NativeJoinHintTranslator are an error too, returned before any connection is attempted.
A failure to open or reach the server is a *ConnectionError, as for NewDatabase.
func (*Database) AlterCollection ¶
AlterCollection applies ops in order inside a single transaction. Partial failures roll back and leave the collection untouched.
func (*Database) CanExecuteJoin ¶ added in v0.4.0
CanExecuteJoin reports whether the database runs q's joins on the server; a non-nil error declines it and DALgo runs the join in its own engine. A Database that declares no join support declines with dal.ErrNotSupported.
func (*Database) Close ¶
Close closes the underlying *sql.DB. After Close the Database value is unusable; further method calls will fail with an error from database/sql.
func (*Database) CreateCollection ¶
func (d *Database) CreateCollection(ctx context.Context, c dbschema.CollectionDef, opts ...ddl.Option) error
CreateCollection creates a table and its inline indexes transactionally. On any error, the transaction rolls back and no schema state remains.
func (*Database) DeleteMulti ¶
func (*Database) DescribeCollection ¶
func (d *Database) DescribeCollection(ctx context.Context, ref *dal.CollectionRef) (*dbschema.CollectionDef, error)
DescribeCollection returns the full schema definition for the named table or view. It queries information_schema for columns and primary-key membership. The table is read from the schema the reference names, else from the configured schema. A column whose PostgreSQL type has no dbschema counterpart (uuid, json, jsonb, arrays, enums, interval, inet, money, ...) is reported as a String field rather than failing the whole table.
func (*Database) DropCollection ¶
DropCollection drops the table and all its indexes (Postgres cascades automatically).
func (*Database) ExecuteQueryToRecordsReader ¶
func (*Database) ExecuteQueryToRecordsetReader ¶
func (*Database) JoinFields ¶ added in v0.4.0
JoinFields lists the columns of source in table order, for the wildcard of a join DALgo runs itself. A Database that declares no join support returns none, which DALgo takes for a source that cannot supply its schema.
func (*Database) ListCollections ¶
func (d *Database) ListCollections(ctx context.Context, parent *dalrecord.Key) ([]dal.CollectionRef, error)
ListCollections returns the tables and views of the configured schema (DefaultSchema unless WithSchema says otherwise) in alphabetical order, under the names PostgreSQL reports. The parent *dalrecord.Key is ignored — Postgres has a flat table namespace within a schema.
func (*Database) ListConstraints ¶
func (d *Database) ListConstraints(ctx context.Context, ref *dal.CollectionRef) ([]dbschema.ConstraintDef, error)
ListConstraints returns a best-effort survey of constraints on the table via information_schema, in the schema the reference names, else the configured schema:
- PRIMARY KEY constraint (one row if any PK columns exist)
- UNIQUE constraints
- FOREIGN KEY constraints
CHECK constraints are NOT enumerated here; read them from DescribeCollection if needed.
func (*Database) ListIndexes ¶
func (d *Database) ListIndexes(ctx context.Context, ref *dal.CollectionRef) ([]dbschema.IndexDef, error)
ListIndexes returns the non-primary-key indexes on the named table via pg_indexes, from the schema the reference names, else the configured schema.
func (*Database) ListReferrers ¶
func (d *Database) ListReferrers(ctx context.Context, ref *dal.CollectionRef) ([]dbschema.Referrer, error)
ListReferrers returns the foreign keys that reference ref, one Referrer per foreign key, with the referencing columns in key order. Only referrers in the schema of ref (the schema the reference names, else the configured one) are reported, because the reader addresses a single schema. It reads pg_catalog rather than information_schema: constraint names are unique only per table in PostgreSQL, so information_schema joins on the name alone mix up tables that reuse a name.
func (*Database) NonDeterministicTextColumns ¶ added in v0.3.0
func (d *Database) NonDeterministicTextColumns(ctx context.Context, ref *dal.CollectionRef) ([]string, error)
NonDeterministicTextColumns returns the columns of the referenced table (or view) whose equality is not exact: columns of type citext and columns with a non-deterministic collation (for example a case-insensitive ICU collation), in column order. Under such a column "A" = "a" can be true, so a comparison or a group-by on it does not mean what a byte-for-byte match means; callers use this list to know where a pushed-down comparison differs from Go's.
The table is looked up in the schema the reference names (dal.NewQualifiedRootCollectionRef), else in the Database's schema (DefaultSchema unless WithSchema says otherwise), and its name is matched as the schema reader matches it (see IdentifierMode). Both are sent as bound parameters. A table with no such column yields an empty non-nil slice. A table that does not exist (under the name as IdentifierMode resolves it) yields a "not found" error, never an empty list, so an empty result means the table exists and none of its columns is inexact in the sense below.
The match is on the column's own type and collation. A column whose type is a domain over citext, or an array of citext, is not reported; treat such columns as inexact yourself.
func (*Database) QueryCapabilities ¶ added in v0.4.0
func (d *Database) QueryCapabilities() dal.QueryCapabilities
QueryCapabilities reports what dalgo2sql's PostgreSQL dialect runs on the server: GROUP BY, HAVING, ORDER BY and the aggregates, and no more.
func (*Database) RunReadonlyTransaction ¶
func (d *Database) RunReadonlyTransaction(ctx context.Context, f dal.ROTxWorker, opts ...dal.TransactionOption) error
func (*Database) RunReadwriteTransaction ¶
func (d *Database) RunReadwriteTransaction(ctx context.Context, f dal.RWTxWorker, opts ...dal.TransactionOption) error
func (*Database) SupportsConcurrentConnections ¶ added in v0.2.0
SupportsConcurrentConnections reports PostgreSQL's own concurrency behaviour (always true — see dal.ConcurrencyAvailable), not dalgo2sql's. An explicit method is required here: dal.ConcurrencyAvailable and the embedded dal.DB (whose Backend requirement embeds dal.ConcurrencyAware) both declare this method at the same promotion depth, which Go otherwise treats as an ambiguous selector.
func (*Database) SupportsTransactionalDDL ¶
SupportsTransactionalDDL reports that PostgreSQL supports transactional DDL — every CREATE / DROP / ALTER statement can be wrapped in a BEGIN/COMMIT and is rolled back atomically on commit failure.
func (*Database) UpdateMulti ¶
func (*Database) UpdateRecord ¶
func (d *Database) UpdateRecord(ctx context.Context, record dalrecord.Record, updates []update.Update, preconditions ...dal.Precondition) error
UpdateRecord is not supported at the database level by dalgo2sql; use Update with an explicit key instead, or call UpdateRecord inside a RunReadwriteTransaction where the transaction object does support it.
type FailureKind ¶ added in v0.3.1
type FailureKind int
FailureKind says what kind of failure a ConnectionError reports. It decides the sentence the error's text uses.
const ( // FailureOther is any failure the kinds below do not describe. It is the // zero value. FailureOther FailureKind = iota // FailureInvalidDSN: the driver cannot parse the connection string, or a file // or service the string names cannot be read. FailureInvalidDSN // FailureMisread: the driver would accept the string but read it as something // else than was meant (a URL in quotes, with a leading space or with another // scheme, or a key=value string whose pairs are not separated by spaces). It is // refused before any connection is attempted. FailureMisread // FailureNetwork: the server could not be reached (name resolution, refused or // reset connection). FailureNetwork // FailureTLS: the TLS handshake with the server failed. FailureTLS // FailureTimeout: the attempt timed out or was canceled. FailureTimeout // FailureServer: the server answered with an error. [ConnectionError.SQLState] // holds its SQLSTATE code. FailureServer )
type IdentifierMode ¶ added in v0.2.4
type IdentifierMode int
IdentifierMode says how a Database treats the names it is given: the schema reader matches a table by it, and a structured query writes the names it spells by it (dalgo2sql's DbOptions.IdentifierCase, which this package sets from it).
const ( // IdentifierFoldLower lower-cases a name before looking it up or writing it, // inside quotes. It matches the DDL this package writes (see quoteIdent), which // stores every name lower-cased, so a database this package created is read // back with any spelling of a name: Album, album and ALBUM are one table. It is // the default, so callers that pass no option and create their tables through // this package keep their behaviour. A caller that reads a database it did not // create with this package (mixed-case table names) must choose // [IdentifierExact] explicitly: with the default, ListCollections reports // "Album" but DescribeCollection("Album") looks for "album" and answers not // found, and so does a structured query. IdentifierFoldLower IdentifierMode = iota // IdentifierExact looks a table up, and writes a name, exactly as it is given. // PostgreSQL names are case-sensitive once quoted, so "Album" and "album" are // different tables; choose this mode to read a database whose tables were // created with mixed-case names. // // The mode affects the schema reader (ListCollections, DescribeCollection, // ListIndexes, ListConstraints, ListReferrers, NonDeterministicTextColumns) and // structured queries. It does not affect the DDL this package writes, which // still lower-cases every name, nor key reads and writes (Get, Set, Insert, // Update, Delete and their multi forms), which accept plain names only and // write them unquoted, so PostgreSQL folds them to lower case. IdentifierExact )
type Option ¶ added in v0.2.4
type Option func(*Database)
Option customises how a Database reads its schema.
An option must be free of side effects and must only set fields of the Database it is given: the constructors apply every option twice, first to a zero Database (to reject an invalid set before any connection is attempted, when the embedded dal.DB is still nil), then to the real one. An option that touches anything else, or that does something observable when it runs, would run twice and, the first time, against a Database that has no connection.
func WithIdentifierMode ¶ added in v0.2.4
func WithIdentifierMode(mode IdentifierMode) Option
WithIdentifierMode selects how names are matched by the schema reader and written by structured queries. See IdentifierMode. Without it the mode is IdentifierFoldLower, or the one dalgo2sql.DbOptions.IdentifierCase names when the caller passed NewDatabaseWithOptions one; the two must not disagree, and a constructor refuses the pair that does.
func WithSchema ¶ added in v0.2.4
WithSchema makes the schema reader inspect the named PostgreSQL schema instead of DefaultSchema. An empty name keeps the default. The name is always sent to the server as a bound parameter, never inside SQL text.
A collection reference that names its own schema (dal.NewQualifiedRootCollectionRef) is read from that schema instead.
The option affects only the schema reader (ListCollections, DescribeCollection, ListIndexes, ListConstraints, ListReferrers, NonDeterministicTextColumns). The DDL this package writes, key reads and writes and structured queries still follow the connection's search_path: a structured query reads another schema when its collection reference names it (dal.NewQualifiedRootCollectionRef).