29. PostgreSQL language
An instance started with the POSTGRESQL element of sql_mode (see 15.10) speaks the PostgreSQL protocol and grammar: queries from psql, psycopg2, Npgsql or pgjdbc are written as for a PostgreSQL server. This chapter lists what this grammar accepts, the MIRAJ extensions that remain available in it, the MIRAJ-specific syntax that is rejected, and what is not yet supported.
The engine itself is the same: a PostgreSQL query is mapped to MIRAJ statements, with their rules (types, conversions, collations).
29.1 Lexical elements#
| Syntax | Meaning |
|---|---|
"Name" | identifier (table, column, alias); "" inside the name for a double quote |
'text' | standard string: the backslash \ is an ordinary character, '' writes an apostrophe |
E'a\tb\n' | string with escapes: \b \f \n \r \t, \\, \', octal \101, hexadecimal \x41, Unicode é and \U0001F600 |
$$text$$, $fn$text$fn$ | string with no escaping at all (function body, text containing apostrophes) |
$1, $2… | parameters of a prepared statement; the same number may appear again |
-- … | comment to the end of the line (also --5) |
/* … /* … */ … */ | comment, possibly nested; /*! … */ is only a comment here |
# | bitwise exclusive OR operator (not a comment) |
A quoted identifier remains case-insensitive: "Articles" and articles designate the same table (all MIRAJ names are case-insensitive). The name as written is kept as is at creation.
29.2 Expressions and queries#
| PostgreSQL form | Read as |
|---|---|
a || b | concatenation (CONCAT(a, b): NULL if either is NULL) |
a ~ 'pattern', ~*, !~, !~*, a OPERATOR(pg_catalog.~) b | REGEXP / NOT REGEXP |
a ILIKE 'x%', NOT ILIKE | LIKE (the default collations already ignore case) |
x::type, CAST(x AS type) | conversion, types in table 29.1 |
int4 '5', timestamptz '…', bytea '\x00', boolean 't' | typed literal |
INTERVAL '1 day', '2 hours 30 minutes'::interval, INTERVAL '3' DAY | interval (d + INTERVAL '1 day') |
TRUE, FALSE | booleans, rendered t / f |
a IS [NOT] DISTINCT FROM b | comparison that treats NULL as a value |
a = ANY (ARRAY[1, 2]), a <> ALL (ARRAY[…]) | a IN (1, 2), a NOT IN (…); a > ANY (ARRAY[…]): disjunction |
position('a' in b), substring(b from 2 for 3), substring(b for 3) | LOCATE, SUBSTRING |
extract(year from d), extract('month' from d), date_part('year', d) | numeric value (date_part: double precision): YEAR(d), MONTH(d)…; also epoch, dow, isodow, doy, week, quarter, isoyear, decade, century, millennium, julian, milliseconds, microseconds |
extract(epoch from t) | seconds since 01/01/1970 at 00:00 UTC of the time as written (a date or a timestamp without time zone is read as UTC, like PostgreSQL); integer for a date, six decimals otherwise |
extract(second from t) | seconds with their fraction (30.500000) |
date - date, date ± n, date + time | number of days, date, timestamp |
date ± INTERVAL '…', timestamp ± INTERVAL '…' | timestamp |
timestamp - timestamp | interval written as text like PostgreSQL (2 days 02:00:00) |
timestamptz ± INTERVAL '…', timestamptz - timestamptz | in days, weeks, months or years: the session's civil time is kept (+ interval '1 day'); in hours, minutes or seconds: elapsed time across a clock change (+ interval '24 hours'); difference of two timestamptz: actual duration between the instants (1 day 01:00:00 on either side of the switch to winter time) |
t AT TIME ZONE 'zone' | pg_at_time_zone function: a timestamptz (now(), to_timestamp(…), timestamptz column…) becomes the timestamp of the civil time in zone; a timestamp is read in zone and becomes a timestamptz; a written offset ('+02') is read west of Greenwich (POSIX notation), like PostgreSQL |
doc -> 'key', doc ->> 'key', doc -> 0, doc -> 'a' ->> 'b', '{…}'::jsonb -> 'a' | JSON_EXTRACT / JSON_UNQUOTE (->> of a JSON null: NULL) |
doc #> '{a,0}', doc #>> '{a,0}' | element at the path written as an array (document, text) |
a @> b, a <@ b | containment of JSON documents (t / f) |
a || b, doc - 'key', doc - 0 between JSON documents | merge (objects) or concatenation (arrays); member or element removed |
x COLLATE "C", COLLATE pg_catalog.default | collation ignored (a MIRAJ collation, utf8mb4_bin, is still applied) |
pg_catalog.version(), public.f(1), public.t | PostgreSQL or MIRAJ function, table of the current database (pg_catalog.pg_class: catalog table, see 29.9) |
current_schema, current_schema() | DATABASE() |
2 ^ 3 | power (POW) |
~5, a & b, a | b, a # b, a << n, a >> n | signed integers (~5 is -6, -8 >> 1 is -4) |
Calculations and conversions follow the reference server, not the MIRAJ language:
- a division or modulo by zero (
1 / 0,5 % 0,x / 0.0) is a22012error (division_by_zero, whichEXCEPTION WHEN division_by_zerocatches), not a NULL; - a text converted to a number (
'abc'::int,CAST('1.5' AS integer),'x'::numeric) that is not one gives22P02instead of its numeric prefix; an integer can be written0x1F,0o17,0b101,1_000;'Infinity','-Infinity'and'NaN'arefloat8values; - a written integer that fits in 32 bits is an
integer(pg_typeof(1), type announced to the client); sumof anintegeris abigint,sumof abigintanumeric(no overflow);avgof integers or decimals is anumericwith at least 16 decimals (2.3333333333333333);stddevandvarianceare the sample standard deviation and variance (stddev_samp,var_samp); withstddev_popandvar_pop, they return on integers or decimals an exactnumericwith at least 16 decimals (stddevof 1 to 5:1.5811388300841897), adouble precisionon floats, and can also be used withOVER;x ^ yandpower(x, y)return adouble precisionbetween integers or with a float, an exactnumericas soon as an operand is a decimal (2 ^ 0.5:1.4142135623730950), with 16 significant digits and no fewer decimals than an operand; zero to a negative power or a negative to a non-integer power:2201F;lower(1),upper(1.5):42883, these functions existing only for text.
Values written into a column (INSERT, UPDATE): a boolean column reads 't', 'yes', 'on', '1', their opposites and prefixes (22P02 for 'maybe'); a uuid column keeps the canonical lowercase form (22P02 for a text that is not a UUID); a json or jsonb column rejects an unreadable document (22P02).
SELECT clauses:
LIMIT n,LIMIT ALL,OFFSET n [ROWS](alone or beforeLIMIT),FETCH {FIRST | NEXT} [n] {ROW | ROWS} ONLY;ORDER BY x [ASC | DESC] [NULLS FIRST | NULLS LAST](MIRAJ sorts NULLs first in ascending order; the requested order is obtained by an added sort key),xpossibly being an alias from the list; ajsonbcolumn is sorted in PostgreSQL order (null< strings < numbers < booleans < arrays < objects);FOR UPDATE,FOR NO KEY UPDATE,FOR SHARE,FOR KEY SHARE, withNOWAITorSKIP LOCKED.
Table 29.1. Conversion types#
| PostgreSQL type | MIRAJ conversion |
|---|---|
int2, smallint, int4, int, integer, int8, bigint | signed integer |
oid | unsigned integer |
bool, boolean | boolean; text read like PostgreSQL (t, true, yes, on, 1, their opposites and prefixes) |
float4, real, float8, double precision | DOUBLE |
numeric(p,s), decimal(p,s); numeric alone | DECIMAL(p,s); DECIMAL(38,10) |
text, varchar[(n)], char[(n)], bpchar, name, uuid, regclass, regtype… | text (CHAR[(n)]) |
date, time[(p)], timetz | DATE, TIME |
timestamp[(p)]; timestamptz, timestamp with time zone | DATETIME(p), to the microsecond when no precision is written, a written offset (+02, Z) is removed; timestamptz: instant converted to the session time zone ('2026-10-08 10:15:00+02'::timestamptz), sent with its offset (OID 1184) |
bytea | bytes; '\x0001ff'::bytea and the escape format ('a\\b\001') are decoded |
json, jsonb | JSON |
interval | interval (literal only) |
vector(n) | vector |
MIRAJ names (SIGNED, DATETIME, BINARY…) are still accepted, and the pg_catalog. prefix is ignored.
29.3 Column types#
| PostgreSQL type | MIRAJ column |
|---|---|
serial, serial4; bigserial, serial8; smallserial, serial2 | INT / BIGINT / SMALLINT AUTO_INCREMENT NOT NULL |
GENERATED {ALWAYS | BY DEFAULT} AS IDENTITY [(…)] | AUTO_INCREMENT NOT NULL (sequence options have no effect) |
int2, int4, int8, smallint, integer, bigint | SMALLINT, INT, BIGINT |
float4, real; float8, double precision | FLOAT; DOUBLE |
boolean, bool | BOOLEAN |
numeric(p,s); numeric | DECIMAL(p,s); DECIMAL(38,10) |
text, varchar without length | LONGTEXT |
varchar(n), character varying(n), char(n), "char", name | VARCHAR(n), CHAR(n), CHAR(1), VARCHAR(63) |
bytea | LONGBLOB |
timestamp[(p)]; timestamptz; time[(p)], timetz | DATETIME(p); TIMESTAMP(p) (an instant stored in UTC, read and written in the session time zone: changing the machine's time zone does not move it, and the two passes of the repeated hour at the switch to winter time remain distinct); TIME(p) (to the microsecond by default, like PostgreSQL) |
date, json, jsonb, uuid | DATE, JSON, JSONB (a JSON column that keeps the name jsonb), UUID |
A serial column that is not by itself the primary key receives a uniqueness constraint (a MIRAJ AUTO_INCREMENT column must be a key).
A jsonb column behaves like a json column (same storage, same functions), but keeps its type: it is announced as jsonb (OID 3802) in RowDescription, pg_attribute.atttypid, format_type, pg_typeof and information_schema.columns.data_type, through a view or a derived table, and col -> 'key', col #> '{a,b}' on it return jsonb; a json column remains json (OID 114). ALTER COLUMN … TYPE jsonb (or json) changes the announced type. In the MIRAJ language, the column is written JSONB (see 5).
29.4 Data definition#
CREATE SCHEMA [IF NOT EXISTS] namecreates a database,DROP SCHEMA name [CASCADE | RESTRICT]drops it with all its contents: each MIRAJ database is a PostgreSQL schema.CREATE TEMP TABLE,CREATE TEMPORARY TABLE: session temporary table;CREATE UNLOGGED TABLE: ordinary table.- Column constraints:
NOT NULL,NULL,DEFAULT expr,PRIMARY KEY,UNIQUE,CHECK (…)(which may reference other columns),REFERENCES t (c), with or without a precedingCONSTRAINT name;DEFERRABLE,INITIALLY DEFERREDhave no effect;COLLATE "C"ignored. DEFAULT 'text'::character varying,DEFAULT now(),DEFAULT CURRENT_TIMESTAMP,DEFAULT 'a' || 'b': the value is kept as MIRAJ text.GENERATED ALWAYS AS (expr) STORED: generated column.TRUNCATE [TABLE] [ONLY] t [RESTART IDENTITY | CONTINUE IDENTITY] [CASCADE | RESTRICT]: one table at a time; the auto-increment counter restarts at 1.CREATE [OR REPLACE] VIEW,CREATE INDEX name ON t (c)(andUSING btree),DROP TABLE … CASCADE.
29.5 Session and transactions#
| Statement | Effect |
|---|---|
SET [SESSION | LOCAL] name {TO | =} value[, …], SET name TO DEFAULT | session variable (lock_wait_timeout, autocommit…); a list becomes a text a, b |
SET TIME ZONE 'Europe/Paris', SET TIME ZONE LOCAL, SET TIME ZONE -5 | session time_zone: now(), SHOW TIME ZONE, current_setting('TimeZone') and AT TIME ZONE follow it, the TimeZone parameter is returned to the client; unknown time zone: 22023 |
SET statement_timeout TO 10000, '10s', '1min' | max_statement_time (seconds); SHOW statement_timeout returns 10s |
RESET name, RESET ALL | default value; RESET ALL has no effect |
SHOW name, SHOW TIME ZONE | one row, one column named like the parameter, SHOW tag; PostgreSQL-specific parameters (statement_timeout, server_version_num…) are read by current_setting |
SHOW ALL | all parameters (name, setting, description), like pg_settings |
SET search_path TO a, b, public | the current database becomes the first named database that exists and that the account can read; public keeps the current database; SHOW search_path returns the current database |
BEGIN [WORK | TRANSACTION] [ISOLATION LEVEL …] [READ ONLY | READ WRITE] [[NOT] DEFERRABLE], START TRANSACTION … | transaction; the isolation level applies to it alone |
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL … | level of the following transactions |
SHOW TRANSACTION ISOLATION LEVEL, SHOW transaction_isolation | read committed, repeatable read… |
COMMIT [TRANSACTION], END, ROLLBACK [TRANSACTION], ABORT, SAVEPOINT s, RELEASE [SAVEPOINT] s, ROLLBACK TO [SAVEPOINT] s | end of transaction, savepoints |
DISCARD ALL, DEALLOCATE [PREPARE] {name | ALL} | prepared statements released |
DECLARE c [NO SCROLL] CURSOR [WITH | WITHOUT HOLD] FOR query, FETCH [FORWARD] {n | ALL} [FROM] c, MOVE …, CLOSE {c | ALL} | SQL cursor (psycopg2 named cursors); WITHOUT HOLD requires a transaction block; forward reading only |
The parameters that PostgreSQL announces to clients (application_name, client_encoding, DateStyle, TimeZone, extra_float_digits…) are maintained by the network server (see 15.10.4).
29.6 Available MIRAJ extensions#
They are written with PostgreSQL lexical conventions (quoted identifiers, standard strings):
- vector indexes (
CREATE VECTOR INDEX,CREATE INDEX … USING hnsw), distances<->,<#>,<+>; full-text indexes (CREATE FULLTEXT INDEX),MATCH (…) AGAINST (…); - cached views (
CREATE CACHED VIEW,REFRESH VIEW); - REST endpoints (
CREATE ENDPOINT, body limited to a singleSELECTin the PostgreSQL language), change events (LISTEN,UNLISTEN,NOTIFY,WAIT FOR CHANGES); BACKUP DATABASE,RESTORE DATABASE, XA transactions (XA START…),KILL,USE;- MIRAJ
SHOWstatements (SHOW TABLES,SHOW DATABASES,SHOW CREATE TABLE,SHOW PROCESSLIST,SHOW VARIABLES,SHOW CLUSTER STATUS…),@@nameand@vvariables; - partitioning, temporal tables (
FOR SYSTEM_TIME),QUALIFY,GROUP BY … WITH ROLLUP.
29.7 Rejected MIRAJ syntax#
These forms have a different meaning in PostgreSQL, or do not exist there: they receive a syntax error (42601) whose message names the construct.
| MIRAJ syntax | In the PostgreSQL language |
|---|---|
`name` (backticks) | "name" |
'l\'été', 'a\nb' (escapes inside '…') | 'l''été', E'a\nb' |
# comment | -- comment (# is the exclusive OR) |
LIMIT 10, 5 | LIMIT 5 OFFSET 10 |
a || b as logical OR | a OR b (|| concatenates) |
a && b | a AND b |
!a | NOT a |
"text" as a string | 'text' ("…" is an identifier) |
29.8 Not yet supported#
| Form | Response |
|---|---|
CREATE EVENT, ALTER EVENT | 0A000: no scheduled events in the PostgreSQL language (29.13); routines and triggers: 29.13 |
arrays (int[], ARRAY[…] outside = ANY), interval type as a column | 0A000 (catalog arrays can be read, see 29.9) |
SELECT DISTINCT ON, FETCH … WITH TIES, WITH RECURSIVE in a view | 0A000 |
binary COPY, COPY of a server file, prepared COPY, ON CONFLICT on an expression | 0A000; see 29.11 |
table column aliases (FROM t AS x(a, b)) or of a query whose list contains * | syntax error, 0A000 |
COMMENT ON an object other than a table or column, DROP INDEX a, b | 0A000 |
doc ? 'key', ?|, ?& (JSON key existence) | syntax error: ? is the parameter of the embedded API; doc -> 'key' IS NOT NULL replaces it |
29.9 The pg_catalog catalog#
PostgreSQL clients describe the database by reading the system tables (pg_class, pg_attribute…). An instance in the PostgreSQL language keeps them in a virtual database pg_catalog, built from the MIRAJ catalog on every statement that reads it, like information_schema: nothing is written to it (a write receives 42501), and an instance in the MIRAJ language does not see it (neither its tables nor the functions of this chapter).
Model: a single PostgreSQL database, miraj (pg_database); each MIRAJ database is a schema (pg_namespace). The current database is at the head of the search path: its tables are "visible" (pg_table_is_visible), those of other databases are named database.table. As in PostgreSQL, pg_catalog comes before any schema: SELECT * FROM pg_class reads the catalog table (a WITH expression of the same name takes precedence).
OIDs: base types keep those of PostgreSQL (int4 23, text 25, numeric 1700, arrays _int4 1007…), as do the catalog tables ('pg_class'::regclass is 1259). MIRAJ objects receive a synthetic, stable OID: a hash of their kind, database and name, greater than 16 384, which does not change from one restart to the next (renaming an object changes its OID). The vector type is that of the pgvector extension (OID 16385, extension vector in pg_extension).
Table 29.2. pg_catalog tables#
| Table | Contents |
|---|---|
pg_namespace | the MIRAJ databases visible to the account, pg_catalog, information_schema |
pg_class | tables (r), views (v), indexes (i) and sequences (S), and the catalog tables |
pg_attribute, pg_attrdef | columns (PostgreSQL type and modifier, NOT NULL, identity d for an auto-increment, generated column s), default values in the PostgreSQL language; view columns |
pg_index, pg_constraint | indexes (primary key <table>_pkey, uniqueness, secondary btree, full-text gin, spatial gist, vector hnsw); constraints p, u, f (actions, referenced columns), c |
pg_type, pg_enum, pg_range | base types and their arrays, an enumerated type <table>_<column>_enum per ENUM column and its values; pg_range empty |
pg_proc | stored routines (functions f, procedures p) and array_in / array_recv |
pg_database, pg_roles, pg_user, pg_authid, pg_auth_members | the miraj database; one role per account name (root superuser, OID 10); rolpassword always masked (******** in pg_roles and pg_user, NULL in pg_authid), never the SCRAM verifier (see 15.10.2) |
pg_description | comments on tables, columns and routines (COMMENT ON) |
pg_settings | parameters announced to clients (server_version, DateStyle, password_encryption = scram-sha-256…) and session variables (SHOW ALL) |
pg_am, pg_collation, pg_tablespace, pg_extension | access methods, collations default/C/POSIX, pg_default/pg_global, vector extension |
pg_trigger, pg_sequence | triggers and sequences |
pg_tables, pg_views, pg_indexes, pg_matviews, pg_stat_activity | usual views; pg_stat_activity lists the sessions (SHOW PROCESSLIST) |
pg_inherits, pg_partitioned_table, pg_depend, pg_locks, pg_stat_user_tables, pg_language, pg_shdescription, pg_policy, pg_publication*, pg_statistic_ext, pg_rewrite, pg_foreign_*, pg_opclass, pg_event_trigger, pg_init_privs | present and empty, so that the queries of clients that join them run |
The columns are those of PostgreSQL 16, in their order, with their type (oid, name, "char", int2, bool…); catalog arrays (conkey, proargnames, indkey…) are written as text, {1,3} or 1 3 for int2vector.
PostgreSQL functions#
They exist only in the PostgreSQL language, and take precedence over a MIRAJ function of the same name (version(), length()…).
| Functions | Role |
|---|---|
version() | PostgreSQL 16.4 (MIRAJ 1.0.0) on … |
current_database(), current_catalog, current_schema, current_schemas(bool) | miraj, current database, {pg_catalog,database} |
current_user, session_user, user, pg_backend_pid(), txid_current() | account without its host, connection number, transaction |
format_type(oid, typmod), pg_typeof(x) | character varying(80), numeric(12,2), timestamp without time zone… |
pg_get_indexdef(oid [, column]), pg_get_constraintdef(oid), pg_get_viewdef(oid), pg_get_expr(text, oid), pg_get_triggerdef(oid) | definitions, in the PostgreSQL language |
pg_get_function_result(oid), pg_get_function_arguments(oid), pg_get_userbyid(oid) | routines, roles |
obj_description(oid [, catalog]), col_description(oid, n), shobj_description | comments |
pg_table_is_visible, pg_type_is_visible, pg_function_is_visible | object of the current database or of pg_catalog |
has_table_privilege, has_schema_privilege… , pg_has_role | true: the catalog shows only what the account can see |
pg_relation_size, pg_total_relation_size, pg_table_size, pg_database_size, pg_size_pretty | in-memory sizes, written like PostgreSQL (20 kB) |
pg_encoding_to_char, quote_ident, quote_literal, quote_nullable | |
current_setting(name [, missing_ok]), set_config(name, value, local) | parameters; set_config returns the value without applying it; TimeZone, transaction_isolation and statement_timeout follow the session |
date_trunc(field, t), make_date(y, m, d), make_timestamp(…), make_time(h, m, s) | dates and times; date outside the calendar: 22008 |
to_char(t | n, pattern), to_date(text, pattern), to_timestamp(text, pattern), to_timestamp(seconds) | date patterns (YYYY, MM, DD, HH24, HH12, MI, SS, MS, US, AM, Month, Mon, Day, Dy, DDD, IW, Q, J, FM, TH, quoted text) and number patterns (9, 0, ., D, ,, G, S, MI, FM) |
age(a, b), age(t), justify_days, justify_hours, justify_interval | intervals written as text like PostgreSQL (2 years 7 mons 6 days); INTERVAL '45 days' written alone also returns its text |
to_regclass, to_regtype, to_regproc, to_regnamespace | OID of a name, NULL if it does not exist |
array_to_string, array_length, array_upper, array_lower, array_position, cardinality | on arrays written as text |
pg_is_in_recovery(), pg_column_is_updatable, pg_relation_is_publishable | false, true, false |
clock_timestamp(), statement_timestamp(), transaction_timestamp(), random(), gen_random_uuid(), pg_sleep(s), length(t) | MIRAJ functions under their PostgreSQL name (length counts characters) |
strpos(t, s), to_hex(n), encode(bytes, f), decode(t, f) | formats hex, base64, escape |
format(template, …) | %s, %I (quoted identifier), %L (quoted literal, NULL), %%, position %2$s, width %10s, %-10s, %*s |
regexp_replace(t, pattern, replacement [, start [, n]] [, flags]) | first match, all with g, the nth with n; case-sensitive unless i; \1…\9, \& in the replacement |
log(x), log(b, x), trunc(x [, n]), gcd, lcm, factorial, width_bucket(x, low, high, n) | log(x) is decimal (ln: natural) |
jsonb_build_object, json_build_object, jsonb_build_array, json_build_array, to_jsonb, to_json | JSON documents; a boolean becomes true / false |
jsonb_set(d, path, value [, create]), jsonb_array_length, jsonb_typeof, json_typeof | path written as an array ('{a,0}') |
string_agg(x, separator [ORDER BY …]), json_agg, jsonb_agg, json_object_agg, jsonb_object_agg, bool_and, bool_or, every | aggregates (GROUP_CONCAT, JSON_ARRAYAGG, JSON_OBJECTAGG, MIN, MAX) |
pg_advisory_lock(key), pg_try_advisory_lock(key), pg_advisory_unlock(key) | session advisory locks (bigint key or two integers), cumulative, released at the end of the session; MIRAJ named locks (GET_LOCK) named pg_advisory:<key> |
Conversions: 'clients'::regclass gives the OID of the table (42P01 if it does not exist), c.oid::regclass its name (qualified by its database outside the current database); likewise ::regtype (format_type), ::regproc, ::regnamespace, ::regrole.
Forms recognized for catalog queries: array[n] (i.indkey[0], (current_schemas(true))[1]), x = ANY (array) and x <> ALL (array) on a catalog array, generate_series(start, end [, step]) in a FROM (with AS s(n)), column aliases of a derived table ((SELECT …) AS x(a, b)), an alias after AS that is a reserved word (AS default). A condition (a = b, x IS NULL, EXISTS (…)…) placed in the list of a SELECT is a boolean (t / f).
Some fixed client queries use forms that the engine does not execute (functions that return rows such as information_schema._pg_expandarray, pg_partition_ancestors, arrays built by a correlated ARRAY(SELECT …)): the server recognizes their text (psql 10 to 18, pgjdbc 42.x, Npgsql 6 to 10) and rewrites them into equivalent queries on the same catalog.
Definitions and comments#
COMMENT ON TABLE clients IS 'Les clients';
COMMENT ON COLUMN clients.nom IS 'Nom complet'; -- IS NULL removes the comment
CREATE TABLE lignes (id serial PRIMARY KEY, commande bigint REFERENCES commandes, qte int);
CREATE INDEX lignes_qte ON lignes (qte);
DROP INDEX lignes_qte; -- without a table, like PostgreSQL
SHOW ALL;
SHOW statement_timeout;REFERENCES twithout columns designates the primary key oft(42830if it has none).DROP INDEX [IF EXISTS] [database.]namelooks for the table that carries the index in the current database (or the one written); one index per statement.COMMENT ONapplies to a table or a column; a column comment redefines the column (the table is rewritten).
29.10 Kept definitions and language switching#
A view, a default value, a CHECK constraint, a generated column, a partitioning, a REST endpoint or a LISTEN filter written in the PostgreSQL language are kept as MIRAJ text by the catalog (the query is rewritten from its tree). An instance can therefore change language (stop, change sql_mode, restart): its objects remain readable and usable in the other language.
Expressions kept with a table (generated column, expression index, default value, CHECK constraint) also keep the language of their creation: they are always computed according to its rules, whatever the language of the instance that writes or reads the table. A column GENERATED ALWAYS AS (split_part(code, '-', 1)) STORED or an index on (doc ->> 'k') created in the PostgreSQL language give the same value in an instance reopened in the MIRAJ language (split_part, strpos, jsonb_typeof… are still found there, length counts characters, ->> returns NULL for null), and a table created in the MIRAJ language keeps MIRAJ rules. The MIRAJ language itself does not gain the PostgreSQL functions: a query written in the MIRAJ language cannot call split_part (1305).
The partitioning function (and subpartitioning function) and the query of a view follow the same rule: a PARTITION BY LIST (length(a)) table created in the PostgreSQL language stores its rows by number of characters in any instance (including ADD / REORGANIZE PARTITION), and a view created in the PostgreSQL language is read in that language — its WITH CHECK OPTION condition too —, a view created in the MIRAJ language in that of MIRAJ. Only an UPDATE or a DELETE through a view, rewritten into a statement on the table, compiles the view's condition in the language of the session: a function specific to the other language is rejected there (1305).
Display follows the language of the instance: SHOW CREATE TABLE, SHOW CREATE VIEW and information_schema.VIEWS.VIEW_DEFINITION return, in the PostgreSQL language, a definition written in PostgreSQL (quoted identifiers, ||, LIMIT ALL, PostgreSQL types, indexes as CREATE INDEX), whatever the language in which the object was created. A BOOLEAN column (kept as TINYINT(1)) is written boolean there; ENUM(…) and SET(…) remain written as MIRAJ reads them (extension, values as standard strings), so that the text recreates the same column. SHOW CREATE SEQUENCE, SHOW CREATE ENDPOINT, SHOW CREATE USER and SHOW GRANTS return their text with PostgreSQL lexical conventions ("…" identifiers, standard strings): these lines can be replayed as is on a PostgreSQL instance (miraj-dump --users). A routine or trigger written in PL/pgSQL is kept as MIRAJ text with its original text alongside (29.13); written in the MIRAJ language, its body remains displayed in the MIRAJ language, like that of an event.
CREATE VIEW resume AS
SELECT nom || ' (' || etat || ')' AS libelle FROM articles WHERE nom ILIKE 'st%' FETCH FIRST 5 ROWS ONLY;
SHOW CREATE VIEW resume;
-- CREATE VIEW "resume" AS select ((("nom" || ' (') || "etat") || ')') as "libelle" from "articles"
-- where ("nom" like 'st%') limit 5A computed column without an alias keeps its written name (SELECT a + 1 FROM t names the column a + 1).
29.11 Writes: RETURNING, ON CONFLICT and COPY#
RETURNING#
INSERT, UPDATE and DELETE accept a RETURNING clause, written like the list of a SELECT (*, t.*, expressions, aliases): the statement returns one row per written row, in the order of the writes, then its usual tag (INSERT 0 2, UPDATE 1, DELETE 3).
INSERT INTO clients (nom) VALUES ('Dupont'), ('Durand') RETURNING id, nom, cree_le;
UPDATE stock SET quantite = quantite - 1 WHERE article = 12 RETURNING quantite;
DELETE FROM paniers WHERE expire < now() RETURNING *;
INSERT INTO t AS x (code, n) VALUES ('a', 1) ON CONFLICT (code) DO UPDATE SET n = x.n + 1 RETURNING x.n;INSERTandUPDATEreturn the values after the write: auto-increment value (SERIAL, identity),DEFAULT, generated columns, and what aBEFOREtrigger changed;DELETEreturns the values from before the deletion. The returned rows are read in the same transaction as the write, locks still held.- Expressions are checked before any write (an unknown column,
42703, modifies nothing); a subquery in the clause sees the state from before the statement, like PostgreSQL. - Extended protocol: Describe of the statement describes the columns of the clause (RowDescription), which is what pgjdbc's
getGeneratedKeysand Npgsql'sExecuteReaderexpect. - Privileges:
SELECTon the table, in addition to the write privilege. - An
UPDATE … RETURNINGis never deferred (deferred_update): its row is written right away so it can be read back. - Not supported (
0A000): through a view, on a partitioned table (Cluster edition), in a multi-tableUPDATEorDELETE(DELETE … USING), subquery correlated to the written row.
RETURNING exists only in the PostgreSQL language: the MIRAJ language is unchanged.
ON CONFLICT#
INSERT INTO t (id, code) VALUES (1, 'a') ON CONFLICT DO NOTHING;
INSERT INTO t (id, code) VALUES (1, 'a') ON CONFLICT (code) DO NOTHING;
INSERT INTO t (id, code) VALUES (1, 'a') ON CONFLICT ON CONSTRAINT t_pkey DO NOTHING;
INSERT INTO t AS x (code, n) VALUES ('a', 5), ('b', 2)
ON CONFLICT (code) DO UPDATE SET n = x.n + EXCLUDED.n WHERE x.n < 100;- The target designates the arbiter uniqueness constraint: a list of columns (the constraint whose columns are exactly these, otherwise
42P10) orON CONSTRAINT name(primary key named<table>_pkeyas inpg_catalog, or the name of aUNIQUEconstraint; unknown:42704). Without a target (DO NOTHINGonly), any uniqueness constraint is an arbiter. - Only the arbiter is handled: a row that violates another uniqueness constraint receives the
23505error, as in PostgreSQL (whereasON DUPLICATE KEY UPDATEreacts to any key). DO NOTHINGdiscards the proposed row without a warning; other errors (NOT NULL,CHECK, foreign keys) remain errors.DO UPDATE SET col = expr, …modifies the existing row: a column alone or qualified by the table (or its alias,INSERT INTO t AS x) designates the existing row,EXCLUDED.colthe proposed row (default values andBEFORE INSERTtrigger included),DEFAULTthe default value.WHERE condition(on the existing row andEXCLUDED) leaves the row as is when it is not true. The table'sUPDATEtriggers run.- A row inserted or modified by the statement cannot be modified a second time by it (two proposed rows with the same key):
21000, nothing is written. - Count: one per inserted or modified row (
INSERT 0 2), none forDO NOTHINGor a falseWHERE; a conflicting row modified without a change of value is counted too. - Not supported (
0A000): a target made of expressions (ON CONFLICT (lower(code))), of a partial index (WHERE), withCOLLATEor an operator class;DO UPDATE SET (a, b) = (…); through a view or on a partitioned table.
COPY#
| Form | Effect |
|---|---|
COPY t [(col, …)] FROM STDIN [[WITH] (options)] | rows sent by the client (CopyData), loaded at the end of the data; tag COPY n |
COPY t [(col, …)] TO STDOUT [[WITH] (options)] | rows of the table sent to the client, one per message; COPY n |
COPY (query) TO STDOUT [[WITH] (options)] | rows of a query (SELECT, or a write with a RETURNING clause) |
Options: FORMAT text (default) or FORMAT csv, DELIMITER 'c', NULL 'text', HEADER (skipped when reading, written first when writing), QUOTE 'c', ESCAPE 'c' (CSV), ENCODING 'UTF8', FREEZE (no effect). The legacy form of the options is accepted (WITH DELIMITER AS '|' NULL AS '', WITH CSV HEADER), as sent by psycopg2 (copy_from, copy_to) and old scripts.
- Text format: fields separated by a tab, NULL written
\N, escapes\t,\n,\r,\\,\b,\f,\v, octal (\101) and hexadecimal (\x41). CSV: comma, double quotes,""for a quote inside a quoted field, NULL = unquoted empty field,""= empty string; a quoted field may contain line breaks. - Received values: UTF-8 text (
22021otherwise); booleans written like PostgreSQL (t,true,yes,on,1…, otherwise22P02);byteain hexadecimal\x…or escaped form; date-time with offset (2026-10-06 10:00:00+02) loaded without the offset, like a literal; the other conversions are those ofLOAD DATAin the session'ssql_mode(strict mode: an invalid value is an error). Written values: those aSELECTreturns as text (t/f,byteaas\x…, ISO dates). - A row that does not have as many fields as columns:
22P04(missing data for column "nom",extra data after last expected column), with the row as context (COPY t, line 3). COPY … FROM STDINruns like anINSERT(INSERTprivilege, checked before the client sends the data; triggers, constraints, foreign keys) and as a single statement: an error, a client abort (CopyFail,57014) or a cancellation (Ctrl-C,57014) write nothing. After an error in the middle of the data, the server reads and discards the rest until the end of the transfer, then returns the error. The server first reads the table's columns: theSELECTprivilege is also required.COPY … TO STDOUTrequires theSELECTprivilege, like the corresponding query.- Without a column list, generated columns are excluded, in both directions.
psql:\copy t from 'file.csv' with (format csv, header)and\copy t to 'output.txt'read and write the file on the client's machine, throughCOPY … FROM STDIN/TO STDOUT.- Not supported (
0A000):FORMAT binary,HEADER MATCH,FORCE_QUOTE,FORCE_NOT_NULL,FORCE_NULL,DEFAULT,ON_ERROR,COPY … FROM … WHERE, an encoding other than UTF-8, andCOPYin the extended protocol (prepared statement). Server files:COPY t FROM '/path',COPY t TO '/path'andPROGRAMare rejected (0A000); to load or write a server file,LOAD DATA INFILEandSELECT … INTO OUTFILEremain available in the MIRAJ language, subject tosecure_file_privand theFILEprivilege.
The data of a COPY … FROM STDIN is kept in memory until the end of the transfer, then loaded through the LOAD DATA LOCAL INFILE path (no temporary file).
29.12 MIRAJ tools on a PostgreSQL instance#
MIRAJ tools connect to both languages through the same client (miraj-connect): they detect the instance's language at connection time and send it their statements in that language.
Protocol selection: --protocol auto | miraj | postgresql option of each tool (protocol key of the [monitor] table of proxy.toml), auto by default.
autofirst sends a PostgreSQL encryption request (SSLRequest, or GSSENCRequest with--no-ssl) and reads one byte.SorN: PostgreSQL instance, the exchange continues on the same connection (TLS, then SCRAM-SHA-256 or cleartext password). Any other byte is the first of the legacy protocol's greeting: the connection is closed and reopened in the MIRAJ language. The MIRAJ server recognizes this probe and closes it without counting it inAborted_connects(one "PostgreSQL client on a MIRAJ-language instance" line in its log).- The detected language is remembered per endpoint (
host:port) for the lifetime of the process: one extra connection, once, towards a MIRAJ instance. mirajandpostgresqlforce the protocol (a legacy client on a PostgreSQL instance waits for the server's connection timeout before being closed).- TLS options are the same in both protocols (
--ssl,--ssl-ca,--ssl-insecure,--no-ssl); the proxy relays the probe like any other bytes.
| Tool | On a PostgreSQL instance |
|---|---|
miraj-cli (network mode, chapter 25) | statements and files in the PostgreSQL language; database=> prompt; errors with their SQLSTATE |
miraj-dump (18.3) | script written in the PostgreSQL language, reloadable by psql -f, miraj-cli or miraj-dump restore |
miraj-backup (18.2) | BACKUP / RESTORE (MIRAJ extensions) written in this language; warnings received as notices |
miraj-migrate (chapter 17) | PostgreSQL target: definitions rewritten in this language, rows loaded by COPY … FROM STDIN, routines and triggers translated to PL/pgSQL (29.13) |
| Miraj Server Manager (chapter 21) | measurements and settings through queries valid in both languages; instance language displayed ("SQL language") |
| installer | verification of the root password in both protocols |
miraj-proxy (chapter 22) | probe in the nodes' protocol; refusal 9043 as ErrorResponse 08004 for a PostgreSQL client |
Export of a PostgreSQL instance (miraj-dump):
- tables through
SHOW CREATE TABLE(already in PostgreSQL), rows as multi-rowINSERT:"…"identifiers, standard strings, booleanstrue/false,byteaas'\x…'::bytea, floats at shortest representation (-1.5e300); sequences, views, REST endpoints, accounts and privileges in the same lexical conventions, withoutDELIMITER; - at the top:
SET client_encoding = 'UTF8'andSET foreign_key_checks = 0; reading is done in aREPEATABLE READ, READ ONLYtransaction; - procedures, functions and triggers written in PL/pgSQL are written in that language (29.13); those written in the MIRAJ language (created before a language change) are translated to PL/pgSQL on a best-effort basis, otherwise quoted as a comment with the reason, like events;
--users: the password is carried over as its hash (IDENTIFIED BY PASSWORD '*…'). A reloaded account has no SCRAM-SHA-256 verifier: it connects with a cleartext password (over TLS or on the loopback) until its password is reset (ALTER USER … IDENTIFIED BY …), like an account created before SCRAM (15.10.2). The tool reminds you of this;- an export from a MIRAJ instance cannot be reloaded as is into a PostgreSQL instance, nor the reverse: the export follows the language of the instance read.
miraj-dump --db shop --users -r shop.sql --port 7007
psql -h 127.0.0.1 -p 7008 -U root -d miraj -v ON_ERROR_STOP=1 -f shop.sql(on Windows, PGCLIENTENCODING=UTF8 or chcp 65001 for accented text.)
Cluster: all the nodes of a cluster have the same language. A node of another language is rejected on joining (error 9054, logged by both nodes): see 16.12.
29.13 Routines and triggers (PL/pgSQL)#
An instance in the PostgreSQL language accepts CREATE FUNCTION and CREATE PROCEDURE written in LANGUAGE plpgsql or LANGUAGE sql, and triggers CREATE TRIGGER … EXECUTE FUNCTION f(). They are translated at creation into MIRAJ routines and triggers, executed by the same interpreter as those written in the MIRAJ language. Whatever falls outside the subset described here is rejected at creation (0A000, the construct is named in the message): nothing is stored incorrectly.
CREATE FUNCTION add_tax(price numeric(10,2), rate numeric(4,2) DEFAULT 0.2)
RETURNS numeric(10,2) LANGUAGE plpgsql IMMUTABLE AS $$
DECLARE
result numeric(10,2);
BEGIN
result := price * (1 + rate);
RAISE NOTICE 'tax of % is %', price, result - price;
RETURN round(result, 2);
END;
$$;
SELECT add_tax(10); -- 12.00, with the notice "NOTICE: tax of 10.00 is 2.00"
CREATE PROCEDURE bump(INOUT n int, step int DEFAULT 1) LANGUAGE plpgsql AS $$
BEGIN
n := n + step;
END $$;
CALL bump(41); -- one row: n = 42
CREATE FUNCTION set_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NEW.updated_at := now();
RETURN NEW;
END $$;
CREATE TRIGGER set_updated_at BEFORE UPDATE ON products
FOR EACH ROW EXECUTE FUNCTION set_updated_at();CREATE FUNCTION and CREATE PROCEDURE header#
| Element | Supported |
|---|---|
CREATE [OR REPLACE], [schema.]name | the schema is a MIRAJ database; a single routine per name and kind (no overloading: OR REPLACE replaces the routine of the same name, whatever its parameters) |
| parameters | [IN | OUT | INOUT] [name] type [DEFAULT expr | = expr]; without a name, the parameter is read as $1, $2…; base types of 29.3, table.column%TYPE; the last arguments of a call may be omitted if they have a default value |
| result | RETURNS type, RETURNS void, RETURNS trigger; a function with one OUT (or INOUT) parameter returns its final value |
LANGUAGE plpgsql | sql | before or after AS |
| body | AS $$…$$, AS $label$…$label$, AS '…'; standard form RETURN expr (sql language) |
IMMUTABLE | STABLE | VOLATILE | kept (provolatile); IMMUTABLE makes the routine DETERMINISTIC |
STRICT, RETURNS NULL ON NULL INPUT, CALLED ON NULL INPUT | STRICT: NULL returned without running the body if an argument is NULL |
SECURITY INVOKER | DEFINER | INVOKER by default, like PostgreSQL (MIRAJ: SQL SECURITY DEFINER by default) |
[NOT] LEAKPROOF, PARALLEL …, COST n, ROWS n | accepted with no effect |
CALL p(…) of a procedure with OUT / INOUT parameters | returns a row of their final values (the argument of an OUT is ignored, often NULL), like PostgreSQL |
DROP FUNCTION | PROCEDURE [IF EXISTS] name [(types)] [CASCADE | RESTRICT] | types have no effect; a function called by triggers is dropped only with CASCADE (which drops them; otherwise error 9058, SQLSTATE 2BP01) |
A LANGUAGE sql function returns the result of its last SELECT (first row, NULL with no row); its earlier statements are writes. A LANGUAGE sql procedure runs only writes.
PL/pgSQL subset#
| Construct | Translation, remarks |
|---|---|
block [<<label>>] [DECLARE …] BEGIN … [EXCEPTION …] END [label] | MIRAJ block; nested blocks, variable shadowing |
DECLARE name [CONSTANT] type [NOT NULL] [{DEFAULT | := | =} expr] | base types, table.column%TYPE, variable%TYPE, table%ROWTYPE, RECORD; CONSTANT and NOT NULL require a value, with no check afterwards |
name ALIAS FOR $n, name CURSOR FOR query | cursor without arguments |
target := expr (or =) | variable, record field r.field, NEW.column |
IF … ELSIF … ELSE … END IF, CASE [value] WHEN v1, v2 THEN … END CASE | CASE with no matching branch and no ELSE: 20000 |
LOOP, WHILE … LOOP, EXIT [label] [WHEN c], CONTINUE [label] [WHEN c] | labeled loops |
FOR i IN [REVERSE] a .. b [BY s] LOOP | bounds evaluated once; i integer, local to the loop |
FOR r IN query LOOP, FOR r IN cursor LOOP | r: record or list of variables |
SELECT … INTO [STRICT] targets | first row, targets set to NULL with no row; STRICT: P0002 with no row, P0003 with several |
INSERT … VALUES (…) RETURNING columns INTO targets | one row; column written by the query or generated identity column |
FOUND, GET DIAGNOSTICS n = ROW_COUNT | maintained after SELECT … INTO, PERFORM, INSERT, UPDATE, DELETE, FETCH and FOR loops |
PERFORM query | query executed, result ignored, FOUND |
OPEN, FETCH [NEXT | FORWARD] [FROM] cursor INTO targets, CLOSE | declared cursors, read forward |
RAISE NOTICE | WARNING | INFO 'format %', args | notice returned to the client (NOTICE / WARNING / INFO, numbers 9055 to 9057 in SHOW WARNINGS), including from a function or a trigger; %% writes %; a NULL argument is written <NULL>; DEBUG and LOG: nothing |
RAISE [EXCEPTION] 'format', args [USING ERRCODE = …, MESSAGE = …], RAISE condition, RAISE SQLSTATE 'xxxxx', RAISE; | error with SQLSTATE P0001 by default, or the one given (condition name or code); RAISE; re-raises the handled error; DETAIL, HINT, COLUMN, CONSTRAINT, TABLE… accepted with no effect |
ASSERT condition [, message] | P0004 if the condition is false or NULL |
EXCEPTION WHEN condition [OR …] THEN … | OTHERS, PostgreSQL condition names (unique_violation, no_data_found, too_many_rows, division_by_zero, check_violation, foreign_key_violation, not_null_violation, raise_exception… and classes such as integrity_constraint_violation), SQLSTATE 'xxxxx'; SQLERRM, SQLSTATE and GET STACKED DIAGNOSTICS … = MESSAGE_TEXT | RETURNED_SQLSTATE in the handler (PostgreSQL SQLSTATE: 23505 for a duplicate) |
NULL;, CALL, INSERT, UPDATE, DELETE, TRUNCATE | body statements |
Triggers#
CREATE [OR REPLACE] TRIGGER name {BEFORE | AFTER} {INSERT | UPDATE [OF col, …] | DELETE} [OR …]
ON table FOR EACH ROW [WHEN (condition)] EXECUTE {FUNCTION | PROCEDURE} f([arguments]);
DROP TRIGGER [IF EXISTS] name ON table [CASCADE | RESTRICT];- Function
fis aRETURNS triggerfunction written in PL/pgSQL, without parameters. It readsNEW,OLD(NULL when the row does not exist:OLDof anINSERT,NEWof aDELETE),TG_OP,TG_NAME,TG_WHEN,TG_LEVEL,TG_TABLE_NAME,TG_RELNAME,TG_TABLE_SCHEMA,TG_NARGS,TG_ARGV[i]; it modifiesNEWin aBEFOREtrigger. RETURN NEW(orOLDfor aDELETE) lets the row be written;RETURN NULLin aBEFOREtrigger skips the row: it is neither inserted, modified nor deleted, is not counted, and the following triggers do not run.RETURN OLDof aBEFORE UPDATEkeeps the old row. The value returned by anAFTERtrigger is ignored. Reaching the end of the function withoutRETURN:2F005.- A single function can serve several triggers, on several tables; two tables may have a trigger of the same name. Replacing the function (
CREATE OR REPLACE FUNCTION) updates the triggers that call it. - The triggers of a table, for a given timing and event, run in alphabetical order of their names, as in PostgreSQL.
- Calling a trigger function directly (
SELECT f()):0A000.
A PostgreSQL trigger becomes a MIRAJ trigger per event, named name$table$event (set_updated_at$products$update), whose body is the translation of the function for that event. An instance in the MIRAJ language sees them under these names (SHOW TRIGGERS); in the PostgreSQL language, pg_trigger, \d table and DROP TRIGGER name ON table see them under their PostgreSQL name, one row per trigger.
Rejected (0A000)#
RETURNS SETOF, RETURNS TABLE, RETURN NEXT, RETURN QUERY (functions that return rows); function with several OUT parameters, parameter or result of composite type (RECORD, table); EXECUTE (dynamic SQL), FOR … IN EXECUTE; FOREACH and arrays; cursors with arguments, OPEN … FOR, refcursor, FETCH other than NEXT, MOVE; COMMIT / ROLLBACK in a procedure; VARIADIC, polymorphic types (anyelement…); INSERT … ON CONFLICT, UPDATE | DELETE … RETURNING, RETURNING of a row other than an INSERT … VALUES in the body; a SELECT without INTO in the body (42601, like PostgreSQL); other statements (DDL) in the body; LANGUAGE c, internal, plpython…; SET, WINDOW, TRANSFORM, SUPPORT clauses; BEGIN ATOMIC; FOR EACH STATEMENT triggers (or without FOR EACH ROW), INSTEAD OF, TRUNCATE, CONSTRAINT TRIGGER, REFERENCING; ALTER FUNCTION | PROCEDURE | TRIGGER; anonymous blocks DO $$ … $$.
Scheduled events (CREATE | ALTER EVENT): rejected in the PostgreSQL language. PostgreSQL has none (pg_cron is an extension) and a MIRAJ event written in PL/pgSQL would be invented syntax. Events created in the MIRAJ language, before a language change, continue to run.
Storage, display and language switching#
- The catalog keeps the MIRAJ text of the translation (like a view, 29.10): this is what runs, and an instance restarted in the MIRAJ language runs and displays (
SHOW CREATE FUNCTION, in the MIRAJ language) the routines and triggers created in PL/pgSQL. - The original text (
CREATE FUNCTION … AS $$…$$,CREATE TRIGGER …) is kept alongside: display in the PostgreSQL language comes from it, character for character for the body (prosrc,pg_get_functiondef,\sf), and a replaced trigger function is retranslated from it. Back in the PostgreSQL language, the displayed definitions are the same as before the switch. - A routine or trigger written in the MIRAJ language remains displayed in the MIRAJ language (
SHOW CREATE,pg_get_functiondef).
SHOW CREATE FUNCTION | PROCEDURE returns, for a routine written in PostgreSQL, the text of pg_get_functiondef; SHOW CREATE TRIGGER name (PostgreSQL name or MIRAJ name) that of pg_get_triggerdef. Since MIRAJ applies the precision of a parameter or of the result (a returned value is converted to the declared type), the displayed header keeps it (numeric(10,2); PostgreSQL forgets it and writes numeric).
Catalog, psql and tools#
pg_proc:prosrc(body as written),prolang(sql14,plpgsql13600, seepg_language),prokind,provolatile,proisstrict,prosecdef,pronargs,pronargdefaults,prorettype(void2278,trigger2279),proargtypes,proallargtypes,proargmodes,proargnames;pg_get_function_result,pg_get_function_arguments,pg_get_function_identity_arguments,pg_get_functiondef,'name'::regproc,'name(types)'::regprocedure.pg_trigger: one row per PostgreSQL trigger (tgtypeof its events,tgfoid,tgargs,tgattr,tgqual);pg_get_triggerdef(oid [, pretty]).- psql 18:
\df,\df+(volatility, security, language),\sf,\sf+,\d table("Triggers" section). miraj-dumpwrites routines and triggers as PL/pgSQL (CREATE OR REPLACE FUNCTION … AS $function$…$function$,DROP … CASCADEfirst; one trigger per name and per table, after the rows), withoutDELIMITER: the script reloads throughpsql -fandmiraj-dump restore. A routine written in the MIRAJ language is translated there to PL/pgSQL on a best-effort basis, otherwise quoted as a comment.miraj-migratetowards an instance in the PostgreSQL language translates the source's routines and triggers to PL/pgSQL (17); whatever cannot be translated is quoted with a warning.
Differences#
- An
EXCEPTIONhandler does not roll back the writes made by the block before the error (PostgreSQL rolls them back through an implicit savepoint); the failing statement itself is rolled back. The chosen handler is the most specific one that covers the error (number, SQLSTATE, thenOTHERS), not the first one written. - A function cannot call itself, directly or indirectly (1424: recursion rejected, as for MIRAJ routines).
UPDATE OF colfires when the value of one of those columns changes (case included), not when the column is merely named bySETwithout changing.%TYPE/%ROWTYPEtypes and the fields of aRECORDare resolved at creation: the tables (and the queries of aFOR r IN query) must exist; a table modified afterwards is not re-read (recreate the routine). Likewise,TG_TABLE_NAMEis fixed when the trigger is created.numericwithout precision isnumeric(38,10)(29.3): a result is displayed with ten decimals.- Text comparisons ignore case (MIRAJ collations), in the body as elsewhere.
- PostgreSQL-specific functions (
pg_sleep,format…) used in a body run only on an instance in the PostgreSQL language; an instance switched back to the MIRAJ language rejects them at execution. - A
RAISEnotice does not carry up theDETAIL/HINTfields. - A routine's variables mask a column of the same name in its queries (PostgreSQL reports the ambiguity).
- No comparison with a reference PostgreSQL server for this corpus (the local server could not be reached without changing its configuration): the behaviors described follow the PostgreSQL 16 documentation.
29.14 ODBC driver and Python library#
The MIRAJ ODBC Driver (chapter 28) and the Python miraj library (26.12) also connect to a PostgreSQL instance, without any API change, through the same PostgreSQL client as the tools (29.12):
- Protocol selection:
PROTOCOLkeyword of the connection string or ODBC data source,protocolparameter ofmiraj.connect();auto(default),mirajorpostgresql, with the same detection as the tools. - Parameters: the application's markers (
?in ODBC,%s/%(name)sin Python) become$1,$2… of a named server-side prepared statement, reused (repeatedSQLPreparethenSQLExecute,executemany); values sent as text with their type. - Results: columns described with their PostgreSQL type, precision and scale, their source table and column, their nullability and auto-increment (read from
pg_attribute);SQL_ATTR_MAX_ROWSlimits the rows sent by the server. - ODBC:
SQLGetInfoannouncesPostgreSQLand the"quote; catalog functions returnTABLE_CAT=mirajand the MIRAJ database asTABLE_SCHEM;SQLCancelsends a cancel request (CancelRequest), the statement fails withHY008. - Python:
mogrifywrites the literals of this language;copy_from,copy_toandcopy_expertgo throughCOPY … FROM STDIN/TO STDOUT;boolandjsondecoded, vector as text[x,y,…];lastrowidisNone(INSERT … RETURNING). - Errors: PostgreSQL SQLSTATE, with no MIRAJ code.
Third-party PostgreSQL drivers (psqlODBC, psycopg2/psycopg) also work.