Documentation
¶
Overview ¶
Package workspace is one person's working state against the databases, and the rules for changing it — with no UI in it.
A Workspace holds the active connection and its catalog, the session pinned to it, the run in flight, and — per connection it has been on — a result set: result tabs, the last statement, error and plan (results.go). The TUI (package tui) and the browser UI (dbc web) are both clients: they turn keys and clicks into calls here, and draw what comes back. The rules therefore exist once — ONE RUN AT A TIME, A PINNED SESSION per active connection, CANCEL REACHES THE SERVER (see run.go) — rather than as two copies that drift.
Starting work, and where its outcome lands ¶
Methods that start work (Connect, Run, Explain, RunScript) never block. They do the bookkeeping that must happen at once — claim the run slot, record history, bump a generation — and return a Start holding a Job: the blocking half, for the caller to run wherever its UI runs blocking work.
UI goroutine worker goroutine
──────────── ────────────────
st, err := w.Run(...) ── claims the slot, returns st.Job
err != nil → refused (busy, no connection, nothing to run)
log st.Notes
hand st.Job to a worker ─────────► ev := st.Job()
runs the statements
LANDS the outcome under w.mu:
frees the slot, sets last*
◄── returns ev (*RunDone)
draw ev (grid, status, log)
The TUI wraps a Job in a tea.Cmd, and the event comes back to Update as a message; the web layer runs it on a goroutine and writes the event to the workspace's SSE stream. The Job lands its own outcome before returning, so the workspace's state is right no matter when — or whether — a UI gets round to drawing the event.
Why a Job and not a channel of events: Bubble Tea already is an event loop with its own way of running blocking work (tea.Cmd), and the TUI's test harness drives it synchronously — press a key, run the command, feed its message back — so a workspace that ran its own goroutines would make every TUI test a race against a channel. Handing the blocking half back to the caller keeps both UIs in charge of their own concurrency.
Events that happen mid-run rather than at the end — a script's s.Show and s.Print — cannot be a Job's return value, and go to Options.Sink instead.
Locking ¶
mu guards the bookkeeping and is only ever held briefly. sessMu guards the pinned session and is held for as long as a statement runs on it — which may be minutes. The rule that keeps them from deadlocking: never take sessMu while holding mu. (Taking mu under sessMu is not needed today and is avoided too.)
Index ¶
- Constants
- func AttachColumns(ctx ai.Context, cols [][]db.Column, err error) (ai.Context, string)
- func Pick(ed Editor) (stmts []string, tag string)
- func PickAll(text string) (stmts []string, tag string)
- func PlanStatus(p *explain.Plan) string
- func Preview(stmt string) string
- func Project(src *model.Result, rows, cols []int) *model.Result
- func ResultStatus(r *model.Result, maxRows, shown int) string
- func SortRows(order []int, r *model.Result, col int, desc bool, numeric []bool)
- func StmtRange(text string, caret int) [2]int
- func WidestNumeric(r *model.Result, c int) int
- type Connected
- type Editor
- type Event
- type ExplainDone
- type GridView
- type Job
- type Level
- type Note
- type Options
- type Reason
- type Refusal
- type ResultTab
- type RowCounts
- type RunDone
- type SchemaLoaded
- type SchemaPick
- type ScriptPrint
- type ScriptShow
- type SessionReleased
- type Start
- type Workspace
- func (w *Workspace) Active() string
- func (w *Workspace) Busy() bool
- func (w *Workspace) CanShareResults() bool
- func (w *Workspace) Cancel() (n Note, status string)
- func (w *Workspace) CancelConnect() (Note, bool)
- func (w *Workspace) Catalog() *model.Result
- func (w *Workspace) CatalogSchema() string
- func (w *Workspace) ChatContext(question string, ed Editor, views ...GridView) (ctx ai.Context, refs []db.TableRef)
- func (w *Workspace) Close()
- func (w *Workspace) CloseResultTab(id int) error
- func (w *Workspace) CloseUnpinnedResultTabs() int
- func (w *Workspace) Complete(buffer string, caret int) (res sqlcomplete.Result, ready bool)
- func (w *Workspace) Config() *config.Config
- func (w *Workspace) Connect(name string) Start
- func (w *Workspace) ConnectPick(name string, pick SchemaPick) Start
- func (w *Workspace) Connecting() (name string, ok bool)
- func (w *Workspace) Databases() []db.DatabaseInfo
- func (w *Workspace) Derived(name string) bool
- func (w *Workspace) Diagram(ctx context.Context, sel erd.Selection) (*erd.Schema, error)
- func (w *Workspace) Disconnect() (left string, st Start, err error)
- func (w *Workspace) DropResults(conn string)
- func (w *Workspace) Explain(stmt, where string, analyze bool) (Start, error)
- func (w *Workspace) ExplainEditor(ed Editor, analyze bool) (Start, error)
- func (w *Workspace) History() *userdata.History
- func (w *Workspace) HistoryKey() string
- func (w *Workspace) LastErr() string
- func (w *Workspace) LastResult() *model.Result
- func (w *Workspace) LastResultSeq() (*model.Result, int)
- func (w *Workspace) LastStmt() string
- func (w *Workspace) ListTables() (Start, error)
- func (w *Workspace) LoadCompletions(ctx context.Context) error
- func (w *Workspace) LookupColumns(refs []db.TableRef) ([][]db.Column, error)
- func (w *Workspace) Manager() *db.Manager
- func (w *Workspace) PickSchema(pick SchemaPick) (Start, error)
- func (w *Workspace) PinResultTab(id int, pin bool) error
- func (w *Workspace) Plan() *explain.Plan
- func (w *Workspace) Recount(conn string) Job
- func (w *Workspace) Refresh() (Start, error)
- func (w *Workspace) Rename(buffer string, caret int, name string) ([]sqlcomplete.Edit, error)
- func (w *Workspace) RenameResults(from, to string)
- func (w *Workspace) ResultTabLimit() int
- func (w *Workspace) ResultTabs() (tabs []ResultTab, cur int)
- func (w *Workspace) RowCounts() map[db.TableRef]db.RowCount
- func (w *Workspace) RowCountsShown() bool
- func (w *Workspace) Run(stmts []string, tag string) (Start, error)
- func (w *Workspace) RunEditor(ed Editor, all bool) (Start, error)
- func (w *Workspace) RunProgress() (step, steps int)
- func (w *Workspace) RunScript(path string) (Start, error)
- func (w *Workspace) RunStmts(stmts []string, tag string) (Start, error)
- func (w *Workspace) RunTag() string
- func (w *Workspace) RunningStatus() string
- func (w *Workspace) Schemas() []db.SchemaInfo
- func (w *Workspace) ScriptChatContext(question, name, source string, views ...GridView) (ctx ai.Context, refs []db.TableRef)
- func (w *Workspace) ScriptResults() (n, at, cut int)
- func (w *Workspace) Session() (conn string, stateful bool)
- func (w *Workspace) ShareResultTab(id int, share bool) error
- func (w *Workspace) ShowColumns(qname string) (Start, error)
- func (w *Workspace) ShowResultTab(id int) error
- func (w *Workspace) ShowRowCounts(on bool) Job
- func (w *Workspace) ShowScriptResult(i int) error
- func (w *Workspace) Stop()
- func (w *Workspace) Switch(name string) Start
- func (w *Workspace) SwitchPick(name string, pick SchemaPick) Start
- func (w *Workspace) TableIndex() *db.TableIndex
- func (w *Workspace) Ticking(gen int) (status string, ok bool)
Constants ¶
const CompletionTimeout = DiagramTimeout
CompletionTimeout bounds reading a schema for completion. The ERD's bound: the same three catalog queries.
const DiagramTimeout = 30 * time.Second
DiagramTimeout bounds reading a schema for a diagram: three catalog queries, which take milliseconds on a normal database and seconds on a catalog of tens of thousands of tables. Longer than SchemaLookupTimeout, because here the user asked for the diagram and is waiting for it, while the assistant's lookup is a side errand that must not hold up a question.
const MaxSchemaTables = 8
MaxSchemaTables caps how many tables' columns go with one question. A query joining more than this is rare; a question whose words happen to name a dozen tables is not asking about all of them.
const MaxScriptResults = 20
MaxScriptResults caps how many of one script run's s.Show results are kept for a UI to switch between. A script that shows a result per loop pass could otherwise hold every one of them — each possibly max_rows rows — for as long as the tab lives. The newest are kept: the last result shown is usually the one the script was building toward.
const SchemaLookupTimeout = 3 * time.Second
SchemaLookupTimeout bounds the catalog query a question's tables cost at send time. Past it the question goes without columns rather than waiting on a busy database: the model answering a little less precisely beats the user watching a spinner for a lookup they never asked for.
Variables ¶
This section is empty.
Functions ¶
func AttachColumns ¶
AttachColumns puts a lookup's columns into the context a question was asked with. Only the tables the catalog could describe are kept, so the transcript's "sent: schema of …" names exactly the tables whose columns went. A failed lookup drops the schema entirely and returns the line the transcript shows about it: the question still goes, and the user is told why the model may be guessing column names rather than reading dbc's.
Both UIs call this, so the rule for what a failed or partial lookup sends cannot differ between the terminal and the browser.
func Pick ¶
Pick is what Ctrl+R executes: the selection's statements when there is a selection, otherwise the statement under the caret. tag names the run for the status bar and log: "query" when the buffer holds one statement, "statement 2/4" when the caret picked one of several, "selection" or "selection (3 statements)".
func PickAll ¶
PickAll is what Ctrl+Shift+R executes: every statement in the buffer, whatever the caret or selection says. A one-statement buffer is tagged "query", as Pick tags it, since "all" of one says nothing more.
func PlanStatus ¶
PlanStatus is the status-bar summary of a plan: "plan · 12 steps, cost 431 · 2 findings".
func Project ¶
Project builds a Result holding the given result rows (in the order given — a slice of a display order) and result columns (likewise), with Raw carried along — so a copied selection exports exactly like a whole result: NULLs stay NULLs, numbers stay right-aligned in HTML.
Only the listed columns are taken, so a column the grid hides is left out by not listing it: a copy takes what the user sees. That is what makes hiding useful for sharing — hide the noisy columns, then copy the table for Teams.
func ResultStatus ¶
ResultStatus is the status-bar summary of a result. shown is how many rows the UI displays (max_display_rows can cap it below len(r.Rows)).
func SortRows ¶
SortRows fills order with the display order of r's rows sorted by result column col (ascending, or descending with desc). col < 0 means the result's own order. numeric says which columns compare as numbers — pass export.NumericColumns(r); it is a parameter because a UI computes it once per result, and this may run on every header click.
NULLs sort LAST in both directions: "biggest first" should not open on a screen of nothing. A numeric column compares numbers, so 10 sorts after 9; anything else compares its text case-insensitively. The sort is stable, so rows that tie keep the result's order.
func StmtRange ¶
StmtRange is the byte range of the statement under the caret, for an editor's gutter marker — zero when the buffer holds fewer than two statements, since marking the only one says nothing.
func WidestNumeric ¶
WidestNumeric is the widest display value of numeric column c over EVERY row of r — for a column export.NumericColumns vouched for.
Auto-sizing measures only a grid's first few hundred rows (a text column's width costs a grapheme walk per value, and a huge result should not pay that on arrival), which is fine for text, whose width varies without pattern, and wrong for numbers, which grow: an id column sized by rows 1–500 shows "10…" at row 1,000. A number's display text is plain ASCII, so its width is len() — no decoding — and scanning every row is a length read per cell. NULLs count as their "NULL" text, which is how both grids draw them.
The widest text is measured rather than derived from the column's min/max: a float's text is not monotonic in its value (0.123456789 is wider than 1000), and a length scan is as cheap as a comparison.
Types ¶
type Connected ¶
type Connected struct {
Name string
Catalog *model.Result // the connection's tables; nil if the catalog query failed
// Databases and Schemas are the levels above the tables: the server's
// databases, on a driver with a database picker (db.HasDatabases:
// Postgres, MySQL), and this database's schemas with their table
// counts, on one whose sidebar is loaded a schema at a time
// (db.Navigable: Postgres). Schema is the one whose tables Catalog
// holds, "" for every schema's. Each is empty on the other drivers,
// and Databases or Schemas is nil when its listing failed.
Databases []db.DatabaseInfo
Schemas []db.SchemaInfo
Schema string
Err error // the connect's failure (db.ErrCanceled when stopped); nil on success
Changed bool // the active connection moved to Name
// Left is the connection Changed moved away from, "" when there was
// none (the first connect, or one after a Disconnect). A UI that knows
// no other workspace is on it may close its pool once Release has run
// — see Workspace.Derived for the one kind worth closing.
Left string
// Stale reports a connect superseded by a newer one: nothing landed,
// and a UI draws nothing.
Stale bool
Notes []Note
Status string // for a status bar; "" leaves it
// Release, when non-nil, closes the session pinned to the connection
// just left — rolling back what it left open. It is a separate Job
// because it waits for any statement still running on that session,
// and the switch itself must not.
Release Job
// Counts, when non-nil, fetches the row counts of the catalog's tables
// for the sidebar; its event is a *RowCounts. It is separate so the
// tables list draws at once and the numbers fill in after — a count
// reads a table's rows, and the list must never wait for that. nil
// while the sidebar's counts are off (Workspace.ShowRowCounts).
Counts Job
}
Connected lands a connect.
type Editor ¶
Editor is what a UI's SQL editor holds at the moment of a request: the buffer, the caret as a byte offset into it, and the selected text ("" when nothing is selected). The UI sends the caret and the workspace picks the statement, so "the statement under the caret" means the same thing in every UI.
type Event ¶
type Event interface {
// contains filtered or unexported methods
}
Event is something that happened in the workspace. The concrete types are the pointers below.
type ExplainDone ¶
type ExplainDone struct {
Tag string
Conn string
Stmt string
Plan *explain.Plan
Err error
Elapsed time.Duration
Stale bool
Notes []Note
Status string // as RunDone's: set on failure only
}
ExplainDone lands an explain.
type GridView ¶
type GridView struct {
Result *model.Result // the result the grid holds
Hidden []int // result columns hidden in the grid, ascending
SortCol int // the result column sorted on; -1 for the result's own order
SortDesc bool
Order []int // display row → result row; read, never kept
}
GridView is how a UI's grid shows the result it holds — presentation, so it stays with each UI, but the assistant's data rule needs it: rows go in the order the user sees them, and hidden columns stay out.
type Job ¶
type Job func() Event
Job is the blocking half of a request: it does the work, lands the outcome in the workspace, and returns an Event describing what landed (nil when there is nothing to tell). Run it off the UI goroutine.
type Level ¶
type Level int
Level is how a Note should be shown — the TUI's log colors, the web log's classes.
type Note ¶
Note is one line for the user's log. The workspace writes the words, so a busy refusal or a stopped run reads the same in the terminal and the browser.
type Options ¶
type Options struct {
// Sink receives the events that happen mid-run, from the run's own
// goroutine: a script's ScriptShow and ScriptPrint. It must not block
// for long, and must not call back into the Workspace's start methods
// synchronously. nil drops them.
Sink func(Event)
// WholeCatalog is for a UI with no schema picker (the TUI): a connect
// that names no schema lists every schema's tables, as long as the
// database has at most db.AllSchemasLimit of them, instead of opening
// on the search_path's first schema. Past the limit it opens on that
// schema all the same, with a note: listing a huge catalog is the cost
// the per-schema load exists to avoid.
WholeCatalog bool
}
Options configure New beyond the config.
type Reason ¶
type Reason int
Reason is why a request was refused — what the web layer maps to a status code (Busy → 409, the rest → 400).
type Refusal ¶
Refusal is a request the workspace declined, with the reason in words for the user. It is an error, but not a failure: nothing was attempted, and nothing about the workspace changed.
type ResultTab ¶ added in v0.9.0
type ResultTab struct {
ID int // stable for the tab's life: what ShowResultTab and the rest take
Seq int // numbers the result on it; moves whenever that result does
Title string // what produced it, one line: a statement's preview, "preview cats", "script x.go"
Stmt string // the whole statement, for a tooltip ("" for a script's)
Pinned bool
// Result is the result the tab shows. Shared: read it, never write it.
Result *model.Result
Shows int // how many s.Show results of a script it holds (0 for a run's own)
}
ResultTab describes one tab of the active connection's result set, for a UI's strip.
type RowCounts ¶ added in v0.3.0
type RowCounts struct {
Conn string
Counts map[db.TableRef]db.RowCount // read-only; see db.Manager.RowCounts
// Stale reports counts for a connection that is no longer the active
// one, or a counting a later connect canceled: nothing landed, and a UI
// draws nothing.
Stale bool
Notes []Note
}
RowCounts lands the row counts of the active connection's tables. The counts are also kept in the workspace (see Workspace.RowCounts), so a UI that redraws its tables list later finds them there.
type RunDone ¶
type RunDone struct {
Tag string
Conn string
Stmts []string // what ran; nil for a script
Result *model.Result // the last statement's result; nil on failure and for a script
Plan *explain.Plan // Result is itself a query plan (an EXPLAIN the user typed)
Err error // the failure, or db.ErrCanceled when stopped
Script bool
Elapsed time.Duration
Stale bool // a straggler from a run already written off: draw nothing
Notes []Note
// Status is for the status bar when the run failed or stopped. On
// success it is "": the summary depends on how many rows the UI shows
// (see ResultStatus).
Status string
// Wrote reports that the run may have changed rows (db.ChangesRows of
// a statement it reached, or a script), failed or not. A UI with other
// workspaces on the same Manager recounts their sidebars on it
// (Workspace.Recount); Counts alone would not tell it, being nil
// whenever this workspace's own counts are off.
Wrote bool
// Counts, when non-nil, refreshes the sidebar's row counts: the run
// Wrote, and the sidebar shows counts (Workspace.ShowRowCounts). Its
// event is a *RowCounts, as Connected.Counts's is.
Counts Job
}
RunDone lands a run of statements, or of a script.
type SchemaLoaded ¶ added in v0.3.0
type SchemaLoaded struct {
Conn string
Schema string // the schema loaded; "" for every schema
Catalog *model.Result // its tables; nil when the load failed, and the old list stays
// Stale reports a load superseded by a later pick or connect: nothing
// landed, and a UI draws nothing.
Stale bool
Notes []Note
// Counts is the row counting of the new tables, as on Connected.
Counts Job
}
SchemaLoaded lands a schema pick (Workspace.PickSchema): the tables of another schema of the active database, in place of the ones the sidebar listed.
type SchemaPick ¶ added in v0.3.0
type SchemaPick struct {
// Name is the schema to list. One the database no longer has falls
// back to the default rather than to an empty list.
Name string
// All lists every schema's tables, where the database has at most
// db.AllSchemasLimit of them; past that, the default schema, with a
// note saying why.
All bool
}
SchemaPick says which tables a sidebar on a db.Navigable driver lists (Postgres: see db/navigate.go). The zero value is the default: the first schema on search_path, which is what a bare table name means there.
type ScriptPrint ¶
ScriptPrint is a script's s.Print, mid-run: a line for the log of Conn, the connection the script run started on.
type ScriptShow ¶
ScriptShow is a script's s.Show, mid-run: a result to put in the grid — when Conn, the connection the script run started on (whose result set it landed in), is still the active one.
type SessionReleased ¶
type SessionReleased struct {
Conn string
Stateful bool // it held state, now gone
Notes []Note // a warning when it did
}
SessionReleased reports that switching connections closed the session pinned to the previous one.
type Start ¶
type Start struct {
Tag string // names the run: "query", "statement 2/4", "explain", …
Gen int // the run generation, for a ticker (see Ticking)
Notes []Note // what to log now — present even when the request was refused
Job Job // the blocking half; nil when refused or when there is nothing to do
}
Start is what a start method returns.
type Workspace ¶
type Workspace struct {
// contains filtered or unexported fields
}
Workspace is one person's working state. Its methods are safe for concurrent use.
func New ¶
New builds a workspace over cfg's connections. It does no IO: the first connect is the caller's Connect(w.Active()).
The active connection starts as the config's default_connection, or the first connection when that names none — so a UI can draw it before the connect completes.
func (*Workspace) Busy ¶
Busy reports whether a run (statements, an explain or a script) is in flight.
func (*Workspace) CanShareResults ¶ added in v0.9.0
CanShareResults reports whether the active connection lets the assistant see result rows (ai_rows), which is what sharing a result tab needs — for a UI to offer "Share with the assistant", or say why not. The config is read live: dbc web can change ai_rows on a connection at any time.
func (*Workspace) Cancel ¶
Cancel is Ctrl+K and the Stop button: it stops the run in flight or, with none, the connect in flight. The Note says what it did; status is for a status bar ("" leaves it). The stopped work still lands, as stopped.
func (*Workspace) CancelConnect ¶
CancelConnect abandons the connect in flight. It reports false when there is none. The connect still lands, as canceled.
func (*Workspace) Catalog ¶
Catalog is the active connection's tables, as its catalog query returned them; nil before the first connect completes, or when the query failed.
func (*Workspace) CatalogSchema ¶ added in v0.3.0
CatalogSchema is the schema whose tables Catalog holds, or "" when it holds every schema's (always, on a driver that is not db.Navigable).
func (*Workspace) ChatContext ¶
func (w *Workspace) ChatContext(question string, ed Editor, views ...GridView) (ctx ai.Context, refs []db.TableRef)
ChatContext gathers what the next question may carry — see package ai for what actually goes, which is decided by the connection's ai_rows.
Which statement is "this query": the one under the editor's caret, since that is what the user is looking at. When it is the statement that last ran, its result (or error) comes along; when the editor is empty, the last run's statement stands in.
question is the question being (or about to be) asked; its words, like the statement's, pick which tables' schema goes along. The Tables come back named but without columns — the caller looks those up (they cost a catalog query) — and refs is the same tables as the catalog knows them, for that lookup.
A result tab the user SHARED (ShareResultTab) changes which result goes: the shared one, whatever the caret is on, framed as shared (ai.Context Shared) — explicit beats implied. The statement under the caret still goes as "this query", with its error when it is the one that just failed; the shared result's own statement goes along to say what the rows are of, and names tables for the schema lookup too.
views are the UI's grids: the one on screen and, when the shared tab is not on screen, that tab's (its parked grid, or the page's snapshot of it). Each is matched to a result by GridView.Result, so passing one that matches nothing is harmless. A shared result with no matching view goes unsorted and with nothing hidden — a UI that kept a view for it must pass it, or columns the user hid would reach the model.
It is cheap enough to call every frame, as the TUI's context chip does.
func (*Workspace) Close ¶
func (w *Workspace) Close()
Close stops what is in flight and releases the pinned session, rolling back whatever it left open. It waits for a statement still running to return from its cancel.
func (*Workspace) CloseResultTab ¶ added in v0.9.0
CloseResultTab closes result tab id, pinned or not. When it was on screen, the tab to its right takes its place (its left, at the end of the strip), as a browser's tabs do; the last one closed leaves the pane empty. A run in flight that picked it lands in a new tab instead.
func (*Workspace) CloseUnpinnedResultTabs ¶ added in v0.9.0
CloseUnpinnedResultTabs closes every unpinned tab of the active connection's result set — except the one shared with the assistant, which the user is still using — and reports how many went. The current tab, if kept, stays current; otherwise the last kept tab comes on screen.
func (*Workspace) Complete ¶ added in v0.4.0
Complete suggests what to type at caret (a byte offset into buffer), from the active connection's cached schema. It never blocks on the database: ready is false when the schema is not cached yet, and the caller then runs LoadCompletions off its event loop and asks again. With no connection, or after a failed load, it is ready with the dialect's vocabulary alone.
func (*Workspace) Connect ¶
Connect opens the connection and loads its sidebar, opening on the default schema (see SchemaPick, Options.WholeCatalog).
func (*Workspace) ConnectPick ¶ added in v0.3.0
func (w *Workspace) ConnectPick(name string, pick SchemaPick) Start
ConnectPick opens the connection and fetches its catalog for a sidebar. The catalog goes through the pool, not the pinned session: it is the app's query, and must not land inside a transaction the user has open.
On a db.Navigable driver the catalog is three short reads rather than one long one — the server's databases, this database's schemas, and the tables of the one schema pick names — so a connect to a big server costs what its sidebar shows, not what the server holds. Each has its own budget (navListTimeout, schemaSummaryTimeout, catalogTimeout), and a schema list too slow to count its tables falls back to their names (listSchemas). Elsewhere it is the whole table list, as it always was — after, on MySQL, the server's databases (db.HasDatabases), for the database picker.
The connect runs under its own context, so Cancel can abandon it — without that, only connect_timeout bounds it, and with connect_timeout = "0" an unreachable host would hold "connecting…" until the OS gave up on the TCP connect, over a minute later. Starting a connect cancels any still in flight: the user has changed their mind, and the older one must not land after the newer one and switch the connection back.
pick A ──► connGen=1, dial A ───────────── (canceled) ──► lands Stale pick B ──► connGen=2, cancel A, dial B ─► lands, active = B
The Job's event is a *Connected.
func (*Workspace) Connecting ¶
Connecting reports whether a connect is in flight, and to what.
func (*Workspace) Databases ¶ added in v0.3.0
func (w *Workspace) Databases() []db.DatabaseInfo
Databases is the active connection's server's databases, for a database picker; nil on a driver without databases (db.HasDatabases), or when the listing failed. The slice is shared: read it, never write to it.
func (*Workspace) Derived ¶ added in v0.3.0
Derived reports whether name is a connection derived onto another of its base's databases ("<conn>/<database>", config.DatabaseSep) rather than a configured one.
It is what a UI asks of Connected.Left before closing the pool a switch left. A derived pool is the one worth closing: browsing a server's databases opens one per database picked, and without a close each stays open (its *sql.DB until dbc exits, its idle connections until conn_idle_timeout), so the count grows with the browsing rather than with the configured connections. A configured connection's pool is kept, as it always was: there is one per row of the Connections list, and switching back to it should not dial again.
func (*Workspace) Diagram ¶ added in v0.3.0
Diagram reads the active connection's schema and narrows it to sel — the entity-relationship diagram both UIs draw (the TUI saves and opens it, dbc web shows it and serves its files). It blocks for the catalog round trips, so a UI calls it off its event loop.
Like LookupColumns it runs on the POOL, not the pinned session: a diagram must not wait for a long query the user is running, nor read the schema from inside their open transaction.
It does not take the run slot either — reading the catalog is not a run, and a diagram while a query runs is a reasonable thing to want.
A table in sel that is not in the schema is refused (Invalid) rather than dropped quietly: a diagram "around orders" that silently shows the whole database would be a confusing answer.
A diagram of everything (sel naming no tables) is of the schema the sidebar lists, when it lists one (CatalogSchema): on a big database "all" means what the user is looking at, not every table the server holds.
A CATALOG TOO BIG TO READ WHOLE (db.ErrCatalogTooBig) is read SCOPED instead, on Postgres, as completion does (complete.go): Manager.SchemaIn over the schemas the selection can be about (diagramScope). The connection is then remembered as too big (complScoped, shared with completion — both read the same Manager.Schema, so one finding it overflow is the other's answer too), and later diagrams and completion loads skip the failing whole read.
whole (Manager.Schema) ──ok────────────────────────────► Select │ ErrCatalogTooBig (or complScoped[conn] already) ▼ complScoped[conn] = true ▼ scoped (Manager.SchemaIn: diagramScope) ──► Select
What a scoped diagram misses: a neighbour (sel.Depth) in a schema outside the scope, since SchemaIn leaves out keys to tables it did not read; and a bare name that is unique in the scope but not in the database resolves rather than being refused as ambiguous. Both are the price of drawing anything at all where the whole read cannot.
func (*Workspace) Disconnect ¶ added in v0.3.0
Disconnect leaves the active connection without picking another: no active connection, no catalog, and — through the Job — the pinned session closed, rolling back whatever it left open, as a switch does. A connect in flight is abandoned with it (connGen moves on, so it lands Stale), as is the sidebar's counting and schema loading. It returns the connection left: on a connect still dialing, the one being dialed when there was no connection before it.
A run in flight is refused rather than canceled: its session is the one the disconnect would close, and a Stop first leaves the user to decide what becomes of the statement. Nothing to disconnect from is refused too.
The pool is not this workspace's to close — other workspaces may be on the same connection (db.Manager is shared); a UI that knows they are not closes it with db.Manager.Disconnect once the Job has run.
The Job's event is a *SessionReleased, or nil when no session was open.
func (*Workspace) DropResults ¶ added in v0.9.0
DropResults forgets conn's result set — after the connection is removed, so its results do not stay alive in every query tab that visited it.
func (*Workspace) Explain ¶
Explain explains one statement on the active connection. where is how the statement was picked ("statement 2/4", "selection"), folded into the tag; "" or "query" adds nothing. The Job's event is an *ExplainDone.
func (*Workspace) ExplainEditor ¶
ExplainEditor is Ctrl+X (estimate) and Alt+X (analyze): explain the statement under the caret, or the selected one.
func (*Workspace) HistoryKey ¶ added in v0.8.0
HistoryKey is the active connection's database as history records it — what the history pickers (the TUI's Ctrl+P, dbc web's) scope to when they open on "this database".
func (*Workspace) LastErr ¶
LastErr is what the last run on the active connection failed with, "" if it worked. A stopped run leaves it alone: stopping is the user's choice, not a failure to explain.
func (*Workspace) LastResult ¶
LastResult is the result on screen: the active connection's current result tab's — a run's, or a script's s.Show. nil when that set has none.
func (*Workspace) LastResultSeq ¶ added in v0.9.0
LastResultSeq is LastResult with its seq (see results.go), which moves whenever the result on screen does — a run, a show, another result tab or connection — and only then. 0 with no result.
func (*Workspace) LastStmt ¶
LastStmt is the statement the last run on the active connection executed ("" before any, and after a script run: what a script ran is not known).
func (*Workspace) ListTables ¶
ListTables is Ctrl+T: the active driver's catalog query, through the same path a run takes, so it lands in the grid and exports like any result. It is the app's query, not the user's, so it is not recorded.
func (*Workspace) LoadCompletions ¶ added in v0.4.0
LoadCompletions reads the active connection's schema into the completion cache, unless it is cached already. It blocks for the catalog round trips (bounded by CompletionTimeout and ctx), so a UI calls it off its event loop. Callers that arrive while a load is in flight wait for that one rather than starting their own.
The error is the load's, for the UI to report once; completion goes on with the vocabulary alone either way.
func (*Workspace) LookupColumns ¶
LookupColumns fetches the columns of the tables a question involves (the refs ChatContext returned) on the active connection, under SchemaLookupTimeout. It blocks for a catalog round trip, so a UI calls it off its event loop; the result goes to AttachColumns.
It runs on the POOL, not the pinned session: the session may be mid-run, and a question about a slow query must not wait for that query to end.
func (*Workspace) PickSchema ¶ added in v0.3.0
func (w *Workspace) PickSchema(pick SchemaPick) (Start, error)
PickSchema lists another schema's tables in the sidebar, on the active connection, in place of the ones listed now. The tables load off the UI's event loop (the Job), like a connect's; the event is a *SchemaLoaded, with a Counts job for the new tables' row counts.
It shares connGen with Connect, so whichever of a pick and a connect starts later wins: a connect supersedes a pick in flight (another database's schemas are not this one's), and a pick supersedes the old list's counting and any earlier pick. A pick while a connect is still in flight is refused — the schema list it would pick from is being replaced.
pick sales ──► gen=5, load sales ───────────── (canceled) ─► Stale pick hr ──► gen=6, cancel sales, load hr ─► lands, schema = hr
func (*Workspace) PinResultTab ¶ added in v0.9.0
PinResultTab pins or unpins result tab id. A pinned tab keeps its result: the next run that would have replaced it opens a new tab.
func (*Workspace) Plan ¶
Plan is the active connection's last plan: an explain's, or one recognized in a result.
func (*Workspace) Recount ¶ added in v0.4.0
Recount refreshes the sidebar's row counts after another workspace's run on conn may have changed rows, or returns nil when this sidebar is not showing conn's tables. A UI that hosts several workspaces on one Manager (dbc web's query tabs and windows) calls it on the others when a RunDone reports Wrote, so every sidebar on the connection shows the write, not only the one that made it — whether or not the writer's own sidebar counts. It returns nil, too, when this sidebar's counts are off.
Unlike recountLocked it does not drop the Manager's cached counts: the writer's landRun already did, before its RunDone was delivered, so what is cached now was counted after the write. Dropping again would only make each workspace count afresh instead of sharing the writer's counting (db.Manager.RowCounts waits for one in flight).
func (*Workspace) Refresh ¶ added in v0.9.0
Refresh re-reads the active connection's catalog — the server's databases, this database's schemas, and the tables of the schema the sidebar lists now — for when another client has created, dropped or renamed something since the connect. It is the connect a Switch to the connection already active skips (SwitchPick), forced, and differs from one only in what it keeps:
- the session: the connection does not change, so there is no Release and a transaction open on the pinned session stays open;
- the schema pick: the sidebar comes back on the schema it lists now (refreshPickLocked), not the default one;
- the old list, when the re-read cannot list the tables: an empty sidebar would be no truer than the stale one (landConnect).
The Manager's cached row counts for the connection are dropped too, so a sidebar with its counts on counts afresh rather than serving numbers from before the change that prompted the refresh. The completion cache goes with the landing, as on any connect (landConnect).
It runs as a connect does — on the pool, under connGen, cancelable with Cancel — so a connect started after it supersedes it, and it supersedes a schema pick or a counting in flight. Refused with no active connection, and while a connect is still in flight (the catalog it would re-read is about to be replaced).
The Job's event is a *Connected, with Changed false.
func (*Workspace) Rename ¶ added in v0.7.0
Rename renames the alias or CTE under caret in buffer to name (sqlcomplete.Rename), quoting it as the active connection's dialect needs. It needs no schema, so unlike Complete it never waits on a load. The error is a sentence for the user: why there is nothing to rename.
func (*Workspace) RenameResults ¶ added in v0.9.0
RenameResults moves conn from's result sets to to, after a connection rename, so a query tab that visited the old name finds its results under the new one. A set already under to (a name reused) is replaced.
func (*Workspace) ResultTabLimit ¶ added in v0.9.0
ResultTabLimit is how many tabs one connection's result set holds (result_tabs).
func (*Workspace) ResultTabs ¶ added in v0.9.0
ResultTabs is the active connection's result set: its tabs in strip order, and the index of the one on screen (-1 when there is none).
func (*Workspace) RowCounts ¶ added in v0.3.0
RowCounts is the row count of each of the catalog's tables that has one (views never do); nil until the connect's counting lands. The map is shared: read it, never write to it.
func (*Workspace) RowCountsShown ¶ added in v0.9.0
RowCountsShown reports whether the sidebar's row counts are on (ShowRowCounts); they start off.
func (*Workspace) Run ¶
Run executes statements in order on the pinned session, stopping at the first failure, and publishes the last result. The Job's event is a *RunDone.
func (*Workspace) RunEditor ¶
RunEditor is Ctrl+R (all false) or Ctrl+Shift+R (all true) on what the editor holds.
func (*Workspace) RunProgress ¶
RunProgress is how far a multi-statement run has got: the statement executing now (1-based) and how many there are. steps is 0 when idle, and for work that is not a list of statements.
func (*Workspace) RunScript ¶
RunScript runs a Go script. Its s.Show and s.Print fire from the script's goroutine mid-run and go to the sink as ScriptShow and ScriptPrint; the Job's event is a *RunDone with Script set.
func (*Workspace) RunStmts ¶
RunStmts records statements in the history and runs them. It is how anything the user asked to run by hand gets run: Ctrl+R, run all, a table preview.
func (*Workspace) RunTag ¶
RunTag names the run in flight ("statement 2/4", "explain", "script x.go"); "" when idle.
func (*Workspace) RunningStatus ¶
RunningStatus is the status line for the run in flight, "" when idle.
func (*Workspace) Schemas ¶ added in v0.3.0
func (w *Workspace) Schemas() []db.SchemaInfo
Schemas is every schema of the active database, empty ones included, with its table count; nil on a driver that is not db.Navigable. The slice is shared: read it, never write to it.
func (*Workspace) ScriptChatContext ¶ added in v0.9.0
func (w *Workspace) ScriptChatContext(question, name, source string, views ...GridView) (ctx ai.Context, refs []db.TableRef)
ScriptChatContext is ChatContext for a script tab: the editor holds a Go script (name, its file name; source, the editor's text), not SQL.
What differs, and why:
- The whole source is "the query" (ctx.Script marks it as Go): a script is one program, and Pick's statement split would cut it at the first semicolon into something neither SQL nor Go.
- No plan: plans are of statements the editor explained, and a script tab explains nothing.
- Tables are still looked for, in the source and the question — the SQL a script runs is in its string literals, and a table named there is one the model should see the columns of.
- The last run's error and results go only when that run was THIS script: its s.Show results are what "why is this row here?" is about, while another script's (or a statement's) would be answered as if this one produced them.
The sdb API summary is not set here: it goes once per conversation, which only the assistant (the caller) knows — see ai.Context.ScriptAPI.
func (*Workspace) ScriptResults ¶ added in v0.9.0
ScriptResults describes the s.Show results held by the result tab on screen: n of them kept (0 for a tab holding a run's own result), the one the grid is on (at, 0-based), and cut, how many earlier shows were dropped to keep to MaxScriptResults — so a UI can number them as the script showed them (cut+1 … cut+n).
func (*Workspace) Session ¶
Session describes the pinned session: the connection it is pinned to ("" when none is) and whether it holds state a close would lose — an open transaction, a SET, a temp table. A UI shows the latter as a "transaction open" badge.
It takes sessMu, so it waits for a statement in flight to finish; call it from a UI only where that is acceptable (not every frame).
func (*Workspace) ShareResultTab ¶ added in v0.9.0
ShareResultTab shares result tab id with the assistant, or stops sharing it. A shared tab's result goes with every question asked on its connection — in place of the result that would otherwise ride along with the statement under the caret — until it is unshared, closed, or another tab is shared (one at a time). The RESULT is what is shared: a shared tab keeps it as a pinned tab would (resultSet.kept), so the next run opens a new tab rather than change what the assistant sees.
Sharing is refused on a connection without ai_rows: result rows are the database's contents, and sending them is that connection's setting to grant (package ai's DATA RULE), not a menu's. Unsharing always works. A share made while ai_rows was on, then turned off, sends column names only, and the note says the rows were withheld (ai.Build).
func (*Workspace) ShowColumns ¶ added in v0.3.0
ShowColumns lists a table's information_schema.columns rows in the grid, where they can be read, sorted and copied like any result. qname is the name the sidebar shows for the table (see db.TableIndex.Lookup).
It goes through RunStmts, like a table preview: the statement is recorded in the history, because it is what the user would have typed and is the easiest way back to it (or to an edited SELECT * of it). It is not put in the editor, whose text is the user's.
Only a table in the active connection's catalog is accepted. The name is inlined into SQL, so a name from anywhere else is refused, not escaped and tried.
func (*Workspace) ShowResultTab ¶ added in v0.9.0
ShowResultTab puts result tab id on screen: its result becomes the last result, which is what the grid, copies, exports and the assistant read. Allowed mid-run — looking back at an earlier result while a slow query runs is what keeping them is for — since the run lands in the tab it picked when it started, not in whichever is on screen.
func (*Workspace) ShowRowCounts ¶ added in v0.9.0
ShowRowCounts turns the sidebar's row counts on or off. They start off: every count is an exact count(*), which reads the whole table (see db/rowcount.go), so it is the user's to ask for.
Turning them on returns the Job that counts the listed catalog, whose event is a *RowCounts as a connect's is; nil when nothing is listed yet (the next connect or schema pick counts, now that the switch is on). From then on every connect, schema pick and write recounts, as before.
Turning them off drops the counts and cancels a counting in flight; that one lands Stale (countGen moves on). It returns nil: there is nothing to wait for, and the caller redraws its list without numbers at once.
off ──ShowRowCounts(true)──► on ─► Counts job ─► *RowCounts on ──ShowRowCounts(false)─► off: rowCounts nil, in-flight → Stale
func (*Workspace) ShowScriptResult ¶ added in v0.9.0
ShowScriptResult puts the current result tab's script result i (0-based, among the ones kept) back on the grid: it becomes the last result, which is what the grid, copies, exports and the assistant all read. Refused while a run is in flight — a script still showing results would move the grid under the pick — and for an i out of range.
func (*Workspace) Stop ¶
func (w *Workspace) Stop()
Stop cancels the run, the connect and the row counting in flight, if any, without a word — the first step of quitting. The canceled work still lands its outcome.
func (*Workspace) Switch ¶
Switch is picking a connection: a connect with a line for the log, and nothing at all when name is already active and its catalog loaded.
func (*Workspace) SwitchPick ¶ added in v0.3.0
func (w *Workspace) SwitchPick(name string, pick SchemaPick) Start
SwitchPick is Switch, opening the sidebar on pick: a UI that remembers a schema per connection asks for it here, rather than loading the default schema's tables only to replace them with a PickSchema at once.
func (*Workspace) TableIndex ¶
func (w *Workspace) TableIndex() *db.TableIndex
TableIndex is the catalog indexed for name lookups; nil without one.