Skip to content

C:\> Documentation

03

Connecting your database

Contents

Codepanion runs read-only queries against your production database so the agent can look up records, check statuses, and investigate data issues. We support SQL Server and PostgreSQL.

Good to know

Codepanion enforces read-only access on your connection automatically. For SQL Server it adds ApplicationIntent=ReadOnly; for PostgreSQL it sets default_transaction_read_only=on. You provide a standard connection string and we handle the rest.

SQL Server

Give us a standard connection string. Point it at a read replica if you have one, though it isn't required. We add ApplicationIntent=ReadOnly before connecting.

SQL Server connection string
Server=your-server.database.windows.net;Database=yourdb;User Id=codepanion_reader;Password=...;

Create a dedicated user with only SELECT on the tables the agent needs:

sql
CREATE USER codepanion_reader WITH PASSWORD = '...';
GRANT SELECT ON SCHEMA::dbo TO codepanion_reader;

PostgreSQL

Same idea. Give us a standard connection string; we set default_transaction_read_only=on at the session level. A read replica is ideal but not mandatory.

PostgreSQL connection string
Host=your-server.example.com;Database=yourdb;Username=codepanion_reader;Password=...;

Create a dedicated role with only SELECT on the schemas the agent should see:

sql
CREATE ROLE codepanion_reader WITH LOGIN PASSWORD '...';
GRANT USAGE ON SCHEMA public TO codepanion_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO codepanion_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO codepanion_reader;

Where to configure

Open Settings → Database Connections in the Codepanion app and paste your connection string. It's encrypted at rest in Azure Key Vault and only accessed by the agent at query time; Test Connection proves it works before you save.

One database connection per environment
Settings → Database Connections. Every connection is filed under an environment label and shows its provider and the last time it tested green.
One database connection per environment
Screenshot of One database connection per environment. Settings → Database Connections. Every connection is filed under an environment label and shows its provider and the last time it tested green.
Settings → Database Connections. Every connection is filed under an environment label and shows its provider and the last time it tested green.

Each connection is filed under an environment label of lowercase letters, digits and hyphens. If you only have one database, leave it on default and forget it exists; if you investigate across QA, staging and production, add one connection per label and the agent picks the right one per investigation. Providers can differ between them: SQL Server for one environment, PostgreSQL for another.

Kind is Static for the usual case: one fixed connection string. Choose Catalog instead when you're multi-tenant yourself and the real connection string has to be looked up per tenant from your own registry database; see catalog databases.

How queries are built

The agent never writes SQL. It emits a structured query: a table, the columns it wants, filters, ordering, an optional grouping with aggregates (including per-day, per-week and per-month buckets on a date column), an optional join, and a row limit. Codepanion checks every table and column name against the schema it discovered from your database, refuses anything that isn't there, and compiles the rest into parameterised SQL.

what the agent asked for
{
  "table": "payment_attempts",
  "columns": ["id", "status", "error_code", "region", "created_at"],
  "where": [
    { "column": "status", "op": "=", "value": "failed" },
    { "column": "region", "op": "=", "value": "eu-west-1", "logic": "and" }
  ],
  "limit": 50
}

becomes, on a SQL Server connection:

what your database ran
SELECT TOP 50 [payment_attempts].[id], [payment_attempts].[status],
  [payment_attempts].[error_code], [payment_attempts].[region],
  [payment_attempts].[created_at]
FROM [dbo].[payment_attempts]
WHERE [payment_attempts].[status] = @p0 AND [payment_attempts].[region] = @p1

Every identifier is quoted (brackets on SQL Server, double quotes on PostgreSQL) and the table is qualified with the schema that discovery found it in, so a login whose default schema differs cannot steer the query at a different object. The statement is sent as one line; it is wrapped here for reading.

Values travel as parameters, never spliced into the statement, so no string the model produced is ever executed as SQL. The shape of the query is fixed too: SELECT only, capped at 1000 rows and at a total result size, run under a short command timeout on a connection that is already read-only. There is no free-text SQL path for the agent to reach for, so there is nothing for injected text to turn into a statement of its own.

Seeing what ran

Nothing about the agent's access to your database is hidden. Every query appears in the investigation as a tool step, summarised in a line or two and opening onto the whole exchange: the environment it targeted, the structured query it sent, the SQL that was generated from it, the row count, and the rows that came back. An opened step is pictured under reading tool steps.

Computing without handing over the rows

Some questions need arithmetic over more rows than anyone wants in a conversation. The agent can send a result set straight to a sandbox in your own browser instead of into its own context, compute over it there, and keep only the answer. The model gets a description of the data rather than the data: a row count, the column names, and up to five sample rows. The full result set is never persisted into the investigation. How it works, and its limits, are in computing over results.

Query plans for slow lookups

When the agent runs a query it can also capture the database's estimated execution plan (SHOWPLAN_XML on SQL Server, EXPLAIN without ANALYZE on PostgreSQL), which is handy when an investigation is about a slow query. Estimated rather than actual on purpose: the statement is compiled but never executed, so capturing a plan cannot itself touch your data. Plan capture is best-effort and never blocks the result: queries run under a short timeout with a single polite retry, so the agent always gets its rows even if the plan can't be fetched.

Need help getting set up?

We do the first setup on a call with you: the CI step, the connection string, and a first investigation against your real code.