cd /news/ai-agents/your-langchain-sql-agent-sends-the-m… · home › topics › ai-agents › article
[ARTICLE · art-142194] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Your LangChain SQL agent sends the model every table, including the ones that caller can't read

A developer built Schemagate, a LangChain retriever that filters database schema objects by the calling user's grants before any table information reaches the model, addressing the fact that LangChain's SQLDatabase.get_table_info() exposes everything the connection can see rather than what the individual caller may read. The retriever subclasses BaseRetriever and binds caller identity at construction time so a chain cannot omit it and silently fall back to the full schema. It supports Postgres, Oracle, MySQL and SQL Server, reading VPD policies on Oracle, and installs via pip as an optional LangChain extra.

by read2 min views1 publishedSep 30, 2026

A LangChain SQL agent hands the model your schema and asks it to write SQL. On a demo database with eight tables that is fine. On a real one it breaks in two ways at once.

The schema outgrows the context window. I wrote about this before, on a warehouse with 1,245 tables — the table listing alone did not fit, and describing every table with an LLM to help retrieval made it worse, not better.

And the model sees tables the caller is not allowed to read. This one is quieter and worse. SQLDatabase.get_table_info() returns what the connection can see, not what the person asking can see. If your app connects as one service account and serves twenty users, every user's agent gets the full schema: salaries, PII, all of it. The model may never write SQL against those tables. It was still told they exist, and their column names went into the prompt.

So I wrote a retriever.

from schemagate import Catalog, Principal
from schemagate.integrations.langchain import SchemagateRetriever

cat = Catalog().bootstrap("postgresql://localhost/app")

retriever = SchemagateRetriever(
    catalog=cat,
    top_k=6,
    principal=Principal("okta:jdoe", roles={"finance"}),
)

docs = retriever.invoke("revenue by month")

It subclasses BaseRetriever, so it drops into any chain that already takes a retriever. Each selected object comes back as one Document: the DDL in page_content, and the name, kind, score and selection reason in metadata.

Two things happen before the model sees anything. The caller's grants decide which objects are candidates at all. Then the question decides which of those are relevant. A table jdoe cannot read is not ranked and then filtered out — it never enters the ranking.

This is the part I would push back on in someone else's library, so here is the reasoning.

SchemagateRetriever(catalog=cat, principal=p)   # identity fixed here
retriever.invoke(question)                      # not here

A retriever is usually built once per request. Binding identity to the object means a chain cannot forget to pass it. There is no call signature where the principal is optional and quietly defaults to everything. If you want a different caller, build another retriever — they are cheap.

The alternative, invoke(question, principal=...), has one failure mode I did not want to ship: somebody omits the argument, the call still succeeds, and it returns the whole schema. Fail-closed beats convenient.

pip install 'schemagate[langchain]'

Needs langchain-core>=0.3, and it is an optional extra — if you do not use LangChain you do not pay for it.

Postgres, Oracle, MySQL and SQL Server. On Oracle it reads VPD policies rather than inferring from grants.

If you are running a SQL agent against a database where different callers should see different tables, I would like to know how you handle it today — especially if the answer is "one service account and we hope". That was the answer where I work, which is why this exists.

── more in #ai-agents 4 stories · sorted by recency
── more on @langchain 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/your-langchain-sql-a…] indexed:0 read:2min 2026-09-30 · —