14. 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 (51 fonctions, décrites dans miraj.h). Aucune connexion réseau, aucun processus séparé : le moteur tourne dans le processus de l'application.
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-serveravec un client SQL classique plutôt que la bibliothèque embarquée.
14.1 Ce que chaque enveloppe expose#
Toutes les enveloppes tournent autour de trois objets :
Server(TMirajen 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 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 telle qu'elle existe dans le dépôt (crates/miraj-ffi/<langage>).
14.2 C# (.NET)#
Enveloppe : crates/miraj-ffi/csharp/Miraj.cs (P/Invoke, .NET 8, namespace Miraj). Référencez Miraj.cs dans votre projet (ou le binaire compilé) 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). Le programme csharp/Program.cs (compilé par csharp/build.ps1 [-SkipCargo]) est une suite de vérifications complète de l'enveloppe (paramètres nommés, QueryValue, ExecSql, avertissements, sessions multiples, réouverture d'un dossier existant) qui documente son usage en détail.
14.3 Python#
Enveloppe : crates/miraj-ffi/python/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 via la variable d'environnement MIRAJ_DLL, sinon miraj.dll sur le chemin système, sinon miraj.load_library(chemin) explicite.
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 row in rs:
print(row["nom"], row["prix"])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(), et le Cursor obtenu supporte execute(), fetchone()/fetchall() et l'itération, pour s'intégrer avec du code déjà écrit contre l'interface standard des pilotes Python.
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()test_miraj.py (exécuté par python/build.ps1) est la suite de vérification de référence.
14.4 Java (JNA)#
Enveloppe : crates/miraj-ffi/java/src/main/java/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 de package-info.java).
try {
session.execute("SELECT * FROM absente");
} catch (com.cirtait.miraj.MirajException e) {
System.out.println("Erreur " + e.getCode() + " : " + e.getMessage());
}mvn package produit le jar ; java/build.ps1 compile la DLL puis exécute le programme de vérification com.cirtait.miraj.verif.MirajDllVerif (97 vérifications).
14.5 PHP (FFI)#
Enveloppe : crates/miraj-ffi/php/src (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 faireGestion 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é (voir crates/miraj-ffi/php/README.md) — 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. composer run verification (ou php/build.ps1) lance la suite de vérification.
14.6 Node.js#
Enveloppe : crates/miraj-ffi/node/index.js + index.d.ts (déclarations TypeScript), par koffi 3.3.1, appels uniquement synchrones (voir le commentaire en tête d'index.js pour la justification : 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 avec l'option { bigInt: true }). npm test (ou node/build.ps1) exécute test/verification.js.
14.7 Go#
Enveloppe : crates/miraj-ffi/go (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`)Gestion des erreurs : *miraj.Error (Code, SQLState, Line, Column, Message), à la manière idiomatique Go (valeur d'erreur renvoyée, pas d'exception).
À noter honnêtement : au moment de la rédaction, cette enveloppe Go est écrite mais pas encore compilée ni testée en exécution — Go n'était pas installé sur le poste de développement qui l'a produite (voir
crates/miraj-ffi/go/README.md, section « Tests »). Les tests (go test ./..., lancés pargo/build.ps1) reprennent les vérifications de l'enveloppe C#, mais n'ont pas encore été exécutés avec succès. Linux et macOS sont évoqués comme piste future (variantepurego, non implémentée).
14.8 C / C++#
Deux niveaux dans crates/miraj-ffi/cpp :
- C pur (
miraj_c_test.c) : appels directs à l'API C demiraj.h(voir § 14.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 viaclose().
#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';
}Édition de liens : miraj.dll.lib (bibliothèque d'import produite par cargo) contre l'exécutable, miraj.dll à côté. cpp/build.ps1 compile la DLL (MSVC) puis les deux programmes de test (99 vérifications C++, 23 en C pur).
14.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 crates/miraj-ffi/include/miraj.h peut être utilisée directement. Elle compte 51 fonctions exportées par miraj.dll, organisées en familles :
| Famille | Rôle | Exemples de fonctions |
|---|---|---|
| Globale | 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 | Ouverture/fermeture du moteur embarqué sur un dossier racine, session racine, création de sessions, langue, sauvegarde forcée. | miraj_options_init, miraj_server_open, 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 | 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_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_warning_count / _warning, miraj_session_last_error |
| Listes de résultats | Un script à plusieurs instructions renvoie une liste de résultats, un par instruction. | miraj_result_list_count, miraj_result_list_take, miraj_result_list_free |
| Résultat | 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_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 |
Concepts communs à toute l'API :
- Handles opaques (
MirajServer*,MirajSession*,MirajResult*,MirajResultList*), libérés explicitement par la fonction_close/_freecorrespondante (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 exemple1146table inconnue,1105erreur 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 parmiraj_last_error_*; un succès l'efface. - 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, endouble), la même convention que l'API Delphi historique. - 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.
Le fichier crates/miraj-ffi/tests/contract.rs vérifie que chacune des 51 fonctions est bien déclarée dans miraj.h, dans l'unité Delphi et dans chaque enveloppe de langage, pour éviter tout écart entre elles.
14.10 Delphi (Miraj.pas)#
crates/miraj-ffi/delphi/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).
14.11 Récapitulatif des enveloppes#
| Langage | Technique | État | Suite de vérification |
|---|---|---|---|
| Delphi | Imports statiques cdecl | Enveloppe historique de référence | — |
| C# | P/Invoke (.NET 8) | Complète | 79 vérifications (csharp/Program.cs) |
| C++ | En-tête seul, RAII, exceptions | Complète | 99 (C++) + 23 (C pur) |
| Python | ctypes + couche DB-API 2.0 minimale | Complète | 124 vérifications |
| Java | JNA 5.19.1 | Complète | 97 vérifications |
| PHP | FFI | Complète (limites de déploiement web documentées, § 14.5) | 124 vérifications |
| Node.js | koffi, synchrone | Complète | 116 vérifications |
| Go | Chargement dynamique sans cgo, pilote database/sql | Écrite, non encore compilée ni exécutée (Go absent du poste de développement) | non exécutée |