Mirajv1.0
FR

41. PostgreSQL : Catalogue et outils

Ce chapitre décrit ce qu'un client voit d'une instance MIRAJ en langage PostgreSQL : le catalogue pg_catalog, information_schema et les instructions SHOW, puis les clients et pilotes (psql, psycopg2, pgjdbc, Npgsql, ODBC, bibliothèque Python miraj), les outils de MIRAJ sur une telle instance et la sauvegarde.

Les exemples portent sur le schéma ventes de la prise en main (tables client, article, commande, ligne_commande), complété d'une vue v_total_commande, d'une séquence seq_facture, d'une fonction ttc et d'un déclencheur trg_maj_stock.

41.1 Modèle : une base, un schéma par base MIRAJ#

Notion PostgreSQLDans MIRAJ
base de données (pg_database, current_database())une seule, miraj (OID 16384, encodage UTF8)
schéma (pg_namespace)une base MIRAJ ; s'y ajoutent pg_catalog et information_schema
schéma courant (current_schema, search_path)la base courante de la session
publicpas un schéma : dans un nom (public.client), alias de la base courante
propriétaire des objetsroot (OID 10)
SELECT oid, nspname FROM pg_namespace ORDER BY nspname;
    oid     |      nspname
------------+--------------------
      13183 | information_schema
  689122271 | miraj
         11 | pg_catalog
 1505511691 | ventes
(4 lignes)

Le schéma miraj est la base système de MIRAJ : seul root le voit. Un compte ordinaire ne voit que les schémas sur lesquels il a un droit (le compte app de la prise en main voit information_schema, pg_catalog et ventes).

SELECT current_database(), current_schema, current_schemas(true), current_schemas(false);
 current_database() | current_schema | current_schemas(true) | current_schemas(false)
--------------------+----------------+-----------------------+------------------------
 miraj              | ventes         | {pg_catalog,ventes}   | {ventes}
(1 ligne)

Identifiants d'objets (OID). Les types de base et les tables du catalogue gardent les OID de PostgreSQL (int4 23, text 25, numeric 1700, pg_class 1259). Les objets que vous créez reçoivent un OID calculé depuis leur genre, leur schéma et leur nom : il ne change pas d'un redémarrage à l'autre, mais renommer un objet change son OID.

Résolution des noms. pg_catalog passe avant tout schéma, comme dans PostgreSQL : SELECT * FROM pg_class lit la table du catalogue, sans préfixe. Une table d'un autre schéma se nomme schéma.table.

Vues sur le catalogue. Une vue peut lire les tables de pg_catalog et d'information_schema, seules, jointes, au travers d'une autre vue ou d'une expression WITH :

CREATE VIEW v_tables AS
SELECT n.nspname AS schema, c.relname AS nom
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r' AND n.nspname = 'ventes';

Sa requête est rejouée à chaque lecture, pour le compte qui la lit : elle suit le catalogue sans délai (une table créée y paraît aussitôt) et ne montre à chacun que ce que le catalogue lui montre. Aucun résultat n'en est gardé, même pour une vue créée par CREATE CACHED VIEW ; REFRESH VIEW y est sans effet. Une vue sur pg_authid ou pg_shadow reste réservée à qui administre les comptes, à la création comme à la lecture (42501).

Lecture seule. Le catalogue est reconstruit à chaque instruction qui le lit ; rien ne s'y écrit :

INSERT INTO pg_class (relname) VALUES ('x');
ERREUR 42501 : permission denied for schema pg_catalog

41.2 Le catalogue pg_catalog#

Tables et vues présentes#

Les colonnes sont celles de PostgreSQL 16, dans leur ordre et avec leur type.

Table ou vueContenu
pg_namespaceles schémas visibles du compte, pg_catalog, information_schema
pg_databasela base miraj
pg_classtables (relkind = r), vues (v), index (i), séquences (S), et les tables du catalogue
pg_attribute, pg_attrdefcolonnes des tables et des vues (type, attnotnull, identité attidentity = d pour un serial ou BY DEFAULT, a pour GENERATED ALWAYS, colonne générée), valeurs par défaut
pg_index, pg_indexesindex : clé primaire <table>_pkey, unicité, index secondaires (btree), plein texte (gin), spatiaux (gist), vectoriels (hnsw)
pg_constraintcontraintes p (clé primaire), u (unicité), f (clé étrangère), c (CHECK)
pg_type, pg_enum, pg_rangetypes de base et leurs tableaux (92 types), un type énuméré par colonne ENUM ; pg_range vide
pg_proc, pg_languagefonctions (prokind = f) et procédures (p) ; langages internal, c, sql, plpgsql
pg_triggerdéclencheurs, sous leur nom PostgreSQL
pg_sequence, pg_sequencesséquences (CREATE SEQUENCE) ; pg_sequences les présente avec leur schéma, leur propriétaire et leurs bornes
pg_views, pg_tables, pg_matviewsvues usuelles ; pg_matviews vide
pg_descriptioncommentaires des tables et des colonnes (COMMENT ON)
pg_roles, pg_user, pg_authid, pg_shadow, pg_auth_membersun rôle par compte, ses attributs (rolsuper, rolcreatedb, rolcreaterole) et les rôles accordés ; mot de passe toujours masqué ; pg_authid et pg_shadow réservées à qui administre les comptes (42501, permission denied for table pg_authid)
pg_settingsparamètres annoncés aux clients et variables de la session (209 lignes)
pg_stat_activitysessions ouvertes
pg_am, pg_collation, pg_tablespace, pg_extensionméthodes d'accès (heap, btree, hash, gist, gin, hnsw), collations default / C / POSIX, pg_default / pg_global, extension vector 0.8.0pg_inherits, pg_partitioned_table, pg_depend, pg_locks, pg_stat_user_tables, pg_shdescription, pg_policy, pg_publication, pg_publication_rel, pg_publication_namespace, pg_statistic_ext, pg_rewrite, pg_foreign_data_wrapper, pg_foreign_server, pg_foreign_table, pg_opclass, pg_event_trigger, pg_init_privsprésentes et vides, pour que les requêtes des clients qui les joignent s'exécutent

Les tableaux du catalogue (conkey, proargnames…) sont rendus en tableaux ({1,2}), indkey en int2vector (1 2).

Extensions. vector est la seule ligne de pg_extension et n'a pas à être installée. CREATE EXTENSION [IF NOT EXISTS] nom [WITH] [SCHEMA s] [VERSION v] [CASCADE] et DROP EXTENSION [IF EXISTS] nom [, …] sont acceptés sans effet pour plpgsql, pgcrypto, uuid-ossp, unaccent, pg_trgm, fuzzystrmatch, btree_gin, btree_gist et vector, dont les fonctions (gen_random_bytes, uuid_generate_v4, unaccent, similarity, levenshtein…) sont toujours disponibles (37. Fonctions SQL) : un script d'installation écrit pour PostgreSQL passe sans changement.

Objets d'un schéma#

SELECT c.relname, c.relkind, c.relnatts, c.reltuples
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'ventes'
ORDER BY c.relkind, c.relname;
       relname       | relkind | relnatts | reltuples
---------------------+---------+----------+-----------
 article_pkey        | i       |        1 |       3.0
 client_pkey         | i       |        1 |       3.0
 code                | i       |        1 |       3.0
 commande_pkey       | i       |        1 |       3.0
 email               | i       |        1 |       3.0
 idx_commande_date   | i       |        1 |       3.0
 ligne_commande_pkey | i       |        2 |       4.0
 article             | r       |        5 |       3.0
 client              | r       |        6 |       3.0
 commande            | r       |        4 |       3.0
 ligne_commande      | r       |        4 |       4.0
 seq_facture         | S       |        3 |       1.0
 v_total_commande    | v       |        0 |       0.0
(13 lignes)

Les noms sont ceux de MIRAJ : la clé primaire s'appelle <table>_pkey, une contrainte UNIQUE de colonne porte le nom de sa colonne (email, code), une clé étrangère <table>_ibfk_<n>, une contrainte CHECK sans nom CONSTRAINT_<n>, numérotée dans sa table (article et ligne_commande ont chacune leur CONSTRAINT_1).

Colonnes d'une table#

SELECT a.attnum, a.attname, format_type(a.atttypid, a.atttypmod) AS type,
       a.attnotnull, a.attidentity, pg_get_expr(d.adbin, d.adrelid) AS defaut
FROM pg_attribute a
LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
WHERE a.attrelid = 'client'::regclass AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum;
 attnum | attname |            type             | attnotnull | attidentity |      defaut
--------+---------+-----------------------------+------------+-------------+-------------------
      1 | id      | integer                     | t          | d           |
      2 | nom     | character varying(80)       | t          |             |
      3 | email   | character varying(120)      | t          |             |
      4 | ville   | text                        | f          |             |
      5 | actif   | boolean                     | t          |             | true
      6 | cree_le | timestamp without time zone | f          |             | CURRENT_TIMESTAMP
(6 lignes)

Dans pg_attribute, une colonne serial apparaît comme une colonne d'identité (attidentity = d), sans valeur par défaut dans pg_attrdef ; une colonne GENERATED ALWAYS AS IDENTITY porte a. information_schema.columns la présente, elle, comme PostgreSQL : nextval('client_id_seq'::regclass) (41.6).

Index et contraintes#

SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'commande' ORDER BY indexname;
     indexname     |                                 indexdef
-------------------+---------------------------------------------------------------------------
 commande_pkey     | CREATE UNIQUE INDEX commande_pkey ON ventes.commande USING btree (id)
 idx_commande_date | CREATE INDEX idx_commande_date ON ventes.commande USING btree (passee_le)
(2 lignes)
SELECT conname, contype, conkey, confrelid::regclass AS cible, pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE conrelid = 'ligne_commande'::regclass
ORDER BY conname;
        conname        | contype | conkey |  cible   |                             definition
-----------------------+---------+--------+----------+---------------------------------------------------------------------
 CONSTRAINT_1          | c       |        | 0        | CHECK (("quantite" > 0))
 ligne_commande_ibfk_1 | f       | {1}    | commande | FOREIGN KEY (commande_id) REFERENCES commande(id) ON DELETE CASCADE
 ligne_commande_ibfk_2 | f       | {2}    | article  | FOREIGN KEY (article_id) REFERENCES article(id)
 ligne_commande_pkey   | p       | {1,2}  | 0        | PRIMARY KEY (commande_id, article_id)
(4 lignes)

Pour une contrainte qui n'est pas une clé étrangère, confrelid::regclass rend 0.

Colonnes de la clé primaire d'une table :

SELECT a.attname
FROM pg_class c
JOIN pg_index i ON i.indrelid = c.oid AND i.indisprimary
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY (i.indkey)
WHERE c.relname = 'ligne_commande'
ORDER BY a.attnum;
   attname
-------------
 commande_id
 article_id
(2 lignes)

Tables qui référencent une table :

SELECT conrelid::regclass AS table_enfant, conname, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE contype = 'f' AND confrelid = 'client'::regclass;
 table_enfant |     conname     |           pg_get_constraintdef(oid)
--------------+-----------------+-----------------------------------------------
 commande     | commande_ibfk_1 | FOREIGN KEY (client_id) REFERENCES client(id)
(1 ligne)

Vues, séquences, routines, déclencheurs#

SELECT pg_get_viewdef('v_total_commande'::regclass);
 select "o"."id", "o"."client_id", sum(("l"."quantite" * "l"."prix_unit")) as "montant" from "commande" as "o" join "ligne_commande" as "l" on ("l"."commande_id" = "o"."id") group by "o"."id","o"."client_id";

La définition est celle que MIRAJ garde : la requête est réécrite, identifiants toujours entre guillemets. pg_get_viewdef la rend précédée d'une espace et terminée par ;, comme PostgreSQL ; pg_views.definition et information_schema.views.view_definition rendent le même texte, sans l'espace ni le ;.

SELECT seqrelid::regclass AS sequence, seqstart, seqincrement, seqmin, seqmax, seqcache, seqcycle
FROM pg_sequence;
  sequence   | seqstart | seqincrement | seqmin |       seqmax        | seqcache | seqcycle
-------------+----------+--------------+--------+---------------------+----------+----------
 seq_facture |     1000 |            1 |      1 | 9223372036854775806 |     1000 | f
(1 ligne)
SELECT p.proname, p.prokind, l.lanname, p.provolatile,
       pg_get_function_arguments(p.oid) AS arguments, pg_get_function_result(p.oid) AS resultat
FROM pg_proc p
JOIN pg_language l ON l.oid = p.prolang
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'ventes';
  proname  | prokind | lanname | provolatile |                    arguments                     |   resultat
-----------+---------+---------+-------------+--------------------------------------------------+---------------
 maj_stock | f       | plpgsql | v           |                                                  | trigger
 ttc       | f       | plpgsql | i           | ht numeric(10,2), taux numeric(4,2) DEFAULT 0.19 | numeric(10,2)
(2 lignes)
SELECT pg_get_functiondef('ttc'::regproc);
CREATE OR REPLACE FUNCTION ventes.ttc(ht numeric(10,2), taux numeric(4,2) DEFAULT 0.19)
 RETURNS numeric(10,2)
 LANGUAGE plpgsql
 IMMUTABLE
AS $function$
BEGIN
  RETURN round(ht * (1 + taux), 2);
END;
$function$
SELECT tgname, tgrelid::regclass AS table, tgfoid::regproc AS fonction, pg_get_triggerdef(oid) AS definition
FROM pg_trigger;
    tgname     |     table      | fonction  |                                                  definition
---------------+----------------+-----------+--------------------------------------------------------------------------------------------------------------
 trg_maj_stock | ligne_commande | maj_stock | CREATE TRIGGER trg_maj_stock AFTER INSERT ON ventes.ligne_commande FOR EACH ROW EXECUTE FUNCTION maj_stock()
(1 ligne)

Les routines et déclencheurs sont décrits au chapitre 40.

Commentaires#

COMMENT ON porte sur une table ou une colonne ; IS NULL retire le commentaire. Un commentaire de colonne redéfinit la colonne : la table est réécrite.

COMMENT ON TABLE client IS 'Clients de la boutique';
COMMENT ON COLUMN client.email IS 'Adresse de contact, unique';
SELECT obj_description('client'::regclass, 'pg_class') AS de_table,
       col_description('client'::regclass, 3) AS de_colonne;
        de_table        |         de_colonne
------------------------+----------------------------
 Clients de la boutique | Adresse de contact, unique
(1 ligne)

Rôles#

SELECT rolname, rolsuper, rolcanlogin, rolpassword FROM pg_roles ORDER BY rolname;
 rolname | rolsuper | rolcanlogin | rolpassword
---------+----------+-------------+-------------
 app     | f        | t           | ********
 root    | t        | t           | ********
(2 lignes)

Un rôle par nom de compte (sans son hôte). Le mot de passe n'est jamais rendu : ******** dans pg_roles et pg_user, NULL dans pg_authid. rolsuper, rolcreatedb et rolcreaterole suivent les privilèges globaux du compte, pg_auth_members les rôles accordés, et pg_has_role les interroge (voir 39.2 et 38.5). Les droits détaillés ne sont pas dans le catalogue (relacl est NULL) : SHOW GRANTS FOR compte les donne (voir 39).

Fonctions d'information#

FonctionsRésultat
version()PostgreSQL 16.4 (MIRAJ 1.0.0) on x86_64-windows, 64-bit (aarch64-macos, x86_64-linux… selon la plate-forme du serveur)
current_database(), current_catalogmiraj
current_schema, current_schemas(bool)schéma courant ; {pg_catalog,ventes}
current_user, current_role, session_user, pg_get_userbyid(oid)nom du compte, sans son hôte
pg_backend_pid()numéro de la session (celui de pg_stat_activity.pid et de KILL)
pg_postmaster_start_time(), pg_conf_load_time()instant du démarrage du serveur, du chargement de sa configuration
inet_server_addr(), inet_client_addr()adresses du serveur et du client de la connexion (127.0.0.1)
pg_reload_conf()t
txid_current(), pg_current_xact_id(), txid_current_if_assigned(), txid_current_snapshot()numéro de la transaction en cours (NULL pour txid_current_if_assigned() hors écriture), instantané (626841:626841:)
pg_advisory_lock(n), pg_try_advisory_lock(n), pg_advisory_unlock(n), pg_advisory_xact_lock(n), pg_try_advisory_xact_lock(n)verrous consultatifs, de session ou de transaction ; les formes try rendent t ou f
pg_column_size(x), pg_input_is_valid(texte, type)taille d'une valeur en octets (pg_column_size('abc'::text) : 4) ; pg_input_is_valid('x', 'integer') : f
pg_terminate_backend(pid [, délai]), pg_cancel_backend(pid)ferme la session pid, interrompt sa requête en cours : t ; f avec un avertissement si pid est inconnu
format_type(oid, typmod), pg_typeof(x)character varying(80), numeric(10,2), timestamp without time zone…
pg_get_indexdef(oid), pg_get_constraintdef(oid), pg_get_viewdef(oid), pg_get_expr(texte, oid)définitions, en langage PostgreSQL
pg_get_functiondef(oid), pg_get_function_arguments(oid), pg_get_function_identity_arguments(oid), pg_get_function_result(oid), pg_get_triggerdef(oid)routines et déclencheurs (pg_get_function_identity_arguments : ht numeric(10,2), taux numeric(4,2), sans valeurs par défaut)
obj_description(oid [, catalogue]), col_description(oid, n), shobj_description(oid, catalogue)commentaires
to_regclass(nom), to_regtype(nom), to_regnamespace(nom), to_regproc(nom), to_regrole(nom)OID, ou NULL si le nom n'existe pas
pg_table_is_visible(oid), pg_function_is_visible(oid), pg_type_is_visible(oid)t pour un objet du schéma courant ou de pg_catalog
pg_relation_filepath(relation)chemin de la relation à la manière de PostgreSQL (base/16384/1075441615)
has_table_privilege([rôle,] table, privilège), has_column_privilege([rôle,] table, colonne, privilège), has_any_column_privilege(…), has_schema_privilege([rôle,] schéma, privilège), has_function_privilege([rôle,] fonction, 'EXECUTE')privilèges réels du compte de la session, ou d'un autre compte pour qui administre les comptes (voir 39.5) ; un objet que le compte ne voit pas rend 42P01 / 3F000 / 42883 ; has_database_privilege, has_sequence_privilege, has_type_privilege : toujours t
pg_has_role([compte,] rôle, 'MEMBER' | 'USAGE' | 'SET')le rôle est-il accordé au compte (voir 39.5) ; rôle inconnu : 42704
pg_relation_size(relation), pg_total_relation_size(relation), pg_table_size(relation), pg_indexes_size(relation), pg_database_size(nom), pg_size_pretty(n)tailles en octets ; la relation se donne par son OID ou par son nom (pg_relation_size('client'), 'ventes.client'), un nom inconnu rend 42P01 (relation "absente" does not exist) ; pg_indexes_size rend la taille estimée des index de la table, que pg_total_relation_size ajoute à celle de la table (client : 456 + 276 = 732) ; pg_size_pretty(20480::bigint) rend 20 kB
current_setting(nom [, absent_ok])valeur d'un paramètre ; inconnu : 42704 (unrecognized configuration parameter "nexiste"), ou NULL avec true
pg_encoding_to_char(n), quote_ident(t), quote_literal(t), quote_nullable(t)UTF8 ; "Mon Nom", 'l''été', NULL
pg_is_in_recovery()f

Conversions de noms :

ÉcritureRésultat
'client'::regclassOID de la table du schéma courant ('ventes.client'::regclass pour un autre schéma) ; 42P01 (relation "absente" does not exist) si elle n'existe pas
c.oid::regclass, conrelid::regclassnom de la table
'int4'::regtype::oid23 ; atttypid::regtype rend le nom (integer, numeric)
'ttc'::regproc, tgfoid::regprocOID d'une routine, son nom

41.3 information_schema et instructions SHOW#

information_schema#

information_schema est celui de MIRAJ, présenté en langage PostgreSQL à la manière de PostgreSQL : les colonnes de catalogue (table_catalog, constraint_catalog, catalog_name…) portent le nom de la base PostgreSQL de l'instance, miraj, celui que rend current_database() (le langage MIRAJ y lit def, comme la référence). Ses tables usuelles répondent aux requêtes portables, avec quelques différences à connaître :

VueRemarques
schematacatalog_name vaut miraj ; liste aussi miraj, performance_schema et sys
tablestable_catalog = miraj ; table_type : BASE TABLE, VIEW, SEQUENCE
columnsdata_type porte le nom du standard (integer, character varying, timestamp without time zone, boolean, numeric) ; une colonne serial est présentée comme dans PostgreSQL : column_default = nextval('<table>_<colonne>_seq'::regclass), is_identity = NO ; une colonne GENERATED … AS IDENTITY porte is_identity = YES, identity_generation ALWAYS ou BY DEFAULT et un column_default NULL ; udt_name porte le nom interne du type (int4, int8, varchar, bpchar, bool, timestamptz, float8… ; <table>_<colonne>_enum pour un type énuméré, comme pg_type.typname), udt_schema son schéma (pg_catalog, ou celui de la table pour un type énuméré), udt_catalog vaut miraj
routinesdata_type porte le nom du standard (trigger, void, record pour ces pseudo-types) ; type_udt_name le nom interne du type du résultat, type_udt_schema vaut pg_catalog, type_udt_catalog miraj (NULL pour une procédure)
table_constraintsla clé primaire s'y appelle <table>_pkey, comme dans pg_constraint ; porte table_catalog
constraint_column_usagecolonnes sur lesquelles porte chaque contrainte : celles d'une clé primaire ou UNIQUE, les colonnes référencées d'une clé étrangère, celles que cite un CHECK ; vue du langage PostgreSQL seulement (absente d'une instance en langage MIRAJ, où une vue qui la lit reste lisible)
key_column_usageporte aussi referenced_table_name et referenced_column_name pour une clé étrangère
referential_constraintsupdate_rule, delete_rule (NO ACTION, CASCADE…)
check_constraintscheck_clause est écrite en langage PostgreSQL, identifiants entre guillemets doubles (("prix" >= 0))
viewsview_definition en langage PostgreSQL
routinesdata_type porte le nom du standard (integer, text, character varying…), trigger pour une fonction de déclencheur, void, record
triggersun déclencheur écrit en langage PostgreSQL y porte son nom, une ligne par événement, action_statement = EXECUTE FUNCTION f(…) et sa condition WHEN dans action_condition
sequencesséquences créées par CREATE SEQUENCE
domainsvue présente, vide
schema_privileges, table_privilegesdroits accordés (grantee = 'app'@'%')

Les noms de ces vues et de leurs colonnes s'écrivent indifféremment en minuscules ou en majuscules.

SELECT constraint_name, table_name, constraint_type
FROM information_schema.table_constraints
WHERE table_schema = 'ventes' AND table_name IN ('client', 'commande')
ORDER BY table_name, constraint_name;
 constraint_name | table_name | constraint_type
-----------------+------------+-----------------
 client_pkey     | client     | PRIMARY KEY
 email           | client     | UNIQUE
 commande_ibfk_1 | commande   | FOREIGN KEY
 commande_pkey   | commande   | PRIMARY KEY
(4 lignes)
SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
FROM information_schema.key_column_usage
WHERE table_schema = 'ventes' AND referenced_table_name IS NOT NULL
ORDER BY table_name, constraint_name;
    constraint_name    |   table_name   | column_name | referenced_table_name | referenced_column_name
-----------------------+----------------+-------------+-----------------------+------------------------
 commande_ibfk_1       | commande       | client_id   | client                | id
 ligne_commande_ibfk_1 | ligne_commande | commande_id | commande              | id
 ligne_commande_ibfk_2 | ligne_commande | article_id  | article               | id
(3 lignes)

Pour un outil écrit pour PostgreSQL, préférer pg_catalog, plus fidèle ; information_schema convient aux requêtes simples (liste des tables, des colonnes).

Instructions SHOW#

SHOW nom lit un paramètre, comme dans PostgreSQL ; SHOW ALL rend tous les paramètres (name, setting, description).

SHOW server_version;          -- 16.4
SHOW search_path;             -- ventes
SHOW sql_mode;                -- POSTGRESQL,STRICT_TRANS_TABLES
SHOW transaction_isolation;   -- read committed
SHOW statement_timeout;       -- 5min

Les instructions SHOW de MIRAJ restent disponibles, en extension :

InstructionRend
SHOW DATABASESles schémas (bases MIRAJ)
SHOW TABLES, SHOW FULL TABLEStables, vues et séquences du schéma courant, avec leur genre
SHOW CREATE TABLE t, SHOW CREATE VIEW v, SHOW CREATE SEQUENCE s, SHOW CREATE FUNCTION fla définition, écrite en langage PostgreSQL
SHOW COLUMNS FROM t, DESCRIBE t, SHOW INDEX FROM t, SHOW TABLE STATUSdescription à la manière de MIRAJ : types de MIRAJ (int(11), longtext, tinyint(1), datetime(6)), clé primaire PRIMARY
SHOW TRIGGERScolonnes de MIRAJ ; un déclencheur écrit en langage PostgreSQL y porte son nom et, pour Statement, EXECUTE FUNCTION f(…) (une ligne par événement)
SHOW PROCESSLISTsessions (voir 41.7)
SHOW GRANTS FOR comptedroits d'un compte
SHOW VARIABLES LIKE '…', SHOW STATUS LIKE '…'variables et compteurs du serveur
SHOW CREATE TABLE commande;
CREATE TABLE "commande" (
  "id" integer GENERATED BY DEFAULT AS IDENTITY (START WITH 4) NOT NULL,
  "client_id" integer NOT NULL,
  "passee_le" date NOT NULL DEFAULT CURRENT_DATE,
  "etat" character varying(12) NOT NULL DEFAULT 'ouverte'::character varying,
  PRIMARY KEY ("id"),
  CONSTRAINT "commande_ibfk_1" FOREIGN KEY ("client_id") REFERENCES "client" ("id")
);
CREATE INDEX "idx_commande_date" ON "commande" ("passee_le")

START WITH donne le numéro que recevra la prochaine ligne : un script rejoué ailleurs reprend la numérotation là où elle en était.

EXPLAIN requête rend le plan de MIRAJ, dans sa propre présentation (colonnes id, select_type, table, type, possible_keys, key, key_len, ref, rows, Extra) :

EXPLAIN SELECT * FROM client WHERE id = 1;
 id | select_type | table  |  type  | possible_keys |   key   | key_len |  ref  | rows | Extra
----+-------------+--------+--------+---------------+---------+---------+-------+------+-------
  1 | SIMPLE      | client | eq_ref | PRIMARY,email | PRIMARY |         | const |    1 |
(1 ligne)

41.4 psql#

$env:PGPASSWORD = "mot de passe"
psql -h 127.0.0.1 -p 7007 -U root -d ventes
psql "host=serveur.exemple port=7007 user=root dbname=ventes sslmode=require"

Le paramètre -d (ou dbname) nomme le schéma courant (voir 32.2). Le serveur ne travaille qu'en UTF-8 : sous Windows, chcp 65001 ou PGCLIENTENCODING=UTF8.

Les commandes de description lisent pg_catalog :

CommandeMontre
\ll'unique base miraj
\dnles schémas, c'est-à-dire les bases MIRAJ
\dt, \dv, \di, \ds, \dtables, vues, index, séquences du schéma courant ; \dt ventes.* pour un autre schéma
\d table, \d+ tablecolonnes (types PostgreSQL, valeurs par défaut, identité), index, contraintes, clés étrangères, déclencheurs
\ducomptes
\df, \df+, \sf fonctionroutines, texte d'une fonction
\dxextension vector
\dptables, sans droits détaillés
\xaffichage étendu
\copy t from 'fichier.csv' with (format csv, header), \copy t to 'sortie.txt'charge ou écrit un fichier du poste client (voir 35)

Ces commandes envoient des requêtes ordinaires sur le catalogue. Celle de \d sans argument (tables, vues et séquences du schéma courant) rend ici :

 Schema |       Name       |   Type   | Owner
--------+------------------+----------+-------
 ventes | article          | table    | root
 ventes | client           | table    | root
 ventes | commande         | table    | root
 ventes | ligne_commande   | table    | root
 ventes | seq_facture      | sequence | root
 ventes | v_total_commande | view     | root
(6 lignes)

Une requête saisie ou passée par -c peut contenir plusieurs instructions ; chacune rend son résultat et son étiquette. Ctrl-C annule la requête en cours (57014). psql -f script.sql exécute un script, par exemple un export de miraj-dump (41.9).

Non pris en charge : \gdesc. Les requêtes du catalogue sont celles de psql 10 à 18 ; une version qui en changerait le texte peut recevoir une erreur de syntaxe sur une commande \d.

41.5 Pilotes et bibliothèques#

Tout pilote PostgreSQL se connecte au port du serveur, avec le nom d'un schéma pour base. Le pilote ODBC de MIRAJ et la bibliothèque Python miraj parlent aussi les deux langages.

PiloteConnexion
psycopg2 (Python)psycopg2.connect(host=…, port=7007, user=…, password=…, dbname="ventes")
pgjdbc (Java)jdbc:postgresql://serveur:7007/ventes
Npgsql (.NET)Host=serveur;Port=7007;Username=…;Password=…;Database=ventes
MIRAJ ODBC DriverDRIVER={MIRAJ ODBC Driver};SERVER=serveur;PORT=7007;UID=…;PWD=…;DATABASE=ventes
bibliothèque Python mirajmiraj.connect(host=…, port=7007, user=…, password=…, dbname="ventes")

Paramètres annoncés à la connexion : server_version = 16.4, server_encoding = client_encoding = UTF8, DateStyle = ISO, MDY, integer_datetimes = on, standard_conforming_strings = on, TimeZone = le fuseau du serveur. Un autre client_encoding est refusé (0A000).

psycopg2#

import psycopg2, psycopg2.extras

conn = psycopg2.connect(host="127.0.0.1", port=7007, user="app",
                        password="secret123", dbname="ventes")
cur = conn.cursor()

cur.execute("SELECT code, prix, stock FROM article WHERE prix > %s ORDER BY id", (2,))
for code, prix, stock in cur:
    print(code, prix, stock)          # CAH-96 2.50 200 / CLA-A4 4.90 80

cur.execute("INSERT INTO client (nom, email) VALUES (%(nom)s, %(email)s) RETURNING id",
            {"nom": "Nadia Haddad", "email": "[email protected]"})
identifiant = cur.fetchone()[0]
conn.commit()
SujetComportement
Paramètres%s et %(nom)s, substitués par psycopg2 ; cursor.mogrify écrit 'l''été', NULL, '2026-01-02'::date
Types reçusint, str, bool, Decimal (numeric), float, date, time, datetime (avec fuseau pour un timestamptz), dict / list (json, jsonb), memoryview (bytea), str (uuid)
Types envoyéspsycopg2.extras.Json(…) pour une colonne jsonb, psycopg2.Binary(…) pour un bytea, Decimal, dates et heures
Étiquettecursor.statusmessage : SELECT 3, INSERT 0 1, UPDATE 2… ; cursor.rowcount
Identifiant généréINSERT … RETURNING id ; cursor.lastrowid vaut 0
Transactionsouvertes à la première instruction ; commit(), rollback(), autocommit, with conn: ; set_session(isolation_level=…, readonly=True) (une écriture reçoit alors 25006)
Erreursclasses de psycopg2 d'après le SQLSTATE : UndefinedTable (42P01), UniqueViolation (23505), InFailedSqlTransaction (25P02), QueryCanceled (57014)… ; e.pgcode, e.diag.message_primary
AvisRAISE NOTICE d'une fonction : conn.notices (NOTICE: bonjour 42)
Lotsexecutemany, psycopg2.extras.execute_values, execute_batch
Curseur nomméconn.cursor(name="grand") : DECLARE … CURSOR côté serveur, lu par fetchmany ; dans une transaction
Curseurs à dictionnaireRealDictCursor, DictCursor
Copiescopy_from, copy_to, copy_expert (texte et CSV)
Annulationconn.cancel() depuis un autre fil : la requête échoue en 57014 ; SET statement_timeout TO 1000 aussi
Mode asynchronepsycopg2.connect(async_=1, …) avec wait_select

Chargement et export en masse :

import io

donnees = io.StringIO("GOM-01\tGomme blanche\t0.80\t300\nREG-30\tRègle 30 cm\t1.90\t120\n")
cur.copy_from(donnees, "article", columns=("code", "libelle", "prix", "stock"))

sortie = io.StringIO()
cur.copy_expert("COPY (SELECT code, libelle, prix FROM article ORDER BY id) "
                "TO STDOUT WITH (FORMAT csv, HEADER)", sortie)
print(sortie.getvalue())
code,libelle,prix
STY-01,Stylo bille,1.30
CAH-96,Cahier 96 pages,2.50
CLA-A4,Classeur A4,4.90
GOM-01,Gomme blanche,0.80
REG-30,Règle 30 cm,1.90

À savoir :

  • cursor.callproc("f", …) appelle une fonction : psycopg2 envoie SELECT * FROM f(…), qui rend une ligne d'une colonne. Pour une procédure, écrire cur.execute("CALL p(%s)", …).
  • Les options de la connexion (options="-c statement_timeout=5000", formes -c nom=valeur et --nom=valeur, une barre oblique inverse devant un espace de la valeur) sont appliquées avant la première requête, comme autant de SET : SHOW statement_timeout rend 5s, et RESET statement_timeout comme RESET ALL y reviennent. Une option inconnue, une valeur refusée ou un paramètre du démarrage du serveur (shared_buffers…) refuse la connexion (FATAL, 42704, 22023, 55P02…), comme PostgreSQL. Les autres options de la ligne de commande du serveur PostgreSQL (-d, -S…) sont refusées (42601).
  • Les paramètres qu'un pilote écrit dans le message de démarrage lui-même (pgjdbc et Npgsql : client_encoding, DateStyle, TimeZone, extra_float_digits, search_path, application_name…) sont posés de la même façon, comme valeurs de départ de la session. Là, un nom inconnu ou une valeur que le serveur ne sait pas honorer (client_encoding autre qu'UTF8, DateStyle autre qu'ISO, fuseau inconnu) est ignoré sans refuser la connexion.
  • Une instruction execute qui contient plusieurs requêtes ne rend que le résultat de la dernière, comme avec PostgreSQL.

pgjdbc et Npgsql#

Ces pilotes préparent leurs requêtes : ils emploient le protocole étendu (paramètres $1, $2…, valeurs en texte ou en binaire), que le serveur prend en charge. Le chapitre 16 en donne le détail et la liste des fonctions essayées (16.10.4).

PilotePris en charge
pgjdbc 42.7PreparedStatement typé, instructions préparées côté serveur, lots (executeBatch), setFetchSize, niveaux d'isolement, getGeneratedKeys (RETURNING), CopyManager en texte et CSV, DatabaseMetaData (getSchemas, getTables, getColumns, getPrimaryKeys, getImportedKeys, getExportedKeys, getIndexInfo, getTypeInfo…), ResultSet modifiable
Npgsql 10Open (types chargés depuis pg_type), commandes paramétrées typées, commande préparée réutilisée, transactions, NpgsqlBatch, annulation, ExecuteReader sur INSERT … RETURNING, BeginTextImport / BeginTextExport, GetSchema (Tables, Columns, Indexes, ForeignKeys…)
DBeaverpasse par pgjdbc : navigateur (schémas, tables, colonnes, clés, index) et édition des lignes
await using var conn = new NpgsqlConnection("Host=serveur;Port=7007;Username=app;Password=…;Database=ventes");
await conn.OpenAsync();
await using var cmd = new NpgsqlCommand("SELECT libelle, prix FROM article WHERE prix > $1", conn);
cmd.Parameters.Add(new NpgsqlParameter { Value = 2.0m, NpgsqlDbType = NpgsqlDbType.Numeric });

Limites communes : pas de COPY binaire (BeginBinaryImport de Npgsql), pas de COPY dans une instruction préparée.

Pilote ODBC de MIRAJ#

MIRAJ ODBC Driver (installation et sources de données : chapitre 29) reconnaît le langage de l'instance à la connexion ; PROTOCOL=postgresql dans la chaîne l'impose.

import pyodbc

cn = pyodbc.connect("DRIVER={MIRAJ ODBC Driver};SERVER=127.0.0.1;PORT=7007;"
                    "UID=app;PWD=secret123;DATABASE=ventes")
cur = cn.cursor()
cur.execute("SELECT id, nom, actif FROM client WHERE ville = ?", "Alger")
print(cur.fetchall())                 # [(1, 'Amel Kaci', True)]
cur.execute("INSERT INTO client (nom, email) VALUES (?, ?) RETURNING id", "Essai", "[email protected]")
print(cur.fetchone()[0])
cn.commit()
DomaineSur une instance en langage PostgreSQL
SQLcelui de ce manuel ; les marqueurs ? sont liés comme paramètres ; les séquences d'échappement ODBC sont traduites ({fn UCASE('abc')}, {d '2026-10-11'}, {fn CONVERT('12', SQL_INTEGER)})
SQLGetInfoSQL_DBMS_NAME = PostgreSQL, SQL_DBMS_VER = 16.04.0000, guillemet d'identifiant ", SQL_SCHEMA_TERM = schema, SQL_DATABASE_NAME = miraj
CatalogueSQLTables : TABLE_CAT = miraj, TABLE_SCHEM = le schéma (ventes), types TABLE, VIEW, SEQUENCE ; SQLColumns : types nommés int4, varchar, text, numeric… ; SQLPrimaryKeys (clé nommée <table>_pkey), SQLForeignKeys, SQLStatistics
Typesbool rendu en booléen, numeric en décimal exact ; SQLGetTypeInfo : text, json, jsonb, varchar, bpchar, uuid, bool, int8, bytea, numeric, int4, int2, float4, float8, date, time, timestamp, timestamptz
Résultatsplusieurs instructions dans un texte : un résultat par instruction (SQLMoreResults, cursor.nextset())
ErreursSQLSTATE de PostgreSQL (42P01, 23505…), code natif 0
Base inconnueDATABASE=inconnue : connexion refusée, 3D000
TLSSSLMODE=required sur un serveur sans certificat : 08001 (« TLS exigé, mais le serveur ne le propose pas »)
Identifiant généréINSERT … RETURNING (le protocole ne transmet pas de dernier identifiant)

COPY n'est pas disponible par ODBC. Les autres limites du pilote sont au 29.6. Un pilote PostgreSQL tiers (psqlODBC) se connecte aussi à une telle instance.

Bibliothèque Python miraj#

La bibliothèque miraj (27.12) offre l'interface DB-API de psycopg2 vers les deux langages : une même application se connecte à une instance MIRAJ ou PostgreSQL sans changer d'API.

import miraj

conn = miraj.connect(host="127.0.0.1", port=7007, user="app", password="secret123",
                     dbname="ventes", protocol="postgresql")
cur = conn.cursor()
cur.execute("SELECT libelle, prix FROM article WHERE prix > %s", (2,))
  • protocol : auto (défaut, langage reconnu à la connexion), miraj ou postgresql. dbname nomme le schéma ; un schéma inconnu est refusé.
  • Les %s deviennent les paramètres $1, $2… d'une instruction préparée gardée par connexion et réutilisée (executemany prépare une fois).
  • statusmessage porte l'étiquette du serveur ; lastrowid vaut None : utiliser INSERT … RETURNING.
  • copy_from, copy_to, copy_expert et insert_rows passent par COPY.
  • Erreurs : SQLSTATE de PostgreSQL (e.sqlstate, e.pgcode) ; avis dans connection.notices.
  • Pas de curseur nommé (cursor(name=…) : NotSupportedError) : le résultat est lu en entier à l'exécution.

41.6 Requêtes courantes sur le catalogue#

Tables d'un schéma, avec leur taille et leur commentaire :

SELECT n.nspname AS schema, c.relname AS table,
       pg_size_pretty(pg_total_relation_size(c.oid)) AS taille,
       obj_description(c.oid, 'pg_class') AS commentaire
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r' AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'miraj')
ORDER BY 1, 2;
 schema |     table      |  taille   |      commentaire
--------+----------------+-----------+------------------------
 ventes | article        | 589 bytes |
 ventes | client         | 732 bytes | Clients de la boutique
 ventes | commande       | 344 bytes |
 ventes | ligne_commande | 176 bytes |
(4 lignes)

Les tailles sont celles des données en mémoire, index compris, et non celles des fichiers.

Existence d'une table, sans erreur si elle manque :

SELECT to_regclass('client') IS NOT NULL AS client, to_regclass('absente') IS NOT NULL AS absente;
 client | absente
--------+---------
 t      | f
(1 ligne)

Colonnes d'une table par information_schema :

SELECT column_name, data_type, is_nullable, column_default, udt_name, is_identity
FROM information_schema.columns
WHERE table_schema = 'ventes' AND table_name = 'client'
ORDER BY ordinal_position;
 column_name |          data_type          | is_nullable |           column_default           | udt_name  | is_identity
-------------+-----------------------------+-------------+------------------------------------+-----------+-------------
 id          | integer                     | NO          | nextval('client_id_seq'::regclass) | int4      | NO
 nom         | character varying           | NO          |                                    | varchar   | NO
 email       | character varying           | NO          |                                    | varchar   | NO
 ville       | text                        | YES         |                                    | text      | NO
 actif       | boolean                     | NO          | true                               | bool      | NO
 cree_le     | timestamp without time zone | YES         | now()                              | timestamp | NO
(6 lignes)

Types connus du serveur :

SELECT typname, oid, typcategory FROM pg_type
WHERE typname IN ('bool', 'int4', 'numeric', 'text', 'timestamptz', 'jsonb', 'uuid', 'vector')
ORDER BY typname;
   typname   |  oid  | typcategory
-------------+-------+-------------
 bool        |    16 | B
 int4        |    23 | N
 jsonb       |  3802 | U
 numeric     |  1700 | N
 text        |    25 | S
 timestamptz |  1184 | D
 uuid        |  2950 | U
 vector      | 16385 | U
(8 lignes)

41.7 Sessions et paramètres#

SELECT pid, usename, datname, client_addr, state, query FROM pg_stat_activity ORDER BY pid;
 pid | usename | datname | client_addr | state  |                                       query
-----+---------+---------+-------------+--------+------------------------------------------------------------------------------------
   1 | root    | miraj   | localhost   | idle   |
  28 | root    | miraj   | localhost   | active | SELECT pid, usename, datname, client_addr, state, query FROM pg_stat_activity ORDER BY pid
(2 lignes)

pg_stat_activity reprend les sessions de SHOW PROCESSLIST. Un compte ordinaire n'y voit que ses propres sessions. application_name est celui que la session a donné à la connexion ou par SET ; backend_start n'est pas renseignée.

Une session se ferme par pg_terminate_backend(pid), ou par l'instruction KILL de MIRAJ, avec son pid ; pg_cancel_backend(pid) et KILL QUERY pid n'interrompent que la requête en cours (57014). La session fermée reçoit l'erreur fatale 57P01 (terminating connection due to administrator command) avant la fin de sa connexion (voir le chapitre 38).

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND state = 'idle';
 pg_terminate_backend
----------------------
 t
 t
(2 lignes)

KILL 53 ferme de même la session 53 et rend l'étiquette KILL.

Les deux fonctions rendent t ; pour un pid inconnu, f avec l'avertissement PID 99999 is not a PostgreSQL backend process (KILL répond 42704, Unknown thread id: 99999). Un compte ne ferme que ses propres sessions, sauf droit d'administration (PROCESS) : 42501, permission denied to terminate process (You are not owner of thread 1 pour KILL).

Paramètres :

SELECT name, setting FROM pg_settings
WHERE name IN ('server_version', 'server_version_num', 'client_encoding', 'DateStyle', 'TimeZone',
               'max_connections', 'password_encryption', 'search_path', 'transaction_isolation',
               'statement_timeout')
ORDER BY name;
         name          |    setting
-----------------------+----------------
 DateStyle             | ISO, MDY
 TimeZone              | Etc/GMT-1
 client_encoding       | UTF8
 max_connections       | 151
 password_encryption   | scram-sha-256
 search_path           | ventes
 server_version        | 16.4
 server_version_num    | 160004
 statement_timeout     | 5min
 transaction_isolation | read committed
(10 lignes)

pg_settings réunit les paramètres que PostgreSQL annonce aux clients et les variables de session de MIRAJ (autocommit, lock_wait_timeout…). Comme dans PostgreSQL, ses textes se comparent et se trient octet par octet : les noms à majuscules (DateStyle, TimeZone) viennent en tête, et name = 'datestyle' ne trouve rien. statement_timeout vaut par défaut la durée maximale d'une instruction fixée au serveur (5 minutes). current_setting('nom') lit une valeur ; les réglages se font par SET (voir 38).

41.8 Outils MIRAJ#

Les outils de MIRAJ reconnaissent le langage de l'instance à la connexion et lui parlent dans ce langage. L'option --protocol auto | miraj | postgresql (défaut auto) permet de l'imposer ; les options de connexion sont les mêmes dans les deux langages (--host, --port, --user, --password, variable MIRAJ_PASSWORD, --ssl, --ssl-ca, --ssl-insecure, --no-ssl).

OutilSur une instance en langage PostgreSQL
miraj-cliconsole SQL : instructions et fichiers en langage PostgreSQL, invite schéma=>
miraj-backupsauvegarde physique à chaud et restauration (41.9)
miraj-dumpexport en un script SQL écrit en langage PostgreSQL (41.9)
miraj-migratemigration vers MIRAJ : définitions récrites en langage PostgreSQL, lignes chargées par COPY, routines traduites en PL/pgSQL (chapitre 18)
Miraj Server Managersupervision et réglages ; affiche le langage de l'instance (chapitre 22)
miraj-proxyrelaie les connexions PostgreSQL sans changement (chapitre 23)

miraj-cli#

$env:MIRAJ_PASSWORD = "mot de passe"
miraj-cli.exe --port 7007 -d ventes
OK, 0 ligne(s) affectée(s)
MIRAJ 1.0.0 - connecté (langage PostgreSQL) - tapez \help pour l'aide, \q pour quitter.
ventes=> SELECT code, prix FROM article ORDER BY id LIMIT 2;
+--------+------+
| code   | prix |
+--------+------+
| STY-01 | 1.30 |
| CAH-96 | 2.50 |
+--------+------+
2 ligne(s)
ventes=> SELECT 1/0;
ERREUR 22012 : division by zero
UsageCommande
console interactivemiraj-cli --port 7007 -d ventes
une ou plusieurs instructionsmiraj-cli --port 7007 -d ventes -e "SELECT 1 AS a; SELECT 2 AS b;"
un scriptmiraj-cli --port 7007 script.sql : instructions exécutées une à une, arrêt à la première erreur (code de sortie 1)
résultat en JSON--json : [ {"code": "STY-01", "prix": 1.30} ], suivi de 1 ligne(s)
  • Avec -d, la console pose d'abord le schéma courant ; la ligne OK, 0 ligne(s) affectée(s) qui précède la bannière (ou le premier résultat de -e) en est le compte rendu.
  • Les erreurs s'affichent avec leur SQLSTATE (ERREUR 42P01 : relation "absente" does not exist).
  • Avec -e et plusieurs instructions, les résultats de celles qui précèdent une erreur sont affichés, puis l'erreur ; les suivantes ne sont pas exécutées.
  • Un script est découpé selon la lexicographie de PostgreSQL : chaînes standard ('l''été'), E'…', $$…$$.
  • \l liste les schémas (SHOW DATABASES), \d les tables du schéma courant (SHOW TABLES), \d table décrit une table à la manière de MIRAJ (DESCRIBE : types int(11), longtext…). Pour une description en types PostgreSQL, interroger pg_attribute (41.2) ou SHOW CREATE TABLE.
  • Ctrl-C interrompt l'instruction en cours (une boucle de procédure, une longue requête) et rend la main à l'invite.
  • Code de sortie 2 : connexion impossible (mot de passe faux : connexion à [email protected]:7007 impossible : erreur SQL 28P01 : Access denied for user 'root'@'localhost' (using password: YES)).
  • Avec --protocol miraj sur une instance PostgreSQL, la connexion échoue après une attente (server disconnected) : laisser auto.

Le reste de la console (historique, \timing, source) est décrit au chapitre 26.

41.9 Sauvegarde et restauration#

Deux méthodes, les mêmes que dans le langage MIRAJ (chapitre 19) :

MéthodeNatureOutil
copie physique à chaudfichiers du schéma, restauration immédiateBACKUP DATABASE / RESTORE DATABASE, miraj-backup
export SQLscript en langage PostgreSQL, lisible et portablemiraj-dump

BACKUP DATABASE et RESTORE DATABASE#

Ces instructions sont des extensions de MIRAJ, disponibles en langage PostgreSQL. Le serveur doit être démarré avec un dossier de sauvegarde (--backup-dir E:\sauvegardes, ou <backup_dir> dans miraj_config.xml) ; le dossier nommé dans l'instruction est relatif à celui-ci.

BACKUP DATABASE ventes TO 'sauv1';
 Database |      Directory       | Files | Bytes | End_lsn
----------+----------------------+-------+-------+---------
 ventes   | E:\sauvegardes\sauv1 |     9 |  7247 |    1464
(1 ligne)
RESTORE DATABASE ventes_copie FROM 'sauv1';
WARNING:  base ventes restaurée sous le nom ventes_copie : les références qualifiées par l'ancien nom (vues, routines, clés étrangères) le gardent
   Database   |      Directory       | Files | Bytes | End_lsn
--------------+----------------------+-------+-------+---------
 ventes_copie | E:\sauvegardes\sauv1 |     9 |  7247 |    1464
(1 ligne)

Le schéma restauré est aussitôt utilisable (SELECT count(*) FROM ventes_copie.client;) et apparaît dans pg_namespace. La sauvegarde ne bloque pas les écritures.

Sans --backup-dir, l'instruction est refusée (XX000, the server is running without the --backup-dir option so it cannot execute this statement) ; avec un chemin hors de ce dossier aussi (XX000, The server is running with the --backup-dir option so it cannot execute this statement).

miraj-backup#

$env:MIRAJ_PASSWORD = "mot de passe"
miraj-backup backup  --db ventes --to sauv2 --port 7007
miraj-backup restore --from sauv2 --db ventes_copie --replace --port 7007
miraj-backup verify  --from E:\sauvegardes\sauv2
miraj-backup list    --from E:\sauvegardes\sauv2
ventes : 9 fichier(s), 7.1 Ko, position 1464 → E:\sauvegardes\sauv2
ventes_copie : 9 fichier(s), 7.1 Ko, position 1464 → E:\sauvegardes\sauv2
Sauvegarde valide : base ventes, 9 fichier(s), 7.1 Ko, position 1464

backup et restore envoient BACKUP / RESTORE au serveur (le dossier est celui du serveur, relatif à son --backup-dir) ; --all sauvegarde tous les schémas, --replace remplace un schéma existant. verify et list lisent un dossier local. Le compte doit avoir le droit BACKUP_ADMIN. Une copie physique ne contient pas les comptes : les garder par miraj-dump --users.

miraj-dump#

miraj-dump --db ventes --users -r ventes.sql --port 7007
miraj-dump restore ventes.sql --port 7008
psql -h 127.0.0.1 -p 7008 -U root -d miraj -v ON_ERROR_STOP=1 -f ventes.sql

Sur une instance en langage PostgreSQL, le script est écrit dans ce langage et se recharge par miraj-dump restore, miraj-cli ou psql -f :

  • tables telles que SHOW CREATE TABLE les rend, lignes en INSERT multi-lignes (identifiants entre guillemets, chaînes standard, booléens true / false, bytea en '\x…'::bytea) ;
  • séquences, vues, routines et déclencheurs en PL/pgSQL (CREATE OR REPLACE FUNCTION … AS $function$…$function$), sans DELIMITER ;
  • la lecture se fait dans une seule transaction : le script reflète un instant unique, sans bloquer les autres sessions ;
  • --users ajoute les comptes et leurs droits. Un compte rechargé se connecte par mot de passe en clair, sous TLS ou en boucle locale, jusqu'à ce que son mot de passe soit redéfini (ALTER USER … IDENTIFIED BY …) ; un tel script est un fichier sensible, à protéger comme un fichier de mots de passe ;
  • un export suit le langage de l'instance lue : celui d'une instance en langage MIRAJ ne se recharge pas dans une instance en langage PostgreSQL, ni l'inverse.

Les options d'export (--tables, --no-data, --no-routines…) sont décrites au 19.3.

41.10 Non pris en charge et écarts#

SujetSur une instance MIRAJ
Vues du catalogue absentespg_stat_database, pg_stat_all_tables, pg_statio_user_tables, pg_stats, pg_prepared_statements, pg_cursors, pg_available_extensions, pg_timezone_names, pg_operator : 42P01
Tables présentes mais videspg_locks, pg_stat_user_tables, pg_depend, pg_inherits… (41.2) : pas de verrous ni de statistiques d'accès par ce biais
Fonctions absentesinet_server_port, pg_current_wal_lsn : 42883
Fonctions de tailletailles en mémoire ; pg_indexes_size est une estimation (largeur moyenne des clés par ligne vivante)
'nom'::regclass écrit seulrend l'OID (un nombre) ; oid::regclass rend le nom
confrelid::regclass d'une contrainte sans table cible0 (PostgreSQL : -)
Droitsrelacl, attacl, nspacl NULL ; has_table_privilege, has_column_privilege, has_any_column_privilege et has_schema_privilege rendent les privilèges réels (9.5), les autres has_*_privilege toujours t ; pg_shadow et pg_authid réservées à qui administre les comptes, mot de passe toujours NULL
pg_stat_activitybackend_start non renseigné ; client_addr en texte (localhost) ; une transaction ouverte et inactive reste idle
txid_current()numéro positif, le même dans toute la transaction, mais propre à MIRAJ : ni croissant d'une transaction à l'autre, ni comparable à ceux de PostgreSQL
pg_settingscolonnes short_desc, unit vides pour les variables de MIRAJ ; la ligne application_name suit la valeur donnée à la connexion ou par SET
COMMENT ONtables et colonnes seulement ; COMMENT ON VIEW, FUNCTION, SCHEMA… : 0A000
CREATE EXTENSION, VACUUMVACUUM : erreur de syntaxe 42601 ; ANALYZE : 0A000 ; CREATE EXTENSION d'une extension autre que celles de 41.2 (postgis…) : 0A000. L'extension vector est toujours présente ; CREATE EXTENSION vector est accepté sans effet
information_schemacelui de MIRAJ : colonnes propres à MIRAJ (column_type, column_key, extra…) ; les colonnes ajoutées pour PostgreSQL (udt_name, udt_schema, type_udt_name, table_constraints.table_catalog) valent aussi au travers d'une vue
EXPLAINplan de MIRAJ, dans sa présentation
Noms de contraintesceux de MIRAJ (<table>_pkey, <table>_ibfk_<n>, CONSTRAINT_<n>, nom de la colonne pour un UNIQUE de colonne)
OID des objetscalculés depuis le nom : stables, mais changés par un renommage
pg_get_viewdef, CHECKidentifiants toujours entre guillemets ; requête réécrite
psql\gdesc refusé
psycopg2callproc d'une procédure : 42883 (écrire CALL) ; client_encoding autre qu'UTF-8 : 0A000 ; lastrowid = 0
ODBCpas de COPY
LISTEN / NOTIFYacceptés, mais le client ne reçoit pas de notification asynchrone : voir 38
DROP INDEX a, b0A000 : un index par instruction

La liste condensée de ce que le catalogue contient est au 30.9.