Documentation
¶
Overview ¶
Package database owns connections, statement classification, execution and schema introspection.
Index ¶
- Constants
- Variables
- func ConnectDB(ctx context.Context, dbType, dbSource string, pool PoolConfig) (*sqlx.DB, error)
- func NormalizeExportDialect(dialect string) (string, error)
- func Print(w io.Writer, res *Result) error
- func SplitComplete(sql string) ([]string, string)
- func SplitStatements(sql string) []string
- func ValidateDriver(dbType string) error
- func Write(w io.Writer, res *Result, format Format) error
- type Analysis
- type Column
- type ConnectionDef
- type ConnectionInfo
- type ExportOptions
- type ExportResult
- type ForeignKey
- type Format
- type Kind
- type Manager
- func (m *Manager) Close() error
- func (m *Manager) DB(ctx context.Context, name string) (*sqlx.DB, error)
- func (m *Manager) Def(name string) (ConnectionDef, error)
- func (m *Manager) List() []ConnectionInfo
- func (m *Manager) Names() []string
- func (m *Manager) Ping(ctx context.Context, name string) error
- type Permission
- type PoolConfig
- type QueryOptions
- type Result
- type StatementError
- type Table
- type TableDetail
Constants ¶
const ( // DefaultExportBatchSize is the number of rows emitted per INSERT statement. DefaultExportBatchSize = 100 // MaxExportBatchSize bounds memory use and SQL statement size. MaxExportBatchSize = 1000 // DefaultMaxExportRows bounds MCP exports when no explicit operator limit is set. DefaultMaxExportRows = 100000 )
const ( // CodePermissionDenied means the statement needs a higher permission level. CodePermissionDenied = "PERMISSION_DENIED" // CodeStatementRefused means the statement is refused at every level. CodeStatementRefused = "STATEMENT_REFUSED" )
Error codes are stable identifiers a client or model can branch on without depending on the exact wording of the message.
const DefaultMaxCellChars = 500
DefaultMaxCellChars caps the characters returned for a single text value.
A row cap alone does not bound the response: one TEXT or JSON column can hold megabytes, so `SELECT * FROM documents` can blow a model's context window in a single call even at a small row limit.
const DefaultMaxRows = 500
DefaultMaxRows caps the rows materialized for a single query when the caller does not ask for a specific limit. Results are buffered in memory, so an unbounded SELECT on a large table would otherwise blow up the process.
const DefaultPermission = PermissionReadOnly
DefaultPermission is used when nothing else is configured.
Variables ¶
var DefaultPool = PoolConfig{ MaxOpen: 3, MaxIdle: 3, MaxLifetime: 15 * time.Minute, }
DefaultPool is used for every field left at zero.
var Drivers = []string{"pgx", "odbc", "godror", "sqlite3", "sqlserver"}
Drivers are the database/sql driver names registered by the blank imports above.
var ErrUnknownConnection = errors.New("unknown connection")
ErrUnknownConnection is returned when a named connection is not configured.
var Formats = []Format{FormatTable, FormatJSON, FormatCSV}
Formats lists the accepted output formats.
Functions ¶
func NormalizeExportDialect ¶ added in v0.2.3
NormalizeExportDialect validates a target dialect and returns its canonical name.
func SplitComplete ¶ added in v0.2.2
SplitComplete returns the statements terminated by a top-level semicolon and the unterminated text after the last one, verbatim so a caller can keep buffering it. It is what an interactive reader needs: run what is complete, keep the rest.
func SplitStatements ¶ added in v0.2.2
SplitStatements cuts a script into statements at top-level semicolons.
It runs the same lexer the classifier uses, so a semicolon inside a string, a comment or a dollar-quoted block does not end a statement. Each returned statement keeps its terminating semicolon when it had one; blank pieces are dropped.
A semicolon counts when it is top-level under either lexical dialect. The splitter does not know which engine the text is bound for, and requiring agreement would leave a MySQL user's `SELECT 'it\'s';` waiting forever for a terminator. The classifier still refuses anything that reads as a batch under either dialect, so the choice affects only where input is cut.
func ValidateDriver ¶
ValidateDriver reports whether dbType is a driver dbq was built with.
database/sql would also reject an unknown driver, but only after the caller has handed over a connection string; checking up front keeps credentials out of driver-level error paths and gives a usable message.
Types ¶
type Analysis ¶
type Analysis struct {
// Kind is the statement category.
Kind Kind
// Unbounded marks a data-modifying statement whose blast radius cannot be
// shown to be limited: no WHERE clause, a predicate that is really a
// tautology, a multi-table target, or an INSERT that is not plain VALUES.
Unbounded bool
// Refused marks a statement that is rejected at every permission level,
// because no permission level can make it safe to run here.
Refused bool
// Reason explains Unbounded or Refused, in terms an operator and a model
// can both act on.
Reason string
}
Analysis is the result of inspecting one SQL statement.
func Analyze ¶
Analyze classifies a SQL statement and measures its blast radius.
The statement is read under every lexical dialect dbq supports and the most dangerous reading wins. That matters because the same text can be a single harmless read on one engine and a two-statement batch ending in DROP TABLE on another — `SELECT 'a\'; DROP TABLE users; --'` is one string literal to MySQL and two statements to PostgreSQL. Choosing a dialect from the connection type would make a misconfigured driver name into a bypass, so both are checked.
func Authorize ¶
func Authorize(connection, sql string, granted Permission) (Analysis, error)
Authorize checks a statement against a granted permission level.
It returns the analysis so the caller can decide between Query and Exec without re-parsing.
func (Analysis) RequiredPermission ¶
func (a Analysis) RequiredPermission() Permission
RequiredPermission is the minimum level needed to run the statement.
The level depends on blast radius, not only on category: a bounded `DELETE ... WHERE id = 1` needs safe-write, but `DELETE FROM users` with no effective filter needs full. Without that distinction "safe-write" would permit emptying every table, which is not what the name promises.
type Column ¶
type Column struct {
Name string `json:"name"`
Type string `json:"type"`
Nullable bool `json:"nullable"`
Default string `json:"default,omitempty"`
Position int `json:"position"`
PrimaryKey bool `json:"primary_key,omitempty"`
}
Column describes one column of a table.
type ConnectionDef ¶
type ConnectionDef struct {
Name string
Type string
Dialect string
Source string
Description string
Permission Permission
// Pool tunes the connection pool. Zero fields use DefaultPool.
Pool PoolConfig
}
ConnectionDef is a named connection profile.
func (ConnectionDef) CatalogType ¶ added in v0.2.2
func (d ConnectionDef) CatalogType() string
CatalogType returns the database dialect used for schema introspection. Most drivers identify their dialect directly; ODBC connections may override it.
type ConnectionInfo ¶
type ConnectionInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Dialect string `json:"dialect,omitempty"`
Description string `json:"description,omitempty"`
Permission Permission `json:"permission"`
}
ConnectionInfo is the redacted view of a connection, safe to return over the API or to an MCP client. It never carries the DSN.
type ExportOptions ¶ added in v0.2.3
type ExportOptions struct {
Dialect string
Table string
BatchSize int
MaxRows int
Timeout time.Duration
}
ExportOptions controls a streaming INSERT statement export.
type ExportResult ¶ added in v0.2.3
ExportResult describes an INSERT export without containing its data.
func ExportInserts ¶ added in v0.2.3
func ExportInserts(ctx context.Context, db *sqlx.DB, query string, w io.Writer, opts ExportOptions) (*ExportResult, error)
ExportInserts runs a read query and writes its rows as batched INSERT statements.
type ForeignKey ¶
type ForeignKey struct {
Column string `json:"column"`
ReferencedTable string `json:"referenced_table"`
// ReferencedColumn may be empty when the catalog does not expose it.
ReferencedColumn string `json:"referenced_column,omitempty"`
}
ForeignKey is a reference from one column to another table's column.
func ListForeignKeys ¶
func ListForeignKeys(ctx context.Context, db *sqlx.DB, driver, schema, table string) ([]ForeignKey, error)
ListForeignKeys returns the outgoing foreign keys of a table.
Without these a model writing a multi-table JOIN is guessing at join keys, which is the single most common way generated SQL goes wrong. Failure is not fatal to callers: a restricted account may not see the constraint catalog.
type Format ¶ added in v0.2.2
type Format string
Format is a CLI output rendering of a Result.
const ( // FormatTable is the human-readable ASCII table used by the REPL. FormatTable Format = "table" // FormatJSON renders a query result as an array of row objects, and an // exec result as a single object, for piping into jq. FormatJSON Format = "json" // FormatCSV renders a header line and one line per row. FormatCSV Format = "csv" )
func ParseFormat ¶ added in v0.2.2
ParseFormat normalizes a user-supplied format name.
type Kind ¶
type Kind string
Kind is the coarse classification of a SQL statement.
const ( // KindRead only reads: SELECT, SHOW, EXPLAIN, WITH ... SELECT. KindRead Kind = "read" // KindWrite modifies data: INSERT, UPDATE, DELETE, MERGE. KindWrite Kind = "write" // KindSchema modifies schema or privileges, or can touch the filesystem: // CREATE, DROP, ALTER, TRUNCATE, GRANT, COPY, SELECT ... INTO OUTFILE. KindSchema Kind = "schema" // KindSession mutates connection-local state: BEGIN, COMMIT, USE, SET. // Always refused, at every permission level — see Analysis. KindSession Kind = "session" // KindUnknown is a statement dbq could not classify. Requires full access. KindUnknown Kind = "unknown" )
func (Kind) ReturnsRows ¶
ReturnsRows reports whether a statement of this kind produces a result set, which decides between Query and Exec.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns the connection pools for every configured profile.
Pools are opened lazily on first use. The HTTP server explicitly pings every enabled connection at startup; other commands only open the one they use.
func NewManager ¶
func NewManager(defs []ConnectionDef) (*Manager, error)
NewManager builds a Manager from connection definitions. No network I/O happens here.
func (*Manager) Close ¶
Close shuts every open pool down. Errors are joined so one bad pool does not hide the others.
func (*Manager) Def ¶
func (m *Manager) Def(name string) (ConnectionDef, error)
Def returns the definition for name.
func (*Manager) List ¶
func (m *Manager) List() []ConnectionInfo
List returns the redacted view of every connection.
type Permission ¶
type Permission string
Permission is the access level granted to a caller for a connection.
The levels are ordered: PermissionReadOnly < PermissionSafeWrite < PermissionFull.
const ( // PermissionReadOnly allows only statements that cannot change data or schema. PermissionReadOnly Permission = "read-only" // PermissionSafeWrite additionally allows DML whose reach can be shown to be // bounded: an INSERT ... VALUES, or an UPDATE/DELETE with an effective // WHERE clause on a single table. PermissionSafeWrite Permission = "safe-write" // PermissionFull allows everything else: DDL, privilege changes, and writes // that could affect an unbounded number of rows. PermissionFull Permission = "full" )
func ParsePermission ¶
func ParsePermission(s string) (Permission, error)
ParsePermission normalizes a configured permission string.
Empty input yields DefaultPermission. The machine-readable aliases used by other MCP database servers (read_only, safe_write, high_risk_write) are accepted so client configurations can be carried over.
func (Permission) CanWrite ¶
func (p Permission) CanWrite() bool
CanWrite reports whether the permission allows any modifying statement.
func (Permission) Min ¶
func (p Permission) Min(other Permission) Permission
Min returns the least permissive of p and other, which is how a global ceiling is combined with a per-connection grant. Composition can only ever restrict, never widen.
func (Permission) Rank ¶
func (p Permission) Rank() int
Rank returns the ordering value of the permission, higher is more permissive.
func (Permission) Valid ¶
func (p Permission) Valid() bool
Valid reports whether p is a known permission level.
type PoolConfig ¶ added in v0.2.2
type PoolConfig struct {
// MaxOpen caps open connections, idle or in use.
MaxOpen int
// MaxIdle caps idle connections kept for reuse.
MaxIdle int
// MaxLifetime retires a connection after this long, so the pool follows
// DNS changes and failovers. Negative disables the limit.
MaxLifetime time.Duration
// MaxIdleTime closes a connection idle for this long. Negative disables it.
MaxIdleTime time.Duration
}
PoolConfig tunes the database/sql pool behind one connection.
Zero means "use the default"; a negative value means unlimited (for MaxIdle, which cannot be unlimited, it means "as many as MaxOpen"). Keeping the pool small by default is deliberate: dbq is usually one of many clients of a database, and an agent can issue queries far faster than a person.
func (PoolConfig) Merged ¶ added in v0.2.2
func (p PoolConfig) Merged(fallback PoolConfig) PoolConfig
Merged returns p with every zero field taken from fallback, which is how a connection's own settings sit on top of the global ones.
type QueryOptions ¶
type QueryOptions struct {
// MaxRows limits the rows read from the result set. Zero means DefaultMaxRows.
// A negative value disables the limit.
MaxRows int
// MaxCellChars limits the characters kept per text value. Zero means
// DefaultMaxCellChars. A negative value disables the limit.
MaxCellChars int
// Timeout bounds the statement. Zero means no dbq-imposed deadline.
Timeout time.Duration
}
QueryOptions tunes a single statement execution.
type Result ¶
type Result struct {
// Columns are the result set column names, in order. Nil for Exec results.
Columns []string `json:"columns,omitempty"`
// Rows holds the result set. Values are normalized to JSON-friendly types.
Rows [][]any `json:"rows,omitempty"`
// RowCount is len(Rows).
RowCount int `json:"row_count"`
// Truncated is true when the result set hit the row limit and more rows exist.
Truncated bool `json:"truncated,omitempty"`
// CellsTruncated counts values that were shortened to the cell limit.
// Non-zero means the displayed values are prefixes, not the stored data.
CellsTruncated int `json:"cells_truncated,omitempty"`
// RowsAffected is set for statements executed via Exec. Nil for queries.
RowsAffected *int64 `json:"rows_affected,omitempty"`
// Kind is how dbq classified the statement.
Kind Kind `json:"kind"`
// Duration is the wall-clock execution time.
Duration time.Duration `json:"-"`
// DurationMS is Duration in milliseconds, for JSON consumers.
DurationMS int64 `json:"duration_ms"`
}
Result is a driver-independent, JSON-serializable query result.
type StatementError ¶
type StatementError struct {
// Code is a stable machine-readable identifier.
Code string
// Connection is the profile the statement was aimed at.
Connection string
// Kind is how dbq classified the statement.
Kind Kind
// Granted and Required are the effective and needed permission levels.
// Required is empty when the statement is refused unconditionally.
Granted Permission
Required Permission
// Detail explains the specific problem, e.g. why a DELETE is unbounded.
Detail string
// Advice tells the caller what to do about it.
Advice string
}
StatementError is returned when a statement is rejected by the access policy.
func (*StatementError) Error ¶
func (e *StatementError) Error() string
Error deliberately never includes the SQL text. Errors flow to logs and to model context, and statements routinely carry data the caller should not have to think twice about echoing.
type Table ¶
type Table struct {
Schema string `json:"schema,omitempty"`
Name string `json:"name"`
Type string `json:"type,omitempty"`
}
Table is a table or view discovered by introspection.
func ListTables ¶
ListTables returns the tables and views visible to the connection.
schema is optional; when empty every non-system schema is returned. The queries are driver-specific because catalog layout is not portable: information_schema exists on PostgreSQL and SQL Server, SQLite has sqlite_master, and Oracle has the ALL_* views.
type TableDetail ¶
type TableDetail struct {
Schema string `json:"schema,omitempty"`
Name string `json:"name"`
// Type is BASE TABLE or VIEW. It matters: a model will otherwise happily
// generate an INSERT against a view.
Type string `json:"type,omitempty"`
Columns []Column `json:"columns"`
ForeignKeys []ForeignKey `json:"foreign_keys,omitempty"`
}
TableDetail is the full description of a single table.
func DescribeTable ¶
func DescribeTable(ctx context.Context, db *sqlx.DB, driver, schema, table string) (*TableDetail, error)
DescribeTable returns the column layout of a single table.