Aller au contenu
Bastide Connexion Créer un compte
Press / to search

Database

FrançaisMarkdownv0.3.0

Every Bastide project can carry a PostgreSQL 17 database, with its data API, authentication and storage. This page explains how your application connects to it, how to read and write it from the console, through an agent or through the API, which extensions are allowed, and what each tier changes to connections, timeouts and isolation.

What you get

  • One database per project, with a single set of roles. Every environment class of the project (production, staging, previews) talks to the same database: there is no per-branch database today (see Branches and migrations).
  • A connection pooler (Supavisor) in front of the database. Your application never reaches PostgreSQL directly.
  • A data API in the Supabase format under the project address: /rest/v1 (PostgREST), /auth/v1, /storage/v1, /realtime/v1.
  • pgvector, built into the cluster image and allowed on every tier.

The database is created with the project when you create it with project_create_with_backend, which queues its provisioning. Its state is read with backend_read: provisioning, ready, failed, deleting or retained. A project without a database shows on the Données (Data) screen: « Ce projet n'a pas de base de données. » (this project has no database).

Connecting from your application

On every deployment, Bastide sets two variables on the target environment, once the database is ready:

VariablePooler modeUse
DATABASE_URLtransactionThe application's default address: Prisma, Drizzle, pg, NextAuth.
DIRECT_URLsession, with connection_limit=1Migrations (prisma migrate holds a session advisory lock) and anything that needs session state.

Both addresses carry the same role and password. The user name has the form <role>.<database>: that is how the pooler finds your database.

  • Your variable wins. If you set DATABASE_URL or DIRECT_URL yourself (for instance to a database hosted elsewhere), Bastide leaves it alone.
  • **Do not add pgbouncer=true.** With it, Prisma sends simple queries that transaction mode refuses (EPSSIMPLEQUERY). Without it, prepared statements work in both modes.
  • An isolated environment receives nothing. A preview forked from a third-party repository carries no sensitive variable, and these two addresses are sensitive.

Attention. The database is reachable only from your project's containers, on the platform's private network. No direct connection from your machine (psql, a desktop client) is open. To read or write by hand, use the console's SQL editor, an agent or the API, described below.

What transaction mode does not keep

Transaction mode shares server connections between your clients. Three behaviours follow, each reproduced by a test:

RefWhat happensWhat to do instead
LIM-03NOTIFY does not reach a client doing LISTEN.Listen from a session-mode connection (DIRECT_URL).
LIM-04pg_advisory_lock does not exclude another connection of the same project.pg_advisory_xact_lock inside a transaction, or session mode.
LIM-05A SET, a temporary table or a WITH HOLD cursor leaks into the next connection.SET LOCAL, ON COMMIT DROP, a cursor without WITH HOLD, or session mode.

The data API

Your project's address is https://<identifier>.donnees.bastide.app, where the identifier is the backend's (bak_…, returned by backend_read) without the bak_ prefix, in lower case. Supabase paths are served there without rewriting: the key in the apikey header, a session token in Authorization.

  • Only the public schema is served by the API.
  • A response carries at most 1000 rows, truncated without error (LIM-01): paginate with Range, or limit and offset.
  • A resource is embedded only along a declared foreign key, otherwise 400 PGRST200 (LIM-02).
  • A table is granted to the API roles only if it has row-level security and at least one policy (LIM-11). See Data security.

Publishable and secret keys are managed from the « Clés et points d'entrée » (keys and endpoints) section of Données: see Authentication.

The console's SQL editor

Open Données, then the Éditeur SQL (SQL editor) section. The table list is on the left; a click shows rows or structure.

  1. Read-only by default. The box is ticked: the database then refuses any write, whatever the statement looks like. One statement at a time, at most one hundred rows returned. Ctrl+Enter (Cmd+Enter on a Mac) runs it.
  2. Switching to write requires the maintainer rank, which is only granted to an account holding a second factor (see Account). The button becomes « Mesurer l'effet » (measure the effect): the statement is executed then rolled back, and the dialog shows how many rows would be inserted, updated or deleted, and which tables would be dropped or truncated.
  3. Applying replays the statement and commits only if the effect is the same. If the database changed in between, the count is redone and the dialog says so.
  4. A destructive effect (table dropped or truncated, rows deleted) asks you to retype the number of affected rows.

Every applied write enters the audit log with its effect and your name. A result can be copied as CSV. Whatever the database returns (column names, cells, messages) is displayed as text, never interpreted.

Tables

The « Tables » section of Données shows, in three columns, the table list, the first hundred rows of the one you pick, and on the right the essentials of the database: endpoints, state of the REST API, keys, realtime and security posture. The Structure button switches to the columns and their types. A table of the public schema without row-level security carries the label « Exposée sans politique » (exposed without a policy).

These reads go through the same operation as the editor, database.sql.read: read-only and audited.

Through an agent, the command line or the API

ActionMCP toolCommand lineAPI
Readdatabase_sql_readbastide database sql readGET /v1/projects/{projectId}/database/sql
Writedatabase_sql_writebastide database sql writePOST /v1/projects/{projectId}/database/sql
Read the database and its keysbackend_readbastide backend readGET /v1/projects/{projectId}/backend

A read (rank developer, scope db:read):

{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "sql": "select id, total from commandes order by id desc limit 10"
}

A write (rank maintainer, scope db:write) takes two steps. The first call executes, measures, rolls back and returns a nonce; the second, with confirmationNonce, commits only if the effect is identical. A destructive effect also requires typedConfirmation (the number of rows, in digits) and the db:destructive scope. A person approves the call before it goes out; a service account cannot write.

{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "sql": "delete from commandes where etat = 'annulee'",
  "idempotencyKey": "commandes-annulees-2026-10-05"
}

An agent reads no column classified as personal data: the statement is refused with colonne_personnelle_refusee (see Data security).

Allowed extensions

An extension is installed with create extension, in a migration or in the SQL editor in write mode. The database accepts only those on the allow-list of the project's tier.

TierAllowed extensions
Discoverybtree_gin, btree_gist, citext, cube, earthdistance, fuzzystrmatch, hstore, intarray, isn, lo, ltree, moddatetime, pg_trgm, pgcrypto, seg, tablefunc, tsm_system_rows, unaccent, uuid-ossp, vector
Standard, Pro, SovereignDiscovery's, plus autoinc, bloom, dict_int, dict_xsyn, insert_username, intagg, refint, sslinfo, tcn, tsm_system_time

The price grid states ceilings (20 on Discovery, 50 on Standard): the Standard list deliberately names fewer. Arbitrary extensions on Pro and Sovereign require a dedicated instance, which is not served yet: a Pro project follows the Standard list today.

Never allowed on the shared cluster: adminpack, amcheck, dblink, file_fdw, pageinspect, pg_buffercache, pg_freespacemap, pg_prewarm, pg_stat_statements, pg_surgery, pg_visibility, pg_walinspect, pgrowlocks, pgstattuple, plperl, plpython3u, pltcl, postgres_fdw, xml2. They open the network from the data node, the file system, the shared cache, or arbitrary code.

Changing tier replaces the list but removes no extension already installed: its next installation is what would be refused.

Connections and timeouts by tier

TierClient / server connectionsStatement timeoutIdle-in-transaction timeout
Discovery30 / 330 s60 s
Standard60 / 530 s60 s
Pro200 / 15300 s300 s
Sovereignnegotiated300 s300 s
  • Beyond the client connections, the pooler refuses the extra client with a message naming the ceiling (« max client connections reached, limit: 60 »).
  • Beyond the timeout, PostgreSQL cancels the statement (code 57014) or closes a connection left idle in a transaction (code 25P03). The timeout in force is read with SHOW statement_timeout.
  • A tier change takes effect without a restart: the connections of that project alone are closed once, and reopen under the new limits.
  • Realtime holds one session-mode connection per instance while a client is connected: it counts against your server ceiling (three on Discovery).

Isolation by tier

TierDatabase isolation, per the price gridWhat is served today
DiscoveryShared clusterShared cluster, at most 800 projects per cluster
StandardShared clusterShared cluster, at most 400 projects per cluster once it holds a Standard project
ProDedicated PostgreSQL instanceNot yet: the project stays on the shared cluster, with the Pro limits above
SovereignDedicated instance and nodeNot yet: on quotation

On the shared cluster, each project has its own database and roles; the project's roles connect only through the pooler. The number of projects per cluster is a published risk choice: a cluster incident hits everything it carries at once.

Common errors

  • **variables_requises_manquantes on DATABASE_URL**: the database was not ready at deployment time. Wait for the ready state (backend_read), then redeploy.
  • **EPSSIMPLEQUERY**: remove pgbouncer=true from the address.
  • **backend_indisponible**: the platform cannot reach the database; the message names the cause.
  • **colonne_personnelle_refusee**: an agent's statement touches a column classified as personal. A person reads it from the console.
  • **42704 on a grant … to authenticated**: roles carry the database name as a prefix. Enable row-level security and write a policy: the grants follow (LIM-11).
  • **57014**: the statement exceeded the tier's timeout. Add an index, split the work, or change tier.

See also

Report a mistake on this page