driver

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package driver is TableX's database abstraction layer.

Everything that genuinely differs between SQL engines lives behind the Dialect interface: DSN building, identifier quoting, placeholder syntax, metadata introspection and a handful of capability flags. The generic query path (open pool, run query, scan rows, paginate) is implemented once on top of database/sql in this package, so handlers never import a concrete engine package. Adding a new engine means implementing Dialect and registering it — nothing else in the application changes.

See docs/database-drivers.md for the design rationale.

Index

Constants

View Source
const (
	ObjectTable   = "table"
	ObjectView    = "view"
	ObjectMatView = "matview"
)

SchemaEditor is an optional Dialect capability for structure editing (DDL). Engines opt in by implementing it; handlers discover it via a type assertion, mirroring Monitor. Every identifier in the returned statements is quoted with QuoteIdent inside the builder — the handler validates identifiers first (new names via ValidNewIdentifier, existing objects by exact match against introspection). Builders return one or more statements: a PostgreSQL column "modify" expands to several ALTER COLUMN steps; other engines return a single statement. Referential actions passed to the FK builders are pre-validated keywords emitted verbatim. Object kinds for DropObjectSQL / RenameObjectSQL. matview is PostgreSQL-only; other engines treat it as a plain view.

View Source
const (
	DefaultMaxOpenConns = 8
	DefaultMaxIdleConns = 4
)

Defaults for a zero Tuning. Pool sizing is deliberately modest: PoolCap multiplies it, and the product is what has to stay clear of the server's own max_connections.

View Source
const MaxCellBytes = 1 << 20 // 1 MiB

MaxCellBytes bounds how much of one text cell a DISPLAY result retains. It mirrors the []byte branch's existing 1 MiB heuristic, which never covered the string branch — and pgx returns PostgreSQL text/json as string, so those cells were entirely uncapped: one 100 MB `text` value, times a 500-row page, is an unbounded allocation driven purely by row content.

Every display consumer renders at most a couple of hundred characters per cell (`truncate 200` in the browse and result grids), so 1 MiB is five thousand times more than any of them can show and the cap is never observable. The one consumer that needs the exact value — the row-edit prefill, which is submitted back as the new value — scans through ScanResultVerbatim instead, so a long TEXT column can never be truncated on save.

View Source
const ReadStmtTimeout = 60 * time.Second

ReadStmtTimeout is the DEFAULT budget for a single generated read statement (introspection, browse, count, estimate, and the three handler-built reads) so a few slow reads cannot tie up every pooled connection until the client disconnects. It is generous on purpose: the same introspection methods drive metadata pages and export planning on large schemas, which must not be truncated. Operators override it with Tuning.ReadStmtTimeout.

It is applied ONLY to generated reads. It is deliberately NOT applied to the SQL console (Query — the user's own SQL), export Stream, any Pinned path, or any generated mutation/DDL (Exec/ExecScript): force-cancelling a write or DDL mid-statement is riskier (partial effects, lock churn) than a slow read holding a connection, and those are already bounded by the request context.

Variables

View Source
var ErrUnsupported = errors.New("operation not supported by this engine")

ErrUnsupported is returned by optional Dialect operations that a given engine does not provide (e.g. listing events on SQLite). Callers gate on Capabilities() before calling, so this is a defensive backstop.

Functions

func AddForeignKeyDDL

func AddForeignKeyDDL(d Dialect, t TableRef, name string, cols []string, refTable string, refCols []string, onUpdate, onDelete string) string

AddForeignKeyDDL builds the "ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY …" statement shared verbatim by the MySQL and PostgreSQL SchemaEditors. The reference target carries both Database and Schema from t; each dialect's QualifyTable selects the level it qualifies by (MySQL: Database, PostgreSQL: Schema) and ignores the other, so v1's same-database / same-schema target assumption holds for both engines without an engine branch here.

func AssembleCreateTable

func AssembleCreateTable(qualified string, colDefs, quotedPK []string) string

AssembleCreateTable renders the CREATE TABLE statement every SchemaEditor shares: the pre-built column definitions plus, when quotedPK is non-empty, a PRIMARY KEY table constraint. qualified and quotedPK arrive already quoted (QualifyTable / QuoteEach); what genuinely diverges per engine — the columnDef grammar and PostgreSQL's COMMENT ON COLUMN trailer — stays with the dialects.

func BaseTypeName

func BaseTypeName(typ string) string

BaseTypeName extracts the lower-cased bare type name from a SQL column type ("DECIMAL(10,2)" → "decimal"). Shared by the dialects' column builders and introspection.

func BasicColumnDef

func BasicColumnDef(d Dialect, c ColumnSpec) string

BasicColumnDef renders the simple column definition shared by the PostgreSQL and SQLite DDL builders: quoted name, type, NOT NULL, DEFAULT. (MySQL's columnDef carries extra attributes — unsigned/zerofill/charset/ auto-increment — and stays engine-local.) ColumnSpec.Default is already a validated SQL fragment (see the handlers' buildDefault), never raw input.

func CheckColumnScope

func CheckColumnScope(g GrantSpec, privs []string, grant bool, columnPrivs []string) error

CheckColumnScope re-checks a column-scoped spec inside a grant builder. The grammar rule — a column list needs a table, since the database-scope object takes none — applies to GRANT and REVOKE alike; the per-privilege rule runs on grant only, mirroring the allowlist asymmetry the builders already have (a revoke is authorized by the introspected grant, not by a curated list). columnPrivs is the engine's own column-grantable allowlist — the one per-engine fact in an otherwise identical check, and an intentional divergence, not a bug in flight.

func ChooseFrameDelimiter

func ChooseFrameDelimiter(body string) string

ChooseFrameDelimiter picks a DELIMITER token for an opaque frame around body: one that occurs nowhere in the body, so neither TableX's exact-line terminator match nor an external client's substring scan can fire early. The terminator is always emitted on its own line, so a body merely *ending* in a token prefix (e.g. '$' before a '$$' token) cannot splice into it.

func DefaultLimitClause

func DefaultLimitClause(limit int, offset int64) string

DefaultLimitClause renders the "LIMIT n OFFSET n" clause every supported engine accepts; the dialects delegate here rather than each formatting it. The offset is int64 (see Dialect.LimitClause).

func DropColumnDDL

func DropColumnDDL(d Dialect, t TableRef, col string) string

DropColumnDDL builds the "ALTER TABLE … DROP COLUMN …" statement shared verbatim by all three SchemaEditors (unlike columnDef / AddIndexSQL, which genuinely diverge per engine). Each dialect's QualifyTable selects the level it qualifies by.

func FloorWarning

func FloorWarning(d Dialect, info ServerInfo) string

FloorWarning returns a human-readable advisory when the connected server is older than the dialect's documented floor, or "" when it is new enough, the version could not be parsed, or the dialect declares no floor.

func FormatCollationMarker

func FormatCollationMarker(kind, name, collation string) string

FormatCollationMarker renders the disclosure marker line for one object. The caller guarantees kind is a collationMarkerKinds member.

func FormatFrameMarker

func FormatFrameMarker(delim string) string

FormatFrameMarker renders the opaque-frame marker line for a frame using delim (chosen via ChooseFrameDelimiter, so it is always grammar-valid).

func GroupableDropClass

func GroupableDropClass(class string) bool

GroupableDropClass reports whether class may be rendered as a multi-object DROP (see groupableDropClasses).

func HasSelfEdge

func HasSelfEdge(name string, deps map[string][]string) bool

HasSelfEdge reports whether deps[name] contains name itself — the one cycle shape SCC's single-member components cannot distinguish from an acyclic node.

func HasUnsafeIdentifierRune

func HasUnsafeIdentifierRune(s string) bool

HasUnsafeIdentifierRune reports whether s contains a rune no identifier-shaped input may carry: control characters INCLUDING DEL (0x7f), quote characters, backtick, semicolon, or backslash. It is the single reject set behind ValidNewIdentifier, the handlers' account-name validator and safeSortColumn.

Unifying on one set was a deliberate hardening, not behavior-neutral: sort columns previously accepted DEL and now reject it (a column literally named with a 0x7f byte loses sortability — an acceptable trade for one shared, strictest-common reject set).

func HumanBytesIEC

func HumanBytesIEC(n int64) string

HumanBytesIEC formats a non-negative byte count using IEC units (B, KiB, MiB, …). It is the shared core; internal/view's HumanBytes wraps it with an em-dash guard for the negative "unknown" sentinel.

func IndexKeyParts

func IndexKeyParts(d Dialect, cols []IndexColumn) string

IndexKeyParts renders an index's key list — "`a`(10), `b` DESC" — shared by the three SchemaEditors because the syntax genuinely coincides where the options exist at all.

It drops any part the dialect's IndexOptions does not claim, so this is the single place enforcing that promise rather than three. The handler refuses an unsupported option first, with a message; this is the backstop that keeps a caller who skipped that check from emitting SQL the engine cannot parse — or, worse, SQL it parses and ignores.

func MemoizedDump

func MemoizedDump(ctx context.Context, key string, load func() (any, error)) (any, error)

MemoizedDump returns the value for key, calling load on a miss. Errors are cached too: a failed catalog read would fail the same way for every table, so re-running it per table only multiplies the latency before the same failure.

key must identify everything load depends on — the schema at minimum.

func NextvalTextRefs

func NextvalTextRefs(expr string) (refs []string, dynamic bool)

NextvalTextRefs scans one deparsed default expression for LATE-BOUND sequence references — deparsed as nextval(('literal'::text)::regclass) — returning the literal contents, plus dynamic=true when any nextval argument is not a plain (possibly parenthesized) quoted literal: an expression or concatenation cannot be resolved or rewritten and the caller warns. Early-bound '…'::regclass arguments are ignored here — pg_depend already records those edges.

func NonFiniteFloat

func NonFiniteFloat(s string) string

NonFiniteFloat classifies a numeric column's textual value as a non-finite float token — "nan", "+inf" or "-inf" (as strconv.FormatFloat emits them, plus PostgreSQL's Infinity spellings) — or "" for an ordinary finite value. A non-finite value rendered bare is not a valid unquoted SQL numeric literal and would fail on restore, so each dialect's ValueLiteral maps these to an engine-correct literal (or NULL when the engine cannot store them).

func NormalizePrivileges

func NormalizePrivileges(privs []string, check func(string) bool) ([]string, error)

NormalizePrivileges trims and upper-cases submitted privilege keywords and re-validates each with check before a dialect emits them verbatim. Shared by the GrantSQL builders (check = allowlist membership) and RevokeSQL builders (check = ValidPrivilegeKeyword).

func ParseFrameMarker

func ParseFrameMarker(line string) (delim string, ok bool)

ParseFrameMarker validates line against the full frame marker grammar and returns the frame's delimiter token. ok is false for anything else.

func ParseQualifiedSeqLiteral

func ParseQualifiedSeqLiteral(content string) (schema, name string, ok bool)

ParseQualifiedSeqLiteral parses a late-bound literal's content into (schema, name). ok is false for an unqualified or malformed reference — binding one against an unknown restore-time search_path would be a guess.

func PrivilegeList

func PrivilegeList(privs, cols []string, quote func(string) string) string

PrivilegeList renders the privilege list of a GRANT or REVOKE. With no columns it is the bare comma-joined keywords; with columns each keyword carries the SAME parenthesised column list — "SELECT (a, b), UPDATE (a, b)" — which is the MySQL and PostgreSQL grammar both engines share, so the two dialects need not each re-derive it.

privs must already have passed NormalizePrivileges and cols must already have been matched against introspection; quote is the dialect's QuoteIdent. cols is emitted in the caller's order, which is the introspected column order, so two identical grants produce byte-identical SQL.

func QuoteAnsiIdent

func QuoteAnsiIdent(name string) string

QuoteAnsiIdent quotes an identifier with the ANSI double-quote convention ("name", embedded quotes doubled), shared byte-for-byte by the PostgreSQL and SQLite dialects. QuoteAnsiString quotes a string literal ('lit', embedded single quotes doubled) the same way. Keeping one implementation guarantees the two engines cannot drift.

func QuoteAnsiString

func QuoteAnsiString(s string) string

QuoteAnsiString quotes a string literal with the ANSI single-quote convention.

func QuoteEach

func QuoteEach(d Dialect, names []string) []string

QuoteEach returns names each quoted via the dialect's QuoteIdent, in order. Shared by the dialect DDL builders so the per-name quoting loop isn't reimplemented per engine.

func Register

func Register(d Dialect)

Register adds a dialect to the global registry. Engine packages call this from their init(); cmd/tablex blank-imports them so registration happens at startup. Registering the same name twice panics (a programming error).

func RegisteredNames

func RegisteredNames() []string

RegisteredNames returns every registered dialect's name, sorted. It exists so callers that only need to validate or display the engine list (config validation, error messages) do not have to hold Dialect values — and, more importantly, so no second copy of the list has to be maintained by hand.

The registry is populated by each engine package's init() via the blank imports in package main, so a build that omits a driver reports an engine list that matches what the binary can actually open.

func RenameColumnDDL

func RenameColumnDDL(d Dialect, t TableRef, old, newName string) string

RenameColumnDDL builds the "ALTER TABLE … RENAME COLUMN old TO new" statement, which all three engines spell identically (MySQL since 8.0.3, MariaDB since 10.5.2, PostgreSQL always, SQLite since 3.25). A dialect whose server may predate that support decides so in Capabilities, not here.

func RenderValueLiteral

func RenderValueLiteral(quote func(string) string, hooks ValueLiteralHooks, col ResultColumn, v Value) string

RenderValueLiteral is the shared dump-literal scaffold behind every ValueLiteral implementation: NULL, binary, the optional text pre-pass, the numeric passthrough with non-finite handling, and the quoted-text fallback.

func RewriteSequenceRefs

func RewriteSequenceRefs(ddl string, rewrites map[string]string) string

RewriteSequenceRefs replaces the content of every single-quoted literal in ddl that is cast to ::regclass or ::text AND parses as a schema-qualified identifier matching a rewrites key (SeqRefKey-shaped) with the mapped replacement (also SeqRefKey-shaped), rendered with PostgreSQL identifier quoting. Unqualified literals never match — a rewrite must never guess a search_path. A nil/empty map returns ddl unchanged.

func RoutineKeyword

func RoutineKeyword(r model.Routine) string

RoutineKeyword picks FUNCTION or PROCEDURE for a routine-scope grant statement. Both engines need the kind spelled out, each for its own reason: MySQL's grammar has no ON ROUTINE at all, so the wrong keyword targets an object that does not exist rather than failing to parse; PostgreSQL has ON ROUTINE from PG 11, but naming the kind keeps a mismatched Routine.Type an error here rather than a grant on whatever object shares the name.

func SCC

func SCC(names []string, deps map[string][]string) [][]string

SCC returns the strongly-connected components of the (names, deps) graph — Tarjan's algorithm, iterative-free depth-first (the graphs are small catalog object sets). deps[v] lists the nodes v depends on; edges to names outside the set are ignored (boundary edges). Components are returned in reverse topological order of the condensation (a component precedes the components that depend on it), each with its members in visit order. A single node is its own component; it is a GENUINE cycle only when len > 1 or it carries a self-edge — callers use SCC to find the cycles TopoOrder merely tolerates.

func SeqRefKey

func SeqRefKey(schema, name string) string

SeqRefKey builds the canonical lookup key for a schema-qualified sequence reference. NUL-separated so a dotted relation name cannot collide with a schema qualification.

func ServerFlavorFromContext

func ServerFlavorFromContext(ctx context.Context) string

ServerFlavorFromContext returns the flavor stashed by WithServerFlavor, or "".

func SplitAccount

func SplitAccount(s string) (user, host string)

SplitAccount parses a "user@host" (or "'user'@'host'") account string on the LAST "@": the host is the substring after the final @, since user names may themselves contain @. Each part is trimmed of surrounding ' ` " quote characters (strings.Trim strips any run of them, not just one). This is the handler-facing account decoder, shared so handlers need not import a concrete dialect. It decodes the account <select> value (built from an already-decoded model.User Name@Host) and the server-reported CURRENT_USER() string — NEITHER of which uses information_schema's doubled-quote escaping. It therefore deliberately does NOT collapse doubled quotes; the mysql dialect's internal splitGrantee is the companion variant that DOES, because it decodes raw information_schema.*_PRIVILEGES grantees.

func StreamResult

func StreamResult(rows *sql.Rows, perRow func(cols []ResultColumn, row []Value) error) error

StreamResult iterates rows one at a time, calling perRow for each, without buffering the whole result set — used by streaming export. It closes rows.

func StringSet

func StringSet(names []string) map[string]bool

StringSet builds a membership set from a name slice — the "which tables are in scope" lookup every dialect's DumpObjects performs. Exported because the dialects live in separate packages.

func TopoOrder

func TopoOrder(names []string, deps map[string][]string) []string

TopoOrder orders names so every dependency precedes its dependents: deps[v] lists the names v depends on. The sort is stable (ties keep the input order) and cycle-tolerant — a cycle's members are still all emitted (never dropped), in DFS-completion order rather than input order: for a 2-cycle A→B→A with input [A,B] the output is [B,A] (visiting A recurses into B, which completes first). Dialects use it to dependency-order view DDL, where a mutually-recursive cycle cannot be perfectly ordered but every member must still appear. The self-edge guard (dep != n) means a single node with a self-dependency is NOT reordered, so callers needing to detect a self-cycle must inspect the edges directly.

func UnbracketHost

func UnbracketHost(host string) string

UnbracketHost returns a bracketed IP literal ("[::1]", "[fe80::1%eth0]") in bare form; any other host is returned unchanged. The network dialects call this before net.JoinHostPort, which brackets ANY host containing ':' — an already-bracketed input would otherwise come out double-bracketed ("[[::1]]:5432") and unresolvable. Boundary canonicalization (config.CanonicalHost) normally strips brackets before a host reaches a dialect; this is defense in depth for direct driver callers.

func ValidNewIdentifier

func ValidNewIdentifier(caps Capabilities, s string) bool

ValidNewIdentifier reports whether s is acceptable as the name of an object being CREATED or RENAMED-to (table, database, column, index, constraint). It is deliberately permissive: names with spaces, hyphens, dots, leading digits or non-Latin scripts are legal in every supported engine and already browsable/editable in TableX — refusing to create them was an asymmetry, not a protection, since QuoteIdent makes them safe. It still rejects what could only be an injection attempt or an unusable name: empty/blank or space-padded names, control bytes, quote characters, backslashes, semicolons, and a length beyond the engine's byte limit (caps.IdentifierMaxBytes). The length rule is dialect-aware because PostgreSQL silently truncates a >63-byte identifier — validate-first prevents creating the object under a truncated name — while MySQL/SQLite would raise a clean error. Callers must still quote via QuoteIdent.

Do NOT use safeSortColumn for this: its contract assumes the name was already matched against live introspection, which a new name never is.

func ValidPrivilegeKeyword

func ValidPrivilegeKeyword(s string) bool

ValidPrivilegeKeyword reports whether s has the shape of a SQL privilege keyword: uppercase A–Z words separated by single spaces, at most 32 bytes ("SELECT", "ALL PRIVILEGES", "CREATE TEMPORARY TABLES"). It is the defence-in-depth gate on the introspection-driven revoke path (and on MySQL *_PRIVILEGES strings) — membership in the displayed grants or the grant allowlist is the primary check; this guarantees that whatever passed it can only ever be a keyword, never an injection fragment.

func ValidateCreateTable

func ValidateCreateTable(cols []ColumnSpec, pk []string) error

ValidateCreateTable applies the CreateTableSQL contract checks shared by all engines: at least one column, no duplicate column names, and every PRIMARY KEY entry present among the columns.

func WithDumpMemo

func WithDumpMemo(ctx context.Context) context.Context

WithDumpMemo attaches a fresh memo to ctx. Call it once per dump, before the per-table builders run. The memo is scoped to that context and dies with it, so nothing is cached across requests and a catalog change between dumps cannot be served stale.

func WithReadTimeout

func WithReadTimeout(ctx context.Context) (context.Context, context.CancelFunc)

WithReadTimeout derives a context bounded by the DEFAULT ReadStmtTimeout, for callers with no Connection in hand. Prefer (*Connection).WithReadTimeout, which honours the operator's configured budget.

func WithServerFlavor

func WithServerFlavor(ctx context.Context, flavor string) context.Context

WithServerFlavor carries the connection's already-detected engine flavor (e.g. "MariaDB" vs "MySQL", parsed once by ServerInfo at connect) into a stateless Dialect introspection call, so the dialect need not re-query VERSION() on every call. ServerFlavorFromContext reads it back (empty when unset — e.g. a direct dialect call in a test, where the dialect falls back to its own probe).

func XHexLiteral

func XHexLiteral(b []byte) string

XHexLiteral spells binary content as the X'…' hex literal MySQL, SQLite and the non-Dumper fallback share (PostgreSQL spells '\x…').

Types

type BulkIntrospector

type BulkIntrospector interface {
	BulkColumns(ctx context.Context, db *sql.DB, scope Scope) (map[string][]model.Column, error)
	BulkForeignKeys(ctx context.Context, db *sql.DB, scope Scope) (map[string][]model.ForeignKey, error)
}

BulkIntrospector is an optional Dialect capability: one catalog query returns the columns (and one the foreign keys) of EVERY table in a scope, replacing a per-table N+1 on schema-wide pages (designer) and export preflight. Engines whose catalog is inherently per-table (SQLite PRAGMA) omit it, and callers fall back to the per-table methods. Implementations MUST be built on the same query as the per-table method (a shared scan with an optional table filter) so the two paths cannot drift.

type Capabilities

type Capabilities struct {
	HasSchemas bool // PostgreSQL: true; MySQL/SQLite: false
	HasUsers   bool // privilege management
	// HasForeignKeys / HasStoredRoutines / HasTriggers / HasEvents / HasViews:
	// the engine has the object kind at all. The four with a Connection listing
	// gate it (ListViews / ListRoutines / ListTriggers / ListEvents return an
	// empty slice without a round-trip when false), so a dialect that lacks the
	// kind does not have to implement a stub that queries for nothing.
	HasForeignKeys           bool
	HasStoredRoutines        bool
	HasTriggers              bool
	HasEvents                bool // MySQL/MariaDB scheduled events
	HasViews                 bool
	SupportsExplain          bool
	SupportsTransactionalDDL bool // PG: true; MySQL: false; SQLite: partial
	SupportsCharset          bool // CREATE DATABASE ... CHARACTER SET (MySQL)
	SupportsColumnModify     bool // ALTER COLUMN type/null/default (MySQL/PG; not SQLite)
	SupportsColumnRename     bool // ALTER TABLE ... RENAME COLUMN (all three; MariaDB needs 10.5.2+)
	SupportsColumnPosition   bool // FIRST / AFTER col placement on ADD and MODIFY (MySQL only)
	SupportsForeignKeyDDL    bool // ALTER TABLE add/drop FK (MySQL/PG; not SQLite)
	CanManageDatabases       bool // CREATE/DROP DATABASE (MySQL/PG; not SQLite — one file)
	CanDropConnectedDatabase bool // DROP DATABASE works from a connection bound to that database (MySQL; PG needs a maintenance connection)
	ExecReportsChangedRows   bool // Exec's RowsAffected counts CHANGED rows, not matched (MySQL default protocol; PG/SQLite count matched)
	SupportsTruncate         bool // TRUNCATE TABLE (MySQL/PG; SQLite uses DELETE FROM)
	AccountHasHost           bool // account names carry a host part, 'user'@'host' (MySQL)
	SupportsRoleAttributes   bool // LOGIN/SUPERUSER/CREATEDB/CREATEROLE role attributes (PG)
	// SupportsRoles: role membership (GRANT role TO account) is available, so
	// the Users page renders its memberships section. Version-gated on
	// MySQL/MariaDB and failing CLOSED on an unknown version — the catalog
	// table is absent before MySQL 8.0 / MariaDB 10.0.5, so a wrong "yes" turns
	// the page into an error rather than hiding a section. Promises RoleManager.
	SupportsRoles        bool
	RestrictedDropColumn bool // dropping a PK/indexed/outgoing-FK column is refused (SQLite)
	// IsNetworkEngine: the connection target is a network address (host/port)
	// authenticated with credentials, rather than a local file the process
	// opens. It is the login path's single engine discriminator and carries
	// three consequences:
	//
	//   - Ad-hoc login is offered ONLY for network engines. A file-backed
	//     engine has no credentials, so letting a visitor type its connection
	//     target would be an unauthenticated arbitrary file open (and, for
	//     SQLite historically, create) on the host. File-backed engines are
	//     reachable only through an operator-configured predefined server.
	//   - Credentials are collected at login only for network engines.
	//   - The SSRF host policy (auth.CheckHost / DialControl) applies only to
	//     network engines — there is no host to resolve otherwise.
	//
	// A future engine that is network-attached but should still be barred from
	// ad-hoc login needs its own flag; today the two coincide exactly.
	IsNetworkEngine bool
	// ShowsSSLModeUI: the engine's AD-HOC LOGIN FORM exposes the sslmode
	// selector (PG). Deliberately not "UsesSSLMode": MySQL also consumes and
	// validates ConnParams.SSLMode from predefined-server config — the honest
	// distinction is which login form shows the selector.
	ShowsSSLModeUI bool
	// DatabasesShareConnection: every logical database is served by the ONE
	// server connection (SQLite: one file; ATTACH-ed names are session-scoped),
	// so ConnFor never opens per-database pools.
	DatabasesShareConnection bool

	// IdentifierMaxBytes bounds a new/renamed object name in bytes (0 = no hard
	// limit). PostgreSQL's 63 (NAMEDATALEN-1) is load-bearing: it silently
	// truncates a longer identifier, creating the object under an unexpected
	// name, so validate-first is a correctness requirement, not just politeness.
	// MySQL's 64 and SQLite's absence surface over-long names as clean errors.
	IdentifierMaxBytes int
}

Capabilities advertises which features an engine supports. The UI reads these and never assumes a feature exists, so tabs/links appear only where valid.

type Collation

type Collation struct {
	Name    string
	Charset string
	Default bool // the charset's default collation
}

Collation is one server collation and the character set it belongs to, for the create-database form (offer) and its validation (allowlist).

type CollationLister

type CollationLister interface {
	ListCollations(ctx context.Context, db *sql.DB) ([]Collation, error)
}

CollationLister is an optional Dialect capability: engines whose CREATE DATABASE accepts a collation (Capabilities.SupportsCharset) implement it so the create-database form can offer — and the handler validate against — the server's real collation list (information_schema.COLLATIONS).

type CollationProber

type CollationProber interface {
	CollationProbeSQL() string
}

CollationProber is an optional Dialect capability backing the SQL import's db-collation marker verification: the single-row, two-column statement reporting the session's current database and that database's default collation. Engines whose dumps carry no collation markers omit it and the importer ignores marker events.

type ColumnModifier

type ColumnModifier interface {
	ModifyColumnSQL(t TableRef, old string, c ColumnSpec) ([]string, error)
}

ColumnModifier is the SchemaEditor half that changes an EXISTING column in place. It is separate because SQLite has no such statement at all — ALTER TABLE there can add, drop and rename a column but never redefine one — and folding it into SchemaEditor forced SQLite to carry a method that only ever returned an error, duplicating what Capabilities.SupportsColumnModify already says. An engine that sets that flag MUST implement this.

A PostgreSQL "modify" expands to several ordered ALTER COLUMN steps; other engines restate the whole column in one statement, which is why the caller fills the spec with the attributes the form cannot express first (see ColumnSpec).

type ColumnPlacement

type ColumnPlacement int

ColumnPlacement says where a column sits in the table's column order. Only MySQL/MariaDB can express this (FIRST / AFTER col); PostgreSQL and SQLite have no reordering statement, so they report SupportsColumnPosition false and ignore it. The zero value keeps whatever the engine would do on its own: append for ADD COLUMN, leave the column where it is for a MODIFY.

const (
	PlaceDefault ColumnPlacement = iota
	PlaceFirst
	PlaceAfter
)

type ColumnPrivileger

type ColumnPrivileger interface {
	PrivilegeManager
	// ColumnGrantablePrivileges is the subset of the table-scope allowlist that
	// accepts a column list — SQL restricts it to the privileges that read or
	// write column values (SELECT/INSERT/UPDATE/REFERENCES), and "GRANT DELETE
	// (col)" is a syntax error rather than a narrower DELETE. It is the single
	// source of truth for the form's column control and for grant validation,
	// exactly as GrantablePrivileges is for the keyword set.
	ColumnGrantablePrivileges() []string
}

ColumnPrivileger is an optional capability extending PrivilegeManager to column-scope grants (GRANT SELECT (a, b) ON t TO u). It embeds PrivilegeManager because column scope is a refinement of table scope, never an alternative to it: the same GrantSQL/RevokeSQL builders emit both, keyed on GrantSpec.Columns.

The read side needs no separate interface — Privileger returns column grants as ordinary model.Privilege rows carrying a Column, so an engine's grant listing and its revoke path stay one code path. Both halves must ship together: a grant nobody can list is a grant nobody can revoke.

type ColumnRenamer

type ColumnRenamer interface {
	RenameColumnSQL(t TableRef, old, newName string) ([]string, error)
}

ColumnRenamer renames a column WITHOUT restating its definition. It is its own capability rather than a field on ColumnSpec because renaming and redefining do not travel together:

  • SQLite implements no ColumnModifier at all — it cannot redefine a column — yet it has had ALTER TABLE … RENAME COLUMN since 3.25. Folding rename into the modify path would lock out the one engine whose only column edit IS a rename.
  • MariaDB below 10.5.2 is the mirror image: it can redefine a column but has no RENAME COLUMN, which is why SupportsColumnRename is decided from the detected server version rather than inferred from SupportsColumnModify.

Keeping it separate also keeps a rename from being a redefinition by accident. MySQL's other spelling, CHANGE, restates the whole column, so a rename issued that way silently applies every attribute the form could not express; RENAME COLUMN touches the name and nothing else.

old is validated by the caller against introspection and newName via ValidNewIdentifier; implementations only quote and assemble. An engine that sets SupportsColumnRename MUST implement this.

type ColumnSpec

type ColumnSpec struct {
	Name     string
	Type     string
	Comment  string
	Nullable bool
	Default  *string
	// DefaultExpr marks Default as an expression carried verbatim from
	// introspection (CURRENT_TIMESTAMP, nextval(...), (uuid())), as opposed to a
	// literal the form produced. MySQL wraps non-temporal expression defaults in
	// the parentheses its grammar requires; without this flag an expression
	// default re-applied through the editor would be re-quoted into a string
	// literal ('CURRENT_TIMESTAMP').
	DefaultExpr bool

	// Attributes preserved across a whole-definition MODIFY (MySQL restates the
	// entire column, so anything omitted is dropped). The handler fills these
	// from the existing column before ModifyColumnSQL; the add path leaves them
	// zero. Engines that modify per-attribute (PostgreSQL) ignore them, except
	// Identity, which PostgreSQL needs to avoid DROP/SET DEFAULT on an identity
	// column (a hard error).
	AutoIncrement bool   // MySQL AUTO_INCREMENT
	Unsigned      bool   // MySQL UNSIGNED numeric attribute
	Zerofill      bool   // MySQL ZEROFILL numeric attribute
	OnUpdate      string // MySQL "ON UPDATE <expr>" (validated CURRENT_TIMESTAMP[(n)])
	Collation     string // column collation to preserve (bare identifier)
	Charset       string // MySQL column character set to preserve (bare identifier)
	Identity      string // PostgreSQL identity mode to preserve: "" / "always" / "default"

	// Placement moves the column within the table's column ORDER. Only engines
	// that set Capabilities.SupportsColumnPosition act on it; the others MUST
	// ignore it rather than approximate it — PostgreSQL and SQLite have no way
	// to reorder columns at all, and the only faithful alternative (rebuild the
	// table, copy the data, swap it in) is not something a column edit should
	// do behind the user's back.
	Placement ColumnPlacement
	// PlacementAfter names the EXISTING column that Placement == PlaceAfter
	// refers to. The caller validates it against introspection first, and must
	// reject the column being modified naming itself.
	PlacementAfter string

	// SameBaseType is set on a MODIFY when the new base type matches the existing
	// one (only length/precision/attributes changed). PostgreSQL uses it to omit
	// the "USING col::type" cast: a same-base change needs no cast and, on a
	// shrink that would lose data, errors cleanly instead of silently truncating.
	// Other engines ignore it; the add path leaves it false.
	SameBaseType bool
}

ColumnSpec describes a column to add or modify. Default is nil for "no default"; a non-nil *Default is a pre-validated literal or expression emitted verbatim (the handler validates it and, for string types, quotes it via QuoteString before populating this). Type is the assembled, already-validated engine type (an allowlisted base type plus an optional length/precision).

type ConnParams

type ConnParams struct {
	Host     string
	Socket   string
	Port     int
	User     string
	Password string
	Database string            // optional for MySQL; required for PostgreSQL
	FilePath string            // SQLite database file path
	SSLMode  string            // engine-specific TLS preference (sslmode / tls)
	Params   map[string]string // extra driver params, merged last

	// DialControl, when non-nil, is installed as the net.Dialer.Control hook on
	// every TCP connection opened from these params. It re-validates the
	// *resolved peer IP* of each connection (the SSRF / DNS-rebinding backstop:
	// a rebinding DNS answer cannot slip past the pre-flight resolution because
	// the address actually dialed is re-checked). It is set only for ad-hoc
	// network logins; predefined servers are operator-trusted and carry no
	// dial-time restriction. SQLite ignores it (no network).
	DialControl func(network, address string, c syscall.RawConn) error

	// OnStatement, when non-nil, is notified after each statement this
	// connection runs — the audit trail's statement half. It rides ConnParams
	// rather than being set on a Connection so that every DERIVED dial inherits
	// it for free: the session stamps it once at login and the per-database
	// pools, pinned script connections and export connections all carry it.
	//
	// Which statements reach it is deliberately asymmetric, and the asymmetry is
	// the whole design: see StatementObserver.
	OnStatement StatementObserver

	// Tuning carries the operator's pool and statement-timeout settings down
	// every dial. It rides ConnParams rather than a package-level variable so
	// there is no shared mutable state and a test can dial with its own values:
	// the session's base params are stamped once at login, and every derived
	// dial (per-database pool, pinned script, export connection, maintenance
	// connection) copies them for free. A zero Tuning means "driver defaults".
	Tuning Tuning
}

ConnParams are the user-supplied connection inputs collected at login, translated by each Dialect into an engine-specific DSN.

type Connection

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

Connection is the engine-neutral handle handlers use. It wraps a *sql.DB pool plus its Dialect and cached ServerInfo, and exposes high-level operations that delegate the engine-specific bits to the Dialect. Handlers never touch a concrete driver package or build raw SQL for identifiers.

func Open

func Open(ctx context.Context, d Dialect, p ConnParams) (*Connection, error)

Open dials a database using the dialect and params, verifies connectivity, loads ServerInfo and returns a ready Connection. Pool limits come from p.Tuning (operator config), defaulted so a single browser cannot exhaust the server.

func (*Connection) AlterUser

func (c *Connection) AlterUser(ctx context.Context, u UserSpec) error

AlterUser sets an account's password and/or role attributes.

func (*Connection) AuditTeardown

func (c *Connection) AuditTeardown(ctx context.Context, planned []TeardownDrop) ([]string, error)

AuditTeardown returns the drop-first teardown advisories (see TeardownAuditor); no warnings for a dialect without the capability.

func (*Connection) Browse

func (c *Connection) Browse(ctx context.Context, t TableRef, page Pagination, sorts []Sort) (*ResultSet, error)

Browse runs a paginated, optionally sorted SELECT * over a table. Sort columns must already exist (validated by the caller against Columns); Browse re-checks their shape and quotes them. It never accepts a raw WHERE fragment — filtering is done through the parameterized Search path — so there is no string-concat injection surface here.

func (*Connection) BulkColumns

func (c *Connection) BulkColumns(ctx context.Context, scope Scope) (map[string][]model.Column, bool, error)

BulkColumns returns every table's columns in one query when the dialect supports it (ok=false otherwise — the caller falls back to per-table calls).

func (*Connection) BulkForeignKeys

func (c *Connection) BulkForeignKeys(ctx context.Context, scope Scope) (map[string][]model.ForeignKey, bool, error)

BulkForeignKeys returns every table's foreign keys in one query when the dialect supports it (ok=false otherwise).

func (*Connection) CanManageEvents

func (c *Connection) CanManageEvents() bool

CanManageEvents reports whether scheduled events can be administered here.

func (*Connection) CanManagePrivileges

func (c *Connection) CanManagePrivileges() bool

CanManagePrivileges reports whether the engine supports GRANT/REVOKE.

func (*Connection) CanManageRoles

func (c *Connection) CanManageRoles() bool

CanManageRoles reports whether role membership can be read and edited here.

func (*Connection) CanManageRoutines

func (c *Connection) CanManageRoutines() bool

CanManageRoutines reports whether stored routines can be administered here.

func (*Connection) CanManageTriggers

func (c *Connection) CanManageTriggers() bool

CanManageTriggers reports whether triggers can be administered here.

func (*Connection) CanManageUsers

func (c *Connection) CanManageUsers() bool

CanManageUsers reports whether the engine supports account administration.

func (*Connection) Capabilities

func (c *Connection) Capabilities() Capabilities

Capabilities returns the engine capability flags.

func (*Connection) Close

func (c *Connection) Close() error

Close shuts the pool down (called on logout / session expiry).

func (*Connection) ColumnGrantablePrivileges

func (c *Connection) ColumnGrantablePrivileges() []string

ColumnGrantablePrivileges returns the privileges that accept a column list on this engine, or nil when column-scope grants are unsupported. A nil result is the signal the UI uses to hide the column control entirely.

func (*Connection) Columns

func (c *Connection) Columns(ctx context.Context, t TableRef) ([]model.Column, error)

func (*Connection) CountRows

func (c *Connection) CountRows(ctx context.Context, t TableRef) (int64, error)

CountRows returns the exact row count for a table. Used to drive pagination totals.

func (*Connection) CountRowsBounded

func (c *Connection) CountRowsBounded(ctx context.Context, t TableRef, max int64) (n int64, exact bool, err error)

CountRowsBounded counts a relation's rows but stops looking once more than max exist, so a relation with no usable statistics estimate — a VIEW above all, whose count re-runs the whole underlying query — cannot cost an unbounded scan on every Browse and Structure render.

It reports exact=true with the true count when the relation holds at most max rows, and exact=false with max when it holds more ("at least this many"). The engine work is bounded either way: the derived table stops at max+1 rows. max <= 0 degrades to the plain unbounded CountRows.

This is phpMyAdmin's $cfg['MaxExactCountViews'] shape. The SQL — SELECT COUNT(*) FROM (SELECT 1 FROM rel LIMIT n) t — is accepted verbatim by every supported engine; only the LIMIT spelling varies, and that comes from the dialect. The derived table is aliased because MySQL requires it.

func (*Connection) CreateProgram

func (c *Connection) CreateProgram(ctx context.Context, create string) error

CreateProgram runs one stored-program CREATE statement. It is a plain Exec rather than a script: the statement is sent whole, so a body full of internal semicolons (MySQL's BEGIN…END) needs no DELIMITER — that is a client-side convention for text scripts, not a wire-protocol one.

func (*Connection) CreateSQL

func (c *Connection) CreateSQL(ctx context.Context, t TableRef) (string, error)

func (*Connection) CreateUser

func (c *Connection) CreateUser(ctx context.Context, u UserSpec) error

CreateUser creates a database account/role.

func (*Connection) DB

func (c *Connection) DB() *sql.DB

DB exposes the underlying pool for advanced operations (transactions, batch import). Most code should prefer the higher-level methods.

func (*Connection) DataSelectOnly

func (c *Connection) DataSelectOnly(ctx context.Context, scope Scope, tables []string) (map[string]bool, error)

DataSelectOnly returns the set of tables whose data SELECT must use FROM ONLY. A nil map (dialect without DataScoper, or MySQL/SQLite) means every table uses a plain FROM — today's behavior.

func (*Connection) Dialect

func (c *Connection) Dialect() Dialect

Dialect returns the engine dialect (for quoting in DDL builders, etc.).

func (*Connection) DropEvent

func (c *Connection) DropEvent(ctx context.Context, s Scope, e model.Event) error

DropEvent drops a scheduled event the caller has already resolved from a listing.

func (*Connection) DropRoutine

func (c *Connection) DropRoutine(ctx context.Context, s Scope, r model.Routine) error

DropRoutine drops a stored procedure or function the caller has already resolved from a listing.

func (*Connection) DropTrigger

func (c *Connection) DropTrigger(ctx context.Context, s Scope, t model.Trigger) error

DropTrigger drops a trigger the caller has already resolved from a listing.

func (*Connection) DropUser

func (c *Connection) DropUser(ctx context.Context, name, host string) error

DropUser removes a database account/role.

func (*Connection) DumpDataTables

func (c *Connection) DumpDataTables(ctx context.Context, scope Scope, tables []string) ([]string, error)

func (*Connection) DumpForeignTable

func (c *Connection) DumpForeignTable(ctx context.Context, scope Scope, name string) (DumpPlan, error)

DumpForeignTable builds a single foreign table's structure-only plan (see ForeignTableDumper).

func (*Connection) DumpGlobalObjects

func (c *Connection) DumpGlobalObjects(ctx context.Context, structure bool) (DumpPlan, error)

DumpGlobalObjects collects the database-global objects (see GlobalDumper). An empty plan for dialects without the capability.

func (*Connection) DumpInheritsChildCreate

func (c *Connection) DumpInheritsChildCreate(ctx context.Context, t TableRef, parents []string) (string, error)

DumpInheritsChildCreate emits a linked INHERITS child's CREATE (see Inheritor). It is only called for a dialect that returned parents from InheritanceParents, so the capability is always present here.

func (*Connection) DumpObjects

func (c *Connection) DumpObjects(ctx context.Context, scope Scope, tables []string, dbScope, structure bool) (DumpPlan, error)

func (*Connection) DumpTableCreate

func (c *Connection) DumpTableCreate(ctx context.Context, t TableRef) (string, error)

func (*Connection) DumpTableCreateStaged

func (c *Connection) DumpTableCreateStaged(ctx context.Context, t TableRef, parents []string, stripDefaults, stripConstraints []string) (string, []DumpScript, error)

DumpTableCreateStaged re-renders a table create with deferred clauses (see StagedTableDumper). ErrUnsupported for dialects without the capability — the resolver only calls it for dialects that emitted deferrable table edges.

func (*Connection) DumpView

func (c *Connection) DumpView(ctx context.Context, scope Scope, name string, withData bool) (plan DumpPlan, ok bool, err error)

DumpView dumps a single view/matview when the dialect supports it (ok true). ok is false for a dialect without ViewDumper, letting the caller fall back to the table path.

func (*Connection) Engine

func (c *Connection) Engine() string

Engine returns the dialect name ("mysql", "postgres", "sqlite"). MariaDB reports "mysql" — unlike Info().Engine, which carries the detected flavor.

func (*Connection) EstimateRows

func (c *Connection) EstimateRows(ctx context.Context, t TableRef) (int64, error)

EstimateRows returns the engine's statistics-based row estimate for a table, or -1 when no usable estimate exists (engine keeps none, relation never analyzed, view). Callers fall back to CountRows on -1.

func (*Connection) Exec

func (c *Connection) Exec(ctx context.Context, query string) (ExecResult, error)

Exec runs a non-row statement and reports affected rows.

func (*Connection) ExecScript

func (c *Connection) ExecScript(ctx context.Context, stmts []string, useTx bool) error

ExecScript runs a sequence of non-row statements. When useTx is set it wraps them in a single transaction (all-or-nothing, mirroring the importer's BeginTx pattern); otherwise it runs them sequentially, stopping at the first error. The structure editor relies on this because some builders emit several statements (e.g. a PostgreSQL column modify) that must run as one unit on engines with transactional DDL — Connection.Exec has no transaction path.

func (*Connection) Explain

func (c *Connection) Explain(ctx context.Context, query string, analyze bool) (*ResultSet, error)

Explain runs EXPLAIN (or EXPLAIN ANALYZE) for a query if the engine supports it, returning the plan as a result set.

func (*Connection) ExplainSQL

func (c *Connection) ExplainSQL(query string, analyze bool) (string, bool)

ExplainSQL returns the exact statement Explain would execute (e.g. SQLite's "EXPLAIN QUERY PLAN …"), so the console can label the result with what really ran rather than a generic "EXPLAIN …".

func (*Connection) FloorWarning

func (c *Connection) FloorWarning() string

FloorWarning returns the advisory for a server older than the documented engine floor, or "" when the server is new enough (or the engine declares no floor — SQLite is compiled in, so there is no server to be too old).

func (*Connection) ForeignKeys

func (c *Connection) ForeignKeys(ctx context.Context, t TableRef) ([]model.ForeignKey, error)

func (*Connection) Grant

func (c *Connection) Grant(ctx context.Context, g GrantSpec) error

Grant runs a GRANT built from g.

func (*Connection) GrantRole

func (c *Connection) GrantRole(ctx context.Context, g RoleGrant) error

GrantRole runs GRANT <role> TO <account>.

func (*Connection) GrantRoutine

func (c *Connection) GrantRoutine(ctx context.Context, g RoutineGrant) error

GrantRoutine runs a GRANT on a routine.

func (*Connection) GrantablePrivileges

func (c *Connection) GrantablePrivileges(table bool) []string

GrantablePrivileges returns the engine's curated privilege allowlist for the grant form and grant validation, or nil when unsupported.

func (*Connection) Indexes

func (c *Connection) Indexes(ctx context.Context, t TableRef) ([]model.Index, error)

func (*Connection) Info

func (c *Connection) Info() ServerInfo

Info returns the cached server info.

func (*Connection) InheritanceParents

func (c *Connection) InheritanceParents(ctx context.Context, scope Scope, tables []string) (map[string][]string, error)

InheritanceParents returns the same-schema ordinary-inheritance parent map (see Inheritor). A nil map — dialect without Inheritor — means no table links, so every table dumps standalone (today's behavior).

func (*Connection) IsForeignTable

func (c *Connection) IsForeignTable(ctx context.Context, scope Scope, name string) (bool, error)

IsForeignTable reports whether name is a foreign table (false for dialects without the capability).

func (*Connection) KillProcess

func (c *Connection) KillProcess(ctx context.Context, id int64) error

KillProcess terminates a server session. The caller must have matched id against a fresh process list first — the engine decides whether the account is ALLOWED to kill it, but TableX still refuses to name a session nobody reported.

func (*Connection) ListCollations

func (c *Connection) ListCollations(ctx context.Context) ([]Collation, error)

ListCollations returns the server's collation list for the create-database form and its validation, or nil for engines without collation support.

func (*Connection) ListDatabaseNames

func (c *Connection) ListDatabaseNames(ctx context.Context) ([]model.Database, error)

ListDatabaseNames is the cheap identity-only database listing (Name/IsSystem populated, no sizes or table counts) for navigation and existence checks. It falls back to the full listing when the dialect has no NameLister.

func (*Connection) ListDatabases

func (c *Connection) ListDatabases(ctx context.Context) ([]model.Database, error)

func (*Connection) ListEvents

func (c *Connection) ListEvents(ctx context.Context, scope Scope) ([]model.Event, error)

func (*Connection) ListRoutines

func (c *Connection) ListRoutines(ctx context.Context, scope Scope) ([]model.Routine, error)

func (*Connection) ListSchemas

func (c *Connection) ListSchemas(ctx context.Context, database string) ([]model.Schema, error)

func (*Connection) ListTableNames

func (c *Connection) ListTableNames(ctx context.Context, scope Scope) ([]model.Table, error)

ListTableNames is the cheap identity-only table listing (Name/Schema/Type populated, no statistics) for navigation and existence checks. It falls back to the full listing when the dialect has no NameLister.

func (*Connection) ListTables

func (c *Connection) ListTables(ctx context.Context, scope Scope) ([]model.Table, error)

func (*Connection) ListTriggers

func (c *Connection) ListTriggers(ctx context.Context, scope Scope) ([]model.Trigger, error)

func (*Connection) ListUsers

func (c *Connection) ListUsers(ctx context.Context) ([]model.User, error)

func (*Connection) ListViews

func (c *Connection) ListViews(ctx context.Context, scope Scope) ([]model.View, error)

ListViews is capability-gated HERE rather than in each dialect, as are the three extended listings after it. The Dialect contract already says an engine without the feature must return an empty slice; enforcing it centrally means a new dialect cannot forget, an engine that lacks the object kind pays no round-trip for it, and the Capabilities flags are load-bearing rather than decorative.

func (*Connection) ObjectDefinition

func (c *Connection) ObjectDefinition(ctx context.Context, scope Scope, kind ProgramKind, name string) (def string, ok bool, err error)

ObjectDefinition returns the full CREATE statement for one already-listed object. ok is false when the engine has no DefinitionViewer, which is not an error: it means the listing's own Definition is already the complete statement and the caller should use that instead of paying a second query.

func (*Connection) Ping

func (c *Connection) Ping(ctx context.Context) error

Ping verifies the pool is still alive.

func (*Connection) Privileges

func (c *Connection) Privileges(ctx context.Context, ref TableRef) ([]model.Privilege, error)

Privileges lists grants for a database (ref.Table == "") or a table. Returns nil for engines without a privilege system (SQLite).

func (*Connection) ProcessIDColumn

func (c *Connection) ProcessIDColumn() string

ProcessIDColumn returns the Processes() column holding the killable session identifier, or "" when the engine cannot terminate sessions.

func (*Connection) QualifiedName

func (c *Connection) QualifiedName(t TableRef) string

QualifiedName exposes the engine-correct quoted table reference for handlers that build display SQL or pass-through queries.

func (*Connection) Query

func (c *Connection) Query(ctx context.Context, query string, limit int) (*ResultSet, error)

Query runs a row-returning statement and scans up to limit rows. Used by the SQL console (the user's own SQL under their own credentials) and by Browse.

func (*Connection) ReplaceProgram

func (c *Connection) ReplaceProgram(ctx context.Context, drop []string, create, restore string) error

ReplaceProgram redefines an existing stored program: drop, then create.

On an engine with transactional DDL the pair runs in one transaction, so a rejected CREATE leaves the original untouched. MySQL has no such thing — every DDL statement commits itself — so a CREATE that fails after the DROP succeeded would destroy the routine outright. There, the object's previous definition is replayed to put it back, and the caller is told whether that worked: losing a stored procedure to a typo is not an acceptable outcome of pressing Save.

restore is the object's current full CREATE statement (what the editor was pre-filled with).

func (*Connection) Revoke

func (c *Connection) Revoke(ctx context.Context, g GrantSpec) error

Revoke runs a REVOKE built from g.

func (*Connection) RevokeRole

func (c *Connection) RevokeRole(ctx context.Context, g RoleGrant) error

RevokeRole runs REVOKE <role> FROM <account>.

func (*Connection) RevokeRoutine

func (c *Connection) RevokeRoutine(ctx context.Context, g RoutineGrant) error

RevokeRoutine runs a REVOKE on a routine.

func (*Connection) RoleMemberships

func (c *Connection) RoleMemberships(ctx context.Context) ([]model.RoleMembership, error)

RoleMemberships lists the role→account edges, or nil when unsupported (not an error: the section simply does not render).

func (*Connection) RoutineGrantablePrivileges

func (c *Connection) RoutineGrantablePrivileges() []string

RoutineGrantablePrivileges returns the routine-scope allowlist, or nil when the engine has no routine grants — the signal the UI uses to hide the tab.

func (*Connection) RoutinePrivileges

func (c *Connection) RoutinePrivileges(ctx context.Context, s Scope, r model.Routine) ([]model.Privilege, error)

RoutinePrivileges lists the grants on one routine, or nil when unsupported.

func (*Connection) RunTableMaintenance

func (c *Connection) RunTableMaintenance(ctx context.Context, t TableRef, op string) (*ResultSet, error)

RunTableMaintenance runs one maintenance command and returns whatever the engine reported. op must be one of TableMaintenanceOps' names — the dialect rejects anything else, so an op cannot be smuggled in through the form.

It runs as a query, not an exec: MySQL's OPTIMIZE/CHECK/REPAIR/ANALYZE all answer with a status table that is the entire point of running them, while PostgreSQL's VACUUM and REINDEX simply return no rows. No read timeout is applied — VACUUM FULL on a large table legitimately takes minutes, and the request context already bounds it.

func (*Connection) SearchExpr

func (c *Connection) SearchExpr(col string) string

SearchExpr returns the expression used to LIKE-match a column (already validated against introspection by the caller): the quoted identifier, text-cast where the engine requires it for LIKE (PostgreSQL).

func (*Connection) SetStatementObserver

func (c *Connection) SetStatementObserver(fn StatementObserver)

SetStatementObserver installs the audit observer on an already-open connection.

It exists for one case: the LOGIN pool is dialled before the identity to record is known — it is the connection that reveals it — so its observer cannot ride the ConnParams the way every derived dial's does. Call it before the Connection is shared with anything; there is no locking, because at that point there is nothing to race.

func (*Connection) Stream

func (c *Connection) Stream(ctx context.Context, query string, perRow func([]ResultColumn, []Value) error) error

Stream runs a query and delivers rows one at a time to perRow without buffering — used by streaming export of large tables.

func (*Connection) StreamArgs

func (c *Connection) StreamArgs(ctx context.Context, query string, args []any, perRow func([]ResultColumn, []Value) error) error

StreamArgs is Stream with bound parameters, for an export restricted to specific rows: the row-identity values are BOUND, never concatenated, so a "with selected" export keeps the same injection-free guarantee as the edit and delete paths it shares its row keys with.

func (*Connection) TableMaintenanceOps

func (c *Connection) TableMaintenanceOps() []TableMaintenanceOp

TableMaintenanceOps lists the maintenance commands this engine offers, or nil when it offers none.

func (*Connection) Tuning

func (c *Connection) Tuning() Tuning

Tuning returns the resolved pool and timeout settings in force for this connection (no zero fields), for diagnostics and tests.

func (*Connection) ValueLiteral

func (c *Connection) ValueLiteral(col ResultColumn, v Value) string

ValueLiteral renders a cell through the dialect's Dumper. The fallback for a non-Dumper dialect covers the generic shapes (NULL, X'…' hex, bare numerics with the finite guard, quoted text) so the data phase still emits something restorable.

func (*Connection) WithReadTimeout

func (c *Connection) WithReadTimeout(ctx context.Context) (context.Context, context.CancelFunc)

WithReadTimeout derives a context bounded by this connection's read budget, for a generated read built OUTSIDE the Connection wrappers — the handlers (TableSearch, runQBE, fetchRow) that bind args through conn.DB().QueryContext and so cannot use Query (which takes no args). The caller must invoke the returned cancel once the rows are scanned.

type DDLErrorHint

type DDLErrorHint interface {
	DDLErrorHint(err error) (hint string, ok bool)
}

DDLErrorHint is an optional Dialect capability that turns a raw DDL error into a user-facing hint (e.g. PostgreSQL's "cannot alter type of a column used by a view"), so handlers surface guidance without sniffing engine-specific error text themselves. ok is false when the error has no engine-specific hint.

type DataScoper

type DataScoper interface {
	DataSelectOnly(ctx context.Context, db *sql.DB, scope Scope, tables []string) (map[string]bool, error)
}

DataScoper is an optional Dialect capability reporting, per table, whether its data SELECT must use `FROM ONLY` instead of `FROM`. PostgreSQL returns true for an ordinary relkind='r' table so an INHERITS parent scans only its OWN rows — its separately-dumped children then contribute their rows exactly once, instead of every child row being duplicated inside the parent (silent data corruption). A partitioned parent (relkind='p') returns false: its descendant rows ride the parent scan while the children are excluded from the data list.

type DatabaseManager

type DatabaseManager interface {
	// CreateDatabaseSQL creates the database. collation, when non-empty, is a
	// default collation for the new database (MySQL/MariaDB `COLLATE …`; the
	// charset is implied by the collation). It is emitted as a BARE identifier
	// (backtick-quoting is invalid in that position), so the handler MUST have
	// validated it against the server's introspected collation list
	// (CollationLister) — never pass raw user input. Engines whose Capabilities
	// lack SupportsCharset ignore it.
	CreateDatabaseSQL(name, collation string) string
	// DropDatabaseSQL drops the database. PostgreSQL emits WITH (FORCE) (13+) so
	// the drop terminates OTHER sessions still connected to it rather than
	// failing; it cannot drop the database its own session is connected to, so
	// the caller must run it on a connection bound elsewhere.
	DropDatabaseSQL(name string) string
}

DatabaseManager is an optional Dialect capability for server-level database administration (CREATE / DROP DATABASE). Engines whose Capabilities report CanManageDatabases implement it; SQLite (one file per connection) does not. The name is quoted with QuoteIdent inside the builder; the handler validates it (ValidNewIdentifier for create, introspection match for drop) first.

type DatabaseRebinder

type DatabaseRebinder interface {
	RebindDatabase(p ConnParams, database string) ConnParams
}

DatabaseRebinder is an optional Dialect capability for engines whose logical "database" does not map to a DSN parameter: SQLite's database is the file the connection opened (ATTACH names are session-scoped), so rebinding is the identity. Engines without the hook get the default p.Database = database rebind on the private-connection dial paths (PinnedFor / ExportConnFor / server-dump sections).

type DefinitionViewer

type DefinitionViewer interface {
	ObjectDefinition(ctx context.Context, db *sql.DB, scope Scope, kind ProgramKind, name string) (string, error)
}

DefinitionViewer is an optional Dialect capability returning the full, replayable CREATE statement for ONE named object.

It exists because model.Routine/Trigger/Event.Definition holds whatever the engine's catalog cheaply reports alongside the listing, and that is not the same artefact on every engine. PostgreSQL's pg_get_functiondef and pg_get_triggerdef, and SQLite's sqlite_master.sql, are already complete CREATE statements. MySQL's information_schema ROUTINE_DEFINITION/ACTION_STATEMENT is the BODY only — no signature, no parameter list, no DEFINER — which is why the dump path reads SHOW CREATE instead (see the mysql DumpObjects comment). MySQL therefore implements this; engines whose listed Definition is already complete omit it and Connection.ObjectDefinition reports "not supported" so the caller falls back to the listing, paying no second round-trip.

name must ALREADY have been validated against a listing from this same scope — implementations quote it, they do not check that it exists.

type Dialect

type Dialect interface {
	// Identity.
	Name() string        // canonical id: "mysql", "postgres", "sqlite"
	DisplayName() string // human label: "MySQL / MariaDB", "PostgreSQL", "SQLite"
	DefaultPort() int    // 0 for engines without a network port (SQLite)

	// Connection.
	SQLDriverName() string                 // database/sql driver name: "mysql", "pgx", "sqlite"
	BuildDSN(p ConnParams) (string, error) // engine-specific DSN/connection string

	// SQL syntax.
	QuoteIdent(name string) string // `name` / "name" with correct escaping
	QuoteString(s string) string   // 'literal' with correct escaping (for generated DDL/dumps)
	Placeholder(n int) string      // "?" (n ignored) / "$1"
	// LimitClause renders "LIMIT x OFFSET y" (or the engine equivalent). The
	// offset is int64 end-to-end: browse positions on >2^31-row tables must
	// not truncate through a 32-bit int.
	LimitClause(limit int, offset int64) string
	QualifyTable(t TableRef) string // engine-correct "[db|schema.]table", quoted
	// InsertDefaultRowSQL renders an INSERT that adds one all-defaults row, for a
	// table with NO insertable (non-generated) columns — a zero-column table, or
	// one whose every column is generated (both legal in PostgreSQL). Without it a
	// SQL dump silently loses every such row. PostgreSQL/SQLite use DEFAULT VALUES;
	// MySQL uses the empty () VALUES () form. qualified is already quoted.
	InsertDefaultRowSQL(qualified string) string

	// Connection-scoped helpers.
	ServerInfo(ctx context.Context, db *sql.DB) (ServerInfo, error)

	// Introspection — each takes ctx + *sql.DB and returns neutral model types.
	ListDatabases(ctx context.Context, db *sql.DB) ([]model.Database, error)
	ListSchemas(ctx context.Context, db *sql.DB, database string) ([]model.Schema, error)
	ListTables(ctx context.Context, db *sql.DB, scope Scope) ([]model.Table, error)
	Columns(ctx context.Context, db *sql.DB, t TableRef) ([]model.Column, error)
	Indexes(ctx context.Context, db *sql.DB, t TableRef) ([]model.Index, error)
	ForeignKeys(ctx context.Context, db *sql.DB, t TableRef) ([]model.ForeignKey, error)
	CreateSQL(ctx context.Context, db *sql.DB, t TableRef) (string, error)

	// Extended introspection. Implementations whose Capabilities report the
	// feature absent should return an empty slice (not an error).
	ListViews(ctx context.Context, db *sql.DB, scope Scope) ([]model.View, error)
	ListRoutines(ctx context.Context, db *sql.DB, scope Scope) ([]model.Routine, error)
	ListTriggers(ctx context.Context, db *sql.DB, scope Scope) ([]model.Trigger, error)
	ListEvents(ctx context.Context, db *sql.DB, scope Scope) ([]model.Event, error)
	ListUsers(ctx context.Context, db *sql.DB) ([]model.User, error)

	// ExplainSQL wraps a query for EXPLAIN; ok is false when unsupported.
	ExplainSQL(query string, analyze bool) (sql string, ok bool)

	Capabilities() Capabilities
}

Dialect captures everything that differs between database engines. A single value per engine is registered at init time; it is stateless and safe for concurrent use (all per-connection state lives in *sql.DB / Connection).

func All

func All() []Dialect

All returns every registered dialect, sorted by Name for stable UI ordering.

func Get

func Get(name string) (Dialect, bool)

Get returns the dialect registered under name.

func Specialize

func Specialize(d Dialect, info ServerInfo) Dialect

Specialize applies the dialect's optional ServerSpecializer hook, returning d unchanged when the engine has no per-connection state. It is the single specialization point shared by Open and OpenPinned so the pooled and pinned paths cannot drift apart.

type DropForm

type DropForm struct {
	// Class is the DROP command class whose grammar accepts a comma-separated
	// object list ("FUNCTION", "TYPE", …). "" — the zero value — means the
	// object's DROP has single-object syntax only (COLLATION, CAST, OPERATOR
	// CLASS, USER MAPPING) and can never be grouped: such a cycle is RETAINED
	// (drops omitted + warned) instead.
	Class string
	// Ref is this object's reference inside such a list: everything the command
	// needs after the class keyword and IF EXISTS.
	Ref string
	// RoutineRef, when non-empty, is this object's reference inside a grouped
	// DROP ROUTINE — the FLAT input signature (IN/INOUT/VARIADIC types only, no
	// ORDER BY split), which is the one spelling that matches functions,
	// procedures and aggregates alike, so an all-routine cycle of mixed kinds
	// drops in ONE statement. It is NOT interchangeable with Ref: an ordered-set
	// aggregate's identity arguments render an `ORDER BY` split that only DROP
	// AGGREGATE accepts.
	RoutineRef string
}

DropForm describes how one object's teardown DROP may be merged into a MULTI-OBJECT drop command. A dependency cycle on the RESTORED catalog (a mutually-recursive routine pair, a base type and its I/O functions) cannot be linearized into individual reverse-ordered DROPs — each one fails under RESTRICT while the other member still references it, and the importer stops at the first error. A single DROP command listing every member drops them as a group, so mutual dependencies AMONG them do not trigger RESTRICT (exactly like `DROP TABLE a, b` for circular foreign keys). CASCADE is never used: it would strip objects outside the export's knowledge.

type DumpClause

type DumpClause struct {
	Text     string       // appended to the script's SQL when the clause is kept
	Deps     []string     // node ids this clause (and only this clause) requires
	Finalize []DumpScript // the deferred DDL emitted instead when the clause is cut
	PreData  bool         // true: Finalize runs in the pre-data finalizer lane (domain defaults/constraints must precede consuming types/tables and data); false: post-data
}

DumpClause is one deferrable trailing clause of a DumpScript (see DumpScript.Clauses). PostgreSQL's clause grammar composes by appending, so keeping a clause is exactly `SQL += Text` in declaration order.

type DumpMarker

type DumpMarker struct {
	Kind      string // "routine", "trigger" or "event"
	Name      string // object name, decoded
	Collation string // the source database's collation as recorded at dump time
}

DumpMarker is one validated TableX db-collation disclosure marker.

func ParseCollationMarker

func ParseCollationMarker(line string) (DumpMarker, bool)

ParseCollationMarker validates line (without its trailing newline) against the full db-collation marker grammar and decodes it. ok is false for anything else — including marker-like lines with unknown types, malformed fields or trailing garbage, which stay ordinary comments.

type DumpMemo

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

DumpMemo caches a dump's schema-wide catalog reads so a dialect's per-table builders can share one query instead of repeating it per table.

The problem it solves is structural: the dump planner calls DumpTableCreate once per table, and a dialect's builder may need facts (PostgreSQL identity options, for one) that are cheapest to read for the whole schema at once. The builder cannot see the table list, and the planner cannot see what the builder needs — so the memo rides the context between them, next to WithServerFlavor.

The contract is deliberately weak: a MISS simply computes the value. With no memo attached — every caller outside the dump path — every lookup misses, so the memoized and unmemoized paths return identical results and the memo can only change how many queries run, never what they produce. That is what keeps this an optimization rather than a second code path with its own bugs.

The load function runs under the memo's lock, so a value is computed exactly once even though the dump path is sequential today.

type DumpPlan

type DumpPlan struct {
	// Types are user-defined types (PostgreSQL enums, domains, composites)
	// emitted BEFORE routines and tables, which may reference them — without
	// this pass a restore into an empty server fails at the first CREATE TABLE
	// naming an enum/domain. Their Drops run in teardown AFTER the tables, so a
	// column using the type is already gone. Only PostgreSQL populates this, and
	// the writer emits it only when structure is dumped.
	Types []DumpScript
	// Collations are user-defined collations (PostgreSQL) emitted BEFORE the types
	// and tables — a domain, composite attribute or column COLLATE may reference
	// one. Their Drops run in teardown AFTER the types (a domain over a collated
	// base type drops first). Only PostgreSQL populates this, structure-gated.
	Collations []DumpScript
	// Routines are emitted before the tables: functions/procedures may be
	// referenced by views, triggers or generated columns.
	Routines []DumpScript
	// Sequences (CREATE SEQUENCE) are emitted after teardown, before the
	// tables, so a serial/standalone column DEFAULT nextval(...) resolves at
	// CREATE TABLE time. Their Drops run in teardown AFTER the DROP TABLEs:
	// an owned sequence is auto-dropped with its table, and an OWNED BY /
	// default dependency would block an earlier DROP. Only PostgreSQL populates
	// this; the writer emits it only when structure is dumped.
	Sequences []DumpScript
	// ForeignData (C4.3) holds foreign-table creates (schema plans) and rides
	// the pre-data slot between sequences and tables (pg_dump's foreign-data
	// priority; a foreign table can INHERIT a local parent — the edge hoists
	// the parent). Structure-only: foreign tables have NO data pass — their
	// rows live on the remote server.
	ForeignData []DumpScript
	// Views (and materialized views) are emitted after the tables,
	// dependency-ordered so a view referencing another view restores.
	Views []DumpScript
	// PostData is emitted after all rows: foreign-key ALTERs (PostgreSQL),
	// NOT VALID constraints, secondary indexes (SQLite), triggers (so restored
	// INSERTs do not fire them), events, materialized-view refreshes and
	// sequence synchronization, in that order.
	PostData []DumpScript
	// Warnings are best-effort advisories (skipped objects, unsynchronized
	// external state, dependency hazards) the writer emits as inert `-- WARNING:`
	// comment lines — routed through commentSafe — at the top of the dump body in
	// every mode. They never fail the export.
	Warnings []string
	// SchemaComment is the COMMENT ON SCHEMA text (PostgreSQL), emitted in the
	// structure phase INDEPENDENTLY of the CREATE SCHEMA line (the default/public
	// section writes no CREATE SCHEMA, but a comment on it must still round-trip).
	SchemaComment string
	// ViewEdges (C3) are the DATABASE-WIDE (dependent, source) edges among
	// views/matviews, as qualified "schema.name" pairs resolved by relation OID
	// through pg_rewrite — the planner topo-orders cross-schema matview
	// REFRESHes with them. Only PostgreSQL populates this.
	ViewEdges [][2]string
	// TableNodes (C3) maps each dumped table's BARE name to its dependency-graph
	// metadata (tables are not DumpScripts — the handler owns their emission —
	// so their edges ride here). Only PostgreSQL populates this.
	TableNodes map[string]DumpTableNode
	// DataOnlyTables (C4.3) names tables whose ROWS are dumped but whose
	// STRUCTURE is not: the local leaves of a partition tree containing a
	// FOREIGN leaf. The tree's root emits every child's structure recursively,
	// but its data scan cannot run (a plain FROM on the root would query the
	// foreign leaf's REMOTE server), so the local leaves are read individually
	// (FROM ONLY) instead — emitting their creates too would duplicate the DDL.
	DataOnlyTables []string
	// SuppressedRelations (C4.3) names relations classified state (c) —
	// unreproducible under the redaction policy, emitted only as inert
	// templates — whose dependents (triggers/comments/rules) were suppressed
	// with them.
	SuppressedRelations []string
	// StructureOnlyTables (C4.3) names tables whose STRUCTURE is dumped but
	// whose DATA scan must not run: a mixed local/foreign partition tree's
	// ROOT (its plain FROM would recurse into the foreign leaf and query the
	// remote server; the local leaves in DataOnlyTables carry the rows).
	StructureOnlyTables []string
	// SequenceRewrites (C5.1/C5.2) maps an out-of-scope source sequence (keyed
	// SeqRefKey(schema, name)) to the replacement sequence materialized in its
	// place (a SeqRefKey-shaped value). The handler routes every emitted table's
	// DDL through RewriteSequenceRefs so early-bound ('s'::regclass) and
	// resolvable late-bound ('s'::text) default references bind to the
	// replacement. Only PostgreSQL populates this, and only for scoped
	// (non-database) exports — a database-scope dump emits every sequence.
	SequenceRewrites map[string]string
	// StagedDefaultColumns (C5.3) names, per BARE table, columns whose inline
	// DEFAULT must be suppressed at CREATE time and re-established post-data
	// (Kind "staged-default"): the multi-parent divergent-default conflict —
	// CREATE … INHERITS fails on conflicting parent defaults before any staged
	// DDL could run, so every affected hierarchy member creates default-less
	// and re-emits its own state via ALTER TABLE ONLY. The handler renders
	// these tables through StagedTableDumper. Only PostgreSQL populates this.
	StagedDefaultColumns map[string][]string
}

DumpPlan is the non-table DDL a restore-equivalent SQL dump emits around the table CREATEs and data. Each slice is already ordered for restore.

type DumpScript

type DumpScript struct {
	// Kind classifies the object: "routine", "view", "matview", "trigger",
	// "event", "index", "constraint", "sequence", "sequence-def",
	// "sequence-own", "refresh". The exporter uses it for teardown ordering
	// (constraints drop before tables) and to keep the data-section items
	// (setval "sequence" / matview "refresh") in data-only dumps while dropping
	// structure-only items ("sequence-def" CREATE SEQUENCE lives in DumpPlan.
	// Sequences; "sequence-own" ALTER SEQUENCE … OWNED BY is post-data structure).
	Kind           string
	Comment        string // short human label for the dump ("Trigger trg_audit")
	Drop           string // optional teardown statement (DROP ... IF EXISTS), emitted when the user asked to drop first
	SQL            string
	NeedsDelimiter bool // MySQL routine/trigger/event body: internal ';' needs DELIMITER wrapping

	// DropInline marks an object whose Drop is emitted INLINE, immediately
	// before its own CREATE (mysqldump parity for routines and events), instead
	// of in the reverse-ordered teardown block. PostgreSQL leaves it false for
	// every class: a routine's drop must precede the DROP TYPE of a type in its
	// signature, which only the reverse teardown order guarantees.
	DropInline bool
	// StageOf, on a script that is one CREATION STAGE of a larger logical object
	// (a shell type and the CREATE that completes it), is that object's
	// canonical node id. Creation ordering uses the per-stage Name — the whole
	// point of staging is that the stages sort separately — but TEARDOWN
	// collapses them back onto this id: the RESTORED catalog holds one object
	// whose dependencies close the very cycle staging broke, so the drop graph
	// must see it as one node.
	StageOf string
	// DropForm describes how this object's Drop may be merged into a
	// multi-object DROP — the only non-CASCADE way to drop a dependency cycle
	// (see DropForm). Zero value: ungroupable.
	DropForm DropForm

	// Name is this script's node id in the C3 dump dependency graph:
	// "<catalog-kind>:<qualified name>[(identity args)]" — e.g.
	// "type:public.mood", "relation:public.emp", "routine:public.f(integer)".
	// Identity arguments / operand types / access method are part of the id so
	// overloads and cross-type members never collapse. A staged object's later
	// stage appends "-final" ("type-final:…", "routine…-final"). Empty means the
	// script takes no part in graph ordering (MySQL/SQLite plans) and keeps its
	// stable class-priority position.
	Name string
	// DependsOn lists node ids this script requires created first. An id
	// missing from the emitted set is a BOUNDARY edge (a reference to an
	// out-of-scope object): the topological sort ignores it and the planner
	// surfaces it as a restore-prerequisite diagnostic, never a failure.
	DependsOn []string
	// Clauses are deferrable trailing clauses (a domain's DEFAULT / CHECK):
	// normally each Text is appended to SQL in order, but when a clause's Deps
	// close a dependency cycle the planner CUTS it — the clause text is omitted
	// and its Finalize scripts (ALTER … SET DEFAULT / ADD CONSTRAINT, plus any
	// moved COMMENT) run later in the lane PreData selects.
	Clauses []DumpClause
	// Stub, when non-empty, is a dependency-reduced replacement create for a
	// routine caught in a dependency cycle: the same signature with every
	// argument default omitted (the whole trailing default group — a partial
	// omission would be invalid DDL) and an unchecked placeholder body
	// (check_function_bodies=false stores string bodies verbatim, and
	// CREATE OR REPLACE may change language/body/defaults, so the original SQL
	// — already CREATE OR REPLACE — restores the real object as the "-final"
	// stage). StubDeps are the edges the stub RETAINS (signature types); if
	// those still form a cycle the plan preflight-fails.
	Stub     string
	StubDeps []string

	// OpaqueFrame marks SQL as raw bytes fetched under
	// character_set_results=binary (MySQL object bodies): the writer wraps it
	// in a TableX opaque frame — a frame marker plus a body-derived DELIMITER
	// — and the splitter replays the framed bytes verbatim with no
	// string/comment interpretation, so a non-UTF-8 or NO_BACKSLASH_ESCAPES
	// body cannot be mangled or mis-split. Implies DELIMITER wrapping.
	OpaqueFrame bool
	// Pre/Post are complete session-guard statements the writer emits around
	// SQL (outside any DELIMITER wrap): mysqldump-style save/set/restore of
	// the object's creation context (sql_mode, character_set_client,
	// collation_connection, event time_zone). Save variables MUST use
	// object-local @saved_* names, never the preamble's @OLD_* names, which
	// the postamble restores last.
	Pre  []string
	Post []string
	// Markers are pre-formatted inert TableX marker lines (FormatCollationMarker)
	// emitted verbatim before the script — disclosure comments to external
	// clients, warning events to TableX's own importer.
	Markers []string
}

DumpScript is one self-contained piece of dump DDL. SQL holds one complete statement (or, for multi-statement bodies, a script) WITHOUT a trailing terminator — the exporter appends ';' or wraps in DELIMITER directives.

type DumpTableNode

type DumpTableNode struct {
	Name                  string // the table's node id ("relation:<schema>\x00<name>")
	Deps                  []string
	DeferrableDefaults    map[string][]string // column name → node ids its DEFAULT expression references
	DeferrableConstraints map[string][]string // constraint name → node ids its expression references
}

DumpTableNode is one table's dependency-graph metadata (see DumpPlan.TableNodes): hard edges plus the deferrable per-column DEFAULT and per-constraint expression edges the planner may CUT to break a cycle — cutting re-renders the CREATE without the clause via StagedTableDumper and re-adds it post-data (data INSERTs name every column explicitly, so a post-data SET DEFAULT never changes restored rows, and a re-added CHECK validates them).

type Dumper

type Dumper interface {
	// DumpTableCreate returns the restore-oriented CREATE statement(s) for one
	// table, ending without a trailing terminator on the last statement.
	DumpTableCreate(ctx context.Context, db *sql.DB, t TableRef) (string, error)

	// DumpDataTables returns the subset of tables whose rows the data phase
	// reads directly, preserving order. PostgreSQL excludes partition children:
	// their structure rides with the parent and a SELECT on the parent already
	// returns their rows (dumping both would duplicate every row).
	DumpDataTables(ctx context.Context, db *sql.DB, scope Scope, tables []string) ([]string, error)

	// DumpObjects returns everything beyond table DDL and data for the given
	// tables. dbScope and structure are independent: dbScope=false (table-scope
	// export) returns only objects belonging to the listed tables (triggers,
	// indexes, constraints, owned sequences), while dbScope=true also covers
	// database-scope objects (routines, views, events, standalone sequences).
	// structure=false (data-only export) suppresses ALL structure-only DDL
	// (routines/views/triggers/indexes/constraints/sequence ownership) so a
	// data-only dump neither fails on nor wastes introspection it would
	// discard, while still collecting the data-section items (sequence setval,
	// matview refresh) pg_dump emits in a --data-only run.
	DumpObjects(ctx context.Context, db *sql.DB, scope Scope, tables []string, dbScope, structure bool) (DumpPlan, error)

	// ValueLiteral renders a scanned cell as an engine-correct SQL literal for
	// the dump's INSERT statements: the binary form (X'…' vs '\x…'), MySQL's
	// zero-date sentinel, and non-finite float handling all live here rather
	// than in engine switches at the call site.
	ValueLiteral(col ResultColumn, v Value) string

	// DumpPreamble writes the session state a restore needs pinned before the
	// script body (FK checks, sql_mode, string-escape settings, time zone);
	// DumpPostamble restores it for sessions that outlive the script. Either
	// may write nothing.
	DumpPreamble(w io.Writer)
	DumpPostamble(w io.Writer)
}

Dumper is an optional Dialect capability producing restore-equivalent dump DDL (discovered by type assertion, mirroring SchemaEditor/Monitor). The plain CreateSQL is display-oriented; these methods are restore-oriented: PostgreSQL strips foreign keys from the CREATE (returning them as post-data ALTERs so cyclic/self-referencing schemas restore), preserves PARTITION BY, and emits partition children with the parent; MySQL wraps procedural bodies for DELIMITER handling; SQLite returns the extra sqlite_master rows (indexes, triggers) the table's own row does not carry.

Fallback contract (asymmetric, by design): when a Dialect does NOT implement Dumper, the Connection passthroughs (DumpTableCreate/DumpDataTables/DumpObjects) degrade differently. DumpTableCreate hard-fails with ErrUnsupported — a table's restore DDL cannot be faked from the display-oriented CreateSQL without silently dropping restore-critical detail — while DumpDataTables returns the tables unchanged and DumpObjects returns an empty plan, so a minimal future dialect that implements no Dumper still exports its rows (data phase) and simply omits the extra objects. Implementers keep this split: the data/object passes degrade gracefully; the structure-DDL pass fails loudly.

type EventManager

type EventManager interface {
	DropEventSQL(s Scope, e model.Event) ([]string, error)
	NewEventTemplate() string
}

type ExecResult

type ExecResult struct {
	RowsAffected int64
}

ExecResult is the engine-neutral outcome of a non-query statement.

type ExportConnAdjuster

type ExportConnAdjuster interface {
	ExportConnParams(p ConnParams) ConnParams
}

ExportConnAdjuster is an optional Dialect capability: dialect-owned session pinning for a dedicated EXPORT connection's params (MySQL pins time_zone, sql_mode and sql_quote_show_create so dumps render restore-parsable). Contract: p is a value, and implementations MUST clone Params before modifying — the map is shared with ServerConfig.Params and the session's base params — and set the pins AFTER copying, so they overwrite any same-name key coming from operator config.

type FilePathValidator

type FilePathValidator interface {
	ValidateFilePath(path string) error
}

FilePathValidator is an optional Dialect capability for a file-backed engine (Capabilities.IsNetworkEngine false): reject an operator-configured database file path that the engine could technically open but that cannot work as a predefined server, at config load rather than at first use. SQLite refuses the in-memory spellings — every pooled or transient connection would open its own private empty database.

The error is surfaced to the operator verbatim (prefixed with the server's position and name), so it must read as a complete explanation on its own.

type ForeignKeyEditor

type ForeignKeyEditor interface {
	AddForeignKeySQL(t TableRef, name string, cols []string, refTable string, refCols []string, onUpdate, onDelete string) ([]string, error)
	DropForeignKeySQL(t TableRef, name string) ([]string, error)
}

ForeignKeyEditor is the SchemaEditor half that adds and drops foreign-key constraints, split out for the same reason as ColumnModifier: SQLite cannot ALTER a table to add or drop one, so it carried two more error-only stubs. An engine that sets Capabilities.SupportsForeignKeyDDL MUST implement this.

The referential actions reaching these builders are pre-validated keywords, emitted verbatim.

type ForeignTableDumper

type ForeignTableDumper interface {
	IsForeignTable(ctx context.Context, db *sql.DB, scope Scope, name string) (bool, error)
	DumpForeignTable(ctx context.Context, db *sql.DB, scope Scope, name string) (DumpPlan, error)
}

ForeignTableDumper (C4.3) is an optional Dialect capability for the SQL-export-only foreign-table path: IsForeignTable resolves a name the ordinary relation listings deliberately exclude (foreign tables never enter ListTables/ListTableNames — no browsing, CSV/JSON or data pass), and DumpForeignTable builds its STRUCTURE-ONLY plan (create + triggers + rules + comments + restore-prerequisite warnings; never any row read).

type GlobalDumper

type GlobalDumper interface {
	DumpGlobalObjects(ctx context.Context, db *sql.DB, structure bool) (DumpPlan, error)
}

GlobalDumper is an optional Dialect capability collecting DATABASE-GLOBAL (non-schema-owned) objects once per database, before the per-schema DumpObjects passes — the C3 architectural hook the C4 object breadth (casts, foreign-data wrappers/servers/user mappings) plugs its producers into. Globals live per database, so a server-scope export runs it once per database section.

type GrantSpec

type GrantSpec struct {
	Privileges []string
	Database   string
	Schema     string // PostgreSQL table scope; ignored elsewhere
	Table      string // "" → database scope
	Grantee    string
	Host       string // MySQL grantee host part
	WithGrant  bool
	// Columns restricts the grant to specific columns of Table
	// (GRANT SELECT (a, b) ON t). Empty — the default — is an object-wide
	// grant. Names must have been matched against live introspection by the
	// caller; the builders only quote them.
	//
	// Dropping this list does not fail safe, it WIDENS: "SELECT on two columns"
	// silently becomes "SELECT on every column". So a dialect that cannot
	// express column scope must not receive it — Connection.Grant/Revoke refuse
	// a non-empty Columns unless the dialect is a ColumnPrivileger, and the
	// builders re-check.
	Columns []string
	// DatabasePatterns carries the stored grant pattern(s) exactly as
	// introspected (model.Privilege.StoredObject) for a MySQL database-scope
	// REVOKE: pre-escaped as stored, used verbatim, one REVOKE per pattern.
	// Empty (the default, and always for GRANT and other engines) falls back
	// to escaping Database.
	DatabasePatterns []string
}

GrantSpec describes one GRANT or REVOKE. Privileges must be pre-validated by the caller (grant: membership in GrantablePrivileges; revoke: membership in the freshly introspected grants) — the builders re-check defensively but the handler is the authority. Grantee "PUBLIC" is PostgreSQL's pseudo-role and is emitted as the bare keyword, never as a quoted identifier.

type Grouper

type Grouper[K comparable, T any] struct {
	// contains filtered or unexported fields
}

Grouper accumulates pointers to grouped elements keyed by K, preserving the order keys are first seen, then flattens them to a value slice. It removes the repeated "order []K + byKey map + lookup-or-create + rebuild tail" idiom the dialect Indexes/ForeignKeys introspection used to spell out at every call site (a row stream arrives grouped by name/id and must collapse to one element per group, preserving query order).

func NewGrouper

func NewGrouper[K comparable, T any]() *Grouper[K, T]

NewGrouper returns an empty Grouper.

func (*Grouper[K, T]) GetOrAdd

func (g *Grouper[K, T]) GetOrAdd(key K, create func() T) *T

GetOrAdd returns the existing element for key, or inserts create()'s result and returns a pointer to it. The returned pointer is stable across calls; callers mutate the group through it (e.g. appending columns to an index).

func (*Grouper[K, T]) Slice

func (g *Grouper[K, T]) Slice() []T

Slice returns the grouped elements as values, in first-seen key order.

type IndexColumn

type IndexColumn struct {
	Name string
	// Desc orders this key part descending. Only set it when
	// IndexOptions().SupportsDesc — MariaDB, for one, PARSES the keyword and
	// then ignores it, which is worse than refusing it.
	Desc bool
	// Prefix indexes only the leading Prefix characters/bytes of the column
	// (MySQL's col(10)). Zero indexes the whole value. Only set it when
	// IndexOptions().SupportsPrefix.
	Prefix int
}

IndexColumn is one key part of an index, in key order.

type IndexOptioner

type IndexOptioner interface {
	IndexOptions() IndexOptions
}

IndexOptioner is the optional capability declaring the above. A dialect that implements it must honour what it claims: drivertest checks that each claimed option actually changes the statement AddIndexSQL builds, because a silently-dropped option is a promise the UI keeps making and the database never hears.

type IndexOptions

type IndexOptions struct {
	// Methods are the offerable access methods, engine spelling. Empty means
	// the engine has no choice to make (MySQL: the storage engine decides).
	Methods []string
	// SupportsDesc: a descending key part is honoured, not merely parsed.
	SupportsDesc bool
	// SupportsPrefix: a key part may index a leading prefix of the value.
	SupportsPrefix bool
	// SupportsPartial: the index may carry a WHERE predicate.
	SupportsPartial bool
}

IndexOptions reports which optional parts of an index an engine can express, so the UI offers exactly those and the handler refuses the rest. An engine with none of them omits IndexOptioner entirely and gets the zero value.

type IndexSpec

type IndexSpec struct {
	Name    string
	Columns []IndexColumn
	Unique  bool
	// Method is the access method / index type: an entry from
	// IndexOptions().Methods, or "" for the engine default. It is emitted as a
	// keyword, so only an allowlisted value may reach it.
	Method string
	// Where is a partial index's predicate, already guarded by the caller. It
	// is the one part of an index that is user-written SQL rather than an
	// identifier or a keyword — see docs/security.md.
	Where string
}

IndexSpec describes an index to create. It replaced a (name, cols, unique) argument list because everything past "unique" is engine-optional, and three more positional parameters would have forced every dialect to carry arguments it ignores.

The handler validates every part against IndexOptions() before this is built, so a dialect can assume it was handed only what it said it supports.

type Inheritor

type Inheritor interface {
	InheritanceParents(ctx context.Context, db *sql.DB, scope Scope, tables []string) (map[string][]string, error)
	DumpInheritsChildCreate(ctx context.Context, db *sql.DB, t TableRef, parents []string) (string, error)
}

Inheritor is an optional Dialect capability for ordinary (INHERITS, non-partition) table inheritance. PostgreSQL implements it; MySQL/SQLite have no equivalent. InheritanceParents reports, per same-schema ordinary inheritance child among the given tables, its parent bare-names (ordered by inhseqno) — used to order parents before children and to decide the linked create. DumpInheritsChildCreate emits a child's CREATE TABLE with an INHERITS clause and local-only columns/constraints so the link and provenance survive.

type LexerProfile

type LexerProfile struct {
	// BackslashStrings: a backslash escapes the next character inside '…' and
	// "…" string literals (MySQL default mode; never backtick identifiers).
	BackslashStrings bool
	// EscapeStringE: E'…' literals honor backslash escapes even though plain
	// '…' strings do not (PostgreSQL).
	EscapeStringE bool
	// DollarQuotes: $tag$…$tag$ dollar-quoted literals (PostgreSQL). Engines
	// where '$' is an identifier character must leave this off, or a$$b would
	// open a quote.
	DollarQuotes bool
	// HashComments: '#' starts a line comment (MySQL).
	HashComments bool
	// DashCommentNeedsSpace: a `--` line comment requires following whitespace
	// or end-of-line (MySQL); otherwise any `--` starts a comment.
	DashCommentNeedsSpace bool
	// NestedBlockComments: /* … */ block comments nest (PostgreSQL).
	NestedBlockComments bool
	// DelimiterDirectives: mysql-client DELIMITER directives are recognized,
	// including the TableX opaque frames dumped routine bodies travel in.
	DelimiterDirectives bool
	// DollarInWords: '$' is an ordinary identifier character (MySQL/SQLite).
	DollarInWords bool
	// BracketIdentifiers: [name] is a quoted identifier, with ]] escaping a
	// literal ']' — the T-SQL / Microsoft Access convention, which SQLite also
	// accepts. Engines where '[' is an operator (PostgreSQL array subscripts)
	// must leave this off. Without it a separator inside a bracket-quoted name
	// (`[a;b]`) splits the statement.
	BracketIdentifiers bool
	// BatchSeparator, when non-empty, is a CLIENT-SIDE batch terminator
	// recognized as the only content on its own line, case-insensitively —
	// T-SQL's `GO`. It ends the current statement the way the statement
	// separator does, but the word itself is never sent to the server. Empty
	// (every engine here today) means the engine has none.
	BatchSeparator string
	// RoutineBodyRe, when non-nil, matches routine-creating statements whose
	// BEGIN…END bodies hold internal semicolons, so the splitter tracks block
	// depth instead of splitting inside the body. Engines whose bodies are
	// quoted strings (PostgreSQL dollar quoting) leave it nil.
	RoutineBodyRe *regexp.Regexp
	// Returning describes which DML statement kinds support a RETURNING clause
	// on this engine+version, so the SQL console can classify a RETURNING-bearing
	// INSERT/UPDATE/DELETE/REPLACE/MERGE as row-returning (grid) rather than a
	// bare Exec. Populated per dialect from flavor/version.
	Returning ReturningCaps
}

LexerProfile describes the statement-lexing grammar of an engine's SQL scripts. The handler-side statement splitter consumes it instead of branching on raw engine names, so an engine's script grammar lives with its dialect: string-escape rules, comment forms, dollar quoting, mysql-client DELIMITER directives and procedural BEGIN…END body tracking.

func DefaultLexerProfile

func DefaultLexerProfile() LexerProfile

DefaultLexerProfile is the grammar assumed for a dialect without a StatementLexer: standard-conforming quoting with PostgreSQL's extensions (dollar quotes, E'…' strings, nested block comments) — the same fallback the splitter's engine-name era used for unknown engines.

func ProfileOf

func ProfileOf(d Dialect) LexerProfile

ProfileOf returns d's LexerProfile, or DefaultLexerProfile when the dialect does not implement StatementLexer.

type LoginDatabaseHint

type LoginDatabaseHint struct {
	Label       string // field label ("" = the generic "Database")
	Placeholder string
	// Default is the value the form may pre-fill into an EMPTY field (and
	// clear again when switching engines, never touching a user-typed value).
	// The server-side authority for the same default is NormalizeParams.
	Default string
	// Note is a plain-text help sentence under the field ("" = none).
	Note string
}

LoginDatabaseHint is per-engine presentation metadata for the ad-hoc login form's database field, carried as data-attributes on the engine <option> so the client script reads the dataset instead of naming engines.

type LoginFormHinter

type LoginFormHinter interface {
	LoginDatabaseHint() LoginDatabaseHint
}

LoginFormHinter is an optional Dialect capability supplying LoginDatabaseHint.

type MaintenanceDatabaseLister

type MaintenanceDatabaseLister interface {
	MaintenanceDatabases() []string
}

MaintenanceDatabaseLister is an optional Dialect capability naming the databases a session may connect to in order to run a statement it cannot run on its own connection — today, DROP DATABASE against the database the session is bound to (Capabilities.CanDropConnectedDatabase false). PostgreSQL returns "postgres" then "template1": a managed host often blocks the postgres database, which is exactly why user-database logins exist, so template1 is the fallback.

The slice is in preference order; the caller tries each in turn and skips the database being operated on. Engines that can act on their own connection (MySQL) or have no server-level databases at all (SQLite) omit it, and the operation is refused with a clear message rather than guessing a name.

Implementations MUST return a fresh slice — the value is shared by every session.

type Monitor

type Monitor interface {
	Status(ctx context.Context, db *sql.DB) ([]model.Variable, error)
	Variables(ctx context.Context, db *sql.DB) ([]model.Variable, error)
	Processes(ctx context.Context, db *sql.DB) (*ResultSet, error)
}

Monitor is an optional capability for server-level status, configuration variables and the process list. Handlers type-assert a Dialect to Monitor and gracefully degrade when an engine does not implement it (e.g. SQLite returns empty process lists). All built-in dialects implement it.

type NameLister

type NameLister interface {
	ListDatabaseNames(ctx context.Context, db *sql.DB) ([]model.Database, error)
	ListTableNames(ctx context.Context, db *sql.DB, scope Scope) ([]model.Table, error)
}

NameLister is an optional Dialect capability providing cheap, identity-only listings for navigation trees, existence checks and counts. Implementations populate only Name/Schema/Type (tables) and Name/IsSystem (databases) and must avoid per-object statistics — the full ListDatabases/ListTables queries aggregate sizes and row counts, which on MySQL and PostgreSQL touch table statistics or stat the relation files for every object. Engines whose full listings are already statistics-free (SQLite) omit it and the Connection passthrough falls back.

type NestedGrouper

type NestedGrouper[K1, K2 comparable, T any] struct {
	// contains filtered or unexported fields
}

NestedGrouper is Grouper's two-level form: rows arrive keyed by an outer group (table) and an inner group (constraint name) and collapse to one element per inner group, preserving first-seen order at both levels. It absorbs the outer "map[K1]*Grouper + ordered key slice + rebuild loop" wrapper the MySQL and PostgreSQL bulk foreign-key introspections both spelled out around the shared inner Grouper.

func NewNestedGrouper

func NewNestedGrouper[K1, K2 comparable, T any]() *NestedGrouper[K1, K2, T]

NewNestedGrouper returns an empty NestedGrouper.

func (*NestedGrouper[K1, K2, T]) GetOrAdd

func (n *NestedGrouper[K1, K2, T]) GetOrAdd(outer K1, inner K2, create func() T) *T

GetOrAdd returns the element for (outer, inner), inserting create()'s result on first sight. Pointer semantics mirror Grouper.GetOrAdd: callers mutate the group through the stable returned pointer.

func (*NestedGrouper[K1, K2, T]) Map

func (n *NestedGrouper[K1, K2, T]) Map() map[K1][]T

Map flattens to outer key → grouped values (inner first-seen order).

type Pagination

type Pagination struct {
	Limit  int
	Offset int64
}

Pagination is the engine-neutral limit/offset for browse pages. Offset is int64 so a 32-bit build can still address rows beyond 2^31.

type ParamsNormalizer

type ParamsNormalizer interface {
	NormalizeParams(p ConnParams) ConnParams
}

ParamsNormalizer is an optional Dialect capability applied by the login handler to freshly-built connection params, so engine-specific defaults live with the dialect instead of the boundary code. PostgreSQL defaults an empty Database to "postgres". This is deliberately a params rewrite, never a BuildDSN-internal default: params.Database must stay observably set — it flows into the session's base params and the server-connection reuse test. Implementations treat p as a value; they may set scalar fields but must clone Params before modifying it (the map is shared with the server config).

type Pinned

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

Pinned is a dedicated single physical database connection for running multi-statement scripts (SQL imports, the console). Pool execution would let successive statements land on different physical connections, so session-scoped state (SET sql_mode / time_zone, FOREIGN_KEY_CHECKS=0, PRAGMA foreign_keys=OFF) would neither reliably apply to later statements nor be cleaned up when a script stops at the first error. A Pinned connection guarantees both: every statement runs on the one connection, and Close discards it (together with its private pool) instead of returning dirty session state to a shared pool.

func OpenPinned

func OpenPinned(ctx context.Context, d Dialect, p ConnParams) (*Pinned, error)

OpenPinned dials a dedicated connection using the dialect and params. A MaxOpenConns(1) pool alone would not be enough — a pool may still recycle the physical session mid-script — so the single connection is checked out up front and held until Close.

Like Open, it loads ServerInfo and routes the dialect through Specialize: Pinned.Dialect feeds driver.ProfileOf, which gates the console/import splitter's RETURNING classification on the detected flavor and version. An unspecialized dialect reports the registered zero value, so MariaDB's `DELETE … RETURNING` would run as an Exec and its rows be discarded. Reading the facts from THIS session (rather than inheriting the login pool's) is also the more correct answer for the session-scoped ones — MySQL's sql_mode.

func (*Pinned) Close

func (p *Pinned) Close() error

Close releases the pinned connection and its private pool, discarding the physical connection along with any session state the script left behind.

func (*Pinned) Dialect

func (p *Pinned) Dialect() Dialect

Dialect returns the engine dialect.

func (*Pinned) Engine

func (p *Pinned) Engine() string

Engine returns the dialect name ("mysql", "postgres", "sqlite").

func (*Pinned) Exec

func (p *Pinned) Exec(ctx context.Context, query string) (ExecResult, error)

Exec runs a non-row statement and reports affected rows.

func (*Pinned) Explain

func (p *Pinned) Explain(ctx context.Context, query string, analyze bool) (*ResultSet, error)

Explain runs EXPLAIN for a query if the engine supports it.

func (*Pinned) ExplainSQL

func (p *Pinned) ExplainSQL(query string, analyze bool) (string, bool)

ExplainSQL returns the exact statement Explain would execute, so the console can label the result with what really ran.

func (*Pinned) Query

func (p *Pinned) Query(ctx context.Context, query string, limit int) (*ResultSet, error)

Query runs a row-returning statement and scans up to limit rows.

type PoolOpener

type PoolOpener interface {
	OpenPool(p ConnParams) (*sql.DB, error)
}

PoolOpener is an optional Dialect capability: a network engine implements it to build its *sql.DB pool through a driver.Connector so ConnParams.DialControl can be applied to every connection (the dial-time SSRF guard). Engines without a network (SQLite) omit it, and Open falls back to the generic sql.Open(SQLDriverName(), BuildDSN()) path.

type PrivilegeManager

type PrivilegeManager interface {
	GrantablePrivileges(table bool) []string // curated allowlist: table or database scope
	GrantSQL(g GrantSpec) ([]string, error)
	RevokeSQL(g GrantSpec) ([]string, error)
}

PrivilegeManager is an optional, write-only Dialect capability for GRANT/REVOKE (the read side stays on Privileger). GrantablePrivileges is the single source of truth for both the grant form's checkbox set and grant validation. RevokeSQL deliberately accepts keywords outside that set — an already-present grant of a version-specific privilege (e.g. PostgreSQL 17's MAINTAIN) must stay revokable — but every keyword still passes ValidPrivilegeKeyword before it is emitted.

type Privileger

type Privileger interface {
	Privileges(ctx context.Context, db *sql.DB, ref TableRef) ([]model.Privilege, error)
}

Privileger is an optional Dialect capability for listing privilege grants scoped to a database (ref.Table == "") or a specific table. Engines without an access-control system (SQLite) do not implement it, and the handlers degrade gracefully.

type ProcessManager

type ProcessManager interface {
	// ProcessIDColumn is the Processes() column whose value KillProcessSQL
	// takes ("Id" on MySQL, "pid" on PostgreSQL).
	ProcessIDColumn() string
	// KillProcessSQL terminates the session with this identifier.
	//
	// The id is an int64, not a string, and that is a deliberate part of the
	// contract rather than a convenience: neither engine accepts a placeholder
	// where the identifier goes (MySQL's KILL takes no parameters at all), so
	// the value is formatted into the statement — and an integer parsed by the
	// caller cannot carry anything else. Do not widen this to a string.
	KillProcessSQL(id int64) string
}

ProcessManager is an optional capability: terminate a server session listed by Monitor.Processes. Engines with no session table (SQLite) omit it and the process page stays read-only.

The process list is an opaque *ResultSet — each engine's own columns — so the dialect also names the column holding the identifier. That pairing is the whole interface: without it a caller would have to guess which column of SHOW FULL PROCESSLIST or pg_stat_activity is the one to kill.

type ProgramKind

type ProgramKind string

ProgramKind names a STORED PROGRAM — a routine, trigger or event — as opposed to the relation kinds (ObjectTable / ObjectView / ObjectMatView) that SchemaEditor's DropObjectSQL and RenameObjectSQL take. The two vocabularies are deliberately separate: they name different things, are consumed by different capabilities, and a single Object* family spanning both would invite passing a trigger where a table is meant. "Stored program" is MySQL's own term for the group.

It is a closed set the caller maps a listing row onto, so a dialect only ever sees a kind for an object it listed in the first place.

const (
	ProgramProcedure ProgramKind = "procedure"
	ProgramFunction  ProgramKind = "function"
	ProgramTrigger   ProgramKind = "trigger"
	ProgramEvent     ProgramKind = "event"
)

func ProgramDDLKind

func ProgramDDLKind(stmt string) (ProgramKind, bool)

ProgramDDLKind reports the stored-program kind a CREATE statement declares. The editor uses it to refuse a statement that is not a CREATE at all, or creates a different kind of object than the page it was submitted from — so "edit this trigger" cannot quietly run something else.

type ResultColumn

type ResultColumn struct {
	Name    string
	DBType  string // DatabaseTypeName(), e.g. "VARCHAR", "INT4", "BLOB"
	Numeric bool
	Binary  bool
}

ResultColumn is the metadata for one result column.

type ResultSet

type ResultSet struct {
	Columns   []ResultColumn
	Rows      [][]Value
	Truncated bool // true when more rows existed than the cap returned
}

ResultSet is an engine-neutral table of results, produced by scanning any *sql.Rows generically. It backs both the browse grid and the SQL console, so every engine's output renders through the same template path.

func ScanResult

func ScanResult(rows *sql.Rows, limit int) (*ResultSet, error)

ScanResult reads up to limit rows from rows into a ResultSet, eliding text cells beyond MaxCellBytes. A limit <= 0 uses the default safety cap. The caller retains ownership of rows and must not have consumed them yet; ScanResult closes them.

func ScanResultVerbatim

func ScanResultVerbatim(rows *sql.Rows, limit int) (*ResultSet, error)

ScanResultVerbatim is ScanResult with no per-cell size cap, for the one caller that must round-trip a cell rather than display it: the row-edit form, whose prefilled inputs are posted back as the row's new values. Everything else must use ScanResult.

type ReturningCaps

type ReturningCaps struct {
	Insert, Update, Delete, Replace, Merge bool
}

ReturningCaps records, per leading statement keyword, whether a RETURNING clause is supported. A false gate means a `… RETURNING …` on that statement is run as a plain Exec (the statement still executes; only the grid-vs-count presentation differs), which is correct on engines/versions lacking RETURNING there and avoids misrouting an unquoted identifier named `returning`.

func (ReturningCaps) Allows

func (rc ReturningCaps) Allows(keyword string) bool

Allows reports whether an uppercased leading keyword supports RETURNING.

type RoleGrant

type RoleGrant struct {
	Role       string
	RoleHost   string // MySQL 8 role account host; ignored by MariaDB and PostgreSQL
	Member     string
	MemberHost string // MySQL/MariaDB grantee host
	// AdminOption lets the member grant the role onward. Ignored by REVOKE,
	// which removes the membership entirely.
	AdminOption bool
}

RoleGrant describes one GRANT/REVOKE of a role to an account. Both ends must have been matched against the live account listing by the caller; the builders only quote them.

type RoleManager

type RoleManager interface {
	RoleMemberships(ctx context.Context, db *sql.DB) ([]model.RoleMembership, error)
	GrantRoleSQL(g RoleGrant) ([]string, error)
	RevokeRoleSQL(g RoleGrant) ([]string, error)
}

RoleManager is an optional Dialect capability for role membership — the grants that hand one account another's privileges wholesale. Read and write live on one interface because, as with column grants, a membership that cannot be listed cannot be revoked.

It is paired with Capabilities.SupportsRoles, which is VERSION-GATED on MySQL/MariaDB (roles arrived in MySQL 8.0 and MariaDB 10.0.5) and must fail CLOSED on an unknown version: the catalog table simply does not exist on an older server, so guessing "supported" turns the page into an error instead of a hidden section. That is the opposite of SupportsColumnRename, which fails open because only one old flavor lacks the statement.

The two engine families disagree about what a role IS, and the interface deliberately does not paper over it: a MySQL 8 role is an ordinary account ('r'@'%'), a MariaDB role is a distinct kind of row with no host, and every PostgreSQL role can be granted to any other. Each dialect addresses its own catalog; handlers only move model.RoleMembership around.

type RoutineGrant

type RoutineGrant struct {
	Scope      Scope
	Routine    model.Routine
	Privileges []string
	Grantee    string
	Host       string // MySQL grantee host part
	WithGrant  bool
}

RoutineGrant describes one GRANT/REVOKE on a stored routine. Routine is the object as INTROSPECTION returned it, not as a request named it: PostgreSQL needs its ArgSignature to address one of several overloads, and both engines need its Type to pick the FUNCTION or PROCEDURE keyword.

type RoutineManager

type RoutineManager interface {
	DropRoutineSQL(s Scope, r model.Routine) ([]string, error)
	NewRoutineTemplate(kind ProgramKind) string
}

RoutineManager administers stored routines, as TriggerManager and EventManager do for triggers and events (see ProgramKind). They are three interfaces rather than one because engines support the three kinds independently — SQLite has triggers but neither routines nor events, and only MySQL has events — and folding them together would hand SQLite two methods that never do anything but return an error. That is precisely the shape ColumnModifier and ForeignKeyEditor were split out of SchemaEditor to avoid.

Each takes the LISTED model object rather than a bare name. The caller resolves it from introspection first, so the identifier reaching QuoteIdent is one the catalog returned; the engine then reads whatever else it needs from the same object — PostgreSQL needs a trigger's table to say DROP TRIGGER … ON …, and a routine's argument signature to name an overloaded function unambiguously. The New*Template methods return the skeleton the "new object" editor opens with. They are engine SQL — MySQL's BEGIN…END body and PostgreSQL's dollar-quoted plpgsql block share no syntax — so the dialect owns them; the editor never assembles a statement of its own.

type RoutinePrivileger

type RoutinePrivileger interface {
	// RoutineGrantablePrivileges is the curated allowlist for routine scope —
	// EXECUTE everywhere, plus MySQL's ALTER ROUTINE.
	RoutineGrantablePrivileges() []string
	RoutinePrivileges(ctx context.Context, db *sql.DB, s Scope, r model.Routine) ([]model.Privilege, error)
	GrantRoutineSQL(g RoutineGrant) ([]string, error)
	RevokeRoutineSQL(g RoutineGrant) ([]string, error)
}

RoutinePrivileger is an optional capability for routine-scope grants (GRANT EXECUTE ON FUNCTION …). Read and write on one interface, for the reason the other two privilege capabilities share: an unlistable grant is an unrevokable one.

A routine is not a relation, so this deliberately does NOT reuse Privileger's TableRef: PostgreSQL addresses a routine by schema, name AND identity arguments, which no TableRef has a place for, and silently dropping them would target whichever overload the catalog happened to return first.

type RowEstimator

type RowEstimator interface {
	EstimateRows(ctx context.Context, db *sql.DB, t TableRef) (int64, error)
}

RowEstimator is an optional Dialect capability returning the engine's statistics-based row-count estimate for a table (MySQL information_schema TABLE_ROWS, PostgreSQL reltuples, SQLite sqlite_stat1). It returns -1 when no usable estimate exists (never-analyzed relation, view — SQLite returns -1 until ANALYZE has run); callers then fall back to an exact COUNT(*).

type SchemaEditor

type SchemaEditor interface {
	ColumnTypes() []string // curated base-type allowlist for the UI + validation
	// CreateTableSQL builds the statements for a NEW table: t names it (the
	// handler validates the name and non-existence first), cols is the full
	// ordered column list, pk the PRIMARY KEY column names (each must appear in
	// cols; empty for no PK). One CREATE TABLE statement — PK inline or as a
	// table constraint — plus any trailing comment statements the engine needs.
	// Implementations reject empty cols and unknown pk entries via
	// ValidateCreateTable.
	CreateTableSQL(t TableRef, cols []ColumnSpec, pk []string) ([]string, error)
	AddColumnSQL(t TableRef, c ColumnSpec) ([]string, error)
	DropColumnSQL(t TableRef, col string) ([]string, error)
	// DropObjectSQL emits the DROP for a table, view or materialized view. kind
	// is one of the Object* constants; matview is PostgreSQL-only (other engines
	// treat it as a view). Routing DROP through the dialect keeps a
	// DROP TABLE-on-a-view (which errors on every engine) from ever being emitted.
	DropObjectSQL(t TableRef, kind string) ([]string, error)
	// RenameObjectSQL renames a table, view or materialized view to newName. kind
	// selects the correct statement where engines differ: a MySQL view cannot be
	// renamed with ALTER TABLE (error 1347) and needs RENAME TABLE; a PostgreSQL
	// materialized view uses ALTER MATERIALIZED VIEW.
	RenameObjectSQL(t TableRef, newName, kind string) ([]string, error)
	AddIndexSQL(t TableRef, spec IndexSpec) ([]string, error)
	DropIndexSQL(t TableRef, name string) ([]string, error)
}

type SchemaManager

type SchemaManager interface {
	CreateSchemaSQL(name string) string
	// DropSchemaSQL drops the schema and every object in it (CASCADE); the
	// handler shows an explicit confirmation before invoking it.
	DropSchemaSQL(name string) string
}

SchemaManager is an optional Dialect capability for schema-level management inside one database (PostgreSQL). Engines whose Capabilities report HasSchemas implement it. Names are quoted with QuoteIdent in the builders; the handler validates them first (ValidNewIdentifier for create, introspection match for drop). The statements must run on a connection bound to the target database — schemas are per-database objects.

type Scope

type Scope struct {
	Database string
	Schema   string
}

Scope identifies what to introspect: a database, optionally narrowed to a schema. For engines without schemas, Schema is empty.

type SearchCaster

type SearchCaster interface {
	SearchExpr(quotedIdent string) string
}

SearchCaster is an optional Dialect capability returning the SQL expression used to LIKE-match a column in the search features. PostgreSQL implements it to cast the quoted identifier to text — uuid/json/bool/enum/inet/array columns reject a bare LIKE outright there, which silently dropped whole tables from database search and failed table search. Engines whose LIKE coerces implicitly (MySQL, SQLite) omit it and the identifier is used as-is.

type ServerDumpFramer

type ServerDumpFramer interface {
	ServerDumpProfile() ServerDumpProfile
	// WriteServerDumpHeader writes the engine's help comment lines for the
	// dump header (may write nothing).
	WriteServerDumpHeader(w io.Writer)
	// WriteDatabaseSectionHeader introduces one database's section: the
	// executable CREATE DATABASE …/USE pair (MySQL, preserving the
	// introspected default collation), the \connect meta-command (PostgreSQL),
	// or nothing (SQLite). collation is the database's introspected default
	// collation ("" when unknown/unsupported).
	WriteDatabaseSectionHeader(w io.Writer, name, collation string)
	// UnaddressableDatabase reports a human-readable reason the named database
	// cannot be addressed by this dump format ("" = addressable). PostgreSQL
	// skips names containing \r or \n: a psql \connect argument cannot
	// continue past end-of-line, and the residual fragment would leak into the
	// next section as executable SQL (the pg_dumpall CVE-2016-5424 class).
	UnaddressableDatabase(name string) string
}

ServerDumpFramer is an optional Dialect capability owning everything engine-specific about a server-scope SQL dump's framing: which databases a single script can address, how a database section is introduced, whether the preamble is global or per-section, and the dump-header help text. The handler loops over databases and delegates; it never branches on an engine name.

type ServerDumpProfile

type ServerDumpProfile struct {
	// PerSectionPreamble: the session preamble/postamble must be re-emitted
	// inside each database section (PostgreSQL: session SETs do not survive
	// \connect) instead of once globally.
	PerSectionPreamble bool
	// UsesConnectMarkers: sections switch databases via psql-style \connect
	// meta-commands, which the server-scope import honors (the import-side
	// mirror of this flag — export and import share it).
	UsesConnectMarkers bool
	// FormNote is the plain-text help sentence the export form shows for a
	// server-scope dump ("" = none).
	FormNote string
}

ServerDumpProfile describes the framing shape of an engine's SERVER-scope SQL dump. These are framing-owned flags on purpose: capability bits like DatabasesShareConnection describe connection topology, and any truth-value match with framing needs is coincidence — a new dialect sets its framing here and stays correct.

type ServerInfo

type ServerInfo struct {
	Engine    string // dialect Name(): "mysql", "postgres", "sqlite"
	Flavor    string // "MySQL", "MariaDB", "PostgreSQL", "SQLite"
	Version   string // full version string
	User      string // CURRENT_USER as reported by the server
	Charset   string // server/connection default charset
	Collation string // server/connection default collation
	Host      string // connection target host (for display only)
	Port      int
	Database  string // current database, when bound to one
	// SQLMode carries the MySQL/MariaDB @@SESSION.sql_mode at connect time, so a
	// dialect can specialize string escaping for NO_BACKSLASH_ESCAPES via the
	// optional WithServerInfo hook. Empty for engines that do not report it.
	SQLMode string
}

ServerInfo is the engine-neutral summary shown on the home page and used to decide MariaDB-vs-MySQL display, schema handling, etc.

type ServerSpecializer

type ServerSpecializer interface {
	WithServerInfo(info ServerInfo) Dialect
}

ServerSpecializer is an optional Dialect capability: return a COPY of the dialect specialized for one connection's server facts (MySQL derives NO_BACKSLASH_ESCAPES from sql_mode plus the flavor/version that gate introspection columns and the RETURNING lexer profile; PostgreSQL records the major version that gates MERGE … RETURNING).

Contract: the receiver must be treated as a value and the specialized copy returned — never mutate the registered singleton, which every session shares. Every path that establishes a connection MUST route its dialect through Specialize; a path that skips it leaves the registered zero value in play, so every version/flavor gate silently fails closed (this is exactly how the SQL console once discarded MariaDB's `DELETE … RETURNING` rows).

type Sort

type Sort struct {
	Column     string // must be validated against real columns before use
	Descending bool
}

Sort describes an ORDER BY clause built from a validated column name.

type StagedTableDumper

type StagedTableDumper interface {
	DumpTableCreateStaged(ctx context.Context, db *sql.DB, t TableRef, parents []string, stripDefaults, stripConstraints []string) (string, []DumpScript, error)
}

StagedTableDumper is an optional Dialect capability the C3 cycle resolver uses: re-render one table's create with the named columns' DEFAULT clauses and the named inline constraints OMITTED, returning the reduced create plus the deferred post-data scripts (ALTER … SET DEFAULT / ADD CONSTRAINT, with any constraint comment moved along — a comment cannot ride a create that no longer declares its constraint). parents mirrors DumpInheritsChildCreate (nil for a standalone/plain table).

type StatementEvent

type StatementEvent struct {
	// SQL is the statement as it was sent. The caller bounds its length before
	// recording it: an import statement can be megabytes.
	SQL string
	// Rows is the affected-row count for a mutation, or -1 where the engine or
	// the code path does not report one.
	Rows     int64
	Duration time.Duration
	// Err is the statement's error, or nil. A statement error may echo the
	// statement itself, which is already being recorded, so it carries nothing
	// the trail does not already hold.
	Err error
	// UserSQL distinguishes SQL the USER wrote (the console, an import) from SQL
	// TableX generated on their behalf (the Drop button, the structure editor).
	// An auditor reading "DROP TABLE orders" wants to know which it was.
	UserSQL bool
}

StatementEvent is one statement, as the observer sees it.

type StatementLexer

type StatementLexer interface {
	LexerProfile() LexerProfile
}

StatementLexer is an optional Dialect capability supplying the engine's script-lexing grammar. Dialects that omit it get DefaultLexerProfile.

type StatementObserver

type StatementObserver func(ctx context.Context, ev StatementEvent)

StatementObserver is notified after a statement runs. It is the seam the audit trail hangs on, and it exists here — as a plain callback rather than an import of internal/audit — so this package keeps knowing nothing about the application above it.

WHICH statements are reported is deliberately asymmetric:

  • On a shared pool (Connection), only MUTATIONS — Exec and ExecScript. The row-returning paths are how Browse, introspection and every count run, so reporting them would bury the trail in reads TableX generated for its own rendering.
  • On a pinned connection (Pinned), EVERYTHING. A pinned connection exists only to run a script the user wrote themselves — the SQL console and SQL import — so a SELECT there is as much a user action as a DROP, and an auditor wants both.

It must not block: it runs on the request's goroutine, immediately after the statement it describes.

type StorageDDL

type StorageDDL struct {
	// ID types a short ASCII identifier — at most 64 characters — that must be
	// usable as a PRIMARY KEY and inside an index. That requirement is why it is
	// not simply Text: MySQL cannot index a TEXT column without a prefix length.
	ID string
	// Text types unbounded UTF-8 text.
	Text string
	// Int64 types a signed 64-bit integer.
	Int64 string
	// TableOptions is appended verbatim to every CREATE TABLE (empty for engines
	// that need nothing). It must begin with a space if non-empty. MySQL pins
	// both the character set — a server defaulting to latin1 would otherwise
	// silently mangle non-ASCII text — and a transactional engine, which the
	// storage layer's atomic updates require.
	TableOptions string
}

StorageDDL names the column types internal/storage's schema needs, in one engine's spelling, plus whatever must be appended to a CREATE TABLE.

The vocabulary is three types long on purpose: every field internal/storage stores is an identifier, some text, or a 64-bit integer. Most notably there is no timestamp — an instant is an Int64 of Unix MICROSECONDS, UTC. A portable timestamp type does not exist across these engines (SQLite has no date type at all; MySQL and PostgreSQL disagree about whether a column carries a zone, and their drivers disagree about what Go type to scan one into), so the schema avoids the question rather than papering over it. Comparisons and ordering on an integer instant behave identically everywhere.

type StorageHost

type StorageHost interface {
	StorageDDL() StorageDDL
}

StorageHost is an optional Dialect capability for engines that can host TableX's OWN metadata database — the durable state described in internal/storage, as opposed to the databases a user administers. An engine that omits it simply cannot be named as the storage backend (config refuses it by name at startup); every other capability is unaffected.

It is deliberately the smallest interface in this file. The metadata schema is written once, by TableX, in portable SQL — CREATE TABLE IF NOT EXISTS, positional placeholders, no engine-specific clauses — so the only thing left for a dialect to supply is the SPELLING of a handful of column types.

type TableMaintainer

type TableMaintainer interface {
	TableMaintenanceOps() []TableMaintenanceOp
	TableMaintenanceSQL(t TableRef, op string) (string, error)
}

TableMaintainer is an optional capability exposing the engine's table maintenance commands (MySQL's OPTIMIZE/CHECK/REPAIR/ANALYZE, PostgreSQL's VACUUM/ANALYZE/REINDEX, SQLite's ANALYZE).

The offered set is data, not a fixed enum, because the commands have almost nothing in common across engines; the handler renders whatever the dialect lists and will only run a name from that same list.

Results come back as a ResultSet: MySQL's CHECK and REPAIR report their findings as rows, and discarding them would make "check this table" useless. Engines whose commands return nothing simply produce no rows.

type TableMaintenanceOp

type TableMaintenanceOp struct {
	Name  string // stable key the form posts back ("optimize")
	Label string // button text ("Optimize")
	Note  string // one line explaining what it does, shown as the button's title
	// Confirm, when non-empty, is the prompt shown before running. Set it for
	// anything that takes a heavy lock or rewrites the table — these operations
	// are not destructive, but running one on a large production table by
	// accident is its own kind of damage.
	Confirm string
}

TableMaintenanceOp is one maintenance command an engine offers for a table.

type TableRef

type TableRef struct {
	Database string
	Schema   string
	Table    string
}

TableRef identifies a single table within a database/schema.

type TeardownAuditor

type TeardownAuditor interface {
	AuditTeardown(ctx context.Context, db *sql.DB, planned []TeardownDrop) ([]string, error)
}

TeardownAuditor is an optional Dialect capability (C6): a WARN-ONLY, non-destructive audit of a drop-first teardown, run once per database when the user asked to drop first. It probes the SOURCE catalog for objects that depend on a planned DROP but are NOT themselves dropped by this dump — an external view over a dumped table, an out-of-scope index using a dumped operator class, an out-of-scope inheritance/partition child of a dumped parent — and returns advisory warnings naming the blocked statement.

It is best-effort by construction: the audit runs on the source connection, so blockers that exist only in the RESTORE TARGET are unknowable. It never fails the export, never escalates a DROP to CASCADE, and never suppresses a drop; a fresh target no-ops every `DROP … IF EXISTS` regardless.

type TeardownDrop

type TeardownDrop struct {
	Node string
	SQL  string
}

TeardownDrop is one planned teardown DROP handed to a TeardownAuditor: the object's graph node id (in the dialect's own id spelling — the auditor is the dialect that minted it) and the exact statement, so a warning can name the precise blocked drop.

type TriggerManager

type TriggerManager interface {
	DropTriggerSQL(s Scope, t model.Trigger) ([]string, error)
	// table is the table the editor was opened from, or "" at database level.
	NewTriggerTemplate(table string) string
}

type Tuning

type Tuning struct {
	// MaxOpenConns / MaxIdleConns size a pool. OpenPinned ignores both: a pinned
	// script is one physical session by definition.
	MaxOpenConns int
	MaxIdleConns int
	// ReadStmtTimeout overrides the per-statement budget for generated reads
	// (see the ReadStmtTimeout constant for exactly which statements).
	ReadStmtTimeout time.Duration
}

Tuning is the operator-settable connection behaviour Open applies. Zero fields fall back to the package defaults, so a caller that does not care can leave the whole struct zero.

type UserManager

type UserManager interface {
	CreateUserSQL(u UserSpec) ([]string, error)
	AlterUserSQL(u UserSpec) ([]string, error) // set password and/or role attributes
	DropUserSQL(name, host string) ([]string, error)
}

UserManager is an optional, write-only Dialect capability for account administration (the read side stays on the mandatory ListUsers). Builders quote account parts internally — MySQL as string literals, PostgreSQL as identifiers — and return complete statements for Connection.ExecScript. Engines without an account system (SQLite) do not implement it and the UI hides the controls.

type UserSpec

type UserSpec struct {
	Name        string
	Host        string // MySQL account host part ('user'@'host'); ignored by role engines
	Password    string
	SetPassword bool // distinguishes "set this password (possibly blank)" from "leave unchanged"
	CanLogin    bool // PostgreSQL LOGIN attribute; MySQL accounts always log in
	Super       bool // PostgreSQL SUPERUSER
	CreateDB    bool // PostgreSQL CREATEDB
	CreateRole  bool // PostgreSQL CREATEROLE
}

UserSpec describes a database account to create or alter. Password is the plaintext the operator typed: builders emit it only through QuoteString and callers must never log, flash or render it (or any SQL embedding it).

type Value

type Value struct {
	Null   bool
	Binary bool
	Str    string
	Bytes  []byte // raw binary content; populated only by StreamResult
}

Value is one scanned cell. Null is rendered specially; Binary cells present a short placeholder in Str and carry the raw bytes (for hex export) only on the streaming path — buffered results drop them so a page of BLOBs cannot pin hundreds of megabytes.

func (Value) String

func (v Value) String() string

String returns the textual form (empty for NULL).

type ValueListTyper

type ValueListTyper interface {
	// ValueListTypes returns the base types requiring a value list, spelled as
	// they appear in ColumnTypes().
	ValueListTypes() []string
	// ValueListType assembles the type. base is an allowlisted ColumnTypes()
	// entry; values is the user's list, non-empty, in order. It returns an
	// error for a list this engine cannot accept (a duplicate, or more members
	// than the type allows).
	ValueListType(base string, values []string) (string, error)
}

ValueListTyper is the optional capability for column types defined by a LIST OF VALUES rather than a length or precision — MySQL's ENUM and SET. The column editor needs two things from the dialect to offer them: which of its ColumnTypes() entries take a value list, and how that list is spelled.

It is the dialect's job because the spelling is engine syntax. PostgreSQL has enumerated types but they are user-defined types (CREATE TYPE … AS ENUM), not an inline column type, and it has no SET at all; SQLite has neither. Both therefore omit this, and the editor's value-list control does not appear.

ValueListType is the ONLY place a user-supplied value reaches a type definition, so implementations MUST quote each value through QuoteString. The values arrive exactly as typed — they are data, not identifiers, and nothing upstream can allowlist them.

type ValueLiteralHooks

type ValueLiteralHooks struct {
	// BinaryLiteral spells a binary cell's raw bytes as an SQL literal.
	BinaryLiteral func(b []byte) string
	// TextSpecial, when non-nil, may short-circuit a non-null, non-binary
	// value with an engine literal (ok=true) before any numeric handling.
	TextSpecial func(col ResultColumn, s string) (lit string, ok bool)
	// NonFinite maps a NonFiniteFloat class ("nan", "+inf", "-inf") to the
	// engine literal. nil applies the fallback finite guard: a non-finite
	// token is not a valid bare numeric literal, so it is emitted as quoted
	// text.
	NonFinite func(class string) string
}

ValueLiteralHooks parameterize RenderValueLiteral with the two things the engines genuinely disagree on:

  • binary spelling: X'…' (MySQL/SQLite/fallback) vs '\x…' (PostgreSQL);
  • non-finite floats: NULL (MySQL, SQLite NaN), quoted tokens ('NaN'/'Infinity', PostgreSQL), overflowing exponents (SQLite ±Inf), or the fallback's quote-the-text finite guard.

TextSpecial is a PRE-PASS hook that runs on the text path BEFORE the numeric check (MySQL's zero-date sentinel lives on a temporal column's text path), while NonFinite runs INSIDE the numeric branch — two distinct insertion points by design.

type VersionFloor

type VersionFloor interface {
	// ServerBelowFloor reports the documented floor for the connected flavor, and
	// whether this server is older than it. An implementation that cannot parse
	// the version must answer false: guessing "too old" would cry wolf on every
	// unfamiliar build string.
	ServerBelowFloor() (floor string, below bool)
}

VersionFloor is implemented by a dialect that knows the oldest server release its introspection and DDL rely on. TableX used to perform no version check at all, so connecting something older degraded in place — a missing catalog column surfaced as an empty listing or a confusing error, at the point of use, with nothing pointing at the real cause.

It reports rather than refuses. An operator with an older server may still get most of the tool working, and locking them out over a documented feature floor would be a worse trade than telling them plainly. The check is per CONNECTION because that is the only place the server version is known: a specialized dialect has already read it.

type ViewDumper

type ViewDumper interface {
	DumpView(ctx context.Context, db *sql.DB, scope Scope, name string, withData bool) (DumpPlan, error)
}

ViewDumper is an optional Dialect capability: dump a SINGLE view or materialized view's restore-oriented DDL. A table-scope SQL export whose target is a view needs this because the schema-wide view pass inside DumpObjects runs only for a whole-database dump (dbScope) — without it, the table path emits a physical CREATE TABLE snapshot (PostgreSQL), errors (SQLite), or mis-scans the object (MySQL). The returned plan populates Views with the CREATE [MATERIALIZED] VIEW (plus its object/column comments and, for SQLite, the view's INSTEAD OF triggers); for a populated materialized view with withData set it appends a REFRESH to PostData. The caller emits no CREATE TABLE and no row INSERTs for the object.

Directories

Path Synopsis
Package drivertest is the reusable conformance suite for a TableX Dialect.
Package drivertest is the reusable conformance suite for a TableX Dialect.
Package mysql implements the TableX Dialect for MySQL and MariaDB.
Package mysql implements the TableX Dialect for MySQL and MariaDB.
Package postgres implements the TableX Dialect for PostgreSQL using the pure-Go jackc/pgx/v5 driver (via its database/sql adapter).
Package postgres implements the TableX Dialect for PostgreSQL using the pure-Go jackc/pgx/v5 driver (via its database/sql adapter).
Package sqlite implements the TableX Dialect for SQLite using the pure-Go modernc.org/sqlite driver (no CGo).
Package sqlite implements the TableX Dialect for SQLite using the pure-Go modernc.org/sqlite driver (no CGo).

Jump to

Keyboard shortcuts

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