sql

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package sql owns connection pooling, parameter binding, and row retrieval for projections. It deliberately knows nothing about Kubernetes types.

Index

Constants

View Source
const (
	DefaultMaxOpenConns    = 10
	DefaultConnMaxLifetime = 30 * time.Minute
	DefaultQueryTimeout    = 10 * time.Second
	// DefaultMaxPreparedStatements bounds the per-pool statement cache. Each
	// entry costs a statement on every connection the pool holds, so an
	// unbounded cache is unbounded state in the database as well as here.
	DefaultMaxPreparedStatements = 256
	DefaultMaxRows               = 5000
	// DefaultMaxBytes bounds a result set by size as well as by row count.
	//
	// maxRows alone does not bound memory: one row can carry a megabyte of
	// JSON or text, and a JSON-aggregated read returns the whole collection as
	// a single row, where maxRows never applies at all. Generous enough that an
	// ordinary list never meets it, and low enough that one projection cannot
	// take a server every other projection shares.
	DefaultMaxBytes  = 64 << 20
	DefaultKeepAlive = 30 * time.Second
	// DefaultStatsInterval is how often pool statistics are republished. It is
	// deliberately not DefaultKeepAlive: the two used to share a ticker, so
	// keepAliveInterval: 0 — a supported setting, for a database behind a proxy
	// that objects to being pinged — silently took every pool metric with it.
	DefaultStatsInterval = 15 * time.Second
)

Defaults applied when a projection leaves pool settings unset.

Variables

View Source
var ErrTooBusy = errors.New("projection is at its query concurrency limit")

ErrTooBusy reports that a projection is at its concurrency limit. Callers turn it into a 429 rather than queueing behind an overloaded database.

Functions

func HasReturning

func HasReturning(stmt, driver string) bool

HasReturning reports whether a statement answers with the rows it wrote.

The word has to appear as statement text, which is why this cannot be a search over the string: "-- returning the newest first" is a comment and 'returning to sender' is data. A write misread as returning rows is run as a query rather than for its effect; it succeeds, answers with no rows, and the client is told the object was not found or that something else changed it first — for a write that in fact landed.

func IsForeignKeyViolation

func IsForeignKeyViolation(err error) bool

IsForeignKeyViolation reports whether a write failed against a foreign key.

func IsSerializationFailure added in v0.3.0

func IsSerializationFailure(err error) bool

IsSerializationFailure reports whether the database rolled a write back because it could not run it alongside a concurrent one.

This is the one class of failure a database raises specifically to say that nothing was changed and the same request will probably succeed if it is sent again: PostgreSQL's serialization_failure and deadlock_detected, and MySQL's deadlock, whose message ends "try restarting transaction". A projection using queries.create.statements for a check-then-insert meets it under any real concurrency, and so does any projection whose database runs at SERIALIZABLE. CockroachDB reports its retries the same way, and always at SERIALIZABLE.

Deliberately not a lock wait timeout — MySQL 1205, or SQLite giving up after its busy timeout. Those say a lock was held too long, not that this write lost a race, and the difference matters twice over. They mean sustained contention rather than an unlucky interleaving, so telling a controller to come straight back makes the pile-up worse where a timeout carries backpressure. And MySQL rolls back only the statement on 1205 unless innodb_rollback_on_timeout is set, so "nothing was changed" is exactly what cannot be promised about a multi-statement write. They stay with the other timeouts.

SQLite has no code in this class to report: it takes one writer at a time and the pool gives it a busy timeout, so what a projection meets there is the lock wait above.

func IsStatementTimeout

func IsStatementTimeout(err error) bool

IsStatementTimeout reports whether the database gave up on a statement rather than the client giving up on waiting for it.

With dataSource.statementTimeout the database's deadline is the one that fires, so this is the ordinary shape of a query that ran too long. It means the same thing to a client as a context deadline — come back later, or ask for less — so it is answered the same way, and not as an internal error.

func IsUnavailable

func IsUnavailable(err error) bool

IsUnavailable reports whether a query failed because the database could not be reached, as opposed to rejecting the statement.

The distinction decides what a client is told: an unreachable database is a 503 worth retrying, while a rejected statement is the projection's fault and retrying will not help.

func IsUniqueViolation

func IsUniqueViolation(err error) bool

IsUniqueViolation reports whether a write failed because the row already exists, so callers can answer with AlreadyExists rather than an opaque error.

Every driver phrases this differently, so it is classified by code where the driver offers one, with a text fallback for drivers and wrappers that do not.

func Register

func Register(d Driver) error

Register makes a driver available to projections.

Call it before the server starts serving; a projection that names an unregistered driver is refused when it is compiled.

func RegisterCredentialProvider added in v0.4.0

func RegisterCredentialProvider(p CredentialProvider) error

RegisterCredentialProvider makes a provider available to projections.

Call it before the server starts serving; a projection naming a provider this build did not register is refused when it is compiled.

func RegisteredCredentialProviders added in v0.4.0

func RegisteredCredentialProviders() []string

RegisteredCredentialProviders lists what a projection may name, sorted.

Short, and sometimes empty. A provider that talks to a cloud is that cloud's SDK linked into the binary, so the one this repository builds carries none of them and registers only what needs nothing — reading a credential out of a file. This is what an error about an unknown provider offers instead, so that somebody who deployed the stock image and asked it for AWS credentials is told what it does have rather than only what it does not.

func RegisteredDrivers

func RegisteredDrivers() []string

RegisteredDrivers lists what a projection may name, sorted. It is what an error about an unknown driver should offer instead.

func Rewrite

func Rewrite(stmt, driver string) (string, []string, error)

Rewrite converts a statement written with :name bind parameters into the placeholder syntax of the target driver, returning the rewritten statement and the parameter names in positional order.

Values are never interpolated into the statement: the returned names are resolved to driver arguments by the caller, so a request-supplied value can never change the shape of the query.

String literals, quoted identifiers, comments, and PostgreSQL's :: cast operator are skipped rather than treated as parameters.

func SupportsCredentialProviders added in v0.4.0

func SupportsCredentialProviders(driver string) bool

SupportsCredentialProviders reports whether a driver can authenticate a connection with a password minted for it.

It is Driver.AuthConnector restated as a question, so that the same check the CRD's CEL rule makes is one the code can be asked. A driver without one has no way to hand a per-connection password to the database: the password is in the connection string, the connection string is fixed when the pool opens, and a token that expires would take the pool down with it.

func SupportsNotifications

func SupportsNotifications(driver string) bool

SupportsNotifications reports whether a driver can push a change hint, which is what lets a watch be woken rather than having to ask on a timer.

func SupportsSessionVariables

func SupportsSessionVariables(driver string) bool

SupportsSessionVariables reports whether a driver has anything to set. SQLite has no session state to speak of, so a projection asking for it there is a mistake worth reporting at load time rather than a silent no-op.

func SupportsStatementTimeout

func SupportsStatementTimeout(driver string) bool

SupportsStatementTimeout reports whether a driver can be told to abort a statement that runs too long.

PostgreSQL only, among the built-ins. MySQL's max_execution_time is connection-scoped and applies to read-only SELECTs, which is most of a projection but not all of it, and SQLite has no equivalent at all — so rather than half a feature that is silently absent for a write, the other two are refused.

func Tables

func Tables(stmt, driver string) []string

Tables reports the tables a statement names.

It exists so a projection can say what it needs from the database without anybody reading its SQL: the answer is reported in the projection's status, where it can be handed to whatever manages the schema. Nothing here creates or changes anything — this is a description of what is read, not a schema that will be applied.

The scan is over statement text, sharing skipNonCode with the parameter rewriter, so a table name is never found inside a string literal, a comment, or a dollar-quoted body. It is deliberately conservative and deliberately incomplete:

  • A common table expression is reported alongside the tables it reads, so "WITH recent AS (SELECT ... FROM orders) SELECT * FROM recent" reports both orders and recent. Naming something that is not a table is the safe direction; failing to name one that is would not be.
  • A table named only inside dynamic SQL, or reached through a view, is not reported, because nothing in the statement text says so.
  • What a call like extract(epoch FROM ts) reads is not reported, because the scan steps over such calls whole. Their FROM is part of the function's syntax rather than a clause, and reading it as one named a column where a table belonged.

Both are why the result is described as what the projection names rather than as a complete schema.

func ValidateNotifyChannel

func ValidateNotifyChannel(name string) error

ValidateNotifyChannel rejects a channel name that could not be used safely.

LISTEN takes an identifier rather than a bind parameter, so the name goes into the statement text. It is quoted before it gets there, which is what actually makes it safe; this rejects the names that would be surprising even quoted, and gives a better error than the database would.

func ValidateSessionVariableName

func ValidateSessionVariableName(name string) error

ValidateSessionVariableName rejects anything that would not be safe to place in a statement.

The name cannot be a bind parameter in any of the supported drivers, so it goes into the statement text and has to be beyond suspicion: letters, digits, underscore, and the dot PostgreSQL uses to namespace custom settings.

Scanned by hand rather than with a regular expression because it runs on every query of every projection that sets a variable, and the answer for a given name never changes.

Types

type AuthOptions added in v0.4.0

type AuthOptions struct {
	Provider string
	Options  map[string]string
}

AuthOptions names a registered credential provider and hands it its settings. It is spec.dataSource.auth, carried through unchanged so that the pool layer makes the same decision the API object asked for.

type CredentialProvider added in v0.4.0

type CredentialProvider struct {
	// Name is what a projection's dataSource.auth.provider carries.
	Name string

	// Open builds the Credentials for one data source.
	//
	// Called once when the pool is opened, not once per connection, so this is
	// where a provider does the expensive part — resolving an SDK
	// configuration, discovering a region, reading an instance role — and
	// where it reports a request it cannot serve. An error here fails the
	// projection's compilation, which is where somebody is looking.
	Open func(req CredentialRequest) (Credentials, error)
}

CredentialProvider is one way of obtaining a password for a data source.

It is a registry rather than a switch for the same reason the driver registry is. Every provider means a cloud SDK linked into the binary, and kube-crisp deliberately links no dependency a given build does not need — pulling the AWS SDK into a build that projects a SQLite file would be several megabytes of code that build can never reach. So a provider registers itself, and a build that wants one is a build somebody assembled on purpose.

func LookupCredentialProvider added in v0.4.0

func LookupCredentialProvider(name string) (CredentialProvider, bool)

LookupCredentialProvider returns the provider registered under a name.

type CredentialRequest added in v0.4.0

type CredentialRequest struct {
	// Driver is the registered driver name, as spec.dataSource.driver carries
	// it — "postgres", not "pgx".
	Driver string

	// DSN is the connection string from the Secret, with everything but the
	// password in it: host, port, user, database, TLS settings. A provider
	// reads what it needs to address the database out of this — the endpoint
	// and the user, for RDS — rather than being told twice.
	DSN string

	// Options are the provider-specific settings from
	// spec.dataSource.auth.options, verbatim. A provider validates its own:
	// this is the one place a new provider can need configuration without an
	// API change, so the API cannot check it.
	Options map[string]string
}

CredentialRequest describes the data source a provider is being asked to authenticate.

type Credentials added in v0.4.0

type Credentials interface {
	// Password returns the password to authenticate the next connection with.
	Password(ctx context.Context) (string, error)
}

Credentials mint the password for one new connection.

The interface exists because a cloud database increasingly does not have a password to put in a Secret. AWS RDS IAM, Cloud SQL and Entra all hand out a short-lived token instead — a quarter of an hour, typically — and expect the client to ask for another one when it needs another connection.

The lifetime is what makes this a connection's property rather than a pool's. A token written into the connection string would change the string every time it was refreshed, and the connection string is what a pool is keyed by, so every refresh would build a new pool: live connections dropped, prepared statements thrown away, and the database asked to authenticate everything again, four times an hour, forever. Written in once and never refreshed, the pool survives and every connection opened after the token expires fails to authenticate instead — which is worse, because it looks like the database went down rather than like a credential ran out.

So the token is minted here, per connection, by a Connector that database/sql calls when it decides it needs one more. The pool is keyed by the connection string without the password in it, and nothing about it changes when the token does.

Password is called on the path that opens a connection, so it is called with that caller's context and must respect it. Implementations are expected to cache: database/sql opens connections in bursts after an idle period, and a provider that signs a fresh token for each of them pays for it eight times where it could pay once.

type CredentialsFunc added in v0.4.0

type CredentialsFunc func(ctx context.Context) (string, error)

CredentialsFunc adapts a plain function to Credentials.

func (CredentialsFunc) Password added in v0.4.0

func (f CredentialsFunc) Password(ctx context.Context) (string, error)

Password implements Credentials.

type Driver

type Driver struct {
	// Name is what a projection's dataSource.driver carries.
	Name string

	// SQLDriver is the name the database/sql driver registered itself under,
	// which is not always the same: the pgx driver answers to "pgx".
	SQLDriver string

	// Placeholders is how bind parameters are written for this database.
	Placeholders PlaceholderStyle

	// SessionVariables reports whether a setting can be scoped to one request,
	// which is what row-level security is driven through. A driver without them
	// rejects a projection that asks rather than silently doing nothing.
	SessionVariables bool

	// StatementTimeout reports whether the database can be told to abort a
	// statement that outruns its deadline, rather than only being abandoned.
	StatementTimeout bool

	// Notifications reports whether the database can push a hint that something
	// changed, which is what turns a watch from a poll into a wake-up.
	Notifications bool

	// PrepareDSN adapts a connection string before it is opened, for a default
	// that belongs to the driver rather than to the projection. Optional.
	PrepareDSN func(dsn string) string

	// AuthConnector builds a database/sql connector whose every new connection
	// authenticates with a password minted for it, rather than with one carried
	// in the connection string. Optional; a driver that cannot do this refuses
	// a projection that configures dataSource.auth.
	//
	// It is the seam a short-lived cloud credential needs, and the only one
	// there is. See pkg/sql/authconnector.go for why the token cannot live in
	// the connection string, and pkg/sql/credentials.go for what mints it.
	AuthConnector func(dsn string, creds Credentials) (driver.Connector, error)

	// Encrypted reports whether a connection string asks for transport
	// encryption. Optional; a driver that does not answer is never warned about.
	//
	// Credentials and every projected row cross this connection, so a database
	// reached over a network without it is sending both in the clear. Whether
	// that matters is the operator's call — a unix socket or a sidecar proxy
	// needs nothing — which is why this produces a warning and not a refusal.
	// What it must not do is say nothing at all, which is what happened before:
	// every example in the documentation asks for TLS and nothing noticed when
	// a connection string did not.
	Encrypted func(dsn string) bool

	// Verified reports whether a connection string asks for a TLS mode that
	// establishes which server it reached, rather than only encrypting the way
	// there. Required of any driver that sets AuthConnector, and consulted
	// before a minted credential is handed over.
	//
	// Separate from Encrypted, and stricter, because the two answer different
	// questions and only one of them is enough for a bearer token. A driver
	// answers for itself because only it knows what its connection string can
	// say: PostgreSQL spells the difference as sslmode=require against
	// verify-full, MySQL refuses its unverified modes by name, and a driver
	// added outside this repository has a vocabulary this package has never
	// seen. A driver that does not answer never passes.
	Verified func(dsn string) bool
}

Driver describes a kind of database kube-crisp can project from.

It exists so that the set is open. Everything that differs between databases is stated here rather than scattered through switch statements, so adding one is a registration rather than an edit in six places.

Adding a driver means building your own binary regardless — a database/sql driver has to be linked in — so a build that registers one also regenerates the CRD, whose enum lists the drivers that build accepts.

func Lookup

func Lookup(name string) (Driver, bool)

Lookup returns the driver registered under a name.

type Limiter

type Limiter struct {
	// contains filtered or unexported fields
}

Limiter bounds how many queries one projection has in flight.

It is per projection rather than per pool because pools are shared by every projection reaching the same database: a limit on the pool would be set by whichever projection opened it first. Connections to the database are bounded separately, by the pool's own size.

func NewLimiter

func NewLimiter(n int) *Limiter

NewLimiter returns a limiter for n concurrent queries. Zero or less means unlimited.

func (*Limiter) Acquire

func (l *Limiter) Acquire(ctx context.Context) (func(), error)

Acquire takes a slot, or reports ErrTooBusy once the wait exceeds acquireTimeout. Shedding beats queueing: a client that waits out its own timeout learns nothing, while a fast rejection lets it back off.

func (*Limiter) Limit

func (l *Limiter) Limit() int

Limit reports how many queries may run at once, or zero when unbounded.

type PlaceholderStyle

type PlaceholderStyle int

PlaceholderStyle is how a database wants bind parameters written.

const (
	// PlaceholderDollar numbers its parameters: $1, $2. PostgreSQL.
	PlaceholderDollar PlaceholderStyle = iota
	// PlaceholderQuestion is positional: ?. MySQL and SQLite.
	PlaceholderQuestion
)

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool is a driver-aware connection pool for one data source.

Connections are held open for reuse and, unless disabled, each distinct statement is prepared once per connection so that repeated requests skip parsing and planning.

func Open

func Open(opts PoolOptions) (*Pool, error)

Open establishes a pool. It does not verify connectivity; call Ping for that.

func (*Pool) Check

func (p *Pool) Check(ctx context.Context, statement string) error

Check asks the database whether it could run a statement, without running it.

Preparing is the cheapest way to find out. The database parses the statement and resolves every name in it against the catalogue — which is exactly the half that goes wrong when a projection outlives the schema it was written against — and then answers without touching a row. A statement that prepares may still fail on the data; one that does not prepare cannot succeed at all.

The statement is rewritten first, so what the database is asked about is the statement that would actually run, placeholders and all.

func (*Pool) Close

func (p *Pool) Close() error

Close releases prepared statements and the pool.

func (*Pool) Driver

func (p *Pool) Driver() string

Driver reports the configured driver enum value.

func (*Pool) EnforceTimeoutOn

func (p *Pool) EnforceTimeoutOn(want bool) bool

EnforceTimeoutOn reports whether this driver can be asked to bound a statement at the database, which is what decides whether asking for it means anything.

func (*Pool) Exec

func (p *Pool) Exec(ctx context.Context, stmt *Statement, args map[string]any) (int64, error)

Exec runs a statement that does not return rows and reports how many rows it affected. Statements that return rows, such as INSERT ... RETURNING, should go through Query instead.

func (*Pool) ExecWith

func (p *Pool) ExecWith(ctx context.Context, session []SessionVariable, stmt *Statement, args map[string]any) (int64, error)

ExecWith runs a statement with session variables applied, moving it into a transaction when there are any.

func (*Pool) Listen

func (p *Pool) Listen(ctx context.Context, channel string) (<-chan struct{}, error)

Listen subscribes to a database notification channel and reports each notification on the returned channel.

What comes back is a wake-up, not data. A notification says "something changed, ask again"; it does not say what, and it cannot be relied on to arrive — PostgreSQL delivers notifications on commit and drops them if the connection goes away. Which is exactly the right shape for this: the poll that already knows how to read what changed just stops waiting for its timer.

The channel has room for one, and a send that would block is dropped. Ten notifications arriving while a poll is running mean the same thing as one: read again when it finishes.

The subscription holds a connection for as long as it lives, because that is what LISTEN is. It reconnects on its own; the returned channel is closed only when ctx is done.

func (*Pool) Name

func (p *Pool) Name() string

Name reports the pool's label, which identifies the database without carrying its credentials.

func (*Pool) Ping

func (p *Pool) Ping(ctx context.Context) error

Ping verifies connectivity.

func (*Pool) Prepare

func (p *Pool) Prepare(stmt string, timeout time.Duration, maxRows int) (*Statement, error)

Prepare rewrites a :named statement for this pool's driver.

func (*Pool) PreparedCount

func (p *Pool) PreparedCount() int

PreparedCount reports how many statements are currently cached, for tests and diagnostics.

func (*Pool) Query

func (p *Pool) Query(ctx context.Context, stmt *Statement, args map[string]any) ([]Row, error)

Query executes stmt, resolving each declared bind parameter from args. A parameter with no entry in args is passed as NULL.

func (*Pool) QueryAllWith

func (p *Pool) QueryAllWith(ctx context.Context, session []SessionVariable, stmts []*Statement, args map[string]any) ([][]Row, error)

QueryAllWith runs several queries inside one transaction and returns a result set for each.

It is what makes a paged list and its count agree: run separately, rows can be inserted between them, and a client is told there are more objects than the page it is holding can account for. One transaction is one moment.

func (*Pool) QueryWith

func (p *Pool) QueryWith(ctx context.Context, session []SessionVariable, stmt *Statement, args map[string]any) ([]Row, error)

QueryWith runs a query with session variables applied to the connection it runs on.

With variables to set, the query moves into a transaction: that is the only way a setting can be scoped to one request rather than left behind on a pooled connection for whoever gets it next. Without them nothing changes, and the prepared statement cache still applies.

func (*Pool) Transact

func (p *Pool) Transact(ctx context.Context, stmts []*Statement, args map[string]any) ([]Row, int64, error)

Transact runs statements in order inside one transaction and returns the result of the last one.

This is what lets a projected kind span more than one table. A create that inserts an order and its line items has to be all or nothing; run as separate statements it can leave a row the API will then report as a complete object when it is not.

Only the last statement may return rows, because only its result can be the object the client is answered with. The others are executed for their effect, and their affected-row counts are summed into the count reported back.

Statements inside a transaction do not use the prepared-statement cache: the cache is per pool, and a transaction holds one connection of its own.

func (*Pool) TransactWith

func (p *Pool) TransactWith(ctx context.Context, session []SessionVariable, stmts []*Statement, args map[string]any) ([]Row, int64, error)

TransactWith runs a transaction with session variables applied to it first.

type PoolCache

type PoolCache struct {
	// contains filtered or unexported fields
}

PoolCache keeps one pool per data source key so that many projections sharing a database also share connections.

func NewPoolCache

func NewPoolCache() *PoolCache

NewPoolCache returns an empty cache.

func (*PoolCache) All

func (c *PoolCache) All() []*Pool

All returns every open pool, in no particular order.

func (*PoolCache) Close

func (c *PoolCache) Close()

Close releases every pooled connection.

func (*PoolCache) Evict

func (c *PoolCache) Evict(key string)

Evict closes and removes the pool for key.

func (*PoolCache) EvictIf

func (c *PoolCache) EvictIf(key string, pool *Pool) bool

EvictIf closes and removes the pool for key, but only if it is still the pool the caller was using.

Evicting by key alone is unsafe from anywhere that does not hold the pool it means to drop: the entry may have been replaced since, and closing the replacement takes the database away from every projection now serving through it. The admission check does exactly that — it retries after a pool is closed underneath it, and by then the key may hold a live pool.

func (*PoolCache) Get

func (c *PoolCache) Get(key string, open func() (*Pool, error)) (*Pool, error)

Get returns the pool for key, creating it via open if absent.

func (*PoolCache) Len

func (c *PoolCache) Len() int

Len reports how many pools are open.

func (*PoolCache) RetainOnly

func (c *PoolCache) RetainOnly(keep map[string]struct{}) int

RetainOnly closes and forgets every pool whose key is not in keep, which is how connections belonging to deleted projections are released.

type PoolOptions

type PoolOptions struct {
	Driver          string
	DSN             string
	MaxOpenConns    int
	MaxIdleConns    int
	ConnMaxLifetime time.Duration
	ConnMaxIdleTime time.Duration

	// PreparedStatements caches a prepared statement per distinct SQL text.
	PreparedStatements bool

	// StatementTimeout asks the database to abort a statement that outruns the
	// query's timeout, rather than only stopping this server waiting for it.
	//
	// It costs a transaction per query — that is the only scope a setting can
	// be confined to — and a transaction does not use the statement cache. It
	// is therefore opt-in rather than the default.
	StatementTimeout bool

	// MaxPreparedStatements bounds that cache. Zero takes the default.
	MaxPreparedStatements int

	// KeepAliveInterval pings the pool on this interval to keep connections
	// warm. Zero disables it.
	KeepAliveInterval time.Duration

	// Auth says how the password for this data source is obtained, when it is
	// not the one in the connection string. Nil is the ordinary case: the
	// Secret carries the whole DSN and nothing mints anything.
	Auth *AuthOptions

	// Name labels this pool's metrics.
	Name string
}

PoolOptions configures a connection pool.

type ResultFormat

type ResultFormat string

ResultFormat describes how a statement returns its rows.

const (
	// FormatRows is the ordinary one-object-per-row result set.
	FormatRows ResultFormat = "rows"
	// FormatJSONArray is a single row holding a single JSON array column, as
	// produced by PostgreSQL's json_agg.
	FormatJSONArray ResultFormat = "json"
)

type Row

type Row map[string]any

Row is one result row keyed by column name.

type SessionVariable

type SessionVariable struct {
	Name  string
	Value string
}

SessionVariable is one setting applied to the connection a query runs on, before it runs.

This is what lets a database enforce the tenancy boundary itself. With PostgreSQL row-level security, a policy reading current_setting('app.tenant') decides which rows exist for the query — so a mistake in a projection's WHERE clause cannot hand one tenant another's rows, because the database never offered them.

type Statement

type Statement struct {
	SQL     string
	Params  []string
	Timeout time.Duration
	MaxRows int

	// MaxBytes caps the size of the values a result set carries, in bytes.
	MaxBytes int
	Format   ResultFormat

	// ReturnsRows records whether this statement answers with a result set, so
	// a transaction knows whether to run it as a query or for its effect. The
	// caller sets it; the pool has no opinion about what a statement means.
	ReturnsRows bool

	// Prepared and EnforceTimeout are per statement rather than per pool, so
	// that projections which disagree about them can still share one pool.
	//
	// They used to be pool fields, and the pool key carried them so the
	// disagreement could not arise — which meant one database reached with two
	// different settings got two pools, each with its own MaxOpenConns. That
	// made --max-open-conns-per-datasource a bound on a pool rather than on a
	// database, which is not what it says or what an operator sizing a database
	// would assume.
	//
	// Neither setting was ever a property of the connection. A prepared
	// statement is cached by SQL text, and the statement timeout is applied
	// with SET LOCAL inside the transaction that runs the query, so it dies
	// with that transaction rather than travelling with the connection.
	Prepared       bool
	EnforceTimeout bool
}

Statement is a query prepared for one driver: the rewritten SQL plus the positional order of its bind parameters.

Jump to

Keyboard shortcuts

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