Introduction
AI agents become far more useful when they can interact directly with relational databases on behalf of users — answering questions like "What were last quarter's top-selling products?" or "Update this customer's shipping address" without a human writing SQL by hand. The challenge is giving an agent real database power while keeping it from executing destructive or unsafe queries. This lesson walks through the practical building blocks: letting an agent read your schema, generate SQL, validate that SQL, and only then run it against a real database.
Granting schema awareness to the agent
An agent cannot write correct SQL if it does not know what tables and columns exist. The standard technique is to feed the model a condensed description of the database schema as part of the system prompt or as a retrieved context block. For a PostgreSQL database, you can dump tables and columns using the information_schema, then trim the output before passing it to the model.
The returned rows become a text summary the agent can reference whenever it constructs a query. Without this, the model will hallucinate column names and fail repeatedly.
Generating and validating SQL
Once the agent has schema context, you ask it to produce a single SQL statement in response to the user request. Wrap that output in a parser so you can inspect it before execution. A robust pipeline does three things in order: (1) reject anything that is not a single statement, (2) reject any statement whose leading verb is on a denylist (DROP, TRUNCATE, DELETE without WHERE, UPDATE without WHERE, GRANT, etc.), and (3) run the statement with a read-only or parameter-limited connection depending on intent.
This validation layer runs *before* the SQL ever touches your database. It is the single most important safeguard in the whole pipeline.
Executing through a restricted connection
Even with validation, defense in depth matters. Connect to the database as a user whose permissions are already restricted: a read-only role for analytical questions, and a separate role with only the specific INSERT/UPDATE rights your app needs. Set a short statement timeout at the session level so runaway queries cannot lock a table for minutes.
Pair these timeouts with a row limit appended to generated SELECTs (for example, always add LIMIT 100 unless the user explicitly opts out) so the agent cannot pull a million-row table into memory and stall.
Common pitfalls
The most frequent failure is letting the agent echo raw SQL into chat without ever executing it — users then copy and run it themselves, and you lose audit trails. Another is trusting the model to "decide whether the query is safe" inside the same prompt; self-policing is unreliable, so always enforce validation in code. Finally, avoid passing the full schema of a huge database at once: the model burns context on tables it never needs, so filter to relevant tables per request or use embeddings to retrieve only the matching ones.
Summary
Connecting an agent to SQL is a layered problem. Start by exposing schema metadata so the model can write real SQL, then generate, parse, and validate every statement in code before execution. Back the validator with role-based permissions and connection-level timeouts so a mistake or a malicious prompt can never wipe a table. With those four layers — schema context, code-level validation, restricted roles, and timeouts — an agent can answer data questions and perform safe writes without putting your database at risk.
Lesson Checkpoint
Review what you learned and get feedback on your work.