# dlang.sqlite > ### 🤖 AI-generated > This project, its source code and this documentation were (largely) **generated by AI** > (GitHub Copilot). The code has not been fully reviewed by a human for correctness, > security or production readiness. > > **Use at your own risk.** Review the code before putting it into production, and be extra > careful about the limitations listed below (e.g. no parameter binding). SQLite module (plugin) for the **Dlang** interpreter. The module lets a Dlang program open a SQLite database, run queries, and run queries **asynchronously** without blocking the VM. --- ## Contents - [How it works](#how-it-works) - [Requirements](#requirements) - [Building](#building) - [Installing as a plugin](#installing-as-a-plugin) - [Quick start](#quick-start) - [API reference](#api-reference) - [Result format](#result-format) - [Asynchronous usage](#asynchronous-usage) - [Limitations and known issues](#limitations-and-known-issues) --- ## How it works The plugin builds a native shared library (`dlang.sqlite.dll` on Windows, `dlang.sqlite.so` on Linux). The Dlang interpreter loads it dynamically via `require("sqlite")`, which pulls the file from the `./plugins/` folder next to the interpreter. The plugin exports a single function, `initialize`, which creates a global `sqlite` table in the VM containing the functions `open`, `query`, `query_async`, `poll` and `close`. Queries are executed with `sqlite3_prepare_v2` and the results are pushed onto the VM stack as Dlang tables and lists. --- ## Requirements | Component | Version / note | |---|---| | CMake | 3.10 or newer | | C++ compiler | C++20 (MSVC, GCC or Clang) | | Git | required for the SQLite `FetchContent` | | Ninja | used by the CMake presets | | SQLite | 3.49.1, fetched automatically via `FetchContent` (`sjinks/sqlite3-cmake`) | A working **Dlang interpreter** is needed to actually load the plugin. --- ## Building The project uses `CMakePresets.json`. Use the CMake extension in VS Code, or build manually from the command line: ```bash # Configure (pick the preset that matches your platform) cmake --preset x64-release # Build cmake --build --preset x64-release ``` Available presets: | Preset | Platform | |---|---| | `x64-debug` | Windows x64, Debug | | `x64-release` | Windows x64, Release | | `x86-debug` | Windows x86, Debug | | `x86-release` | Windows x86, Release | | `linux-debug` | Linux, Debug | | `macos-debug` | macOS, Debug | The build output lands in `out/build//dlang.sqlite/`: - Windows: `dlang.sqlite.dll` - Linux: `dlang.sqlite.so` > Note: the project only builds the module (a `SHARED` library). There is no executable; the > module is meant to be loaded by the Dlang interpreter. --- ## Installing as a plugin The plugin loader (`plugins.hpp`) looks for the file as `./plugins/.dll` (Windows) or `./plugins/.so` (Linux), relative to the interpreter's working directory. 1. Create a `plugins` folder next to the Dlang interpreter. 2. Copy the built library there and **rename** it to the name you use in `require(...)`: ```text my-dlang-app/ ├── dlang.exe # or dlang └── plugins/ └── sqlite.dll # Windows (built as dlang.sqlite.dll) sqlite.so # Linux (built as dlang.sqlite.so) ``` 3. In your Dlang script: ```dlang require("sqlite"); ``` The `require` call returns the string `"plugin loaded!"` or `"Failed to load plugin!"`. --- ## Quick start ```dlang // main.dl require("sqlite"); // 1. Open the database (creates the file if it does not exist) if (sqlite.open("test.db")) then print("Database opened"); else print("Could not open the database"); end // 2. Create and populate a table sqlite.query("CREATE TABLE IF NOT EXISTS users (id INTEGER, name TEXT);"); var ins = sqlite.query("INSERT INTO users (id, name) VALUES (1, 'Ada');"); print("Rows changed:", ins.rowsAffected); // 3. A query that returns rows var res = sqlite.query("SELECT id, name FROM users;"); if (res.success) then foreach (row in res.rows) do print(row.name); end else print("Error:", res.error); end // 4. Shut down sqlite.close(); ``` > The syntax above follows the Dlang language (`func`, `if ... then ... end`, > `foreach (x in list) do ... end`, comments with `//` or `--`). --- ## API reference The module is available as the global `sqlite` table (after `require("sqlite")`). ### `sqlite.open(path) -> bool` Opens (or creates) a database file at `path`. - If a connection is already open, it is closed first. - Sets `PRAGMA journal_mode = WAL` and `PRAGMA synchronous = NORMAL` for better performance. - Returns `true` on success, `false` on failure or if the argument is not a string. - The path is taken from the argument; there is a single **global** connection (`g_db`). ### `sqlite.query(sql) -> table` Runs the SQL synchronously and returns a result table (see [Result format](#result-format)). Works for `SELECT`, `INSERT`, `UPDATE`, `DELETE`, DDL and multiple statements in one string. ### `sqlite.query_async(sql, callback)` Runs the SQL on a **background thread** and calls `callback` later with the result. The call does not block the VM. - `sql` must be a string and `callback` must be a function; otherwise nothing happens. - The result is pushed onto a queue; the callback is **only** invoked during a call to `sqlite.poll()`. - Returns nothing. ### `sqlite.poll()` Drains all completed asynchronous tasks and invokes their callbacks (each with one argument: the result table). Call this regularly in your main loop. ### `sqlite.close() -> bool` Closes the global database connection and returns `true`. --- ## Result format `query` and the `query_async` callback return a table with these fields: | Field | Type | Meaning | |---|---|---| | `success` | bool | `true` if the query ran without error | | `error` | string | SQLite error message; **only present** when `success` is `false` | | `rowsAffected` | number | Number of changed rows (`sqlite3_changes`) | | `rows` | list | List of rows; each row is a table of column name → value | Values inside a row are converted as follows: | SQLite type | Dlang type | |---|---| | `INTEGER` | number (via `double`, so 64-bit precision may be lost) | | `FLOAT` | number | | `TEXT` | string | | `NULL` / other | `nil` | Example of reading a row: ```dlang var res = sqlite.query("SELECT id, name FROM users;"); foreach (row in res.rows) do print(row.id, row.name); end ``` --- ## Asynchronous usage ```dlang require("sqlite"); sqlite.open("test.db"); func onResult(res) if (res.success) then print("Query finished, rows:", res.rowsAffected); else print("Query failed:", res.error); end end // Start the query on a background thread sqlite.query_async("SELECT * FROM users;", onResult); // Process completed tasks in the main loop while (true) do sqlite.poll(); end ``` Important: - Without regular `sqlite.poll()` calls, callbacks are **never** invoked. - The callback always runs on the main thread (during `poll`), never on the background thread. --- ## Limitations and known issues These are deliberate simplifications or real risks in the current, AI-generated code: 1. **No parameter binding.** SQL is passed to SQLite as a plain string. Never put user input directly into a query → **SQL injection**. This is the most important limitation to fix. 2. **Single global connection.** `g_db` is a single `sqlite3*`; concurrent queries or multiple databases are not supported. 3. **Thread safety.** `sqlite3_open`, `sqlite3_close` and `query` run without a lock alongside the background threads of `query_async`. A `close()` during an in-flight async query can cause crashes or undefined behaviour. SQLite is also not opened in `serialized` mode by default. 4. **64-bit integers.** `SQLITE_INTEGER` is converted to `double`; values above 2^53 may lose precision. 5. **Column names as keys.** For columns with the same name (e.g. from a `JOIN`) and for unnamed expressions, the last value wins; the column name can be empty. 6. **No transactions or busy timeout.** There is no API for `BEGIN`/`COMMIT` or for `sqlite3_busy_timeout`; on a locked database a query fails immediately with `SQLITE_BUSY`. 7. **Windows and Linux only.** The plugin loader supports no other platforms. 8. **AI code.** This module is AI-generated and not fully tested. The project contains no tests. --- ## License There is no license file in this repository yet. Add a license before reusing or distributing this project.