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

config

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: 15 Imported by: 0

Documentation

Index

Constants

View Source
const (

	// DefaultAIContextRows mirrors ai.DefaultContextRows. It is repeated
	// rather than imported so config stays a leaf package that every other
	// one can depend on.
	DefaultAIContextRows = 10

	// DefaultConnIdleTimeout is how long a pooled connection may sit unused
	// before the pool closes it (conn_idle_timeout). database/sql's own
	// default is forever, which keeps server-side backends — their memory and
	// a slot in the server's max_connections — alive for hours after the
	// last query. An hour is long enough that interactive use (run, read,
	// think, run again) never pays a reconnect, and short enough that a dbc
	// left open overnight gives its connections back. It is a ceiling, not a
	// keepalive: a server or proxy that cuts idle connections sooner still
	// does, and the drivers' liveness checks on checkout replace those
	// transparently (for Postgres, with db.pgShouldPing's help: pgx alone
	// skips the check within a second of the last checkout). The editor's
	// pinned session is checked out, never idle,
	// so this never expires it.
	DefaultConnIdleTimeout = time.Hour

	// DefaultConnectTimeout bounds opening a connection (connect_timeout):
	// the dial, the handshake and the first ping together. Without it an
	// unreachable host fails only at the OS's TCP connect timeout — over a
	// minute — while the UI shows "connecting…". Five seconds is ample for
	// any reachable server, including one across a VPN, and short enough that
	// a wrong host or a down VPN is reported while the user is still looking.
	DefaultConnectTimeout = 5 * time.Second
)
View Source
const (
	// DemoBytdb is the bytdb-backed demo, and the one active by default.
	DemoBytdb = "demo-bytdb"

	// DemoSQLite is the in-memory SQLite demo. It was plain "demo" before the
	// bytdb demo arrived; the engine suffix now names both the same way. The
	// shipped scripts name it directly — their ? placeholders are SQLite's, so
	// they could not run on the bytdb demo ($1) under any name.
	DemoSQLite = "demo-sqlite"
)

Names of the built-in demo connections. Both are registered when no config file is found, so the two embedded engines can be queried side by side without writing a config first.

View Source
const (
	TLSDisable    = "disable"
	TLSPrefer     = "prefer"
	TLSRequire    = "require"
	TLSVerifyCA   = "verify-ca"
	TLSVerifyFull = "verify-full"
)

TLS modes, as written in the tls key.

View Source
const DatabaseSep = "/"

DatabaseSep joins a configured connection's name to one of its server's databases, in a derived connection's name: "ProdDr/analytics".

A Postgres connection is bound to one database — every catalog the server has is per database, and there is no USE to move a session to another — so reaching a second database on the same host means opening a second pool. A MySQL pool is opened the same way, on the database its sidebar lists (see SupportsDatabases). Naming that pool "<base>/<database>" lets everything keyed by connection name (the Manager's pools and row counts, the pinned session, history, saved tabs, the web layout's per-connection keys) keep working with no second key, and a tab saved on a derived connection reopens on it after a restart, because the name alone says how to rebuild it.

Variables

View Source
var ErrConnExists = errors.New("a connection by that name already exists")

ErrConnExists is AddConn refusing a name already in use: ConnByName would always find the first of two, so the second could never be reached.

View Source
var ErrConnNotFound = errors.New("no connection by that name")

ErrConnNotFound is ReplaceConn finding no connection by the old name — removed (in another window, say) since the caller looked it up.

TLSModes lists the modes in order of strictness, for messages and the web form's select.

Functions

func DemoBytdbPath

func DemoBytdbPath() (string, error)

DemoBytdbPath returns the file backing the built-in bytdb demo, creating its parent directory. It prefers the OS cache directory and falls back to the temp directory on systems where the cache directory is undefined (a bare container with no $HOME, say).

func DerivedName added in v0.3.0

func DerivedName(base, database string) string

DerivedName is the name of base's connection to database.

func ExpandDSN

func ExpandDSN(name, driver, dsn string) (string, []string)

ExpandDSN expands ${VAR} and $VAR in a DSN from the environment, as Load does for the file's connections. It does this itself rather than via os.ExpandEnv so that a typo'd ${PGPASS} comes back as a warning naming it instead of silently becoming "" and surfacing later as a baffling auth failure. name is the connection's, for the warning.

Each value is escaped for where it lands in the DSN of driver (any alias db.Driver accepts): inside a quoted Postgres value, in a URL's password, in a MySQL param — see dsnexpand.go. So a password kept in the environment may hold any character, as one typed into the DSN form's field may.

func SavedDir added in v0.3.0

func SavedDir() string

SavedDir is the directory relative tls_* paths of a saved connection (one added in dbc web) resolve against: the saved-connections file's own, as a config file's resolve against its directory. "" when there is no home directory, which leaves them relative to the working directory.

func SavedFile

func SavedFile() string

SavedFile is where saved connections live, beside config.toml; "" when no home directory is known, and they then last only as long as a dbc web.

func SupportsDatabases added in v0.3.0

func SupportsDatabases(driver string) bool

SupportsDatabases reports whether a driver's connections can be derived onto another database of their server: Postgres, and MySQL (MariaDB too). Both tie what a connection's sidebar lists to the database its DSN names — Postgres because every catalog is per database, MySQL because dbc scopes its catalog queries to DATABASE() — so on both another database is another connection. MySQL could USE its way across instead, but a pool's connections are interchangeable and a USE would stick to only one of them; setting the database the pool dials with keeps every connection of it on the same one. SQLite and bytdb open one file, with no server to hold a second database.

db.HasDatabases (the database pickers) is this same set, so a picker never offers a database ConnByName could not derive.

Types

type Config

type Config struct {
	ScriptsDir string `toml:"scripts_dir"`
	MaxRows    int    `toml:"max_rows"` // rows fetched from the server

	// MaxDisplayRows caps how many of those rows the TUI table renders. It is
	// separate from MaxRows because the two are limited by different things:
	// fetching more is a question of memory and patience, rendering more costs
	// a widget per value and shows up as a sluggish scroll. Raising max_rows
	// for the sake of an export should not make the table crawl. Zero means no
	// display cap.
	MaxDisplayRows int `toml:"max_display_rows"`

	// The AI assistant (package ai). AIAgent picks the ACP backend —
	// "copilot" (the default), "claude" or "gemini"; AIModel is a preferred
	// model id, applied when the agent offers it. AIContextRows caps how many
	// result rows go with a question on connections that allow rows at all
	// (ai_rows); 0 sends none, and unset means ai.DefaultContextRows.
	AIAgent       string `toml:"ai_agent"`
	AIModel       string `toml:"ai_model"`
	AIContextRows int    `toml:"ai_context_rows"`

	// ConnIdleTimeout is how long a pooled connection may sit idle before it
	// is closed; see DefaultConnIdleTimeout. Written as a duration string in
	// the file ("30m", "2h"); 0 keeps idle connections open indefinitely, as
	// database/sql does by default. It applies to every connection — it is a
	// question of how long dbc sits idle, not of which database it talks to.
	ConnIdleTimeout time.Duration `toml:"conn_idle_timeout"`

	// ConnectTimeout caps opening a connection; see DefaultConnectTimeout.
	// A duration string in the file ("10s"); 0 sets no limit of dbc's own,
	// leaving it to the driver (a DSN's own connect_timeout, say) and the OS.
	// A DSN-level timeout still applies either way — whichever is shorter
	// wins. Global for the same reason as ConnIdleTimeout.
	ConnectTimeout time.Duration `toml:"connect_timeout"`

	// PlanTheme is the palette of the plan pictures the shell and the TUI
	// draw — `dbc explain -t pdf|jpeg|png` and the TUI's Save as PDF /
	// JPEG, and the schema diagrams of `dbc erd -t png|jpeg` and the TUI's
	// Diagram items: "dark" (the default, dbc's own slate) or "light" (paper, kinder
	// to a printer). Load normalizes it to one of the two, so readers can
	// hand it to theme.ByName without a second error to handle. dbc web
	// ignores it: its files follow the light or dark the view is wearing.
	// A string rather than a theme.Palette so config stays a leaf package.
	PlanTheme string `toml:"plan_theme"`

	DefaultConnection string `toml:"default_connection"`

	// Connections is written freely while the config is being built (Load,
	// the demo pruning, an ad-hoc --dsn), when nothing else can see it. Once
	// something may run beside a change — dbc web adding a connection while
	// its tabs connect and run — the change goes through AddConn/RemoveConn/
	// ReplaceConn and every reader through Conns/ConnByName, which take connMu.
	//
	// The writers are copy-on-write: AddConn, RemoveConn and ReplaceConn
	// build a new slice rather than edit the old one in place, so a slice a
	// reader got from Conns (or a range over the field that began before the
	// change) never sees an element change under it.
	Connections []Connection `toml:"connection"`

	Path string `toml:"-"` // file the config was loaded from ("" if none)
	Demo bool   `toml:"-"` // true when running with the built-in demo connection

	// Warnings are load-time findings worth telling the user about but not
	// worth refusing to start over, e.g. a DSN referencing an unset env var.
	Warnings []string `toml:"-"`
	// contains filtered or unexported fields
}

Config is the application configuration.

func Load

func Load(explicit string) (*Config, error)

Load reads the config from an explicit path, or searches ./dbc.toml then ~/.config/dbc/config.toml. With no config anywhere, it falls back to the built-in demo connections so the app is usable immediately.

func LoadDemo

func LoadDemo(explicit string, demo DemoEngine) (*Config, error)

LoadDemo is Load with a say in which built-in demo connection starts out active. The choice only matters when there is no config file: a real config names its own default_connection.

func (*Config) AddConn

func (c *Config) AddConn(cn Connection) error

AddConn appends a connection at runtime. The name must be new.

func (*Config) ConnByName

func (c *Config) ConnByName(name string) (Connection, bool)

ConnByName finds a connection config by name. A name no connection has exactly, of the form "<base>/<database>" where base is a Postgres or MySQL connection (SupportsDatabases), is that connection derived onto database (see DatabaseSep): a copy with Name set to name and Base and Database filled in.

The exact match is tried first, so a configured connection whose own name contains a "/" is still found as itself. Then the name is matched against each such connection's name plus the separator, rather than split at a "/": a quoted Postgres database name may contain one, and so may a connection's name. Of two bases that both match ("a" and "a/b" for "a/b/c"), the longer wins, as the more specific.

func (*Config) Conns

func (c *Config) Conns() []Connection

Conns is the connection list as it stands, for a reader that may run beside AddConn/RemoveConn. The slice is the one the writers replace wholesale, never edit, so it is safe to keep and range without the lock — callers must not modify it.

func (*Config) LoadSaved

func (c *Config) LoadSaved(path string, knownDriver func(string) bool)

LoadSaved merges the saved connections at path into c, after what c already holds, recording anything skipped in c.Warnings. A file that cannot be read or parsed is a warning too, not a failure: the config file's connections still work, and refusing to start over a stray character in a file dbc wrote would be out of proportion. See MergeSaved for knownDriver.

Call it once c is complete (after Load, the demos and any ad-hoc connection), before anything else can see c.

func (*Config) MergeSaved

func (c *Config) MergeSaved(saved []SavedConn, knownDriver func(string) bool) []string

MergeSaved adds saved connections to c, marked Web, and returns warnings for the ones it skips; it does not add them to c.Warnings (dbc web also merges the entries it moves from its old store, and logs them itself).

Skipped, and left in the file for the user to sort out:

  • an entry with no name or no driver (a hand edit gone wrong);
  • a name c already has — the config file's word wins, it being the user's deliberate one where this is only what was once typed in a form; so does the first of two saved entries by one name;
  • a driver knownDriver rejects. config cannot know the drivers itself (it is a leaf; db registers them), so the caller passes db's check. nil accepts every driver, and an unknown one fails at connect;
  • TLS settings TLSOpts.Check refuses. Skipped rather than merged without them: a connection that was meant to verify its server must not quietly connect in plaintext instead.

func (*Config) RemoveConn

func (c *Config) RemoveConn(name string) bool

RemoveConn drops a connection at runtime, reporting whether there was one by that name. A default_connection naming it is left alone: the lookups that honor it (workspace.New, dbc web's defaultConn) already fall back to the first connection when it names nothing.

func (*Config) ReplaceConn

func (c *Config) ReplaceConn(old string, cn Connection) error

ReplaceConn swaps the connection named old for cn, in place: the entry keeps its position, so an edit does not move it down the sidebar. cn may carry a new name; ErrConnExists refuses one another connection already has. Doing the lookup, the clash check and the swap under one lock is what makes two windows editing (or one editing while another removes) safe: the second of them fails here instead of leaving two entries by one name or resurrecting a removed one.

type Connection

type Connection struct {
	Name   string `toml:"name"`
	Driver string `toml:"driver"` // postgres | mysql | sqlite | bytdb
	DSN    string `toml:"dsn"`    // env vars are expanded, e.g. ${PGPASS}

	// TLSOpts holds the tls, tls_ca, tls_cert and tls_key keys: TLS for
	// postgres and mysql, one spelling for both — see tls.go. Embedded
	// untagged so the keys sit flat beside dsn. Load leaves the paths
	// absolute (ExpandTLS), so the db package never has to know which file
	// they were written in.
	TLSOpts

	// Migrations is the directory `dbc migrate` reads for this connection,
	// relative to the working directory. Per connection rather than global
	// because a config typically lists several databases and each has its
	// own schema. Empty means the -dir flag (or ".") decides.
	Migrations string `toml:"migrations"`

	// AIRows lets the AI assistant see this connection's result ROWS (up to
	// ai_context_rows of them). Off by default and per connection, because
	// rows are the database's contents and sending them to a hosted model is
	// a decision about that data: fine for a scratch database, not
	// something a production connection should do because a global switch
	// was flipped. The query text and errors go regardless — see package ai.
	AIRows bool `toml:"ai_rows"`

	// Demo marks a built-in demo connection, the only kind db.Manager seeds
	// with the demo cats table when it opens it. Set by demoFallback alone,
	// never read from a file: seeding runs CREATE TABLE and DELETE FROM cats,
	// so it must not be possible to switch on for a real database. It is a
	// per-connection flag rather than Config.Demo because a demo config can
	// also carry the ad-hoc --dsn connection, which is the user's database.
	Demo bool `toml:"-"`

	// Web marks a connection added in dbc web's browser UI and kept in the
	// saved-connections file (connections.toml, see SavedStore), not in the
	// config file. Never read from a file's key — MergeSaved sets it — so dbc
	// web can tell which ones it may edit or remove: the config file's
	// connections are the file's to change. Every dbc merges the saved ones,
	// so the TUI and headless runs list them too.
	Web bool `toml:"-"`

	// Base and Database describe a connection derived from a configured
	// one: the same server, credentials and TLS, opened on another database
	// of that server. ConnByName makes one up for a name of the form
	// "<base>/<database>" (see DatabaseSep); none is ever stored, in the
	// config or the saved-connections file. Database overrides whatever
	// database the DSN names when the pool is opened (db.openPool); "" keeps
	// the DSN's. Both are "" on a configured connection.
	Base     string `toml:"-"`
	Database string `toml:"-"`
}

Connection describes one database host connection.

type DemoEngine

type DemoEngine string

DemoEngine names which built-in demo connection starts out active. Both connections exist either way — this only picks the default.

const (
	DemoDefault      DemoEngine = "" // same as DemoEngineBytdb
	DemoEngineBytdb  DemoEngine = "bytdb"
	DemoEngineSQLite DemoEngine = "sqlite"
)

func ParseDemoEngine

func ParseDemoEngine(s string) (DemoEngine, error)

ParseDemoEngine resolves a -demo flag or $DBC_DEMO value. An empty string is the default (bytdb); anything unrecognized is an error rather than a silent fallback, because silently ignoring it would leave the user staring at the engine they just asked not to use.

type SavedConn

type SavedConn struct {
	Name   string `toml:"name"`
	Driver string `toml:"driver"`
	DSN    string `toml:"dsn"` // as typed: ${VAR}s unexpanded
	// TLSOpts is Connection.TLSOpts, with its paths as typed too: ${VAR}s
	// unexpanded and a relative path left relative (to SavedDir). Each
	// process resolves them as it merges the entry, as it does the DSN.
	TLSOpts
	// AIRows is Connection.AIRows: the assistant may see result rows.
	AIRows bool `toml:"ai_rows"`
	// Added orders the sidebar in dbc web (after the config file's). It is
	// kept across an edit so an edited entry does not move.
	Added time.Time `toml:"added"`
}

SavedConn is one connection in the saved-connections file.

func ReadSaved

func ReadSaved(path string) ([]SavedConn, error)

ReadSaved reads the saved connections at path, in file order. A missing file, or path == "", is none and no error: most users never add one.

type SavedStore

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

SavedStore is the writer of the saved-connections file, for dbc web: each change is a locked read-modify-write of the whole file, so it always starts from what is on disk — another dbc web's change, or a hand edit, included — rather than from what this process read at startup.

With path == "" (no home directory) it keeps the list in memory, lasting as long as the process, and callers do not branch on which kind they have.

func OpenSaved

func OpenSaved(path string) *SavedStore

OpenSaved returns the store for path. Nothing is read or created until the first call.

func (*SavedStore) Add

func (s *SavedStore) Add(c SavedConn) error

Add appends a connection. A name the file already has is ErrConnExists — another dbc web may have added it since this one started — rather than a replacement. A zero Added is now.

func (*SavedStore) Delete

func (s *SavedStore) Delete(name string) error

Delete removes a connection. A missing one is success: the caller wanted it gone.

func (*SavedStore) Get

func (s *SavedStore) Get(name string) (SavedConn, bool, error)

Get finds one saved connection by name.

func (*SavedStore) List

func (s *SavedStore) List() ([]SavedConn, error)

List returns the saved connections, in file order.

func (*SavedStore) Path

func (s *SavedStore) Path() string

Path is the file; "" for a memory-only store.

func (*SavedStore) Persistent

func (s *SavedStore) Persistent() bool

Persistent reports whether changes reach a file.

func (*SavedStore) Update

func (s *SavedStore) Update(old string, c SavedConn) error

Update replaces the connection named old with c, in place (the file's order is the sidebar's), keeping old's Added. c may carry a new name; one another entry has is ErrConnExists. A missing old is ErrConnNotFound: it was removed meanwhile, and writing c would bring it back.

type TLSOpts added in v0.3.0

type TLSOpts struct {
	TLS     string `toml:"tls,omitempty" json:"tls,omitempty"`           // a mode above; "" leaves TLS to the DSN
	TLSCA   string `toml:"tls_ca,omitempty" json:"tls_ca,omitempty"`     // PEM file of CA certificates to trust
	TLSCert string `toml:"tls_cert,omitempty" json:"tls_cert,omitempty"` // PEM client certificate, for servers that ask for one
	TLSKey  string `toml:"tls_key,omitempty" json:"tls_key,omitempty"`   // its PEM private key
	// TLSKeyPassword is "${VAR}" (or "$VAR"): the environment variable
	// holding tls_key's passphrase, when the key is encrypted. The
	// reference, never the passphrase — see "AN ENCRYPTED tls_key" above.
	TLSKeyPassword string `toml:"tls_key_password,omitempty" json:"tls_key_password,omitempty"`
}

TLSOpts is a connection's TLS settings. It is embedded (untagged) in Connection and SavedConn, so the TOML keys sit flat beside dsn in both files — a saved entry can still be copied into config.toml as it is. The JSON tags are dbc web's: its connection form and list embed it the same way. None of it is secret (paths, not the keys themselves), so unlike the DSN it does go back to the browser.

func ExpandTLS added in v0.3.0

func ExpandTLS(name string, t TLSOpts, base string) (TLSOpts, []string)

ExpandTLS returns t with its file paths made absolute: ${VAR} and $VAR expanded from the environment (as ExpandDSN does, with a warning for an unset one), a leading ~/ taken as the home directory, and a relative path joined to base — the directory of the file that defines the connection.

Relative to that file, not to the working directory: dbc is started from wherever the user happens to be (a project for its scripts, a migrations folder), and "certs/ca.pem" written next to config.toml should mean the same file every time. base "" leaves a relative path relative to the working directory — the ad-hoc --tls-* flags, typed in the shell, mean that. name is the connection's, for the warnings.

func (*TLSOpts) Check added in v0.3.0

func (t *TLSOpts) Check() error

Check normalizes the mode's case and turns away settings that cannot be what was meant. Every rule here fails closed: a typo in a security setting must not become "no TLS".

  • an unknown mode ("verify_full", "true") is refused rather than read as unset, which would fall back to the DSN — often plaintext;
  • a CA or certificate with no mode is refused: whether it was meant to verify or merely to be offered cannot be guessed;
  • so are files beside tls = "disable", which would never be read;
  • a client certificate needs its key and the other way round;
  • tls_key_password must be an environment reference, and needs a tls_key to unlock. A literal is refused rather than used: accepting it would leave a passphrase in a plain-text file, and in every browser dbc web shows the connection to.

func (TLSOpts) KeyPasswordVar added in v0.3.0

func (t TLSOpts) KeyPasswordVar() string

KeyPasswordVar is the name of the environment variable tls_key_password refers to, or "" when there is none — or when it is not a reference, which Check refuses.

func (TLSOpts) Set added in v0.3.0

func (t TLSOpts) Set() bool

Set reports whether any TLS key is given.

Jump to

Keyboard shortcuts

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