├── lib ├── win32 │ ├── sqlite3.dll │ ├── sqlite3.lib │ └── sqlite3.def ├── win64 │ ├── sqlite3.dll │ ├── sqlite3.lib │ └── sqlite3.def └── Makefile ├── .gitignore ├── sqlite3.mak ├── run-dstep.sh ├── LICENSE ├── dub.sdl ├── README.md ├── .github └── workflows │ ├── documentation.yaml │ └── main.yaml └── source ├── d2sqlite3 ├── internal │ ├── memory.d │ └── util.d ├── library.d ├── package.d ├── statement.d ├── results.d └── database.d └── tests.d /lib/win32/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlang-community/d2sqlite3/HEAD/lib/win32/sqlite3.dll -------------------------------------------------------------------------------- /lib/win32/sqlite3.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlang-community/d2sqlite3/HEAD/lib/win32/sqlite3.lib -------------------------------------------------------------------------------- /lib/win64/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlang-community/d2sqlite3/HEAD/lib/win64/sqlite3.dll -------------------------------------------------------------------------------- /lib/win64/sqlite3.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlang-community/d2sqlite3/HEAD/lib/win64/sqlite3.lib -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | dub.selections.json 3 | __test__* 4 | d2sqlite3-test* 5 | libd2sqlite3.a 6 | TODO.txt 7 | docs.json 8 | docs 9 | sqlite3.o 10 | lib/*/*.lib 11 | -------------------------------------------------------------------------------- /sqlite3.mak: -------------------------------------------------------------------------------- 1 | CC?= /usr/bin/cc 2 | all: sqlite3.o 3 | 4 | sqlite3.o: c/sqlite3.c 5 | $(CC) -c -O2 -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_UNLOCK_NOTIFY c/sqlite3.c 6 | 7 | clean: 8 | rm -f *.o 9 | -------------------------------------------------------------------------------- /run-dstep.sh: -------------------------------------------------------------------------------- 1 | dstep --package "d2sqlite3" --skip SQLITE_STDCALL --global-attribute={nothrow,@nogc} --space-after-function-name=false -o source/d2sqlite3/sqlite3.d c/sqlite3.h 2 | sed -i '' '1i\ 3 | /++ Auto-generated C API bindings. +/\ 4 | ' source/d2sqlite3/sqlite3.d 5 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | # This must be run from a VS Native Tools command interpreter, or after running 2 | # vcvarsall.bat 3 | 4 | .PHONY: all x86 x64 5 | 6 | all: x86 x64 7 | 8 | x86: win32\sqlite3.lib 9 | 10 | x64: win64\sqlite3.lib 11 | 12 | win32\sqlite3.lib: win32\sqlite3.dll 13 | +lib /def:win32\sqlite3.def /out:win32\sqlite3.lib /machine:x86 14 | 15 | win64\sqlite3.lib: win64\sqlite3.dll 16 | +lib /def:win64\sqlite3.def /out:win64\sqlite3.lib /machine:x64 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "d2sqlite3" 2 | description "A thin wrapper around SQLite 3" 3 | homepage "https://github.com/dlang-community/d2sqlite3" 4 | authors "Nicolas Sicard" "Other contributors: see Github repo" 5 | copyright "Copyright 2011-18 Nicolas Sicard" 6 | license "BSL-1.0" 7 | targetType "library" 8 | configuration "with-lib" { 9 | systemDependencies "SQLite version >= 3.8.7" 10 | libs "sqlite3" 11 | excludedSourceFiles "source/tests.d" 12 | } 13 | configuration "without-lib" { 14 | excludedSourceFiles "source/tests.d" 15 | } 16 | configuration "all-included" { 17 | libs "sqlite3" platform="windows" 18 | copyFiles "lib/win32/sqlite3.dll" "lib/win32/sqlite3.lib" platform="windows-x86" 19 | copyFiles "lib/win64/sqlite3.dll" "lib/win64/sqlite3.lib" platform="windows-x86_64" 20 | preBuildCommands "make -C $PACKAGE_DIR -f sqlite3.mak" platform="posix" 21 | sourceFiles "sqlite3.o" platform="posix" 22 | libs "dl" platform="linux-gdc" 23 | excludedSourceFiles "source/tests.d" 24 | } 25 | configuration "ci" { 26 | preBuildCommands "make -C $PACKAGE_DIR -f sqlite3.mak" platform="posix" 27 | sourceFiles "sqlite3.o" platform="posix" 28 | versions "SqliteEnableColumnMetadata" "SqliteEnableUnlockNotify" 29 | } 30 | 31 | configuration "unittest" { 32 | // Make sure source/tests.d is included 33 | systemDependencies "SQLite version >= 3.8.7" 34 | libs "sqlite3" 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `D2Sqlite3` 2 | 3 | [![Dub](https://img.shields.io/dub/v/d2sqlite3.svg)](http://code.dlang.org/packages/d2sqlite3) 4 | [![Downloads](https://img.shields.io/dub/dt/d2sqlite3.svg)](https://code.dlang.org/packages/d2sqlite3) 5 | 6 | This is a small wrapper around SQLite for the D programming language. 7 | It wraps the C API in an idiomatic manner and handles built-in D types and 8 | `Nullable!T` automatically. 9 | 10 | ## Documentation 11 | 12 | [Online documentation](http://dlang-community.github.io/d2sqlite3/d2sqlite3.html) 13 | 14 | ## `dub` configurations 15 | 16 | - **`with-lib`** (the default): assumes that SQLite is already installed and available to the linker. Set the right path for the SQLite library in your project's `dub.json` file using the `lflags` setting: 17 | 18 | ```json 19 | "lflags": ["-L/path/to/lib"] 20 | ``` 21 | 22 | - **`without-lib`**: you manage linking SQLite yourself. 23 | 24 | - **`all-included`**: on Windows, use a prebuilt SQLite DLL (bundled with this library); on Posix systems, builds SQLite from the source amalgamation (bundled with this library), using the default building configuration with these options defined: 25 | - SQLITE_ENABLE_COLUMN_METADATA 26 | - SQLITE_ENABLE_UNLOCK_NOTIFY 27 | 28 | Set the right configuration for you project in its `dub.json` file using the `subConfigurations` setting, e.g.: 29 | 30 | ```json 31 | "subConfigurations": { 32 | "d2sqlite3": "all-included" 33 | } 34 | ``` 35 | 36 | ## Library versions 37 | 38 | These versions can be used to build the library: 39 | 40 | - `SqliteEnableColumnMetadata`: to enable corresponding special methods of `Row`. 41 | - `SqliteEnableUnlockNotify`: to enable SQLite's builtin unlock notification mechanism. 42 | - `SqliteFakeUnlockNotify`: to emulate an unlock notification mechanism. 43 | 44 | ## C binding generation 45 | 46 | The D binding file `sqlite3.d` is generated from the C header file `sqlite3.h`, using [jacob-carlborg/dstep](https://github.com/jacob-carlborg/dstep). I try to keep it up to date. 47 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yaml: -------------------------------------------------------------------------------- 1 | # Documentation build: Only runs on Linux 2 | # Still need to install dependencies and the compiler because DDOX 3 | # does a full build 4 | name: Documentation 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | doc: 9 | name: Build and upload documentation 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 30 12 | steps: 13 | 14 | # Checkout this repository and its submodules 15 | - name: Checkout repository 16 | uses: actions/checkout@v2 17 | 18 | # Install the D compiler 19 | - name: Prepare compiler 20 | uses: dlang-community/setup-dlang@v1 21 | with: 22 | compiler: ldc-latest 23 | 24 | - name: 'Install dependencies & setup environment' 25 | run: | 26 | sudo apt-get update 27 | sudo apt-get install libsqlite3-dev 28 | 29 | - name: Build documentation 30 | run: | 31 | dub build -b ddox 32 | # Generate the HTML to docs 33 | dub run ddox -- generate-html docs.json ./docs/ 34 | 35 | - name: Upload documentation artifact 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: documentation 39 | path: docs/ 40 | 41 | - name: Deploy documentation 42 | if: github.event_name == 'push' 43 | run: | 44 | # Remove gh-branch if it already exists, check it out 45 | git branch -D gh-pages || true 46 | git checkout --orphan gh-pages 47 | # Remove all staged files - We only need the docs 48 | git rm -rf $(git ls-files) 49 | # We can have some leftover files (e.g. build) 50 | # So add docs (which is only what we need), then `git mv` it. 51 | git add docs/ 52 | git mv -k docs/* ./ 53 | # Configure user (because persist-credentials does not persist everything...) 54 | git config --global user.name "${{ github.actor }}" 55 | git config --global user.email "${{ github.actor }}@users.noreply.github.com" 56 | # We're done 57 | git commit -m "Documentation for commit ${{ github.sha }}" 58 | git push -f ${{ github.event.repository.clone_url }} gh-pages:gh-pages 59 | -------------------------------------------------------------------------------- /source/d2sqlite3/internal/memory.d: -------------------------------------------------------------------------------- 1 | /+ 2 | This module is part of d2sqlite3. 3 | 4 | Authors: 5 | Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) 6 | 7 | Copyright: 8 | Copyright 2011-18 Nicolas Sicard. 9 | 10 | License: 11 | $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 | +/ 13 | module d2sqlite3.internal.memory; 14 | 15 | import std.traits : isFunctionPointer, isDelegate, isCallable; 16 | import core.memory : GC; 17 | import core.stdc.stdlib : malloc, free; 18 | 19 | package(d2sqlite3): 20 | 21 | struct WrappedDelegate(T) 22 | { 23 | T dlg; 24 | string name; 25 | } 26 | 27 | void* delegateWrap(T)(T dlg, string name = null) nothrow 28 | if (isFunctionPointer!T || isDelegate!T) 29 | { 30 | import std.functional : toDelegate; 31 | 32 | if (dlg is null) 33 | return null; 34 | 35 | alias D = typeof(toDelegate(dlg)); 36 | auto d = cast(WrappedDelegate!D*) malloc(WrappedDelegate!D.sizeof); 37 | d.dlg = toDelegate(dlg); 38 | d.name = name; 39 | return cast(void*) d; 40 | } 41 | 42 | WrappedDelegate!T* delegateUnwrap(T)(void* ptr) nothrow 43 | if (isCallable!T) 44 | { 45 | return cast(WrappedDelegate!T*) ptr; 46 | } 47 | 48 | // Anchors and returns a pointer to D memory, so that it will not 49 | // be moved or collected. For use with releaseMem. 50 | inout(void)* anchorMem(inout(void)* ptr) 51 | { 52 | GC.addRoot(ptr); 53 | // Cast to work around https://issues.dlang.org/show_bug.cgi?id=21484 54 | GC.setAttr(cast(void*) ptr, GC.BlkAttr.NO_MOVE); 55 | return ptr; 56 | } 57 | 58 | // Passed to sqlite3_xxx_blob64/sqlite3_xxx_text64 to unanchor memory. 59 | extern(C) void releaseMem(const void* ptr) 60 | { 61 | // Cast to work around https://issues.dlang.org/show_bug.cgi?id=21484 62 | GC.setAttr(cast(void*) ptr, GC.BlkAttr.NO_MOVE); 63 | GC.removeRoot(ptr); 64 | } 65 | 66 | // Adapted from https://p0nce.github.io/d-idioms/#GC-proof-resource-class 67 | void ensureNotInGC(T)(string info = null) nothrow 68 | { 69 | import core.memory : GC; 70 | import core.stdc.stdio : fprintf, stderr; 71 | import core.stdc.stdlib : exit; 72 | 73 | if (!GC.inFinalizer) 74 | return; 75 | 76 | fprintf(stderr, 77 | "Error: clean-up of %s incorrectly depends on destructors called by the GC.\n", 78 | T.stringof.ptr); 79 | if (info) 80 | fprintf(stderr, "Info: %s\n", info.ptr); 81 | assert(false); 82 | } 83 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | # Github worflow to test this library on all platforms, 2 | # build the documentation, and upload it to Github pages. 3 | name: CI 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | main: 9 | name: Run 10 | strategy: 11 | # Disable `fail-fast` because we want the whole test suite to run even if one 12 | # of the nigthly is broken 13 | fail-fast: false 14 | matrix: 15 | # TODO: FIXME. Requires fixes to the dub.sdl to work on Windows 16 | # os: [ ubuntu-18.04, macOS-10.15, windows-2019 ] 17 | os: [ ubuntu-18.04, macOS-10.15 ] 18 | # Oldest supported FE is 2.090.1 because we need `GC.inFinalizer` 19 | # https://dlang.org/changelog/2.090.0.html 20 | dc: [ ldc-master, ldc-latest, ldc-1.21.0, dmd-master, dmd-latest, dmd-2.090.1 ] 21 | 22 | runs-on: ${{ matrix.os }} 23 | timeout-minutes: 30 24 | steps: 25 | 26 | - uses: actions/checkout@v2 27 | - name: Prepare compiler 28 | uses: dlang-community/setup-dlang@v1 29 | with: 30 | compiler: ${{ matrix.dc }} 31 | 32 | # Install os-specific packages 33 | # Those will show up in the list of steps, but be grayed out, 34 | # hence the usage of the `[OSX]` tag 35 | - name: '[OSX] Install dependencies & setup environment' 36 | if: runner.os == 'macOS' 37 | run: | 38 | echo "LIBRARY_PATH=${LD_LIBRARY_PATH-}:/usr/local/lib/" >> $GITHUB_ENV 39 | echo "PKG_CONFIG_PATH=/usr/local/opt/sqlite/lib/pkgconfig" >> $GITHUB_ENV 40 | 41 | - name: '[Linux] Install dependencies & setup environment' 42 | if: runner.os == 'Linux' 43 | run: | 44 | sudo apt-get update 45 | sudo apt-get install -y libsqlite3-dev 46 | 47 | - name: '[Windows] Install dependencies & setup environment' 48 | if: runner.os == 'Windows' 49 | shell: powershell 50 | run: | 51 | echo "LIB=${{ github.workspace }}\lib\win64\;$LIB" >> $GITHUB_ENV 52 | 53 | # Add whatever debugging information can be useful in the long run here 54 | - name: Print system information 55 | shell: bash 56 | run: | 57 | ${DC} --version 58 | dub --version 59 | 60 | # Build and run the tests 61 | - name: '[POSIX] Build & test' 62 | if: runner.os != 'Windows' 63 | #continue-on-error: matrix.dc == 'ldc-master' || matrix.dc == 'dmd-master' 64 | run: dub test -c ci 65 | 66 | - name: '[Windows] Build & test' 67 | if: runner.os == 'Windows' 68 | #continue-on-error: matrix.dc == 'ldc-master' || matrix.dc == 'dmd-master' 69 | shell: cmd 70 | run: | 71 | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" 72 | dub test -c ci 73 | -------------------------------------------------------------------------------- /source/d2sqlite3/library.d: -------------------------------------------------------------------------------- 1 | /++ 2 | Miscellaneous SQLite3 library functions. 3 | 4 | Authors: 5 | Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) 6 | 7 | Copyright: 8 | Copyright 2011-18 Nicolas Sicard. 9 | 10 | License: 11 | $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 | +/ 13 | module d2sqlite3.library; 14 | 15 | import d2sqlite3.sqlite3; 16 | import d2sqlite3.database : SqliteException; 17 | import std.exception : enforce; 18 | import std.string : format; 19 | 20 | /++ 21 | Gets the library's version string (e.g. "3.8.7"), version number (e.g. 3_008_007) 22 | or source ID. 23 | 24 | These values are returned by the linked SQLite C library. They can be checked against 25 | the values of the enums defined by the `d2sqlite3` package (`SQLITE_VERSION`, 26 | `SQLITE_VERSION_NUMBER` and `SQLITE_SOURCE_ID`). 27 | 28 | See_Also: $(LINK http://www.sqlite.org/c3ref/libversion.html). 29 | +/ 30 | string versionString() 31 | { 32 | import std.conv : to; 33 | return sqlite3_libversion().to!string; 34 | } 35 | 36 | /// Ditto 37 | int versionNumber() nothrow 38 | { 39 | return sqlite3_libversion_number(); 40 | } 41 | 42 | /// Ditto 43 | string sourceID() 44 | { 45 | import std.conv : to; 46 | return sqlite3_sourceid().to!string; 47 | } 48 | 49 | /++ 50 | Tells whether SQLite was compiled with the thread-safe options. 51 | 52 | See_also: $(LINK http://www.sqlite.org/c3ref/threadsafe.html). 53 | +/ 54 | bool threadSafe() nothrow 55 | { 56 | return cast(bool) sqlite3_threadsafe(); 57 | } 58 | 59 | /++ 60 | Manually initializes (or shuts down) SQLite. 61 | 62 | SQLite initializes itself automatically on the first request execution, so this 63 | usually wouldn't be called. Use for instance before a call to config(). 64 | +/ 65 | void initialize() 66 | { 67 | immutable result = sqlite3_initialize(); 68 | enforce(result == SQLITE_OK, new SqliteException("Initialization: error %s".format(result), result)); 69 | } 70 | /// Ditto 71 | void shutdown() 72 | { 73 | immutable result = sqlite3_shutdown(); 74 | enforce(result == SQLITE_OK, new SqliteException("Shutdown: error %s".format(result), result)); 75 | } 76 | 77 | /++ 78 | Sets a configuration option. 79 | 80 | Use before initialization, e.g. before the first 81 | call to initialize and before execution of the first statement. 82 | 83 | See_Also: $(LINK http://www.sqlite.org/c3ref/config.html). 84 | +/ 85 | void config(Args...)(int code, Args args) 86 | { 87 | immutable result = sqlite3_config(code, args); 88 | enforce(result == SQLITE_OK, new SqliteException("Configuration: error %s".format(result), result)); 89 | } 90 | 91 | /++ 92 | Tests if an SQLite compile option is set 93 | 94 | See_Also: $(LINK http://sqlite.org/c3ref/compileoption_get.html). 95 | +/ 96 | bool isCompiledWith(string option) 97 | { 98 | import std.string : toStringz; 99 | return cast(bool) sqlite3_compileoption_used(option.toStringz); 100 | } 101 | /// 102 | version (SqliteEnableUnlockNotify) 103 | unittest 104 | { 105 | assert(isCompiledWith("SQLITE_ENABLE_UNLOCK_NOTIFY")); 106 | assert(!isCompiledWith("SQLITE_UNKNOWN_COMPILE_OPTION")); 107 | } 108 | -------------------------------------------------------------------------------- /source/d2sqlite3/package.d: -------------------------------------------------------------------------------- 1 | /++ 2 | D2SQLite3 provides a thin and convenient wrapper around the SQLite C API. 3 | 4 | Features: 5 | $(UL 6 | $(LI Use reference-counted structs (`Database`, `Statement`) instead of SQLite objects 7 | pointers.) 8 | $(LI Run multistatement SQL code with `Database.run()`.) 9 | $(LI Use built-in integral types, floating point types, `string`, `immutable(ubyte)[]` and 10 | `Nullable` types directly: conversions to and from SQLite types is automatic and GC-safe.) 11 | $(LI Bind multiple values to a prepare statement with `Statement.bindAll()` or 12 | `Statement.inject()`. It's also possible to bind the fields of a struct automatically with 13 | `Statement.inject()`.) 14 | $(LI Handle the results of a query as a range of `Row`s, and the columns of a row 15 | as a range of `ColumnData` (equivalent of a `Variant` fit for SQLite types).) 16 | $(LI Access the data in a result row directly, by index or by name, 17 | with the `Row.peek!T()` methods.) 18 | $(LI Make a struct out of the data of a row with `Row.as!T()`.) 19 | $(LI Register D functions as SQLite callbacks, with `Database.setUpdateHook()` $(I et al).) 20 | $(LI Create new SQLite functions, aggregates or collations out of D functions or delegate, 21 | with automatic type converions, with `Database.createFunction()` $(I et al).) 22 | $(LI Store all the rows and columns resulting from a query at once with the `cached` function 23 | (sometimes useful even if not memory-friendly...).) 24 | $(LI Use an unlock notification when two or more connections access the same database in 25 | shared-cache mode, either using SQLite's dedicated API (sqlite_unlock_notify) or using an 26 | emulated equivalent.) 27 | ) 28 | 29 | Authors: 30 | Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) 31 | 32 | Copyright: 33 | Copyright 2011-18 Nicolas Sicard. 34 | 35 | License: 36 | $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 37 | +/ 38 | module d2sqlite3; 39 | 40 | public import d2sqlite3.library; 41 | public import d2sqlite3.database; 42 | public import d2sqlite3.statement; 43 | public import d2sqlite3.results; 44 | public import d2sqlite3.sqlite3; 45 | 46 | /// 47 | unittest // Documentation example 48 | { 49 | // Note: exception handling is left aside for clarity. 50 | import d2sqlite3; 51 | import std.typecons : Nullable; 52 | 53 | // Open a database in memory. 54 | auto db = Database(":memory:"); 55 | 56 | // Create a table 57 | db.run("DROP TABLE IF EXISTS person; 58 | CREATE TABLE person ( 59 | id INTEGER PRIMARY KEY, 60 | name TEXT NOT NULL, 61 | score FLOAT 62 | )"); 63 | 64 | // Prepare an INSERT statement 65 | Statement statement = db.prepare( 66 | "INSERT INTO person (name, score) 67 | VALUES (:name, :score)" 68 | ); 69 | 70 | // Bind values one by one (by parameter name or index) 71 | statement.bind(":name", "John"); 72 | statement.bind(2, 77.5); 73 | statement.execute(); 74 | statement.reset(); // Need to reset the statement after execution. 75 | 76 | // Bind muliple values at the same time 77 | statement.bindAll("John", null); 78 | statement.execute(); 79 | statement.reset(); 80 | 81 | // Bind, execute and reset in one call 82 | statement.inject("Clara", 88.1); 83 | 84 | // Count the changes 85 | assert(db.totalChanges == 3); 86 | 87 | // Count the Johns in the table. 88 | auto count = db.execute("SELECT count(*) FROM person WHERE name == 'John'") 89 | .oneValue!long; 90 | assert(count == 2); 91 | 92 | // Read the data from the table lazily 93 | ResultRange results = db.execute("SELECT * FROM person"); 94 | foreach (Row row; results) 95 | { 96 | // Retrieve "id", which is the column at index 0, and contains an int, 97 | // e.g. using the peek function (best performance). 98 | auto id = row.peek!long(0); 99 | 100 | // Retrieve "name", e.g. using opIndex(string), which returns a ColumnData. 101 | auto name = row["name"].as!string; 102 | 103 | // Retrieve "score", which is at index 2, e.g. using the peek function, 104 | // using a Nullable type 105 | auto score = row.peek!(Nullable!double)(2); 106 | if (!score.isNull) 107 | { 108 | // ... 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /source/d2sqlite3/internal/util.d: -------------------------------------------------------------------------------- 1 | /+ 2 | This module is part of d2sqlite3. 3 | 4 | Authors: 5 | Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) 6 | 7 | Copyright: 8 | Copyright 2011-18 Nicolas Sicard. 9 | 10 | License: 11 | $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 | +/ 13 | module d2sqlite3.internal.util; 14 | 15 | import std.traits : isBoolean, isIntegral, isFloatingPoint, isSomeString, 16 | isArray, isStaticArray, isDynamicArray; 17 | import std.typecons : Nullable; 18 | import d2sqlite3.sqlite3; 19 | import d2sqlite3.internal.memory; 20 | 21 | package(d2sqlite3): 22 | 23 | string errmsg(sqlite3* db) nothrow 24 | { 25 | import std.conv : to; 26 | return sqlite3_errmsg(db).to!string; 27 | } 28 | 29 | string errmsg(sqlite3_stmt* stmt) nothrow 30 | { 31 | return errmsg(sqlite3_db_handle(stmt)); 32 | } 33 | 34 | auto byStatement(string sql) 35 | { 36 | static struct ByStatement 37 | { 38 | string sql; 39 | size_t end; 40 | 41 | this(string sql) 42 | { 43 | this.sql = sql; 44 | end = findEnd(); 45 | } 46 | 47 | bool empty() const @safe pure nothrow @nogc 48 | { 49 | return !sql.length; 50 | } 51 | 52 | string front() const @safe pure nothrow @nogc 53 | { 54 | return sql[0 .. end]; 55 | } 56 | 57 | void popFront() 58 | { 59 | sql = sql[end .. $]; 60 | end = findEnd(); 61 | } 62 | 63 | private: 64 | size_t findEnd() 65 | { 66 | import std.algorithm : countUntil; 67 | import std.string : toStringz; 68 | import std.utf : byCodeUnit; 69 | 70 | size_t pos; 71 | bool complete; 72 | do 73 | { 74 | auto tail = sql[pos .. $]; 75 | immutable offset = tail.byCodeUnit.countUntil(';') + 1; 76 | pos += offset; 77 | if (offset == 0) 78 | pos = sql.length; 79 | auto part = sql[0 .. pos]; 80 | complete = cast(bool) sqlite3_complete(part.toStringz); 81 | } 82 | while (!complete && pos < sql.length); 83 | return pos; 84 | } 85 | } 86 | 87 | return ByStatement(sql); 88 | } 89 | unittest 90 | { 91 | import std.algorithm : equal, map; 92 | import std.string : strip; 93 | 94 | auto sql = "CREATE TABLE test (dummy); 95 | CREATE TRIGGER trig INSERT ON test BEGIN SELECT 1; SELECT 'a;b'; END; 96 | SELECT 'c;d';; 97 | CREATE"; 98 | assert(equal(sql.byStatement.map!(s => s.strip), [ 99 | "CREATE TABLE test (dummy);", 100 | "CREATE TRIGGER trig INSERT ON test BEGIN SELECT 1; SELECT 'a;b'; END;", 101 | "SELECT 'c;d';", 102 | ";", 103 | "CREATE" 104 | ])); 105 | } 106 | 107 | // getValue and setResult function templates 108 | // used by createFunction and createAggregate 109 | 110 | auto getValue(T)(sqlite3_value* argv) 111 | if (isBoolean!T) 112 | { 113 | return sqlite3_value_int64(argv) != 0; 114 | } 115 | 116 | auto getValue(T)(sqlite3_value* argv) 117 | if (isIntegral!T) 118 | { 119 | import std.conv : to; 120 | return sqlite3_value_int64(argv).to!T; 121 | } 122 | 123 | auto getValue(T)(sqlite3_value* argv) 124 | if (isFloatingPoint!T) 125 | { 126 | import std.conv : to; 127 | if (sqlite3_value_type(argv) == SQLITE_NULL) 128 | return double.nan; 129 | return sqlite3_value_double(argv).to!T; 130 | } 131 | 132 | auto getValue(T)(sqlite3_value* argv) 133 | if (isSomeString!T) 134 | { 135 | import std.conv : to; 136 | return (cast(const(char)*) sqlite3_value_text(argv)).to!T; 137 | } 138 | 139 | auto getValue(T)(sqlite3_value* argv) 140 | if (isArray!T && !isSomeString!T) 141 | { 142 | import std.conv : to; 143 | import core.stdc.string : memcpy; 144 | 145 | auto n = sqlite3_value_bytes(argv); 146 | ubyte[] blob; 147 | blob.length = n; 148 | memcpy(blob.ptr, sqlite3_value_blob(argv), n); 149 | return cast(T) blob; 150 | } 151 | 152 | auto getValue(T : Nullable!U, U...)(sqlite3_value* argv) 153 | { 154 | if (sqlite3_value_type(argv) == SQLITE_NULL) 155 | return T.init; 156 | return T(getValue!(U[0])(argv)); 157 | } 158 | 159 | void setResult(T)(sqlite3_context* context, T value) 160 | if (isIntegral!T || isBoolean!T) 161 | { 162 | import std.conv : to; 163 | sqlite3_result_int64(context, value.to!long); 164 | } 165 | 166 | void setResult(T)(sqlite3_context* context, T value) 167 | if (isFloatingPoint!T) 168 | { 169 | import std.conv : to; 170 | sqlite3_result_double(context, value.to!double); 171 | } 172 | 173 | void setResult(T)(sqlite3_context* context, T value) 174 | if (isSomeString!T) 175 | { 176 | import std.conv : to; 177 | auto val = value.to!string; 178 | sqlite3_result_text64(context, cast(const(char)*) anchorMem(cast(void*) val.ptr), 179 | val.length, &releaseMem, SQLITE_UTF8); 180 | } 181 | 182 | void setResult(T)(sqlite3_context* context, T value) 183 | if (isDynamicArray!T && !isSomeString!T) 184 | { 185 | auto val = cast(void[]) value; 186 | sqlite3_result_blob64(context, anchorMem(val.ptr), val.length, &releaseMem); 187 | } 188 | 189 | void setResult(T)(sqlite3_context* context, T value) 190 | if (isStaticArray!T) 191 | { 192 | auto val = cast(void[]) value; 193 | sqlite3_result_blob64(context, val.ptr, val.sizeof, SQLITE_TRANSIENT); 194 | } 195 | 196 | void setResult(T : Nullable!U, U...)(sqlite3_context* context, T value) 197 | { 198 | if (value.isNull) 199 | sqlite3_result_null(context); 200 | else 201 | setResult(context, value.get); 202 | } 203 | 204 | string nothrowFormat(Args...)(string fmt, Args args) nothrow 205 | { 206 | import std.string : format; 207 | try 208 | return fmt.format(args); 209 | catch (Exception e) 210 | throw new Error("Error: " ~ e.msg); 211 | } 212 | -------------------------------------------------------------------------------- /lib/win32/sqlite3.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | sqlite3_aggregate_context 3 | sqlite3_aggregate_count 4 | sqlite3_auto_extension 5 | sqlite3_backup_finish 6 | sqlite3_backup_init 7 | sqlite3_backup_pagecount 8 | sqlite3_backup_remaining 9 | sqlite3_backup_step 10 | sqlite3_bind_blob 11 | sqlite3_bind_blob64 12 | sqlite3_bind_double 13 | sqlite3_bind_int 14 | sqlite3_bind_int64 15 | sqlite3_bind_null 16 | sqlite3_bind_parameter_count 17 | sqlite3_bind_parameter_index 18 | sqlite3_bind_parameter_name 19 | sqlite3_bind_pointer 20 | sqlite3_bind_text 21 | sqlite3_bind_text16 22 | sqlite3_bind_text64 23 | sqlite3_bind_value 24 | sqlite3_bind_zeroblob 25 | sqlite3_bind_zeroblob64 26 | sqlite3_blob_bytes 27 | sqlite3_blob_close 28 | sqlite3_blob_open 29 | sqlite3_blob_read 30 | sqlite3_blob_reopen 31 | sqlite3_blob_write 32 | sqlite3_busy_handler 33 | sqlite3_busy_timeout 34 | sqlite3_cancel_auto_extension 35 | sqlite3_changes 36 | sqlite3_clear_bindings 37 | sqlite3_close 38 | sqlite3_close_v2 39 | sqlite3_collation_needed 40 | sqlite3_collation_needed16 41 | sqlite3_column_blob 42 | sqlite3_column_bytes 43 | sqlite3_column_bytes16 44 | sqlite3_column_count 45 | sqlite3_column_database_name 46 | sqlite3_column_database_name16 47 | sqlite3_column_decltype 48 | sqlite3_column_decltype16 49 | sqlite3_column_double 50 | sqlite3_column_int 51 | sqlite3_column_int64 52 | sqlite3_column_name 53 | sqlite3_column_name16 54 | sqlite3_column_origin_name 55 | sqlite3_column_origin_name16 56 | sqlite3_column_table_name 57 | sqlite3_column_table_name16 58 | sqlite3_column_text 59 | sqlite3_column_text16 60 | sqlite3_column_type 61 | sqlite3_column_value 62 | sqlite3_commit_hook 63 | sqlite3_compileoption_get 64 | sqlite3_compileoption_used 65 | sqlite3_complete 66 | sqlite3_complete16 67 | sqlite3_config 68 | sqlite3_context_db_handle 69 | sqlite3_create_collation 70 | sqlite3_create_collation16 71 | sqlite3_create_collation_v2 72 | sqlite3_create_function 73 | sqlite3_create_function16 74 | sqlite3_create_function_v2 75 | sqlite3_create_module 76 | sqlite3_create_module_v2 77 | sqlite3_create_window_function 78 | sqlite3_data_count 79 | sqlite3_db_cacheflush 80 | sqlite3_db_config 81 | sqlite3_db_filename 82 | sqlite3_db_handle 83 | sqlite3_db_mutex 84 | sqlite3_db_readonly 85 | sqlite3_db_release_memory 86 | sqlite3_db_status 87 | sqlite3_declare_vtab 88 | sqlite3_enable_load_extension 89 | sqlite3_enable_shared_cache 90 | sqlite3_errcode 91 | sqlite3_errmsg 92 | sqlite3_errmsg16 93 | sqlite3_errstr 94 | sqlite3_exec 95 | sqlite3_expanded_sql 96 | sqlite3_expired 97 | sqlite3_extended_errcode 98 | sqlite3_extended_result_codes 99 | sqlite3_file_control 100 | sqlite3_finalize 101 | sqlite3_free 102 | sqlite3_free_table 103 | sqlite3_get_autocommit 104 | sqlite3_get_auxdata 105 | sqlite3_get_table 106 | sqlite3_global_recover 107 | sqlite3_initialize 108 | sqlite3_interrupt 109 | sqlite3_keyword_check 110 | sqlite3_keyword_count 111 | sqlite3_keyword_name 112 | sqlite3_last_insert_rowid 113 | sqlite3_libversion 114 | sqlite3_libversion_number 115 | sqlite3_limit 116 | sqlite3_load_extension 117 | sqlite3_log 118 | sqlite3_malloc 119 | sqlite3_malloc64 120 | sqlite3_memory_alarm 121 | sqlite3_memory_highwater 122 | sqlite3_memory_used 123 | sqlite3_mprintf 124 | sqlite3_msize 125 | sqlite3_mutex_alloc 126 | sqlite3_mutex_enter 127 | sqlite3_mutex_free 128 | sqlite3_mutex_leave 129 | sqlite3_mutex_try 130 | sqlite3_next_stmt 131 | sqlite3_open 132 | sqlite3_open16 133 | sqlite3_open_v2 134 | sqlite3_os_end 135 | sqlite3_os_init 136 | sqlite3_overload_function 137 | sqlite3_prepare 138 | sqlite3_prepare16 139 | sqlite3_prepare16_v2 140 | sqlite3_prepare16_v3 141 | sqlite3_prepare_v2 142 | sqlite3_prepare_v3 143 | sqlite3_profile 144 | sqlite3_progress_handler 145 | sqlite3_randomness 146 | sqlite3_realloc 147 | sqlite3_realloc64 148 | sqlite3_release_memory 149 | sqlite3_reset 150 | sqlite3_reset_auto_extension 151 | sqlite3_result_blob 152 | sqlite3_result_blob64 153 | sqlite3_result_double 154 | sqlite3_result_error 155 | sqlite3_result_error16 156 | sqlite3_result_error_code 157 | sqlite3_result_error_nomem 158 | sqlite3_result_error_toobig 159 | sqlite3_result_int 160 | sqlite3_result_int64 161 | sqlite3_result_null 162 | sqlite3_result_pointer 163 | sqlite3_result_subtype 164 | sqlite3_result_text 165 | sqlite3_result_text16 166 | sqlite3_result_text16be 167 | sqlite3_result_text16le 168 | sqlite3_result_text64 169 | sqlite3_result_value 170 | sqlite3_result_zeroblob 171 | sqlite3_result_zeroblob64 172 | sqlite3_rollback_hook 173 | sqlite3_rtree_geometry_callback 174 | sqlite3_rtree_query_callback 175 | sqlite3_set_authorizer 176 | sqlite3_set_auxdata 177 | sqlite3_set_last_insert_rowid 178 | sqlite3_shutdown 179 | sqlite3_sleep 180 | sqlite3_snprintf 181 | sqlite3_soft_heap_limit 182 | sqlite3_soft_heap_limit64 183 | sqlite3_sourceid 184 | sqlite3_sql 185 | sqlite3_status 186 | sqlite3_status64 187 | sqlite3_step 188 | sqlite3_stmt_busy 189 | sqlite3_stmt_readonly 190 | sqlite3_stmt_status 191 | sqlite3_str_append 192 | sqlite3_str_appendall 193 | sqlite3_str_appendchar 194 | sqlite3_str_appendf 195 | sqlite3_str_errcode 196 | sqlite3_str_finish 197 | sqlite3_strglob 198 | sqlite3_stricmp 199 | sqlite3_str_length 200 | sqlite3_strlike 201 | sqlite3_str_new 202 | sqlite3_strnicmp 203 | sqlite3_str_reset 204 | sqlite3_str_value 205 | sqlite3_str_vappendf 206 | sqlite3_system_errno 207 | sqlite3_table_column_metadata 208 | sqlite3_test_control 209 | sqlite3_thread_cleanup 210 | sqlite3_threadsafe 211 | sqlite3_total_changes 212 | sqlite3_trace 213 | sqlite3_trace_v2 214 | sqlite3_transfer_bindings 215 | sqlite3_update_hook 216 | sqlite3_uri_boolean 217 | sqlite3_uri_int64 218 | sqlite3_uri_parameter 219 | sqlite3_user_data 220 | sqlite3_value_blob 221 | sqlite3_value_bytes 222 | sqlite3_value_bytes16 223 | sqlite3_value_double 224 | sqlite3_value_dup 225 | sqlite3_value_free 226 | sqlite3_value_int 227 | sqlite3_value_int64 228 | sqlite3_value_nochange 229 | sqlite3_value_numeric_type 230 | sqlite3_value_pointer 231 | sqlite3_value_subtype 232 | sqlite3_value_text 233 | sqlite3_value_text16 234 | sqlite3_value_text16be 235 | sqlite3_value_text16le 236 | sqlite3_value_type 237 | sqlite3_vfs_find 238 | sqlite3_vfs_register 239 | sqlite3_vfs_unregister 240 | sqlite3_vmprintf 241 | sqlite3_vsnprintf 242 | sqlite3_vtab_collation 243 | sqlite3_vtab_config 244 | sqlite3_vtab_nochange 245 | sqlite3_vtab_on_conflict 246 | sqlite3_wal_autocheckpoint 247 | sqlite3_wal_checkpoint 248 | sqlite3_wal_checkpoint_v2 249 | sqlite3_wal_hook 250 | sqlite3_win32_is_nt 251 | sqlite3_win32_mbcs_to_utf8 252 | sqlite3_win32_mbcs_to_utf8_v2 253 | sqlite3_win32_set_directory 254 | sqlite3_win32_set_directory16 255 | sqlite3_win32_set_directory8 256 | sqlite3_win32_sleep 257 | sqlite3_win32_unicode_to_utf8 258 | sqlite3_win32_utf8_to_mbcs 259 | sqlite3_win32_utf8_to_mbcs_v2 260 | sqlite3_win32_utf8_to_unicode 261 | sqlite3_win32_write_debug 262 | -------------------------------------------------------------------------------- /lib/win64/sqlite3.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | sqlite3_aggregate_context 3 | sqlite3_aggregate_count 4 | sqlite3_auto_extension 5 | sqlite3_backup_finish 6 | sqlite3_backup_init 7 | sqlite3_backup_pagecount 8 | sqlite3_backup_remaining 9 | sqlite3_backup_step 10 | sqlite3_bind_blob 11 | sqlite3_bind_blob64 12 | sqlite3_bind_double 13 | sqlite3_bind_int 14 | sqlite3_bind_int64 15 | sqlite3_bind_null 16 | sqlite3_bind_parameter_count 17 | sqlite3_bind_parameter_index 18 | sqlite3_bind_parameter_name 19 | sqlite3_bind_pointer 20 | sqlite3_bind_text 21 | sqlite3_bind_text16 22 | sqlite3_bind_text64 23 | sqlite3_bind_value 24 | sqlite3_bind_zeroblob 25 | sqlite3_bind_zeroblob64 26 | sqlite3_blob_bytes 27 | sqlite3_blob_close 28 | sqlite3_blob_open 29 | sqlite3_blob_read 30 | sqlite3_blob_reopen 31 | sqlite3_blob_write 32 | sqlite3_busy_handler 33 | sqlite3_busy_timeout 34 | sqlite3_cancel_auto_extension 35 | sqlite3_changes 36 | sqlite3_clear_bindings 37 | sqlite3_close 38 | sqlite3_close_v2 39 | sqlite3_collation_needed 40 | sqlite3_collation_needed16 41 | sqlite3_column_blob 42 | sqlite3_column_bytes 43 | sqlite3_column_bytes16 44 | sqlite3_column_count 45 | sqlite3_column_database_name 46 | sqlite3_column_database_name16 47 | sqlite3_column_decltype 48 | sqlite3_column_decltype16 49 | sqlite3_column_double 50 | sqlite3_column_int 51 | sqlite3_column_int64 52 | sqlite3_column_name 53 | sqlite3_column_name16 54 | sqlite3_column_origin_name 55 | sqlite3_column_origin_name16 56 | sqlite3_column_table_name 57 | sqlite3_column_table_name16 58 | sqlite3_column_text 59 | sqlite3_column_text16 60 | sqlite3_column_type 61 | sqlite3_column_value 62 | sqlite3_commit_hook 63 | sqlite3_compileoption_get 64 | sqlite3_compileoption_used 65 | sqlite3_complete 66 | sqlite3_complete16 67 | sqlite3_config 68 | sqlite3_context_db_handle 69 | sqlite3_create_collation 70 | sqlite3_create_collation_v2 71 | sqlite3_create_collation16 72 | sqlite3_create_function 73 | sqlite3_create_function_v2 74 | sqlite3_create_function16 75 | sqlite3_create_module 76 | sqlite3_create_module_v2 77 | sqlite3_create_window_function 78 | sqlite3_data_count 79 | sqlite3_data_directory 80 | sqlite3_db_cacheflush 81 | sqlite3_db_config 82 | sqlite3_db_filename 83 | sqlite3_db_handle 84 | sqlite3_db_mutex 85 | sqlite3_db_readonly 86 | sqlite3_db_release_memory 87 | sqlite3_db_status 88 | sqlite3_declare_vtab 89 | sqlite3_enable_load_extension 90 | sqlite3_enable_shared_cache 91 | sqlite3_errcode 92 | sqlite3_errmsg 93 | sqlite3_errmsg16 94 | sqlite3_errstr 95 | sqlite3_exec 96 | sqlite3_expanded_sql 97 | sqlite3_expired 98 | sqlite3_extended_errcode 99 | sqlite3_extended_result_codes 100 | sqlite3_file_control 101 | sqlite3_finalize 102 | sqlite3_free 103 | sqlite3_free_table 104 | sqlite3_fts5_may_be_corrupt 105 | sqlite3_get_autocommit 106 | sqlite3_get_auxdata 107 | sqlite3_get_table 108 | sqlite3_global_recover 109 | sqlite3_initialize 110 | sqlite3_interrupt 111 | sqlite3_keyword_check 112 | sqlite3_keyword_count 113 | sqlite3_keyword_name 114 | sqlite3_last_insert_rowid 115 | sqlite3_libversion 116 | sqlite3_libversion_number 117 | sqlite3_limit 118 | sqlite3_load_extension 119 | sqlite3_log 120 | sqlite3_malloc 121 | sqlite3_malloc64 122 | sqlite3_memory_alarm 123 | sqlite3_memory_highwater 124 | sqlite3_memory_used 125 | sqlite3_mprintf 126 | sqlite3_msize 127 | sqlite3_mutex_alloc 128 | sqlite3_mutex_enter 129 | sqlite3_mutex_free 130 | sqlite3_mutex_leave 131 | sqlite3_mutex_try 132 | sqlite3_next_stmt 133 | sqlite3_open 134 | sqlite3_open_v2 135 | sqlite3_open16 136 | sqlite3_os_end 137 | sqlite3_os_init 138 | sqlite3_overload_function 139 | sqlite3_prepare 140 | sqlite3_prepare_v2 141 | sqlite3_prepare_v3 142 | sqlite3_prepare16 143 | sqlite3_prepare16_v2 144 | sqlite3_prepare16_v3 145 | sqlite3_profile 146 | sqlite3_progress_handler 147 | sqlite3_randomness 148 | sqlite3_realloc 149 | sqlite3_realloc64 150 | sqlite3_release_memory 151 | sqlite3_reset 152 | sqlite3_reset_auto_extension 153 | sqlite3_result_blob 154 | sqlite3_result_blob64 155 | sqlite3_result_double 156 | sqlite3_result_error 157 | sqlite3_result_error_code 158 | sqlite3_result_error_nomem 159 | sqlite3_result_error_toobig 160 | sqlite3_result_error16 161 | sqlite3_result_int 162 | sqlite3_result_int64 163 | sqlite3_result_null 164 | sqlite3_result_pointer 165 | sqlite3_result_subtype 166 | sqlite3_result_text 167 | sqlite3_result_text16 168 | sqlite3_result_text16be 169 | sqlite3_result_text16le 170 | sqlite3_result_text64 171 | sqlite3_result_value 172 | sqlite3_result_zeroblob 173 | sqlite3_result_zeroblob64 174 | sqlite3_rollback_hook 175 | sqlite3_rtree_geometry_callback 176 | sqlite3_rtree_query_callback 177 | sqlite3_set_authorizer 178 | sqlite3_set_auxdata 179 | sqlite3_set_last_insert_rowid 180 | sqlite3_shutdown 181 | sqlite3_sleep 182 | sqlite3_snprintf 183 | sqlite3_soft_heap_limit 184 | sqlite3_soft_heap_limit64 185 | sqlite3_sourceid 186 | sqlite3_sql 187 | sqlite3_status 188 | sqlite3_status64 189 | sqlite3_step 190 | sqlite3_stmt_busy 191 | sqlite3_stmt_readonly 192 | sqlite3_stmt_status 193 | sqlite3_str_append 194 | sqlite3_str_appendall 195 | sqlite3_str_appendchar 196 | sqlite3_str_appendf 197 | sqlite3_str_errcode 198 | sqlite3_str_finish 199 | sqlite3_str_length 200 | sqlite3_str_new 201 | sqlite3_str_reset 202 | sqlite3_str_value 203 | sqlite3_str_vappendf 204 | sqlite3_strglob 205 | sqlite3_stricmp 206 | sqlite3_strlike 207 | sqlite3_strnicmp 208 | sqlite3_system_errno 209 | sqlite3_table_column_metadata 210 | sqlite3_temp_directory 211 | sqlite3_test_control 212 | sqlite3_thread_cleanup 213 | sqlite3_threadsafe 214 | sqlite3_total_changes 215 | sqlite3_trace 216 | sqlite3_trace_v2 217 | sqlite3_transfer_bindings 218 | sqlite3_update_hook 219 | sqlite3_uri_boolean 220 | sqlite3_uri_int64 221 | sqlite3_uri_parameter 222 | sqlite3_user_data 223 | sqlite3_value_blob 224 | sqlite3_value_bytes 225 | sqlite3_value_bytes16 226 | sqlite3_value_double 227 | sqlite3_value_dup 228 | sqlite3_value_free 229 | sqlite3_value_int 230 | sqlite3_value_int64 231 | sqlite3_value_nochange 232 | sqlite3_value_numeric_type 233 | sqlite3_value_pointer 234 | sqlite3_value_subtype 235 | sqlite3_value_text 236 | sqlite3_value_text16 237 | sqlite3_value_text16be 238 | sqlite3_value_text16le 239 | sqlite3_value_type 240 | sqlite3_version 241 | sqlite3_vfs_find 242 | sqlite3_vfs_register 243 | sqlite3_vfs_unregister 244 | sqlite3_vmprintf 245 | sqlite3_vsnprintf 246 | sqlite3_vtab_collation 247 | sqlite3_vtab_config 248 | sqlite3_vtab_nochange 249 | sqlite3_vtab_on_conflict 250 | sqlite3_wal_autocheckpoint 251 | sqlite3_wal_checkpoint 252 | sqlite3_wal_checkpoint_v2 253 | sqlite3_wal_hook 254 | sqlite3_win32_is_nt 255 | sqlite3_win32_mbcs_to_utf8 256 | sqlite3_win32_mbcs_to_utf8_v2 257 | sqlite3_win32_set_directory 258 | sqlite3_win32_set_directory16 259 | sqlite3_win32_set_directory8 260 | sqlite3_win32_sleep 261 | sqlite3_win32_unicode_to_utf8 262 | sqlite3_win32_utf8_to_mbcs 263 | sqlite3_win32_utf8_to_mbcs_v2 264 | sqlite3_win32_utf8_to_unicode 265 | sqlite3_win32_write_debug 266 | -------------------------------------------------------------------------------- /source/d2sqlite3/statement.d: -------------------------------------------------------------------------------- 1 | /++ 2 | Managing prepared statements. 3 | 4 | Authors: 5 | Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) 6 | 7 | Copyright: 8 | Copyright 2011-18 Nicolas Sicard. 9 | 10 | License: 11 | $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 | +/ 13 | module d2sqlite3.statement; 14 | 15 | import d2sqlite3.database; 16 | import d2sqlite3.results; 17 | import d2sqlite3.sqlite3; 18 | import d2sqlite3.internal.memory; 19 | import d2sqlite3.internal.util; 20 | 21 | import std.conv : to; 22 | import std.exception : enforce; 23 | import std.string : format, toStringz; 24 | import std.typecons : Nullable; 25 | 26 | /// Set _UnlockNotify version if compiled with SqliteEnableUnlockNotify or SqliteFakeUnlockNotify 27 | version (SqliteEnableUnlockNotify) version = _UnlockNotify; 28 | else version (SqliteFakeUnlockNotify) version = _UnlockNotify; 29 | 30 | /++ 31 | A prepared statement. 32 | 33 | This struct is a reference-counted wrapper around a `sqlite3_stmt*` pointer. 34 | Instances of this struct are typically returned by `Database.prepare()`. 35 | +/ 36 | struct Statement 37 | { 38 | import std.meta : allSatisfy; 39 | import std.traits : isIntegral, isSomeChar, isBoolean, isFloatingPoint, 40 | isSomeString, isStaticArray, isDynamicArray, isIterable; 41 | import std.typecons : RefCounted, RefCountedAutoInitialize; 42 | 43 | private: 44 | 45 | /// Returns $(D true) if the value can be directly bound to the statement 46 | enum bool isBindable(T) = 47 | is(T == typeof(null)) || is(T == void*) || isIntegral!T || isSomeChar!T 48 | || isBoolean!T || isFloatingPoint!T || isSomeString!T || isStaticArray!T 49 | || isDynamicArray!T || is(T == Nullable!U, U...); 50 | 51 | struct Payload 52 | { 53 | Database db; 54 | sqlite3_stmt* handle; // null if error or empty statement 55 | int paramCount; 56 | debug string sql; 57 | 58 | ~this() nothrow 59 | { 60 | debug ensureNotInGC!Statement(sql); 61 | sqlite3_finalize(handle); 62 | } 63 | } 64 | 65 | RefCounted!(Payload, RefCountedAutoInitialize.no) p; 66 | 67 | void checkResult(int result) 68 | { 69 | enforce(result == SQLITE_OK, new SqliteException(errmsg(p.handle), result)); 70 | } 71 | 72 | version (_UnlockNotify) 73 | { 74 | auto sqlite3_blocking_prepare_v2(Database db, const char *zSql, int nByte, 75 | sqlite3_stmt **ppStmt, const char **pzTail) 76 | { 77 | int rc; 78 | while(SQLITE_LOCKED == (rc = sqlite3_prepare_v2(db.handle(), zSql, nByte, ppStmt, pzTail))) 79 | { 80 | rc = db.waitForUnlockNotify(); 81 | if(rc != SQLITE_OK) break; 82 | } 83 | return rc; 84 | } 85 | } 86 | 87 | package(d2sqlite3): 88 | this(Database db, string sql) 89 | { 90 | sqlite3_stmt* handle; 91 | enforce(sql.length <= int.max, "Length of SQL statement exceeds `int.max`"); 92 | version (_UnlockNotify) 93 | { 94 | auto result = sqlite3_blocking_prepare_v2(db, sql.ptr, cast(int) sql.length, 95 | &handle, null); 96 | } 97 | else 98 | { 99 | auto result = sqlite3_prepare_v2(db.handle(), sql.ptr, cast(int) sql.length, 100 | &handle, null); 101 | } 102 | enforce(result == SQLITE_OK, new SqliteException(errmsg(db.handle()), result, sql)); 103 | p = Payload(db, handle); 104 | p.paramCount = sqlite3_bind_parameter_count(p.handle); 105 | debug p.sql = sql; 106 | } 107 | 108 | version (_UnlockNotify) 109 | { 110 | /// Setup and waits for unlock notify using the provided `IUnlockNotifyHandler` 111 | auto waitForUnlockNotify() 112 | { 113 | return p.db.waitForUnlockNotify(); 114 | } 115 | } 116 | 117 | public: 118 | /++ 119 | Gets the SQLite internal _handle of the statement. 120 | +/ 121 | inout(sqlite3_stmt)* handle() inout @safe pure nothrow @nogc 122 | { 123 | return p.handle; 124 | } 125 | 126 | /++ 127 | Explicitly finalizes the prepared statement. 128 | 129 | After a call to `finalize()`, the `Statement` object is destroyed and cannot be used. 130 | +/ 131 | void finalize() 132 | { 133 | destroy(p); 134 | } 135 | 136 | /++ 137 | Tells whether the statement is empty (no SQL statement). 138 | +/ 139 | bool empty() const @safe pure nothrow @nogc 140 | { 141 | return p.handle is null; 142 | } 143 | /// 144 | unittest 145 | { 146 | auto db = Database(":memory:"); 147 | auto statement = db.prepare(" ; "); 148 | assert(statement.empty); 149 | } 150 | 151 | /++ 152 | Binds values to parameters of this statement, using parameter index. 153 | 154 | Params: 155 | index = The index of the parameter (starting from 1). 156 | 157 | value = The bound _value. The type of value must be compatible with the SQLite 158 | types: it must be a boolean or numeric type, a string, an array, null, 159 | or a Nullable!T where T is any of the previous types. 160 | +/ 161 | void bind(T)(int index, T value) 162 | in 163 | { 164 | assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 165 | } 166 | do 167 | { 168 | assert(p.handle); 169 | 170 | static if (is(T == typeof(null)) || is(T == void*)) 171 | checkResult(sqlite3_bind_null(p.handle, index)); 172 | 173 | // Handle nullable before user-provided hook as we don't want to write 174 | // `Nullable.null` when the value `isNull`. 175 | else static if (is(T == Nullable!U, U...)) 176 | { 177 | if (value.isNull) 178 | checkResult(sqlite3_bind_null(p.handle, index)); 179 | else 180 | this.bind(index, value.get); 181 | } 182 | 183 | // Check for user-defined hook 184 | else static if (is(typeof(value.toString((in char[]) {})))) 185 | { 186 | string str = format("%s", value); 187 | auto ptr = anchorMem(cast(void*) str.ptr); 188 | checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr, 189 | str.length, &releaseMem, SQLITE_UTF8)); 190 | } 191 | else static if (is(typeof(value.toString()) : string)) 192 | { 193 | string str = value.toString(); 194 | auto ptr = anchorMem(cast(void*) str.ptr); 195 | checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr, 196 | str.length, &releaseMem, SQLITE_UTF8)); 197 | } 198 | 199 | else static if (isIntegral!T || isSomeChar!T || isBoolean!T) 200 | checkResult(sqlite3_bind_int64(p.handle, index, value.to!long)); 201 | else static if (isFloatingPoint!T) 202 | checkResult(sqlite3_bind_double(p.handle, index, value.to!double)); 203 | else static if (isSomeString!T) 204 | { 205 | string str = value.to!string; 206 | auto ptr = anchorMem(cast(void*) str.ptr); 207 | checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr, 208 | str.length, &releaseMem, SQLITE_UTF8)); 209 | } 210 | else static if (isStaticArray!T) 211 | checkResult(sqlite3_bind_blob64(p.handle, index, cast(void*) value.ptr, 212 | value.sizeof, SQLITE_TRANSIENT)); 213 | else static if (isDynamicArray!T) 214 | { 215 | const void[] arr = value; 216 | checkResult(sqlite3_bind_blob64(p.handle, index, anchorMem(arr.ptr), 217 | arr.length, &releaseMem)); 218 | } 219 | else 220 | static assert(0, "Don't know how to bind an instance of type: " ~ T.stringof); 221 | } 222 | 223 | /++ 224 | Binds values to parameters of this statement, using parameter names. 225 | 226 | Params: 227 | name = The name of the parameter, including the ':', '@' or '$' that introduced it. 228 | 229 | value = The bound _value. The type of value must be compatible with the SQLite 230 | types: it must be a boolean or numeric type, a string, an array, null, 231 | or a Nullable!T where T is any of the previous types. 232 | 233 | Warning: 234 | While convenient, this overload of `bind` is less performant, because it has to 235 | retrieve the column index with a call to the SQLite function 236 | `sqlite3_bind_parameter_index`. 237 | +/ 238 | void bind(T)(string name, T value) 239 | in 240 | { 241 | assert(name.length); 242 | } 243 | do 244 | { 245 | assert(p.handle); 246 | auto index = sqlite3_bind_parameter_index(p.handle, name.toStringz); 247 | assert(index > 0, "no parameter named '%s'".format(name)); 248 | bind(index, value); 249 | } 250 | 251 | /++ 252 | Binds all the arguments at once in order. 253 | +/ 254 | void bindAll(Args...)(Args args) 255 | in 256 | { 257 | assert(Args.length == this.parameterCount, "parameter count mismatch"); 258 | } 259 | do 260 | { 261 | foreach (index, _; Args) 262 | bind(index + 1, args[index]); 263 | } 264 | 265 | /++ 266 | Clears the bindings. 267 | 268 | This does not reset the statement. Use `Statement.reset()` for this. 269 | +/ 270 | void clearBindings() 271 | { 272 | assert(p.handle); 273 | checkResult(sqlite3_clear_bindings(p.handle)); 274 | } 275 | 276 | /++ 277 | Executes the statement and return a (possibly empty) range of results. 278 | +/ 279 | ResultRange execute() 280 | { 281 | return ResultRange(this); 282 | } 283 | 284 | /++ 285 | Resets a this statement before a new execution. 286 | 287 | Calling this method invalidates any `ResultRange` struct returned by a previous call 288 | to `Database.execute()` or `Statement.execute()`. 289 | 290 | This does not clear the bindings. Use `Statement.clearBindings()` for this. 291 | +/ 292 | void reset() 293 | { 294 | assert(p.handle); 295 | checkResult(sqlite3_reset(p.handle)); 296 | } 297 | 298 | /++ 299 | Binds arguments, executes and resets the statement, in one call. 300 | 301 | This convenience function is equivalent to: 302 | --- 303 | bindAll(args); 304 | execute(); 305 | reset(); 306 | --- 307 | +/ 308 | void inject(Args...)(Args args) 309 | if (allSatisfy!(isBindable, Args)) 310 | { 311 | bindAll(args); 312 | execute(); 313 | reset(); 314 | } 315 | 316 | /++ 317 | Binds the fields of a struct in order, executes and resets the statement, in one call. 318 | +/ 319 | void inject(T)(auto ref const T obj) 320 | if (is(T == struct)) 321 | { 322 | import std.meta : Filter; 323 | import std.traits : FieldNameTuple; 324 | 325 | enum accesible(string F) = __traits(compiles, __traits(getMember, obj, F)); 326 | enum bindable(string F) = isBindable!(typeof(__traits(getMember, obj, F))); 327 | 328 | alias FieldNames = Filter!(bindable, Filter!(accesible, FieldNameTuple!T)); 329 | assert(FieldNames.length == this.parameterCount, "parameter count mismatch"); 330 | foreach (i, field; FieldNames) 331 | bind(i + 1, __traits(getMember, obj, field)); 332 | execute(); 333 | reset(); 334 | } 335 | 336 | /++ 337 | Binds iterable values in order, executes and resets the statement, in one call. 338 | +/ 339 | void inject(T)(auto ref T obj) 340 | if (!isBindable!T && isIterable!T) 341 | in 342 | { 343 | static if (__traits(compiles, obj.length)) 344 | assert(obj.length == this.parameterCount, "parameter count mismatch"); 345 | } 346 | do 347 | { 348 | static if (__traits(compiles, { foreach (string k, ref v; obj) {} })) 349 | { 350 | foreach (string k, ref v; obj) bind(k, v); 351 | } 352 | else 353 | { 354 | int i = 1; 355 | foreach (ref v; obj) bind(i++, v); 356 | } 357 | execute(); 358 | reset(); 359 | } 360 | 361 | /// Gets the count of bind parameters. 362 | int parameterCount() nothrow 363 | { 364 | assert(p.handle); 365 | return p.paramCount; 366 | } 367 | 368 | /++ 369 | Gets the name of the bind parameter at the given index. 370 | 371 | Params: 372 | index = The index of the parameter (the first parameter has the index 1). 373 | 374 | Returns: The name of the parameter or null is not found or out of range. 375 | +/ 376 | string parameterName(int index) 377 | in 378 | { 379 | assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 380 | } 381 | do 382 | { 383 | assert(p.handle); 384 | return sqlite3_bind_parameter_name(p.handle, index).to!string; 385 | } 386 | 387 | /++ 388 | Gets the index of a bind parameter. 389 | 390 | Returns: The index of the parameter (the first parameter has the index 1) 391 | or 0 is not found or out of range. 392 | +/ 393 | int parameterIndex(string name) 394 | in 395 | { 396 | assert(name.length); 397 | } 398 | do 399 | { 400 | assert(p.handle); 401 | return sqlite3_bind_parameter_index(p.handle, name.toStringz); 402 | } 403 | } 404 | 405 | /++ 406 | Turns $(D_PARAM value) into a _literal that can be used in an SQLite expression. 407 | +/ 408 | string literal(T)(T value) 409 | { 410 | import std.string : replace; 411 | import std.traits : isBoolean, isNumeric, isSomeString, isArray; 412 | 413 | static if (is(T == typeof(null))) 414 | return "NULL"; 415 | else static if (isBoolean!T) 416 | return value ? "1" : "0"; 417 | else static if (isNumeric!T) 418 | return value.to!string(); 419 | else static if (isSomeString!T) 420 | return format("'%s'", value.replace("'", "''")); 421 | else static if (isArray!T) 422 | return "'X%(%X%)'".format(cast(Blob) value); 423 | else 424 | static assert(false, "cannot make a literal of a value of type " ~ T.stringof); 425 | } 426 | /// 427 | unittest 428 | { 429 | assert(null.literal == "NULL"); 430 | assert(false.literal == "0"); 431 | assert(true.literal == "1"); 432 | assert(4.literal == "4"); 433 | assert(4.1.literal == "4.1"); 434 | assert("foo".literal == "'foo'"); 435 | assert("a'b'".literal == "'a''b'''"); 436 | import std.conv : hexString; 437 | auto a = cast(Blob) hexString!"DEADBEEF"; 438 | assert(a.literal == "'XDEADBEEF'"); 439 | } 440 | -------------------------------------------------------------------------------- /source/d2sqlite3/results.d: -------------------------------------------------------------------------------- 1 | /++ 2 | Managing query results. 3 | 4 | Authors: 5 | Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) 6 | 7 | Copyright: 8 | Copyright 2011-18 Nicolas Sicard. 9 | 10 | License: 11 | $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 | +/ 13 | module d2sqlite3.results; 14 | 15 | import d2sqlite3.database; 16 | import d2sqlite3.statement; 17 | import d2sqlite3.sqlite3; 18 | import d2sqlite3.internal.util; 19 | 20 | import std.conv : to; 21 | import std.exception : enforce; 22 | import std.string : format; 23 | import std.typecons : Nullable; 24 | 25 | /// Set _UnlockNotify version if compiled with SqliteEnableUnlockNotify or SqliteFakeUnlockNotify 26 | version (SqliteEnableUnlockNotify) version = _UnlockNotify; 27 | else version (SqliteFakeUnlockNotify) version = _UnlockNotify; 28 | 29 | /++ 30 | An input range interface to access the rows resulting from an SQL query. 31 | 32 | The elements of the range are `Row` structs. A `Row` is just a view of the current 33 | row when iterating the results of a `ResultRange`. It becomes invalid as soon as 34 | `ResultRange.popFront()` is called (it contains undefined data afterwards). Use 35 | `cached` to store the content of rows past the execution of the statement. 36 | 37 | Instances of this struct are typically returned by `Database.execute()` or 38 | `Statement.execute()`. 39 | +/ 40 | struct ResultRange 41 | { 42 | private: 43 | Statement statement; 44 | int state = SQLITE_DONE; 45 | int colCount = 0; 46 | Row current; 47 | 48 | package(d2sqlite3): 49 | this(Statement statement) 50 | { 51 | if (!statement.empty) 52 | { 53 | version (_UnlockNotify) state = sqlite3_blocking_step(statement); 54 | else state = sqlite3_step(statement.handle); 55 | } 56 | else 57 | state = SQLITE_DONE; 58 | 59 | enforce(state == SQLITE_ROW || state == SQLITE_DONE, 60 | new SqliteException(errmsg(statement.handle), state)); 61 | 62 | this.statement = statement; 63 | colCount = sqlite3_column_count(statement.handle); 64 | current = Row(statement, colCount); 65 | } 66 | 67 | version (_UnlockNotify) 68 | { 69 | auto sqlite3_blocking_step(Statement statement) 70 | { 71 | int rc; 72 | while(SQLITE_LOCKED == (rc = sqlite3_step(statement.handle))) 73 | { 74 | rc = statement.waitForUnlockNotify(); 75 | if(rc != SQLITE_OK) break; 76 | sqlite3_reset(statement.handle); 77 | } 78 | return rc; 79 | } 80 | } 81 | 82 | public: 83 | /++ 84 | Range interface. 85 | +/ 86 | bool empty() @property 87 | { 88 | return state == SQLITE_DONE; 89 | } 90 | 91 | /// ditto 92 | ref Row front() return @property 93 | { 94 | assert(!empty, "no rows available"); 95 | return current; 96 | } 97 | 98 | /// ditto 99 | void popFront() 100 | { 101 | assert(!empty, "no rows available"); 102 | version (_UnlockNotify) state = sqlite3_blocking_step(statement); 103 | else state = sqlite3_step(statement.handle); 104 | current = Row(statement, colCount); 105 | enforce(state == SQLITE_DONE || state == SQLITE_ROW, 106 | new SqliteException(errmsg(statement.handle), state)); 107 | } 108 | 109 | /++ 110 | Gets only the first value of the first row returned by the execution of the statement. 111 | +/ 112 | auto oneValue(T)() 113 | { 114 | return front.peek!T(0); 115 | } 116 | /// 117 | unittest 118 | { 119 | auto db = Database(":memory:"); 120 | db.execute("CREATE TABLE test (val INTEGER)"); 121 | auto count = db.execute("SELECT count(*) FROM test").oneValue!long; 122 | assert(count == 0); 123 | } 124 | } 125 | /// 126 | unittest 127 | { 128 | auto db = Database(":memory:"); 129 | db.run("CREATE TABLE test (i INTEGER); 130 | INSERT INTO test VALUES (1); 131 | INSERT INTO test VALUES (2);"); 132 | 133 | auto results = db.execute("SELECT * FROM test"); 134 | assert(!results.empty); 135 | assert(results.front.peek!long(0) == 1); 136 | results.popFront(); 137 | assert(!results.empty); 138 | assert(results.front.peek!long(0) == 2); 139 | results.popFront(); 140 | assert(results.empty); 141 | } 142 | 143 | /++ 144 | A row returned when stepping over an SQLite prepared statement. 145 | 146 | The data of each column can be retrieved: 147 | $(UL 148 | $(LI using Row as a random-access range of ColumnData.) 149 | $(LI using the more direct peek functions.) 150 | ) 151 | 152 | Warning: 153 | The data of the row is invalid when the next row is accessed (after a call to 154 | `ResultRange.popFront()`). 155 | +/ 156 | struct Row 157 | { 158 | import std.traits : isBoolean, isIntegral, isSomeChar, isFloatingPoint, isSomeString, isArray; 159 | import std.traits : isInstanceOf, TemplateArgsOf; 160 | 161 | private: 162 | Statement statement; 163 | int frontIndex = 0; 164 | int backIndex = -1; 165 | 166 | this(Statement statement, int colCount) nothrow 167 | { 168 | this.statement = statement; 169 | backIndex = colCount - 1; 170 | } 171 | 172 | public: 173 | /// Range interface. 174 | bool empty() const @property nothrow 175 | { 176 | return length == 0; 177 | } 178 | 179 | /// ditto 180 | ColumnData front() @property 181 | { 182 | assertInitialized(); 183 | return opIndex(0); 184 | } 185 | 186 | /// ditto 187 | void popFront() nothrow 188 | { 189 | assertInitialized(); 190 | frontIndex++; 191 | } 192 | 193 | /// ditto 194 | Row save() @property 195 | { 196 | return this; 197 | } 198 | 199 | /// ditto 200 | ColumnData back() @property 201 | { 202 | assertInitialized(); 203 | return opIndex(backIndex - frontIndex); 204 | } 205 | 206 | /// ditto 207 | void popBack() nothrow 208 | { 209 | assertInitialized(); 210 | backIndex--; 211 | } 212 | 213 | /// ditto 214 | size_t length() const @property nothrow 215 | { 216 | return backIndex - frontIndex + 1; 217 | } 218 | 219 | /// ditto 220 | ColumnData opIndex(size_t index) 221 | { 222 | assertInitialized(); 223 | auto i = internalIndex(index); 224 | auto type = sqlite3_column_type(statement.handle, i); 225 | final switch (type) 226 | { 227 | case SqliteType.INTEGER: 228 | return ColumnData(peek!long(index)); 229 | 230 | case SqliteType.FLOAT: 231 | return ColumnData(peek!double(index)); 232 | 233 | case SqliteType.TEXT: 234 | return ColumnData(peek!string(index)); 235 | 236 | case SqliteType.BLOB: 237 | return ColumnData(peek!(Blob, PeekMode.copy)(index)); 238 | 239 | case SqliteType.NULL: 240 | return ColumnData(null); 241 | } 242 | } 243 | 244 | /// Ditto 245 | ColumnData opIndex(string columnName) 246 | { 247 | return opIndex(indexForName(columnName)); 248 | } 249 | 250 | /++ 251 | Returns the data of a column directly. 252 | 253 | Contrary to `opIndex`, the `peek` functions return the data directly, automatically cast to T, 254 | without the overhead of using a wrapping type (`ColumnData`). 255 | 256 | When using `peek` to retrieve an array or a string, you can use either: 257 | $(UL 258 | $(LI `peek!(..., PeekMode.copy)(index)`, 259 | in which case the function returns a copy of the data that will outlive the step 260 | to the next row, 261 | or) 262 | $(LI `peek!(..., PeekMode.slice)(index)`, 263 | in which case a slice of SQLite's internal buffer is returned (see Warnings).) 264 | ) 265 | 266 | Params: 267 | T = The type of the returned data. T must be a boolean, a built-in numeric type, a 268 | string, an array or a `Nullable`. 269 | $(TABLE 270 | $(TR 271 | $(TH Condition on T) 272 | $(TH Requested database type) 273 | ) 274 | $(TR 275 | $(TD `isIntegral!T || isBoolean!T`) 276 | $(TD INTEGER) 277 | ) 278 | $(TR 279 | $(TD `isFloatingPoint!T`) 280 | $(TD FLOAT) 281 | ) 282 | $(TR 283 | $(TD `isSomeString!T`) 284 | $(TD TEXT) 285 | ) 286 | $(TR 287 | $(TD `isArray!T`) 288 | $(TD BLOB) 289 | ) 290 | $(TR 291 | $(TD `is(T == Nullable!U, U...)`) 292 | $(TD NULL or U) 293 | ) 294 | ) 295 | 296 | index = The index of the column in the prepared statement or 297 | the name of the column, as specified in the prepared statement 298 | with an AS clause. The index of the first column is 0. 299 | 300 | Returns: 301 | A value of type T. The returned value results from SQLite's own conversion rules: 302 | see $(LINK http://www.sqlite.org/c3ref/column_blob.html) and 303 | $(LINK http://www.sqlite.org/lang_expr.html#castexpr). It's then converted 304 | to T using `std.conv.to!T`. 305 | 306 | Warnings: 307 | When using `PeekMode.slice`, the data of the slice will be $(B invalidated) 308 | when the next row is accessed. A copy of the data has to be made somehow for it to 309 | outlive the next step on the same statement. 310 | 311 | When using referring to the column by name, the names of all the columns are 312 | tested each time this function is called: use 313 | numeric indexing for better performance. 314 | +/ 315 | T peek(T)(size_t index) 316 | if (isBoolean!T || isIntegral!T || isSomeChar!T) 317 | { 318 | assertInitialized(); 319 | return sqlite3_column_int64(statement.handle, internalIndex(index)).to!T; 320 | } 321 | 322 | /// ditto 323 | T peek(T)(size_t index) 324 | if (isFloatingPoint!T) 325 | { 326 | assertInitialized(); 327 | return sqlite3_column_double(statement.handle, internalIndex(index)).to!T; 328 | } 329 | 330 | /// ditto 331 | T peek(T, PeekMode mode = PeekMode.copy)(size_t index) 332 | if (isSomeString!T) 333 | { 334 | import core.stdc.string : strlen, memcpy; 335 | 336 | assertInitialized(); 337 | auto i = internalIndex(index); 338 | auto str = cast(const(char)*) sqlite3_column_text(statement.handle, i); 339 | 340 | if (str is null) 341 | return null; 342 | 343 | auto length = strlen(str); 344 | static if (mode == PeekMode.copy) 345 | { 346 | char[] text; 347 | text.length = length; 348 | memcpy(text.ptr, str, length); 349 | return text.to!T; 350 | } 351 | else static if (mode == PeekMode.slice) 352 | return cast(T) str[0..length]; 353 | else 354 | static assert(false); 355 | } 356 | 357 | /// ditto 358 | T peek(T, PeekMode mode = PeekMode.copy)(size_t index) 359 | if (isArray!T && !isSomeString!T) 360 | { 361 | assertInitialized(); 362 | auto i = internalIndex(index); 363 | auto ptr = sqlite3_column_blob(statement.handle, i); 364 | auto length = sqlite3_column_bytes(statement.handle, i); 365 | static if (mode == PeekMode.copy) 366 | { 367 | import core.stdc.string : memcpy; 368 | ubyte[] blob; 369 | blob.length = length; 370 | memcpy(blob.ptr, ptr, length); 371 | return cast(T) blob; 372 | } 373 | else static if (mode == PeekMode.slice) 374 | return cast(T) ptr[0..length]; 375 | else 376 | static assert(false); 377 | } 378 | 379 | /// ditto 380 | T peek(T)(size_t index) 381 | if (isInstanceOf!(Nullable, T) 382 | && !isArray!(TemplateArgsOf!T[0]) && !isSomeString!(TemplateArgsOf!T[0])) 383 | { 384 | assertInitialized(); 385 | alias U = TemplateArgsOf!T[0]; 386 | if (sqlite3_column_type(statement.handle, internalIndex(index)) == SqliteType.NULL) 387 | return T.init; 388 | return T(peek!U(index)); 389 | } 390 | 391 | /// ditto 392 | T peek(T, PeekMode mode = PeekMode.copy)(size_t index) 393 | if (isInstanceOf!(Nullable, T) 394 | && (isArray!(TemplateArgsOf!T[0]) || isSomeString!(TemplateArgsOf!T[0]))) 395 | { 396 | assertInitialized(); 397 | alias U = TemplateArgsOf!T[0]; 398 | if (sqlite3_column_type(statement.handle, internalIndex(index)) == SqliteType.NULL) 399 | return T.init; 400 | return T(peek!(U, mode)(index)); 401 | } 402 | 403 | /// ditto 404 | T peek(T)(string columnName) 405 | { 406 | return peek!T(indexForName(columnName)); 407 | } 408 | 409 | /++ 410 | Determines the type of the data in a particular column. 411 | 412 | `columnType` returns the type of the actual data in that column, whereas 413 | `columnDeclaredTypeName` returns the name of the type as declared in the SELECT statement. 414 | 415 | See_Also: $(LINK http://www.sqlite.org/c3ref/column_blob.html) and 416 | $(LINK http://www.sqlite.org/c3ref/column_decltype.html). 417 | +/ 418 | SqliteType columnType(size_t index) 419 | { 420 | assertInitialized(); 421 | return cast(SqliteType) sqlite3_column_type(statement.handle, internalIndex(index)); 422 | } 423 | /// Ditto 424 | SqliteType columnType(string columnName) 425 | { 426 | return columnType(indexForName(columnName)); 427 | } 428 | /// Ditto 429 | string columnDeclaredTypeName(size_t index) 430 | { 431 | assertInitialized(); 432 | return sqlite3_column_decltype(statement.handle, internalIndex(index)).to!string; 433 | } 434 | /// Ditto 435 | string columnDeclaredTypeName(string columnName) 436 | { 437 | return columnDeclaredTypeName(indexForName(columnName)); 438 | } 439 | /// 440 | unittest 441 | { 442 | auto db = Database(":memory:"); 443 | db.run("CREATE TABLE items (name TEXT, price REAL); 444 | INSERT INTO items VALUES ('car', 20000); 445 | INSERT INTO items VALUES ('air', 'free');"); 446 | 447 | auto results = db.execute("SELECT name, price FROM items"); 448 | 449 | auto row = results.front; 450 | assert(row.columnType(0) == SqliteType.TEXT); 451 | assert(row.columnType("price") == SqliteType.FLOAT); 452 | assert(row.columnDeclaredTypeName(0) == "TEXT"); 453 | assert(row.columnDeclaredTypeName("price") == "REAL"); 454 | 455 | results.popFront(); 456 | row = results.front; 457 | assert(row.columnType(0) == SqliteType.TEXT); 458 | assert(row.columnType("price") == SqliteType.TEXT); 459 | assert(row.columnDeclaredTypeName(0) == "TEXT"); 460 | assert(row.columnDeclaredTypeName("price") == "REAL"); 461 | } 462 | 463 | /++ 464 | Determines the name of a particular column. 465 | 466 | See_Also: $(LINK http://www.sqlite.org/c3ref/column_name.html). 467 | +/ 468 | string columnName(size_t index) 469 | { 470 | assertInitialized(); 471 | return sqlite3_column_name(statement.handle, internalIndex(index)).to!string; 472 | } 473 | /// 474 | unittest 475 | { 476 | auto db = Database(":memory:"); 477 | db.run("CREATE TABLE items (name TEXT, price REAL); 478 | INSERT INTO items VALUES ('car', 20000);"); 479 | 480 | auto row = db.execute("SELECT name, price FROM items").front; 481 | assert(row.columnName(1) == "price"); 482 | } 483 | 484 | version (SqliteEnableColumnMetadata) 485 | { 486 | /++ 487 | Determines the name of the database, table, or column that is the origin of a 488 | particular result column in SELECT statement. 489 | 490 | Warning: 491 | These methods are defined only when this library is compiled with 492 | `-version=SqliteEnableColumnMetadata`, and SQLite compiled with the 493 | `SQLITE_ENABLE_COLUMN_METADATA` option defined. 494 | 495 | See_Also: $(LINK http://www.sqlite.org/c3ref/column_database_name.html). 496 | +/ 497 | string columnDatabaseName(size_t index) 498 | { 499 | assertInitialized(); 500 | return sqlite3_column_database_name(statement.handle, internalIndex(index)).to!string; 501 | } 502 | /// Ditto 503 | string columnDatabaseName(string columnName) 504 | { 505 | return columnDatabaseName(indexForName(columnName)); 506 | } 507 | /// Ditto 508 | string columnTableName(size_t index) 509 | { 510 | assertInitialized(); 511 | return sqlite3_column_database_name(statement.handle, internalIndex(index)).to!string; 512 | } 513 | /// Ditto 514 | string columnTableName(string columnName) 515 | { 516 | return columnTableName(indexForName(columnName)); 517 | } 518 | /// Ditto 519 | string columnOriginName(size_t index) 520 | { 521 | assertInitialized(); 522 | return sqlite3_column_origin_name(statement.handle, internalIndex(index)).to!string; 523 | } 524 | /// Ditto 525 | string columnOriginName(string columnName) 526 | { 527 | return columnOriginName(indexForName(columnName)); 528 | } 529 | } 530 | 531 | /++ 532 | Returns a struct with field members populated from the row's data. 533 | 534 | Neither the names of the fields nor the names of the columns are checked. The fields 535 | are filled with the columns' data in order. Thus, the order of the struct members must be the 536 | same as the order of the columns in the prepared statement. 537 | 538 | SQLite's conversion rules will be used. For instance, if a string field has the same rank 539 | as an INTEGER column, the field's data will be the string representation of the integer. 540 | +/ 541 | T as(T)() 542 | if (is(T == struct)) 543 | { 544 | import std.traits : FieldTypeTuple, FieldNameTuple; 545 | 546 | alias FieldTypes = FieldTypeTuple!T; 547 | T obj; 548 | foreach (i, fieldName; FieldNameTuple!T) 549 | __traits(getMember, obj, fieldName) = peek!(FieldTypes[i])(i); 550 | return obj; 551 | } 552 | /// 553 | unittest 554 | { 555 | struct Item 556 | { 557 | int _id; 558 | string name; 559 | } 560 | 561 | auto db = Database(":memory:"); 562 | db.run("CREATE TABLE items (name TEXT); 563 | INSERT INTO items VALUES ('Light bulb')"); 564 | 565 | auto results = db.execute("SELECT rowid AS id, name FROM items"); 566 | auto row = results.front; 567 | auto thing = row.as!Item(); 568 | 569 | assert(thing == Item(1, "Light bulb")); 570 | } 571 | 572 | private: 573 | int internalIndex(size_t index) 574 | { 575 | assertInitialized(); 576 | auto i = index + frontIndex; 577 | assert(i >= 0 && i <= backIndex, "invalid column index: %d".format(i)); 578 | assert(i <= int.max, "invalid index value: %d".format(i)); 579 | return cast(int) i; 580 | } 581 | 582 | int indexForName(string name) 583 | { 584 | assertInitialized(); 585 | assert(name.length, "column with no name"); 586 | foreach (i; frontIndex .. backIndex + 1) 587 | { 588 | assert(i <= int.max, "invalid index value: %d".format(i)); 589 | if (sqlite3_column_name(statement.handle, cast(int) i).to!string == name) 590 | return i; 591 | } 592 | 593 | assert(false, "invalid column name: '%s'".format(name)); 594 | } 595 | 596 | void assertInitialized() nothrow 597 | { 598 | assert(!empty, "Accessing elements of an empty row"); 599 | assert(statement.handle !is null, "operation on an empty statement"); 600 | } 601 | } 602 | 603 | /// Behavior of the `Row.peek()` method for arrays/strings 604 | enum PeekMode 605 | { 606 | /++ 607 | Return a copy of the data into a new array/string. 608 | The copy is safe to use after stepping to the next row. 609 | +/ 610 | copy, 611 | 612 | /++ 613 | Return a slice of the data. 614 | The slice can point to invalid data after stepping to the next row. 615 | +/ 616 | slice 617 | } 618 | 619 | /++ 620 | Some data retrieved from a column. 621 | +/ 622 | struct ColumnData 623 | { 624 | import std.traits : isBoolean, isIntegral, isNumeric, isFloatingPoint, 625 | isSomeString, isArray; 626 | import std.variant : Algebraic, VariantException; 627 | 628 | alias SqliteVariant = Algebraic!(long, double, string, Blob, typeof(null)); 629 | 630 | private 631 | { 632 | SqliteVariant _value; 633 | SqliteType _type; 634 | } 635 | 636 | /++ 637 | Creates a new `ColumnData` from the value. 638 | +/ 639 | this(T)(inout T value) inout 640 | if (isBoolean!T || isIntegral!T) 641 | { 642 | _value = SqliteVariant(value.to!long); 643 | _type = SqliteType.INTEGER; 644 | } 645 | 646 | /// ditto 647 | this(T)(T value) 648 | if (isFloatingPoint!T) 649 | { 650 | _value = SqliteVariant(value.to!double); 651 | _type = SqliteType.FLOAT; 652 | } 653 | 654 | /// ditto 655 | this(T)(T value) 656 | if (isSomeString!T) 657 | { 658 | if (value is null) 659 | { 660 | _value = SqliteVariant(null); 661 | _type = SqliteType.NULL; 662 | } 663 | else 664 | { 665 | _value = SqliteVariant(value.to!string); 666 | _type = SqliteType.TEXT; 667 | } 668 | } 669 | 670 | /// ditto 671 | this(T)(T value) 672 | if (isArray!T && !isSomeString!T) 673 | { 674 | if (value is null) 675 | { 676 | _value = SqliteVariant(null); 677 | _type = SqliteType.NULL; 678 | } 679 | else 680 | { 681 | _value = SqliteVariant(value.to!Blob); 682 | _type = SqliteType.BLOB; 683 | } 684 | } 685 | /// ditto 686 | this(T)(T value) 687 | if (is(T == typeof(null))) 688 | { 689 | _value = SqliteVariant(null); 690 | _type = SqliteType.NULL; 691 | } 692 | 693 | /++ 694 | Returns the Sqlite type of the column. 695 | +/ 696 | SqliteType type() const nothrow 697 | { 698 | assertInitialized(); 699 | return _type; 700 | } 701 | 702 | /++ 703 | Returns the data converted to T. 704 | 705 | If the data is NULL, defaultValue is returned. 706 | 707 | Throws: 708 | VariantException if the value cannot be converted 709 | to the desired type. 710 | +/ 711 | auto as(T)(T defaultValue = T.init) 712 | if (isBoolean!T || isNumeric!T || isSomeString!T) 713 | { 714 | assertInitialized(); 715 | 716 | if (_type == SqliteType.NULL) 717 | return defaultValue; 718 | 719 | return _value.coerce!T; 720 | } 721 | 722 | /// ditto 723 | auto as(T)(T defaultValue = T.init) 724 | if (isArray!T && !isSomeString!T) 725 | { 726 | assertInitialized(); 727 | 728 | if (_type == SqliteType.NULL) 729 | return defaultValue; 730 | 731 | Blob data = _value.get!Blob; 732 | return cast(T) data; 733 | } 734 | 735 | /// ditto 736 | auto as(T : Nullable!U, U...)(T defaultValue = T.init) 737 | { 738 | assertInitialized(); 739 | 740 | if (_type == SqliteType.NULL) 741 | return defaultValue; 742 | 743 | return T(as!U()); 744 | } 745 | 746 | void toString(scope void delegate(const(char)[]) sink) 747 | { 748 | assertInitialized(); 749 | 750 | if (_type == SqliteType.NULL) 751 | sink("null"); 752 | else 753 | sink(_value.toString); 754 | } 755 | 756 | private: 757 | void assertInitialized() const nothrow 758 | { 759 | assert(_value.hasValue, "Accessing uninitialized ColumnData"); 760 | } 761 | } 762 | 763 | /++ 764 | Caches all the results of a query into memory at once. 765 | 766 | This allows to keep all the rows returned from a query accessible in any order 767 | and indefinitely. 768 | 769 | Returns: 770 | A `CachedResults` struct that allows to iterate on the rows and their 771 | columns with an array-like interface. 772 | 773 | The `CachedResults` struct is equivalent to an array of 'rows', which in 774 | turn can be viewed as either an array of `ColumnData` or as an associative 775 | array of `ColumnData` indexed by the column names. 776 | +/ 777 | CachedResults cached(ResultRange results) 778 | { 779 | return CachedResults(results); 780 | } 781 | /// 782 | unittest 783 | { 784 | auto db = Database(":memory:"); 785 | db.run("CREATE TABLE test (msg TEXT, num FLOAT); 786 | INSERT INTO test (msg, num) VALUES ('ABC', 123); 787 | INSERT INTO test (msg, num) VALUES ('DEF', 456);"); 788 | 789 | auto results = db.execute("SELECT * FROM test").cached; 790 | assert(results.length == 2); 791 | assert(results[0][0].as!string == "ABC"); 792 | assert(results[0][1].as!int == 123); 793 | assert(results[1]["msg"].as!string == "DEF"); 794 | assert(results[1]["num"].as!int == 456); 795 | } 796 | 797 | /++ 798 | Stores all the results of a query. 799 | 800 | The `CachedResults` struct is equivalent to an array of 'rows', which in 801 | turn can be viewed as either an array of `ColumnData` or as an associative 802 | array of `ColumnData` indexed by the column names. 803 | 804 | Unlike `ResultRange`, `CachedResults` is a random-access range of rows, and its 805 | data always remain available. 806 | 807 | See_Also: 808 | `cached` for an example. 809 | +/ 810 | struct CachedResults 811 | { 812 | import std.array : appender; 813 | 814 | // A row of retrieved data 815 | struct CachedRow 816 | { 817 | ColumnData[] columns; 818 | alias columns this; 819 | 820 | size_t[string] columnIndexes; 821 | 822 | private this(Row row, size_t[string] columnIndexes) 823 | { 824 | this.columnIndexes = columnIndexes; 825 | 826 | auto colapp = appender!(ColumnData[]); 827 | foreach (i; 0 .. row.length) 828 | colapp.put(row[i]); 829 | columns = colapp.data; 830 | } 831 | 832 | // Returns the data at the given index in the row. 833 | ColumnData opIndex(size_t index) 834 | { 835 | return columns[index]; 836 | } 837 | 838 | // Returns the data at the given column. 839 | ColumnData opIndex(string name) 840 | { 841 | auto index = name in columnIndexes; 842 | assert(index, "unknown column name: %s".format(name)); 843 | return columns[*index]; 844 | } 845 | } 846 | 847 | // All the rows returned by the query. 848 | CachedRow[] rows; 849 | alias rows this; 850 | 851 | private size_t[string] columnIndexes; 852 | 853 | this(ResultRange results) 854 | { 855 | if (!results.empty) 856 | { 857 | auto first = results.front; 858 | foreach (i; 0 .. first.length) 859 | { 860 | assert(i <= int.max, "invalid column index value: %d".format(i)); 861 | auto name = sqlite3_column_name(results.statement.handle, cast(int) i).to!string; 862 | columnIndexes[name] = i; 863 | } 864 | } 865 | 866 | auto rowapp = appender!(CachedRow[]); 867 | while (!results.empty) 868 | { 869 | rowapp.put(CachedRow(results.front, columnIndexes)); 870 | results.popFront(); 871 | } 872 | rows = rowapp.data; 873 | } 874 | } 875 | -------------------------------------------------------------------------------- /source/tests.d: -------------------------------------------------------------------------------- 1 | module tests.d; 2 | 3 | version (unittest): 4 | 5 | import d2sqlite3; 6 | import std.algorithm; 7 | import std.exception : assertThrown, assertNotThrown; 8 | import std.string : format; 9 | import std.typecons : Nullable; 10 | import std.conv : hexString; 11 | 12 | unittest // Test version of SQLite library 13 | { 14 | import std.string : startsWith; 15 | assert(versionString.startsWith("3.")); 16 | assert(versionNumber >= 3_008_007); 17 | } 18 | 19 | unittest // COV 20 | { 21 | auto ts = threadSafe; 22 | } 23 | 24 | unittest // Configuration logging and db.close() 25 | { 26 | static extern (C) void loggerCallback(void* arg, int code, const(char)* msg) nothrow 27 | { 28 | ++*(cast(int*) arg); 29 | } 30 | 31 | int marker = 42; 32 | 33 | shutdown(); 34 | config(SQLITE_CONFIG_MULTITHREAD); 35 | config(SQLITE_CONFIG_LOG, &loggerCallback, &marker); 36 | initialize(); 37 | 38 | { 39 | auto db = Database(":memory:"); 40 | try 41 | { 42 | db.run("DROP TABLE wtf"); 43 | } 44 | catch (Exception e) 45 | { 46 | } 47 | db.close(); 48 | } 49 | assert(marker == 43); 50 | 51 | shutdown(); 52 | config(SQLITE_CONFIG_LOG, null, null); 53 | initialize(); 54 | 55 | { 56 | auto db = Database(":memory:"); 57 | try 58 | { 59 | db.run("DROP TABLE wtf"); 60 | } 61 | catch (Exception e) 62 | { 63 | } 64 | } 65 | assert(marker == 43); 66 | } 67 | 68 | unittest // Database.tableColumnMetadata() 69 | { 70 | auto db = Database(":memory:"); 71 | db.run("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, 72 | val FLOAT NOT NULL)"); 73 | assert(db.tableColumnMetadata("test", "id") == 74 | TableColumnMetadata("INTEGER", "BINARY", false, true, true)); 75 | assert(db.tableColumnMetadata("test", "val") == 76 | TableColumnMetadata("FLOAT", "BINARY", true, false, false)); 77 | } 78 | 79 | unittest // Database.run() 80 | { 81 | auto db = Database(":memory:"); 82 | int i; 83 | db.run(`SELECT 1; SELECT 2;`, (ResultRange r) { i = r.oneValue!int; return false; }); 84 | assert(i == 1); 85 | } 86 | 87 | unittest // Database.errorCode() 88 | { 89 | auto db = Database(":memory:"); 90 | db.run(`SELECT 1;`); 91 | assert(db.errorCode == SQLITE_OK); 92 | try 93 | db.run(`DROP TABLE non_existent`); 94 | catch (SqliteException e) 95 | assert(db.errorCode == SQLITE_ERROR); 96 | } 97 | 98 | unittest // Database.config 99 | { 100 | auto db = Database(":memory:"); 101 | db.run(` 102 | CREATE TABLE test (val INTEGER); 103 | CREATE TRIGGER test_trig BEFORE INSERT ON test 104 | BEGIN 105 | SELECT RAISE(FAIL, 'Test failed'); 106 | END; 107 | `); 108 | int res = 42; 109 | db.config(SQLITE_DBCONFIG_ENABLE_TRIGGER, 0, &res); 110 | assert(res == 0); 111 | db.execute("INSERT INTO test (val) VALUES (1)"); 112 | } 113 | 114 | unittest // Database.createFunction(ColumnData[]...) 115 | { 116 | string myList(ColumnData[] args...) 117 | { 118 | import std.array : appender; 119 | import std.string : format, join; 120 | 121 | auto app = appender!(string[]); 122 | foreach (arg; args) 123 | { 124 | if (arg.type == SqliteType.TEXT) 125 | app.put(`"%s"`.format(arg)); 126 | else 127 | app.put("%s".format(arg)); 128 | } 129 | return app.data.join(", "); 130 | } 131 | auto db = Database(":memory:"); 132 | db.createFunction("my_list", &myList); 133 | auto list = db.execute("SELECT my_list(42, 3.14, 'text', x'00FF', NULL)").oneValue!string; 134 | assert(list == `42, 3.14, "text", [0, 255], null`, list); 135 | } 136 | 137 | unittest // Database.createFunction() exceptions 138 | { 139 | import std.exception : assertThrown; 140 | 141 | int myFun(int a, int b = 1) 142 | { 143 | return a * b; 144 | } 145 | 146 | auto db = Database(":memory:"); 147 | db.createFunction("myFun", &myFun); 148 | assertThrown!SqliteException(db.execute("SELECT myFun()")); 149 | assertThrown!SqliteException(db.execute("SELECT myFun(1, 2, 3)")); 150 | assert(db.execute("SELECT myFun(5)").oneValue!int == 5); 151 | assert(db.execute("SELECT myFun(5, 2)").oneValue!int == 10); 152 | 153 | db.createFunction("myFun", null); 154 | assertThrown!SqliteException(db.execute("SELECT myFun(5)")); 155 | assertThrown!SqliteException(db.execute("SELECT myFun(5, 2)")); 156 | } 157 | 158 | unittest // Database.setUpdateHook() 159 | { 160 | int i; 161 | auto db = Database(":memory:"); 162 | db.setUpdateHook((int type, string dbName, string tableName, long rowid) { 163 | assert(type == SQLITE_INSERT); 164 | assert(dbName == "main"); 165 | assert(tableName == "test"); 166 | assert(rowid == 1); 167 | i = 42; 168 | }); 169 | db.run("CREATE TABLE test (val INTEGER); 170 | INSERT INTO test VALUES (100)"); 171 | assert(i == 42); 172 | db.setUpdateHook(null); 173 | } 174 | 175 | unittest // Database commit and rollback hooks 176 | { 177 | int i; 178 | auto db = Database(":memory:"); 179 | db.setCommitHook({ i = 42; return SQLITE_OK; }); 180 | db.setRollbackHook({ i = 666; }); 181 | db.begin(); 182 | db.execute("CREATE TABLE test (val INTEGER)"); 183 | db.rollback(); 184 | assert(i == 666); 185 | db.begin(); 186 | db.execute("CREATE TABLE test (val INTEGER)"); 187 | db.commit(); 188 | assert(i == 42); 189 | db.setCommitHook(null); 190 | db.setRollbackHook(null); 191 | } 192 | 193 | unittest // Miscellaneous functions 194 | { 195 | auto db = Database(":memory:"); 196 | assert(db.attachedFilePath("main") is null); 197 | assert(!db.isReadOnly); 198 | db.close(); 199 | } 200 | 201 | unittest // Execute an SQL statement 202 | { 203 | auto db = Database(":memory:"); 204 | db.run(""); 205 | db.run("-- This is a comment!"); 206 | db.run(";"); 207 | db.run("ANALYZE; VACUUM;"); 208 | } 209 | 210 | unittest // Unexpected multiple statements 211 | { 212 | auto db = Database(":memory:"); 213 | db.execute("BEGIN; CREATE TABLE test (val INTEGER); ROLLBACK;"); 214 | assertThrown(db.execute("DROP TABLE test")); 215 | 216 | db.execute("CREATE TABLE test (val INTEGER); DROP TABLE test;"); 217 | assertNotThrown(db.execute("DROP TABLE test")); 218 | 219 | db.execute("SELECT 1; CREATE TABLE test (val INTEGER); DROP TABLE test;"); 220 | assertThrown(db.execute("DROP TABLE test")); 221 | } 222 | 223 | unittest // Multiple statements with callback 224 | { 225 | import std.array : appender; 226 | auto db = Database(":memory:"); 227 | auto test = appender!string; 228 | db.run("SELECT 1, 2, 3; SELECT 'A', 'B', 'C';", (ResultRange r) { 229 | foreach (col; r.front) 230 | test.put(col.as!string); 231 | return true; 232 | }); 233 | assert(test.data == "123ABC"); 234 | } 235 | 236 | unittest // Different arguments and result types with createFunction 237 | { 238 | auto db = Database(":memory:"); 239 | 240 | T display(T)(T value) 241 | { 242 | return value; 243 | } 244 | 245 | db.createFunction("display_integer", &display!int); 246 | db.createFunction("display_float", &display!double); 247 | db.createFunction("display_text", &display!string); 248 | db.createFunction("display_blob", &display!Blob); 249 | 250 | assert(db.execute("SELECT display_integer(42)").oneValue!int == 42); 251 | assert(db.execute("SELECT display_float(3.14)").oneValue!double == 3.14); 252 | assert(db.execute("SELECT display_text('ABC')").oneValue!string == "ABC"); 253 | assert(db.execute("SELECT display_blob(x'ABCD')").oneValue!Blob == cast(Blob) hexString!"ABCD"); 254 | 255 | assert(db.execute("SELECT display_integer(NULL)").oneValue!int == 0); 256 | assert(db.execute("SELECT display_float(NULL)").oneValue!double == 0.0); 257 | assert(db.execute("SELECT display_text(NULL)").oneValue!string is null); 258 | assert(db.execute("SELECT display_blob(NULL)").oneValue!(Blob) is null); 259 | } 260 | 261 | unittest // Different Nullable argument types with createFunction 262 | { 263 | auto db = Database(":memory:"); 264 | 265 | auto display(T : Nullable!U, U...)(T value) 266 | { 267 | if (value.isNull) 268 | return T.init; 269 | return value; 270 | } 271 | 272 | db.createFunction("display_integer", &display!(Nullable!int)); 273 | db.createFunction("display_float", &display!(Nullable!double)); 274 | db.createFunction("display_text", &display!(Nullable!string)); 275 | db.createFunction("display_blob", &display!(Nullable!Blob)); 276 | 277 | assert(db.execute("SELECT display_integer(42)").oneValue!(Nullable!int) == 42); 278 | assert(db.execute("SELECT display_float(3.14)").oneValue!(Nullable!double) == 3.14); 279 | assert(db.execute("SELECT display_text('ABC')").oneValue!(Nullable!string) == "ABC"); 280 | assert(db.execute("SELECT display_blob(x'ABCD')").oneValue!(Nullable!Blob) == cast(Blob) hexString!"ABCD"); 281 | 282 | assert(db.execute("SELECT display_integer(NULL)").oneValue!(Nullable!int).isNull); 283 | assert(db.execute("SELECT display_float(NULL)").oneValue!(Nullable!double).isNull); 284 | assert(db.execute("SELECT display_text(NULL)").oneValue!(Nullable!string).isNull); 285 | assert(db.execute("SELECT display_blob(NULL)").oneValue!(Nullable!Blob).isNull); 286 | } 287 | 288 | unittest // Callable struct with createFunction 289 | { 290 | import std.functional : toDelegate; 291 | 292 | struct Fun 293 | { 294 | int factor; 295 | 296 | this(int factor) 297 | { 298 | this.factor = factor; 299 | } 300 | 301 | int opCall(int value) 302 | { 303 | return value * factor; 304 | } 305 | } 306 | 307 | auto f = Fun(2); 308 | auto db = Database(":memory:"); 309 | db.createFunction("my_fun", toDelegate(f)); 310 | assert(db.execute("SELECT my_fun(4)").oneValue!int == 8); 311 | } 312 | 313 | unittest // Callbacks 314 | { 315 | bool wasTraced = false; 316 | bool wasProfiled = false; 317 | bool hasProgressed = false; 318 | 319 | auto db = Database(":memory:"); 320 | db.setTraceCallback((string s) { wasTraced = true; }); 321 | db.execute("SELECT * FROM sqlite_master;"); 322 | assert(wasTraced); 323 | db.setProfileCallback((string s, ulong t) { wasProfiled = true; }); 324 | db.execute("SELECT * FROM sqlite_master;"); 325 | assert(wasProfiled); 326 | 327 | db.setProgressHandler(1, { hasProgressed = true; return 0; }); 328 | db.execute("SELECT * FROM sqlite_master;"); 329 | assert(hasProgressed); 330 | } 331 | 332 | unittest // Statement.oneValue() 333 | { 334 | Statement statement; 335 | { 336 | auto db = Database(":memory:"); 337 | statement = db.prepare(" SELECT 42 "); 338 | } 339 | assert(statement.execute.oneValue!int == 42); 340 | } 341 | 342 | unittest // Statement.finalize() 343 | { 344 | auto db = Database(":memory:"); 345 | auto statement = db.prepare(" SELECT 42 "); 346 | statement.finalize(); 347 | } 348 | 349 | unittest // Simple parameters binding 350 | { 351 | auto db = Database(":memory:"); 352 | db.execute("CREATE TABLE test (val INTEGER)"); 353 | 354 | auto statement = db.prepare("INSERT INTO test (val) VALUES (?)"); 355 | statement.bind(1, 36); 356 | statement.clearBindings(); 357 | statement.bind(1, 42); 358 | statement.execute(); 359 | statement.reset(); 360 | statement.bind(1, 42); 361 | statement.execute(); 362 | 363 | assert(db.lastInsertRowid == 2); 364 | assert(db.changes == 1); 365 | assert(db.totalChanges == 2); 366 | 367 | auto results = db.execute("SELECT * FROM test"); 368 | foreach (row; results) 369 | assert(row.peek!int(0) == 42); 370 | } 371 | 372 | unittest // Multiple parameters binding 373 | { 374 | auto db = Database(":memory:"); 375 | db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)"); 376 | auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (:i, @f, $t)"); 377 | 378 | assert(statement.parameterCount == 3); 379 | assert(statement.parameterName(2) == "@f"); 380 | assert(statement.parameterIndex("$t") == 3); 381 | assert(statement.parameterIndex(":foo") == 0); 382 | 383 | statement.bind("$t", "TEXT"); 384 | statement.bind(":i", 42); 385 | statement.bind("@f", 3.14); 386 | statement.execute(); 387 | statement.reset(); 388 | statement.bind(1, 42); 389 | statement.bind(2, 3.14); 390 | statement.bind(3, "TEXT"); 391 | statement.execute(); 392 | 393 | auto results = db.execute("SELECT * FROM test"); 394 | foreach (row; results) 395 | { 396 | assert(row.length == 3); 397 | assert(row.peek!int("i") == 42); 398 | assert(row.peek!double("f") == 3.14); 399 | assert(row.peek!string("t") == "TEXT"); 400 | } 401 | } 402 | 403 | // Binding/peeking structs with `toString` and `fromString` 404 | unittest 405 | { 406 | auto db = Database(":memory:"); 407 | db.execute("CREATE TABLE test (val TEXT)"); 408 | 409 | static struct ToStringSink { 410 | string value; 411 | void toString(scope void delegate(in char[]) sink) const 412 | { 413 | sink(this.value); 414 | } 415 | } 416 | 417 | static struct ToStringMethod { 418 | string value; 419 | string toString() const 420 | { 421 | return this.value; 422 | } 423 | } 424 | 425 | auto statement = db.prepare("INSERT INTO test (val) VALUES (?)"); 426 | statement.bind(1, ToStringMethod("oldmethod")); 427 | statement.clearBindings(); 428 | statement.bind(1, ToStringMethod("method")); 429 | statement.execute(); 430 | statement.reset(); 431 | statement.bind(1, ToStringSink("sink")); 432 | statement.execute(); 433 | 434 | assert(db.lastInsertRowid == 2); 435 | assert(db.changes == 1); 436 | assert(db.totalChanges == 2); 437 | 438 | auto results = db.execute("SELECT * FROM test"); 439 | results.equal!((a, b) => a.peek!string(0) == b)(["method", "sink"]); 440 | } 441 | 442 | unittest // Multiple parameters binding: tuples 443 | { 444 | auto db = Database(":memory:"); 445 | db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)"); 446 | auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (?, ?, ?)"); 447 | statement.bindAll(42, 3.14, "TEXT"); 448 | statement.execute(); 449 | 450 | auto results = db.execute("SELECT * FROM test"); 451 | foreach (row; results) 452 | { 453 | assert(row.length == 3); 454 | assert(row.peek!int(0) == 42); 455 | assert(row.peek!double(1) == 3.14); 456 | assert(row.peek!string(2) == "TEXT"); 457 | } 458 | } 459 | 460 | unittest // Binding/peeking integral values 461 | { 462 | auto db = Database(":memory:"); 463 | db.run("CREATE TABLE test (val INTEGER)"); 464 | 465 | auto statement = db.prepare("INSERT INTO test (val) VALUES (?)"); 466 | statement.inject(cast(byte) 42); 467 | statement.inject(42U); 468 | statement.inject(42UL); 469 | statement.inject('\x2A'); 470 | 471 | auto results = db.execute("SELECT * FROM test"); 472 | foreach (row; results) 473 | assert(row.peek!long(0) == 42); 474 | } 475 | 476 | void foobar() // Binding/peeking floating point values 477 | { 478 | auto db = Database(":memory:"); 479 | db.run("CREATE TABLE test (val FLOAT)"); 480 | 481 | auto statement = db.prepare("INSERT INTO test (val) VALUES (?)"); 482 | statement.inject(42.0F); 483 | statement.inject(42.0); 484 | statement.inject(42.0L); 485 | statement.inject("42"); 486 | 487 | auto results = db.execute("SELECT * FROM test"); 488 | foreach (row; results) 489 | assert(row.peek!double(0) == 42.0); 490 | } 491 | 492 | unittest // Binding/peeking text values 493 | { 494 | auto db = Database(":memory:"); 495 | db.run("CREATE TABLE test (val TEXT); 496 | INSERT INTO test (val) VALUES ('I am a text.')"); 497 | 498 | auto results = db.execute("SELECT * FROM test"); 499 | assert(results.front.peek!(string, PeekMode.slice)(0) == "I am a text."); 500 | assert(results.front.peek!(string, PeekMode.copy)(0) == "I am a text."); 501 | 502 | import std.exception : assertThrown; 503 | import std.variant : VariantException; 504 | assertThrown!VariantException(results.front[0].as!Blob); 505 | } 506 | 507 | unittest // Binding/peeking blob values 508 | { 509 | auto db = Database(":memory:"); 510 | db.execute("CREATE TABLE test (val BLOB)"); 511 | 512 | auto statement = db.prepare("INSERT INTO test (val) VALUES (?)"); 513 | auto array = cast(Blob) [1, 2, 3]; 514 | statement.inject(array); 515 | ubyte[3] sarray = [1, 2, 3]; 516 | statement.inject(sarray); 517 | 518 | auto results = db.execute("SELECT * FROM test"); 519 | foreach (row; results) 520 | { 521 | assert(row.peek!(Blob, PeekMode.slice)(0) == [1, 2, 3]); 522 | assert(row[0].as!Blob == [1, 2, 3]); 523 | } 524 | } 525 | 526 | unittest // Struct injecting 527 | { 528 | static struct Test 529 | { 530 | int i; 531 | double f; 532 | string t; 533 | } 534 | 535 | auto db = Database(":memory:"); 536 | db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)"); 537 | auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (?, ?, ?)"); 538 | auto test = Test(42, 3.14, "TEXT"); 539 | statement.inject(test); 540 | statement.inject(Test(42, 3.14, "TEXT")); 541 | auto itest = cast(immutable) Test(42, 3.14, "TEXT"); 542 | statement.inject(itest); 543 | 544 | auto results = db.execute("SELECT * FROM test"); 545 | assert(!results.empty); 546 | foreach (row; results) 547 | { 548 | assert(row.length == 3); 549 | assert(row.peek!int(0) == 42); 550 | assert(row.peek!double(1) == 3.14); 551 | assert(row.peek!string(2) == "TEXT"); 552 | } 553 | } 554 | 555 | unittest // Iterable struct injecting 556 | { 557 | import std.range : iota; 558 | 559 | auto db = Database(":memory:"); 560 | db.execute("CREATE TABLE test (a INTEGER, b INTEGER, c INTEGER)"); 561 | auto statement = db.prepare("INSERT INTO test (a, b, c) VALUES (?, ?, ?)"); 562 | statement.inject(iota(0, 3)); 563 | 564 | auto results = db.execute("SELECT * FROM test"); 565 | assert(!results.empty); 566 | foreach (row; results) 567 | { 568 | assert(row.length == 3); 569 | assert(row.peek!int(0) == 0); 570 | assert(row.peek!int(1) == 1); 571 | assert(row.peek!int(2) == 2); 572 | } 573 | } 574 | 575 | unittest // Injecting nullable 576 | { 577 | import std.array : array; 578 | 579 | auto db = Database(":memory:"); 580 | db.execute("CREATE TABLE test (i INTEGER, s TEXT)"); 581 | auto statement = db.prepare("INSERT INTO test (i, s) VALUES (?, ?)"); 582 | statement.inject(Nullable!int(1), "one"); 583 | statement = db.prepare("INSERT INTO test (i) VALUES (?)"); 584 | statement.inject(Nullable!int.init); 585 | 586 | auto results = db.execute("SELECT i FROM test ORDER BY rowid"); 587 | assert(results.equal!((a, b) => a.peek!(Nullable!int)(0) == b)( 588 | [ Nullable!int(1), Nullable!int.init ] )); 589 | } 590 | 591 | unittest // Injecting tuple 592 | { 593 | import std.typecons : tuple; 594 | 595 | auto db = Database(":memory:"); 596 | db.execute("CREATE TABLE test (i INTEGER, f FLOAT, t TEXT)"); 597 | auto statement = db.prepare("INSERT INTO test (i, f, t) VALUES (?, ?, ?)"); 598 | statement.inject(tuple(42, 3.14, "TEXT")); 599 | 600 | auto results = db.execute("SELECT * FROM test"); 601 | foreach (row; results) 602 | { 603 | assert(row.length == 3); 604 | assert(row.peek!int(0) == 42); 605 | assert(row.peek!double(1) == 3.14); 606 | assert(row.peek!string(2) == "TEXT"); 607 | } 608 | } 609 | 610 | unittest // Injecting dict 611 | { 612 | auto db = Database(":memory:"); 613 | db.execute("CREATE TABLE test (a TEXT, b TEXT, c TEXT)"); 614 | auto statement = db.prepare("INSERT INTO test (c, b, a) VALUES (:c, :b, :a)"); 615 | statement.inject([":a":"a", ":b":"b", ":c":"c"]); 616 | 617 | auto results = db.execute("SELECT * FROM test"); 618 | foreach (row; results) 619 | { 620 | assert(row.length == 3); 621 | assert(row.peek!string(0) == "a"); 622 | assert(row.peek!string(1) == "b"); 623 | assert(row.peek!string(2) == "c"); 624 | } 625 | } 626 | 627 | unittest // Binding Nullable 628 | { 629 | auto db = Database(":memory:"); 630 | db.execute("CREATE TABLE test (a, b, c, d, e);"); 631 | 632 | auto statement = db.prepare("INSERT INTO test (a,b,c,d,e) VALUES (?,?,?,?,?)"); 633 | statement.bind(1, Nullable!int(123)); 634 | statement.bind(2, Nullable!int()); 635 | statement.bind(3, Nullable!(uint, 0)(42)); 636 | statement.bind(4, Nullable!(uint, 0)()); 637 | statement.bind(5, Nullable!bool(false)); 638 | statement.execute(); 639 | 640 | auto results = db.execute("SELECT * FROM test"); 641 | foreach (row; results) 642 | { 643 | assert(row.length == 5); 644 | assert(row.peek!int(0) == 123); 645 | assert(row.columnType(1) == SqliteType.NULL); 646 | assert(row.peek!int(2) == 42); 647 | assert(row.columnType(3) == SqliteType.NULL); 648 | assert(!row.peek!bool(4)); 649 | } 650 | } 651 | 652 | unittest // Peeking Nullable 653 | { 654 | auto db = Database(":memory:"); 655 | auto results = db.execute("SELECT 1, NULL, 8.5, NULL"); 656 | foreach (row; results) 657 | { 658 | assert(row.length == 4); 659 | assert(row.peek!(Nullable!double)(2).get == 8.5); 660 | assert(row.peek!(Nullable!double)(3).isNull); 661 | assert(row.peek!(Nullable!(int, 0))(0).get == 1); 662 | assert(row.peek!(Nullable!(int, 0))(1).isNull); 663 | } 664 | } 665 | 666 | unittest // GC anchoring test 667 | { 668 | import core.memory : GC; 669 | 670 | auto db = Database(":memory:"); 671 | auto stmt = db.prepare("SELECT ?"); 672 | 673 | auto str = ("I am test string").dup; 674 | stmt.bind(1, str); 675 | str = null; 676 | 677 | foreach (_; 0..3) 678 | { 679 | GC.collect(); 680 | GC.minimize(); 681 | } 682 | 683 | ResultRange results = stmt.execute(); 684 | foreach(row; results) 685 | { 686 | assert(row.length == 1); 687 | assert(row.peek!string(0) == "I am test string"); 688 | } 689 | } 690 | 691 | version (unittest) // ResultRange is an input range of Row 692 | { 693 | import std.range.primitives : isInputRange, ElementType; 694 | static assert(isInputRange!ResultRange); 695 | static assert(is(ElementType!ResultRange == Row)); 696 | } 697 | 698 | unittest // Statement error 699 | { 700 | auto db = Database(":memory:"); 701 | db.execute("CREATE TABLE test (val INTEGER NOT NULL)"); 702 | auto stmt = db.prepare("INSERT INTO test (val) VALUES (?)"); 703 | stmt.bind(1, null); 704 | import std.exception : assertThrown; 705 | assertThrown!SqliteException(stmt.execute()); 706 | } 707 | 708 | version (unittest) // Row is a random access range of ColumnData 709 | { 710 | import std.range.primitives : isRandomAccessRange, ElementType; 711 | static assert(isRandomAccessRange!Row); 712 | static assert(is(ElementType!Row == ColumnData)); 713 | } 714 | 715 | unittest // Row.init 716 | { 717 | import core.exception : AssertError; 718 | 719 | Row row; 720 | assert(row.empty); 721 | assertThrown!AssertError(row.front); 722 | assertThrown!AssertError(row.back); 723 | assertThrown!AssertError(row.popFront); 724 | assertThrown!AssertError(row.popBack); 725 | assertThrown!AssertError(row[""]); 726 | assertThrown!AssertError(row.peek!long(0)); 727 | } 728 | 729 | unittest // Peek 730 | { 731 | auto db = Database(":memory:"); 732 | db.run("CREATE TABLE test (value); 733 | INSERT INTO test VALUES (NULL); 734 | INSERT INTO test VALUES (42); 735 | INSERT INTO test VALUES (3.14); 736 | INSERT INTO test VALUES ('ABC'); 737 | INSERT INTO test VALUES (x'DEADBEEF');"); 738 | 739 | import std.math : isNaN; 740 | auto results = db.execute("SELECT * FROM test"); 741 | auto row = results.front; 742 | assert(row.peek!long(0) == 0); 743 | assert(row.peek!double(0) == 0); 744 | assert(row.peek!string(0) is null); 745 | assert(row.peek!Blob(0) is null); 746 | results.popFront(); 747 | row = results.front; 748 | assert(row.peek!long(0) == 42); 749 | assert(row.peek!double(0) == 42); 750 | assert(row.peek!string(0) == "42"); 751 | assert(row.peek!Blob(0) == cast(Blob) "42"); 752 | results.popFront(); 753 | row = results.front; 754 | assert(row.peek!long(0) == 3); 755 | assert(row.peek!double(0) == 3.14); 756 | assert(row.peek!string(0) == "3.14"); 757 | assert(row.peek!Blob(0) == cast(Blob) "3.14"); 758 | results.popFront(); 759 | row = results.front; 760 | assert(row.peek!long(0) == 0); 761 | assert(row.peek!double(0) == 0.0); 762 | assert(row.peek!string(0) == "ABC"); 763 | assert(row.peek!Blob(0) == cast(Blob) "ABC"); 764 | results.popFront(); 765 | row = results.front; 766 | assert(row.peek!long(0) == 0); 767 | assert(row.peek!double(0) == 0.0); 768 | assert(row.peek!string(0) == hexString!"DEADBEEF"); 769 | assert(row.peek!Blob(0) == cast(Blob) hexString!"DEADBEEF"); 770 | } 771 | 772 | unittest // Peeking NULL values 773 | { 774 | auto db = Database(":memory:"); 775 | db.run("CREATE TABLE test (val TEXT); 776 | INSERT INTO test (val) VALUES (NULL)"); 777 | 778 | auto results = db.execute("SELECT * FROM test"); 779 | assert(results.front.peek!bool(0) == false); 780 | assert(results.front.peek!long(0) == 0); 781 | assert(results.front.peek!double(0) == 0); 782 | assert(results.front.peek!string(0) is null); 783 | assert(results.front.peek!Blob(0) is null); 784 | } 785 | 786 | unittest // Row life-time 787 | { 788 | auto db = Database(":memory:"); 789 | auto row = db.execute("SELECT 1 AS one").front; 790 | assert(row[0].as!long == 1); 791 | assert(row["one"].as!long == 1); 792 | } 793 | 794 | unittest // PeekMode 795 | { 796 | auto db = Database(":memory:"); 797 | db.run("CREATE TABLE test (value); 798 | INSERT INTO test VALUES (x'01020304'); 799 | INSERT INTO test VALUES (x'0A0B0C0D');"); 800 | 801 | auto results = db.execute("SELECT * FROM test"); 802 | auto row = results.front; 803 | auto b1 = row.peek!(Blob, PeekMode.copy)(0); 804 | auto b2 = row.peek!(Blob, PeekMode.slice)(0); 805 | results.popFront(); 806 | row = results.front; 807 | auto b3 = row.peek!(Blob, PeekMode.slice)(0); 808 | auto b4 = row.peek!(Nullable!Blob, PeekMode.copy)(0); 809 | assert(b1 == cast(Blob) hexString!"01020304"); 810 | // assert(b2 != cast(Blob) x"01020304"); // PASS if SQLite reuses internal buffer 811 | // assert(b2 == cast(Blob) x"0A0B0C0D"); // PASS (idem) 812 | assert(b3 == cast(Blob) hexString!"0A0B0C0D"); 813 | assert(!b4.isNull && b4 == cast(Blob) hexString!"0A0B0C0D"); 814 | } 815 | 816 | unittest // Row random-access range interface 817 | { 818 | import std.array : front, popFront; 819 | 820 | auto db = Database(":memory:"); 821 | db.run("CREATE TABLE test (a INTEGER, b INTEGER, c INTEGER, d INTEGER); 822 | INSERT INTO test VALUES (1, 2, 3, 4); 823 | INSERT INTO test VALUES (5, 6, 7, 8);"); 824 | 825 | { 826 | auto results = db.execute("SELECT * FROM test"); 827 | auto values = [1, 2, 3, 4, 5, 6, 7, 8]; 828 | foreach (row; results) 829 | { 830 | while (!row.empty) 831 | { 832 | assert(row.front.as!int == values.front); 833 | row.popFront(); 834 | values.popFront(); 835 | } 836 | } 837 | } 838 | 839 | { 840 | auto results = db.execute("SELECT * FROM test"); 841 | auto values = [4, 3, 2, 1, 8, 7, 6, 5]; 842 | foreach (row; results) 843 | { 844 | while (!row.empty) 845 | { 846 | assert(row.back.as!int == values.front); 847 | row.popBack(); 848 | values.popFront(); 849 | } 850 | } 851 | } 852 | 853 | { 854 | auto row = db.execute("SELECT * FROM test").front; 855 | row.popFront(); 856 | auto copy = row.save(); 857 | row.popFront(); 858 | assert(row.front.as!int == 3); 859 | assert(copy.front.as!int == 2); 860 | } 861 | } 862 | 863 | unittest // ColumnData.init 864 | { 865 | import core.exception : AssertError; 866 | ColumnData data; 867 | assertThrown!AssertError(data.type); 868 | assertThrown!AssertError(data.as!string); 869 | } 870 | 871 | unittest // ColumnData-compatible types 872 | { 873 | import std.meta : AliasSeq; 874 | 875 | alias AllCases = AliasSeq!(bool, true, int, int.max, float, float.epsilon, 876 | real, 42.0L, string, "おはよう!", const(ubyte)[], [0x00, 0xFF], 877 | string, "", Nullable!byte, 42); 878 | 879 | void test(Cases...)() 880 | { 881 | auto cd = ColumnData(Cases[1]); 882 | assert(cd.as!(Cases[0]) == Cases[1]); 883 | static if (Cases.length > 2) 884 | test!(Cases[2..$])(); 885 | } 886 | 887 | test!AllCases(); 888 | } 889 | 890 | unittest // ColumnData.toString 891 | { 892 | auto db = Database(":memory:"); 893 | auto rc = db.execute("SELECT 42, 3.14, 'foo_bar', x'00FF', NULL").cached; 894 | assert("%(%s%)".format(rc) == "[42, 3.14, foo_bar, [0, 255], null]"); 895 | } 896 | 897 | unittest // CachedResults copies 898 | { 899 | auto db = Database(":memory:"); 900 | db.run("CREATE TABLE test (msg TEXT); 901 | INSERT INTO test (msg) VALUES ('ABC')"); 902 | 903 | static getdata(Database db) 904 | { 905 | return db.execute("SELECT * FROM test").cached; 906 | } 907 | 908 | auto data = getdata(db); 909 | assert(data.length == 1); 910 | assert(data[0][0].as!string == "ABC"); 911 | } 912 | 913 | unittest // UTF-8 914 | { 915 | auto db = Database(":memory:"); 916 | bool ran = false; 917 | db.run("SELECT '\u2019\u2019';", (ResultRange r) { 918 | assert(r.oneValue!string == "\u2019\u2019"); 919 | ran = true; 920 | return true; 921 | }); 922 | assert(ran); 923 | } 924 | 925 | unittest // loadExtension failure test 926 | { 927 | import std.exception : collectExceptionMsg; 928 | auto db = Database(":memory:"); 929 | auto msg = collectExceptionMsg(db.loadExtension("foobar")); 930 | assert(msg.canFind("(not authorized)")); 931 | } 932 | -------------------------------------------------------------------------------- /source/d2sqlite3/database.d: -------------------------------------------------------------------------------- 1 | /++ 2 | Managing SQLite3 database connections. 3 | 4 | Authors: 5 | Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) 6 | 7 | Copyright: 8 | Copyright 2011-18 Nicolas Sicard. 9 | 10 | License: 11 | $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 | +/ 13 | module d2sqlite3.database; 14 | 15 | import d2sqlite3.statement; 16 | import d2sqlite3.results; 17 | import d2sqlite3.sqlite3; 18 | import d2sqlite3.internal.memory; 19 | import d2sqlite3.internal.util; 20 | 21 | import std.conv : text, to; 22 | import std.exception : enforce; 23 | import std.string : format, fromStringz, toStringz; 24 | import std.typecons : Nullable; 25 | 26 | import core.stdc.stdlib : free; 27 | 28 | /// Set _UnlockNotify version if compiled with SqliteEnableUnlockNotify or SqliteFakeUnlockNotify 29 | version (SqliteEnableUnlockNotify) version = _UnlockNotify; 30 | else version (SqliteFakeUnlockNotify) version = _UnlockNotify; 31 | 32 | /// Type for the internal representation of blobs 33 | alias Blob = immutable(ubyte)[]; 34 | 35 | /// SQLite type codes 36 | enum SqliteType 37 | { 38 | INTEGER = SQLITE_INTEGER, /// 39 | FLOAT = SQLITE_FLOAT, /// 40 | TEXT = SQLITE3_TEXT, /// 41 | BLOB = SQLITE_BLOB, /// 42 | NULL = SQLITE_NULL /// 43 | } 44 | 45 | /++ 46 | A caracteristic of user-defined functions or aggregates. 47 | +/ 48 | enum Deterministic 49 | { 50 | /++ 51 | The returned value is the same if the function is called with the same parameters. 52 | +/ 53 | yes = 0x800, 54 | 55 | /++ 56 | The returned value can vary even if the function is called with the same parameters. 57 | +/ 58 | no = 0 59 | } 60 | 61 | /++ 62 | An database connection. 63 | 64 | This struct is a reference-counted wrapper around a `sqlite3*` pointer. 65 | +/ 66 | struct Database 67 | { 68 | import std.traits : isFunctionPointer, isDelegate; 69 | import std.typecons : RefCounted, RefCountedAutoInitialize; 70 | 71 | private: 72 | struct Payload 73 | { 74 | sqlite3* handle; 75 | void* updateHook; 76 | void* commitHook; 77 | void* rollbackHook; 78 | void* progressHandler; 79 | void* traceCallback; 80 | void* profileCallback; 81 | version (_UnlockNotify) IUnlockNotifyHandler unlockNotifyHandler; 82 | debug string filename; 83 | 84 | this(sqlite3* handle) nothrow 85 | { 86 | this.handle = handle; 87 | } 88 | 89 | ~this() nothrow 90 | { 91 | debug ensureNotInGC!Database(filename); 92 | free(updateHook); 93 | free(commitHook); 94 | free(rollbackHook); 95 | free(progressHandler); 96 | free(traceCallback); 97 | free(profileCallback); 98 | 99 | if (!handle) 100 | return; 101 | sqlite3_progress_handler(handle, 0, null, null); 102 | sqlite3_close(handle); 103 | } 104 | } 105 | 106 | RefCounted!(Payload, RefCountedAutoInitialize.no) p; 107 | 108 | void check(int result) 109 | { 110 | enforce(result == SQLITE_OK, new SqliteException(errmsg(p.handle), result)); 111 | } 112 | 113 | public: 114 | /++ 115 | Opens a database connection. 116 | 117 | Params: 118 | path = The path to the database file. In recent versions of SQLite, the path can be 119 | an URI with options. 120 | 121 | flags = Options flags. 122 | 123 | See_Also: $(LINK http://www.sqlite.org/c3ref/open.html) to know how to use the flags 124 | parameter or to use path as a file URI if the current configuration allows it. 125 | +/ 126 | this(string path, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) 127 | { 128 | sqlite3* hdl; 129 | auto result = sqlite3_open_v2(path.toStringz, &hdl, flags, null); 130 | enforce(result == SQLITE_OK, new SqliteException(hdl ? errmsg(hdl) : "Error opening the database", result)); 131 | p = Payload(hdl); 132 | debug p.filename = path; 133 | } 134 | 135 | /++ 136 | Explicitly closes the database connection. 137 | 138 | After a successful call to `close()`, using the database connection or one of its prepared 139 | statement is an error. The `Database` object is destroyed and cannot be used any more. 140 | +/ 141 | void close() 142 | { 143 | auto result = sqlite3_close(p.handle); 144 | enforce(result == SQLITE_OK, new SqliteException(errmsg(p.handle), result)); 145 | p.handle = null; 146 | destroy(p); 147 | } 148 | 149 | /++ 150 | Gets the SQLite internal _handle of the database connection. 151 | +/ 152 | sqlite3* handle() @property nothrow 153 | { 154 | return p.handle; 155 | } 156 | 157 | /++ 158 | Gets the path associated with an attached database. 159 | 160 | Params: 161 | database = The name of an attached database. 162 | 163 | Returns: The absolute path of the attached database. 164 | If there is no attached database, or if database is a temporary or 165 | in-memory database, then null is returned. 166 | +/ 167 | string attachedFilePath(string database = "main") 168 | { 169 | assert(p.handle); 170 | return sqlite3_db_filename(p.handle, database.toStringz).to!string; 171 | } 172 | 173 | /++ 174 | Gets the read-only status of an attached database. 175 | 176 | Params: 177 | database = The name of an attached database. 178 | +/ 179 | bool isReadOnly(string database = "main") 180 | { 181 | assert(p.handle); 182 | immutable ret = sqlite3_db_readonly(p.handle, database.toStringz); 183 | enforce(ret >= 0, new SqliteException("Database not found: %s".format(database), ret)); 184 | return ret == 1; 185 | } 186 | 187 | /++ 188 | Gets metadata for a specific table column of an attached database. 189 | 190 | Params: 191 | table = The name of the table. 192 | 193 | column = The name of the column. 194 | 195 | database = The name of a database attached. If null, then all attached databases 196 | are searched for the table using the same algorithm used by the database engine 197 | to resolve unqualified table references. 198 | +/ 199 | TableColumnMetadata tableColumnMetadata(string table, string column, string database = "main") 200 | { 201 | TableColumnMetadata data; 202 | char* pzDataType, pzCollSeq; 203 | int notNull, primaryKey, autoIncrement; 204 | assert(p.handle); 205 | check(sqlite3_table_column_metadata(p.handle, database.toStringz, table.toStringz, 206 | column.toStringz, &pzDataType, &pzCollSeq, ¬Null, &primaryKey, &autoIncrement)); 207 | data.declaredTypeName = pzDataType.to!string; 208 | data.collationSequenceName = pzCollSeq.to!string; 209 | data.isNotNull = cast(bool) notNull; 210 | data.isPrimaryKey = cast(bool) primaryKey; 211 | data.isAutoIncrement = cast(bool) autoIncrement; 212 | return data; 213 | } 214 | 215 | /++ 216 | Executes a single SQL statement and returns the results directly. 217 | 218 | It's the equivalent of `prepare(sql).execute()`. 219 | Or when used with args the equivalent of: 220 | --- 221 | auto stm = prepare(sql); 222 | stm.bindAll(args); 223 | stm.execute(); 224 | --- 225 | 226 | The results become undefined when the Database goes out of scope and is destroyed. 227 | 228 | Params: 229 | sql = The code of the SQL statement. 230 | args = Optional arguments to bind to the SQL statement. 231 | +/ 232 | ResultRange execute(Args...)(string sql, Args args) 233 | { 234 | auto stm = prepare(sql); 235 | static if (Args.length) stm.bindAll(args); 236 | return stm.execute(); 237 | } 238 | /// 239 | unittest 240 | { 241 | auto db = Database(":memory:"); 242 | db.execute("CREATE TABLE test (val INTEGER)"); 243 | db.execute("INSERT INTO test (val) VALUES (:v)", 1); 244 | assert(db.execute("SELECT val FROM test WHERE val=:v", 1).oneValue!int == 1); 245 | } 246 | 247 | /++ 248 | Runs an SQL script that can contain multiple statements. 249 | 250 | Params: 251 | script = The code of the SQL script. 252 | 253 | dg = A delegate to call for each statement to handle the results. The passed 254 | ResultRange will be empty if a statement doesn't return rows. If the delegate 255 | return false, the execution is aborted. 256 | +/ 257 | void run(string script, bool delegate(ResultRange) dg = null) 258 | { 259 | foreach (sql; script.byStatement) 260 | { 261 | auto stmt = prepare(sql); 262 | auto results = stmt.execute(); 263 | if (dg && !dg(results)) 264 | return; 265 | } 266 | } 267 | /// 268 | unittest 269 | { 270 | auto db = Database(":memory:"); 271 | db.run(`CREATE TABLE test1 (val INTEGER); 272 | CREATE TABLE test2 (val FLOAT); 273 | DROP TABLE test1; 274 | DROP TABLE test2;`); 275 | } 276 | 277 | /++ 278 | Prepares (compiles) a single SQL statement and returns it, so that it can be bound to 279 | values before execution. 280 | 281 | The statement becomes invalid if the Database goes out of scope and is destroyed. 282 | +/ 283 | Statement prepare(string sql) 284 | { 285 | return Statement(this, sql); 286 | } 287 | 288 | /// Convenience functions equivalent to an SQL statement. 289 | void begin() { execute("BEGIN"); } 290 | /// Ditto 291 | void commit() { execute("COMMIT"); } 292 | /// Ditto 293 | void rollback() { execute("ROLLBACK"); } 294 | 295 | /++ 296 | Returns the rowid of the last INSERT statement. 297 | +/ 298 | long lastInsertRowid() 299 | { 300 | assert(p.handle); 301 | return sqlite3_last_insert_rowid(p.handle); 302 | } 303 | 304 | /++ 305 | Gets the number of database rows that were changed, inserted or deleted by the most 306 | recently executed SQL statement. 307 | +/ 308 | int changes() @property nothrow 309 | { 310 | assert(p.handle); 311 | return sqlite3_changes(p.handle); 312 | } 313 | 314 | /++ 315 | Gets the number of database rows that were changed, inserted or deleted since the 316 | database was opened. 317 | +/ 318 | int totalChanges() @property nothrow 319 | { 320 | assert(p.handle); 321 | return sqlite3_total_changes(p.handle); 322 | } 323 | 324 | /++ 325 | Gets the SQLite error code of the last operation. 326 | +/ 327 | int errorCode() @property nothrow 328 | { 329 | return p.handle ? sqlite3_errcode(p.handle) : 0; 330 | } 331 | 332 | /++ 333 | Interrupts any pending database operations. 334 | 335 | It's safe to call this function from anouther thread. 336 | 337 | See_also: $(LINK http://www.sqlite.org/c3ref/interrupt.html). 338 | +/ 339 | void interrupt() 340 | { 341 | assert(p.handle); 342 | sqlite3_interrupt(p.handle); 343 | } 344 | 345 | /++ 346 | Sets a connection configuration option. 347 | 348 | See_Also: $(LINK http://www.sqlite.org/c3ref/db_config.html). 349 | +/ 350 | void config(Args...)(int code, Args args) 351 | { 352 | assert(p.handle); 353 | auto result = sqlite3_db_config(p.handle, code, args); 354 | enforce(result == SQLITE_OK, new SqliteException("Database configuration: error %s".format(result), result)); 355 | } 356 | 357 | /++ 358 | Enables or disables loading extensions. 359 | +/ 360 | void enableLoadExtensions(bool enable = true) 361 | { 362 | assert(p.handle); 363 | immutable ret = sqlite3_enable_load_extension(p.handle, enable); 364 | enforce(ret == SQLITE_OK, 365 | new SqliteException("Could not enable loading extensions.", ret)); 366 | } 367 | 368 | /++ 369 | Loads an extension. 370 | 371 | Params: 372 | path = The path of the extension file. 373 | 374 | entryPoint = The name of the entry point function. If null is passed, SQLite 375 | uses the name of the extension file as the entry point. 376 | +/ 377 | void loadExtension(string path, string entryPoint = null) 378 | { 379 | assert(p.handle); 380 | char* p_err; 381 | scope (failure) 382 | sqlite3_free(p_err); 383 | 384 | immutable ret = sqlite3_load_extension(p.handle, path.toStringz, entryPoint.toStringz, &p_err); 385 | enforce(ret == SQLITE_OK, new SqliteException( 386 | "Could not load extension: %s:%s (%s)".format(entryPoint, path, 387 | p_err !is null ? fromStringz(p_err) : "No additional info"), ret)); 388 | } 389 | 390 | /++ 391 | Creates and registers a new function in the database. 392 | 393 | If a function with the same name and the same arguments already exists, it is replaced 394 | by the new one. 395 | 396 | The memory associated with the function will be released when the database connection 397 | is closed. 398 | 399 | Params: 400 | name = The name that the function will have in the database. 401 | 402 | fun = a delegate or function that implements the function. $(D_PARAM fun) 403 | must satisfy the following criteria: 404 | $(UL 405 | $(LI It must not be variadic.) 406 | $(LI Its arguments must all have a type that is compatible with SQLite types: 407 | it must be a boolean or numeric type, a string, an array, `null`, 408 | or a `Nullable!T` where T is any of the previous types.) 409 | $(LI Its return value must also be of a compatible type.) 410 | ) 411 | or 412 | $(UL 413 | $(LI It must be a normal or type-safe variadic function where the arguments 414 | are of type `ColumnData`. In other terms, the signature of the function must be: 415 | `function(ColumnData[] args)` or `function(ColumnData[] args...)`) 416 | $(LI Its return value must be a boolean or numeric type, a string, an array, `null`, 417 | or a `Nullable!T` where T is any of the previous types.) 418 | ) 419 | Pass a `null` function pointer to delete the function from the database connection. 420 | 421 | det = Tells SQLite whether the result of the function is deterministic, i.e. if the 422 | result is the same when called with the same parameters. Recent versions of SQLite 423 | perform optimizations based on this. Set to `Deterministic.no` otherwise. 424 | 425 | See_Also: $(LINK http://www.sqlite.org/c3ref/create_function.html). 426 | +/ 427 | void createFunction(T)(string name, T fun, Deterministic det = Deterministic.yes) 428 | if (isFunctionPointer!T || isDelegate!T) 429 | { 430 | import std.meta : AliasSeq, staticMap, EraseAll; 431 | import std.traits : variadicFunctionStyle, Variadic, ParameterTypeTuple, 432 | ParameterDefaultValueTuple, ReturnType, Unqual; 433 | 434 | static assert(variadicFunctionStyle!(fun) == Variadic.no 435 | || is(ParameterTypeTuple!fun == AliasSeq!(ColumnData[])), 436 | "only type-safe variadic functions with ColumnData arguments are supported"); 437 | 438 | static if (is(ParameterTypeTuple!fun == AliasSeq!(ColumnData[]))) 439 | { 440 | extern(C) static nothrow 441 | void x_func(sqlite3_context* context, int argc, sqlite3_value** argv) 442 | { 443 | string name; 444 | try 445 | { 446 | import std.array : appender; 447 | auto args = appender!(ColumnData[]); 448 | 449 | foreach (i; 0 .. argc) 450 | { 451 | auto value = argv[i]; 452 | immutable type = sqlite3_value_type(value); 453 | 454 | final switch (type) 455 | { 456 | case SqliteType.INTEGER: 457 | args.put(ColumnData(getValue!long(value))); 458 | break; 459 | 460 | case SqliteType.FLOAT: 461 | args.put(ColumnData(getValue!double(value))); 462 | break; 463 | 464 | case SqliteType.TEXT: 465 | args.put(ColumnData(getValue!string(value))); 466 | break; 467 | 468 | case SqliteType.BLOB: 469 | args.put(ColumnData(getValue!Blob(value))); 470 | break; 471 | 472 | case SqliteType.NULL: 473 | args.put(ColumnData(null)); 474 | break; 475 | } 476 | } 477 | 478 | auto ptr = sqlite3_user_data(context); 479 | 480 | auto wrappedDelegate = delegateUnwrap!T(ptr); 481 | auto dlg = wrappedDelegate.dlg; 482 | name = wrappedDelegate.name; 483 | setResult(context, dlg(args.data)); 484 | } 485 | catch (Exception e) 486 | { 487 | sqlite3_result_error(context, "error in function %s(): %s" 488 | .nothrowFormat(name, e.msg).toStringz, -1); 489 | } 490 | } 491 | } 492 | else 493 | { 494 | static assert(!is(ReturnType!fun == void), "function must not return void"); 495 | 496 | alias PT = staticMap!(Unqual, ParameterTypeTuple!fun); 497 | alias PD = ParameterDefaultValueTuple!fun; 498 | 499 | extern (C) static nothrow 500 | void x_func(sqlite3_context* context, int argc, sqlite3_value** argv) 501 | { 502 | string name; 503 | try 504 | { 505 | // Get the deledate and its name 506 | auto ptr = sqlite3_user_data(context); 507 | auto wrappedDelegate = delegateUnwrap!T(ptr); 508 | auto dlg = wrappedDelegate.dlg; 509 | name = wrappedDelegate.name; 510 | 511 | enum maxArgc = PT.length; 512 | enum minArgc = PT.length - EraseAll!(void, PD).length; 513 | 514 | if (argc > maxArgc) 515 | { 516 | auto txt = ("too many arguments in function %s(), expecting at most %s" 517 | ).format(name, maxArgc); 518 | sqlite3_result_error(context, txt.toStringz, -1); 519 | } 520 | else if (argc < minArgc) 521 | { 522 | auto txt = ("too few arguments in function %s(), expecting at least %s" 523 | ).format(name, minArgc); 524 | sqlite3_result_error(context, txt.toStringz, -1); 525 | } 526 | else 527 | { 528 | PT args; 529 | foreach (i, type; PT) 530 | { 531 | if (i < argc) 532 | args[i] = getValue!type(argv[i]); 533 | else 534 | static if (is(typeof(PD[i]))) 535 | args[i] = PD[i]; 536 | } 537 | setResult(context, dlg(args)); 538 | } 539 | } 540 | catch (Exception e) 541 | { 542 | sqlite3_result_error(context, "error in function %s(): %s" 543 | .nothrowFormat(name, e.msg).toStringz, -1); 544 | } 545 | } 546 | } 547 | 548 | assert(name.length, "function has an empty name"); 549 | 550 | if (!fun) 551 | createFunction(name, null); 552 | 553 | assert(p.handle); 554 | check(sqlite3_create_function_v2(p.handle, name.toStringz, -1, 555 | SQLITE_UTF8 | det, delegateWrap(fun, name), &x_func, null, null, &free)); 556 | } 557 | /// 558 | unittest 559 | { 560 | string star(int count, string starSymbol = "*") 561 | { 562 | import std.range : repeat; 563 | import std.array : join; 564 | 565 | return starSymbol.repeat(count).join; 566 | } 567 | 568 | auto db = Database(":memory:"); 569 | db.createFunction("star", &star); 570 | assert(db.execute("SELECT star(5)").oneValue!string == "*****"); 571 | assert(db.execute("SELECT star(3, '♥')").oneValue!string == "♥♥♥"); 572 | } 573 | /// 574 | unittest 575 | { 576 | // The implementation of the new function 577 | string myList(ColumnData[] args) 578 | { 579 | import std.array : appender; 580 | import std.string : format, join; 581 | 582 | auto app = appender!(string[]); 583 | foreach (arg; args) 584 | { 585 | if (arg.type == SqliteType.TEXT) 586 | app.put(`"%s"`.format(arg)); 587 | else 588 | app.put("%s".format(arg)); 589 | } 590 | return app.data.join(", "); 591 | } 592 | 593 | auto db = Database(":memory:"); 594 | db.createFunction("my_list", &myList); 595 | auto list = db.execute("SELECT my_list(42, 3.14, 'text', NULL)").oneValue!string; 596 | assert(list == `42, 3.14, "text", null`); 597 | } 598 | 599 | /// Ditto 600 | void createFunction(T)(string name, T fun = null) 601 | if (is(T == typeof(null))) 602 | { 603 | assert(name.length, "function has an empty name"); 604 | assert(p.handle); 605 | check(sqlite3_create_function_v2(p.handle, name.toStringz, -1, SQLITE_UTF8, 606 | null, fun, null, null, null)); 607 | } 608 | 609 | /++ 610 | Creates and registers a new aggregate function in the database. 611 | 612 | Params: 613 | name = The name that the aggregate function will have in the database. 614 | 615 | agg = The struct of type T implementing the aggregate. T must implement 616 | at least these two methods: `accumulate()` and `result()`. 617 | Each parameter and the returned type of `accumulate()` and `result()` must be 618 | a boolean or numeric type, a string, an array, `null`, or a `Nullable!T` 619 | where T is any of the previous types. These methods cannot be variadic. 620 | 621 | det = Tells SQLite whether the result of the function is deterministic, i.e. if the 622 | result is the same when called with the same parameters. Recent versions of SQLite 623 | perform optimizations based on this. Set to `Deterministic.no` otherwise. 624 | 625 | See_Also: $(LINK http://www.sqlite.org/c3ref/create_function.html). 626 | +/ 627 | void createAggregate(T)(string name, T agg, Deterministic det = Deterministic.yes) 628 | { 629 | import std.meta : staticMap; 630 | import std.traits : isAggregateType, ReturnType, variadicFunctionStyle, Variadic, 631 | Unqual, ParameterTypeTuple; 632 | import core.stdc.stdlib : malloc; 633 | 634 | static assert(isAggregateType!T, 635 | T.stringof ~ " should be an aggregate type"); 636 | static assert(is(typeof(T.accumulate) == function), 637 | T.stringof ~ " should have a method named accumulate"); 638 | static assert(is(typeof(T.result) == function), 639 | T.stringof ~ " should have a method named result"); 640 | static assert(is(typeof({ 641 | alias RT = ReturnType!(T.result); 642 | setResult!RT(null, RT.init); 643 | })), T.stringof ~ ".result should return an SQLite-compatible type"); 644 | static assert(variadicFunctionStyle!(T.accumulate) == Variadic.no, 645 | "variadic functions are not supported"); 646 | static assert(variadicFunctionStyle!(T.result) == Variadic.no, 647 | "variadic functions are not supported"); 648 | 649 | alias PT = staticMap!(Unqual, ParameterTypeTuple!(T.accumulate)); 650 | alias RT = ReturnType!(T.result); 651 | 652 | static struct Context 653 | { 654 | T aggregate; 655 | string functionName; 656 | } 657 | 658 | extern(C) static nothrow 659 | void x_step(sqlite3_context* context, int /* argc */, sqlite3_value** argv) 660 | { 661 | auto ctx = cast(Context*) sqlite3_user_data(context); 662 | if (!ctx) 663 | { 664 | sqlite3_result_error_nomem(context); 665 | return; 666 | } 667 | 668 | PT args; 669 | try 670 | { 671 | foreach (i, type; PT) 672 | args[i] = getValue!type(argv[i]); 673 | 674 | ctx.aggregate.accumulate(args); 675 | } 676 | catch (Exception e) 677 | { 678 | sqlite3_result_error(context, "error in aggregate function %s(): %s" 679 | .nothrowFormat(ctx.functionName, e.msg).toStringz, -1); 680 | } 681 | } 682 | 683 | extern(C) static nothrow 684 | void x_final(sqlite3_context* context) 685 | { 686 | auto ctx = cast(Context*) sqlite3_user_data(context); 687 | if (!ctx) 688 | { 689 | sqlite3_result_error_nomem(context); 690 | return; 691 | } 692 | 693 | try 694 | { 695 | setResult(context, ctx.aggregate.result()); 696 | } 697 | catch (Exception e) 698 | { 699 | sqlite3_result_error(context, "error in aggregate function %s(): %s" 700 | .nothrowFormat(ctx.functionName, e.msg).toStringz, -1); 701 | } 702 | } 703 | 704 | static if (is(T == class) || is(T == Interface)) 705 | assert(agg, "Attempt to create an aggregate function from a null reference"); 706 | 707 | auto ctx = cast(Context*) malloc(Context.sizeof); 708 | ctx.aggregate = agg; 709 | ctx.functionName = name; 710 | 711 | assert(p.handle); 712 | check(sqlite3_create_function_v2(p.handle, name.toStringz, PT.length, SQLITE_UTF8 | det, 713 | cast(void*) ctx, null, &x_step, &x_final, &free)); 714 | } 715 | /// 716 | unittest // Aggregate creation 717 | { 718 | import std.array : Appender, join; 719 | 720 | // The implementation of the aggregate function 721 | struct Joiner 722 | { 723 | private 724 | { 725 | Appender!(string[]) stringList; 726 | string separator; 727 | } 728 | 729 | this(string separator) 730 | { 731 | this.separator = separator; 732 | } 733 | 734 | void accumulate(string word) 735 | { 736 | stringList.put(word); 737 | } 738 | 739 | string result() 740 | { 741 | return stringList.data.join(separator); 742 | } 743 | } 744 | 745 | auto db = Database(":memory:"); 746 | db.run("CREATE TABLE test (word TEXT); 747 | INSERT INTO test VALUES ('My'); 748 | INSERT INTO test VALUES ('cat'); 749 | INSERT INTO test VALUES ('is'); 750 | INSERT INTO test VALUES ('black');"); 751 | 752 | db.createAggregate("dash_join", Joiner("-")); 753 | auto text = db.execute("SELECT dash_join(word) FROM test").oneValue!string; 754 | assert(text == "My-cat-is-black"); 755 | } 756 | 757 | /++ 758 | Creates and registers a collation function in the database. 759 | 760 | Params: 761 | name = The name that the function will have in the database. 762 | 763 | fun = a delegate or function that implements the collation. The function $(D_PARAM fun) 764 | must be `nothrow`` and satisfy these criteria: 765 | $(UL 766 | $(LI Takes two string arguments (s1 and s2). These two strings are slices of C-style strings 767 | that SQLite manages internally, so there is no guarantee that they are still valid 768 | when the function returns.) 769 | $(LI Returns an integer (ret).) 770 | $(LI If s1 is less than s2, ret < 0.) 771 | $(LI If s1 is equal to s2, ret == 0.) 772 | $(LI If s1 is greater than s2, ret > 0.) 773 | $(LI If s1 is equal to s2, then s2 is equal to s1.) 774 | $(LI If s1 is equal to s2 and s2 is equal to s3, then s1 is equal to s3.) 775 | $(LI If s1 is less than s2, then s2 is greater than s1.) 776 | $(LI If s1 is less than s2 and s2 is less than s3, then s1 is less than s3.) 777 | ) 778 | 779 | See_Also: $(LINK http://www.sqlite.org/lang_aggfunc.html) 780 | +/ 781 | void createCollation(T)(string name, T fun) 782 | if (isFunctionPointer!T || isDelegate!T) 783 | { 784 | import std.traits : isImplicitlyConvertible, functionAttributes, FunctionAttribute, 785 | ParameterTypeTuple, isSomeString, ReturnType; 786 | 787 | static assert(isImplicitlyConvertible!(typeof(fun("a", "b")), int), 788 | "the collation function has a wrong signature"); 789 | 790 | static assert(functionAttributes!(T) & FunctionAttribute.nothrow_, 791 | "only nothrow functions are allowed as collations"); 792 | 793 | alias PT = ParameterTypeTuple!fun; 794 | static assert(isSomeString!(PT[0]), 795 | "the first argument of function " ~ name ~ " should be a string"); 796 | static assert(isSomeString!(PT[1]), 797 | "the second argument of function " ~ name ~ " should be a string"); 798 | static assert(isImplicitlyConvertible!(ReturnType!fun, int), 799 | "function " ~ name ~ " should return a value convertible to an int"); 800 | 801 | extern (C) static nothrow 802 | int x_compare(void* ptr, int n1, const(void)* str1, int n2, const(void)* str2) 803 | { 804 | static string slice(const(void)* str, int n) nothrow 805 | { 806 | // The string data is owned by SQLite, so it should be safe 807 | // to take a slice of it. 808 | return str ? (cast(immutable) (cast(const(char)*) str)[0 .. n]) : null; 809 | } 810 | 811 | return delegateUnwrap!T(ptr).dlg(slice(str1, n1), slice(str2, n2)); 812 | } 813 | 814 | assert(p.handle); 815 | auto dgw = delegateWrap(fun, name); 816 | auto result = sqlite3_create_collation_v2(p.handle, name.toStringz, SQLITE_UTF8, 817 | delegateWrap(fun, name), &x_compare, &free); 818 | if (result != SQLITE_OK) 819 | { 820 | free(dgw); 821 | throw new SqliteException(errmsg(p.handle), result); 822 | } 823 | } 824 | /// 825 | unittest // Collation creation 826 | { 827 | // The implementation of the collation 828 | int my_collation(string s1, string s2) nothrow 829 | { 830 | import std.uni : icmp; 831 | import std.exception : assumeWontThrow; 832 | 833 | return assumeWontThrow(icmp(s1, s2)); 834 | } 835 | 836 | auto db = Database(":memory:"); 837 | db.createCollation("my_coll", &my_collation); 838 | db.run("CREATE TABLE test (word TEXT); 839 | INSERT INTO test (word) VALUES ('straße'); 840 | INSERT INTO test (word) VALUES ('strasses');"); 841 | 842 | auto word = db.execute("SELECT word FROM test ORDER BY word COLLATE my_coll") 843 | .oneValue!string; 844 | assert(word == "straße"); 845 | } 846 | 847 | /++ 848 | Registers a delegate of type `UpdateHookDelegate` as the database's update hook. 849 | 850 | Any previously set hook is released. Pass `null` to disable the callback. 851 | 852 | See_Also: $(LINK http://www.sqlite.org/c3ref/commit_hook.html). 853 | +/ 854 | void setUpdateHook(UpdateHookDelegate updateHook) 855 | { 856 | extern(C) static nothrow 857 | void callback(void* ptr, int type, const(char)* dbName, const(char)* tableName, long rowid) 858 | { 859 | WrappedDelegate!UpdateHookDelegate* dg; 860 | dg = delegateUnwrap!UpdateHookDelegate(ptr); 861 | dg.dlg(type, dbName.to!string, tableName.to!string, rowid); 862 | } 863 | 864 | free(p.updateHook); 865 | p.updateHook = delegateWrap(updateHook); 866 | assert(p.handle); 867 | sqlite3_update_hook(p.handle, &callback, p.updateHook); 868 | } 869 | 870 | /++ 871 | Registers a delegate of type `CommitHookDelegate` as the database's commit hook. 872 | Any previously set hook is released. 873 | 874 | Params: 875 | commitHook = A delegate that should return a non-zero value 876 | if the operation must be rolled back, or 0 if it can commit. 877 | Pass `null` to disable the callback. 878 | 879 | See_Also: $(LINK http://www.sqlite.org/c3ref/commit_hook.html). 880 | +/ 881 | void setCommitHook(CommitHookDelegate commitHook) 882 | { 883 | extern(C) static nothrow 884 | int callback(void* ptr) 885 | { 886 | auto dlg = delegateUnwrap!CommitHookDelegate(ptr).dlg; 887 | return dlg(); 888 | } 889 | 890 | free(p.commitHook); 891 | p.commitHook = delegateWrap(commitHook); 892 | assert(p.handle); 893 | sqlite3_commit_hook(p.handle, &callback, p.commitHook); 894 | } 895 | 896 | /++ 897 | Registers a delegate of type `RoolbackHookDelegate` as the database's rollback hook. 898 | 899 | Any previously set hook is released. 900 | Pass `null` to disable the callback. 901 | 902 | See_Also: $(LINK http://www.sqlite.org/c3ref/commit_hook.html). 903 | +/ 904 | void setRollbackHook(RoolbackHookDelegate rollbackHook) 905 | { 906 | extern(C) static nothrow 907 | void callback(void* ptr) 908 | { 909 | auto dlg = delegateUnwrap!RoolbackHookDelegate(ptr).dlg; 910 | dlg(); 911 | } 912 | 913 | free(p.rollbackHook); 914 | p.rollbackHook = delegateWrap(rollbackHook); 915 | assert(p.handle); 916 | sqlite3_rollback_hook(p.handle, &callback, p.rollbackHook); 917 | } 918 | 919 | /++ 920 | Registers a delegate of type `ProgressHandlerDelegate` as the progress handler. 921 | 922 | Any previously set handler is released. 923 | Pass `null` to disable the callback. 924 | 925 | Params: 926 | pace = The approximate number of virtual machine instructions that are 927 | evaluated between successive invocations of the handler. 928 | 929 | progressHandler = A delegate that should return 0 if the operation can continue 930 | or another value if it must be aborted. 931 | 932 | See_Also: $(LINK http://www.sqlite.org/c3ref/progress_handler.html). 933 | +/ 934 | void setProgressHandler(int pace, ProgressHandlerDelegate progressHandler) 935 | { 936 | extern(C) static nothrow 937 | int callback(void* ptr) 938 | { 939 | auto dlg = delegateUnwrap!ProgressHandlerDelegate(ptr).dlg; 940 | return dlg(); 941 | } 942 | 943 | free(p.progressHandler); 944 | p.progressHandler = delegateWrap(progressHandler); 945 | assert(p.handle); 946 | sqlite3_progress_handler(p.handle, pace, &callback, p.progressHandler); 947 | } 948 | 949 | /++ 950 | Registers a delegate of type `TraceCallbackDelegate` as the trace callback. 951 | 952 | Any previously set profile or trace callback is released. 953 | Pass `null` to disable the callback. 954 | 955 | The string parameter that is passed to the callback is the SQL text of the statement being 956 | executed. 957 | 958 | See_Also: $(LINK http://www.sqlite.org/c3ref/profile.html). 959 | +/ 960 | void setTraceCallback(TraceCallbackDelegate traceCallback) 961 | { 962 | extern(C) static nothrow 963 | void callback(void* ptr, const(char)* str) 964 | { 965 | auto dlg = delegateUnwrap!TraceCallbackDelegate(ptr).dlg; 966 | dlg(str.to!string); 967 | } 968 | 969 | free(p.traceCallback); 970 | p.traceCallback = delegateWrap(traceCallback); 971 | assert(p.handle); 972 | sqlite3_trace(p.handle, &callback, p.traceCallback); 973 | } 974 | 975 | /++ 976 | Registers a delegate of type `ProfileCallbackDelegate` as the profile callback. 977 | 978 | Any previously set profile or trace callback is released. 979 | Pass `null` to disable the callback. 980 | 981 | The string parameter that is passed to the callback is the SQL text of the statement being 982 | executed. The time unit is defined in SQLite's documentation as nanoseconds (subject to change, 983 | as the functionality is experimental). 984 | 985 | See_Also: $(LINK http://www.sqlite.org/c3ref/profile.html). 986 | +/ 987 | void setProfileCallback(ProfileCallbackDelegate profileCallback) 988 | { 989 | extern(C) static nothrow 990 | void callback(void* ptr, const(char)* str, sqlite3_uint64 time) 991 | { 992 | auto dlg = delegateUnwrap!ProfileCallbackDelegate(ptr).dlg; 993 | dlg(str.to!string, time); 994 | } 995 | 996 | free(p.profileCallback); 997 | p.profileCallback = delegateWrap(profileCallback); 998 | assert(p.handle); 999 | sqlite3_profile(p.handle, &callback, p.profileCallback); 1000 | } 1001 | 1002 | version (_UnlockNotify) 1003 | { 1004 | /++ 1005 | Registers a `IUnlockNotifyHandler` used to handle database locks. 1006 | 1007 | When running in shared-cache mode, a database operation may fail with an SQLITE_LOCKED error if 1008 | the required locks on the shared-cache or individual tables within the shared-cache cannot be obtained. 1009 | See SQLite Shared-Cache Mode for a description of shared-cache locking. 1010 | This API may be used to register a callback that SQLite will invoke when the connection currently 1011 | holding the required lock relinquishes it. 1012 | This API can be used only if the SQLite library was compiled with the `SQLITE_ENABLE_UNLOCK_NOTIFY` 1013 | C-preprocessor symbol defined. 1014 | 1015 | See_Also: $(LINK http://sqlite.org/c3ref/unlock_notify.html). 1016 | 1017 | Parameters: 1018 | unlockNotifyHandler - custom handler used to control the unlocking mechanism 1019 | +/ 1020 | void setUnlockNotifyHandler(IUnlockNotifyHandler unlockNotifyHandler) 1021 | { 1022 | p.unlockNotifyHandler = unlockNotifyHandler; 1023 | } 1024 | 1025 | /// Setup and waits for unlock notify using the provided `IUnlockNotifyHandler` 1026 | package (d2sqlite3) auto waitForUnlockNotify() 1027 | { 1028 | if (p.unlockNotifyHandler is null) return SQLITE_LOCKED; 1029 | 1030 | version (SqliteEnableUnlockNotify) 1031 | { 1032 | extern(C) static nothrow 1033 | void callback(void** ntfPtr, int nPtr) 1034 | { 1035 | for (int i=0; i Duration.zero); } 1241 | do 1242 | { 1243 | maxDuration = max; 1244 | } 1245 | 1246 | /// Blocks for some time to retry the statement 1247 | void waitOne() 1248 | { 1249 | import core.thread : Thread; 1250 | import std.random : uniform; 1251 | 1252 | if (!sw.running) sw.start; 1253 | 1254 | Thread.sleep(uniform(50, 100).msecs); 1255 | 1256 | if (sw.peek > maxDuration) 1257 | { 1258 | sw.stop; 1259 | res = SQLITE_LOCKED; 1260 | } 1261 | else res = SQLITE_OK; 1262 | } 1263 | 1264 | /// Resets the handler for the next use 1265 | void reset() 1266 | { 1267 | res = SQLITE_LOCKED; 1268 | sw.reset(); 1269 | } 1270 | 1271 | /// Result after wait is finished 1272 | @property int result() const 1273 | out (result) { assert(result == SQLITE_OK || result == SQLITE_LOCKED); } 1274 | do 1275 | { 1276 | return res; 1277 | } 1278 | } 1279 | } 1280 | 1281 | unittest 1282 | { 1283 | import core.time : Duration, msecs; 1284 | 1285 | /++ 1286 | Tests the unlock notify facility. 1287 | Params: 1288 | delay - time to wait in the transaction to block the other one 1289 | expected - expected result (can be used to test timeout when fake unlock notify is used) 1290 | +/ 1291 | void testUnlockNotify(Duration delay = 500.msecs, int expected = 3) 1292 | { 1293 | import core.thread : Thread; 1294 | import core.time : msecs, seconds; 1295 | import std.concurrency : spawn; 1296 | 1297 | static void test(int n, Duration delay) 1298 | { 1299 | auto db = Database("file::memory:?cache=shared", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI | SQLITE_OPEN_MEMORY); 1300 | db.setUnlockNotifyHandler = new UnlockNotifyHandler(); 1301 | db.execute("BEGIN IMMEDIATE"); 1302 | Thread.sleep(delay); 1303 | db.execute("INSERT INTO foo (bar) VALUES (?)", n); 1304 | db.commit(); 1305 | } 1306 | 1307 | auto db = Database("file::memory:?cache=shared", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI | SQLITE_OPEN_MEMORY); 1308 | db.execute(`CREATE TABLE foo (bar INTEGER);`); 1309 | 1310 | spawn(&test, 1, delay); 1311 | Thread.sleep(100.msecs); 1312 | spawn(&test, 2, delay); 1313 | Thread.sleep(2*delay + 100.msecs); 1314 | assert(db.execute("SELECT sum(bar) FROM foo").oneValue!int == expected, format!"%s != %s"(db.execute("SELECT sum(bar) FROM foo").oneValue!int, expected)); 1315 | } 1316 | 1317 | testUnlockNotify(); 1318 | version (SqliteFakeUnlockNotify) testUnlockNotify(1500.msecs, 1); //timeout test 1319 | } 1320 | } 1321 | 1322 | /++ 1323 | Exception thrown when SQLite functions return an error. 1324 | +/ 1325 | class SqliteException : Exception 1326 | { 1327 | /++ 1328 | The _code of the error that raised the exception 1329 | +/ 1330 | int code; 1331 | 1332 | /++ 1333 | The SQL code that raised the exception, if applicable. 1334 | +/ 1335 | string sql; 1336 | 1337 | private this(string msg, string sql, int code, 1338 | string file = __FILE__, size_t line = __LINE__, Throwable next = null) 1339 | @safe pure nothrow @nogc 1340 | { 1341 | this.sql = sql; 1342 | this.code = code; 1343 | super(msg, file, line, next); 1344 | } 1345 | 1346 | package(d2sqlite3): 1347 | this(string msg, int code, string sql = null, 1348 | string file = __FILE__, size_t line = __LINE__, Throwable next = null) 1349 | @safe pure nothrow 1350 | { 1351 | this(text("error ", code, ": ", msg), sql, code, file, line, next); 1352 | } 1353 | } 1354 | --------------------------------------------------------------------------------