diff --git a/.claude/skills/sqlitecpp-coding-standards/SKILL.md b/.claude/skills/sqlitecpp-coding-standards/SKILL.md index 159fc12a..15bc3b36 100644 --- a/.claude/skills/sqlitecpp-coding-standards/SKILL.md +++ b/.claude/skills/sqlitecpp-coding-standards/SKILL.md @@ -20,6 +20,8 @@ description: SQLiteCpp coding standards and API rules for core edits, public hea ## Documentation and style - Doxygen required for public API (`@brief`, `@param`, `@return`, `@throw`). - ASCII only, 4 spaces, Allman braces, max 120 chars, LF line endings, final newline. +- LF line endings are enforced repo-wide by `.gitattributes` (`* text=auto eol=lf`), matching + `.editorconfig` (`end_of_line = lf`); never commit CRLF. Run `git add --renormalize .` if a file drifts. - Use `#pragma once` in headers. ## Naming conventions diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..7a0ae4f6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Enforce LF line endings for all text files across platforms. +# Matches .editorconfig (end_of_line = lf): git normalizes on commit/checkout +# regardless of the contributor OS, editor, or tooling. +* text=auto eol=lf + +# Known binary assets: never normalize or diff these. +*.db3 binary +*.png binary diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index cb7947dc..40019067 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,42 +1,42 @@ -# SQLiteCpp LLM Coding Guide - -## Non-Negotiables (MUST Follow) -- RAII only. Acquire in constructors, release in destructors. -- NEVER throw in destructors. Use `SQLITECPP_ASSERT()` instead. -- Errors: use `SQLite::Exception` for throwing APIs; `tryExec()`, `tryExecuteStep()`, `tryReset()` return SQLite codes. -- C++11 only in core library. C++14 only in `VariadicBind.h` and `ExecuteMany.h`. -- Public API headers must NOT include `sqlite3.h`. Use `SQLite::OPEN_*` flags from `Database.h`. -- Export public API with `SQLITECPP_API` from `SQLiteCppExport.h`. -- Threading: one `Database`/`Statement`/`Column` per thread. -- Public API must have Doxygen: `@brief`, `@param`, `@return`, `@throw`. -- Tests required for new functionality in `tests/`. -- Portability: Windows, Linux, macOS. -- Style: ASCII only, 4 spaces, Allman braces, max 120 chars, LF line endings, final newline, `#pragma once`. - -## Workflow (Common Tasks) -### Add a Method -1. Declare in `include/SQLiteCpp/.h` with Doxygen. -2. Implement in `src/.cpp`. -3. Add tests in `tests/_test.cpp`. -4. Update `CHANGELOG.md`. - -### Add a Class -1. Create `include/SQLiteCpp/NewClass.h` and `src/NewClass.cpp`. -2. Add to `CMakeLists.txt` (`SQLITECPP_SRC` and `SQLITECPP_INC`). -3. Add to `meson.build`. -4. Include in `SQLiteCpp.h` if public API. -5. Create `tests/NewClass_test.cpp`. -6. Add test to `CMakeLists.txt` and `meson.build`. - -### Git Workflow (GitHub Issues) -**When to create a new branch:** -- User explicitly mentions working on a task, issue, or feature (e.g., "work on issue #123", "implement feature X") -- User references a GitHub issue number - -**Before starting work:** -1. Run `git status` to check current branch. -2. If on `master` or wrong branch, create a task-specific branch from `master`. - +# SQLiteCpp LLM Coding Guide + +## Non-Negotiables (MUST Follow) +- RAII only. Acquire in constructors, release in destructors. +- NEVER throw in destructors. Use `SQLITECPP_ASSERT()` instead. +- Errors: use `SQLite::Exception` for throwing APIs; `tryExec()`, `tryExecuteStep()`, `tryReset()` return SQLite codes. +- C++11 only in core library. C++14 only in `VariadicBind.h` and `ExecuteMany.h`. +- Public API headers must NOT include `sqlite3.h`. Use `SQLite::OPEN_*` flags from `Database.h`. +- Export public API with `SQLITECPP_API` from `SQLiteCppExport.h`. +- Threading: one `Database`/`Statement`/`Column` per thread. +- Public API must have Doxygen: `@brief`, `@param`, `@return`, `@throw`. +- Tests required for new functionality in `tests/`. +- Portability: Windows, Linux, macOS. +- Style: ASCII only, 4 spaces, Allman braces, max 120 chars, LF line endings, final newline, `#pragma once`. + +## Workflow (Common Tasks) +### Add a Method +1. Declare in `include/SQLiteCpp/.h` with Doxygen. +2. Implement in `src/.cpp`. +3. Add tests in `tests/_test.cpp`. +4. Update `CHANGELOG.md`. + +### Add a Class +1. Create `include/SQLiteCpp/NewClass.h` and `src/NewClass.cpp`. +2. Add to `CMakeLists.txt` (`SQLITECPP_SRC` and `SQLITECPP_INC`). +3. Add to `meson.build`. +4. Include in `SQLiteCpp.h` if public API. +5. Create `tests/NewClass_test.cpp`. +6. Add test to `CMakeLists.txt` and `meson.build`. + +### Git Workflow (GitHub Issues) +**When to create a new branch:** +- User explicitly mentions working on a task, issue, or feature (e.g., "work on issue #123", "implement feature X") +- User references a GitHub issue number + +**Before starting work:** +1. Run `git status` to check current branch. +2. If on `master` or wrong branch, create a task-specific branch from `master`. + **Branch naming:** `--` or `-` - `123-fix-short-description` for bug fixes - `123-feature-short-description` for new features @@ -44,192 +44,192 @@ (issue ID is optional; do not use `000-`) **If user only requests a branch:** create it and stop (no file changes). - -**Commits:** -- Imperative mood, ~50 char first line, body wrapped at 72 chars. -- Reference issue: `Closes #123` or `Fixes #123`. - -```bash -git fetch origin -git checkout -b 123-fix-short-description origin/master -``` - -## Common Pitfalls -- `bind()` is 1-based; `getColumn()` is 0-based. -- Reusing `Statement` requires `reset()` and `clearBindings()`. -- `bindNoCopy()` only if data lifetime exceeds statement execution. -- `execAndGet()` returns a temporary `Column`, copy immediately. -- `exec()` returns changes for DML, 0 for DDL. - -## Build and Test -### Quick Build Commands -**Windows (Visual Studio 2026):** -```batch -mkdir build -cd build -cmake -G "Visual Studio 18 2026" -DSQLITECPP_BUILD_TESTS=ON -DSQLITECPP_BUILD_EXAMPLES=ON .. -cmake --build . --config Release -``` - -**Windows (using build.bat):** -```batch -build.bat -``` - -**Unix/macOS:** -```bash -mkdir build && cd build -cmake -DCMAKE_BUILD_TYPE=Debug -DSQLITECPP_BUILD_TESTS=ON -DSQLITECPP_BUILD_EXAMPLES=ON .. -cmake --build . -``` - -**Meson:** -```bash -meson setup builddir -DSQLITECPP_BUILD_TESTS=true -DSQLITECPP_BUILD_EXAMPLES=true -meson compile -C builddir -``` - -### Essential CMake Options -| Option | Default | Description | -|--------|---------|-------------| -| `SQLITECPP_BUILD_TESTS` | OFF | Build unit tests | -| `SQLITECPP_BUILD_EXAMPLES` | OFF | Build examples | -| `SQLITECPP_INTERNAL_SQLITE` | ON | Use bundled sqlite3/ | -| `BUILD_SHARED_LIBS` | OFF | Build as DLL/shared library | -| `SQLITE_ENABLE_COLUMN_METADATA` | ON | Enable `getColumnOriginName()` | - -### Running Tests -```bash -cd build -ctest --output-on-failure -ctest --output-on-failure -V -ctest --output-on-failure -R "Database" -``` -```bash -meson test -C builddir -meson test -C builddir -v -``` - -### Style Checking -```bash -python cpplint.py src/*.cpp include/SQLiteCpp/*.h -``` - -## Troubleshooting -**MSVC:** -``` -c:\path\to\file.cpp(42): error C2065: 'undeclaredVar': undeclared identifier -c:\path\to\file.cpp(50,15): error C2039: 'foo': is not a member of 'SQLite::Database' -``` - -**GCC/Clang:** -``` -src/Database.cpp:42:5: error: use of undeclared identifier 'undeclaredVar' -include/SQLiteCpp/Database.h:100:10: error: no member named 'foo' in 'SQLite::Database' -``` - -**Linker:** -``` -error LNK2019: unresolved external symbol "SQLite::Database::exec" -``` - -Usually means missing SQLiteCpp library or missing source file in build. - -## Reference -### Project Snapshot -- SQLiteCpp is a lean C++11 RAII wrapper around SQLite3 C APIs. -- Minimal dependencies (C++11 STL + SQLite3). -- Cross-platform, thread-safe at SQLite multi-thread level. -- Keep naming close to SQLite. -- Public headers avoid `sqlite3.h`; use `SQLite::OPEN_*` flags from `Database.h`. - -### Repository Structure -``` -SQLiteCpp/ -+-- include/SQLiteCpp/ # Public headers -| +-- SQLiteCpp.h # Umbrella include + version macros -| +-- Database.h # Connection + open flags -| +-- Statement.h # Prepared statements -| +-- Column.h # Result column access -| +-- Transaction.h # RAII transaction -| +-- Savepoint.h # RAII savepoint -| +-- Backup.h # Online backup -| +-- Exception.h # SQLite::Exception -| +-- Assertion.h # SQLITECPP_ASSERT macro -| +-- VariadicBind.h # Bind helper (C++11/14) -| +-- ExecuteMany.h # Batch execute (C++14) -| +-- SQLiteCppExport.h # SQLITECPP_API macro -+-- src/ # Implementations -+-- tests/ # Unit tests (*_test.cpp) -+-- sqlite3/ # Bundled SQLite3 source -+-- examples/ # Example applications -+-- CMakeLists.txt / meson.build / build.bat / build.sh -``` - -### Naming Rules -| Element | Convention | Example | -|---------|------------|---------| -| Types | PascalCase | `Database`, `Statement`, `TransactionBehavior` | -| Files | Named like contained class | `Database.h`, `Statement.cpp` | -| Functions and variables | camelCase | `executeStep()`, `getColumn()` | -| Member variables | Prefix `m` | `mDatabase`, `mQuery` | -| Function arguments | Prefix `a` | `aDatabase`, `aQuery` | -| Boolean variables | Prefix `b`/`mb` | `bExists`, `mbDone` | -| Pointer variables | Prefix `p`/`mp` | `pValue`, `mpSQLite` | -| Constants | ALL_CAPS | `OPEN_READONLY`, `SQLITE_OK` | - -### File Header Template -```cpp -/** - * @file ClassName.h - * @ingroup SQLiteCpp - * @brief Brief description of the file. - * - * Copyright (c) 2012-2025 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include -// other includes... -``` - -### Doxygen Format -```cpp -/** - * @brief Execute a step of the prepared query to fetch one row of results. - * - * @param[in] aIndex Index of the column (0-based) - * - * @return - true (SQLITE_ROW) if there is another row ready - * - false (SQLITE_DONE) if the query has finished executing - * - * @throw SQLite::Exception in case of error - */ -``` - -### Error Handling -- Use `SQLite::Exception` for errors (inherits `std::runtime_error`). -- Use `check(ret)` after SQLite C API calls. -- In destructors, use `SQLITECPP_ASSERT()` instead of throwing. - -### Minimal API Example -```cpp -SQLite::Database db("example.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); - -SQLite::Statement query(db, "SELECT * FROM test WHERE id > ? AND name = :name"); -query.bind(1, 5); -query.bind(":name", "John"); -while (query.executeStep()) -{ - int id = query.getColumn(0); - std::string value = query.getColumn(1); -} - -SQLite::Statement insert(db, "INSERT INTO test VALUES (?, ?)"); -insert.bind(1, 42); -insert.bind(2, "value"); -int changes = insert.exec(); -``` + +**Commits:** +- Imperative mood, ~50 char first line, body wrapped at 72 chars. +- Reference issue: `Closes #123` or `Fixes #123`. + +```bash +git fetch origin +git checkout -b 123-fix-short-description origin/master +``` + +## Common Pitfalls +- `bind()` is 1-based; `getColumn()` is 0-based. +- Reusing `Statement` requires `reset()` and `clearBindings()`. +- `bindNoCopy()` only if data lifetime exceeds statement execution. +- `execAndGet()` returns a temporary `Column`, copy immediately. +- `exec()` returns changes for DML, 0 for DDL. + +## Build and Test +### Quick Build Commands +**Windows (Visual Studio 2026):** +```batch +mkdir build +cd build +cmake -G "Visual Studio 18 2026" -DSQLITECPP_BUILD_TESTS=ON -DSQLITECPP_BUILD_EXAMPLES=ON .. +cmake --build . --config Release +``` + +**Windows (using build.bat):** +```batch +build.bat +``` + +**Unix/macOS:** +```bash +mkdir build && cd build +cmake -DCMAKE_BUILD_TYPE=Debug -DSQLITECPP_BUILD_TESTS=ON -DSQLITECPP_BUILD_EXAMPLES=ON .. +cmake --build . +``` + +**Meson:** +```bash +meson setup builddir -DSQLITECPP_BUILD_TESTS=true -DSQLITECPP_BUILD_EXAMPLES=true +meson compile -C builddir +``` + +### Essential CMake Options +| Option | Default | Description | +|--------|---------|-------------| +| `SQLITECPP_BUILD_TESTS` | OFF | Build unit tests | +| `SQLITECPP_BUILD_EXAMPLES` | OFF | Build examples | +| `SQLITECPP_INTERNAL_SQLITE` | ON | Use bundled sqlite3/ | +| `BUILD_SHARED_LIBS` | OFF | Build as DLL/shared library | +| `SQLITE_ENABLE_COLUMN_METADATA` | ON | Enable `getColumnOriginName()` | + +### Running Tests +```bash +cd build +ctest --output-on-failure +ctest --output-on-failure -V +ctest --output-on-failure -R "Database" +``` +```bash +meson test -C builddir +meson test -C builddir -v +``` + +### Style Checking +```bash +python cpplint.py src/*.cpp include/SQLiteCpp/*.h +``` + +## Troubleshooting +**MSVC:** +``` +c:\path\to\file.cpp(42): error C2065: 'undeclaredVar': undeclared identifier +c:\path\to\file.cpp(50,15): error C2039: 'foo': is not a member of 'SQLite::Database' +``` + +**GCC/Clang:** +``` +src/Database.cpp:42:5: error: use of undeclared identifier 'undeclaredVar' +include/SQLiteCpp/Database.h:100:10: error: no member named 'foo' in 'SQLite::Database' +``` + +**Linker:** +``` +error LNK2019: unresolved external symbol "SQLite::Database::exec" +``` + +Usually means missing SQLiteCpp library or missing source file in build. + +## Reference +### Project Snapshot +- SQLiteCpp is a lean C++11 RAII wrapper around SQLite3 C APIs. +- Minimal dependencies (C++11 STL + SQLite3). +- Cross-platform, thread-safe at SQLite multi-thread level. +- Keep naming close to SQLite. +- Public headers avoid `sqlite3.h`; use `SQLite::OPEN_*` flags from `Database.h`. + +### Repository Structure +``` +SQLiteCpp/ ++-- include/SQLiteCpp/ # Public headers +| +-- SQLiteCpp.h # Umbrella include + version macros +| +-- Database.h # Connection + open flags +| +-- Statement.h # Prepared statements +| +-- Column.h # Result column access +| +-- Transaction.h # RAII transaction +| +-- Savepoint.h # RAII savepoint +| +-- Backup.h # Online backup +| +-- Exception.h # SQLite::Exception +| +-- Assertion.h # SQLITECPP_ASSERT macro +| +-- VariadicBind.h # Bind helper (C++11/14) +| +-- ExecuteMany.h # Batch execute (C++14) +| +-- SQLiteCppExport.h # SQLITECPP_API macro ++-- src/ # Implementations ++-- tests/ # Unit tests (*_test.cpp) ++-- sqlite3/ # Bundled SQLite3 source ++-- examples/ # Example applications ++-- CMakeLists.txt / meson.build / build.bat / build.sh +``` + +### Naming Rules +| Element | Convention | Example | +|---------|------------|---------| +| Types | PascalCase | `Database`, `Statement`, `TransactionBehavior` | +| Files | Named like contained class | `Database.h`, `Statement.cpp` | +| Functions and variables | camelCase | `executeStep()`, `getColumn()` | +| Member variables | Prefix `m` | `mDatabase`, `mQuery` | +| Function arguments | Prefix `a` | `aDatabase`, `aQuery` | +| Boolean variables | Prefix `b`/`mb` | `bExists`, `mbDone` | +| Pointer variables | Prefix `p`/`mp` | `pValue`, `mpSQLite` | +| Constants | ALL_CAPS | `OPEN_READONLY`, `SQLITE_OK` | + +### File Header Template +```cpp +/** + * @file ClassName.h + * @ingroup SQLiteCpp + * @brief Brief description of the file. + * + * Copyright (c) 2012-2025 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ +#pragma once + +#include +// other includes... +``` + +### Doxygen Format +```cpp +/** + * @brief Execute a step of the prepared query to fetch one row of results. + * + * @param[in] aIndex Index of the column (0-based) + * + * @return - true (SQLITE_ROW) if there is another row ready + * - false (SQLITE_DONE) if the query has finished executing + * + * @throw SQLite::Exception in case of error + */ +``` + +### Error Handling +- Use `SQLite::Exception` for errors (inherits `std::runtime_error`). +- Use `check(ret)` after SQLite C API calls. +- In destructors, use `SQLITECPP_ASSERT()` instead of throwing. + +### Minimal API Example +```cpp +SQLite::Database db("example.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + +SQLite::Statement query(db, "SELECT * FROM test WHERE id > ? AND name = :name"); +query.bind(1, 5); +query.bind(":name", "John"); +while (query.executeStep()) +{ + int id = query.getColumn(0); + std::string value = query.getColumn(1); +} + +SQLite::Statement insert(db, "INSERT INTO test VALUES (?, ?)"); +insert.bind(1, 42); +insert.bind(2, "value"); +int changes = insert.exec(); +``` diff --git a/LICENSE.txt b/LICENSE.txt index ed04151c..0abd7549 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,20 +1,20 @@ -The MIT License (MIT) - -Copyright (c) 2012-2025 Sebastien Rombauts (sebastien.rombauts@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The MIT License (MIT) + +Copyright (c) 2012-2025 Sebastien Rombauts (sebastien.rombauts@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index e1321e69..00e972c6 100644 --- a/README.md +++ b/README.md @@ -1,281 +1,281 @@ -SQLiteC++ ---------- - -[![release](https://img.shields.io/github/release/SRombauts/SQLiteCpp.svg)](https://github.com/SRombauts/SQLiteCpp/releases) -[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/SRombauts/SQLiteCpp/blob/master/LICENSE.txt) -[![Travis CI Linux Build Status](https://travis-ci.org/SRombauts/SQLiteCpp.svg?branch=master)](https://travis-ci.org/SRombauts/SQLiteCpp "Travis CI Linux Build Status") -[![AppVeyor Windows Build status](https://ci.appveyor.com/api/projects/status/github/SRombauts/SQLiteCpp?svg=true)](https://ci.appveyor.com/project/SbastienRombauts/SQLiteCpp "AppVeyor Windows Build status") -[![GitHub Actions Build status](https://github.com/SRombauts/SQLiteCpp/workflows/build/badge.svg)](https://github.com/SRombauts/SQLiteCpp/actions "GitHhub Actions Build status") -[![Coveralls](https://img.shields.io/coveralls/SRombauts/SQLiteCpp.svg)](https://coveralls.io/github/SRombauts/SQLiteCpp "Coveralls test coverage") -[![Coverity](https://img.shields.io/coverity/scan/14508.svg)](https://scan.coverity.com/projects/srombauts-sqlitecpp "Coverity Scan Build Status") -[![Join the chat at https://gitter.im/SRombauts/SQLiteCpp](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/SRombauts/SQLiteCpp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -SQLiteC++ (SQLiteCpp) is a lean and easy to use C++ SQLite3 wrapper. - - - - -## About SQLiteC++: - -SQLiteC++ offers an encapsulation around the native C APIs of SQLite, -with a few intuitive and well documented C++ classes. - -### License: - -Copyright (c) 2012-2025 Sébastien Rombauts (sebastien.rombauts@gmail.com) - -Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt -or copy at http://opensource.org/licenses/MIT) - -#### Note on redistribution of SQLite source files - -As stated by the MIT License, you are welcome to reuse, modify, and redistribute the SQLiteCpp source code -the way you want it to, be it a git submodule, a subdirectory, or a selection of some source files. - -I would love a mention in your README, a web link to the SQLite repository, and a mention of the author, -but none of those are mandatory. - -### About SQLite underlying library: - -SQLite is a library that implements a serverless transactional SQL database engine. -It is the most widely deployed SQL database engine in the world. -All of the code and documentation in SQLite has been dedicated to the public domain by the authors. -[http://www.sqlite.org/about.html](http://www.sqlite.org/about.html) - -### The goals of SQLiteC++ are: - -- to offer the best of the existing simple C++ SQLite wrappers -- to be elegantly written with good C++11 design, STL, exceptions and RAII idiom -- to keep dependencies to a minimum (C++11 STL and SQLite3) -- to be portable -- to be light and fast -- to be thread-safe only as much as SQLite "Multi-thread" mode (see below) -- to have a good unit test coverage -- to use API names sticking with those of the SQLite library -- to be well documented with Doxygen tags, and with some good examples -- to be well maintained -- to use a permissive MIT license, similar to BSD or Boost, for proprietary/commercial usage - -It is designed using the Resource Acquisition Is Initialization (RAII) idiom -(see [http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)), -and throwing exceptions in case of SQLite errors (except in destructors, -where assert() are used instead). -Each SQLiteC++ object must be constructed with a valid SQLite database connection, -and then is always valid until destroyed. - -### Supported platforms: - -Now requires a C++11 compiler. Use branch [sqlitecpp-2.x](https://github.com/SRombauts/SQLiteCpp/tree/sqlitecpp-2.x) for latest pre-C++11 developments. - -Developments and tests are done under the following OSs: -- Ubuntu 14.04, 16.04 and 18.04 (Travis CI and Github Actions) -- Windows 10, and Windows Server 2012 R2, Windows Server 2016, Windows Server 2022, Windows Server 2025 (AppVeyor and Github Actions) -- MacOS 10.11 and 11.7 (Travis CI and Github Actions) -- Valgrind memcheck tool - -And the following IDEs/Compilers -- GCC 4.8.4, 5.3.0, 7.1.1 and latest eg 9.4 (C++11, C++14, C++17) -- Clang 5 and 7 (Travis CI) -- AppleClang 8, 9 and 13 (Travis CI and Github Actions) -- Xcode 8 & 9 (Travis CI) -- Visual Studio Community/Entreprise 2026, 2022, 2019, 2017, and 2015 (AppVeyor and Github Actions) - -### Dependencies - -- a modern C++11 STL implementation with GCC, Clang, or Visual Studio 2015 -- exception support (the class Exception inherits from std::runtime_error) -- the SQLite library (3.7.15 minimum from 2012-12-12) either by linking to it dynamically or statically (install the libsqlite3-dev package under Debian/Ubuntu/Mint Linux), - or by adding its source file in your project code base (source code provided in src/sqlite3 for Windows), - with the `SQLITE_ENABLE_COLUMN_METADATA` macro defined (see http://www.sqlite.org/compile.html#enable_column_metadata). - -## Getting started -### Installation - -To use this wrapper, you need to add the SQLiteC++ source files from the src/ directory -in your project code base, and compile/link against the sqlite library. - -The easiest way to do this is to add the wrapper as a library. -The "CMakeLists.txt" file defining the static library is provided in the root directory, -so you simply have to add_subdirectory(SQLiteCpp) to you main CMakeLists.txt -and link to the "SQLiteCpp" wrapper library. - +SQLiteC++ +--------- + +[![release](https://img.shields.io/github/release/SRombauts/SQLiteCpp.svg)](https://github.com/SRombauts/SQLiteCpp/releases) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/SRombauts/SQLiteCpp/blob/master/LICENSE.txt) +[![Travis CI Linux Build Status](https://travis-ci.org/SRombauts/SQLiteCpp.svg?branch=master)](https://travis-ci.org/SRombauts/SQLiteCpp "Travis CI Linux Build Status") +[![AppVeyor Windows Build status](https://ci.appveyor.com/api/projects/status/github/SRombauts/SQLiteCpp?svg=true)](https://ci.appveyor.com/project/SbastienRombauts/SQLiteCpp "AppVeyor Windows Build status") +[![GitHub Actions Build status](https://github.com/SRombauts/SQLiteCpp/workflows/build/badge.svg)](https://github.com/SRombauts/SQLiteCpp/actions "GitHhub Actions Build status") +[![Coveralls](https://img.shields.io/coveralls/SRombauts/SQLiteCpp.svg)](https://coveralls.io/github/SRombauts/SQLiteCpp "Coveralls test coverage") +[![Coverity](https://img.shields.io/coverity/scan/14508.svg)](https://scan.coverity.com/projects/srombauts-sqlitecpp "Coverity Scan Build Status") +[![Join the chat at https://gitter.im/SRombauts/SQLiteCpp](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/SRombauts/SQLiteCpp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +SQLiteC++ (SQLiteCpp) is a lean and easy to use C++ SQLite3 wrapper. + + + + +## About SQLiteC++: + +SQLiteC++ offers an encapsulation around the native C APIs of SQLite, +with a few intuitive and well documented C++ classes. + +### License: + +Copyright (c) 2012-2025 Sébastien Rombauts (sebastien.rombauts@gmail.com) + +Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt +or copy at http://opensource.org/licenses/MIT) + +#### Note on redistribution of SQLite source files + +As stated by the MIT License, you are welcome to reuse, modify, and redistribute the SQLiteCpp source code +the way you want it to, be it a git submodule, a subdirectory, or a selection of some source files. + +I would love a mention in your README, a web link to the SQLite repository, and a mention of the author, +but none of those are mandatory. + +### About SQLite underlying library: + +SQLite is a library that implements a serverless transactional SQL database engine. +It is the most widely deployed SQL database engine in the world. +All of the code and documentation in SQLite has been dedicated to the public domain by the authors. +[http://www.sqlite.org/about.html](http://www.sqlite.org/about.html) + +### The goals of SQLiteC++ are: + +- to offer the best of the existing simple C++ SQLite wrappers +- to be elegantly written with good C++11 design, STL, exceptions and RAII idiom +- to keep dependencies to a minimum (C++11 STL and SQLite3) +- to be portable +- to be light and fast +- to be thread-safe only as much as SQLite "Multi-thread" mode (see below) +- to have a good unit test coverage +- to use API names sticking with those of the SQLite library +- to be well documented with Doxygen tags, and with some good examples +- to be well maintained +- to use a permissive MIT license, similar to BSD or Boost, for proprietary/commercial usage + +It is designed using the Resource Acquisition Is Initialization (RAII) idiom +(see [http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)), +and throwing exceptions in case of SQLite errors (except in destructors, +where assert() are used instead). +Each SQLiteC++ object must be constructed with a valid SQLite database connection, +and then is always valid until destroyed. + +### Supported platforms: + +Now requires a C++11 compiler. Use branch [sqlitecpp-2.x](https://github.com/SRombauts/SQLiteCpp/tree/sqlitecpp-2.x) for latest pre-C++11 developments. + +Developments and tests are done under the following OSs: +- Ubuntu 14.04, 16.04 and 18.04 (Travis CI and Github Actions) +- Windows 10, and Windows Server 2012 R2, Windows Server 2016, Windows Server 2022, Windows Server 2025 (AppVeyor and Github Actions) +- MacOS 10.11 and 11.7 (Travis CI and Github Actions) +- Valgrind memcheck tool + +And the following IDEs/Compilers +- GCC 4.8.4, 5.3.0, 7.1.1 and latest eg 9.4 (C++11, C++14, C++17) +- Clang 5 and 7 (Travis CI) +- AppleClang 8, 9 and 13 (Travis CI and Github Actions) +- Xcode 8 & 9 (Travis CI) +- Visual Studio Community/Entreprise 2026, 2022, 2019, 2017, and 2015 (AppVeyor and Github Actions) + +### Dependencies + +- a modern C++11 STL implementation with GCC, Clang, or Visual Studio 2015 +- exception support (the class Exception inherits from std::runtime_error) +- the SQLite library (3.7.15 minimum from 2012-12-12) either by linking to it dynamically or statically (install the libsqlite3-dev package under Debian/Ubuntu/Mint Linux), + or by adding its source file in your project code base (source code provided in src/sqlite3 for Windows), + with the `SQLITE_ENABLE_COLUMN_METADATA` macro defined (see http://www.sqlite.org/compile.html#enable_column_metadata). + +## Getting started +### Installation + +To use this wrapper, you need to add the SQLiteC++ source files from the src/ directory +in your project code base, and compile/link against the sqlite library. + +The easiest way to do this is to add the wrapper as a library. +The "CMakeLists.txt" file defining the static library is provided in the root directory, +so you simply have to add_subdirectory(SQLiteCpp) to you main CMakeLists.txt +and link to the "SQLiteCpp" wrapper library. + Example for Linux: -```cmake -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/thirdparty/SQLiteCpp) - -add_executable(main src/main.cpp) -target_link_libraries(main - SQLiteCpp - sqlite3 - pthread - dl - ) +```cmake +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/thirdparty/SQLiteCpp) + +add_executable(main src/main.cpp) +target_link_libraries(main + SQLiteCpp + sqlite3 + pthread + dl + ) +``` +Thus this SQLiteCpp repository can be directly used as a Git submodule. +See the [SQLiteCpp_Example](https://github.com/SRombauts/SQLiteCpp_Example) side repository for a standalone "from scratch" example. + +Under Debian/Ubuntu/Mint Linux, you can install the libsqlite3-dev package if you don't want to use the embedded sqlite3 library. + +### Building example and unit-tests: + +Use git to clone the repository. Then init and update submodule "googletest". + +```Shell +git clone https://github.com/SRombauts/SQLiteCpp.git +cd SQLiteCpp +git submodule init +git submodule update +``` + +### Installing SQLiteCpp (vcpkg) + +Alternatively, you can build and install SQLiteCpp using [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: + +```bash or powershell +git clone https://github.com/Microsoft/vcpkg.git +cd vcpkg +./bootstrap-vcpkg.sh +./vcpkg integrate install +./vcpkg install sqlitecpp ``` -Thus this SQLiteCpp repository can be directly used as a Git submodule. -See the [SQLiteCpp_Example](https://github.com/SRombauts/SQLiteCpp_Example) side repository for a standalone "from scratch" example. - -Under Debian/Ubuntu/Mint Linux, you can install the libsqlite3-dev package if you don't want to use the embedded sqlite3 library. - -### Building example and unit-tests: - -Use git to clone the repository. Then init and update submodule "googletest". - -```Shell -git clone https://github.com/SRombauts/SQLiteCpp.git -cd SQLiteCpp -git submodule init -git submodule update -``` - -### Installing SQLiteCpp (vcpkg) - -Alternatively, you can build and install SQLiteCpp using [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: - -```bash or powershell -git clone https://github.com/Microsoft/vcpkg.git -cd vcpkg -./bootstrap-vcpkg.sh -./vcpkg integrate install -./vcpkg install sqlitecpp -``` - -The SQLiteCpp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. - - -#### Using SQLiteCpp on a system-wide installation - + +The SQLiteCpp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + + +#### Using SQLiteCpp on a system-wide installation + If you installed this package to your system, a `SQLiteCppConfig.cmake` file will be generated & installed to your system. -This file lets you link against the SQLiteCpp library for use in your Cmake project. - -Here's an example of using this in your CMakeLists.txt -```cmake -# You can optionally define a minimum version in this call -find_package(SQLiteCpp REQUIRED) -# For this example, lets say you created an target with add_executable (or add_library) called "my_target" -# You can optionally declare PUBLIC or PRIVATE linkage here, depending on your needs. -target_link_libraries(my_target PRIVATE SQLiteCpp) -``` - -#### CMake and tests -A CMake configuration file is also provided for multi-platform support and testing. - -Typical generic build for MS Visual Studio under Windows (from [build.bat](build.bat)): - -```Batchfile -mkdir build -cd build - -cmake .. # cmake .. -G "Visual Studio 16 2019" # for Visual Studio 2019 -@REM Generate a Visual Studio solution for latest version found -cmake -DSQLITECPP_BUILD_EXAMPLES=ON -DSQLITECPP_BUILD_TESTS=ON .. - -@REM Build default configuration (ie 'Debug') -cmake --build . - -@REM Build and run tests -ctest --output-on-failure -``` - -Generating the Linux Makefile, building in Debug and executing the tests (from [build.sh](build.sh)): - -```Shell -mkdir Debug -cd Debug - -# Generate a Makefile for GCC (or Clang, depanding on CC/CXX envvar) -cmake -DSQLITECPP_BUILD_EXAMPLES=ON -DSQLITECPP_BUILD_TESTS=ON .. - -# Build (ie 'make') -cmake --build . - -# Build and run unit-tests (ie 'make test') -ctest --output-on-failure -``` - -#### Building with meson - -You can build SQLiteCpp with [meson](https://mesonbuild.com/) using the provided meson project. - -you can install meson using pip: `pip install meson` however you may need to install ninja and other dependencies depending on your platform as an compiler toolchain - -Arch Linux: - -```sh -# install clang (compiler toolchain) and ninja (recommended build system) -sudo pacman -Syu clang ninja -# install python and pip (required for meson) -sudo pacman -Syu python python-pip +This file lets you link against the SQLiteCpp library for use in your Cmake project. + +Here's an example of using this in your CMakeLists.txt +```cmake +# You can optionally define a minimum version in this call +find_package(SQLiteCpp REQUIRED) +# For this example, lets say you created an target with add_executable (or add_library) called "my_target" +# You can optionally declare PUBLIC or PRIVATE linkage here, depending on your needs. +target_link_libraries(my_target PRIVATE SQLiteCpp) +``` + +#### CMake and tests +A CMake configuration file is also provided for multi-platform support and testing. + +Typical generic build for MS Visual Studio under Windows (from [build.bat](build.bat)): + +```Batchfile +mkdir build +cd build + +cmake .. # cmake .. -G "Visual Studio 16 2019" # for Visual Studio 2019 +@REM Generate a Visual Studio solution for latest version found +cmake -DSQLITECPP_BUILD_EXAMPLES=ON -DSQLITECPP_BUILD_TESTS=ON .. + +@REM Build default configuration (ie 'Debug') +cmake --build . + +@REM Build and run tests +ctest --output-on-failure +``` + +Generating the Linux Makefile, building in Debug and executing the tests (from [build.sh](build.sh)): + +```Shell +mkdir Debug +cd Debug + +# Generate a Makefile for GCC (or Clang, depanding on CC/CXX envvar) +cmake -DSQLITECPP_BUILD_EXAMPLES=ON -DSQLITECPP_BUILD_TESTS=ON .. + +# Build (ie 'make') +cmake --build . + +# Build and run unit-tests (ie 'make test') +ctest --output-on-failure +``` + +#### Building with meson + +You can build SQLiteCpp with [meson](https://mesonbuild.com/) using the provided meson project. + +you can install meson using pip: `pip install meson` however you may need to install ninja and other dependencies depending on your platform as an compiler toolchain + +Arch Linux: + +```sh +# install clang (compiler toolchain) and ninja (recommended build system) +sudo pacman -Syu clang ninja +# install python and pip (required for meson) +sudo pacman -Syu python python-pip # install meson -pip install meson -``` - -Ubuntu: - -```sh -# install gcc(compiler toolchain) and ninja (recommended build system) -sudo apt install build-essential ninja-build -# install python and pip (required for meson) -sudo apt install python3 python3-pip -# install meson -pip install meson -``` - -for example you can build the library using the default options with: - -```sh -# setup the build directory +pip install meson +``` + +Ubuntu: + +```sh +# install gcc(compiler toolchain) and ninja (recommended build system) +sudo apt install build-essential ninja-build +# install python and pip (required for meson) +sudo apt install python3 python3-pip +# install meson +pip install meson +``` + +for example you can build the library using the default options with: + +```sh +# setup the build directory meson setup builddir -# build sqlitecpp -meson compile -C builddir -``` - -or if you wish to build with tests and examples: - -```sh -# setup the build directory with tests and examples enabled -meson setup builddir -DSQLITECPP_BUILD_TESTS=true -DSQLITECPP_BUILD_EXAMPLES=true -# build sqlitecpp -meson compile -C builddir -``` - -#### Using SQLiteCpp as subproject in meson - -please check the examples in the examples folder for usage of SQLiteCpp as a subproject in meson, as for the wrap file you can use the one provided in the subprojects folder called `SQLiteCpp.wrap` - -> keep in mind that even that this wrap should be up to date, it is recommended to check the latest version of SQLiteCpp and update the wrap file accordingly - -#### System SQLiteCpp support under meson - -additionally meson can detect and use the bundled sqlitecpp library included on your system if available, for example with vcpkg you would need to set the `PKG_CONFIG_PATH` environment variable to the vcpkg directory before running meson setup, and if applies the corresponding `PKG-CONFIG` executable to the path. - -#### Building the Doxygen/html documentation - -Make sure you have Dogygen installed and configure CMake using the `SQLITECPP_RUN_DOXYGEN=ON` flag: - -``` -cmake -DSQLITECPP_RUN_DOXYGEN=ON -``` - -and then execute the `SQLiteCpp_doxygen` target (or build all targets, see above). -The documentation will be generated in the 'doc' subfolder of the source tree. - -#### CMake options - - * For more options on customizing the build, see the [CMakeLists.txt](https://github.com/SRombauts/SQLiteCpp/blob/master/CMakeLists.txt) file. - -#### Troubleshooting - -Under Linux, if you get multiple linker errors like "undefined reference to sqlite3_xxx", -it's that you lack the "sqlite3" library: install the libsqlite3-dev package. - -If you get a single linker error "Column.cpp: undefined reference to sqlite3_column_origin_name", -it's that your "sqlite3" library was not compiled with -the `SQLITE_ENABLE_COLUMN_METADATA` macro defined (see [http://www.sqlite.org/compile.html#enable_column_metadata](http://www.sqlite.org/compile.html#enable_column_metadata)). -You can: - - either recompile the sqlite3 library provided by your distribution yourself (seek help online) - - or turn off the `option(SQLITE_ENABLE_COLUMN_METADATA "Enable Column::getColumnOriginName(). Require support from sqlite3 library." ON)` in [CMakeFiles.txt](CMakeFiles.txt) (or other build system scripts) - - or turn on the `option(SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ON)` in [CMakeFiles.txt](CMakeFiles.txt) - +# build sqlitecpp +meson compile -C builddir +``` + +or if you wish to build with tests and examples: + +```sh +# setup the build directory with tests and examples enabled +meson setup builddir -DSQLITECPP_BUILD_TESTS=true -DSQLITECPP_BUILD_EXAMPLES=true +# build sqlitecpp +meson compile -C builddir +``` + +#### Using SQLiteCpp as subproject in meson + +please check the examples in the examples folder for usage of SQLiteCpp as a subproject in meson, as for the wrap file you can use the one provided in the subprojects folder called `SQLiteCpp.wrap` + +> keep in mind that even that this wrap should be up to date, it is recommended to check the latest version of SQLiteCpp and update the wrap file accordingly + +#### System SQLiteCpp support under meson + +additionally meson can detect and use the bundled sqlitecpp library included on your system if available, for example with vcpkg you would need to set the `PKG_CONFIG_PATH` environment variable to the vcpkg directory before running meson setup, and if applies the corresponding `PKG-CONFIG` executable to the path. + +#### Building the Doxygen/html documentation + +Make sure you have Dogygen installed and configure CMake using the `SQLITECPP_RUN_DOXYGEN=ON` flag: + +``` +cmake -DSQLITECPP_RUN_DOXYGEN=ON +``` + +and then execute the `SQLiteCpp_doxygen` target (or build all targets, see above). +The documentation will be generated in the 'doc' subfolder of the source tree. + +#### CMake options + + * For more options on customizing the build, see the [CMakeLists.txt](https://github.com/SRombauts/SQLiteCpp/blob/master/CMakeLists.txt) file. + +#### Troubleshooting + +Under Linux, if you get multiple linker errors like "undefined reference to sqlite3_xxx", +it's that you lack the "sqlite3" library: install the libsqlite3-dev package. + +If you get a single linker error "Column.cpp: undefined reference to sqlite3_column_origin_name", +it's that your "sqlite3" library was not compiled with +the `SQLITE_ENABLE_COLUMN_METADATA` macro defined (see [http://www.sqlite.org/compile.html#enable_column_metadata](http://www.sqlite.org/compile.html#enable_column_metadata)). +You can: + - either recompile the sqlite3 library provided by your distribution yourself (seek help online) + - or turn off the `option(SQLITE_ENABLE_COLUMN_METADATA "Enable Column::getColumnOriginName(). Require support from sqlite3 library." ON)` in [CMakeFiles.txt](CMakeFiles.txt) (or other build system scripts) + - or turn on the `option(SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ON)` in [CMakeFiles.txt](CMakeFiles.txt) + ### AI Coding Assistance SQLiteCpp includes instructions for AI coding assistants: @@ -284,186 +284,186 @@ SQLiteCpp includes instructions for AI coding assistants: These files help AI assistants understand SQLiteCpp's coding standards, architecture, and best practices. -### Continuous Integration - -This project is continuously tested under Ubuntu Linux with the gcc and clang compilers -using the Travis CI community service with the above CMake building and testing procedure. -It is also tested in the same way under Windows Server 2012 R2 with Visual Studio 2013 compiler -using the AppVeyor continuous integration service. - -Detailed results can be seen online: - - [https://travis-ci.org/SRombauts/SQLiteCpp](https://travis-ci.org/SRombauts/SQLiteCpp) - - [https://ci.appveyor.com/project/SbastienRombauts/SQLiteCpp](https://ci.appveyor.com/project/SbastienRombauts/SQLiteCpp) - -### Thread-safety - -SQLite supports three modes of thread safety, as describe in "SQLite And Multiple Threads": -see [http://www.sqlite.org/threadsafe.html](http://www.sqlite.org/threadsafe.html) - -This SQLiteC++ wrapper does no add any locks (no mutexes) nor any other thread-safety mechanism -above the SQLite library itself, by design, for lightness and speed. - -Thus, SQLiteC++ naturally supports the "Multi Thread" mode of SQLite: -"In this mode, SQLite can be safely used by multiple threads -provided that no single database connection is used simultaneously in two or more threads." - -But SQLiteC++ does not support the fully thread-safe "Serialized" mode of SQLite, -because of the way it shares the underlying SQLite precompiled statement -in a custom shared pointer (See the inner class "Statement::Ptr"). - -### Valgrind memcheck - -Run valgrind to search for memory leaks in your application, the SQLiteCpp wrapper, or the sqlite3 library. -Execute the following command under Unix like OS (Linux, MacOS or WSL2/Ubuntu under Windows Subsystem for Linux): - -```Shell -valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose build/SQLiteCpp_example1 -``` - -or uncoment the line at the end of [build.sh](build.sh) - -## Examples +### Continuous Integration + +This project is continuously tested under Ubuntu Linux with the gcc and clang compilers +using the Travis CI community service with the above CMake building and testing procedure. +It is also tested in the same way under Windows Server 2012 R2 with Visual Studio 2013 compiler +using the AppVeyor continuous integration service. + +Detailed results can be seen online: + - [https://travis-ci.org/SRombauts/SQLiteCpp](https://travis-ci.org/SRombauts/SQLiteCpp) + - [https://ci.appveyor.com/project/SbastienRombauts/SQLiteCpp](https://ci.appveyor.com/project/SbastienRombauts/SQLiteCpp) + +### Thread-safety + +SQLite supports three modes of thread safety, as describe in "SQLite And Multiple Threads": +see [http://www.sqlite.org/threadsafe.html](http://www.sqlite.org/threadsafe.html) + +This SQLiteC++ wrapper does no add any locks (no mutexes) nor any other thread-safety mechanism +above the SQLite library itself, by design, for lightness and speed. + +Thus, SQLiteC++ naturally supports the "Multi Thread" mode of SQLite: +"In this mode, SQLite can be safely used by multiple threads +provided that no single database connection is used simultaneously in two or more threads." + +But SQLiteC++ does not support the fully thread-safe "Serialized" mode of SQLite, +because of the way it shares the underlying SQLite precompiled statement +in a custom shared pointer (See the inner class "Statement::Ptr"). + +### Valgrind memcheck + +Run valgrind to search for memory leaks in your application, the SQLiteCpp wrapper, or the sqlite3 library. +Execute the following command under Unix like OS (Linux, MacOS or WSL2/Ubuntu under Windows Subsystem for Linux): + +```Shell +valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose build/SQLiteCpp_example1 +``` + +or uncoment the line at the end of [build.sh](build.sh) + +## Examples ### The first sample demonstrates how to query a database and get results: - -```C++ -try -{ - // Open a database file - SQLite::Database db("example.db3"); - - // Compile a SQL query, containing one parameter (index 1) - SQLite::Statement query(db, "SELECT * FROM test WHERE size > ?"); - - // Bind the integer value 6 to the first parameter of the SQL query - query.bind(1, 6); - - // Loop to execute the query step by step, to get rows of result - while (query.executeStep()) - { - // Demonstrate how to get some typed column value - int id = query.getColumn(0); - const char* value = query.getColumn(1); - int size = query.getColumn(2); - - std::cout << "row: " << id << ", " << value << ", " << size << std::endl; - } -} -catch (std::exception& e) -{ - std::cout << "exception: " << e.what() << std::endl; -} -``` - -### The second sample shows how to manage a transaction: - -```C++ -try -{ - SQLite::Database db("transaction.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); - - db.exec("DROP TABLE IF EXISTS test"); - - // Begin transaction - SQLite::Transaction transaction(db); - - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - - int nb = db.exec("INSERT INTO test VALUES (NULL, \"test\")"); - std::cout << "INSERT INTO test VALUES (NULL, \"test\")\", returned " << nb << std::endl; - - // Commit transaction - transaction.commit(); -} -catch (std::exception& e) -{ - std::cout << "exception: " << e.what() << std::endl; -} -``` - -### The third sample shows how to manage a prepared statement with a transaction: - -```C++ + +```C++ try { - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); - - db.exec("DROP TABLE IF EXISTS test"); - - db.exec("CREATE TABLE test (value INTEGER)"); - - // Begin transaction - SQLite::Transaction transaction(db); - - // Prepare query - SQLite::Statement query {db, "INSERT INTO test (value) VALUES (?)"}; - - // Collection to save in database - std::vector values{1, 2, 3}; - - for (const auto& v: values) - { - query.bind(1, v); - query.exec(); - query.reset(); - } - - // Commit transaction - transaction.commit(); -} -catch (std::exception& e) -{ - std::cout << "exception: " << e.what() << std::endl; -} -``` - -### How to handle assertion in SQLiteC++: -Exceptions shall not be used in destructors, so SQLiteC++ uses SQLITECPP_ASSERT() to check for errors in destructors. -If you don't want assert() to be called, you have to enable and define an assert handler as shown below, -and by setting the flag SQLITECPP_ENABLE_ASSERT_HANDLER when compiling the lib. - -```C++ -#ifdef SQLITECPP_ENABLE_ASSERT_HANDLER -namespace SQLite -{ -/// definition of the assertion handler enabled when SQLITECPP_ENABLE_ASSERT_HANDLER is defined in the project (CMakeList.txt) -void assertion_failed(const char* apFile, const long apLine, const char* apFunc, const char* apExpr, const char* apMsg) -{ - // Print a message to the standard error output stream, and abort the program. - std::cerr << apFile << ":" << apLine << ":" << " error: assertion failed (" << apExpr << ") in " << apFunc << "() with message \"" << apMsg << "\"\n"; - std::abort(); -} -} -#endif -``` - -## How to contribute -### GitHub website -The most efficient way to help and contribute to this wrapper project is to -use the tools provided by GitHub: -- please fill bug reports and feature requests here: [https://github.com/SRombauts/SQLiteCpp/issues](https://github.com/SRombauts/SQLiteCpp/issues) -- fork the repository, make some small changes and submit them with pull-request - -### Contact -You can also email me directly, I will try to answer questions and requests whenever I get the time for it. - -### Coding Style Guidelines -The source code use the CamelCase naming style variant where: -- type names (class, struct, typedef, enums...) begin with a capital letter -- files (.cpp/.h) are named like the class they contain -- function and variable names begin with a lower case letter -- member variables begin with a 'm', function arguments begin with a 'a', booleans with a 'b', pointers with a 'p' -- each file, class, method and member variable is documented using Doxygen tags -- braces on their own line -See also [http://www.appinf.com/download/CppCodingStyleGuide.pdf](http://www.appinf.com/download/CppCodingStyleGuide.pdf) for good guidelines - -## See also - Some other simple C++ SQLite wrappers: - -See bellow a short comparison of other wrappers done at the time of writing: - - [sqdbcpp](http://code.google.com/p/sqdbcpp/): RAII design, simple, no dependencies, UTF-8/UTF-16, new BSD license - - [sqlite3cc](http://ed.am/dev/sqlite3cc): uses boost, modern design, LPGPL - - [sqlite3pp](https://github.com/iwongu/sqlite3pp): modern design inspired by boost, MIT License + // Open a database file + SQLite::Database db("example.db3"); + + // Compile a SQL query, containing one parameter (index 1) + SQLite::Statement query(db, "SELECT * FROM test WHERE size > ?"); + + // Bind the integer value 6 to the first parameter of the SQL query + query.bind(1, 6); + + // Loop to execute the query step by step, to get rows of result + while (query.executeStep()) + { + // Demonstrate how to get some typed column value + int id = query.getColumn(0); + const char* value = query.getColumn(1); + int size = query.getColumn(2); + + std::cout << "row: " << id << ", " << value << ", " << size << std::endl; + } +} +catch (std::exception& e) +{ + std::cout << "exception: " << e.what() << std::endl; +} +``` + +### The second sample shows how to manage a transaction: + +```C++ +try +{ + SQLite::Database db("transaction.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); + + db.exec("DROP TABLE IF EXISTS test"); + + // Begin transaction + SQLite::Transaction transaction(db); + + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + + int nb = db.exec("INSERT INTO test VALUES (NULL, \"test\")"); + std::cout << "INSERT INTO test VALUES (NULL, \"test\")\", returned " << nb << std::endl; + + // Commit transaction + transaction.commit(); +} +catch (std::exception& e) +{ + std::cout << "exception: " << e.what() << std::endl; +} +``` + +### The third sample shows how to manage a prepared statement with a transaction: + +```C++ +try +{ + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); + + db.exec("DROP TABLE IF EXISTS test"); + + db.exec("CREATE TABLE test (value INTEGER)"); + + // Begin transaction + SQLite::Transaction transaction(db); + + // Prepare query + SQLite::Statement query {db, "INSERT INTO test (value) VALUES (?)"}; + + // Collection to save in database + std::vector values{1, 2, 3}; + + for (const auto& v: values) + { + query.bind(1, v); + query.exec(); + query.reset(); + } + + // Commit transaction + transaction.commit(); +} +catch (std::exception& e) +{ + std::cout << "exception: " << e.what() << std::endl; +} +``` + +### How to handle assertion in SQLiteC++: +Exceptions shall not be used in destructors, so SQLiteC++ uses SQLITECPP_ASSERT() to check for errors in destructors. +If you don't want assert() to be called, you have to enable and define an assert handler as shown below, +and by setting the flag SQLITECPP_ENABLE_ASSERT_HANDLER when compiling the lib. + +```C++ +#ifdef SQLITECPP_ENABLE_ASSERT_HANDLER +namespace SQLite +{ +/// definition of the assertion handler enabled when SQLITECPP_ENABLE_ASSERT_HANDLER is defined in the project (CMakeList.txt) +void assertion_failed(const char* apFile, const long apLine, const char* apFunc, const char* apExpr, const char* apMsg) +{ + // Print a message to the standard error output stream, and abort the program. + std::cerr << apFile << ":" << apLine << ":" << " error: assertion failed (" << apExpr << ") in " << apFunc << "() with message \"" << apMsg << "\"\n"; + std::abort(); +} +} +#endif +``` + +## How to contribute +### GitHub website +The most efficient way to help and contribute to this wrapper project is to +use the tools provided by GitHub: +- please fill bug reports and feature requests here: [https://github.com/SRombauts/SQLiteCpp/issues](https://github.com/SRombauts/SQLiteCpp/issues) +- fork the repository, make some small changes and submit them with pull-request + +### Contact +You can also email me directly, I will try to answer questions and requests whenever I get the time for it. + +### Coding Style Guidelines +The source code use the CamelCase naming style variant where: +- type names (class, struct, typedef, enums...) begin with a capital letter +- files (.cpp/.h) are named like the class they contain +- function and variable names begin with a lower case letter +- member variables begin with a 'm', function arguments begin with a 'a', booleans with a 'b', pointers with a 'p' +- each file, class, method and member variable is documented using Doxygen tags +- braces on their own line +See also [http://www.appinf.com/download/CppCodingStyleGuide.pdf](http://www.appinf.com/download/CppCodingStyleGuide.pdf) for good guidelines + +## See also - Some other simple C++ SQLite wrappers: + +See bellow a short comparison of other wrappers done at the time of writing: + - [sqdbcpp](http://code.google.com/p/sqdbcpp/): RAII design, simple, no dependencies, UTF-8/UTF-16, new BSD license + - [sqlite3cc](http://ed.am/dev/sqlite3cc): uses boost, modern design, LPGPL + - [sqlite3pp](https://github.com/iwongu/sqlite3pp): modern design inspired by boost, MIT License - [SQLite++](http://sqlitepp.berlios.de/): uses boost build system, Boost License 1.0 - [CppSQLite](http://www.codeproject.com/Articles/6343/CppSQLite-C-Wrapper-for-SQLite/): famous Code Project but old design, BSD License - [easySQLite](http://code.google.com/p/easysqlite/): manages table as structured objects, complex - - [sqlite_modern_cpp](https://github.com/keramer/sqlite_modern_cpp): modern C++11, all in one file, MIT license - - [sqlite_orm](https://github.com/fnc12/sqlite_orm): modern C++14, header only all in one file, no raw string queries, BSD-3 license + - [sqlite_modern_cpp](https://github.com/keramer/sqlite_modern_cpp): modern C++11, all in one file, MIT license + - [sqlite_orm](https://github.com/fnc12/sqlite_orm): modern C++14, header only all in one file, no raw string queries, BSD-3 license diff --git a/cmake/SQLiteCppConfig.cmake.in b/cmake/SQLiteCppConfig.cmake.in index 7d0941c4..2b48df4d 100644 --- a/cmake/SQLiteCppConfig.cmake.in +++ b/cmake/SQLiteCppConfig.cmake.in @@ -1,13 +1,13 @@ -include(CMakeFindDependencyMacro) -if(NOT @SQLITECPP_INTERNAL_SQLITE@) - find_dependency(SQLite3 REQUIRED) +include(CMakeFindDependencyMacro) +if(NOT @SQLITECPP_INTERNAL_SQLITE@) + find_dependency(SQLite3 REQUIRED) endif() -if(@UNIX@) - set(THREADS_PREFER_PTHREAD_FLAG @THREADS_PREFER_PTHREAD_FLAG@) - find_dependency(Threads REQUIRED) -endif() - -@PACKAGE_INIT@ - -include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") -check_required_components("@PROJECT_NAME@") +if(@UNIX@) + set(THREADS_PREFER_PTHREAD_FLAG @THREADS_PREFER_PTHREAD_FLAG@) + find_dependency(Threads REQUIRED) +endif() + +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/examples/example1/README.md b/examples/example1/README.md index f1913616..a1980b4f 100644 --- a/examples/example1/README.md +++ b/examples/example1/README.md @@ -1,6 +1,6 @@ -examples/example1 - main example --------------------------------- - -SQLiteCpp_Example demonstrates how to use SQLiteCpp as a subdirectory of a CMake project. - +examples/example1 - main example +-------------------------------- + +SQLiteCpp_Example demonstrates how to use SQLiteCpp as a subdirectory of a CMake project. + See also examples/example2 on how to use SQLiteCpp as a subdirectory of a CMake project \ No newline at end of file diff --git a/examples/example2/CMakeLists.txt b/examples/example2/CMakeLists.txt index 851eeed0..0b2c0eb7 100644 --- a/examples/example2/CMakeLists.txt +++ b/examples/example2/CMakeLists.txt @@ -1,26 +1,26 @@ -# Example CMake file for compiling & linking a project with the the SQLiteCpp wrapper -# -# Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) -# -# Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt -# or copy at http://opensource.org/licenses/MIT) -cmake_minimum_required(VERSION 3.5...4.0) -project(SQLiteCpp_Example VERSION 2.0) - -# SQLiteC++ 3.x now requires C++11 compiler -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Add SQLite3 C++ wrapper around sqlite3 library (and sqlite3 itself provided for ease of use) -# Here you can set CMake variables to avoid building Example, as well as cpplint, cppcheck... -# or set them in the cmake command line (see for instance provided build.bat/build.sh scripts) -set(SQLITECPP_RUN_CPPCHECK OFF CACHE BOOL "" FORCE) -set(SQLITECPP_RUN_CPPLINT OFF CACHE BOOL "" FORCE) -set(SQLITECPP_USE_STATIC_RUNTIME OFF CACHE BOOL "" FORCE) -add_subdirectory(../.. SQLiteCpp) # out-of-source build requires explicit subdir name for compilation artifacts - -# Add main.cpp example source code to the executable -add_executable(SQLiteCpp_Example src/main.cpp) - -# Link SQLiteCpp_example1 with SQLiteCpp -target_link_libraries(SQLiteCpp_Example SQLiteCpp) +# Example CMake file for compiling & linking a project with the the SQLiteCpp wrapper +# +# Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) +# +# Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt +# or copy at http://opensource.org/licenses/MIT) +cmake_minimum_required(VERSION 3.5...4.0) +project(SQLiteCpp_Example VERSION 2.0) + +# SQLiteC++ 3.x now requires C++11 compiler +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Add SQLite3 C++ wrapper around sqlite3 library (and sqlite3 itself provided for ease of use) +# Here you can set CMake variables to avoid building Example, as well as cpplint, cppcheck... +# or set them in the cmake command line (see for instance provided build.bat/build.sh scripts) +set(SQLITECPP_RUN_CPPCHECK OFF CACHE BOOL "" FORCE) +set(SQLITECPP_RUN_CPPLINT OFF CACHE BOOL "" FORCE) +set(SQLITECPP_USE_STATIC_RUNTIME OFF CACHE BOOL "" FORCE) +add_subdirectory(../.. SQLiteCpp) # out-of-source build requires explicit subdir name for compilation artifacts + +# Add main.cpp example source code to the executable +add_executable(SQLiteCpp_Example src/main.cpp) + +# Link SQLiteCpp_example1 with SQLiteCpp +target_link_libraries(SQLiteCpp_Example SQLiteCpp) diff --git a/examples/example2/README.md b/examples/example2/README.md index fd5cecde..09773c24 100644 --- a/examples/example2/README.md +++ b/examples/example2/README.md @@ -1,8 +1,8 @@ -examples/example2 - SQLiteCpp_Example -------------------------------------- - -SQLiteCpp_Example demonstrates how to use SQLiteCpp as a subdirectory of a CMake project. - -See https://github.com/SRombauts/SQLiteCpp_Example - +examples/example2 - SQLiteCpp_Example +------------------------------------- + +SQLiteCpp_Example demonstrates how to use SQLiteCpp as a subdirectory of a CMake project. + +See https://github.com/SRombauts/SQLiteCpp_Example + See also examples/example1 for the main example on how to use SQLiteCpp in a C++ project \ No newline at end of file diff --git a/meson_options.txt b/meson_options.txt index 2fa1ee20..a29a5a36 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,24 +1,24 @@ -# Options relative to SQLite and SQLiteC++ functions -## Enable the use of SQLite column metadata and Column::getColumnOriginName() method, -## Require that the sqlite3 library is also compiled with this flag (default under Debian/Ubuntu, but not on Mac OS X). -option('SQLITE_ENABLE_COLUMN_METADATA', type: 'boolean', value: false, description: 'Enable Column::getColumnOriginName(). Require support from sqlite3 library.') -## Enable the user definition of a assertion_failed() handler (default to false, easier to handler for beginners). -option('SQLITE_ENABLE_ASSERT_HANDLER', type: 'boolean', value: false, description: 'Enable the user definition of a assertion_failed() handler.') -## Enable database encryption API. Requires implementations of sqlite3_key & sqlite3_rekey. -## SQLCipher, SQLite Multiple Ciphers, and the commercial SQLite Encryption Extensions provide -## these API functions for encrypting a database. -option('SQLITE_HAS_CODEC', type: 'boolean', value: false, description: 'Enable database encryption API. Not available in the public release of SQLite.') -## Force forward declaration of legacy struct sqlite3_value (pre SQLite 3.19) -option('SQLITE_USE_LEGACY_STRUCT', type: 'boolean', value: false, description: 'Fallback to forward declaration of legacy struct sqlite3_value (pre SQLite 3.19)') -## Enable ommit load extension -option('SQLITE_OMIT_LOAD_EXTENSION', type: 'boolean', value: false, description: 'Enable ommit load extension.') -## Disable the support for std::filesystem (C++17) -option('SQLITECPP_DISABLE_STD_FILESYSTEM', type: 'boolean', value: false, description: 'Disable the support for std::filesystem (C++17)') -## Disable the support for sqlite3_expanded_sql (since SQLite 3.14.0) -option('SQLITECPP_DISABLE_SQLITE3_EXPANDED_SQL', type: 'boolean', value: false, description: 'Disable the support for sqlite3_expanded_sql (since SQLite 3.14.0)') -## Stack protection is not supported on MinGW-W64 on Windows, allow this flag to be turned off. -option('SQLITECPP_USE_STACK_PROTECTION', type: 'boolean', value: true, description: 'Enable stack protection for MySQL.') -## Enable build for the tests of SQLiteC++ -option('SQLITECPP_BUILD_TESTS', type: 'boolean', value: false, description: 'Build SQLiteC++ unit tests.') -## Build the examples of SQLiteC++ -option('SQLITECPP_BUILD_EXAMPLES', type: 'boolean', value: false, description: 'Build SQLiteC++ examples.') +# Options relative to SQLite and SQLiteC++ functions +## Enable the use of SQLite column metadata and Column::getColumnOriginName() method, +## Require that the sqlite3 library is also compiled with this flag (default under Debian/Ubuntu, but not on Mac OS X). +option('SQLITE_ENABLE_COLUMN_METADATA', type: 'boolean', value: false, description: 'Enable Column::getColumnOriginName(). Require support from sqlite3 library.') +## Enable the user definition of a assertion_failed() handler (default to false, easier to handler for beginners). +option('SQLITE_ENABLE_ASSERT_HANDLER', type: 'boolean', value: false, description: 'Enable the user definition of a assertion_failed() handler.') +## Enable database encryption API. Requires implementations of sqlite3_key & sqlite3_rekey. +## SQLCipher, SQLite Multiple Ciphers, and the commercial SQLite Encryption Extensions provide +## these API functions for encrypting a database. +option('SQLITE_HAS_CODEC', type: 'boolean', value: false, description: 'Enable database encryption API. Not available in the public release of SQLite.') +## Force forward declaration of legacy struct sqlite3_value (pre SQLite 3.19) +option('SQLITE_USE_LEGACY_STRUCT', type: 'boolean', value: false, description: 'Fallback to forward declaration of legacy struct sqlite3_value (pre SQLite 3.19)') +## Enable ommit load extension +option('SQLITE_OMIT_LOAD_EXTENSION', type: 'boolean', value: false, description: 'Enable ommit load extension.') +## Disable the support for std::filesystem (C++17) +option('SQLITECPP_DISABLE_STD_FILESYSTEM', type: 'boolean', value: false, description: 'Disable the support for std::filesystem (C++17)') +## Disable the support for sqlite3_expanded_sql (since SQLite 3.14.0) +option('SQLITECPP_DISABLE_SQLITE3_EXPANDED_SQL', type: 'boolean', value: false, description: 'Disable the support for sqlite3_expanded_sql (since SQLite 3.14.0)') +## Stack protection is not supported on MinGW-W64 on Windows, allow this flag to be turned off. +option('SQLITECPP_USE_STACK_PROTECTION', type: 'boolean', value: true, description: 'Enable stack protection for MySQL.') +## Enable build for the tests of SQLiteC++ +option('SQLITECPP_BUILD_TESTS', type: 'boolean', value: false, description: 'Build SQLiteC++ unit tests.') +## Build the examples of SQLiteC++ +option('SQLITECPP_BUILD_EXAMPLES', type: 'boolean', value: false, description: 'Build SQLiteC++ examples.') diff --git a/sqlite3/README.md b/sqlite3/README.md index 61890922..f301a4c8 100644 --- a/sqlite3/README.md +++ b/sqlite3/README.md @@ -1,15 +1,15 @@ -sqlite3 -------- - -Copyright (c) 2012-2026 Sebastien Rombauts (sebastien.rombauts@gmail.com) - -"sqlite3.c" and "sqlite3.h" files from sqlite-amalgamation-3530200.zip (SQLite 3.53.2 2026-06-03) - -Those files are provided for easy setup and compatibility under Windows/Linux/MacOS. -They are used by default by the CMake build. - -Use -DSQLITECPP_INTERNAL_SQLITE=OFF to link against the Linux "libsqlite3-dev" package instead. - -### License: - -All of the code and documentation in SQLite has been dedicated to the public domain by the authors. +sqlite3 +------- + +Copyright (c) 2012-2026 Sebastien Rombauts (sebastien.rombauts@gmail.com) + +"sqlite3.c" and "sqlite3.h" files from sqlite-amalgamation-3530200.zip (SQLite 3.53.2 2026-06-03) + +Those files are provided for easy setup and compatibility under Windows/Linux/MacOS. +They are used by default by the CMake build. + +Use -DSQLITECPP_INTERNAL_SQLITE=OFF to link against the Linux "libsqlite3-dev" package instead. + +### License: + +All of the code and documentation in SQLite has been dedicated to the public domain by the authors. diff --git a/subprojects/.gitignore b/subprojects/.gitignore index b9ebc324..bf32ff01 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -1,5 +1,5 @@ -#ignore everything here -* -# but not the wrap files and the .gitignore -!*.wrap +#ignore everything here +* +# but not the wrap files and the .gitignore +!*.wrap !*.gitignore \ No newline at end of file diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index 5d6c07dd..ca0533f5 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -1,644 +1,644 @@ -/** - * @file Database_test.cpp - * @ingroup tests - * @brief Test of a SQLiteCpp Database. - * - * Copyright (c) 2012-2025 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ - -#include - -#include // for SQLITE_ERROR and SQLITE_VERSION_NUMBER - -#include - -#ifdef SQLITECPP_HAVE_STD_FILESYSTEM -#include -#endif // c++17 - -#include -#include - -#ifdef SQLITECPP_ENABLE_ASSERT_HANDLER -namespace SQLite -{ -/// definition of the assertion handler enabled when SQLITECPP_ENABLE_ASSERT_HANDLER is defined in the project (CMakeList.txt) -void assertion_failed(const char* apFile, const long apLine, const char* apFunc, const char* apExpr, const char* apMsg) -{ - // TODO: unit test that this assertion callback get called (already tested manually) - std::cout << "assertion_failed(" << apFile << ", " << apLine << ", " << apFunc << ", " << apExpr << ", " << apMsg << ")\n"; -} -} -#endif - -// NOTE on macOS FindSQLite3 find an unrelated sqlite3.h from Mono.framework that doesn't match the actual package version! -#if defined(SQLITECPP_INTERNAL_SQLITE) || !defined(__APPLE__) -TEST(SQLiteCpp, version) -{ - EXPECT_STREQ(SQLITE_VERSION, SQLite::VERSION); - EXPECT_EQ (SQLITE_VERSION_NUMBER, SQLite::VERSION_NUMBER); - EXPECT_STREQ(SQLITE_VERSION, SQLite::getLibVersion()); - EXPECT_EQ (SQLITE_VERSION_NUMBER, SQLite::getLibVersionNumber()); -} -#endif - -TEST(Database, ctorExecCreateDropExist) -{ - remove("test.db3"); - { - // Try to open a non-existing database - std::string filename = "test.db3"; - EXPECT_THROW(SQLite::Database not_found(filename), SQLite::Exception); - - // Create a new database using a string or a std::filesystem::path if using c++17 and a - // compatible compiler - #ifdef SQLITECPP_HAVE_STD_FILESYSTEM - SQLite::Database db(std::filesystem::path("test.db3"), SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); - #else - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); - #endif // have std::filesystem - - EXPECT_STREQ("test.db3", db.getFilename().c_str()); - EXPECT_FALSE(db.tableExists("test")); - EXPECT_FALSE(db.tableExists(std::string("test"))); - EXPECT_EQ(0, db.getLastInsertRowid()); - - EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); - EXPECT_TRUE(db.tableExists("test")); - EXPECT_TRUE(db.tableExists(std::string("test"))); - EXPECT_EQ(0, db.getLastInsertRowid()); - - EXPECT_EQ(0, db.exec("DROP TABLE IF EXISTS test")); - EXPECT_FALSE(db.tableExists("test")); - EXPECT_FALSE(db.tableExists(std::string("test"))); - EXPECT_EQ(0, db.getLastInsertRowid()); - } // Close DB test.db3 - remove("test.db3"); -} - -#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1600) - -SQLite::Database DatabaseBuilder(const char* apName) -{ - return SQLite::Database(apName, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); -} - -TEST(Database, moveConstructor) -{ - remove("test.db3"); - { - // Create a new database, using the move constructor - SQLite::Database db = DatabaseBuilder("test.db3"); - EXPECT_FALSE(db.tableExists("test")); - EXPECT_TRUE(db.getHandle() != NULL); - SQLite::Database moved = std::move(db); - EXPECT_TRUE(db.getHandle() == NULL); - EXPECT_TRUE(moved.getHandle() != NULL); - EXPECT_FALSE(moved.tableExists("test")); - } // Close DB test.db3 - remove("test.db3"); -} - -#endif - -TEST(Database, createCloseReopen) -{ - remove("test.db3"); - { - // Try to open the non-existing database - EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception); - - // Create a new database - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); - EXPECT_FALSE(db.tableExists("test")); - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - EXPECT_TRUE(db.tableExists("test")); - } // Close DB test.db3 - { - // Reopen the database file - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); - EXPECT_TRUE(db.tableExists("test")); - } // Close DB test.db3 - remove("test.db3"); -} - -TEST(Database, inMemory) -{ - { - // Create a new database - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); - EXPECT_FALSE(db.tableExists("test")); - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - EXPECT_TRUE(db.tableExists("test")); - // Create a new database: not shared with the above db - SQLite::Database db2(":memory:"); - EXPECT_FALSE(db2.tableExists("test")); - } // Close an destroy DBs - { - // Create a new database: no more "test" table - SQLite::Database db(":memory:"); - EXPECT_FALSE(db.tableExists("test")); - } // Close an destroy DB -} - -TEST(Database, backup) -{ - // Create a new in-memory database - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); - EXPECT_FALSE(db.tableExists("test")); - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - EXPECT_TRUE(db.tableExists("test")); - - // Export the data into a file - remove("backup.db3"); - EXPECT_NO_THROW(db.backup("backup.db3", SQLite::Database::Save)); - - // Trash the table - db.exec("DROP TABLE test;"); - EXPECT_FALSE(db.tableExists("test")); - - // Import the data back from the file - EXPECT_NO_THROW(db.backup("backup.db3", SQLite::Database::Load)); - remove("backup.db3"); - - EXPECT_TRUE(db.tableExists("test")); -} - -#if SQLITE_VERSION_NUMBER >= 3007015 // SQLite v3.7.15 is first version with PRAGMA busy_timeout -TEST(Database, busyTimeout) -{ - { - // Create a new database with default timeout of 0ms - SQLite::Database db(":memory:"); - // Busy timeout default to 0ms: any contention between threads or process leads to SQLITE_BUSY error - EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); - - // Set a non null busy timeout: any contention between threads will leads to as much retry as possible during the time - db.setBusyTimeout(5000); - EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); - - // Reset timeout to 0 - db.setBusyTimeout(0); - EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); - } - { - // Create a new database with a non null busy timeout - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE, 5000); - EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); - - // Reset timeout to null - db.setBusyTimeout(0); - EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); - } - { - // Create a new database with a non null busy timeout - const std::string memory = ":memory:"; - SQLite::Database db(memory, SQLite::OPEN_READWRITE, 5000); - EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); - - // Reset timeout to null - db.setBusyTimeout(0); - EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); - } -} -#endif // SQLITE_VERSION_NUMBER >= 3007015 - -TEST(Database, exec) -{ - // Create a new database - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); - - // Create a new table with an explicit "id" column aliasing the underlying rowid - // NOTE: here exec() returns 0 only because it is the first statements since database connexion, - // but its return is an undefined value for "CREATE TABLE" statements. - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - EXPECT_EQ(0, db.getChanges()); - EXPECT_EQ(0, db.getLastInsertRowid()); - EXPECT_EQ(0, db.getTotalChanges()); - - // first row : insert the "first" text value into new row of id 1 - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first')")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(1, db.getLastInsertRowid()); - EXPECT_EQ(1, db.getTotalChanges()); - - // second row : insert the "second" text value into new row of id 2 - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'second')")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(2, db.getLastInsertRowid()); - EXPECT_EQ(2, db.getTotalChanges()); - - // third row : insert the "third" text value into new row of id 3 - const std::string insert("INSERT INTO test VALUES (NULL, 'third')"); - EXPECT_EQ(1, db.exec(insert)); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(3, db.getLastInsertRowid()); - EXPECT_EQ(3, db.getTotalChanges()); - - // update the second row : update text value to "second_updated" - EXPECT_EQ(1, db.exec("UPDATE test SET value='second-updated' WHERE id='2'")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(3, db.getLastInsertRowid()); // last inserted row ID is still 3 - EXPECT_EQ(4, db.getTotalChanges()); - - // delete the third row - EXPECT_EQ(1, db.exec("DELETE FROM test WHERE id='3'")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(3, db.getLastInsertRowid()); - EXPECT_EQ(5, db.getTotalChanges()); - - // drop the whole table, ie the two remaining columns - // NOTE: here exec() returns 1, like the last time, as it is an undefined value for "DROP TABLE" statements - db.exec("DROP TABLE IF EXISTS test"); - EXPECT_FALSE(db.tableExists("test")); - EXPECT_EQ(5, db.getTotalChanges()); - - // Re-Create the same table - // NOTE: here exec() returns 1, like the last time, as it is an undefined value for "CREATE TABLE" statements - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - EXPECT_EQ(5, db.getTotalChanges()); - - // insert two rows with two *different* statements => returns only 1, ie. for the second INSERT statement - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first');INSERT INTO test VALUES (NULL, 'second');")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(2, db.getLastInsertRowid()); - EXPECT_EQ(7, db.getTotalChanges()); - -#if (SQLITE_VERSION_NUMBER >= 3007011) - // insert two rows with only one statement (starting with SQLite 3.7.11) => returns 2 - EXPECT_EQ(2, db.exec("INSERT INTO test VALUES (NULL, 'third'), (NULL, 'fourth');")); - EXPECT_EQ(2, db.getChanges()); - EXPECT_EQ(4, db.getLastInsertRowid()); - EXPECT_EQ(9, db.getTotalChanges()); -#endif -} - -TEST(Database, tryExec) -{ - // Create a new database - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); - - // Create a new table with an explicit "id" column aliasing the underlying rowid - EXPECT_EQ(SQLite::OK, db.tryExec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); - EXPECT_EQ(0, db.getChanges()); - EXPECT_EQ(0, db.getLastInsertRowid()); - EXPECT_EQ(0, db.getTotalChanges()); - - // first row : insert the "first" text value into new row of id 1 - EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'first')")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(1, db.getLastInsertRowid()); - EXPECT_EQ(1, db.getTotalChanges()); - - // second row : insert the "second" text value into new row of id 2 - EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'second')")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(2, db.getLastInsertRowid()); - EXPECT_EQ(2, db.getTotalChanges()); - - // third row : insert the "third" text value into new row of id 3 - const std::string insert("INSERT INTO test VALUES (NULL, 'third')"); - EXPECT_EQ(SQLite::OK, db.tryExec(insert)); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(3, db.getLastInsertRowid()); - EXPECT_EQ(3, db.getTotalChanges()); - - // update the second row : update text value to "second_updated" - EXPECT_EQ(SQLite::OK, db.tryExec("UPDATE test SET value='second-updated' WHERE id='2'")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(3, db.getLastInsertRowid()); // last inserted row ID is still 3 - EXPECT_EQ(4, db.getTotalChanges()); - - // delete the third row - EXPECT_EQ(SQLite::OK, db.tryExec("DELETE FROM test WHERE id='3'")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(3, db.getLastInsertRowid()); - EXPECT_EQ(5, db.getTotalChanges()); - - // drop the whole table, ie the two remaining columns - EXPECT_EQ(SQLite::OK, db.tryExec("DROP TABLE IF EXISTS test")); - EXPECT_FALSE(db.tableExists("test")); - EXPECT_EQ(5, db.getTotalChanges()); - - // Re-Create the same table - EXPECT_EQ(SQLite::OK, db.tryExec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); - EXPECT_EQ(5, db.getTotalChanges()); - - // insert two rows with two *different* statements => only 1 change, ie. for the second INSERT statement - EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'first');INSERT INTO test VALUES (NULL, 'second');")); - EXPECT_EQ(1, db.getChanges()); - EXPECT_EQ(2, db.getLastInsertRowid()); - EXPECT_EQ(7, db.getTotalChanges()); - -#if (SQLITE_VERSION_NUMBER >= 3007011) - // insert two rows with only one statement (starting with SQLite 3.7.11) - EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'third'), (NULL, 'fourth');")); - EXPECT_EQ(2, db.getChanges()); - EXPECT_EQ(4, db.getLastInsertRowid()); - EXPECT_EQ(9, db.getTotalChanges()); -#endif -} - -TEST(Database, execAndGet) -{ - // Create a new database - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); - - // Create a new table with an explicit "id" column aliasing the underlying rowid - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)"); - - // insert a few rows - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first', 3)")); - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'second', 5)")); - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'third', 7)")); - - // Get a single value result with an easy to use shortcut - EXPECT_STREQ("second", db.execAndGet("SELECT value FROM test WHERE id=2")); - EXPECT_STREQ("third", db.execAndGet("SELECT value FROM test WHERE weight=7")); - const std::string query("SELECT weight FROM test WHERE value='first'"); - EXPECT_EQ(3, db.execAndGet(query).getInt()); -} - -TEST(Database, execException) -{ - // Create a new database - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); - EXPECT_EQ(SQLite::OK, db.getErrorCode()); - EXPECT_EQ(SQLite::OK, db.getExtendedErrorCode()); - - // exception with SQL error: "no such table" - EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, 'first', 3)"), SQLite::Exception); - EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); - EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); - EXPECT_STREQ("no such table: test", db.getErrorMsg()); - - // Create a new table - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)"); - EXPECT_EQ(SQLite::OK, db.getErrorCode()); - EXPECT_EQ(SQLite::OK, db.getExtendedErrorCode()); - EXPECT_STREQ("not an error", db.getErrorMsg()); - - // exception with SQL error: "table test has 3 columns but 2 values were supplied" - EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, 3)"), SQLite::Exception); - EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); - EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); - EXPECT_STREQ("table test has 3 columns but 2 values were supplied", db.getErrorMsg()); - - // exception with SQL error: "No row to get a column from" - EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value='first'"), SQLite::Exception); - - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first', 3)")); - // exception with SQL error: "No row to get a column from" - EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value='second'"), SQLite::Exception); - - // Add a row with more values than columns in the table: "table test has 3 columns but 4 values were supplied" - EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, 'first', 123, 0.123)"), SQLite::Exception); - EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); - EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); - EXPECT_STREQ("table test has 3 columns but 4 values were supplied", db.getErrorMsg()); -} - -TEST(Database, tryExecError) -{ - // Create a new database - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); - EXPECT_EQ(SQLite::OK, db.getErrorCode()); - EXPECT_EQ(SQLite::OK, db.getExtendedErrorCode()); - - // Insert into nonexistent table: "no such table" - EXPECT_EQ(SQLITE_ERROR, db.tryExec("INSERT INTO test VALUES (NULL, 'first', 3)")); - EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); - EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); - EXPECT_STREQ("no such table: test", db.getErrorMsg()); - - // Create a new table - EXPECT_EQ(SQLite::OK, db.tryExec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)")); - EXPECT_EQ(SQLite::OK, db.getErrorCode()); - EXPECT_EQ(SQLite::OK, db.getExtendedErrorCode()); - EXPECT_STREQ("not an error", db.getErrorMsg()); - - // Add a row with fewer values than columns in the table: "table test has 3 columns but 2 values were supplied" - EXPECT_EQ(SQLITE_ERROR, db.tryExec("INSERT INTO test VALUES (NULL, 3)")); - EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); - EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); - EXPECT_STREQ("table test has 3 columns but 2 values were supplied", db.getErrorMsg()); - - // Add a row with more values than columns in the table: "table test has 3 columns but 4 values were supplied" - EXPECT_EQ(SQLITE_ERROR, db.tryExec("INSERT INTO test VALUES (NULL, 'first', 123, 0.123)")); - EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); - EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); - EXPECT_STREQ("table test has 3 columns but 4 values were supplied", db.getErrorMsg()); - - // Create a first row - EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'first', 3)")); - EXPECT_EQ(1, db.getLastInsertRowid()); - - // Try to insert a new row with the same PRIMARY KEY: "UNIQUE constraint failed: test.id" - EXPECT_EQ(SQLITE_CONSTRAINT, db.tryExec("INSERT INTO test VALUES (1, 'impossible', 456)")); - EXPECT_EQ(SQLITE_CONSTRAINT, db.getErrorCode()); - EXPECT_EQ(SQLITE_CONSTRAINT_PRIMARYKEY, db.getExtendedErrorCode()); - EXPECT_STREQ("UNIQUE constraint failed: test.id", db.getErrorMsg()); -} - -// From https://stackoverflow.com/a/8283265/1163698 How can I create a user-defined function in SQLite? -static void firstchar(sqlite3_context *context, int argc, sqlite3_value **argv) -{ - if (argc == 1) - { - const unsigned char *text = sqlite3_value_text(argv[0]); - if (text && text[0]) - { - char result[2]; - result[0] = text[0]; result[1] = '\0'; - sqlite3_result_text(context, result, -1, SQLITE_TRANSIENT); - return; - } - } - sqlite3_result_null(context); -} - -TEST(Database, createFunction) -{ - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first')")); - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'second')")); - - // exception with SQL error: "no such function: firstchar" - EXPECT_THROW(db.exec("SELECT firstchar(value) FROM test WHERE id=1"), SQLite::Exception); - - db.createFunction("firstchar", 1, true, nullptr, &firstchar, nullptr, nullptr, nullptr); - - EXPECT_EQ(1, db.exec("SELECT firstchar(value) FROM test WHERE id=1")); -} - -TEST(Database, loadExtension) -{ - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); - - // Try to load a non-existing extension (no dynamic library found) - EXPECT_THROW(db.loadExtension("non-existing-extension", "entry-point"), SQLite::Exception); - - // TODO: test a proper extension -} - -TEST(Database, getHeaderInfo) -{ - remove("test.db3"); - { - // Call without passing a database file name - EXPECT_THROW(SQLite::Database::getHeaderInfo(""),SQLite::Exception); - - // Call with a non-existent database - EXPECT_THROW(SQLite::Database::getHeaderInfo("test.db3"), SQLite::Exception); - - // Simulate an incomplete header by writing garbage to a file - { - const unsigned char badData[] = "garbage..."; - const char* pBadData = reinterpret_cast(&badData[0]); - - remove("short.db3"); - std::ofstream corruptDb; - corruptDb.open("short.db3", std::ios::app | std::ios::binary); - corruptDb.write(pBadData, sizeof(badData)); - corruptDb.close(); - - EXPECT_THROW(SQLite::Database::getHeaderInfo("short.db3"), SQLite::Exception); - remove("short.db3"); - } - - // Simulate a corrupt header by writing garbage to a file - { - const unsigned char badData[100] = "garbage..."; - const char* pBadData = reinterpret_cast(&badData[0]); - - remove("corrupt.db3"); - std::ofstream corruptDb; - corruptDb.open("corrupt.db3", std::ios::app | std::ios::binary); - corruptDb.write(pBadData, sizeof(badData)); - corruptDb.close(); - - EXPECT_THROW(SQLite::Database::getHeaderInfo("corrupt.db3"), SQLite::Exception); - remove("corrupt.db3"); - } - - // Create a new database - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - - // Set assorted SQLite header values using associated PRAGMA - db.exec("PRAGMA main.user_version = 12345"); - db.exec("PRAGMA main.application_id = 2468"); - - // Parse header fields from test database - const SQLite::Header h = db.getHeaderInfo(); - - //Test header values explicitly set via PRAGMA statements - EXPECT_EQ(h.userVersion, 12345); - EXPECT_EQ(h.applicationId, 2468); - - //Test header values with expected default values - EXPECT_EQ(h.pageSizeBytes, 4096); - EXPECT_EQ(h.fileFormatWriteVersion,1); - EXPECT_EQ(h.fileFormatReadVersion,1); - EXPECT_EQ(h.reservedSpaceBytes,0); - EXPECT_EQ(h.maxEmbeddedPayloadFrac, 64); - EXPECT_EQ(h.minEmbeddedPayloadFrac, 32); - EXPECT_EQ(h.leafPayloadFrac, 32); - EXPECT_EQ(h.fileChangeCounter, 3); - EXPECT_EQ(h.databaseSizePages, 2); - EXPECT_EQ(h.firstFreelistTrunkPage, 0); - EXPECT_EQ(h.totalFreelistPages, 0); - EXPECT_EQ(h.schemaCookie, 1); - EXPECT_EQ(h.schemaFormatNumber, 4); - EXPECT_EQ(h.defaultPageCacheSizeBytes, 0); - EXPECT_EQ(h.largestBTreePageNumber, 0); - EXPECT_EQ(h.databaseTextEncoding, 1); - EXPECT_EQ(h.incrementalVaccumMode, 0); - EXPECT_EQ(h.versionValidFor, 3); -// NOTE on macOS FindSQLite3 find an unrelated sqlite3.h from Mono.framework that doesn't match the actual package version! -#if defined(SQLITECPP_INTERNAL_SQLITE) || !defined(__APPLE__) - EXPECT_EQ(h.sqliteVersion, SQLITE_VERSION_NUMBER); -#endif - } - remove("test.db3"); -} - -#ifdef SQLITE_HAS_CODEC -TEST(Database, encryptAndDecrypt) -{ - remove("test.db3"); - { - // Try to open the non-existing database - EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception); - EXPECT_THROW(SQLite::Database::isUnencrypted("test.db3"), SQLite::Exception); - EXPECT_THROW(SQLite::Database::isUnencrypted(""), SQLite::Exception); - - // Create a new database - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); - EXPECT_FALSE(db.tableExists("test")); - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - EXPECT_TRUE(db.tableExists("test")); - } // Close DB test.db3 - { - // Reopen the database file and encrypt it - EXPECT_TRUE(SQLite::Database::isUnencrypted("test.db3")); - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE); - // Encrypt the database - db.rekey("123secret"); - } // Close DB test.db3 - { - // Reopen the database file and try to use it - EXPECT_FALSE(SQLite::Database::isUnencrypted("test.db3")); - SQLite::Database db("test.db3", SQLite::OPEN_READONLY); - EXPECT_THROW(db.tableExists("test"), SQLite::Exception); - db.key("123secret"); - EXPECT_TRUE(db.tableExists("test")); - } // Close DB test.db3 - { - // Reopen the database file and decrypt it - EXPECT_FALSE(SQLite::Database::isUnencrypted("test.db3")); - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE); - // Decrypt the database - db.key("123secret"); - db.rekey(""); - } // Close DB test.db3 - { - // Reopen the database file and use it - EXPECT_TRUE(SQLite::Database::isUnencrypted("test.db3")); - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE); - EXPECT_TRUE(db.tableExists("test")); - } // Close DB test.db3 - remove("test.db3"); -} -#else // SQLITE_HAS_CODEC -TEST(Database, encryptAndDecrypt) -{ - remove("test.db3"); - { - // Try to open the non-existing database - EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception); - EXPECT_THROW(SQLite::Database::isUnencrypted("test.db3"), SQLite::Exception); - EXPECT_THROW(SQLite::Database::isUnencrypted(""), SQLite::Exception); - - // Create a new database - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); - EXPECT_FALSE(db.tableExists("test")); - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - EXPECT_TRUE(db.tableExists("test")); - } // Close DB test.db3 - { - // Reopen the database file and encrypt it - EXPECT_TRUE(SQLite::Database::isUnencrypted("test.db3")); - SQLite::Database db("test.db3", SQLite::OPEN_READWRITE); - // Encrypt the database - EXPECT_THROW(db.key("123secret"), SQLite::Exception); - EXPECT_THROW(db.rekey("123secret"), SQLite::Exception); - } // Close DB test.db3 - remove("test.db3"); -} -#endif // SQLITE_HAS_CODEC +/** + * @file Database_test.cpp + * @ingroup tests + * @brief Test of a SQLiteCpp Database. + * + * Copyright (c) 2012-2025 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ + +#include + +#include // for SQLITE_ERROR and SQLITE_VERSION_NUMBER + +#include + +#ifdef SQLITECPP_HAVE_STD_FILESYSTEM +#include +#endif // c++17 + +#include +#include + +#ifdef SQLITECPP_ENABLE_ASSERT_HANDLER +namespace SQLite +{ +/// definition of the assertion handler enabled when SQLITECPP_ENABLE_ASSERT_HANDLER is defined in the project (CMakeList.txt) +void assertion_failed(const char* apFile, const long apLine, const char* apFunc, const char* apExpr, const char* apMsg) +{ + // TODO: unit test that this assertion callback get called (already tested manually) + std::cout << "assertion_failed(" << apFile << ", " << apLine << ", " << apFunc << ", " << apExpr << ", " << apMsg << ")\n"; +} +} +#endif + +// NOTE on macOS FindSQLite3 find an unrelated sqlite3.h from Mono.framework that doesn't match the actual package version! +#if defined(SQLITECPP_INTERNAL_SQLITE) || !defined(__APPLE__) +TEST(SQLiteCpp, version) +{ + EXPECT_STREQ(SQLITE_VERSION, SQLite::VERSION); + EXPECT_EQ (SQLITE_VERSION_NUMBER, SQLite::VERSION_NUMBER); + EXPECT_STREQ(SQLITE_VERSION, SQLite::getLibVersion()); + EXPECT_EQ (SQLITE_VERSION_NUMBER, SQLite::getLibVersionNumber()); +} +#endif + +TEST(Database, ctorExecCreateDropExist) +{ + remove("test.db3"); + { + // Try to open a non-existing database + std::string filename = "test.db3"; + EXPECT_THROW(SQLite::Database not_found(filename), SQLite::Exception); + + // Create a new database using a string or a std::filesystem::path if using c++17 and a + // compatible compiler + #ifdef SQLITECPP_HAVE_STD_FILESYSTEM + SQLite::Database db(std::filesystem::path("test.db3"), SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); + #else + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); + #endif // have std::filesystem + + EXPECT_STREQ("test.db3", db.getFilename().c_str()); + EXPECT_FALSE(db.tableExists("test")); + EXPECT_FALSE(db.tableExists(std::string("test"))); + EXPECT_EQ(0, db.getLastInsertRowid()); + + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); + EXPECT_TRUE(db.tableExists("test")); + EXPECT_TRUE(db.tableExists(std::string("test"))); + EXPECT_EQ(0, db.getLastInsertRowid()); + + EXPECT_EQ(0, db.exec("DROP TABLE IF EXISTS test")); + EXPECT_FALSE(db.tableExists("test")); + EXPECT_FALSE(db.tableExists(std::string("test"))); + EXPECT_EQ(0, db.getLastInsertRowid()); + } // Close DB test.db3 + remove("test.db3"); +} + +#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1600) + +SQLite::Database DatabaseBuilder(const char* apName) +{ + return SQLite::Database(apName, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); +} + +TEST(Database, moveConstructor) +{ + remove("test.db3"); + { + // Create a new database, using the move constructor + SQLite::Database db = DatabaseBuilder("test.db3"); + EXPECT_FALSE(db.tableExists("test")); + EXPECT_TRUE(db.getHandle() != NULL); + SQLite::Database moved = std::move(db); + EXPECT_TRUE(db.getHandle() == NULL); + EXPECT_TRUE(moved.getHandle() != NULL); + EXPECT_FALSE(moved.tableExists("test")); + } // Close DB test.db3 + remove("test.db3"); +} + +#endif + +TEST(Database, createCloseReopen) +{ + remove("test.db3"); + { + // Try to open the non-existing database + EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception); + + // Create a new database + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); + EXPECT_FALSE(db.tableExists("test")); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_TRUE(db.tableExists("test")); + } // Close DB test.db3 + { + // Reopen the database file + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); + EXPECT_TRUE(db.tableExists("test")); + } // Close DB test.db3 + remove("test.db3"); +} + +TEST(Database, inMemory) +{ + { + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); + EXPECT_FALSE(db.tableExists("test")); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_TRUE(db.tableExists("test")); + // Create a new database: not shared with the above db + SQLite::Database db2(":memory:"); + EXPECT_FALSE(db2.tableExists("test")); + } // Close an destroy DBs + { + // Create a new database: no more "test" table + SQLite::Database db(":memory:"); + EXPECT_FALSE(db.tableExists("test")); + } // Close an destroy DB +} + +TEST(Database, backup) +{ + // Create a new in-memory database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); + EXPECT_FALSE(db.tableExists("test")); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_TRUE(db.tableExists("test")); + + // Export the data into a file + remove("backup.db3"); + EXPECT_NO_THROW(db.backup("backup.db3", SQLite::Database::Save)); + + // Trash the table + db.exec("DROP TABLE test;"); + EXPECT_FALSE(db.tableExists("test")); + + // Import the data back from the file + EXPECT_NO_THROW(db.backup("backup.db3", SQLite::Database::Load)); + remove("backup.db3"); + + EXPECT_TRUE(db.tableExists("test")); +} + +#if SQLITE_VERSION_NUMBER >= 3007015 // SQLite v3.7.15 is first version with PRAGMA busy_timeout +TEST(Database, busyTimeout) +{ + { + // Create a new database with default timeout of 0ms + SQLite::Database db(":memory:"); + // Busy timeout default to 0ms: any contention between threads or process leads to SQLITE_BUSY error + EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); + + // Set a non null busy timeout: any contention between threads will leads to as much retry as possible during the time + db.setBusyTimeout(5000); + EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); + + // Reset timeout to 0 + db.setBusyTimeout(0); + EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); + } + { + // Create a new database with a non null busy timeout + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE, 5000); + EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); + + // Reset timeout to null + db.setBusyTimeout(0); + EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); + } + { + // Create a new database with a non null busy timeout + const std::string memory = ":memory:"; + SQLite::Database db(memory, SQLite::OPEN_READWRITE, 5000); + EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); + + // Reset timeout to null + db.setBusyTimeout(0); + EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); + } +} +#endif // SQLITE_VERSION_NUMBER >= 3007015 + +TEST(Database, exec) +{ + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); + + // Create a new table with an explicit "id" column aliasing the underlying rowid + // NOTE: here exec() returns 0 only because it is the first statements since database connexion, + // but its return is an undefined value for "CREATE TABLE" statements. + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_EQ(0, db.getChanges()); + EXPECT_EQ(0, db.getLastInsertRowid()); + EXPECT_EQ(0, db.getTotalChanges()); + + // first row : insert the "first" text value into new row of id 1 + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first')")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(1, db.getLastInsertRowid()); + EXPECT_EQ(1, db.getTotalChanges()); + + // second row : insert the "second" text value into new row of id 2 + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'second')")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(2, db.getLastInsertRowid()); + EXPECT_EQ(2, db.getTotalChanges()); + + // third row : insert the "third" text value into new row of id 3 + const std::string insert("INSERT INTO test VALUES (NULL, 'third')"); + EXPECT_EQ(1, db.exec(insert)); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(3, db.getLastInsertRowid()); + EXPECT_EQ(3, db.getTotalChanges()); + + // update the second row : update text value to "second_updated" + EXPECT_EQ(1, db.exec("UPDATE test SET value='second-updated' WHERE id='2'")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(3, db.getLastInsertRowid()); // last inserted row ID is still 3 + EXPECT_EQ(4, db.getTotalChanges()); + + // delete the third row + EXPECT_EQ(1, db.exec("DELETE FROM test WHERE id='3'")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(3, db.getLastInsertRowid()); + EXPECT_EQ(5, db.getTotalChanges()); + + // drop the whole table, ie the two remaining columns + // NOTE: here exec() returns 1, like the last time, as it is an undefined value for "DROP TABLE" statements + db.exec("DROP TABLE IF EXISTS test"); + EXPECT_FALSE(db.tableExists("test")); + EXPECT_EQ(5, db.getTotalChanges()); + + // Re-Create the same table + // NOTE: here exec() returns 1, like the last time, as it is an undefined value for "CREATE TABLE" statements + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_EQ(5, db.getTotalChanges()); + + // insert two rows with two *different* statements => returns only 1, ie. for the second INSERT statement + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first');INSERT INTO test VALUES (NULL, 'second');")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(2, db.getLastInsertRowid()); + EXPECT_EQ(7, db.getTotalChanges()); + +#if (SQLITE_VERSION_NUMBER >= 3007011) + // insert two rows with only one statement (starting with SQLite 3.7.11) => returns 2 + EXPECT_EQ(2, db.exec("INSERT INTO test VALUES (NULL, 'third'), (NULL, 'fourth');")); + EXPECT_EQ(2, db.getChanges()); + EXPECT_EQ(4, db.getLastInsertRowid()); + EXPECT_EQ(9, db.getTotalChanges()); +#endif +} + +TEST(Database, tryExec) +{ + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); + + // Create a new table with an explicit "id" column aliasing the underlying rowid + EXPECT_EQ(SQLite::OK, db.tryExec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); + EXPECT_EQ(0, db.getChanges()); + EXPECT_EQ(0, db.getLastInsertRowid()); + EXPECT_EQ(0, db.getTotalChanges()); + + // first row : insert the "first" text value into new row of id 1 + EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'first')")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(1, db.getLastInsertRowid()); + EXPECT_EQ(1, db.getTotalChanges()); + + // second row : insert the "second" text value into new row of id 2 + EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'second')")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(2, db.getLastInsertRowid()); + EXPECT_EQ(2, db.getTotalChanges()); + + // third row : insert the "third" text value into new row of id 3 + const std::string insert("INSERT INTO test VALUES (NULL, 'third')"); + EXPECT_EQ(SQLite::OK, db.tryExec(insert)); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(3, db.getLastInsertRowid()); + EXPECT_EQ(3, db.getTotalChanges()); + + // update the second row : update text value to "second_updated" + EXPECT_EQ(SQLite::OK, db.tryExec("UPDATE test SET value='second-updated' WHERE id='2'")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(3, db.getLastInsertRowid()); // last inserted row ID is still 3 + EXPECT_EQ(4, db.getTotalChanges()); + + // delete the third row + EXPECT_EQ(SQLite::OK, db.tryExec("DELETE FROM test WHERE id='3'")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(3, db.getLastInsertRowid()); + EXPECT_EQ(5, db.getTotalChanges()); + + // drop the whole table, ie the two remaining columns + EXPECT_EQ(SQLite::OK, db.tryExec("DROP TABLE IF EXISTS test")); + EXPECT_FALSE(db.tableExists("test")); + EXPECT_EQ(5, db.getTotalChanges()); + + // Re-Create the same table + EXPECT_EQ(SQLite::OK, db.tryExec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); + EXPECT_EQ(5, db.getTotalChanges()); + + // insert two rows with two *different* statements => only 1 change, ie. for the second INSERT statement + EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'first');INSERT INTO test VALUES (NULL, 'second');")); + EXPECT_EQ(1, db.getChanges()); + EXPECT_EQ(2, db.getLastInsertRowid()); + EXPECT_EQ(7, db.getTotalChanges()); + +#if (SQLITE_VERSION_NUMBER >= 3007011) + // insert two rows with only one statement (starting with SQLite 3.7.11) + EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'third'), (NULL, 'fourth');")); + EXPECT_EQ(2, db.getChanges()); + EXPECT_EQ(4, db.getLastInsertRowid()); + EXPECT_EQ(9, db.getTotalChanges()); +#endif +} + +TEST(Database, execAndGet) +{ + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); + + // Create a new table with an explicit "id" column aliasing the underlying rowid + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)"); + + // insert a few rows + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first', 3)")); + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'second', 5)")); + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'third', 7)")); + + // Get a single value result with an easy to use shortcut + EXPECT_STREQ("second", db.execAndGet("SELECT value FROM test WHERE id=2")); + EXPECT_STREQ("third", db.execAndGet("SELECT value FROM test WHERE weight=7")); + const std::string query("SELECT weight FROM test WHERE value='first'"); + EXPECT_EQ(3, db.execAndGet(query).getInt()); +} + +TEST(Database, execException) +{ + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + EXPECT_EQ(SQLite::OK, db.getExtendedErrorCode()); + + // exception with SQL error: "no such table" + EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, 'first', 3)"), SQLite::Exception); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); + EXPECT_STREQ("no such table: test", db.getErrorMsg()); + + // Create a new table + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)"); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + EXPECT_EQ(SQLite::OK, db.getExtendedErrorCode()); + EXPECT_STREQ("not an error", db.getErrorMsg()); + + // exception with SQL error: "table test has 3 columns but 2 values were supplied" + EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, 3)"), SQLite::Exception); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); + EXPECT_STREQ("table test has 3 columns but 2 values were supplied", db.getErrorMsg()); + + // exception with SQL error: "No row to get a column from" + EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value='first'"), SQLite::Exception); + + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first', 3)")); + // exception with SQL error: "No row to get a column from" + EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value='second'"), SQLite::Exception); + + // Add a row with more values than columns in the table: "table test has 3 columns but 4 values were supplied" + EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, 'first', 123, 0.123)"), SQLite::Exception); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); + EXPECT_STREQ("table test has 3 columns but 4 values were supplied", db.getErrorMsg()); +} + +TEST(Database, tryExecError) +{ + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + EXPECT_EQ(SQLite::OK, db.getExtendedErrorCode()); + + // Insert into nonexistent table: "no such table" + EXPECT_EQ(SQLITE_ERROR, db.tryExec("INSERT INTO test VALUES (NULL, 'first', 3)")); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); + EXPECT_STREQ("no such table: test", db.getErrorMsg()); + + // Create a new table + EXPECT_EQ(SQLite::OK, db.tryExec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)")); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + EXPECT_EQ(SQLite::OK, db.getExtendedErrorCode()); + EXPECT_STREQ("not an error", db.getErrorMsg()); + + // Add a row with fewer values than columns in the table: "table test has 3 columns but 2 values were supplied" + EXPECT_EQ(SQLITE_ERROR, db.tryExec("INSERT INTO test VALUES (NULL, 3)")); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); + EXPECT_STREQ("table test has 3 columns but 2 values were supplied", db.getErrorMsg()); + + // Add a row with more values than columns in the table: "table test has 3 columns but 4 values were supplied" + EXPECT_EQ(SQLITE_ERROR, db.tryExec("INSERT INTO test VALUES (NULL, 'first', 123, 0.123)")); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); + EXPECT_STREQ("table test has 3 columns but 4 values were supplied", db.getErrorMsg()); + + // Create a first row + EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, 'first', 3)")); + EXPECT_EQ(1, db.getLastInsertRowid()); + + // Try to insert a new row with the same PRIMARY KEY: "UNIQUE constraint failed: test.id" + EXPECT_EQ(SQLITE_CONSTRAINT, db.tryExec("INSERT INTO test VALUES (1, 'impossible', 456)")); + EXPECT_EQ(SQLITE_CONSTRAINT, db.getErrorCode()); + EXPECT_EQ(SQLITE_CONSTRAINT_PRIMARYKEY, db.getExtendedErrorCode()); + EXPECT_STREQ("UNIQUE constraint failed: test.id", db.getErrorMsg()); +} + +// From https://stackoverflow.com/a/8283265/1163698 How can I create a user-defined function in SQLite? +static void firstchar(sqlite3_context *context, int argc, sqlite3_value **argv) +{ + if (argc == 1) + { + const unsigned char *text = sqlite3_value_text(argv[0]); + if (text && text[0]) + { + char result[2]; + result[0] = text[0]; result[1] = '\0'; + sqlite3_result_text(context, result, -1, SQLITE_TRANSIENT); + return; + } + } + sqlite3_result_null(context); +} + +TEST(Database, createFunction) +{ + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first')")); + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'second')")); + + // exception with SQL error: "no such function: firstchar" + EXPECT_THROW(db.exec("SELECT firstchar(value) FROM test WHERE id=1"), SQLite::Exception); + + db.createFunction("firstchar", 1, true, nullptr, &firstchar, nullptr, nullptr, nullptr); + + EXPECT_EQ(1, db.exec("SELECT firstchar(value) FROM test WHERE id=1")); +} + +TEST(Database, loadExtension) +{ + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE); + + // Try to load a non-existing extension (no dynamic library found) + EXPECT_THROW(db.loadExtension("non-existing-extension", "entry-point"), SQLite::Exception); + + // TODO: test a proper extension +} + +TEST(Database, getHeaderInfo) +{ + remove("test.db3"); + { + // Call without passing a database file name + EXPECT_THROW(SQLite::Database::getHeaderInfo(""),SQLite::Exception); + + // Call with a non-existent database + EXPECT_THROW(SQLite::Database::getHeaderInfo("test.db3"), SQLite::Exception); + + // Simulate an incomplete header by writing garbage to a file + { + const unsigned char badData[] = "garbage..."; + const char* pBadData = reinterpret_cast(&badData[0]); + + remove("short.db3"); + std::ofstream corruptDb; + corruptDb.open("short.db3", std::ios::app | std::ios::binary); + corruptDb.write(pBadData, sizeof(badData)); + corruptDb.close(); + + EXPECT_THROW(SQLite::Database::getHeaderInfo("short.db3"), SQLite::Exception); + remove("short.db3"); + } + + // Simulate a corrupt header by writing garbage to a file + { + const unsigned char badData[100] = "garbage..."; + const char* pBadData = reinterpret_cast(&badData[0]); + + remove("corrupt.db3"); + std::ofstream corruptDb; + corruptDb.open("corrupt.db3", std::ios::app | std::ios::binary); + corruptDb.write(pBadData, sizeof(badData)); + corruptDb.close(); + + EXPECT_THROW(SQLite::Database::getHeaderInfo("corrupt.db3"), SQLite::Exception); + remove("corrupt.db3"); + } + + // Create a new database + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + + // Set assorted SQLite header values using associated PRAGMA + db.exec("PRAGMA main.user_version = 12345"); + db.exec("PRAGMA main.application_id = 2468"); + + // Parse header fields from test database + const SQLite::Header h = db.getHeaderInfo(); + + //Test header values explicitly set via PRAGMA statements + EXPECT_EQ(h.userVersion, 12345); + EXPECT_EQ(h.applicationId, 2468); + + //Test header values with expected default values + EXPECT_EQ(h.pageSizeBytes, 4096); + EXPECT_EQ(h.fileFormatWriteVersion,1); + EXPECT_EQ(h.fileFormatReadVersion,1); + EXPECT_EQ(h.reservedSpaceBytes,0); + EXPECT_EQ(h.maxEmbeddedPayloadFrac, 64); + EXPECT_EQ(h.minEmbeddedPayloadFrac, 32); + EXPECT_EQ(h.leafPayloadFrac, 32); + EXPECT_EQ(h.fileChangeCounter, 3); + EXPECT_EQ(h.databaseSizePages, 2); + EXPECT_EQ(h.firstFreelistTrunkPage, 0); + EXPECT_EQ(h.totalFreelistPages, 0); + EXPECT_EQ(h.schemaCookie, 1); + EXPECT_EQ(h.schemaFormatNumber, 4); + EXPECT_EQ(h.defaultPageCacheSizeBytes, 0); + EXPECT_EQ(h.largestBTreePageNumber, 0); + EXPECT_EQ(h.databaseTextEncoding, 1); + EXPECT_EQ(h.incrementalVaccumMode, 0); + EXPECT_EQ(h.versionValidFor, 3); +// NOTE on macOS FindSQLite3 find an unrelated sqlite3.h from Mono.framework that doesn't match the actual package version! +#if defined(SQLITECPP_INTERNAL_SQLITE) || !defined(__APPLE__) + EXPECT_EQ(h.sqliteVersion, SQLITE_VERSION_NUMBER); +#endif + } + remove("test.db3"); +} + +#ifdef SQLITE_HAS_CODEC +TEST(Database, encryptAndDecrypt) +{ + remove("test.db3"); + { + // Try to open the non-existing database + EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception); + EXPECT_THROW(SQLite::Database::isUnencrypted("test.db3"), SQLite::Exception); + EXPECT_THROW(SQLite::Database::isUnencrypted(""), SQLite::Exception); + + // Create a new database + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + EXPECT_FALSE(db.tableExists("test")); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_TRUE(db.tableExists("test")); + } // Close DB test.db3 + { + // Reopen the database file and encrypt it + EXPECT_TRUE(SQLite::Database::isUnencrypted("test.db3")); + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE); + // Encrypt the database + db.rekey("123secret"); + } // Close DB test.db3 + { + // Reopen the database file and try to use it + EXPECT_FALSE(SQLite::Database::isUnencrypted("test.db3")); + SQLite::Database db("test.db3", SQLite::OPEN_READONLY); + EXPECT_THROW(db.tableExists("test"), SQLite::Exception); + db.key("123secret"); + EXPECT_TRUE(db.tableExists("test")); + } // Close DB test.db3 + { + // Reopen the database file and decrypt it + EXPECT_FALSE(SQLite::Database::isUnencrypted("test.db3")); + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE); + // Decrypt the database + db.key("123secret"); + db.rekey(""); + } // Close DB test.db3 + { + // Reopen the database file and use it + EXPECT_TRUE(SQLite::Database::isUnencrypted("test.db3")); + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE); + EXPECT_TRUE(db.tableExists("test")); + } // Close DB test.db3 + remove("test.db3"); +} +#else // SQLITE_HAS_CODEC +TEST(Database, encryptAndDecrypt) +{ + remove("test.db3"); + { + // Try to open the non-existing database + EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception); + EXPECT_THROW(SQLite::Database::isUnencrypted("test.db3"), SQLite::Exception); + EXPECT_THROW(SQLite::Database::isUnencrypted(""), SQLite::Exception); + + // Create a new database + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + EXPECT_FALSE(db.tableExists("test")); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_TRUE(db.tableExists("test")); + } // Close DB test.db3 + { + // Reopen the database file and encrypt it + EXPECT_TRUE(SQLite::Database::isUnencrypted("test.db3")); + SQLite::Database db("test.db3", SQLite::OPEN_READWRITE); + // Encrypt the database + EXPECT_THROW(db.key("123secret"), SQLite::Exception); + EXPECT_THROW(db.rekey("123secret"), SQLite::Exception); + } // Close DB test.db3 + remove("test.db3"); +} +#endif // SQLITE_HAS_CODEC diff --git a/tests/Transaction_test.cpp b/tests/Transaction_test.cpp index 0db1a181..daa287e3 100644 --- a/tests/Transaction_test.cpp +++ b/tests/Transaction_test.cpp @@ -1,118 +1,118 @@ -/** - * @file Transaction_test.cpp - * @ingroup tests - * @brief Test of a SQLite Transaction. - * - * Copyright (c) 2012-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ - -#include -#include -#include -#include - -#include - -#include - -TEST(Transaction, commitRollback) -{ - // Create a new database - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); - EXPECT_EQ(SQLite::OK, db.getErrorCode()); - - { - // Begin transaction - SQLite::Transaction transaction(db); - - EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); - EXPECT_EQ(SQLite::OK, db.getErrorCode()); - - // Insert a first value - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first')")); - EXPECT_EQ(1, db.getLastInsertRowid()); - - // Commit transaction - transaction.commit(); - - // Commit again throw an exception - EXPECT_THROW(transaction.commit(), SQLite::Exception); - } - - // ensure transactions with different types are well-formed - { - for (auto behavior : { - SQLite::TransactionBehavior::DEFERRED, - SQLite::TransactionBehavior::IMMEDIATE, - SQLite::TransactionBehavior::EXCLUSIVE }) - { - SQLite::Transaction transaction(db, behavior); - transaction.commit(); - } - - EXPECT_THROW(SQLite::Transaction(db, static_cast(-1)), SQLite::Exception); - } - - // Auto rollback if no commit() before the end of scope - { - // Begin transaction - SQLite::Transaction transaction(db); - - // Insert a second value (that will be rollbacked) - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'third')")); - EXPECT_EQ(2, db.getLastInsertRowid()); - - // end of scope: automatic rollback - } - - // Auto rollback of a transaction on error/exception - try - { - // Begin transaction - SQLite::Transaction transaction(db); - - // Insert a second value (that will be rollbacked) - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'second')")); - EXPECT_EQ(2, db.getLastInsertRowid()); - - // Execute with an error => exception with auto-rollback - db.exec("DesiredSyntaxError to raise an exception to rollback the transaction"); - - GTEST_FATAL_FAILURE_("we should never get there"); - transaction.commit(); // We should never get there - } - catch (std::exception& e) - { - std::cout << "SQLite exception: " << e.what() << std::endl; - // expected error, see above - } - - // Double rollback with a manual command before the end of scope - { - // Begin transaction - SQLite::Transaction transaction(db); - - // Insert a second value (that will be rollbacked) - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'third')")); - EXPECT_EQ(2, db.getLastInsertRowid()); - - // Execute a manual rollback - transaction.rollback(); - - // end of scope: the automatic rollback should not raise an error because it is harmless - } - - // Check the results (expect only one row of result, as all other one have been rollbacked) - SQLite::Statement query(db, "SELECT * FROM test"); - int nbRows = 0; - while (query.executeStep()) - { - nbRows++; - EXPECT_EQ(1, query.getColumn(0).getInt()); - EXPECT_STREQ("first", query.getColumn(1).getText()); - } - EXPECT_EQ(1, nbRows); -} +/** + * @file Transaction_test.cpp + * @ingroup tests + * @brief Test of a SQLite Transaction. + * + * Copyright (c) 2012-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ + +#include +#include +#include +#include + +#include + +#include + +TEST(Transaction, commitRollback) +{ + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + + { + // Begin transaction + SQLite::Transaction transaction(db); + + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + + // Insert a first value + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'first')")); + EXPECT_EQ(1, db.getLastInsertRowid()); + + // Commit transaction + transaction.commit(); + + // Commit again throw an exception + EXPECT_THROW(transaction.commit(), SQLite::Exception); + } + + // ensure transactions with different types are well-formed + { + for (auto behavior : { + SQLite::TransactionBehavior::DEFERRED, + SQLite::TransactionBehavior::IMMEDIATE, + SQLite::TransactionBehavior::EXCLUSIVE }) + { + SQLite::Transaction transaction(db, behavior); + transaction.commit(); + } + + EXPECT_THROW(SQLite::Transaction(db, static_cast(-1)), SQLite::Exception); + } + + // Auto rollback if no commit() before the end of scope + { + // Begin transaction + SQLite::Transaction transaction(db); + + // Insert a second value (that will be rollbacked) + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'third')")); + EXPECT_EQ(2, db.getLastInsertRowid()); + + // end of scope: automatic rollback + } + + // Auto rollback of a transaction on error/exception + try + { + // Begin transaction + SQLite::Transaction transaction(db); + + // Insert a second value (that will be rollbacked) + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'second')")); + EXPECT_EQ(2, db.getLastInsertRowid()); + + // Execute with an error => exception with auto-rollback + db.exec("DesiredSyntaxError to raise an exception to rollback the transaction"); + + GTEST_FATAL_FAILURE_("we should never get there"); + transaction.commit(); // We should never get there + } + catch (std::exception& e) + { + std::cout << "SQLite exception: " << e.what() << std::endl; + // expected error, see above + } + + // Double rollback with a manual command before the end of scope + { + // Begin transaction + SQLite::Transaction transaction(db); + + // Insert a second value (that will be rollbacked) + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, 'third')")); + EXPECT_EQ(2, db.getLastInsertRowid()); + + // Execute a manual rollback + transaction.rollback(); + + // end of scope: the automatic rollback should not raise an error because it is harmless + } + + // Check the results (expect only one row of result, as all other one have been rollbacked) + SQLite::Statement query(db, "SELECT * FROM test"); + int nbRows = 0; + while (query.executeStep()) + { + nbRows++; + EXPECT_EQ(1, query.getColumn(0).getInt()); + EXPECT_STREQ("first", query.getColumn(1).getText()); + } + EXPECT_EQ(1, nbRows); +}