Help us shape the future of Go! Take the 2026 Go Developer Survey

db

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2026 License: MIT Imports: 45 Imported by: 0

Documentation

Index

Constants

View Source
const AllSchemasLimit = 5000

AllSchemasLimit is the most tables a Postgres database may have for its sidebar to offer "all schemas" at once. Below it, the whole list is a blink to read and to draw, and one list is easier to scan than a picker; above it, a schema at a time is the only load a big database stays quick under.

Variables

View Source
var ErrCanceled = errors.New("statement canceled")

ErrCanceled is returned when a statement was stopped by the user. Callers test for it with errors.Is; the underlying context error stays wrapped inside for anyone who cares which one it was.

View Source
var ErrInUse = errors.New("bytdb file in use by another process")

ErrInUse is returned when a bytdb file is held by another process — most often a second dbc (another TUI, or a TUI beside dbc web) on the same file, the demo's demo.bytdb included.

bytdb (v0.18.0+) takes an exclusive lock on a "<file>.lock" sidecar when it opens a file and holds it until close: two engines replaying and appending to one WAL would each miss the other's writes. The lock is taken by the driver's OpenConnector, i.e. inside sql.Open, and held for the *sql.DB's life, so the pool this Manager caches keeps the file for as long as the connection is open, idle connections or not. The loser gets this error on its open and nothing is cached, so the caller decides: a demo is dropped with a warning (openDemos), and a configured connection reports it where the user connected. Within one process the driver shares one engine per path, so two connections (or dbc web's store) on one file never collide.

View Source
var ErrNotFields = errors.New("this DSN cannot be shown as separate fields")

ErrNotFields is SplitDSN meeting a DSN the fields cannot hold without losing something — several Postgres hosts, a MySQL unix socket. The form then edits the DSN as text instead.

View Source
var ErrSessionLost = errors.New("session lost")

ErrSessionLost is returned when a pinned session's connection died after the session had taken on state (see FaultLost). The statement is not retried on a fresh session: a COMMIT replayed there would find no transaction and "succeed", and a statement after a lost SET would silently run under the defaults.

Functions

func BadConn

func BadConn(err error) bool

BadConn reports whether err means the connection the statement ran on is no longer usable — a caller holding a Session should discard it and open a fresh one.

func BuildDSN added in v0.3.0

func BuildDSN(driver string, p DSNParts) (string, error)

BuildDSN writes the DSN for driver (any alias db.Driver accepts) from p. The error is the user's to read: a field missing or holding something the DSN cannot carry.

func BuildSchema added in v0.3.0

func BuildSchema(driver, conn string, tables []TableRef, colRows, keyRows, hidden [][]string) *erd.Schema

BuildSchema assembles the diagram's model from the catalog queries' rows. tables is the sidebar's catalog (TableRefs of TablesQuery), which decides which tables exist and which are views; columns and keys of anything not in it (an index, a sequence, a system table) are ignored. hidden lists (schema, table) rows of catalog tables the diagram leaves out — the partitions of PartitionsQuery — and so the columns and keys of those too: a key cloned onto a partition, or cloned to reference one, has no box at one end and is dropped with it. Rows of the wrong shape are skipped rather than guessed at, as TableRefs does.

A table's Label follows TableIndex.Display over the whole catalog, hidden tables included, so the name the sidebar shows is the name erd.Schema.Find resolves: hiding a schema's only tables (partitions kept in a schema of their own) does not turn every other label bare.

func ChangesRows added in v0.3.0

func ChangesRows(stmt string) bool

ChangesRows reports whether stmt may have changed the row count of a table: it is not a plain read (IsRead), nor a statement that only sets up the session (sessionVerbs). It errs toward yes — an UPDATE changes no count, a CREATE INDEX none either — since being wrong that way costs one recount, and the other way a stale number in the sidebar.

func ColumnsQuery

func ColumnsQuery(driver string, tables []TableRef) (string, error)

ColumnsQuery returns the statement that lists the columns of the given tables, in declared order, as table_schema · table_name · column_name · data_type. It feeds the assistant's schema context, so the type is the one a person would write in DDL ("character varying(80)", "enum('a','b')"), which is what makes the model's SQL right, rather than a normalized family name ("USER-DEFINED", "ARRAY").

The table names are inlined as literals rather than bound as parameters: the placeholder syntax differs per driver ($1 vs ?), the names come from the connection's own catalog rather than from a user, and they are escaped all the same.

func CountQuery added in v0.3.0

func CountQuery(driver string, t TableRef) (string, error)

CountQuery returns the count(*) of one table, its name quoted for the driver: backticks on MySQL, standard double quotes elsewhere. The name comes from the catalog, but a table may be called anything, quotes included, so it is always quoted rather than trusted.

func DatabasesQuery added in v0.3.0

func DatabasesQuery(driver string) (string, error)

DatabasesQuery returns the statement that lists the databases a connection's server holds and its user may connect to, as (name, current), or "" for a driver without databases (HasDatabases).

Postgres: templates are left out (nobody browses template1), as are databases that refuse connections (datallowconn, e.g. one being dropped) and those the user lacks CONNECT on: picking one would only fail. The privilege test takes the oid, so a name needing quotes is not parsed as SQL.

MySQL: information_schema.schemata already shows only the databases the user holds some privilege in (all of them with SHOW DATABASES), which is the CONNECT test's counterpart. The server's own databases are left out, as Postgres's templates are — information_schema, performance_schema and sys are views of the server, and mysql is its grant tables — unless the DSN itself opened one, so the picker still names the database in use. With no database in the DSN, DATABASE() is NULL and no row is current.

func DefaultDatabase added in v0.3.0

func DefaultDatabase(cc config.Connection) string

DefaultDatabase is the database a connection's DSN opens, as the driver resolves it — pgx falls back to PGDATABASE, then to the user name, when the DSN names none; MySQL then opens on no database at all — or "" when that cannot be told (an unparsable DSN, a driver without databases) or there is none. A database picker maps this one back to the configured connection itself rather than to "<conn>/<database>", so picking it does not open a second pool onto the same database. A MySQL DSN naming no database maps none back: every database it lists is a derived connection, and the configured one stays the "no database" view.

func Driver

func Driver(d string) (string, error)

Driver resolves a config driver name (postgres, pg, mysql, sqlite3, ...) to the canonical database/sql driver name the manager opens it with, so other packages can branch on engine without re-listing the aliases.

func HasDatabases added in v0.3.0

func HasDatabases(driver string) bool

HasDatabases reports whether a driver's connection can list its server's databases and be switched onto one of them, as "<conn>/<database>" (config.DatabaseSep) — the database picker. It is config.SupportsDatabases, which decides what ConnByName will derive, for a known driver.

func InfoColumnsQuery added in v0.3.0

func InfoColumnsQuery(driver string, t TableRef) (string, error)

InfoColumnsQuery returns the statement behind the sidebar's "Show columns": one table's rows of information_schema.columns, in declared order, for a person to read and copy out of the grid. It differs from ColumnsQuery, which feeds the assistant, in three ways. It covers one table. It asks the standard view by name, because that is what was asked for and what a user would type to get it. And it keeps the columns a reader wants beside the type: nullability, default and length.

The select list is the same on every driver, so a copy reads the same wherever it came from. It is the intersection of what the four servers provide; bytdb's information_schema.columns has exactly these columns and a few more. The AS aliases pin the result's column names to lower case, since MySQL 8 reports information_schema column names in upper case.

The statement is shown in the history and can be re-run from there, so a user who wants every column of the view can edit it to SELECT *.

func IsEnvRef added in v0.3.0

func IsEnvRef(s string) bool

IsEnvRef reports whether s is nothing but one ${VAR} or $VAR reference. dbc web's edit form shows such a password, which names where the secret lives rather than being it, and withholds any other.

func IsRead

func IsRead(stmt string) bool

IsRead is isRead for other packages: whether running stmt could change anything. The TUI asks it before an EXPLAIN ANALYZE, to say what will happen before it does.

func Navigable(driver string) bool

Navigable reports whether a driver's sidebar is loaded a level at a time (databases, schemas, then one schema's tables) rather than whole.

func OpenDefaultDemo

func OpenDefaultDemo(m *Manager, cfg *config.Config) error

OpenDefaultDemo is SeedDemos for a headless run on the active demo: it opens the demos in config order — the active one first (see config.demoFallback) — and stops at the first that opens, so the other demo is not touched unless the active one is unusable. That keeps SeedDemos' fallback, where a demo that cannot be opened (the bytdb file held by a running TUI, say) gives way to the other one with a warning, without paying for it on every run.

func PartitionsQuery added in v0.3.0

func PartitionsQuery(driver string) (string, error)

PartitionsQuery returns the statement listing the tables that are partitions of another (schema · table), or "" when the engine has none that the diagram needs to hide.

WHY HIDE THEM. A Postgres partitioned table's partitions are tables in the catalog, and the sidebar lists them (a person may well query one directly). But in a diagram each would be a box of its own, with a clone of every one of the parent's foreign keys: Postgres copies a key declared on a partitioned table onto each partition (pg_constraint rows with conparentid <> 0), and a key referencing a partitioned table onto each of the referenced partitions too. A table split by month over three years would draw 36 copies of itself, each with the parent's lines. The partitioned table itself carries the model: its columns and the keys declared on it.

relispartition is set on every partition, a sub-partition included, and has been since partitioning arrived (Postgres 10). bytdb has no partitioning, and its pg_class need not carry the column, so it gets no query.

func RowEstimatesQuery added in v0.3.0

func RowEstimatesQuery(driver string) (string, error)

RowEstimatesQuery returns the statement that reads a driver's per-table row estimates as (schema, name, estimate), or "" for a driver that keeps none. An estimate the database does not have yet comes back negative or NULL, and is read as unknown.

func SchemaColumnsQuery added in v0.3.0

func SchemaColumnsQuery(driver string) (string, error)

SchemaColumnsQuery returns the statement listing every column of every table and view the connection shows, as schema · table · column · type · nullable (YES/NO) · position. The type is the declared one, as ColumnsQuery gives the assistant ("character varying(80)"), which is what a diagram's reader wants to see. Position is pg_attribute's attnum on Postgres and bytdb — what pg_constraint's key arrays refer to — and the ordinal position elsewhere.

func SchemaKeysQuery added in v0.3.0

func SchemaKeysQuery(driver string) (string, error)

SchemaKeysQuery returns the statement listing every primary, unique and foreign key, in the shape described at the top of this file.

func SchemaSummaryQuery added in v0.3.0

func SchemaSummaryQuery(driver string) (string, error)

SchemaSummaryQuery returns the statement that lists a database's schemas as (name, tables, is_default), or "" for a driver that is not Navigable.

Every user schema is listed, empty ones too (the LEFT JOIN), so a schema the user knows is there is in the picker even with nothing in it. The counts are of what the sidebar would list (TablesQuery's relkinds), and cost one pass over pg_class with no per-table work. is_default marks the first schema on search_path that exists — current_schemas(false) leaves out the implicit pg_catalog and any "$user" with no schema — which is the schema a bare table name resolves to, and so the one to open on.

func SchemaTablesQuery added in v0.3.0

func SchemaTablesQuery(driver, schema string) (string, error)

SchemaTablesQuery is TablesQuery narrowed to one schema, in the same shape. Only a Navigable driver has one; the others list their tables whole.

func SeedDemo

func SeedDemo(m *Manager, name string) error

SeedDemo populates one connection with the demo cats table so the app has something to query out of the box. It seeds unconditionally, whatever the connection is — the tests seed their own private databases with it — so it is never pointed at a user's connection. The built-in demos do not need it: the Manager seeds those itself on first open (see Manager.open), and SeedDemo on one of them only runs the script a second time, harmlessly.

The DELETE is what makes this safe to re-run: the SQLite demo lives in memory and starts empty every time, but the bytdb demo is a file that survives restarts, so without it a second launch would collide with the rows the first one inserted.

func SeedDemos

func SeedDemos(m *Manager, cfg *config.Config) error

SeedDemos opens every built-in demo connection up front — which seeds it (see Manager.open) — and reports which ones could not be opened rather than failing the launch over them. The TUI calls it: it lists every connection, so an unusable one has to be found before the list is shown. A headless run wants only one connection and calls OpenDefaultDemo instead, or nothing.

One demo failing should not take the other down with it. The bytdb demo is a file, so it has failure modes the in-memory SQLite one does not: a second dbc already holds the engine open, the cache directory is read-only, the file is left over from an incompatible version. In any of those cases the connection is dropped from the config — leaving it listed would only offer the user a connection that errors on every query — a warning is recorded, and if it was the active default the surviving demo takes over.

Only connections marked Demo are touched. A demo config can also hold the ad-hoc --dsn connection, and that one is the user's database: it is neither opened here nor ever seeded.

Returns an error only when no connection at all is left to start on.

func SessionLost

func SessionLost(name string, err error) error

SessionLost wraps err (a BadConn error from a stateful session) as ErrSessionLost, in words that tell the user what is gone and what to do.

func SetDriverLog added in v0.3.0

func SetDriverLog(w io.Writer)

SetDriverLog sends the database drivers' own log lines to w. Only go-sql-driver/mysql writes any: it logs a connection that died under it ("mysql packets.go:58 unexpected EOF", "closing bad idle connection") straight to stderr, on top of the error it returns to the caller.

On stderr those lines are right for the headless commands and dbc web's server log, but the terminal UI owns the screen: a stray write lands in the middle of its frame and stays there until the next full redraw. The TUI passes a writer of its own, which shows each line in its log pane. nil restores stderr.

func TablesQuery

func TablesQuery(driver string) (string, error)

TablesQuery returns the statement that lists a driver's tables and views — the `\dt` of whichever database this is. The shape is deliberately the same across drivers (schema/owner, name, type), so the results table reads the same wherever you press the key.

It is ordinary SQL run through the ordinary path, which means it is cancelable, capped by max_rows, and exportable like any other result.

Types

type Column

type Column struct {
	Name string
	Type string
}

Column is one column of a table: its name and declared type.

type DSNParts added in v0.3.0

type DSNParts struct {
	Host     string `json:"host,omitempty"`
	Port     string `json:"port,omitempty"`
	User     string `json:"user,omitempty"`
	Password string `json:"password,omitempty"`
	Database string `json:"database,omitempty"`
	File     string `json:"file,omitempty"`
	Options  string `json:"options,omitempty"`
}

DSNParts is a DSN taken apart into what a person fills in. Host, Port, User, Password and Database are the server engines'; File is the embedded ones'. Options is everything else the driver takes, as key=value pairs separated by spaces or & (a value may be 'single quoted', for Postgres).

func SplitDSN added in v0.3.0

func SplitDSN(driver, dsn string) (DSNParts, error)

SplitDSN takes a stored DSN apart into fields, for the edit form: the inverse of BuildDSN, and of the common hand-written shapes too — a Postgres URL as well as the keyword form. ${VAR}s come back as typed.

A DSN the fields could not hold faithfully is ErrNotFields; one the driver could not read either is an error too. Either way the form falls back to editing the DSN as text, which loses nothing.

type DatabaseInfo added in v0.3.0

type DatabaseInfo struct {
	Name    string
	Current bool // the database this connection is open on
}

DatabaseInfo is one database on a connection's server.

type ExplainOptions

type ExplainOptions struct {
	// Analyze runs the statement to measure it (see the table above).
	Analyze bool
}

ExplainOptions choose what Explain does.

type Fault

type Fault int

Fault says what a failed Run means for the session it ran on, so every holder of a Session applies the same rule when a statement fails.

const (
	// FaultNone: the statement failed (or did not), the session is fine.
	FaultNone Fault = iota
	// FaultRetry: the connection was dead before the statement reached the
	// server (the driver said so with driver.ErrBadConn), and the session
	// held no state. Drop it and run the statement again on a new one.
	FaultRetry
	// FaultDrop: the connection died, possibly with the statement already
	// sent — it may have run. Drop the session but report the error as it
	// stands; replaying a statement that may have run is not safe.
	FaultDrop
	// FaultLost: the connection died with session state on it. Drop the
	// session and report SessionLost: whatever comes next must not run as if
	// the transaction or settings were still there.
	FaultLost
)

type Manager

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

Manager owns the pool of named database connections. Connections are opened lazily on first use and cached for the life of the process.

Opening a connection means a network dial and a ping, which against an unreachable host takes the full ping timeout. mu is never held across that: it guards only the maps, and a connection that is being opened is tracked in opening, so concurrent callers for the same name wait for the one open in progress (and can give up on it with their own ctx) while callers for any other name are not held up at all.

DBContext("a") ──lock── cached? ─yes─► return
                         │no
                  opening["a"]? ─yes─► unlock, wait on call.done (or ctx)
                         │no
              opening["a"] = call, unlock
              sql.Open + ping (no lock held)
              lock, conns["a"] = dbh, delete opening["a"], unlock
              close(call.done) ── wakes the waiters

func NewManager

func NewManager(cfg *config.Config) *Manager

func (*Manager) Catalog added in v0.3.0

func (m *Manager) Catalog(ctx context.Context, name, schema string) (*model.Result, error)

Catalog lists tables for a sidebar: those of schema on a Navigable driver, or the whole catalog when schema is "" (and always, on the other drivers, which are not listed by schema).

It runs on the pool, so it never lands inside a transaction the user has open, and reads the rows itself rather than through Run so that max_rows does not apply (see maxCatalogRows). A list cut at maxCatalogRows comes back with Truncated set, for a UI to say so, rather than as an error, since the tables it did read are still worth listing.

The result has Columns and Rows but no Raw: it is drawn as a list, never as a grid or an export (Ctrl+T, which is, still goes through Run).

func (*Manager) Close

func (m *Manager) Close()

Close closes all open connections. A connection still being opened when Close runs is closed by its opener instead of being cached (see closed).

func (*Manager) Columns

func (m *Manager) Columns(ctx context.Context, name string, tables []TableRef) ([][]Column, error)

Columns describes tables on the named connection: result i holds the columns of tables[i], in declared order, and is nil for a table the catalog did not return (dropped since the connect, or a view the driver cannot describe). It runs on the pool, like the sidebar's catalog query, so it never lands inside a transaction the user has open on their session.

func (*Manager) DB

func (m *Manager) DB(name string) (*sql.DB, error)

DB returns the live *sql.DB for a named connection, opening and pinging it on first use.

func (*Manager) DBContext

func (m *Manager) DBContext(ctx context.Context, name string) (*sql.DB, error)

DBContext is DB with a context: canceling ctx abandons the wait for a connection being opened, and aborts the ping when this caller is the one opening it. The open is additionally capped at connect_timeout (config.ConnectTimeout).

func (*Manager) Databases added in v0.3.0

func (m *Manager) Databases(ctx context.Context, name string) ([]DatabaseInfo, error)

Databases lists the connection's server's databases per DatabasesQuery, on the pool; nil with no error for a driver without databases (HasDatabases).

func (*Manager) Disconnect added in v0.3.0

func (m *Manager) Disconnect(name string) bool

Disconnect closes name's pool, and those derived from it, as Drop does — the user hanging up, rather than a pool known to be unusable — and reports whether it did. A shared in-memory SQLite database is left open: its contents live only as long as its pool (see anchors), so closing it would not disconnect from that database but destroy it. A later DB call opens a closed pool afresh, as after Drop.

func (*Manager) Drop

func (m *Manager) Drop(name string)

Drop closes a single cached connection and forgets it, so a later DB call opens a fresh one. Used when a connection is known to be unusable — an embedded engine whose file could not be initialized, say — where leaving the handle in the pool would only hand it back to the next caller.

The pools derived from the connection onto its server's other databases (config.DatabaseSep) go with it: an edit may have changed the host or the credentials they were opened with, and a removal leaves them no base.

func (*Manager) Explain

func (m *Manager) Explain(ctx context.Context, name, stmt string, opt ExplainOptions) (*explain.Plan, error)

Explain opens a session on the named connection, explains stmt on it and closes it — the headless path, which has no session to reuse.

func (*Manager) ForgetRowCounts added in v0.3.0

func (m *Manager) ForgetRowCounts(name string)

ForgetRowCounts drops the named connection's cached row counts, so the next RowCounts counts afresh. The workspace calls it after a run that may have changed rows (ChangesRows): the cache would otherwise serve the numbers from before it for up to rowCountTTL.

func (*Manager) Names

func (m *Manager) Names() []string

Names returns the configured connection names in config order.

func (*Manager) RowCounts added in v0.3.0

func (m *Manager) RowCounts(ctx context.Context, name string, tables []TableRef) (map[TableRef]RowCount, error)

RowCounts returns the row count of each of tables on the named connection, from the cache when a counting of them started less than rowCountTTL ago. A table missing from the result has no count: a view, or a table whose count failed and has no estimate to fall back on.

The returned map is shared with the cache and other callers: read it, never write to it.

A counting in flight for the same connection is waited for rather than duplicated. If it fails, or turns out not to cover these tables, the waiter counts for itself.

ask ──► cache fresh & covers? ── yes ──► cached counts
          │ no
          ├─ counting in flight? ── yes ──► wait, then ask again
          │ no
          └─ mark in flight ─► count ─► cache (on success) ─► unmark

func (*Manager) Run

func (m *Manager) Run(name, stmt string, args ...any) (*model.Result, error)

Run executes a statement on the named connection, without cancellation.

func (*Manager) RunContext

func (m *Manager) RunContext(ctx context.Context, name, stmt string, args ...any) (*model.Result, error)

RunContext executes a statement on the named connection. SELECT-like statements return their rows; anything else runs as Exec and returns rows affected. Canceling ctx aborts the statement on the server where the driver supports it, and always stops row collection.

The statement takes whichever pooled connection is free. Statements that depend on session state — BEGIN/COMMIT, SET, temp tables — need a Session.

func (*Manager) Schema added in v0.3.0

func (m *Manager) Schema(ctx context.Context, name string) (*erd.Schema, error)

Schema reads the connection's schema for a diagram: its tables and views (as the sidebar lists them), their columns and their keys. It runs on the pool, like the sidebar's catalog query, so it never lands inside a transaction the user has open on their session, and it reads rows itself rather than through Run so the max_rows cap does not apply.

func (*Manager) SchemaSummary added in v0.3.0

func (m *Manager) SchemaSummary(ctx context.Context, name string) ([]SchemaInfo, error)

SchemaSummary lists the connection's schemas per SchemaSummaryQuery, on the pool; nil with no error for a driver that is not Navigable.

func (*Manager) Session

func (m *Manager) Session(ctx context.Context, name string) (*Session, error)

Session pins a connection on the named database. The caller must Close it.

func (*Manager) SetMemoryPool

func (m *Manager) SetMemoryPool(n int)

SetMemoryPool sets the pool cap of every shared in-memory SQLite database, open now or opened later. n counts every connection: the anchor, one per pinned session, and the pool's own. A UI that pins more than one session at a time (dbc web: one per browser tab) raises it; n < 3 is ignored, since the anchor and one session alone would leave nothing for the pool.

A session that finds the pool full waits for a connection under its run's context, so the cost of too small a cap is a run that waits (and can be canceled), not a failure.

type ProbeResult

type ProbeResult struct {
	OK   bool
	Took time.Duration // open + ping; zero when nothing was dialed
	Note string        // words for the user when OK is true but qualified
}

ProbeResult is what a connection test found. OK with a Note is a test that did not dial at all but has something to say (see Probe).

func Probe

func Probe(ctx context.Context, cc config.Connection, timeout time.Duration) (ProbeResult, error)

Probe tests a connection that is not (yet) in the config: it opens a pool with the same driver setup the Manager uses (driverFor, sqliteDSN, openPool), pings it under timeout, and closes it again. Nothing is cached — the point is to learn whether the DSN works before it is saved, without leaving a pool behind for a connection that may never be added.

The embedded engines open a file, and opening a file that does not exist creates it. A test must not leave an empty database behind at a mistyped path, so for those a missing file is reported without opening anything:

sqlite in-memory (mode=memory) ─► open + ping (nothing to create)
sqlite / bytdb, file missing ───► OK, Note: "will be created on first connect"
sqlite / bytdb, file present ───► open + ping
postgres / mysql ───────────────► open + ping

A bytdb file held by another process — or by this one, when a configured connection already has it open — fails with ErrInUse, as a real connect would. The error never carries the DSN: callers show it to the user.

type RowCount added in v0.3.0

type RowCount struct {
	N        int64
	Estimate bool
}

RowCount is a table's number of rows. Estimate reports a number taken from the database's statistics rather than counted.

func (RowCount) Sentence added in v0.3.0

func (c RowCount) Sentence(name string) string

Sentence is the count in words, for a tooltip: "cats with 1 row", "cats with 1,234 rows", "logs with about 12,345,678 rows (estimated)".

func (RowCount) Short added in v0.3.0

func (c RowCount) Short() string

Short is the count as the sidebar prints it beside a name: the exact number with separators while it is short ("1,234", "98,765"), compacted beyond that ("123K", "1.2M"), and marked "~" when estimated.

type SchemaInfo added in v0.3.0

type SchemaInfo struct {
	Name    string
	Tables  int  // its tables, views, matviews and foreign tables
	Default bool // the first schema on search_path: what a bare name means
}

SchemaInfo is one schema of a database, as the sidebar's picker lists it.

type Session

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

Session is a run pinned to one connection from the pool, so a sequence of statements shares a database session: BEGIN/COMMIT bracket the statements between them, SET and temp tables outlive the statement that made them. Close discards the connection rather than returning it to the pool, so none of that state can leak to the pool's next user.

A Session is not safe for concurrent use; its holder serializes access.

func (*Session) Classify

func (s *Session) Classify(err error) Fault

Classify reads a Run error against the session's state:

err == nil or connection still alive ─────────────► FaultNone
connection dead, session stateful ────────────────► FaultLost
connection dead, stateless, driver.ErrBadConn ────► FaultRetry
connection dead, stateless, anything else ────────► FaultDrop

"Dead" is not only driver.ErrBadConn. A driver reports that only when it knows the statement was never sent; a connection cut while the statement was in flight comes back as the driver's own network error ("unexpected EOF", "connection reset by peer"), and the connection is closed behind it. So after any error the driver is asked directly whether the connection is still usable — see alive.

func (*Session) Close

func (s *Session) Close() error

Close releases the session by closing its connection outright, so whatever the session left open is gone on every engine: the server rolls back an unfinished transaction when its connection ends, and SET values and temp tables end with it.

Handing the connection back to the pool instead would leave that to the driver's session reset, and the drivers do not agree: pgx discards a connection that is mid-transaction and bytdb rolls it back, but the MySQL and SQLite drivers return it as-is — so the pool's next user (a catalog refresh, a script's Query) would run inside the user's open transaction. SET values and temp tables survive the reset on every driver. Sessions are opened rarely, so the reconnect this costs is nothing.

Raw with a callback that returns driver.ErrBadConn is database/sql's one supported way to get a checked-out connection closed instead of pooled. It also closes the sql.Conn, so there is no Conn.Close after it.

func (*Session) Explain

func (s *Session) Explain(ctx context.Context, stmt string, opt ExplainOptions) (*explain.Plan, error)

Explain describes how the session's database runs stmt. A stmt that is itself an EXPLAIN is unwrapped first (and its ANALYZE honored), so a user's own "EXPLAIN ANALYZE SELECT …" works too.

It runs under the session's guard, as Run does (sessionguard.go): an ANALYZE runs the statement, so its Stop has to reach the server too.

func (*Session) Name

func (s *Session) Name() string

Name is the connection the session is pinned to.

func (*Session) Run

func (s *Session) Run(ctx context.Context, stmt string, args ...any) (*model.Result, error)

Run executes one statement on the pinned connection, as RunContext does on the pool.

It runs under guard (sessionguard.go): a connection the server cut since the last statement is found before this one is sent, and reported as driver.ErrBadConn, which Classify may retry; and a canceled statement is stopped on the server, not only abandoned by the driver.

func (*Session) Stateful

func (s *Session) Stateful() bool

Stateful reports whether the session may hold state a replacement session would not have — an open transaction, SET values, temp tables. It errs on the side of yes: any successful statement that is not a plain read counts (a write with RETURNING included), even ones (an UPDATE outside a transaction) that leave nothing behind.

type TableIndex

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

TableIndex finds the catalog's tables that a piece of text mentions. It is built once per connect and asked on every frame the assistant's context chip draws, so the lookups are two maps rather than a scan of the catalog.

Matching is by WORD, not by parse. A statement can name a table in more places than any small grammar covers (FROM, JOIN, INTO, UPDATE, a CTE body, a subquery, a DDL statement), and a question in prose names it with no grammar at all ("which cats are oldest?"). A word that happens to equal a table name costs one table's column list in the prompt; a table the model never hears about costs a guessed column name in the SQL it writes back. The first error is much cheaper, so the matcher errs toward it.

func NewTableIndex

func NewTableIndex(tables []TableRef) *TableIndex

NewTableIndex indexes a catalog.

func (*TableIndex) Display

func (x *TableIndex) Display(t TableRef) string

Display is how a table should be named to a reader: bare when the connection has one schema (every sqlite and mysql connection, most Postgres ones), qualified when the name alone could be ambiguous. The sidebar follows the same rule.

func (*TableIndex) Len

func (x *TableIndex) Len() int

Len is the number of tables in the catalog.

func (*TableIndex) Lookup added in v0.3.0

func (x *TableIndex) Lookup(qname string) (TableRef, bool)

Lookup finds a table by the name the sidebar shows for it, which is Display's: "schema.name" when the connection has several schemas, the bare name when it has one. Either spelling is accepted, so a qualified name still resolves on a one-schema connection.

The maps are keyed in lower case (for Mentioned's word matching), but a Postgres catalog can hold both "Cats" and cats. An exact match among the candidates therefore wins; a case-insensitive one is taken only when it is the only candidate, so an ambiguous name is refused rather than guessed.

func (*TableIndex) Mentioned

func (x *TableIndex) Mentioned(sql, prose string) []TableRef

Mentioned returns the tables named in a SQL statement or in prose, in order of first mention, each once. The statement's words come first, since that is what the question is most often about.

In SQL the lexer's view of the text is used, so a table name inside a string literal or a comment does not count, and a quoted identifier ("Cats", `cats`) counts without its quotes. Prose has no such structure — an apostrophe in "what's" is not a string — so it is split on word boundaries only.

A dotted word (public.cats, mydb.cats, c.name) matches only as schema.table. It never falls back to its last part: in `c.name`, `c` is a table alias and `name` a column, and a table called `name` is not meant.

On an index that holds only some of the database's schemas (SetSchemas), a dotted word whose schema is one of the others is reported too, as the table it names, unverified: the index cannot know whether that table exists, and loading the schema to find out is what the sidebar avoids. The column lookup that follows (Manager.Columns) is the check — a table it finds no columns for is dropped before anything is sent. The word's schema must be a real schema, which keeps a table alias ("c.name") from becoming a guess. The name is taken as Postgres would read it: in lower case when unquoted (Postgres folds it), as written when quoted in the SQL (billing."Invoices"), which is why sqlWords marks a quoted part rather than dropping its quotes. Prose has no quoting to read, so its names are folded.

func (*TableIndex) SetSchemas added in v0.3.0

func (x *TableIndex) SetSchemas(all []string) *TableIndex

SetSchemas tells an index built from part of a database's catalog (one schema's tables, as a Postgres sidebar loads them) what the whole database's schemas are. Two rules follow from the whole, not the part:

  • Display qualifies names whenever the DATABASE has several schemas, even though the index holds one schema's tables: on Postgres a bare name resolves through search_path, and the schema being browsed is often not on it, so "orders" would not find sales.orders.
  • Mentioned also reports a schema-qualified word naming one of the schemas it did not load ("billing.invoices") — see Mentioned.

It returns x, for chaining onto NewTableIndex.

type TableRef

type TableRef struct {
	Schema string
	Name   string
	View   bool
}

TableRef names one table or view in a connection's catalog, as a row of TablesQuery reports it.

func TableRefs

func TableRefs(rows [][]string) []TableRef

TableRefs reads TablesQuery's rows (table_schema, table_name, table_type). Rows of any other shape are skipped rather than guessed at.

Jump to

Keyboard shortcuts

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