Mirajv1.0
FR

27. Bibliothèques clientes et API C

En plus du serveur réseau et de la console miraj-cli, MIRAJ peut s'embarquer directement dans une application par sa bibliothèque native miraj.dll, qui expose le moteur au travers d'une API C stable (53 fonctions, décrites dans miraj.h). Aucune connexion réseau, aucun processus séparé : le moteur tourne dans le processus de l'application.

SystèmeBibliothèqueLivrée par
Windows 64 bitsmiraj.dlll'assistant d'installation et le zip Windows x64
Windows 32 bitsmiraj.dll (32 bits)le zip MIRAJ-<version>-Win32.zip
macOS/usr/local/miraj-express/lib/libmiraj.dylible paquet .pkg
Linux 64 bitslibmiraj.sole zip Linux

La même API C est exportée sous les trois noms ; les enveloppes Python (ctypes), PHP et Node.js cherchent le nom propre au système (miraj.dll, libmiraj.dylib, libmiraj.so).

Au-dessus de cette API C, des enveloppes idiomatiques sont fournies pour plusieurs langages, afin d'éviter d'écrire soi-même les appels bas niveau : Delphi (unité Miraj.pas, l'API historique du produit), C#, C++, Python, Java, PHP, Node.js et Go. Chaque enveloppe reprend la même organisation générale (Server / Session / Result) et les mêmes conventions que l'unité Delphi d'origine.

Important : le moteur embarqué vit dans le processus qui l'a ouvert. Il convient aux applications de bureau, aux scripts en ligne de commande et aux tâches planifiées. Pour un serveur web à plusieurs processus ou fils de travail (PHP-FPM, pool de threads Java, etc.) qui doivent partager les mêmes données, utilisez le serveur réseau miraj-server avec un client SQL classique plutôt que la bibliothèque embarquée.

27.1 Ce que chaque enveloppe expose#

Toutes les enveloppes tournent autour de trois objets :

  • Server (TMiraj en Delphi) : ouvre un dossier racine de bases de données, possède la session racine et permet d'en créer d'autres.
  • Session : exécute du SQL (execute, query, paramètres positionnels ? ou nommés :nom), donne accès au dernier identifiant auto-incrémenté, au nombre de lignes affectées, aux avertissements et à la langue des messages d'erreur.
  • Result (ResultSet) : décrit les colonnes (nom, type, échelle) et donne accès aux cellules, soit par position (ligne, colonne), soit par parcours séquentiel (next / field).

Une erreur SQL lève une exception propre à chaque langage (code, SQLSTATE, ligne/colonne pour une erreur de syntaxe), construite à partir de la dernière erreur mémorisée par la DLL.

Les paramètres s'écrivent ? (positionnels, liste de valeurs) ou :nom (nommés, noms et valeurs associés, noms sans les deux-points). Les enveloppes Python, PHP, Node.js et Go cherchent la bibliothèque d'abord à l'emplacement donné par le programme, puis dans la variable d'environnement MIRAJ_DLL (chemin du fichier), puis par le chargeur du système ; C# la cherche sous le nom miraj (dossier de l'exécutable, chemins du système), Java sous le même nom dans la propriété jna.library.path puis les chemins du système ; Delphi et C++ la lient à l'édition de liens.

Les sections suivantes donnent, pour chaque langage, un exemple minimal complet — connexion, requête, lecture du résultat, fermeture — dans la syntaxe réelle de l'enveloppe. Elles supposent la base shop suivante, dont la requête des exemples rend deux lignes :

CREATE DATABASE shop;
USE shop;
CREATE TABLE articles (id INT PRIMARY KEY, nom VARCHAR(30), prix DECIMAL(8,2));
INSERT INTO articles VALUES (1, 'Stylo', 2.50), (2, 'Agenda', 14.90), (3, 'Sac', 39.00);
SELECT nom, prix FROM articles WHERE prix > 10;
+--------+-------+
| nom    | prix  |
+--------+-------+
| Agenda | 14.90 |
| Sac    | 39.00 |
+--------+-------+
2 ligne(s)

Une requête sur une table absente (SELECT * FROM absente) échoue avec le code 1146, le SQLSTATE 42S02 et le message Table 'shop.absente' doesn't exist : c'est l'erreur que montrent les exemples de gestion des erreurs.

27.2 C# (.NET)#

Enveloppe : Miraj.cs (P/Invoke, .NET 8, namespace Miraj). Référencez Miraj.cs dans votre projet et copiez miraj.dll (64 bits) à côté de l'exécutable.

using Miraj;

using var server = MirajServer.Open("data");          // charge les bases du dossier
using var session = server.CreateSession();
session.Execute("USE shop");

using var rs = session.Query("SELECT nom, prix FROM articles WHERE prix > ?", 10);
while (rs.Next())
    Console.WriteLine($"{rs.Field("nom")} : {rs.Field("prix")}");

Gestion des erreurs : MirajException (Code, SqlState, Line, Column) ; un mauvais usage (handle nul, UTF-8 invalide) lève les exceptions .NET standards (ArgumentException, ArgumentOutOfRangeException).

try
{
    session.Execute("SELECT * FROM absente");
}
catch (MirajException e)
{
    Console.WriteLine($"Erreur {e.Code} ({e.SqlState}) : {e.Message}");
}

MirajServer, MirajSession et MirajResultSet implémentent IDisposable (using). L'enveloppe couvre aussi les paramètres nommés, QueryValue, ExecSql, les avertissements, les sessions multiples et la réouverture d'un dossier existant.

27.3 Python#

Enveloppe : miraj.py (module ctypes pur, aucune dépendance à installer). Copiez miraj.py à côté de votre script, ou ajoutez son dossier à PYTHONPATH. La DLL est cherchée dans cet ordre : chemin passé à miraj.load_library(chemin) (fichier ou dossier, avant le premier usage), variable d'environnement MIRAJ_DLL (fichier ou dossier), dossier de miraj.py, puis recherche du système.

import miraj

with miraj.Server.open("data") as server:      # charge les bases du dossier
    session = server.root_session
    session.execute("USE shop")

    with session.query("SELECT nom, prix FROM articles WHERE prix > ?", (10,)) as rs:
        for nom, prix in rs:                     # une ligne = un tuple
            print(nom, prix)                     # Agenda 14.90, puis Sac 39.00

Paramètres : séquence pour les ?, dictionnaire pour les :nom (clés sans les deux-points). Un Result se lit aussi par rs.row(i) (tuple), rs.row_dict(i) ou rs.dicts() (dictionnaires nom de colonne → valeur), rs.fetchall(), rs.columns, ou par le parcours séquentiel rs.next() / rs.field("nom"). Server.open(chemin, save_policy=…, save_interval_ms=…, language=…) charge les bases du dossier, Server.create(chemin) n'en charge aucune ; server.create_session() ouvre une autre session ; session.query_value(sql) rend la première cellule ; session.exec_sql(script, results=liste) exécute un script sans lever d'exception (code d'erreur rendu). Un DECIMAL est lu en decimal.Decimal (Decimal('26.90')).

Gestion des erreurs : miraj.MirajError (code, sqlstate, line, column) ; les mauvais usages détectés par la DLL lèvent miraj.MirajMisuseError (dérive aussi de ValueError) ou miraj.MirajRangeError (dérive aussi de IndexError).

try:
    session.execute("SELECT * FROM absente")
except miraj.MirajError as e:
    print(f"Erreur {e.code} ({e.sqlstate}) : {e}")

Une couche minimale conforme à DB-API 2.0 (PEP 249) est fournie en plus de l'API orientée objet : miraj.connect(root, database=...) renvoie une Connection avec cursor(), commit() et rollback() (qui exécutent COMMIT et ROLLBACK), et le Cursor obtenu supporte execute(), executemany(), fetchone()/fetchmany()/fetchall() et l'itération, pour s'intégrer avec du code déjà écrit contre l'interface standard des pilotes Python (paramstyle = "qmark", :nom accepté avec un dictionnaire). root peut aussi être un Server déjà ouvert : la connexion le partage sans le fermer.

conn = miraj.connect("data", database="shop")
cur = conn.cursor()
cur.execute("SELECT nom FROM articles WHERE prix > ?", (10,))
for (nom,) in cur.fetchall():
    print(nom)
conn.close()

27.4 Java (JNA)#

Enveloppe : paquet com.cirtait.miraj (JNA 5.19.1, compatible Java 8, groupe Maven com.cirtait:miraj-java). MirajLibrary déclare l'API C telle quelle ; Server, Session et Result forment l'enveloppe objet AutoCloseable. La DLL est cherchée sous le nom miraj via la propriété système jna.library.path, puis dans les chemins système ; une JVM 64 bits est nécessaire.

import com.cirtait.miraj.Server;
import com.cirtait.miraj.Session;
import com.cirtait.miraj.Result;

try (Server server = Server.open("data");          // charge les bases du dossier
     Session session = server.createSession()) {
    session.execute("USE shop");
    try (Result rs = session.query("SELECT nom, prix FROM articles WHERE prix > ?", 10)) {
        while (rs.next()) {
            System.out.println(rs.getString("nom") + " : " + rs.getBigDecimal("prix"));
        }
    }
}

Gestion des erreurs : MirajException (héritée pour tout échec renvoyé par la DLL, code SQL inclus). Les types de paramètres sont convertis automatiquement (BigInteger, BigDecimal, LocalDateTime, byte[], etc. — voir le Javadoc du paquet).

try {
    session.execute("SELECT * FROM absente");
} catch (com.cirtait.miraj.MirajException e) {
    System.out.println("Erreur " + e.getCode() + " : " + e.getMessage());
}

27.5 PHP (FFI)#

Enveloppe : bibliothèque PHP Miraj (espace de noms Miraj, chargement PSR-4 via composer.json, PHP 8.1+ en 64 bits, extension ext-ffi).

use Miraj\Server;

$server  = Server::open('data');                      // charge les bases du dossier
$session = $server->rootSession();
$session->useDatabase('shop');

foreach ($session->query('SELECT nom, prix FROM articles WHERE prix > ?', [10]) as $ligne) {
    echo $ligne['nom'], ' : ', $ligne['prix'], "\n";
}
$server->close();                                      // ou laisser __destruct le faire

Paramètres : liste pour les ?, tableau associatif pour les :nom (['s' => 5, 'id' => 3]). Une chaîne part en texte UTF-8, new Miraj\Bytes($octets) en binaire, new Miraj\Decimal('12.50') en décimal exact, une DateTimeInterface en date-heure. Une ligne parcourue par foreach est un tableau associatif ; fetchAll(), fetchNumeric($i), fetchColumn($col), queryValue() et le parcours séquentiel next() / field() sont aussi disponibles. Les dates sont lues en DateTimeImmutable, DECIMAL et TIME en chaîne exacte.

Emplacement de la DLL, par ordre de priorité : Miraj\Native::setLibraryPath('C:/.../miraj.dll') avant le premier appel, la variable d'environnement MIRAJ_DLL, sinon miraj.dll cherché par le chargeur de Windows (dossier de php.exe, PATH…).

Gestion des erreurs : Miraj\MirajException (getCode(), getSqlState(), getSqlLine(), getSqlColumn()).

try {
    $session->execute('SELECT * FROM absente');
} catch (\Miraj\MirajException $e) {
    echo "Erreur {$e->getCode()} ({$e->getSqlState()}) : {$e->getMessage()}\n";
}

L'extension FFI est livrée avec PHP mais pas toujours activée : en ligne de commande, sans toucher à php.ini, php -d extension=ffi -d ffi.enable=1 script.php. En production web, ffi.enable=preload avec un script de préchargement est recommandé — le serveur MIRAJ embarqué ne vit que le temps du processus PHP qui l'a ouvert, donc PHP-FPM et Apache/mod_php n'y sont pas adaptés : préférez miraj-server pour un site web, et réservez l'enveloppe PHP aux scripts en ligne de commande, tâches planifiées et applications de bureau à processus unique.

27.6 Node.js#

Enveloppe : module miraj (index.js + index.d.ts, déclarations TypeScript), par koffi 3.3.1, appels uniquement synchrones (la dernière erreur et les chaînes renvoyées par la DLL sont propres au fil appelant).

const { Server } = require('miraj');

const server = Server.open('data');                  // charge les bases du dossier
const session = server.createSession();
session.execute('USE shop');

const rs = session.query('SELECT nom, prix FROM articles WHERE prix > ?', [10]);
for (const ligne of rs) {
    console.log(ligne.nom, ligne.prix);
}
rs.close();
session.close();
server.close();

Avec le support using (Symbol.dispose, Node 18+ récent / TypeScript 5.2+), la fermeture est automatique :

using server = Server.open('data');
using session = server.createSession();
using rs = session.query('SELECT nom FROM articles WHERE prix > ?', [10]);
for (const ligne of rs) console.log(ligne.nom);

Gestion des erreurs : MirajError (code SQL si positif, -1 mauvais usage, -2 ligne/colonne hors du résultat) ; un type de paramètre non pris en charge lève TypeError.

try {
    session.execute('SELECT * FROM absente');
} catch (e) {
    console.error(`Erreur ${e.code} (${e.sqlState}) : ${e.message}`);
}

Entiers 64 bits : renvoyés en number s'ils tiennent dans Number.MAX_SAFE_INTEGER, en BigInt sinon (ou toujours en BigInt pour les colonnes entières de 64 bits avec l'option Server.open(chemin, { bigInt: true }) ; Result.getBigInt lit toujours un BigInt). Décimaux : chaîne exacte ('26.90') ; en paramètre, new Decimal('12.50') est lié comme un décimal exact. Dates : Date à l'heure locale. Emplacement de la DLL : setLibraryPath(chemin) avant le premier usage, sinon la variable MIRAJ_DLL, sinon miraj.dll à côté du module, sinon la recherche du système. Pour ne pas bloquer la boucle d'événements, exécutez les requêtes longues dans un Worker (worker_threads) avec son propre Server ou sa propre Session.

27.7 Go#

Enveloppe : module github.com/cirtait/miraj-go, sans cgo : la DLL est chargée dynamiquement via golang.org/x/sys/windows (ni gcc ni bibliothèque d'import nécessaires), Windows 64 bits uniquement pour l'instant. Un pilote database/sql est fourni dans le sous-paquet mirajsql, enregistré sous le nom "miraj".

import "github.com/cirtait/miraj-go"

miraj.Load(`C:\...\miraj.dll`)               // sinon MIRAJ_DLL, sinon miraj.dll
srv, err := miraj.Open(`C:\donnees`, nil)    // charge les bases du dossier
defer srv.Close()

s, _ := srv.NewSession()
defer s.Close()
s.UseDatabase("shop")

rs, err := s.Query("SELECT nom FROM articles WHERE prix > :p", miraj.Named("p", 10))
defer rs.Close()
for rs.Next() {
    nom, _ := rs.Field("nom")
    fmt.Println(nom)
}

Ou via database/sql :

import _ "github.com/cirtait/miraj-go/mirajsql"

db, err := sql.Open("miraj", `C:\donnees?database=shop&language=fr`)

La source de données est le dossier racine, suivi au besoin de ?clé=valeur&… :

CléRôleDéfaut
databasebase choisie sur chaque connexionaucune
languagelangue des messages (en, fr…)en
load_databases0 ou 1 : charger les bases du dossier1
save_policymanual, on_statement, periodic ou relaxedrelaxed
save_intervalintervalle de periodic, en millisecondes5000

Une clé inconnue est refusée. Chaque connexion du pool est une session du serveur embarqué ; les sources qui désignent le même dossier partagent un seul serveur (options du premier ouvreur), fermé avec le dernier sql.DB qui l'utilise. sql.OpenDB(mirajsql.NewConnector(dossier, options, base)) évite l'analyse de la chaîne. Paramètres ? ou sql.Named("nom", v) (:nom) ; un DECIMAL est rendu en chaîne décimale exacte ; les transactions de database/sql deviennent START TRANSACTION (READ ONLY selon sql.TxOptions, précédé de SET TRANSACTION ISOLATION LEVEL pour un niveau autre que le défaut), COMMIT et ROLLBACK.

Gestion des erreurs : *miraj.Error (Code, SQLState, Line, Column, Message), à la manière idiomatique Go (valeur d'erreur renvoyée, pas d'exception).

À noter : cette enveloppe Go est fournie sans avoir encore été validée en exécution, contrairement aux autres enveloppes : testez-la sur votre application avant de vous y fier. Linux et macOS ne sont pas encore pris en charge.

27.8 C / C++#

Deux niveaux :

  • C pur : appels directs à l'API C de miraj.h (voir § 27.9) — utile pour un langage sans enveloppe dédiée ou une intégration minimale.
  • C++17 en-tête seul (miraj.hpp) : classes RAII non copiables mais déplaçables (Server, Session, Result, ResultList), qui libèrent leur handle au destructeur ou via close().
#include "miraj.hpp"
#include <iostream>

auto server = miraj::Server::open("data");         // charge les bases du dossier
miraj::Session session = server.create_session();
session.execute("USE shop");

miraj::Result rs = session.query("SELECT nom FROM articles WHERE prix > ?", 10);
while (rs.next())
    std::cout << rs.field<std::string>("nom") << '\n';

Gestion des erreurs : tout échec lève miraj::Error (code SQL, SQLSTATE, ligne/colonne d'une erreur de syntaxe) ; miraj::MisuseError et miraj::RangeError en dérivent pour les codes -1 et -2. Seules Session::exec_sql / exec_script renvoient le code au lieu de lever, pour traiter un script à plusieurs instructions dont certaines peuvent échouer.

try {
    session.execute("SELECT * FROM absente");
} catch (const miraj::Error& e) {
    std::cerr << "Erreur " << e.code() << " (" << e.sql_state() << ") : " << e.what() << '\n';
}

miraj::Server::open_encrypted(dossier, phrase, creer_chiffre = false, options) ouvre un dossier de données chiffré (éditions Entreprise et Cluster ; voir miraj_server_open_encrypted, § 27.9). Server::create(dossier) ouvre sans charger les bases ; Session::wait_changes(delai_ms, limite) attend les événements de changement.

Édition de liens : miraj.dll.lib (bibliothèque d'import, MSVC) contre l'exécutable, miraj.dll à côté.

27.9 API C bas niveau (miraj.h)#

Pour intégrer MIRAJ dans un langage sans enveloppe dédiée, ou pour un contrôle fin sur les appels, l'API C déclarée dans miraj.h peut être utilisée directement. Elle compte 53 fonctions exportées par miraj.dll, organisées en familles :

FamilleRôleFonctions
Globale (6)Version de la bibliothèque, dernière erreur du fil appelant.miraj_version, miraj_last_error_code, miraj_last_error_message, miraj_last_error_sql_state, miraj_last_error_line, miraj_last_error_column
Serveur (10)Ouverture/fermeture du moteur embarqué sur un dossier racine (chiffré ou non), session racine, création de sessions, langue, sauvegarde forcée.miraj_options_init, miraj_server_open, miraj_server_open_encrypted, miraj_server_close, miraj_server_root_session, miraj_server_create_session, miraj_server_session_count, miraj_server_language / _set_language, miraj_server_save_all
Session (17)Exécution SQL (avec ou sans résultat, paramètres positionnels ou nommés, script à plusieurs instructions), base courante, dernier identifiant inséré, avertissements, langue, dernière erreur de la session.miraj_session_id, miraj_session_close, miraj_session_execute, miraj_session_execute_named, miraj_session_query, miraj_session_query_named, miraj_session_exec_sql, miraj_session_use_database, miraj_session_current_database, miraj_session_last_insert_id, miraj_session_row_count, miraj_session_language / _set_language, miraj_session_warning_count / _warning, miraj_session_last_error, miraj_session_wait_changes (attente des événements de changement : délai en millisecondes et nombre maximal de lignes)
Listes de résultats (3)Un script à plusieurs instructions renvoie une liste de résultats, un par instruction réussie.miraj_result_list_count, miraj_result_list_take, miraj_result_list_free
Résultat (17)Métadonnées des colonnes (nom, type, échelle) et lecture des cellules, par type ou en valeur générique.miraj_result_column_count, miraj_result_row_count, miraj_result_affected_rows, miraj_result_column_name, miraj_result_column_type, miraj_result_column_scale, miraj_result_column_index / _column, miraj_result_is_null, miraj_result_get_int / _get_float / _get_datetime / _get_string / _get_text / _get_bytes / _get_value, miraj_result_free

Signatures principales :

void    miraj_options_init(MirajOptions *options);
int32_t miraj_server_open(const char *root, const MirajOptions *options, MirajServer **out);
int32_t miraj_server_open_encrypted(const char *root, const MirajOptions *options,
                                    const char *passphrase, int32_t create_encrypted,
                                    MirajServer **out);
int32_t miraj_session_execute(MirajSession *session, const char *sql, const MirajValue *params,
                              size_t param_count, int64_t *affected);
int32_t miraj_session_query_named(MirajSession *session, const char *sql,
                                  const char *const *names, const MirajValue *params,
                                  size_t param_count, MirajResult **out);
int32_t miraj_session_exec_sql(MirajSession *session, const char *sql, const MirajValue *params,
                               size_t param_count, MirajResultList **results);
int32_t miraj_session_wait_changes(MirajSession *session, uint32_t timeout_ms, size_t limit,
                                   MirajResult **out);
int32_t miraj_result_get_value(const MirajResult *result, int64_t row, int32_t col,
                               MirajValue *out);

Options d'ouverture (MirajOptions, remplie par miraj_options_init) :

ChampRôleDéfaut
save_policyMIRAJ_SAVE_MANUAL (0), MIRAJ_SAVE_ON_STATEMENT (1), MIRAJ_SAVE_PERIODIC (2), MIRAJ_SAVE_RELAXED (3)MIRAJ_SAVE_RELAXED
save_interval_msintervalle de la sauvegarde périodique5000
languagelangue des messages : "en" (pointeur nul), "fr", "zh", "hi", "es", "ar", "pt", "ru", "de", "ja""en"
load_databases1 : charger les bases du dossier à l'ouverture1

miraj_server_open_encrypted ouvre un dossier de données chiffré (éditions Entreprise et Cluster) : passphrase (UTF-8) déverrouille son trousseau ; create_encrypted non nul initialise chiffré un dossier neuf. Avec passphrase nul, l'effet est celui de miraj_server_open.

Valeurs (MirajValue : kind, int_value, float_value, data, len), paramètre ou cellule :

kindValeurChamp
MIRAJ_TYPE_NULL (0)NULL—
MIRAJ_TYPE_BOOL (1), MIRAJ_TYPE_INT (2)booléen, entier signé 64 bitsint_value
MIRAJ_TYPE_UINT (3)entier non signé 64 bitsint_value lu en uint64_t
MIRAJ_TYPE_FLOAT (4), MIRAJ_TYPE_DATETIME (7)flottant, date-heure TDateTimefloat_value
MIRAJ_TYPE_STRING (5), MIRAJ_TYPE_BYTES (6)texte UTF-8, octetsdata + len
MIRAJ_TYPE_DECIMAL (8)décimal exacttexte dans data + len (et flottant le plus proche dans float_value en lecture)

Types des colonnes (miraj_result_column_type) : MIRAJ_DT_INT8 (0), _INT16 (1), _INT32 (2), _INT64 (3), _UINT64 (4), _SINGLE (5), _DOUBLE (6), _DECIMAL (7), _BOOLEAN (8), _STRING (9), _BYTES (10), _DATE (11), _TIME (12), _DATETIME (13), _YEAR (14), _VECTOR (15), _GEOMETRY (16, lue en octets : SRID sur 4 octets petit-boutiens suivi du WKB).

Concepts communs à toute l'API :

  • Handles opaques (MirajServer*, MirajSession*, MirajResult*, MirajResultList*), libérés explicitement par la fonction _close / _free correspondante (pointeur nul accepté sans effet). La session racine appartient au serveur : ne pas la fermer soi-même ; fermez les autres sessions avant le serveur. Un résultat (MirajResult) peut survivre à sa session et à son serveur.
  • Codes de retour : MIRAJ_OK (0) en cas de succès ; un entier positif est un code d'erreur SQL (par exemple 1146 table inconnue, 1105 erreur interne ou panique récupérée) ; MIRAJ_ERR_MISUSE (-1) signale un mauvais usage de l'API (handle nul, UTF-8 invalide) ; MIRAJ_ERR_RANGE (-2) signale un index hors bornes (ligne ou colonne). Un échec est mémorisé comme dernière erreur du fil appelant, consultable par miraj_last_error_* ; un succès l'efface.
  • Exécution : miraj_session_execute exécute un script et rend dans affected (facultatif) les lignes affectées par la dernière instruction ; miraj_session_query rend le résultat de la dernière instruction qui a des colonnes (sinon celui de la dernière instruction) ; miraj_session_exec_sql ne s'arrête pas aux résultats : il rend 0 ou le code de l'erreur qui a interrompu le script (les instructions précédentes restent appliquées) et remplit la liste d'un résultat par instruction réussie, même en cas d'erreur. miraj_result_list_take retire un résultat de la liste (à libérer par miraj_result_free).
  • Lecture des cellules : miraj_result_get_int (arrondi, 0 pour NULL), _get_float, _get_datetime (TDateTime, 0 pour NULL), _get_string (chaîne vide pour NULL), _get_text (texte affiché, NULL pour une valeur absente), _get_bytes (texte UTF-8 pour une colonne non binaire) et _get_value (valeur typée MirajValue). miraj_result_column_index rend -1 pour un nom absent, miraj_result_column l'erreur 1054 ; la recherche par nom ignore la casse. miraj_result_column_type et _column_scale rendent -1 pour une colonne inexistante.
  • Chaînes UTF-8 : en entrée, terminées par un octet nul (SQL, noms) ou données en pointeur + longueur pour les valeurs de paramètre ; en sortie, pointeur + longueur en octets suivis d'un octet nul, valides jusqu'au prochain appel du même fil qui renvoie des données — à recopier aussitôt côté appelant, jamais à libérer.
  • Dates : type TDateTime (nombre de jours depuis le 30/12/1899, en double), la même convention que l'API Delphi historique.
  • Décimaux : type MIRAJ_TYPE_DECIMAL (8), texte exact ("12.50") dans data / len. miraj_result_get_value le rend pour toute valeur DECIMAL (SUM, AVG et division / compris), avec le flottant le plus proche dans float_value ; en paramètre, le texte ([-]chiffres[.chiffres], exposant admis) est lié comme un décimal exact. Jusqu'en octobre 2026, les décimaux étaient rendus en MIRAJ_TYPE_FLOAT. Côté enveloppes : C# decimal (string au-delà de 28-29 chiffres), Delphi TBcd dans un Variant (varFMTBcd ; chaîne au-delà de 64 chiffres ; varFMTBcd et varCurrency acceptés en paramètre), Java BigDecimal, PHP chaîne numérique (paramètre new Miraj\Decimal('12.50')), Node.js chaîne (paramètre new Decimal('12.50')), Go miraj.Decimal (string dans database/sql), C++ miraj::Decimal (std::string text), Python ctypes decimal.Decimal.
  • Fils d'exécution : le serveur est utilisable depuis plusieurs fils ; une session est sérialisée par un verrou interne (une session par fil est conseillée pour éviter les attentes) ; un résultat est lisible depuis plusieurs fils ; une liste de résultats est réservée à un seul fil. Une commande de session s'exécute sur un fil interne à grande pile (le fil appelant attend) : une instruction et les définitions qu'elle évalue (vue, déclencheur, CHECK, valeur par défaut, colonne générée), jusqu'à 1000 niveaux d'imbrication, ne débordent pas la pile d'un fil d'application ordinaire (1 Mo), pour un surcoût d'environ 1 µs par commande.
  • Paniques : aucune ne traverse la frontière de la bibliothèque ; elle devient l'erreur 1105.

Chacune des 53 fonctions est déclarée dans miraj.h, dans l'unité Delphi et dans chaque enveloppe de langage.

27.10 Delphi (Miraj.pas)#

Miraj.pas est l'enveloppe Delphi de miraj.dll : elle reprend l'API publique historique de MIRAJ en Delphi (TMiraj, TMirajSession, IMirajResultSet, EMirajError), pour qu'une application existante passe au nouveau moteur en remplaçant ses unités Miraj, Miraj.Session, Miraj.ResultSet, Miraj.Errors par cette seule unité dans sa clause uses.

uses
  Miraj;

var
  Server: TMiraj;
  Session: TMirajSession;
  RS: IMirajResultSet;
begin
  Server := TMiraj.Open('data');              // charge les bases du dossier
  try
    Session := Server.CreateSession;
    Session.Execute('USE shop');
    RS := Session.Query('SELECT nom, prix FROM articles WHERE prix > ?', [10]);
    while RS.Next do
      WriteLn(RS.Field('nom'), ' : ', RS.Field('prix'));
  finally
    Server.Free;                              // ferme les sessions puis le serveur
  end;
end;

Gestion des erreurs : EMirajError (Code, SqlState, Line, Column), levée par toutes les méthodes de l'API « avec exceptions » (Execute, Query, QueryNamed, UseDatabase, ...). Une API « sans exception » existe aussi sur TMirajSession : ExecSQL renvoie 0 ou le code d'erreur et remplit une liste de IMirajResultSet, une par instruction du script — utile pour exécuter un script à plusieurs instructions sans interrompre le programme à la première erreur.

try
  Session.Execute('SELECT * FROM absente');
except
  on E: EMirajError do
    WriteLn(Format('Erreur %d (%s) : %s', [E.Code, E.SqlState, E.Message]));
end;

IMirajResultSet se libère seul (interface à compteur de références) ; les sessions créées par TMiraj.CreateSession lui appartiennent et se ferment avec Server.Free ou explicitement par Server.CloseSession. Principaux écarts avec l'API Delphi d'origine, documentés en tête du fichier : TMiraj ne dérive plus de TMirajServer (le catalogue interne n'est pas exposé par la DLL — FindDatabase, GetTable sont absents, CurrentDatabase devient CurrentDatabaseName), IMirajResultSet.ColumnVector (TVector) est absent, et EMirajError n'a plus de constructeur à arguments de catalogue (le message arrive déjà traduit dans la langue de la session).

27.11 Récapitulatif des enveloppes#

LangageTechniqueÉtat
DelphiImports statiques cdeclEnveloppe historique de référence
C#P/Invoke (.NET 8)Complète
C++En-tête seul, RAII, exceptionsComplète
Pythonctypes + couche DB-API 2.0 minimaleComplète
JavaJNA 5.19.1Complète
PHPFFIComplète (limites de déploiement web documentées, § 27.5)
Node.jskoffi, synchroneComplète
GoChargement dynamique sans cgo, pilote database/sqlNon encore validée en exécution (§ 27.7)

27.12 Bibliothèque Python miraj du serveur (DB-API, style psycopg2)#

À côté de l'enveloppe ctypes de la DLL (27.3), la bibliothèque Python miraj (wheel miraj-<version>-cp39-abi3-<système>.whl, Python 3.9 et suivants) offre l'interface DB-API 2.0 de psycopg2 vers miraj-server (mode réseau) ou vers un dossier de bases ouvert dans le processus (path=…, mode embarqué) :

import miraj

conn = miraj.connect(host="127.0.0.1", port=7007, user="app", password="...", dbname="ventes",
                     sslmode="verify-full", sslrootcert="ca.pem")
cur = conn.cursor()
cur.execute("SELECT nom, prix FROM articles WHERE prix > %s", (10,))
for nom, prix in cur.fetchall():
    print(nom, prix)
conn.commit()

Installation : pip install miraj-<version>-cp39-abi3-<système>.whl (une seule wheel par système pour toutes les versions de Python à partir de 3.9). La bibliothèque ne dépend d'aucun autre paquet ; miraj.EMBEDDED vaut True si la wheel contient le mode embarqué.

Chaîne de connexion. connect() accepte une chaîne de mots-clés ("host=localhost port=7007 dbname=ventes user=root password='a b'" ; valeur entre apostrophes pour les espaces, \' et \\ échappés, un chemin Windows C:\certs\ca.pem passe tel quel), ou une URI miraj://compte:motdepasse@hôte:7007/base?sslmode=required&connect_timeout=5 (composants encodés en pour-cent, [::1] pour une adresse IPv6 ; seul le schéma miraj:// est accepté), et des paramètres nommés qui la complètent et l'emportent. Un paramètre inconnu lève ProgrammingError. miraj.extensions.parse_dsn(chaîne) rend le dictionnaire des paramètres.

ParamètreRôleDéfaut
hostnom ou adresse du serveurlocalhost
portport TCP (miraj.DEFAULT_PORT)7007
usercompteroot
passwordmot de passeaucun
dbname (ou database)base couranteaucune
sslmodedisabled, preferred, required, verify-ca, verify-full (disable, prefer, require acceptés)preferred
sslrootcert (ou sslca)autorités de certification (PEM ou DER)autorités publiques
sslcert, sslkeycertificat et clé privée du client, ensemble, clé non chiffréeaucun
connect_timeoutdélai de connexion, en secondes (décimales admises)30
autocommitvalidation automatique de chaque instructionFalse
application_namenom de l'applicationaucun
protocolauto (reconnu à la connexion), miraj ou postgresqlauto
pathmode embarqué : dossier de données ouvert dans le processus, sans serveur ni compteaucun
createmode embarqué : crée le dossier et la base dbname s'ils manquentFalse

Les modes TLS ont le même sens que pour le pilote ODBC (27.2). Un délai de connexion écoulé lève OperationalError de SQLSTATE HYT00, comme un serveur qui ne répond plus à temps une fois connecté (« délai dépassé : le serveur n'a pas répondu à temps ») ; une liaison réellement rompue rend 08S01. En mode embarqué, les connexions ouvertes sur un même dossier partagent le moteur, fermé avec la dernière (connection.embedded vaut True) :

conn = miraj.connect(path="/srv/donnees", dbname="ventes", create=True)

Connexion (Connection) : cursor(cursor_factory=…), commit(), rollback(), close() (une transaction en cours est annulée), closed (0 ouverte, 1 fermée), autocommit, isolation_level (constante miraj.extensions.ISOLATION_LEVEL_* ou nom "READ COMMITTED"…), readonly, set_session(isolation_level, readonly, deferrable, autocommit), set_isolation_level(niveau), dsn et get_dsn_parameters() (sans le mot de passe), notices (avertissements des dernières instructions, 50 au plus), insert_rows(table, colonnes, lignes) (insertion en lot) et wait_changes(timeout=None, limit=100), qui attend au plus timeout secondes les événements de changement des abonnements LISTEN de la connexion et en rend au plus limit (miraj.extensions.ChangeEvent : seq, commit_seq, event, database, name, key, columns, payload, session_id). Dans un bloc with, la connexion valide à la sortie, ou annule sur exception, et reste ouverte. Sans autocommit, une transaction s'ouvre à la première instruction et dure jusqu'à commit ou rollback. Une connexion se partage entre fils (threadsafety = 2), ses appels étant sérialisés.

Curseur (Cursor) : execute(requête, valeurs) (séquence pour %s, dictionnaire pour %(nom)s ; sans valeurs, aucun % n'est interprété), executemany() (en un seul passage, rowcount total), mogrify() (requête avec les valeurs écrites en littéraux), callproc(nom, paramètres) (CALL), fetchone(), fetchmany(size) (arraysize par défaut), fetchall(), l'itération, scroll(valeur, mode="relative" | "absolute"), nextset(), copy_from(), copy_to(), copy_expert(), close(), et les attributs description (n-uplets Column de sept champs : name, type_code, display_size, internal_size, precision, scale, null_ok), rowcount (-1 avant toute exécution), rownumber, lastrowid (identifiant AUTO_INCREMENT du dernier INSERT), query et statusmessage (SELECT 3, INSERT 1…).

Types. Entiers en int, FLOAT et DOUBLE en float, DECIMAL en decimal.Decimal, textes en str, binaires en bytes, DATE en datetime.date, DATETIME en datetime.datetime (microsecondes comprises), TIME en datetime.timedelta (négatif ou au-delà de 24 h compris), YEAR en int, NULL en None. En paramètre : ces mêmes types, datetime.time, bytearray et memoryview, uuid.UUID (son texte). Les objets de type de la DB-API (miraj.STRING, BINARY, NUMBER, DATETIME, ROWID) se comparent à type_code ; miraj.Date, Time, Timestamp, Binary et les …FromTicks sont fournis.

Modules d'appoint, sur le modèle de psycopg2 :

ModuleContenu
miraj.extrasDictCursor (lignes lisibles par rang et par nom), RealDictCursor (lignes dict), NamedTupleCursor, execute_values(cur, sql, lignes, template=None, page_size=100, fetch=False) (un INSERT … VALUES (…), (…) par page), execute_batch(cur, sql, lignes, page_size=100), Json (document JSON en paramètre), register_default_json()
miraj.extensionsregister_adapter(type, fonction), adapt(), AsIs, QuotedString, Binary, new_type() et register_type(convertisseur, portée) (global, connexion ou curseur), constantes ISOLATION_LEVEL_*, parse_dsn(), ChangeEvent, to_vector() (vecteur VECTOR(n) reçu en octets → liste de flottants)
miraj.poolSimpleConnectionPool(min, max, …) (un seul fil), ThreadedConnectionPool(min, max, …) (getconn(), putconn(conn), closeall()), PoolError
miraj.errorsune classe par code d'erreur MIRAJ, nommée d'après son nom en casse chameau (DupEntry pour 1062, NoSuchTable pour 1146), sous-classe de l'exception DB-API de son SQLSTATE ; lookup(1062), lookup("DupEntry") ou lookup("ER_DUP_ENTRY")

Erreurs. Hiérarchie de la DB-API (Warning, Error, InterfaceError, DatabaseError, DataError, OperationalError, IntegrityError, InternalError, ProgrammingError, NotSupportedError), plus TransactionRollbackError (SQLSTATE 40…, à rejouer) et QueryCanceledError (70100 après KILL QUERY, 57014). Une exception porte errno (code MIRAJ), sqlstate (et pgcode), pgerror, diag et cursor. Classement par SQLSTATE : 23 → IntegrityError, 42, 3D, 21, 24, 44, 07 → ProgrammingError, 22 → DataError, 0A → NotSupportedError, 25, 2F, XX → InternalError, les autres (HY000, 08…, 28……) → OperationalError. Le message du serveur est celui de la langue de la session (Duplicate entry '1' for key 'PRIMARY' ci-dessous).

from decimal import Decimal

try:
    cur.execute("INSERT INTO articles VALUES (%s, %s, %s)", (1, "Stylo", Decimal("2.50")))
except miraj.errors.DupEntry as e:
    print(e.errno, e.sqlstate)          # 1062 23000

Décimaux : une colonne ou un calcul DECIMAL (dont SUM et AVG d'un entier et la division /) est rendu en decimal.Decimal exact, comme psycopg2, en mode réseau comme en mode embarqué (SUM(quantite) donne Decimal('100'), AVG Decimal('33.3333'), sans perte au-delà de 2^53 ni au-delà de 38 chiffres). Un decimal.Decimal (ou un int de plus de 64 bits) passé en paramètre est écrit sans perte. En mode embarqué, il est lié comme un décimal du moteur (%s * 3 avec Decimal('0.1') vaut Decimal('0.3')) ; en mode réseau, il part en texte, converti exactement par la colonne qui le reçoit, mais un calcul sur lui se fait en flottant.

Instance en langage PostgreSQL#

Sur une instance lancée en langage PostgreSQL (chapitre 30), la même bibliothèque passe par le protocole PostgreSQL v3, sans changement d'API ni de paramstyle (pyformat : %s, %(nom)s) :

  • Détection : avec protocol="auto", la connexion commence par la demande TLS du protocole PostgreSQL ; une instance en langage MIRAJ est rejointe par une nouvelle connexion du protocole hérité (une fois par hôte et port et par processus). dbname nomme la base MIRAJ courante (un schéma) ; une base inconnue est refusée.
  • Paramètres : les %s deviennent $1, $2… liés à une instruction préparée nommée, gardée par connexion (64 au plus) et réutilisée : executemany prépare une fois. cursor.query montre le texte envoyé. Les valeurs partent en texte avec leur type (int8, float8, numeric, bool, bytea, date, timestamp) ; mogrify écrit les littéraux de ce langage ('\x01ff'::bytea, true, '2024-01-02'::date).
  • Types : comme en langage MIRAJ (Decimal, date, datetime, timedelta pour time, bytes, None), et bool pour un booléen. cursor.description garde les codes de type du mode MIRAJ (miraj.NUMBER, STRING…), avec précision et échelle des numeric et nullabilité lue dans pg_attribute.
  • Résultats : statusmessage est l'étiquette du serveur (INSERT 0 1, SELECT 3) ; lastrowid vaut None (utiliser INSERT … RETURNING).
  • Copies : copy_from, copy_to et copy_expert passent par COPY … FROM STDIN / TO STDOUT du serveur (formats texte et CSV, 27.11) ; connection.insert_rows aussi.
  • Transactions : autocommit, commit, rollback, isolation_level, readonly et set_session comme en langage MIRAJ ; callproc appelle une procédure (CALL), dont les paramètres INOUT reviennent en ligne de résultat.
  • Erreurs : SQLSTATE de PostgreSQL (e.sqlstate, e.pgcode), sans code MIRAJ (errno 0) ; 42xxx → ProgrammingError, 23xxx → IntegrityError, 57014 → QueryCanceledError. Avis du serveur (RAISE NOTICE) dans connection.notices.

psycopg2 lui-même fonctionne aussi sur une telle instance (chapitre 30).