Database Plugin
The database plugin gives the agent general SQL tools — list connections, list
tables, describe a table, run a read-only query, and run a gated write — against
one or more configured databases. Each connection picks its own backend: a
native pure-Python driver or an external database CLI. Out-of-the-box engines:
Postgres, MySQL/MariaDB, Oracle, SQL Server (plus SQLite).
Switchable backends
A connection's mode decides how it talks to the database:
mode | Behaviour |
|---|---|
native | Use the engine's Python DBAPI driver. |
cli | Shell out to the engine's command-line tool. |
auto (default) | Native if the driver is importable, otherwise the CLI. |
| Engine | Native driver (extra) | CLI fallback |
|---|---|---|
| Postgres | pg8000 — pip install 'codumentor[postgres]' | psql |
| MySQL / MariaDB | PyMySQL — pip install 'codumentor[mysql]' | mysql |
| Oracle | oracledb (thin mode) — pip install 'codumentor[oracle]' | — |
| SQL Server | python-tds — pip install 'codumentor[mssql]' | go-sqlcmd |
| SQLite | stdlib sqlite3 | — |
pip install 'codumentor[db-all]' installs all four native drivers.
Why both native and CLI
All four native drivers are pure-Python (no system libraries: no libpq, no
Oracle Instant Client, no ODBC/FreeTDS), so they install cleanly and bundle into
the frozen PyInstaller build — the packaged app ships with native support for
every engine. They are imported lazily, so the plugin loads even when a
driver is absent, and mode: auto degrades to the CLI.
The CLI path is the escape hatch: it needs no Python driver at all (you install
or download the CLI separately), and it handles cases the pure-Python drivers
don't — most notably SQL Server with Azure AD / TDS 8.0, where python-tds
is weak and go-sqlcmd (a single static MIT binary) is the better choice.
oracledb thin mode covers Oracle fully, so no Oracle CLI ships.
Native connections support bound
params; CLI connections do not (use
mode: nativefor parameterized queries).
Overview
Five tools, registered via onPromptAssemble (gated by target_agents):
| Tool | Approval | Purpose |
|---|---|---|
sql_list_connections | none | List configured connections (name, engine, host, database, read-only). Touches no database. |
sql_list_tables | none | Explore tables/views. Optional schema and pattern (case-insensitive name substring) filters; see below. |
sql_describe_table | none | Column metadata (name, type, nullability, default). |
sql_query | none | Run a read-only statement; rejects anything that isn't a single SELECT/WITH/EXPLAIN. Row-capped; results are JSON. |
sql_execute | database.execute | Run a write/DDL statement. Permission-gated, and refused on read-only connections (no prompt). |
When exactly one connection is configured, the connection argument is
optional everywhere and defaults to it.
sql_query results come back as a JSON envelope — {"columns": [...], "rows": — rather than an ASCII/markdown
[...], "row_count": N, "truncated": bool}
table (which can trip the LLM server's repetition detection). When truncated
is true, narrow the query.
Schema exploration scales down, not up
sql_list_tables adapts its output so a huge catalog can never silently
swallow schemas (a 2,500-table SQL Server catalog is ~127 KB of JSON — the
content store would char-truncate it mid-schema and the agent would report
whatever schemas happened to sort first):
- Unfiltered, small catalog (≤ ~200 objects): names, grouped per schema —
{"schemas": {"main": {"tables": [...], "views": [...]}}, "total": N}. - Unfiltered, large catalog: per-schema counts only —
{"schemas": {"dbo": {"tables": 1953, "views": 170}, ...}, "hint": ...}— with a hint to drill in viaschema=and/orpattern=. - Filtered (
schemaand/orpattern): names, capped atmax_rowswith an explicit"truncated": true, "returned": K, "total": Nwhen the filter is still too broad.
The intended flow on big databases is therefore: counts overview → drill into
one schema, or search by name fragment (pattern: "kivonat").
Configuration
plugins:
- module: codumentor.plugins.database
class: DatabasePlugin
args:
enabled: true
target_agents: all # main | subagent | all
default_read_only: true # per-connection default
max_rows: 1000 # hard cap for sql_query
statement_timeout: 30 # seconds
connections:
- name: analytics # logical handle the agent uses
engine: postgres # postgres | mysql | oracle | sqlserver | sqlite
mode: auto # native | cli | auto
host: db.example.com
port: 5432
database: analytics
user: reporting
password_secret: pg_analytics_password # per-user secret name
read_only: true
- name: warehouse
engine: sqlserver
mode: cli # go-sqlcmd
host: mssql.example.com
port: 1433
database: warehouse
user: svc_reader
password_secret: mssql_warehouse_password
read_only: true
dependencies: [user_secrets] — passwords resolve through the per-user secrets
registry, so the user_secrets plugin must be loaded.
Credentials
Two models are supported, per connection:
- Per-user secret (preferred) — set
password_secret: <name>. The config stores only the secret name; each user supplies their own password in Settings → User Secrets, and tools authenticate as the calling user. The plugin declares eachpassword_secretto the User Secrets pane viaui_manifest()so it shows "DB password (<connection>) — needed by Databases". See Secret requirements. - Shared service account — set
password: ${secret:<name>}(a shared secret resolved server-side). Useful for a read-only reporting replica with a single account.
If a password_secret is declared but unset, tools return a clear,
non-retryable message naming the secret — no connection attempt is made. A
connection with neither field resolves to "no password" (works for SQLite,
trusted/integrated auth, etc.).
Safety
- Read-only by default, enforced in depth: the
sql_querykeyword guard (singleSELECT/WITH/EXPLAIN, no multiple statements) plus a session-level read-only setting on the connection where the engine supports it (SET default_transaction_read_only=onfor Postgres,SET SESSION TRANSACTION READ ONLYfor MySQL,PRAGMA query_onlyfor SQLite). The session setting is the real guarantee — the keyword guard is only a fast fail. Oracle and SQL Server rely on the guard plus the login's privileges, so grant least privilege there. - Writes are gated.
sql_executeflows through the runtime'sPermissionProvider(database.execute) and is refused outright on a read-only connection. - Result caps.
limitis clamped tomax_rows; an extra sentinel row marks truncation; wide cells are truncated. - Statement timeouts applied natively and as a CLI wall-clock kill.
- Credential hygiene. CLI passwords go through an environment variable (
PGPASSWORD/MYSQL_PWD/SQLCMDPASSWORD), never the command line; SQL is passed on stdin; error text is secret-redacted.
Notes & limitations
- Frozen builds bundle the four native drivers via
scripts/build.spechiddenimports(pg8000,pymysql,oracledb,pytds, plus pg8000's pure deps); their only native transitive dependency,cryptography, is already bundled. - go-sqlcmd output: its
-sCSV does not quote/escape, so the CLI backend coerces results to JSON server-side withFOR JSON PATH. - CLI
params: not supported — usemode: nativefor bound parameters. - Oracle/SQL Server identifiers in
describe_tablefollow each engine's default case-folding (Oracle uppercases unquoted names).