MIRAJv1.0
EN

14. Client libraries and C API

In addition to the network server and the miraj-cli console, MIRAJ can be embedded directly in an application through its native library miraj.dll, which exposes the engine through a stable C API (51 functions, described in miraj.h). No network connection, no separate process: the engine runs inside the application's process.

On top of this C API, idiomatic wrappers are provided for several languages, so that you do not have to write the low-level calls yourself: Delphi (unit Miraj.pas, the product's historical API), C#, C++, Python, Java, PHP, Node.js and Go. Each wrapper follows the same general organization (Server / Session / Result) and the same conventions as the original Delphi unit.

Important: the embedded engine lives in the process that opened it. It is suitable for desktop applications, command-line scripts and scheduled tasks. For a web server with several processes or worker threads (PHP-FPM, Java thread pool, etc.) that must share the same data, use the miraj-server network server with a standard SQL client rather than the embedded library.

14.1 What each wrapper exposes#

All the wrappers revolve around three objects:

  • Server (TMiraj in Delphi): opens a root folder of databases, owns the root session and lets you create others.
  • Session: executes SQL (execute, query, positional ? or named :nom parameters), and gives access to the last auto-increment identifier, the number of affected rows, the warnings and the language of error messages.
  • Result (ResultSet): describes the columns (name, type, scale) and gives access to the cells, either by position (row, column) or by sequential traversal (next / field).

An SQL error raises an exception specific to each language (code, SQLSTATE, line/column for a syntax error), built from the last error stored by the DLL.

The following sections give, for each language, a minimal complete example — connection, query, reading the result, closing — in the actual syntax of the wrapper as it exists in the repository (crates/miraj-ffi/<langage>).

14.2 C# (.NET)#

Wrapper: crates/miraj-ffi/csharp/Miraj.cs (P/Invoke, .NET 8, namespace Miraj). Reference Miraj.cs in your project (or the compiled binary) and copy miraj.dll (64-bit) next to the executable.

using Miraj;

using var server = MirajServer.Open("data");          // loads the databases in the folder
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")}");

Error handling: MirajException (Code, SqlState, Line, Column); misuse (null handle, invalid UTF-8) raises the standard .NET exceptions (ArgumentException, ArgumentOutOfRangeException).

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

MirajServer, MirajSession and MirajResultSet implement IDisposable (using). The program csharp/Program.cs (built by csharp/build.ps1 [-SkipCargo]) is a complete suite of checks of the wrapper (named parameters, QueryValue, ExecSql, warnings, multiple sessions, reopening an existing folder) that documents its usage in detail.

14.3 Python#

Wrapper: crates/miraj-ffi/python/miraj.py (pure ctypes module, no dependency to install). Copy miraj.py next to your script, or add its folder to PYTHONPATH; the DLL is searched for via the MIRAJ_DLL environment variable, otherwise miraj.dll on the system path, otherwise an explicit miraj.load_library(chemin).

import miraj

with miraj.Server.open("data") as server:      # loads the databases in the folder
    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"])

Error handling: miraj.MirajError (code, sqlstate, line, column); misuse detected by the DLL raises miraj.MirajMisuseError (also derives from ValueError) or miraj.MirajRangeError (also derives from IndexError).

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

A minimal DB-API 2.0 (PEP 249) compliant layer is provided in addition to the object-oriented API: miraj.connect(root, database=...) returns a Connection with cursor(), and the resulting Cursor supports execute(), fetchone()/fetchall() and iteration, to integrate with code already written against the standard Python driver interface.

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 (run by python/build.ps1) is the reference verification suite.

14.4 Java (JNA)#

Wrapper: crates/miraj-ffi/java/src/main/java/com/cirtait/miraj (JNA 5.19.1, compatible with Java 8, Maven group com.cirtait:miraj-java). MirajLibrary declares the C API as is; Server, Session and Result form the AutoCloseable object wrapper. The DLL is searched for under the name miraj via the jna.library.path system property, then in the system paths; a 64-bit JVM is required.

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

try (Server server = Server.open("data");          // loads the databases in the folder
     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"));
        }
    }
}

Error handling: MirajException (inherited for any failure returned by the DLL, SQL code included). Parameter types are converted automatically (BigInteger, BigDecimal, LocalDateTime, byte[], etc. — see the Javadoc of 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 produces the jar; java/build.ps1 builds the DLL then runs the verification program com.cirtait.miraj.verif.MirajDllVerif (97 checks).

14.5 PHP (FFI)#

Wrapper: crates/miraj-ffi/php/src (namespace Miraj, PSR-4 loading via composer.json, PHP 8.1+ 64-bit, ext-ffi extension).

use Miraj\Server;

$server  = Server::open('data');                      // loads the databases in the folder
$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();                                      // or let __destruct do it

Error handling: 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";
}

The FFI extension ships with PHP but is not always enabled: on the command line, without touching php.ini, use php -d extension=ffi -d ffi.enable=1 script.php. In web production, ffi.enable=preload with a preload script is recommended (see crates/miraj-ffi/php/README.md) — the embedded MIRAJ server only lives for the duration of the PHP process that opened it, so PHP-FPM and Apache/mod_php are not suitable: prefer miraj-server for a website, and reserve the PHP wrapper for command-line scripts, scheduled tasks and single-process desktop applications. composer run verification (or php/build.ps1) runs the verification suite.

14.6 Node.js#

Wrapper: crates/miraj-ffi/node/index.js + index.d.ts (TypeScript declarations), through koffi 3.3.1, synchronous calls only (see the comment at the top of index.js for the rationale: the last error and the strings returned by the DLL are specific to the calling thread).

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

const server = Server.open('data');                  // loads the databases in the folder
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();

With using support (Symbol.dispose, recent Node 18+ / TypeScript 5.2+), closing is automatic:

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

Error handling: MirajError (SQL code if positive, -1 misuse, -2 row/column out of the result); an unsupported parameter type raises TypeError.

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

64-bit integers: returned as a number if they fit within Number.MAX_SAFE_INTEGER, as a BigInt otherwise (or always as a BigInt with the { bigInt: true } option). npm test (or node/build.ps1) runs test/verification.js.

14.7 Go#

Wrapper: crates/miraj-ffi/go (module github.com/cirtait/miraj-go), without cgo: the DLL is loaded dynamically via golang.org/x/sys/windows (neither gcc nor an import library required), 64-bit Windows only for now. A database/sql driver is provided in the mirajsql subpackage, registered under the name "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)    // loads the databases in the folder
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)
}

Or via database/sql:

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

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

Error handling: *miraj.Error (Code, SQLState, Line, Column, Message), in the idiomatic Go way (an error value is returned, no exceptions).

A candid note: at the time of writing, this Go wrapper is written but not yet compiled or tested at runtime — Go was not installed on the development machine that produced it (see crates/miraj-ffi/go/README.md, "Tests" section). The tests (go test ./..., run by go/build.ps1) reproduce the checks of the C# wrapper, but have not yet been run successfully. Linux and macOS are mentioned as a future direction (purego variant, not implemented).

14.8 C / C++#

Two levels in crates/miraj-ffi/cpp:

  • Pure C (miraj_c_test.c): direct calls to the C API of miraj.h (see § 14.9) — useful for a language without a dedicated wrapper or a minimal integration.
  • Header-only C++17 (miraj.hpp): RAII classes that are non-copyable but movable (Server, Session, Result, ResultList), which release their handle in the destructor or via close().
#include "miraj.hpp"
#include <iostream>

auto server = miraj::Server::open("data");         // loads the databases in the folder
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';

Error handling: every failure raises miraj::Error (SQL code, SQLSTATE, line/column of a syntax error); miraj::MisuseError and miraj::RangeError derive from it for codes -1 and -2. Only Session::exec_sql / exec_script return the code instead of throwing, in order to process a multi-statement script in which some statements may fail.

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

Linking: miraj.dll.lib (import library produced by cargo) against the executable, miraj.dll next to it. cpp/build.ps1 builds the DLL (MSVC) then the two test programs (99 C++ checks, 23 in pure C).

14.9 Low-level C API (miraj.h)#

To integrate MIRAJ into a language without a dedicated wrapper, or for fine-grained control over calls, the C API declared in crates/miraj-ffi/include/miraj.h can be used directly. It comprises 51 functions exported by miraj.dll, organized into families:

FamilyPurposeExample functions
GlobalLibrary version, last error of the calling thread.miraj_version, miraj_last_error_code, miraj_last_error_message, miraj_last_error_sql_state, miraj_last_error_line, miraj_last_error_column
ServerOpening/closing the embedded engine on a root folder, root session, creating sessions, language, forced save.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
SessionSQL execution (with or without a result, positional or named parameters, multi-statement script), current database, last inserted identifier, warnings, language, last error of the 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
Result listsA multi-statement script returns a list of results, one per statement.miraj_result_list_count, miraj_result_list_take, miraj_result_list_free
ResultColumn metadata (name, type, scale) and cell reading, by type or as a generic value.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 common to the whole API:

  • Opaque handles (MirajServer*, MirajSession*, MirajResult*, MirajResultList*), explicitly released by the corresponding _close / _free function (a null pointer is accepted and has no effect). The root session belongs to the server: do not close it yourself; close the other sessions before the server. A result (MirajResult) can outlive its session and its server.
  • Return codes: MIRAJ_OK (0) on success; a positive integer is an SQL error code (for example 1146 unknown table, 1105 internal error or recovered panic); MIRAJ_ERR_MISUSE (-1) signals misuse of the API (null handle, invalid UTF-8); MIRAJ_ERR_RANGE (-2) signals an out-of-bounds index (row or column). A failure is stored as the last error of the calling thread, which can be queried via miraj_last_error_*; a success clears it.
  • UTF-8 strings: as input, null-terminated (SQL, names) or data given as pointer + length for parameter values; as output, pointer + length in bytes followed by a null byte, valid until the next call from the same thread that returns data — to be copied immediately on the caller side, never to be freed.
  • Dates: TDateTime type (number of days since 12/30/1899, as a double), the same convention as the historical Delphi API.
  • Threads: the server can be used from several threads; a session is serialized by an internal lock (one session per thread is advised to avoid waits); a result can be read from several threads; a result list is restricted to a single thread.

The file crates/miraj-ffi/tests/contract.rs verifies that each of the 51 functions is declared in miraj.h, in the Delphi unit and in each language wrapper, to prevent any discrepancy between them.

14.10 Delphi (Miraj.pas)#

crates/miraj-ffi/delphi/Miraj.pas is the Delphi wrapper of miraj.dll: it reproduces the historical public API of MIRAJ in Delphi (TMiraj, TMirajSession, IMirajResultSet, EMirajError), so that an existing application can switch to the new engine by replacing its units Miraj, Miraj.Session, Miraj.ResultSet, Miraj.Errors with this single unit in its uses clause.

uses
  Miraj;

var
  Server: TMiraj;
  Session: TMirajSession;
  RS: IMirajResultSet;
begin
  Server := TMiraj.Open('data');              // loads the databases in the folder
  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;                              // closes the sessions, then the server
  end;
end;

Error handling: EMirajError (Code, SqlState, Line, Column), raised by all methods of the "with exceptions" API (Execute, Query, QueryNamed, UseDatabase, ...). A "no-exception" API also exists on TMirajSession: ExecSQL returns 0 or the error code and fills a list of IMirajResultSet, one per statement of the script — useful for running a multi-statement script without interrupting the program at the first error.

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 releases itself (reference-counted interface); the sessions created by TMiraj.CreateSession belong to it and are closed with Server.Free or explicitly by Server.CloseSession. Main differences from the original Delphi API, documented at the top of the file: TMiraj no longer derives from TMirajServer (the internal catalog is not exposed by the DLL — FindDatabase, GetTable are absent, CurrentDatabase becomes CurrentDatabaseName), IMirajResultSet.ColumnVector (TVector) is absent, and EMirajError no longer has a constructor with catalog arguments (the message already arrives translated into the session language).

14.11 Summary of the wrappers#

LanguageTechniqueStatusVerification suite
DelphiStatic cdecl importsHistorical reference wrapper—
C#P/Invoke (.NET 8)Complete79 checks (csharp/Program.cs)
C++Header-only, RAII, exceptionsComplete99 (C++) + 23 (pure C)
Pythonctypes + minimal DB-API 2.0 layerComplete124 checks
JavaJNA 5.19.1Complete97 checks
PHPFFIComplete (web deployment limits documented, § 14.5)124 checks
Node.jskoffi, synchronousComplete116 checks
GoDynamic loading without cgo, database/sql driverWritten, not yet compiled or run (Go absent from the development machine)not run