{"slug": "letting-ai-agents-query-production-databases-without-giving-them-a-database", "title": "Letting AI agents query production databases without giving them a database account", "summary": "A developer released Truejoin, an open-source (Apache 2.0) data gateway that lets AI agents query production PostgreSQL, MySQL/MariaDB, SQL Server and Oracle databases without granting them a database account. Truejoin rewrites each query before execution, replacing named tables with subqueries that carry the caller's row filters and column masks, so joins, CTEs and aggregates only ever read the restricted view. On 16 held-out questions over a banking-shaped schema with qwen2.5-coder:7b, the developer reports 12 correct answers and 0% hallucination using approved business terms with no model, versus 8 correct and 31.2% hallucination when the model wrote SQL from the DDL.", "body_md": "Once a company has an AI assistant, someone asks whether it can answer questions over production data. I do data consulting for companies that keep everything on-premises, and I kept hearing that question.\n\nThe usual answer is a read-only database account plus a text-to-SQL prompt. That leaves three things unsolved:\n\nI built [Truejoin](https://github.com/yasinyaman/truejoin) to close those three, on your own servers, with Oracle and SQL Server included.\n\nTruejoin is an open-source (Apache 2.0) data gateway. It connects to PostgreSQL, MySQL/MariaDB, SQL Server and Oracle, and serves them three ways from one Python process and one config file:\n\nEvery query, whoever wrote it, runs under the caller's row rules and column masks and leaves one audit event. It is not a warehouse, an ETL tool or a BI dashboard. It reads the databases you already have, live.\n\nFiltering the result is too late. An aggregate over rows the caller may not see has already leaked, and so has a masked column read through an alias, a join or a subquery.\n\nSo Truejoin rewrites the query before it runs. Each table the query names is replaced by a subquery that carries that caller's row conditions and masks. Aliases, joins, CTEs and aggregates then all read the restricted view, because there is nothing else to read.\n\nThe rules are a few lines of config (trimmed here; the full nesting is in `config/reference.yaml`):\n\n```\nrow_filters:\n  orders: [{column: tenant_id, value: \"${tenant}\"}]\nrow_filter_exempt_roles: [dpo]\n\nmasking:\n  enabled: true\n  rules: {email: partial, national_id: hash}\n```\n\nThree things follow from doing it this way:\n\nA model reads the schema and drafts table and column descriptions, PII labels, relationships and business terms (`truejoin catalog analyze`). Sample values are masked before they reach it, and with Ollama nothing leaves your network.\n\nA person then reviews, approves and certifies the draft. Nothing unapproved is used.\n\nThe approved catalog does two jobs. Masks key off its semantic types, and questions are read against its business terms.\n\n`answer_question` first reads a question against the approved vocabulary, with no model involved. It returns the request it understood and the SQL it compiled, so you can check what was asked before you trust the answer. Joins follow declared foreign keys, so a request cannot write a wrong join.\n\nOnly a question the vocabulary cannot read goes to a model, and that answer is marked as model-written.\n\nI benchmarked the approaches on a banking-shaped schema with `qwen2.5-coder:7b`, graded by result set against a gold query. On the 16 held-out questions:\n\n| Approach | Correct (of 16) | Hallucinated | \n|---|---|---|\n| Model writes SQL from the DDL | 8 | 31.2% | \n| Model writes SQL from the catalog | 10 | 18.8% | \n| Approved terms, no model | 12 | 0% | \n\nSixteen questions is a small set and the intervals are wide. The table in the repo shows 13 for the last row; one of those was right by luck and is now refused, so I count 12. The method and every caveat are in [bench/README.md](https://github.com/yasinyaman/truejoin/blob/main/bench/README.md).\n\nTruejoin merges three earlier projects of mine into one package and one process:\n\n`truejoin mcp convert`, which turns an OpenAPI or Postman description into an MCP server definition.\nTheir code moved over with its history. The query rewriter is the one component with no ancestor in the three, and it carries the privacy promise, so its tests were written first.\n\nYou need Python 3.12+ and [uv](https://docs.astral.sh/uv/).\n\n```\ngit clone https://github.com/yasinyaman/truejoin.git\ncd truejoin\nuv sync --all-extras\n\nexport TRUEJOIN_SECRET_KEY=$(uv run python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')\nuv run truejoin config validate -c config/database.yaml\nuv run truejoin users add admin --permission all -c config/database.yaml\nuv run truejoin serve -c config/database.yaml\n```\n\nThe console is at `http://localhost:8000/ui`. Any MCP client can connect over HTTP at `http://<host>:8000/mcp` with an API key in `X-API-Key`. For a desktop client such as Claude Desktop, run the tools over stdio:\n\n```\n{\n  \"mcpServers\": {\n    \"truejoin\": {\n      \"command\": \"uv\",\n      \"args\": [\"run\", \"--directory\", \"/path/to/truejoin\", \"truejoin\", \"serve\",\n               \"--transport\", \"stdio\", \"-c\", \"config/database.yaml\"]\n    }\n  }\n}\n```\n\nThe feedback I want most is on the rewriter. Try to read around a row rule or a mask. If a query gets through, please report it privately through a [GitHub security advisory](https://github.com/yasinyaman/truejoin/security/advisories/new) rather than a public issue.\n\nThe code is at [github.com/yasinyaman/truejoin](https://github.com/yasinyaman/truejoin) and the docs are at [yaman.dev/truejoin](https://yaman.dev/truejoin/).", "url": "https://wpnews.pro/news/letting-ai-agents-query-production-databases-without-giving-them-a-database", "canonical_source": "https://dev.to/yasinyaman/letting-ai-agents-query-production-databases-without-giving-them-a-database-account-524o", "published_at": "2026-11-15 18:15:00+00:00", "updated_at": "2026-11-15 18:20:57.160630+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-safety", "developer-tools", "agent-protocols"], "entities": ["Truejoin", "PostgreSQL", "MySQL", "MariaDB", "SQL Server", "Oracle", "Ollama", "qwen2.5-coder"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/letting-ai-agents-query-production-databases-without-giving-them-a-database", "markdown": "https://wpnews.pro/news/letting-ai-agents-query-production-databases-without-giving-them-a-database.md", "text": "https://wpnews.pro/news/letting-ai-agents-query-production-databases-without-giving-them-a-database.txt", "jsonld": "https://wpnews.pro/news/letting-ai-agents-query-production-databases-without-giving-them-a-database.jsonld"}}