SQLore
FeaturesPricingDownloadAboutContact
Start freeStart for free
SQLore
HomeFeaturesSolutionsPricingDownloadCompareFAQAboutBlogContact

SQLore © 2026

TermsPrivacyRefunds
Download for macOSDownload for WindowsSign in
  1. Home
  2. Blog
  3. How to set up a read-only database user for an AI tool
14 July 2026·6 min readSecurityPostgreSQLMySQL

How to set up a read-only database user for an AI tool

Whatever an AI tool promises about read-only behaviour, the durable guarantee is the one your database enforces. Here is the SQL for both engines, including the grant that silently fails to cover tables created next week.

To create a read-only PostgreSQL user, create a role with LOGIN, grant CONNECT on the database and USAGE plus SELECT on the schema, then set ALTER DEFAULT PRIVILEGES so future tables are covered too. On MySQL, GRANT SELECT ON db.* is enough because the wildcard already applies to tables created later.

Any AI database tool worth connecting will tell you it only issues reads. Believe it, and then enforce it anyway — not because the vendor is lying, but because a guarantee implemented in someone else's application is one refactor away from being different, and a guarantee implemented in your database is not.

PostgreSQL: four statements, and one people forget

The first three statements are the ones every tutorial gives you. The fourth is the one that matters in three months.

-- 1. The role itself
CREATE ROLE ai_readonly LOGIN PASSWORD 'use-a-real-secret';

-- 2. Let it reach the database and see the schema
GRANT CONNECT ON DATABASE analytics TO ai_readonly;
GRANT USAGE ON SCHEMA public TO ai_readonly;

-- 3. Read every table that exists right now
GRANT SELECT ON ALL TABLES IN SCHEMA public TO ai_readonly;

-- 4. ...and every table created from now on
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO ai_readonly;

Step three grants on the tables that exist at the moment you run it. Without step four, the table your next migration adds is invisible to the role, and the failure surfaces as an AI tool insisting a table does not exist while you are looking at it in your client. It is the single most common misconfiguration in this setup.

If you use more than one schema

Both the USAGE grant and the DEFAULT PRIVILEGES statement are per-schema. A database with reporting, billing and public schemas needs all three repeated, and DEFAULT PRIVILEGES additionally applies only to objects created by the role that ran it — so if your migrations run as a different user, run it as that user.

-- Run as the role your migrations use, for each schema
ALTER DEFAULT PRIVILEGES FOR ROLE migrator IN SCHEMA billing
  GRANT SELECT ON TABLES TO ai_readonly;

MySQL: one statement, and a different gotcha

MySQL is simpler because a wildcard grant covers tables created later automatically — there is no DEFAULT PRIVILEGES equivalent to forget.

CREATE USER 'ai_readonly'@'%' IDENTIFIED BY 'use-a-real-secret';
GRANT SELECT ON analytics.* TO 'ai_readonly'@'%';
FLUSH PRIVILEGES;

The gotcha here is the host part. `'ai_readonly'@'%'` accepts connections from anywhere, which is usually more than you meant. Scope it to the address range your team actually connects from, and remember that a desktop tool connects from a laptop rather than from your application servers.

Verify it, because an ungrant is silent

A grant that did not do what you thought produces no error until something needs it. Connect as the new user and try to write — the statement should be rejected outright.

-- Connected as ai_readonly. Both of these must fail.
CREATE TABLE should_not_work (id int);
UPDATE customers SET email = 'x' WHERE id = 1;

Then check the read side is complete, including anything created since: query a table added after you set the grants up. If it is missing on Postgres, step four did not run or ran as the wrong role.

What this does not protect against

A read-only role stops writes. It does not stop an expensive query, and an AI tool that can issue arbitrary SELECTs against a production primary can absolutely cause an incident by reading too much.

  • •Set a statement timeout on the role, so a runaway query dies rather than accumulating.
  • •Point the tool at a read replica if you have one — read-only and off the primary are different protections.
  • •Consider a connection limit, so one tool cannot exhaust the pool.
-- PostgreSQL: cap query time and connections for this role
ALTER ROLE ai_readonly SET statement_timeout = '30s';
ALTER ROLE ai_readonly CONNECTION LIMIT 5;

Two guarantees are better than one

SQLore parses every statement it generates to an AST and validates it down to a single read-only SELECT before execution, then runs it on a read-only connection under a statement timeout. We still recommend the grants above. Ours is the guarantee we control; yours is the one that keeps holding if we ship a bug — and a security review that only has to trust one of them is a much shorter meeting.

Related reading

PostgreSQL

Chat With Your PostgreSQL Database

Read

MySQL

Chat With Your MySQL Database

Read

Evaluation

Choosing A SQL AI Tool: What Actually Differentiates Them

Read

More posts

  • Why AI-generated SQL gets joins wrong
  • Text-to-SQL benchmarks do not predict your schema
SQLore platform

Stop Writing The
Same Three Joins

Start free, upgrade when you are asking enough questions for it to matter.

Start for freeContact sales
sqlore.com
Connectionretail_db

staging

analytics

Add connection

MENU

Chat

Reports

Schema

History

Settings

Rows scanned

15,600+18%

Questions

25,800+14%

Avg latency

41 ms−16%

By product

By month

By connection