Mirajv1.0
FR

32. PostgreSQL : Prise en main

Ce chapitre vous guide pas à pas : démarrer une instance MIRAJ en langage PostgreSQL, s'y connecter avec un client PostgreSQL (psql, psycopg2, un pilote) ou avec la console miraj-cli, créer un schéma, des tables, y écrire et y lire des données, indexer et inspecter le schéma. À la fin, vous saurez faire tourner un cas d'usage complet sans quitter la syntaxe de PostgreSQL.

Les résultats sont montrés tels qu'un client PostgreSQL les reçoit : un tableau de lignes, puis l'étiquette de l'instruction (SELECT 3, INSERT 0 1, CREATE TABLE…). Les erreurs sont données avec leur SQLSTATE.

32.1 Démarrer le serveur en langage PostgreSQL#

Une instance MIRAJ parle un seul langage SQL, choisi au démarrage par l'élément POSTGRESQL de sql_mode :

miraj-server.exe --root D:\donnees --log --sql-mode "STRICT_TRANS_TABLES,POSTGRESQL"
MIRAJ 1.0.0 Entreprise - serveur console sur 127.0.0.1:7007, langage PostgreSQL, dossier "D:\donnees", 12 fils, licence ENT-0001 (Exemple SARL).
Configuration : D:\donnees\miraj_config.xml.

La mention langage PostgreSQL confirme le choix ; la fin de la ligne rappelle la licence installée. Le dossier D:\donnees est créé s'il n'existe pas, avec un compte root local dont le mot de passe, tiré au sort, s'affiche une seule fois juste au-dessus : notez-le, puis remplacez-le (32.9). Laissez cette fenêtre ouverte : le serveur y reste actif.

PointValeur
Portle port principal du serveur (--port, 7007 par défaut) : il n'y a pas de second port, et le port 5432 n'est pas utilisé
Langagefixé pour toute l'instance ; le changer demande un redémarrage, les bases et leurs données restent valables
Réglage permanent<sql_mode>STRICT_TRANS_TABLES,POSTGRESQL</sql_mode> dans miraj_config.xml, ou le choix Langage SQL de l'éditeur de configuration
VérificationSHOW sql_mode; rend STRICT_TRANS_TABLES,POSTGRESQL

Les autres options du serveur (dossier de données, journal, TLS, service Windows) ne dépendent pas du langage : voir l'administration du serveur, et sa section 16.10 pour ce qui est propre à une instance en langage PostgreSQL.

32.2 Ce qu'il faut savoir avant de se connecter#

Un client PostgreSQL voit l'instance ainsi :

Notion PostgreSQLDans MIRAJ
base de données (dbname)une seule : miraj
schémaune base MIRAJ ; CREATE SCHEMA ventes et CREATE DATABASE ventes créent la même chose
schéma courant, search_pathla base courante de la session ; SET search_path TO ventes la choisit
publicn'est pas un schéma : dans un nom (public.client), c'est un alias de la base courante
version annoncéePostgreSQL 16.4 (MIRAJ 31.0.0) (server_version = 16.4)
encodageUTF-8 seulement (client_encoding = UTF8)

Le paramètre dbname de la connexion ne choisit donc pas une base PostgreSQL, mais le schéma courant :

dbname donnéEffet
nom d'une base MIRAJ (ventes)elle devient le schéma courant
miraj, postgres, le nom du compte, ou rienignoré : la connexion s'ouvre sans schéma courant (current_schema est NULL)
un autre nom, inconnuavis NOTICE: database "inconnue" does not exist; connected without a current database, et la connexion s'ouvre sans schéma courant

Sans schéma courant, les tables se nomment schéma.table, ou l'on en choisit un par SET search_path TO ventes;.

32.3 Se connecter avec psql#

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

Sous Windows, passez la console en UTF-8 (chcp 65001) ou fixez PGCLIENTENCODING=UTF8 : un texte accentué saisi dans une autre page de codes est refusé. Les commandes de description (\dn, \dt, \d table…) fonctionnent ; elles sont décrites au chapitre 41. Catalogue et outils.

Premier contrôle, valable dans tout client :

SELECT version();
                                 version()
----------------------------------------------------------------------------
 PostgreSQL 16.4 (MIRAJ 1.0.0) on x86_64-windows, 64-bit
(1 ligne)
SELECT current_database(), current_schema, current_user;
 current_database() | current_schema | current_user
--------------------+----------------+--------------
 miraj              | ventes         | root
(1 ligne)

32.4 Se connecter avec miraj-cli#

La console SQL livrée avec MIRAJ se connecte aussi à une instance en langage PostgreSQL : elle reconnaît le langage à la connexion. Le mot de passe se donne par --password (saisie masquée) ou par la variable d'environnement MIRAJ_PASSWORD.

miraj-cli.exe --port 7007 --user root --password -d ventes
MIRAJ 1.0.0 - connecté (langage PostgreSQL) - tapez \help pour l'aide, \q pour quitter.
ventes=>

L'invite ventes=> montre la base courante (miraj=> sans base courante) ; chaque instruction se termine par un point-virgule et s'écrit en langage PostgreSQL. Les résultats sont encadrés à la manière de miraj-cli :

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

Un fichier de script s'exécute en le nommant (miraj-cli --port 7007 script.sql), une instruction par -e "…". Détails au chapitre 41.

32.5 Se connecter depuis une application#

Tout pilote PostgreSQL convient ; seuls l'hôte, le port et le nom du schéma changent par rapport à un serveur PostgreSQL.

Python (psycopg2)#

L'exemple lit et écrit la table client créée en 32.7.

import psycopg2

conn = psycopg2.connect(host="127.0.0.1", port=7007, user="root",
                        password="mot de passe", dbname="ventes")
cur = conn.cursor()
cur.execute("SELECT id, nom FROM client WHERE ville = %s", ("Alger",))
print(cur.fetchall())          # [(1, 'Amel Kaci')]
print(cur.statusmessage)       # SELECT 1

cur.execute("INSERT INTO client (nom, email, ville) VALUES (%s, %s, %s) RETURNING id",
            ("Nadia Haddad", "[email protected]", "Oran"))
print(cur.fetchone()[0])       # 4 : identifiant généré
conn.commit()
conn.close()

La forme URI est acceptée aussi : psycopg2.connect("postgresql://root:[email protected]:7007/ventes").

  • Les valeurs reviennent dans les types Python habituels : int, str, bool pour un boolean, decimal.Decimal pour un numeric, datetime.date, datetime.datetime (avec fuseau pour un timestamptz, comme now()), dict pour un jsonb.
  • psycopg2 ouvre une transaction à la première instruction : rien n'est gardé sans conn.commit() (ou conn.autocommit = True).
  • L'identifiant d'une ligne insérée se lit par INSERT … RETURNING id ; cursor.lastrowid vaut toujours 0.
  • Après une erreur dans une transaction, les instructions suivantes reçoivent 25P02 jusqu'à conn.rollback(), comme sur un serveur PostgreSQL.

Autres pilotes#

PiloteChaîne de connexion
Npgsql (.NET)Host=127.0.0.1;Port=7007;Username=root;Password=…;Database=ventes
pgjdbc (Java)jdbc:postgresql://127.0.0.1:7007/ventes
ODBC (MIRAJ ODBC Driver)DRIVER={MIRAJ ODBC Driver};SERVER=127.0.0.1;PORT=7007;UID=root;PWD=…;DATABASE=ventes

Le chapitre 41 décrit chaque pilote, ce qu'il prend en charge et ses limites.

Refus de connexion#

CasCe que reçoit le client
mot de passe faux ou compte inconnuFATAL: Access denied for user 'root'@'localhost' (using password: YES) (SQLSTATE 28P01) ; rien ne distingue les deux cas
sslmode=require sur un serveur démarré sans certificatserver does not support SSL, but SSL was required (message du client)
client du protocole hérité (langage MIRAJ)la connexion reste muette puis est fermée par le serveur : ce client ne parle pas à une instance PostgreSQL

L'authentification (SCRAM-SHA-256) et le chiffrement TLS sont décrits au chapitre 39. Comptes et privilèges.

32.6 Créer un schéma#

CREATE SCHEMA ventes;
SET search_path TO ventes;
CREATE SCHEMA
SET

SET search_path fait de ventes le schéma courant de la session ; SHOW search_path; et SELECT current_schema; rendent alors ventes. Dans une application, donnez plutôt le schéma à la connexion (dbname=ventes).

32.7 Créer des tables#

Quatre tables : des clients, des articles, des commandes et leurs lignes. serial donne une clé auto-incrémentée ; les contraintes NOT NULL, UNIQUE, CHECK, REFERENCES et DEFAULT s'écrivent comme dans PostgreSQL.

CREATE TABLE client (
    id       serial PRIMARY KEY,
    nom      varchar(80) NOT NULL,
    email    varchar(120) NOT NULL UNIQUE,
    ville    text,
    actif    boolean NOT NULL DEFAULT true,
    cree_le  timestamp DEFAULT now()
);

CREATE TABLE article (
    id       serial PRIMARY KEY,
    code     varchar(20) NOT NULL UNIQUE,
    libelle  text NOT NULL,
    prix     numeric(10,2) NOT NULL CHECK (prix >= 0),
    stock    integer NOT NULL DEFAULT 0
);

CREATE TABLE commande (
    id          serial PRIMARY KEY,
    client_id   integer NOT NULL REFERENCES client (id),
    passee_le   date NOT NULL DEFAULT current_date,
    etat        varchar(12) NOT NULL DEFAULT 'ouverte'
);

CREATE TABLE ligne_commande (
    commande_id integer NOT NULL REFERENCES commande (id) ON DELETE CASCADE,
    article_id  integer NOT NULL REFERENCES article (id),
    quantite    integer NOT NULL CHECK (quantite > 0),
    prix_unit   numeric(10,2) NOT NULL,
    PRIMARY KEY (commande_id, article_id)
);
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE

Les types et les contraintes sont détaillés aux chapitres 33. Types de données et 34. Langage DDL.

32.8 Écrire des données#

INSERT INTO client (nom, email, ville) VALUES
    ('Amel Kaci', '[email protected]', 'Alger'),
    ('Youssef Ben Ali', '[email protected]', 'Constantine'),
    ('Claire Dupont', '[email protected]', 'Lyon');
INSERT 0 3

RETURNING rend les lignes écrites, avec leurs valeurs générées :

INSERT INTO article (code, libelle, prix, stock) VALUES
    ('STY-01', 'Stylo bille', 1.20, 500),
    ('CAH-96', 'Cahier 96 pages', 2.50, 200),
    ('CLA-A4', 'Classeur A4', 4.90, 80)
RETURNING id, code;
 id |  code
----+--------
  1 | STY-01
  2 | CAH-96
  3 | CLA-A4
(3 lignes)

INSERT 0 3
INSERT INTO commande (client_id, passee_le) VALUES
    (1, '2026-10-01'), (1, '2026-10-05'), (2, '2026-10-06');
INSERT INTO ligne_commande VALUES
    (1, 1, 10, 1.20), (1, 2, 4, 2.50), (2, 3, 2, 4.90), (3, 2, 20, 2.30);
INSERT 0 3
INSERT 0 4

Les contraintes sont appliquées, avec les SQLSTATE de PostgreSQL :

INSERT INTO client (nom, email) VALUES ('Doublon', '[email protected]');
INSERT INTO article (code, libelle, prix) VALUES ('X', 'Prix négatif', -1);
INSERT INTO commande (client_id) VALUES (99);
ERREUR 23505 : Duplicate entry '[email protected]' for key 'email'
ERREUR 23514 : new row for relation "article" violates check constraint "CONSTRAINT_1"
ERREUR 23503 : Cannot add or update a child row: a foreign key constraint fails ("ventes"."commande", CONSTRAINT "commande_ibfk_1" FOREIGN KEY ("client_id") REFERENCES "client" ("id"))

Le SQLSTATE est celui de PostgreSQL ; le texte du message est celui de MIRAJ, en anglais, et cite les noms à sa manière. Une application doit donc se fier au SQLSTATE, pas au texte.

Modifier une ligne, puis insérer ou mettre à jour selon le cas (ON CONFLICT) :

UPDATE article SET stock = stock - 10 WHERE code = 'STY-01' RETURNING code, stock;
  code  | stock
--------+-------
 STY-01 |   490
(1 ligne)

UPDATE 1
INSERT INTO article (code, libelle, prix, stock) VALUES ('STY-01', 'Stylo bille bleu', 1.30, 100)
ON CONFLICT (code) DO UPDATE SET prix = EXCLUDED.prix, stock = article.stock + EXCLUDED.stock
RETURNING code, prix, stock;
  code  | prix | stock
--------+------+-------
 STY-01 | 1.30 |   590
(1 ligne)

INSERT 0 1

Une transaction s'ouvre par BEGIN et se termine par COMMIT ou ROLLBACK. Ici, une suppression et une modification sont annulées :

BEGIN;
DELETE FROM client WHERE id = 3 RETURNING nom;
UPDATE article SET stock = 0;
ROLLBACK;
SELECT code, stock FROM article ORDER BY id;
BEGIN
      nom
---------------
 Claire Dupont
(1 ligne)

DELETE 1
UPDATE 3
ROLLBACK
  code  | stock
--------+-------
 STY-01 |   590
 CAH-96 |   200
 CLA-A4 |    80
(3 lignes)

Voir 35. Langage DML et 38. Transactions et session.

32.9 Changer le mot de passe de root et créer un compte#

Le mot de passe provisoire de root se remplace ainsi :

ALTER USER root@localhost IDENTIFIED BY 'nouveau mot de passe';

Une application ne devrait pas se connecter en root. Un compte et ses droits sur le schéma :

CREATE USER app IDENTIFIED BY 'secret123';
GRANT SELECT, INSERT, UPDATE, DELETE ON ventes.* TO app;
CREATE USER
GRANT

Le compte app lit et écrit alors les tables de ventes, et reçoit 42501 pour le reste (DROP TABLE client : permission denied for table client).

Le compte se crée aussi dans les formes de PostgreSQL, qui rendent leur propre étiquette :

CREATE USER app2 WITH PASSWORD 'secret123';
CREATE ROLE app3 LOGIN PASSWORD 'secret123';
CREATE USER
CREATE ROLE

Les droits sur l'ensemble d'un schéma se donnent par GRANT … ON schéma.*, comme ci-dessus. Voir 39. Comptes et privilèges.

32.10 Interroger les données#

Filtre et tri ; ILIKE ignore la casse, LIKE la respecte, comme dans PostgreSQL :

SELECT id, nom, email FROM client WHERE nom ILIKE '%a%' ORDER BY nom;
 id |       nom       |           email
----+-----------------+----------------------------
  1 | Amel Kaci       | [email protected]
  3 | Claire Dupont   | [email protected]
  2 | Youssef Ben Ali | [email protected]
(3 lignes)

Conversion :: et booléens (rendus t / f) :

SELECT id, nom, actif, cree_le::date AS jour FROM client ORDER BY id;
 id |       nom       | actif |    jour
----+-----------------+-------+------------
  1 | Amel Kaci       | t     | 2026-10-11
  2 | Youssef Ben Ali | t     | 2026-10-11
  3 | Claire Dupont   | t     | 2026-10-11
(3 lignes)

Jointure et agrégat, montant de chaque commande :

SELECT c.nom, o.id AS commande, o.passee_le, sum(l.quantite * l.prix_unit) AS montant
FROM commande o
JOIN client c ON c.id = o.client_id
JOIN ligne_commande l ON l.commande_id = o.id
GROUP BY c.nom, o.id, o.passee_le
ORDER BY montant DESC;
       nom       | commande | passee_le  | montant
-----------------+----------+------------+---------
 Youssef Ben Ali |        3 | 2026-10-06 |   46.00
 Amel Kaci       |        1 | 2026-10-01 |   22.00
 Amel Kaci       |        2 | 2026-10-05 |    9.80
(3 lignes)

Total par client, clients sans commande compris ; NULLS LAST place le total absent en fin de liste :

SELECT c.nom, count(DISTINCT o.id) AS nb_commandes, sum(l.quantite * l.prix_unit) AS total
FROM client c
LEFT JOIN commande o ON o.client_id = c.id
LEFT JOIN ligne_commande l ON l.commande_id = o.id
GROUP BY c.id, c.nom
ORDER BY total DESC NULLS LAST;
       nom       | nb_commandes | total
-----------------+--------------+-------
 Youssef Ben Ali |            1 | 46.00
 Amel Kaci       |            2 | 31.80
 Claire Dupont   |            0 |
(3 lignes)

Voir 36. Requêtes SELECT et 37. Fonctions SQL.

32.11 Créer un index#

Un index accélère les recherches sur une colonne souvent filtrée qui n'est ni clé primaire ni UNIQUE :

CREATE INDEX idx_commande_date ON commande (passee_le);
CREATE INDEX

Il se retire sans nommer la table, comme dans PostgreSQL : DROP INDEX idx_commande_date; (étiquette DROP INDEX).

Un index posé sur la colonne d'une clé étrangère (CREATE INDEX … ON commande (client_id)) se retire comme un autre : la contrainte reste contrôlée, plus lentement sans lui. Un nom d'index est unique dans son schéma (42P07 pour un second index du même nom, même sur une autre table).

32.12 Voir le schéma#

Avec psql, \dt liste les tables et \d client décrit une table. Sans psql, les mêmes renseignements se lisent dans pg_catalog et information_schema, ou par les instructions SHOW de MIRAJ, qui restent disponibles.

Lister les tables du schéma courant :

SELECT tablename FROM pg_tables WHERE schemaname = 'ventes' ORDER BY tablename;
   tablename
----------------
 article
 client
 commande
 ligne_commande
(4 lignes)

SHOW TABLES; rend la même liste (colonne Tables_in_ventes), et SHOW DATABASES; les schémas.

Décrire les colonnes d'une table :

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

Voir la définition complète d'une table, écrite en langage PostgreSQL :

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")

Lister les index :

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)

Le chapitre 41. Catalogue et outils décrit pg_catalog et information_schema.

32.13 Différences à connaître dès le départ#

MIRAJ parle la grammaire de PostgreSQL, mais c'est son propre moteur qui exécute. Les écarts que l'on rencontre le plus tôt :

SujetSur une instance MIRAJ en langage PostgreSQL
Base et schémaune seule base miraj ; un schéma par base MIRAJ ; pas de schéma public (32.2)
Casse des nomstous les identifiants sont insensibles à la casse, même entre guillemets : "Client" et client désignent la même table
Casse des textesles comparaisons de textes respectent la casse (nom = 'AMEL KACI' ne trouve pas Amel Kaci) ; LIKE et ~ la respectent, ILIKE et ~* l'ignorent, comme dans PostgreSQL
Divisionentre deux entiers, / est une division entière : 7 / 2 vaut 3, comme div(7, 2) ; 7.0 / 2 vaut 3.5000000000000000
Division par zéroerreur 22012, comme PostgreSQL
Noms de contraintesceux de MIRAJ : clé primaire <table>_pkey, unicité du nom de la colonne (email), clés étrangères <table>_ibfk_<n>, CHECK CONSTRAINT_<n>
Messages d'erreurSQLSTATE de PostgreSQL, texte de MIRAJ (32.8)
Comptes et droitsCREATE USER … IDENTIFIED BY ou WITH PASSWORD, droits sur un schéma par GRANT … ON schéma.* (32.9)
Niveau d'isolementSHOW transaction_isolation rend read committed par défaut
Paramètres de démarrageles options de la connexion (-c statement_timeout=5000, --lock_timeout=2s) sont appliquées comme autant de SET ; une option inconnue (42704) ou réservée au démarrage du serveur PostgreSQL (shared_buffers… : 55P02) refuse la connexion ; les paramètres du message de démarrage des pilotes (TimeZone, DateStyle, extra_float_digits…) le sont aussi, un nom inconnu y étant ignoré

La syntaxe propre au langage MIRAJ est refusée sur une telle instance, avec une erreur 42601 qui nomme la construction :

SELECT `nom` FROM client;
SELECT * FROM client LIMIT 1, 2;
ERREUR 42601 : syntax error at or near "`" at line 1: a backquoted identifier is MIRAJ syntax, not accepted in the PostgreSQL language
ERREUR 42601 : syntax error at or near "," at line 1: LIMIT offset, count is MIRAJ syntax, not accepted in the PostgreSQL language

La liste complète de ce qui est accepté, refusé ou pas encore pris en charge est au chapitre 30.

32.14 Pour la suite#

Vous savez maintenant démarrer une instance en langage PostgreSQL, vous y connecter, créer un schéma et des tables, écrire, lire, indexer et inspecter le schéma. Les chapitres suivants détaillent chaque sujet :