Semantius Logo
DocsReferencepg_semantiusReference

Reference

On this page

Lookup material for pg_semantius. The pages before this one explain when you need any of it.

Functions

Four functions live in the semantius schema. They are the only things the extension itself owns, along with the schema and the roles.

FunctionWhat it does
semantius.migrate()Installs or updates the core schema. Superuser only. Runs as one transaction, and is safe to run again.
semantius.pending()Lists what a migrate() would apply. Works before the first install.
semantius.version()The version of the installer, which is not always the version in the database.
semantius.status()The state of the installation. This is the one to trust.

semantius.status() returns a single row:

ColumnTells you
extversionVersion of the installer on the server
db_versionVersion actually installed in this database
applied, pendingHow many migrations have run, and how many are waiting
unknown_versionsMigrations in the database that this build does not recognize
changed_versionsMigrations whose source has changed since they were applied
unowned_objectsObjects with unexpected ownership, usually after a restore
default_acls_okWhether default privileges survived a restore
jwt_aud_setWhether the token audience check is switched on

Roles

CREATE EXTENSION creates these four. They belong to the whole PostgreSQL server rather than to one database, and DROP EXTENSION never removes them. All four arrive unable to log in and without a password, so granting those is part of setting up an environment.

RoleWhat it is for
semantius_ownerOwns the core objects. The data dictionary runs as this identity. Bypasses row-level security, so nothing should ever log in as it.
semantius_userThe role requests actually run as. Fully subject to row-level security.
authenticatedWhat a signed-in session becomes. Holds semantius_user and inherits its access.
semantius_authenticatorThe account your application connects as. Has no access of its own, and can do nothing except become authenticated.

If one of these names already exists with different properties, or if semantius_owner already has members, the install refuses rather than adopting it. That check exists so somebody who can create roles cannot pre-claim ownership of your tables.

What Gets Created

CREATE EXTENSION creates the four roles, the semantius schema, and the four functions above. Everything else comes from semantius.migrate() and is created as ordinary database objects, which is what makes backups and DROP EXTENSION behave normally.

SchemaHolds
publicThe model itself: entities, fields, modules, users, roles, permissions, audit logs, settings, and API keys
rbacThe permission and identity functions that policies call
commonShared helpers, including a small cache
auditThe machinery that records changes
pgmqThe message queue

Also created: four event triggers, which record schema changes and tell the API to reload when the model changes. pgcrypto is installed into public, because the API key code expects it there.

Session Settings

These are read from the session at runtime rather than set in a configuration file. In an ordinary deployment the API tier sets the first group and the extension manages the rest.

SettingSet by
request.jwt.claimsYour API tier, once per request, holding the verified sign-in token
request.jwt.claim.sub, .email, .role, .name, .aud and similarThe same, one setting per claim, which is the style Neon and Supabase use
app.current_user_id, app.user_permissions, app.oauth_scopes and others in the app. groupThe permission code itself, per transaction
dd.table_renameThe data dictionary, while renaming a table

One row of public._settings is worth a deliberate decision. jwt_aud pins the audience a sign-in token must name. Set it, and a token for a different audience is rejected. Leave it unset, and the audience is not checked at all, so any token from your trusted issuer is accepted. Set it once you mint tokens for more than one audience from the same issuer.

Errors

SQLSTATEMessageCause
Must be superuser to create this extensionNot connected as a superuser
extension "pg_semantius" must be installed in schema "public"A SCHEMA clause was given. The extension pins its own.
42501permission denied for schema semantiusA request role tried to reach the installer functions
42501semantius.migrate() must be run by a superuserThe second statement ran on a non-superuser connection
55000pg_semantius requires a UTF8 databaseThe database encoding is LATIN1 or SQL_ASCII
55000the pgmq extension is installedThe standalone queue extension is present, and this one carries its own
55000pgcrypto must be installed in schema publicUsually caused by CASCADE. Fix with ALTER EXTENSION pgcrypto SET SCHEMA public.
55000existing role semantius_owner has unexpected attributesA role of that name exists and is not the expected one
55000semantius.migrate() cannot run inside a CREATE/ALTER EXTENSION scriptmigrate() was called from within an extension script

Requirements

  • PostgreSQL 18, the only tested version.
  • A UTF8 database. Checked at install time.
  • Superuser rights to install, and none to use.
  • No standalone pgmq extension in the same database.

semantius.migrate() also fixes several session settings for the duration of the install, so an unusual connection cannot change what gets built. It will refuse to run in a read-only transaction, under a statement or lock timeout shorter than the install takes, or at an isolation level above read committed.

Next Steps