Skip to content
technical guide

Ask PostgreSQL Questions in Plain English

Querying PostgreSQL in natural language safely: the read-only role, how generated SQL is validated before it runs, pooler and schema gotchas, and where it breaks.

By the Datarelix Team · Published · Updated · Last verified

You can ask PostgreSQL questions in plain English, and the part that decides whether it is safe has nothing to do with the model. It is the read-only role you grant and the validation applied to generated SQL before execution. Get those two right and natural-language querying is a scoped SELECT with a nicer interface. Get them wrong and a generated statement reaches production with write privileges.

Before you start

Engineers and analysts who run PostgreSQL — RDS, Cloud SQL, Azure Database, Supabase, Neon, or self-managed — and are evaluating whether natural-language querying is safe to point at it. It assumes you can create a role and run GRANT. If you want the click-by-click connection form, that lives in the PostgreSQL connection guide.

Is it safe to query PostgreSQL in plain English? The two boundaries that matter

Diagram of a question flowing to a model that receives only schema structure and returns a structured plan, crossing a dashed credential boundary to a read-only validator, then an isolated query service holding encrypted credentials, then PostgreSQL running as a SELECT-only role. A return arrow carries the answer and its evidence back to the user, while a blocked arrow shows result rows not crossing back to the model.
Two independent boundaries: the database role limits what is reachable at all; the validator limits what may execute.

Boundary one: the PostgreSQL role

This is the boundary you control, and the one that holds even if everything else fails. A role with CONNECT, USAGE, and SELECT — and nothing else — cannot write, no matter what SQL reaches it. The minimum grant:

CREATE USER datarelix_reader WITH PASSWORD 'choose-a-strong-password';
GRANT CONNECT ON DATABASE your_db TO datarelix_reader;
GRANT USAGE ON SCHEMA public TO datarelix_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO datarelix_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO datarelix_reader;

The ALTER DEFAULT PRIVILEGES line is the one people skip, and then new tables silently go missing until someone re-grants. Make the role a LOGIN role that is not a superuser. The full privilege model is in the PostgreSQL GRANT reference.

On PostgreSQL 14 and later there is a shorter path: GRANT pg_read_all_data TO datarelix_reader; grants read on every table in the database. It replaces the four GRANT/ALTER DEFAULT PRIVILEGES lines and never goes stale, but it is database-wide — the explicit grants above are the tighter option when you want the connection scoped to one schema.

Confirm the grant before you leave the database. Connected as datarelix_reader:

-- Should return true for a table you expect to be readable:
SELECT has_table_privilege('datarelix_reader', 'public.orders', 'SELECT');

-- Should return only the tables you intended to expose:
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
ORDER BY 1, 2;

-- Should fail with "permission denied", proving the boundary:
CREATE TABLE should_not_work (id int);

If the second query lists schemas you did not intend to expose, fix it here rather than relying on the connection’s allowed-schema setting — the grant is the boundary that holds regardless.

Boundary two: validation before execution

A generated statement is parsed into a full syntax tree and rejected unless it is a read — SELECT, CTEs, and set operations only. INSERT, UPDATE, DELETE, MERGE, DDL, and administrative commands have no path through, and neither do PostgreSQL-specific escapes like COPY, DO, CALL, TRUNCATE, VACUUM, or PREPARE/EXECUTE. This matters because parsing is not keyword filtering: a comment-obfuscated DELETE is still a DELETE in the syntax tree. The general argument is in read-only AI analytics.

Both boundaries are deliberate redundancy. The grant alone would be enough for safety; the validator also catches the expensive-but-legal mistakes, and applies an automatic row limit so an unbounded question cannot return an unbounded result.

What the generated SQL looks like

Against a fictional retailer’s schema — customers, orders, order_items — the question “Who were our top 10 customers by revenue in Q2 2026?” produces something like:

SELECT
  c.customer_id,
  c.name,
  SUM(oi.quantity * oi.unit_price) AS revenue
FROM customers AS c
JOIN orders AS o ON o.customer_id = c.customer_id
JOIN order_items AS oi ON oi.order_id = o.order_id
WHERE o.status = 'completed'
  AND o.ordered_at >= DATE '2026-04-01'
  AND o.ordered_at < DATE '2026-07-01'
GROUP BY c.customer_id, c.name
ORDER BY revenue DESC
LIMIT 10;

This is idiomatic PostgreSQL: half-open date ranges rather than BETWEEN, date_trunc('quarter', …) where a period grain is needed, LIMIT/OFFSET for pagination, double quotes for identifiers that need them. A system that generates generic SQL will produce TOP 10 or backticked identifiers that PostgreSQL rejects outright — the general mechanism is in text-to-SQL explained.

Notice also what is decided here: status = 'completed', gross rather than net of refunds, a calendar quarter rather than trailing 90 days. Those are interpretations. Seeing the query is how you check them, which is the whole point of verifying an AI-generated answer.

PostgreSQL-specific things that will bite you

RealityWhat happensWhat to do
Transaction-mode poolersSupabase’s transaction pooler (port 6543) and PgBouncer in transaction mode do not support prepared statements. You get prepared statement … already exists.Use a direct connection or a session-mode pooler (Supabase: port 5432).
Supabase pooler usernamesThe username is postgres.<project-ref>, not postgres, and auth fails confusingly.Copy the username from the pooler connection string, not the direct one.
Azure Flexible ServerPeople append user@servername out of habit. That suffix was only ever needed for the retired Single Server.Use the plain username.
One schema per connectionScope is a single schema. Tables in a second schema are invisible, and the model will confidently answer from what it can see.Create one connection per schema.
SSL mode defaultsrequire encrypts but does not verify the server certificate — protection against passive eavesdropping, not an active MITM.Use verify-full for managed PostgreSQL in production.
Views and materialized viewsThese are readable and often better targets than base tables, since they usually carry your team’s definitions.Point the grant at your analytics schema rather than raw ingestion.
Row-Level SecurityRLS policies apply to the connecting role. On Supabase, where RLS is on by default, a read-only role with no matching policy sees zero rows and the tool reports an empty result rather than an error.Write a SELECT policy for the reader role on each table you expose. Do not grant BYPASSRLS — that would defeat the boundary you just built.

What the model is and is not given

The model receives table names, column names, data types, primary and foreign keys, and any descriptions you have curated. It does not receive rows, and it does not receive the connection string — those live with the execution layer, which is the argument in how credentials stay out of the model.

One exception: a single-row answer value can carry into follow-up context, so “and how does that compare to last year?” resolves — what enters context. Result tables do not.

Schema structure alone is also not enough for good answers. PostgreSQL schemas are full of status, type, and amount columns whose meaning lives in someone’s head. Attaching one-line descriptions closes that gap — see why semantic context matters.

Connecting PostgreSQL to Datarelix

Datarelix connects to PostgreSQL 13+ with either username/password or a postgresql:// connection string; the password is stored encrypted and every other field as plain configuration. Introspection reads the catalog through your role, so anything the read-only role cannot see — a table in a schema you did not grant — is invisible to Datarelix too. It surfaces primary keys, foreign keys, nullability, defaults, and types, and an optional AI schema analysis proposes descriptions and infers undeclared relationships from naming conventions — useful on schemas where foreign keys were never declared.

Setup, SSL modes, and the full troubleshooting table are in the PostgreSQL connection guide.

Where this breaks on PostgreSQL

Nothing here writes: no INSERT, no DDL, no migrations, no administration. This is by design; there is no write path planned. Scope is one schema per connection, and cross-schema joins in a single question are therefore out. There is a server-side row cap, so “export the whole table” is the wrong tool.

Answer quality tracks schema quality. On a well-modelled analytics schema with described columns, results are good; on raw operational tables with cryptic names and undocumented status codes, you will get fluent SQL against the wrong column. Foreign keys that were never declared can only be inferred, and inference is sometimes wrong — check joins on the first few answers against any schema you did not model yourself.

Sources

Grant syntax and privilege behavior follow the PostgreSQL GRANT documentation; SSL mode semantics follow libpq’s SSL support. Pooler behavior is per Supabase’s connection documentation. Product statements describe Datarelix as shipped in August 2026 and are stated canonically on the security page. The SQL example uses a fictional demo schema and is illustrative, not captured output.

Create the role, then ask one question you can check

Create the read-only role above, then follow the quickstart and ask one question whose answer you already know. Read the SQL it generated before you read the number. Your first 14 days run at Pro limits, no credit card required.