handlers

package
v1.0.0-rc.1 Latest Latest
Warning

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

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

Documentation

Overview

Package handlers implements TableX's HTTP handlers — one file per feature area (auth, home, server, database, table, sql, navigation). Handlers resolve the active Connection from the session's UserContext, call the engine-neutral driver API, build a view model and render via internal/view. They never import a concrete database driver or concatenate user input into SQL.

Index

Constants

View Source
const (
	SSOStartPath    = "/auth/sso/start"
	SSOCallbackPath = "/auth/sso/callback"
)

The SSO gate's paths. Exported because three places name them — the route table, the middleware that must let them through, and the handlers that redirect to them — and three literals would drift apart. Both are GET: the provider completes the flow by redirecting the browser, and a redirect cannot be a POST.

View Source
const MaxImportBytes = 32 << 20 // 32 MiB

MaxImportBytes caps an import upload. The server package's route-aware limitBody applies this same cap to import routes *before* CSRF parses the multipart body, so the cap holds even on the no-JS path (token in the form field, not a header) where csrf would otherwise parse the whole body under the looser global limit first.

View Source
const MaxImportExpandedBytes = 4 * MaxImportBytes

MaxImportExpandedBytes bounds a compressed upload AFTER decompression. The upload cap is not itself a bound on the work: gzip's ratio is unbounded in principle, so 32 MiB of well-chosen input expands to many gigabytes — the classic decompression bomb — and the importer holds the whole script in memory. This constant is therefore a memory budget, not a formality.

It is deliberately larger than MaxImportBytes: a compressed upload that could only expand to its own uploaded size would make the option pointless, since the whole reason to gzip a dump is that it is much bigger than the wire.

Variables

This section is empty.

Functions

func BoundedParseForm

func BoundedParseForm(r *http.Request) error

BoundedParseForm parses the request form, dispatching on media type: multipart/form-data goes through ParseMultipartForm (with the small in-memory threshold above), everything else through ParseForm. This closes two gaps: ParseForm never reads a multipart body (so a later PostFormValue would trigger ParseMultipartForm under Go's 32 MiB default), and a prior successful ParseForm leaves r.PostForm non-nil so a multipart body's fields would silently read empty. It is idempotent (a cached form is reused). Exported so the csrf middleware can bound the FIRST parse of a no-JS (form-token) request before it extracts the token.

func RequestID

func RequestID(ctx context.Context) string

RequestID returns the request id, or "" if unset.

func WithListingMemo

func WithListingMemo(ctx context.Context) context.Context

WithListingMemo installs a fresh per-request listing memo (called by the server's request middleware alongside the request id).

func WithRequestID

func WithRequestID(ctx context.Context, id string) context.Context

WithRequestID stashes a request id in ctx (called by the logging middleware).

Types

type CounterSnapshot

type CounterSnapshot struct {
	LoginsOK        int64
	LoginsDenied    int64
	LoginsThrottled int64

	QueryBudgetRefused int64
}

CounterSnapshot is a consistent-enough read of the counters: each value is read atomically, but not all together, which is the right trade for monotonic counters a scraper differentiates anyway.

type Counters

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

Counters are the process-wide numbers only the handlers are in a position to see. The server package renders them at /metrics; it cannot count them itself, because a login rejection and a spent query budget are both invisible from the middleware (the first re-renders a form at 200, the second surfaces as one statement's error inside an otherwise ordinary response).

A nil *Counters is a valid disabled set — every method tolerates it — so the many tests that build a bare &Handlers{} do not have to wire one up.

func (*Counters) Snapshot

func (c *Counters) Snapshot() CounterSnapshot

Snapshot reads every counter. A nil receiver reads as all zeros.

type DBOpLimiter

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

DBOpLimiter caps how many requests may simultaneously hold a PRIVATE database connection — a streaming export, a SQL console script or a SQL import. Those deliberately open transient connections outside the cached-pool machinery (they must not share or evict a session pool), which put them outside PoolBudget as well; nothing else bounds them, and the server has no in-flight request cap, so enough parallel exports could exhaust the DATABASE's max_connections and take down every other client of that server.

It refuses rather than queues: a caller that waited would hold an HTTP worker and its session while contributing nothing, and the client has no way to know the request is parked. 503 + Retry-After is the honest answer.

func NewDBOpLimiter

func NewDBOpLimiter(n int) *DBOpLimiter

NewDBOpLimiter allows up to n concurrent private-connection operations; n <= 0 means unlimited.

func (*DBOpLimiter) InFlight

func (l *DBOpLimiter) InFlight() int

InFlight reports how many slots are currently held (0 when unlimited).

func (*DBOpLimiter) Limit

func (l *DBOpLimiter) Limit() int

Limit reports the concurrency ceiling, or 0 when unlimited.

func (*DBOpLimiter) Refused

func (l *DBOpLimiter) Refused() int64

Refused reports how many operations have been turned away since startup.

func (*DBOpLimiter) TryAcquire

func (l *DBOpLimiter) TryAcquire() (release func(), ok bool)

TryAcquire reserves a slot, returning a release func and true on success. The release func is idempotent, so `defer release()` is safe alongside an early explicit release. A nil limiter or an unlimited one always succeeds.

type Handlers

type Handlers struct {
	View     *view.Renderer
	Sessions *session.Manager
	Cfg      config.Config
	Rate     RateLimiter
	Log      *slog.Logger
	Version  string
	Pools    *PoolBudget      // process-wide per-database pool budget (nil = unlimited)
	DBOps    *DBOpLimiter     // in-flight cap on private-connection ops (nil = unlimited)
	Proxy    *auth.ProxyTrust // trusted-proxy set for client-IP resolution (nil = XFF untrusted)
	Audit    *audit.Logger    // audit trail (nil = not configured; every method is nil-safe)
	Counters *Counters        // /metrics counters the handlers own (nil = not counted; nil-safe)

	// SSO is the discovered OIDC provider, or nil when no gate is configured.
	// Nil is the ONLY signal the handlers use: the gate routes 404 and the
	// middleware lets everything through, so a deployment without [sso] behaves
	// exactly as it did before the feature existed.
	SSO *auth.OIDCProvider

	// AccountRate throttles failed logins per ACCOUNT NAME, across all source
	// addresses. Separate from Rate because it needs its own, higher threshold:
	// its key is shared by everyone typing that name, so reusing the per-IP cap
	// would let one person's typo lock out a whole team. Nil = disabled.
	AccountRate *auth.RateLimiter
}

Handlers carries the shared dependencies for every handler.

func (*Handlers) DBCreateTable

func (h *Handlers) DBCreateTable(w http.ResponseWriter, r *http.Request)

DBCreateTable renders (GET) and runs (POST) the create-table form. It mirrors DBOperations' dispatch shape: validate-first, build via the SchemaEditor, exec via ExecScript, redirect with a flash.

func (*Handlers) DBDesigner

func (h *Handlers) DBDesigner(w http.ResponseWriter, r *http.Request)

DBDesigner renders a read-only schema/relations map of the database: a card per table (columns with primary-key and foreign-key markers) plus a summary of every foreign-key relationship. It is a clean-room visualization built from the neutral introspection model — no .reference/ code is used.

func (*Handlers) DBEvents

func (h *Handlers) DBEvents(w http.ResponseWriter, r *http.Request)

DBEvents lists scheduled events (GET /db/{db}/events).

func (*Handlers) DBEventsManage

func (h *Handlers) DBEventsManage(w http.ResponseWriter, r *http.Request)

DBEventsManage handles POST /db/{db}/events.

func (*Handlers) DBExport

func (h *Handlers) DBExport(w http.ResponseWriter, r *http.Request)

func (*Handlers) DBImport

func (h *Handlers) DBImport(w http.ResponseWriter, r *http.Request)

func (*Handlers) DBOperations

func (h *Handlers) DBOperations(w http.ResponseWriter, r *http.Request)

DBOperations renders and runs database maintenance (create/drop database).

func (*Handlers) DBPrivileges

func (h *Handlers) DBPrivileges(w http.ResponseWriter, r *http.Request)

DBPrivileges lists the server's accounts and the database's direct grants (GET /db/{db}/privileges); with a PrivilegeManager engine it also renders the grant/revoke controls (POST handled by DBPrivilegesManage in access.go).

func (*Handlers) DBPrivilegesManage

func (h *Handlers) DBPrivilegesManage(w http.ResponseWriter, r *http.Request)

DBPrivilegesManage handles POST /db/{db}/privileges (database-scope grant/revoke).

func (*Handlers) DBQBE

func (h *Handlers) DBQBE(w http.ResponseWriter, r *http.Request)

DBQBE is a single-table query-by-example builder (GET form, POST run): pick a table, choose which columns to project and per-column criteria, and run a parameterized SELECT. It reuses the Search operator whitelist and the same parameter-binding/LIKE-escaping rules — no value is ever concatenated.

func (*Handlers) DBRoutines

func (h *Handlers) DBRoutines(w http.ResponseWriter, r *http.Request)

DBRoutines lists stored procedures/functions (GET /db/{db}/routines).

func (*Handlers) DBRoutinesManage

func (h *Handlers) DBRoutinesManage(w http.ResponseWriter, r *http.Request)

DBRoutinesManage handles POST /db/{db}/routines.

func (*Handlers) DBSQL

func (h *Handlers) DBSQL(w http.ResponseWriter, r *http.Request)

DBSQL is the database-level SQL console (GET/POST /db/{db}/sql).

func (*Handlers) DBSearch

func (h *Handlers) DBSearch(w http.ResponseWriter, r *http.Request)

DBSearch searches every table's text columns for a term (GET form, POST run), reporting per-table match counts.

func (*Handlers) DBStructure

func (h *Handlers) DBStructure(w http.ResponseWriter, r *http.Request)

DBStructure renders a database's Structure tab (GET /db/{db}). For PostgreSQL with no schema selected it lists schemas; otherwise it lists tables/views.

func (*Handlers) DBTriggers

func (h *Handlers) DBTriggers(w http.ResponseWriter, r *http.Request)

DBTriggers lists triggers in the database (GET /db/{db}/triggers).

func (*Handlers) DBTriggersManage

func (h *Handlers) DBTriggersManage(w http.ResponseWriter, r *http.Request)

DBTriggersManage handles POST /db/{db}/triggers.

func (*Handlers) EventEditor

func (h *Handlers) EventEditor(w http.ResponseWriter, r *http.Request)

func (*Handlers) Home

func (h *Handlers) Home(w http.ResponseWriter, r *http.Request)

Home renders the server overview (GET /). Requires an authenticated session (enforced by the auth gate middleware).

func (*Handlers) Login

func (h *Handlers) Login(w http.ResponseWriter, r *http.Request)

Login authenticates (POST /login) by opening a real database connection with the supplied (or predefined-server) parameters. On success it builds the session's UserContext, mints the authenticated session in one atomic step (fresh ID + CSRF — anti-fixation) and redirects home. Failures are rate-limited and reported generically.

func (*Handlers) LoginForm

func (h *Handlers) LoginForm(w http.ResponseWriter, r *http.Request)

LoginForm renders the login page (GET /login). Already-authenticated users are redirected home.

func (*Handlers) Logout

func (h *Handlers) Logout(w http.ResponseWriter, r *http.Request)

Logout destroys the session and closes its pools (POST /logout).

func (*Handlers) NavChildren

func (h *Handlers) NavChildren(w http.ResponseWriter, r *http.Request)

NavChildren is the htmx endpoint that lazily loads a node's children (GET /nav/children?db=&schema=).

func (*Handlers) NavTree

func (h *Handlers) NavTree(w http.ResponseWriter, r *http.Request)

NavTree is the htmx endpoint that re-renders the whole top-level database tree (GET /nav), used to refresh the sidebar after a structural change (create / drop / rename database or table) that only swapped #page_content.

func (*Handlers) ObjectDefinition

func (h *Handlers) ObjectDefinition(w http.ResponseWriter, r *http.Request)

ObjectDefinition serves one object's CREATE statement as an htmx fragment (GET /db/{db}/definition?kind=&name=&i=[&table=]). The object is addressed by list position plus name; that rule and its resolvers live in programs.go, shared with the drop actions.

func (*Handlers) RenderLoginThrottled

func (h *Handlers) RenderLoginThrottled(w http.ResponseWriter, r *http.Request)

RenderLoginThrottled renders the generic "too many login attempts" response from the csrf middleware's coarse bare-IP throttle, which fires BEFORE the body is parsed — so no posted sticky values (username/host/server) can be echoed back. It reuses loginThrottled, matching Login's own throttle rejection: a full-page caller gets the login form + HTTP 429 + Retry-After; an htmx caller gets HTTP 200 + HX-Redirect /login.

func (*Handlers) RenderRestricted

func (h *Handlers) RenderRestricted(w http.ResponseWriter, r *http.Request, msg string)

RenderRestricted answers a request refused by restricted mode (config [restrict]) with a 403 and an explanation naming the setting responsible. Exported because the enforcement is a middleware in the server package, which owns the policy but not the renderer.

func (*Handlers) RoutineEditor

func (h *Handlers) RoutineEditor(w http.ResponseWriter, r *http.Request)

RoutineEditor opens the definition editor, as TriggerEditor and EventEditor do for their kinds. Separate entry points rather than one kind= route, so each list page's link is a plain URL and the kind cannot be spoofed into administering another kind.

func (*Handlers) RoutinePrivileges

func (h *Handlers) RoutinePrivileges(w http.ResponseWriter, r *http.Request)

RoutinePrivileges lists the grants on one stored routine (GET /db/{db}/routines/privileges?name=…&i=…), with the grant/revoke controls on an engine that has routine grants (POST handled by RoutinePrivilegesManage in access.go).

It shares dbUsersBody and the db_privileges template with the database- and table-scope pages: same grants table, same grantee <select>, same revoke button — only the object and the allowlist differ.

func (*Handlers) RoutinePrivilegesManage

func (h *Handlers) RoutinePrivilegesManage(w http.ResponseWriter, r *http.Request)

RoutinePrivilegesManage handles POST /db/{db}/routines/privileges — grant and revoke EXECUTE (and MySQL's ALTER ROUTINE) on one stored routine.

It mirrors managePrivileges exactly where it can: same grantee decoding, same existence check, same "grant is validated against the allowlist, revoke against a fresh introspection" split. What it cannot share is the object — a routine is addressed by name AND position (and, on PostgreSQL, by the identity arguments only introspection can supply), so resolution goes through the stored-program addressing rule instead of a TableRef.

func (*Handlers) SSOCallback

func (h *Handlers) SSOCallback(w http.ResponseWriter, r *http.Request)

SSOCallback completes the flow. Every failure below lands on the same generic page: the differences matter to the operator's log, not to whoever is holding a bad callback.

func (*Handlers) SSOStart

func (h *Handlers) SSOStart(w http.ResponseWriter, r *http.Request)

SSOStart begins the authorization-code flow: mint a handshake, remember it on this browser's session, and redirect to the provider.

func (*Handlers) ServerDatabases

func (h *Handlers) ServerDatabases(w http.ResponseWriter, r *http.Request)

ServerDatabases renders the server-level database list (GET /server).

func (*Handlers) ServerDatabasesManage

func (h *Handlers) ServerDatabasesManage(w http.ResponseWriter, r *http.Request)

ServerDatabasesManage handles the server-scoped Create-database control on the databases list (POST /server).

func (*Handlers) ServerExport

func (h *Handlers) ServerExport(w http.ResponseWriter, r *http.Request)

ServerExport / DBExport / TableExport: GET renders the form, POST streams the download. Server scope dumps every (non-system) database.

func (*Handlers) ServerImport

func (h *Handlers) ServerImport(w http.ResponseWriter, r *http.Request)

ServerImport / DBImport / TableImport: GET renders the form, POST runs the SQL script (and, for tables, CSV import). Server scope runs the script against the server connection (so CREATE DATABASE / USE work).

func (*Handlers) ServerProcesses

func (h *Handlers) ServerProcesses(w http.ResponseWriter, r *http.Request)

ServerProcesses shows active processes/connections (GET /server/processes).

func (*Handlers) ServerProcessesManage

func (h *Handlers) ServerProcessesManage(w http.ResponseWriter, r *http.Request)

ServerProcessesManage handles POST /server/processes — terminating a session.

Validate-first, like every other administrative action here: the identifier must parse as an integer (which is also what keeps it out of the statement's syntax — see driver.ProcessManager), and it must appear in a FRESH read of the process list. Whether the account may actually kill that session is the engine's decision; TableX only refuses to name one nobody reported.

func (*Handlers) ServerSQL

func (h *Handlers) ServerSQL(w http.ResponseWriter, r *http.Request)

ServerSQL is the server-level SQL console (GET/POST /server/sql).

func (*Handlers) ServerStatus

func (h *Handlers) ServerStatus(w http.ResponseWriter, r *http.Request)

ServerStatus shows runtime status counters (GET /server/status).

func (*Handlers) ServerUsers

func (h *Handlers) ServerUsers(w http.ResponseWriter, r *http.Request)

ServerUsers lists database accounts/roles (GET /server/users).

func (*Handlers) ServerUsersManage

func (h *Handlers) ServerUsersManage(w http.ResponseWriter, r *http.Request)

ServerUsersManage handles POST /server/users: create_user, drop_user, set_password and (role engines) alter_attrs, dispatched on the action field like DBOperations. The account admin runs on the server connection.

func (*Handlers) ServerVariables

func (h *Handlers) ServerVariables(w http.ResponseWriter, r *http.Request)

ServerVariables shows configuration variables (GET /server/variables).

func (*Handlers) TableBrowse

func (h *Handlers) TableBrowse(w http.ResponseWriter, r *http.Request)

TableBrowse renders the Browse grid (GET /db/{db}/table/{table}).

func (*Handlers) TableBulkApply

func (h *Handlers) TableBulkApply(w http.ResponseWriter, r *http.Request)

TableBulkApply writes the multi-row form (POST .../rows/apply). Mode comes from a hidden field set when the form was RENDERED, not from the button that submitted it: the user already chose Edit or Copy in the browse grid, and a form that could switch between updating and duplicating on submit is one mis-click from doubling a table.

All-or-nothing: every statement runs in one transaction, so a failure halfway through rolls back rather than leaving half the selection written.

func (*Handlers) TableBulkRows

func (h *Handlers) TableBulkRows(w http.ResponseWriter, r *http.Request)

TableBulkRows dispatches a "with selected" action (POST .../rows). The action verb is a form field rather than a route segment because the browse grid is ONE form: a second submit button can add a verb, but it cannot change the form's method or re-check the boxes.

func (*Handlers) TableDelete

func (h *Handlers) TableDelete(w http.ResponseWriter, r *http.Request)

TableDelete deletes the selected rows (POST .../delete). Each token targets one row by its validated key, with values bound as parameters.

func (*Handlers) TableEdit

func (h *Handlers) TableEdit(w http.ResponseWriter, r *http.Request)

TableEdit performs the update (POST .../edit).

func (*Handlers) TableEditForm

func (h *Handlers) TableEditForm(w http.ResponseWriter, r *http.Request)

TableEditForm renders the edit form for one row (GET .../edit?where=token).

func (*Handlers) TableExport

func (h *Handlers) TableExport(w http.ResponseWriter, r *http.Request)

func (*Handlers) TableImport

func (h *Handlers) TableImport(w http.ResponseWriter, r *http.Request)

func (*Handlers) TableInsert

func (h *Handlers) TableInsert(w http.ResponseWriter, r *http.Request)

TableInsert performs the insert (POST .../insert).

func (*Handlers) TableInsertForm

func (h *Handlers) TableInsertForm(w http.ResponseWriter, r *http.Request)

TableInsertForm renders the insert form (GET .../insert). With ?copy=1&where= it prefills from an existing row.

func (*Handlers) TableOperations

func (h *Handlers) TableOperations(w http.ResponseWriter, r *http.Request)

TableOperations renders and runs table maintenance (rename/truncate/drop).

func (*Handlers) TablePrivileges

func (h *Handlers) TablePrivileges(w http.ResponseWriter, r *http.Request)

TablePrivileges lists the grants on a single table (GET .../privileges); with a PrivilegeManager engine it also renders the grant/revoke controls (POST handled by TablePrivilegesManage in access.go).

func (*Handlers) TablePrivilegesManage

func (h *Handlers) TablePrivilegesManage(w http.ResponseWriter, r *http.Request)

TablePrivilegesManage handles POST /db/{db}/table/{table}/privileges (table-scope grant/revoke).

func (*Handlers) TableSQL

func (h *Handlers) TableSQL(w http.ResponseWriter, r *http.Request)

TableSQL is the table-level SQL console (GET/POST /db/{db}/table/{t}/sql).

func (*Handlers) TableSearch

func (h *Handlers) TableSearch(w http.ResponseWriter, r *http.Request)

TableSearch searches a table by per-column conditions (GET form, POST run).

func (*Handlers) TableStructure

func (h *Handlers) TableStructure(w http.ResponseWriter, r *http.Request)

TableStructure serves the Structure tab. GET renders columns, indexes, foreign keys and the reconstructed/native CREATE statement; POST runs a structure-edit operation (add/modify/drop column, add/drop index, add/drop FK).

func (*Handlers) TableTriggers

func (h *Handlers) TableTriggers(w http.ResponseWriter, r *http.Request)

TableTriggers lists triggers, filtered to the current table (GET .../triggers).

func (*Handlers) TableTriggersManage

func (h *Handlers) TableTriggersManage(w http.ResponseWriter, r *http.Request)

TableTriggersManage handles POST /db/{db}/table/{table}/triggers. It shares manageProgram: the table filter comes from the path, which resolveScope already reads, so the database-level and per-table pages need no separate logic.

func (*Handlers) TriggerEditor

func (h *Handlers) TriggerEditor(w http.ResponseWriter, r *http.Request)

type PoolBudget

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

PoolBudget is the process-wide cap on cached per-database pools (config pool_cap), shared by every session. It bounds the unbounded dimension of connection growth: each session caches one pool (up to pool_max_conns connections) per database it touches, so without a budget N sessions × M databases would pile up pools indefinitely. The server-level login pools (one per session) are not counted — the session cap already bounds them — and the short-lived pinned/export connections are bounded by DBOpLimiter instead.

func NewPoolBudget

func NewPoolBudget(capacity int) *PoolBudget

NewPoolBudget builds a budget allowing up to capacity cached per-database pools across all sessions; capacity <= 0 means unlimited.

func (*PoolBudget) InUse

func (b *PoolBudget) InUse() int

InUse reports how many cached pools are currently charged to the budget, and Limit the ceiling (0 when unlimited). Both are for /metrics: pools approaching the cap is the leading indicator of the eviction pressure that makes browsing slow, and it is invisible from the outside.

func (*PoolBudget) Limit

func (b *PoolBudget) Limit() int

Limit reports the pool ceiling, or 0 when unlimited.

type RateLimiter

type RateLimiter interface {
	// Reserve atomically checks and consumes one attempt against every key —
	// a single critical section, so a concurrent burst cannot pass a separate
	// Allowed check before any Record lands.
	Reserve(keys ...string) bool
	Reset(key string)
}

RateLimiter is the subset of auth.RateLimiter the handlers use (kept as an interface-like alias so tests can substitute a no-op).

type UserContext

type UserContext struct {
	ServerID   string
	ServerName string
	Engine     string
	// contains filtered or unexported fields
}

UserContext is the per-session application payload. It owns the live database pools for one logged-in server connection, the user's preferences and the SQL console history. Credentials live here, in server-side memory only — never in the cookie, never logged, never persisted. Close() (wired to the session) releases every pool on logout or expiry.

func NewUserContext

func NewUserContext(serverID, serverName string, d driver.Dialect, base driver.ConnParams, serverConn *driver.Connection, budget *PoolBudget) *UserContext

NewUserContext builds a context around an already-opened server connection. budget may be nil (no pool cap).

d MUST be serverConn.Dialect() — the copy driver.Open routed through driver.Specialize, carrying the detected flavor, server version and sql_mode — and never the registered singleton returned by driver.Get. The singleton is the zero value, so every version/flavor gate reading uc.Dialect() fails closed: that is precisely how the SQL console came to classify MariaDB's `DELETE … RETURNING` as a non-row statement and discard its rows. (The parameter stays separate from serverConn because tests build connection-less contexts, and because a few deliberately pair a dialect with a stand-in connection of another engine.)

The dialect is fixed for the session's lifetime and read without uc.mu. A later rebindServerConn does NOT refresh it: the maintenance database it swaps to lives on the same server, so the specialization is identical, and a mutable field would race every lock-free uc.Dialect() reader.

func (*UserContext) AddFlash

func (uc *UserContext) AddFlash(f view.Flash)

AddFlash queues a flash message to show on the next full page render (used across HX-Location / 303 redirects, which start a fresh request).

func (*UserContext) AddHistory

func (uc *UserContext) AddHistory(q string)

AddHistory records a SQL statement in the per-session console history (most-recent first, de-duplicated, bounded in count and per-entry size).

func (*UserContext) Capabilities

func (uc *UserContext) Capabilities() driver.Capabilities

Capabilities returns the engine capability flags.

func (*UserContext) Close

func (uc *UserContext) Close()

Close releases every pool. Wired to the session's close hook. The closed flag makes every later (or in-flight, racing) ConnFor/PinnedFor/ExportConnFor fail with errSessionClosed instead of re-opening pools nobody would close; a dial already in flight sees the flag when it re-locks and discards its fresh pool.

func (*UserContext) ClosePool

func (uc *UserContext) ClosePool(database string)

ClosePool closes and forgets the cached per-database pool for database (if any), releasing its budget slot, and bumps the database's generation so an in-flight ConnFor dial for it discards its fresh pool instead of re-caching a pool (and re-leaking a slot) for a database that is being dropped. Safe on every engine.

func (*UserContext) ConnFor

func (uc *UserContext) ConnFor(ctx context.Context, database string) (*driver.Connection, error)

ConnFor returns a connection bound to the given database, opening and caching a pool if necessary. Engines whose databases all live on the one server connection (Capabilities().DatabasesShareConnection — SQLite's single file) and the login database reuse the server connection; other databases get their own pool — required for PostgreSQL (one DB per connection) and convenient for MySQL console ergonomics.

func (*UserContext) Dialect

func (uc *UserContext) Dialect() driver.Dialect

Dialect returns the engine dialect.

func (*UserContext) ExportConnFor

func (uc *UserContext) ExportConnFor(ctx context.Context, database string) (*driver.Connection, error)

ExportConnFor opens a dedicated, transient connection pool for a streaming export, bound to the given database (or the login database when empty). The caller owns it and must Close it when the export finishes — shared ConnFor pools must never be closed (they are cached per session). Dialect-owned export session pinning is applied via driver.ExportConnAdjuster (MySQL pins time_zone, sql_mode and sql_quote_show_create so dumps render restore-parsable); the hook clones the shared Params map before modifying.

func (*UserContext) History

func (uc *UserContext) History() []string

History returns a copy of the console history (most recent first).

func (*UserContext) PinnedFor

func (uc *UserContext) PinnedFor(ctx context.Context, database string) (*driver.Pinned, error)

PinnedFor opens a dedicated, transient single connection bound to the given database (or the login database when empty) for multi-statement script execution. Unlike ConnFor it never shares or caches: the caller owns the connection and must Close it when the script finishes, which discards any session state (SETs, PRAGMAs) the script established.

func (*UserContext) RowsPerPage

func (uc *UserContext) RowsPerPage() int

RowsPerPage returns the user's browse page size.

func (*UserContext) ServerConn

func (uc *UserContext) ServerConn() *driver.Connection

ServerConn returns the server-level connection (no specific database). The read is a lock-free atomic load: the pointer is normally set once, but a successful DROP DATABASE on the login database rebinds it (under uc.mu) to a maintenance-DB pool. After Close the underlying pool is closed and any query on it returns database/sql's ErrDBClosed — a clean error, not a leak (only ConnFor could re-open pools, and its closed check prevents that).

func (*UserContext) SetRowsPerPage

func (uc *UserContext) SetRowsPerPage(n int)

SetRowsPerPage updates the browse page-size preference, clamping it to an allowed option so a poisoned or out-of-range value cannot persist.

Jump to

Keyboard shortcuts

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