Merge pull request #2 from SRombauts/master

Update SQLiteCPP
This commit is contained in:
Mattes D 2015-05-26 08:12:32 +02:00
commit 56ec1c8c44
19 changed files with 970 additions and 368 deletions

View File

@ -34,13 +34,30 @@ Version 0.5.1 - April 7 2013
Version 0.6.0 - November 22 2013 Version 0.6.0 - November 22 2013
Renamed Column::getName() to Column::getOriginName() Renamed Column::getName() to Column::getOriginName()
Added a new Column::getName() Added Column::getName()
Version 0.7.0 - January 9 2014 Version 0.7.0 - January 9 2014
Added a new Database::createFunction() API Added Database::createFunction()
Added std::string version of existing APIs Added std::string version of existing APIs
Improved CMake with more build options and Doxygen auto-detection Improved CMake with more build options and Doxygen auto-detection
Version 0.8.0 - Februrary 26 2014 Version 0.8.0 - February 26 2014
Database constructor support opening a database with a custom VFS (default to NULL)
Changed Column::getText() to return empty string "" by default instead of NULL pointer (to handle std::string conversion) Changed Column::getText() to return empty string "" by default instead of NULL pointer (to handle std::string conversion)
Version 1.0.0 - May 3 2015
Public headers file moved to include/ dir
Added support to biicode in CMakeLists.txt
Added Unit Tests
Added aBusyTimeoutMs parameter to Database() constructors
Added Database::getTotalChanges()
Added Database::getErrorCode()
Added Statement::clearBindings()
Added Statement::getColumn(aName)
Added Statement::getErrorCode()
Added Statement::getColumnName(aIndex)
Added Statement::getColumnOriginName(aIndex)
Version 1.1.0 - May 2015 ?
Fix valgrind error on Database destructor
Added Database::loadExtension

View File

@ -1,47 +1,31 @@
if (BIICODE)
# biicode doesn't process files bigger than 5Mb
list(APPEND BII_LIB_SRC sqlite3/sqlite3.c)
# Include base block dir
ADD_BIICODE_TARGETS()
# Link target with dl for linux
if (UNIX)
TARGET_LINK_LIBRARIES(${BII_BLOCK_TARGET} INTERFACE pthread)
if(NOT APPLE)
TARGET_LINK_LIBRARIES(${BII_BLOCK_TARGET} INTERFACE dl)
endif()
endif()
else (BIICODE)
# Main CMake file for compiling the library itself, examples and tests. # Main CMake file for compiling the library itself, examples and tests.
# #
# Copyright (c) 2012-2015 Sebastien Rombauts (sebastien.rombauts@gmail.com) # Copyright (c) 2012-2015 Sebastien Rombauts (sebastien.rombauts@gmail.com)
# #
# Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt # Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
# or copy at http://opensource.org/licenses/MIT) # or copy at http://opensource.org/licenses/MIT)
if (NOT BIICODE)
cmake_minimum_required(VERSION 2.6) cmake_minimum_required(VERSION 2.6)
project(SQLiteCpp) project(SQLiteCpp)
option(SQLITE_ENABLE_COLUMN_METADATA "Enable Column::getName(). Require support from sqlite3 library." OFF)
if (SQLITE_ENABLE_COLUMN_METADATA)
# Enable the use of SQLite column metadata and Column::getName() method,
# Require that the sqlite3 library is also compiled with this flag (default under Debian/Ubuntu, but not on Mac OS X).
add_definitions(-DSQLITE_ENABLE_COLUMN_METADATA)
endif (SQLITE_ENABLE_COLUMN_METADATA)
option(SQLITE_ENABLE_ASSERT_HANDLER "Enable the user defintion of a assertion_failed() handler." OFF)
if (SQLITE_ENABLE_ASSERT_HANDLER)
# Enable the user defintion of a assertion_failed() handler (default to false, easier to handler for begginers).
add_definitions(-DSQLITECPP_ENABLE_ASSERT_HANDLER)
endif (SQLITE_ENABLE_ASSERT_HANDLER)
# Define useful variables to handle OS differences: # Define useful variables to handle OS differences:
if (WIN32) if (WIN32)
set(DEV_NULL "NUL") set(DEV_NULL "NUL")
else (WIN32) # build the SQLite3 C library for Windows (for ease of use)
set(SQLITECPP_INTERNAL_SQLITE_DEFAULT ON)
set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT ON)
else (WIN32) # UNIX
set(DEV_NULL "/dev/null") set(DEV_NULL "/dev/null")
# do not build the SQLite3 C library, but uses the Linux/Mac OS X sqlite3-dev package
set(SQLITECPP_INTERNAL_SQLITE_DEFAULT OFF)
if (APPLE)
set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT OFF)
else (APPLE)
set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT ON)
endif (APPLE)
endif (WIN32) endif (WIN32)
# then Compiler/IDE differences: # then Compiler/IDE differences:
if (MSVC) if (MSVC)
set(CPPLINT_ARG_OUTPUT "--output=vs7") set(CPPLINT_ARG_OUTPUT "--output=vs7")
@ -69,6 +53,22 @@ set(CPPLINT_ARG_VERBOSE "--verbose=3")
set(CPPLINT_ARG_LINELENGTH "--linelength=120") set(CPPLINT_ARG_LINELENGTH "--linelength=120")
# Options relative to SQLite and SQLiteC++ functions
option(SQLITE_ENABLE_COLUMN_METADATA "Enable Column::getName(). Require support from sqlite3 library." ${SQLITE_ENABLE_COLUMN_METADATA_DEFAULT})
if (SQLITE_ENABLE_COLUMN_METADATA)
# Enable the use of SQLite column metadata and Column::getName() method,
# Require that the sqlite3 library is also compiled with this flag (default under Debian/Ubuntu, but not on Mac OS X).
add_definitions(-DSQLITE_ENABLE_COLUMN_METADATA)
endif (SQLITE_ENABLE_COLUMN_METADATA)
option(SQLITE_ENABLE_ASSERT_HANDLER "Enable the user defintion of a assertion_failed() handler." OFF)
if (SQLITE_ENABLE_ASSERT_HANDLER)
# Enable the user defintion of a assertion_failed() handler (default to false, easier to handler for begginers).
add_definitions(-DSQLITECPP_ENABLE_ASSERT_HANDLER)
endif (SQLITE_ENABLE_ASSERT_HANDLER)
## Core source code ## ## Core source code ##
# adding a new file require explicittly modifing the CMakeLists.txt # adding a new file require explicittly modifing the CMakeLists.txt
@ -97,6 +97,7 @@ source_group(inc FILES ${SQLITECPP_INC})
# list of test files of the library # list of test files of the library
set(SQLITECPP_TESTS set(SQLITECPP_TESTS
tests/Column_test.cpp
tests/Database_test.cpp tests/Database_test.cpp
tests/Statement_test.cpp tests/Statement_test.cpp
) )
@ -141,30 +142,29 @@ if (UNIX AND (CMAKE_COMPILER_IS_GNUCXX OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Cla
endif (UNIX AND (CMAKE_COMPILER_IS_GNUCXX OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")) endif (UNIX AND (CMAKE_COMPILER_IS_GNUCXX OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang"))
# SQLite3 library (Windows only) # SQLite3 library (mostly usefull under Windows)
option (SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ON) option(SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ${SQLITECPP_INTERNAL_SQLITE_DEFAULT})
if (WIN32)
if (SQLITECPP_INTERNAL_SQLITE) if (SQLITECPP_INTERNAL_SQLITE)
# build the SQLite3 C library for Windows (for ease of use) versus Linux sqlite3-dev package # build the SQLite3 C library for Windows (for ease of use) versus Linux sqlite3-dev package
add_subdirectory(sqlite3) add_subdirectory(sqlite3)
include_directories("${PROJECT_SOURCE_DIR}/sqlite3") include_directories("${PROJECT_SOURCE_DIR}/sqlite3")
endif (SQLITECPP_INTERNAL_SQLITE) endif (SQLITECPP_INTERNAL_SQLITE)
endif (WIN32)
# Optional additional targets: # Optional additional targets:
option(SQLITECPP_RUN_CPPLINT "Run cpplint.py tool for Google C++ StyleGuide." ON) option(SQLITECPP_RUN_CPPLINT "Run cpplint.py tool for Google C++ StyleGuide." ON)
if (SQLITECPP_RUN_CPPLINT) if (SQLITECPP_RUN_CPPLINT)
find_package(PythonLibs) find_package(PythonInterp)
if (PYTHONLIBS_FOUND) if (PYTHONINTERP_FOUND)
# add a cpplint target to the "all" target # add a cpplint target to the "all" target
add_custom_target(SQLiteCpp_cpplint add_custom_target(SQLiteCpp_cpplint
ALL ALL
COMMAND python ${PROJECT_SOURCE_DIR}/cpplint.py ${CPPLINT_ARG_OUTPUT} ${CPPLINT_ARG_VERBOSE} ${CPPLINT_ARG_LINELENGTH} ${SQLITECPP_SRC} ${SQLITECPP_INC} COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/cpplint.py ${CPPLINT_ARG_OUTPUT} ${CPPLINT_ARG_VERBOSE} ${CPPLINT_ARG_LINELENGTH} ${SQLITECPP_SRC} ${SQLITECPP_INC}
) )
endif (PYTHONLIBS_FOUND) message(STATUS "PYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}")
endif (PYTHONINTERP_FOUND)
else (SQLITECPP_RUN_CPPLINT) else (SQLITECPP_RUN_CPPLINT)
message(STATUS "SQLITECPP_RUN_CPPLINT OFF") message(STATUS "SQLITECPP_RUN_CPPLINT OFF")
endif (SQLITECPP_RUN_CPPLINT) endif (SQLITECPP_RUN_CPPLINT)
@ -237,4 +237,20 @@ else (SQLITECPP_BUILD_TESTS)
message(STATUS "SQLITECPP_BUILD_TESTS OFF") message(STATUS "SQLITECPP_BUILD_TESTS OFF")
endif (SQLITECPP_BUILD_TESTS) endif (SQLITECPP_BUILD_TESTS)
endif (BIICODE)
else (NOT BIICODE)
# biicode doesn't process files bigger than 5Mb
list(APPEND BII_LIB_SRC sqlite3/sqlite3.c)
# Include base block dir
ADD_BIICODE_TARGETS()
# Link target with dl for linux
if (UNIX)
target_link_libraries(${BII_BLOCK_TARGET} INTERFACE pthread)
if (NOT APPLE)
target_link_libraries(${BII_BLOCK_TARGET} INTERFACE dl)
endif ()
endif ()
endif (NOT BIICODE)

View File

@ -1,9 +1,10 @@
SQLiteC++ SQLiteC++
--------- ---------
[![release](https://img.shields.io/github/release/SRombauts/SQLiteCpp.svg?style=flat-square)](https://github.com/SRombauts/SQLiteCpp/releases)
[![Travis CI Linux Build Status](https://travis-ci.org/SRombauts/SQLiteCpp.svg)](https://travis-ci.org/SRombauts/SQLiteCpp "Travis CI Linux Build Status") [![Travis CI Linux Build Status](https://travis-ci.org/SRombauts/SQLiteCpp.svg)](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") [![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")
[![Build Status](https://webapi.biicode.com/v1/badges/sqlite/sqlite/sqlite/master)](https://www.biicode.com/sqlite/sqlite) [![Biicode Build Status](https://webapi.biicode.com/v1/badges/sqlite/sqlite/sqlite/master)](https://www.biicode.com/sqlite/sqlite)
SQLiteC++ (SQLiteCpp) is a smart and easy to use C++ SQLite3 wrapper. SQLiteC++ (SQLiteCpp) is a smart and easy to use C++ SQLite3 wrapper.

View File

@ -1,28 +1,34 @@
Add a full googletest suite Add a Tutorial for SQLite newbies
Add a real example (rework current example?)
Add a Tutorial: for SQLite newbies
Create Github Wiki pages with the README.md and FAQ.txt: Installation, Examples, Tutorial, How to contribute
Publish a versionned ZIP file in Google Project Mirror
Improve Github Wiki pages with the FAQ: Installation, Examples, Tutorial, How to contribute
Publish the Doxygen Documentation in the Github Pages (gh-pages branch) Publish the Doxygen Documentation in the Github Pages (gh-pages branch)
Missing features in v0.9.9: Missing features in v1.1.0:
- getColumnByName() (issue #23) ? std::map getRow() ?
- bind a SQLITE_STATIC value (string/blob) - bind a SQLITE_STATIC value (string/blob)
- bind a dynamic value with zerocopy (unlike SQLITE_TRANSIENT) with custom deleter - bind a dynamic value with zerocopy (unlike SQLITE_TRANSIENT) with custom deleter
- #24: executemany() like in Python https://docs.python.org/2/library/sqlite3.html#sqlite3.Connection.executemany
- #34: Better type for getColumn
Missing documentation in v0.9.9: Missing documentation in v1.1.0:
- explain the noncopyable property for RAII design - explain the noncopyable property for RAII design
- comment on returning error code instead of exception that shall not be thrown when exepected (!?) - comment on returning error code instead of exception that shall not be thrown when expected (!?)
Missing unit tests in v1.0.0:
- Binding variants
- Create Function
- Assert Handler
- Load Extension (not practicable, and easy to verify by code review)
Advanced missing features: Advanced missing features:
- #39: SAVEPOINT https://www.sqlite.org/lang_savepoint.html
- backup support to/from file/:memory: - backup support to/from file/:memory:
- Function ? - Add optional usage of experimental sqlite3_trace() function to enable statistics
- Agregate ? - Agregate ?
- support for different transaction mode ? NO: too specific - support for different transaction mode ? NO: too specific
- operator<< binding ? NO: redundant with bind() - operator<< binding ? NO: redundant with bind()
- ATTACH Database ? NO: can already be done by "ATTACH" Statement - ATTACH Database ? NO: can already be done by "ATTACH" Statement
- Add optional usage of experimental sqlite3_trace() function to enable statistics
Post an article to CodeProject: Is there a license issue ? Post an article to CodeProject: Is there a license issue ?

3
cpplint.py vendored
View File

@ -4784,7 +4784,8 @@ def main():
ProcessFile(filename, _cpplint_state.verbose_level) ProcessFile(filename, _cpplint_state.verbose_level)
_cpplint_state.PrintErrorCounts() _cpplint_state.PrintErrorCounts()
sys.exit(_cpplint_state.error_count > 0) # SRombauts: do not break build for cpplint style warnings
#sys.exit(_cpplint_state.error_count > 0)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -2,9 +2,9 @@
* @file main.cpp * @file main.cpp
* @brief A few short examples in a row. * @brief A few short examples in a row.
* *
* Demonstrate how-to use the SQLite++ wrapper * Demonstrates how-to use the SQLite++ wrapper
* *
* Copyright (c) 2012-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) * Copyright (c) 2012-2015 Sebastien Rombauts (sebastien.rombauts@gmail.com)
* *
* Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
* or copy at http://opensource.org/licenses/MIT) * or copy at http://opensource.org/licenses/MIT)
@ -83,7 +83,11 @@ private:
int main () int main ()
{ {
// Basic example (1/6) : std::cout << "SQlite3 version " << SQLITE_VERSION << std::endl;
std::cout << "SQliteC++ version " << SQLITECPP_VERSION << std::endl;
////////////////////////////////////////////////////////////////////////////
// Very basic first example (1/7) :
try try
{ {
// Open a database file in readonly mode // Open a database file in readonly mode
@ -91,12 +95,28 @@ int main ()
std::cout << "SQLite database file '" << db.getFilename().c_str() << "' opened successfully\n"; std::cout << "SQLite database file '" << db.getFilename().c_str() << "' opened successfully\n";
// Test if the 'test' table exists // Test if the 'test' table exists
bool bExists = db.tableExists("test"); const bool bExists = db.tableExists("test");
std::cout << "SQLite table 'test' exists=" << bExists << "\n"; std::cout << "SQLite table 'test' exists=" << bExists << "\n";
// Get a single value result with an easy to use shortcut // Get a single value result with an easy to use shortcut
std::string value = db.execAndGet("SELECT value FROM test WHERE id=2"); const std::string value = db.execAndGet("SELECT value FROM test WHERE id=2");
std::cout << "execAndGet=" << value.c_str() << std::endl; std::cout << "execAndGet=" << value.c_str() << std::endl;
}
catch (std::exception& e)
{
std::cout << "SQLite exception: " << e.what() << std::endl;
return EXIT_FAILURE; // unexpected error : exit the example program
}
////////////////////////////////////////////////////////////////////////////
// Simple select query - few variations (2/7) :
try
{
// Open a database file in readonly mode
SQLite::Database db(filename_example_db3); // SQLITE_OPEN_READONLY
std::cout << "SQLite database file '" << db.getFilename().c_str() << "' opened successfully\n";
///// a) Loop to get values of column by index, using auto cast to variable type
// Compile a SQL query, containing one parameter (index 1) // Compile a SQL query, containing one parameter (index 1)
SQLite::Statement query(db, "SELECT id as test_id, value as test_val, weight as test_weight FROM test WHERE weight > ?"); SQLite::Statement query(db, "SELECT id as test_id, value as test_val, weight as test_weight FROM test WHERE weight > ?");
@ -108,46 +128,77 @@ int main ()
// Loop to execute the query step by step, to get one a row of results at a time // Loop to execute the query step by step, to get one a row of results at a time
while (query.executeStep()) while (query.executeStep())
{ {
// Demonstrate how to get some typed column value (and the equivalent explicit call) // Demonstrates how to get some typed column value (and the equivalent explicit call)
int id = query.getColumn(0); // = query.getColumn(0).getInt() const int id = query.getColumn(0); // = query.getColumn(0).getInt();
//const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText() //const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText();
std::string value2 = query.getColumn(1); // = query.getColumn(1).getText() const std::string value = query.getColumn(1); // = query.getColumn(1).getText();
int bytes = query.getColumn(1).getBytes(); const int bytes = query.getColumn(1).size(); // .getColumn(1).getBytes();
double weight = query.getColumn(2); // = query.getColumn(2).getInt() const double weight = query.getColumn(2); // = query.getColumn(2).getInt();
std::cout << "row (" << id << ", \"" << value.c_str() << "\"(" << bytes << ") " << weight << ")\n";
}
///// b) Get aliased column names (and original column names if possible)
// Reset the query to use it again
query.reset();
std::cout << "SQLite statement '" << query.getQuery().c_str() << "' reseted (" << query.getColumnCount() << " columns in the result)\n";
static bool bFirst = true;
if (bFirst)
{
// Show how to get the aliased names of the result columns. // Show how to get the aliased names of the result columns.
std::string name0 = query.getColumn(0).getName(); const std::string name0 = query.getColumnName(0);
std::string name1 = query.getColumn(1).getName(); const std::string name1 = query.getColumnName(1);
std::string name2 = query.getColumn(2).getName(); const std::string name2 = query.getColumnName(2);
std::cout << "aliased result [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n"; std::cout << "aliased result [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n";
#ifdef SQLITE_ENABLE_COLUMN_METADATA #ifdef SQLITE_ENABLE_COLUMN_METADATA
// Show how to get origin names of the table columns from which theses result columns come from. // Show how to get origin names of the table columns from which theses result columns come from.
// Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be // Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be
// also defined at compile times of the SQLite library itself. // also defined at compile times of the SQLite library itself.
name0 = query.getColumn(0).getOriginName(); const std::string oname0 = query.getColumnOriginName(0);
name1 = query.getColumn(1).getOriginName(); const std::string oname1 = query.getColumnOriginName(1);
name2 = query.getColumn(2).getOriginName(); const std::string oname2 = query.getColumnOriginName(2);
std::cout << "origin table 'test' [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n"; std::cout << "origin table 'test' [\"" << oname0.c_str() << "\", \"" << oname1.c_str() << "\", \"" << oname2.c_str() << "\"]\n";
#endif #endif
bFirst = false; // Loop to execute the query step by step, to get one a row of results at a time
while (query.executeStep())
{
// Demonstrates that inserting column value in a std:ostream is natural
std::cout << "row (" << query.getColumn(0) << ", \"" << query.getColumn(1) << "\", " << query.getColumn(2) << ")\n";
} }
std::cout << "row (" << id << ", \"" << value2.c_str() << "\" " << bytes << " bytes, " << weight << ")\n";
///// c) Get columns by name
// Reset the query to use it again
query.reset();
std::cout << "SQLite statement '" << query.getQuery().c_str() << "' reseted (" << query.getColumnCount() << " columns in the result)\n";
// Loop to execute the query step by step, to get one a row of results at a time
while (query.executeStep())
{
// Demonstrates how to get column value by aliased name (not the original table names, see above)
const int id = query.getColumn("test_id");
const std::string value = query.getColumn("test_val");
const double weight = query.getColumn("test_weight");
std::cout << "row (" << id << ", \"" << value.c_str() << "\" " << weight << ")\n";
} }
///// d) Uses explicit typed getters instead of auto cast operators
// Reset the query to use it again // Reset the query to use it again
query.reset(); query.reset();
std::cout << "SQLite statement '" << query.getQuery().c_str() << "' reseted (" << query.getColumnCount () << " columns in the result)\n"; std::cout << "SQLite statement '" << query.getQuery().c_str() << "' reseted (" << query.getColumnCount () << " columns in the result)\n";
// Bind the string value "6" to the first parameter of the SQL query // Bind the string value "6" to the first parameter of the SQL query
query.bind(1, "6"); query.bind(1, "6");
std::cout << "binded with string value \"6\" :\n"; std::cout << "binded with string value \"6\" :\n";
// Reuses variables: uses assignement operator in the loop instead of constructor with initialization
int id = 0;
std::string value;
double weight = 0.0;
while (query.executeStep()) while (query.executeStep())
{ {
// Demonstrate that inserting column value in a std:ostream is natural id = query.getColumn(0).getInt();
std::cout << "row (" << query.getColumn(0) << ", \"" << query.getColumn(1) << "\", " << query.getColumn(2) << ")\n"; value = query.getColumn(1).getText();
weight = query.getColumn(2).getInt();
std::cout << "row (" << id << ", \"" << value << "\", " << weight << ")\n";
} }
} }
catch (std::exception& e) catch (std::exception& e)
@ -157,13 +208,13 @@ int main ()
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Object Oriented Basic example (2/6) : // Object Oriented Basic example (3/7) :
try try
{ {
// Open the database and compile the query // Open the database and compile the query
Example example; Example example;
// Demonstrate the way to use the same query with different parameter values // Demonstrates the way to use the same query with different parameter values
example.ListGreaterThan(8); example.ListGreaterThan(8);
example.ListGreaterThan(6); example.ListGreaterThan(6);
example.ListGreaterThan(2); example.ListGreaterThan(2);
@ -174,7 +225,7 @@ int main ()
return EXIT_FAILURE; // unexpected error : exit the example program return EXIT_FAILURE; // unexpected error : exit the example program
} }
// The execAndGet wrapper example (3/6) : // The execAndGet wrapper example (4/7) :
try try
{ {
// Open a database file in readonly mode // Open a database file in readonly mode
@ -194,7 +245,7 @@ int main ()
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Simple batch queries example (4/6) : // Simple batch queries example (5/7) :
try try
{ {
// Open a database file in create/write mode // Open a database file in create/write mode
@ -235,7 +286,7 @@ int main ()
remove("test.db3"); remove("test.db3");
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// RAII transaction example (5/6) : // RAII transaction example (6/7) :
try try
{ {
// Open a database file in create/write mode // Open a database file in create/write mode
@ -303,7 +354,7 @@ int main ()
remove("transaction.db3"); remove("transaction.db3");
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Binary blob and in-memory database example (6/6) : // Binary blob and in-memory database example (7/7) :
try try
{ {
// Open a database file in create/write mode // Open a database file in create/write mode

View File

@ -40,7 +40,7 @@ namespace SQLite
#else #else
// if no assert handler provided by user code, use standard assert() // if no assert handler provided by user code, use standard assert()
// (note: in debug mode, assert() does nothing) // (note: in release mode assert() does nothing)
#define SQLITECPP_ASSERT(expression, message) assert(expression && message) #define SQLITECPP_ASSERT(expression, message) assert(expression && message)
#endif #endif

View File

@ -3,7 +3,7 @@
* @ingroup SQLiteCpp * @ingroup SQLiteCpp
* @brief Encapsulation of a Column in a row of the result pointed by the prepared SQLite::Statement. * @brief Encapsulation of a Column in a row of the result pointed by the prepared SQLite::Statement.
* *
* Copyright (c) 2012-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) * Copyright (c) 2012-2015 Sebastien Rombauts (sebastien.rombauts@gmail.com)
* *
* Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
* or copy at http://opensource.org/licenses/MIT) * or copy at http://opensource.org/licenses/MIT)
@ -62,7 +62,9 @@ public:
// they copy the Statement::Ptr which in turn increments the reference counter. // they copy the Statement::Ptr which in turn increments the reference counter.
/** /**
* @brief Return a pointer to the named assigned to a result column (potentially aliased) * @brief Return a pointer to the named assigned to this result column (potentially aliased)
*
* @see getOriginName() to get original column name (not aliased)
*/ */
const char* getName() const noexcept; // nothrow const char* getName() const noexcept; // nothrow
@ -184,8 +186,9 @@ public:
{ {
return getBlob(); return getBlob();
} }
#ifdef __GNUC__ #ifdef __GNUC__
// NOTE : the following is required by GCC to cast a Column result in a std::string // NOTE : the following is required by GCC and Clang to cast a Column result in a std::string
// (error: conversion from SQLite::Column to non-scalar type std::string {aka std::basic_string<char>}) // (error: conversion from SQLite::Column to non-scalar type std::string {aka std::basic_string<char>})
// but is not working under Microsoft Visual Studio 2010 and 2012 // but is not working under Microsoft Visual Studio 2010 and 2012
// (error C2440: 'initializing' : cannot convert from 'SQLite::Column' to 'std::basic_string<_Elem,_Traits,_Ax>' // (error C2440: 'initializing' : cannot convert from 'SQLite::Column' to 'std::basic_string<_Elem,_Traits,_Ax>'
@ -196,6 +199,16 @@ public:
return getText(); return getText();
} }
#endif #endif
// NOTE : the following is required by GCC and Clang to cast a Column result in a long/int64_t
/// @brief Inline cast operator to long as 64bits integer
inline operator long() const
{
#ifdef __x86_64__
return getInt64();
#else
return getInt();
#endif
}
/// @brief Return UTF-8 encoded English language explanation of the most recent error. /// @brief Return UTF-8 encoded English language explanation of the most recent error.
inline const char* errmsg() const inline const char* errmsg() const

View File

@ -55,11 +55,15 @@ public:
* *
* @param[in] apFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) * @param[in] apFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter)
* @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE... * @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE...
* @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout())
* @param[in] apVfs UTF-8 name of custom VFS to use, or nullptr for sqlite3 default * @param[in] apVfs UTF-8 name of custom VFS to use, or nullptr for sqlite3 default
* *
* @throw SQLite::Exception in case of error * @throw SQLite::Exception in case of error
*/ */
Database(const char* apFilename, const int aFlags = SQLITE_OPEN_READONLY, const char * apVfs = NULL); Database(const char* apFilename,
const int aFlags = SQLITE_OPEN_READONLY,
const int aBusyTimeoutMs = 0,
const char* apVfs = NULL);
/** /**
* @brief Open the provided database UTF-8 filename. * @brief Open the provided database UTF-8 filename.
@ -73,11 +77,15 @@ public:
* *
* @param[in] aFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) * @param[in] aFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter)
* @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE... * @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE...
* @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout())
* @param[in] aVfs UTF-8 name of custom VFS to use, or empty string for sqlite3 default * @param[in] aVfs UTF-8 name of custom VFS to use, or empty string for sqlite3 default
* *
* @throw SQLite::Exception in case of error * @throw SQLite::Exception in case of error
*/ */
Database(const std::string& aFilename, const int aFlags = SQLITE_OPEN_READONLY, const std::string& aVfs = ""); Database(const std::string& aFilename,
const int aFlags = SQLITE_OPEN_READONLY,
const int aBusyTimeoutMs = 0,
const std::string& aVfs = "");
/** /**
* @brief Close the SQLite database connection. * @brief Close the SQLite database connection.
@ -89,6 +97,21 @@ public:
*/ */
virtual ~Database() noexcept; // nothrow virtual ~Database() noexcept; // nothrow
/**
* @brief Set a busy handler that sleeps for a specified amount of time when a table is locked.
*
* This is usefull in multithreaded program to handle case where a table is locked for writting by a thread.
* Any other thread cannot access the table and will receive a SQLITE_BUSY error:
* setting a timeout will wait and retry up to the time specified before returning this SQLITE_BUSY error.
* Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;".
* Default busy timeout is 0ms.
*
* @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY
*
* @throw SQLite::Exception in case of error
*/
void setBusyTimeout(const int aBusyTimeoutMs) noexcept; // nothrow
/** /**
* @brief Shortcut to execute one or multiple statements without results. * @brief Shortcut to execute one or multiple statements without results.
* *
@ -206,16 +229,6 @@ public:
return tableExists(aTableName.c_str()); return tableExists(aTableName.c_str());
} }
/**
* @brief Set a busy handler that sleeps for a specified amount of time when a table is locked.
*
* @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY
*/
inline int setBusyTimeout(int aTimeoutMs) noexcept // nothrow
{
return sqlite3_busy_timeout(mpSQLite, aTimeoutMs);
}
/** /**
* @brief Get the rowid of the most recent successful INSERT into the database from the current connection. * @brief Get the rowid of the most recent successful INSERT into the database from the current connection.
* *
@ -323,6 +336,25 @@ public:
apApp, apFunc, apStep, apFinal, apDestroy); apApp, apFunc, apStep, apFinal, apDestroy);
} }
/**
* @brief Load a module into the current sqlite database instance.
*
* This is the equivalent of the sqlite3_load_extension call, but additionally enables
* module loading support prior to loading the requested module.
*
* @see http://www.sqlite.org/c3ref/load_extension.html
*
* @note UTF-8 text encoding assumed.
*
* @param[in] apExtensionName Name of the shared library containing extension
* @param[in] apEntryPointName Name of the entry point (NULL to let sqlite work it out)
*
* @throw SQLite::Exception in case of error
*/
void loadExtension(const char* apExtensionName,
const char *apEntryPointName);
private: private:
/// @{ Database must be non-copyable /// @{ Database must be non-copyable
Database(const Database&); Database(const Database&);
@ -332,7 +364,13 @@ private:
/** /**
* @brief Check if aRet equal SQLITE_OK, else throw a SQLite::Exception with the SQLite error message * @brief Check if aRet equal SQLITE_OK, else throw a SQLite::Exception with the SQLite error message
*/ */
void check(const int aRet) const; inline void check(const int aRet) const
{
if (SQLITE_OK != aRet)
{
throw SQLite::Exception(sqlite3_errmsg(mpSQLite));
}
}
private: private:
sqlite3* mpSQLite; //!< Pointer to SQLite Database Connection Handle sqlite3* mpSQLite; //!< Pointer to SQLite Database Connection Handle

View File

@ -5,7 +5,7 @@
* *
* Include this main header file in your project to gain access to all functionality provided by the wrapper. * Include this main header file in your project to gain access to all functionality provided by the wrapper.
* *
* Copyright (c) 2012-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) * Copyright (c) 2012-2015 Sebastien Rombauts (sebastien.rombauts@gmail.com)
* *
* Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
* or copy at http://opensource.org/licenses/MIT) * or copy at http://opensource.org/licenses/MIT)
@ -38,5 +38,5 @@
* with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same
* numbers used in [SQLITECPP_VERSION]. * numbers used in [SQLITECPP_VERSION].
*/ */
#define SQLITECPP_VERSION "0.9.9" #define SQLITECPP_VERSION "1.1.0"
#define SQLITECPP_VERSION_NUMBER 0009009 #define SQLITECPP_VERSION_NUMBER 1001000

View File

@ -12,6 +12,7 @@
#include <sqlite3.h> #include <sqlite3.h>
#include <string> #include <string>
#include <map>
#include <SQLiteCpp/Exception.h> #include <SQLiteCpp/Exception.h>
@ -78,8 +79,12 @@ public:
/** /**
* @brief Clears away all the bindings of a prepared statement. * @brief Clears away all the bindings of a prepared statement.
*
* Contrary to the intuition of many, reset() does not reset
* the bindings on a prepared statement.
* Use this routine to reset all parameters to NULL.
*/ */
void clearBindings(void); // throw(SQLite::Exception) void clearBindings(); // throw(SQLite::Exception)
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Bind a value to a parameter of the SQL statement, // Bind a value to a parameter of the SQL statement,
@ -275,6 +280,7 @@ public:
* while the executeStep() method returns true. * while the executeStep() method returns true.
* *
* Throw an exception if there is no row to return a Column from: * Throw an exception if there is no row to return a Column from:
* - if provided index is out of bound
* - before any executeStep() call * - before any executeStep() call
* - after the last executeStep() returned false * - after the last executeStep() returned false
* - after a reset() call * - after a reset() call
@ -283,8 +289,7 @@ public:
* *
* @param[in] aIndex Index of the column, starting at 0 * @param[in] aIndex Index of the column, starting at 0
* *
* @note This method is no more const, starting in v0.5, * @note This method is not const, reflecting the fact that the returned Column object will
* which reflects the fact that the returned Column object will
* share the ownership of the underlying sqlite3_stmt. * share the ownership of the underlying sqlite3_stmt.
* *
* @warning The resulting Column object must not be memorized "as-is". * @warning The resulting Column object must not be memorized "as-is".
@ -295,15 +300,68 @@ public:
*/ */
Column getColumn(const int aIndex); Column getColumn(const int aIndex);
/**
* @brief Return a copy of the column data specified by its column name (less efficient than using an index)
*
* Can be used to access the data of the current row of result when applicable,
* while the executeStep() method returns true.
*
* Throw an exception if there is no row to return a Column from :
* - if provided name is not one of the aliased column names
* - before any executeStep() call
* - after the last executeStep() returned false
* - after a reset() call
*
* Throw an exception if the specified name is not an on of the aliased name of the columns in the result.
*
* @param[in] apName Aliased name of the column, that is, the named specified in the query (not the original name)
*
* @note Uses a map of column names to indexes, build on first call.
*
* @note This method is not const, reflecting the fact that the returned Column object will
* share the ownership of the underlying sqlite3_stmt.
*
* @warning The resulting Column object must not be memorized "as-is".
* Is is only a wrapper around the current result row, so it is only valid
* while the row from the Statement remains valid, that is only until next executeStep() call.
* Thus, you should instead extract immediately its data (getInt(), getText()...)
* and use or copy this data for any later usage.
*/
Column getColumn(const char* apName);
/** /**
* @brief Test if the column value is NULL * @brief Test if the column value is NULL
* *
* @param[in] aIndex Index of the column, starting at 0 * @param[in] aIndex Index of the column, starting at 0
* *
* @return true if the column value is NULL * @return true if the column value is NULL
*
* Throw an exception if the specified index is out of the [0, getColumnCount()) range.
*/ */
bool isColumnNull(const int aIndex) const; bool isColumnNull(const int aIndex) const;
/**
* @brief Return a pointer to the named assigned to the specified result column (potentially aliased)
*
* @see getColumnOriginName() to get original column name (not aliased)
*
* Throw an exception if the specified index is out of the [0, getColumnCount()) range.
*/
const char* getColumnName(const int aIndex) const;
#ifdef SQLITE_ENABLE_COLUMN_METADATA
/**
* @brief Return a pointer to the table column name that is the origin of the specified result column
*
* Require definition of the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro :
* - when building the SQLite library itself (which is the case for the Debian libsqlite3 binary for instance),
* - and also when compiling this wrapper.
*
* Throw an exception if the specified index is out of the [0, getColumnCount()) range.
*/
const char* getColumnOriginName(const int aIndex) const;
#endif
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
/// @brief Return the UTF-8 SQL Query. /// @brief Return the UTF-8 SQL Query.
@ -395,12 +453,44 @@ private:
* *
* @param[in] SQLite return code to test against the SQLITE_OK expected value * @param[in] SQLite return code to test against the SQLITE_OK expected value
*/ */
void check(const int aRet); inline void check(const int aRet) const
{
if (SQLITE_OK != aRet)
{
throw SQLite::Exception(sqlite3_errmsg(mStmtPtr));
}
}
/**
* @brief Check if there is a row of result returnes by executeStep(), else throw a SQLite::Exception.
*/
inline void checkRow() const
{
if (false == mbOk)
{
throw SQLite::Exception("No row to get a column from. executeStep() was not called, or returned false.");
}
}
/**
* @brief Check if there is a Column index is in the range of columns in the result.
*/
inline void checkIndex(const int aIndex) const
{
if ((aIndex < 0) || (aIndex >= mColumnCount))
{
throw SQLite::Exception("Column index out of range.");
}
}
private:
typedef std::map<std::string, int> TColumnNames;
private: private:
std::string mQuery; //!< UTF-8 SQL Query std::string mQuery; //!< UTF-8 SQL Query
Ptr mStmtPtr; //!< Shared Pointer to the prepared SQLite Statement Object Ptr mStmtPtr; //!< Shared Pointer to the prepared SQLite Statement Object
int mColumnCount; //!< Number of columns in the result of the prepared statement int mColumnCount; //!< Number of columns in the result of the prepared statement
TColumnNames mColumnNames; //!< Map of columns index by name
bool mbOk; //!< true when a row has been fetched with executeStep() bool mbOk; //!< true when a row has been fetched with executeStep()
bool mbDone; //!< true when the last executeStep() had no more row to fetch bool mbDone; //!< true when the last executeStep() had no more row to fetch
}; };

View File

@ -1,6 +1,6 @@
# CMake file for compiling the sqlite3 static library under Windows (for ease of use) # CMake file for compiling the sqlite3 static library under Windows (for ease of use)
# #
# Copyright (c) 2013-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) # Copyright (c) 2013-2015 Sebastien Rombauts (sebastien.rombauts@gmail.com)
# #
# Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt # Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
# or copy at http://opensource.org/licenses/MIT) # or copy at http://opensource.org/licenses/MIT)

View File

@ -3,7 +3,7 @@
* @ingroup SQLiteCpp * @ingroup SQLiteCpp
* @brief Encapsulation of a Column in a row of the result pointed by the prepared SQLite::Statement. * @brief Encapsulation of a Column in a row of the result pointed by the prepared SQLite::Statement.
* *
* Copyright (c) 2012-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) * Copyright (c) 2012-2015 Sebastien Rombauts (sebastien.rombauts@gmail.com)
* *
* Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
* or copy at http://opensource.org/licenses/MIT) * or copy at http://opensource.org/licenses/MIT)

View File

@ -26,45 +26,85 @@ namespace SQLite
// Open the provided database UTF-8 filename with SQLITE_OPEN_xxx provided flags. // Open the provided database UTF-8 filename with SQLITE_OPEN_xxx provided flags.
Database::Database(const char* apFilename, const int aFlags /*= SQLITE_OPEN_READONLY*/, const char* apVfs /*= NULL*/) : Database::Database(const char* apFilename,
const int aFlags /* = SQLITE_OPEN_READONLY*/,
const int aBusyTimeoutMs /* = 0 */,
const char* apVfs /* = NULL*/) :
mpSQLite(NULL), mpSQLite(NULL),
mFilename(apFilename) mFilename(apFilename)
{ {
int ret = sqlite3_open_v2(apFilename, &mpSQLite, aFlags, apVfs); const int ret = sqlite3_open_v2(apFilename, &mpSQLite, aFlags, apVfs);
if (SQLITE_OK != ret) if (SQLITE_OK != ret)
{ {
std::string strerr = sqlite3_errmsg(mpSQLite); std::string strerr = sqlite3_errmsg(mpSQLite);
sqlite3_close(mpSQLite); // close is required even in case of error on opening sqlite3_close(mpSQLite); // close is required even in case of error on opening
throw SQLite::Exception(strerr); throw SQLite::Exception(strerr);
} }
if (aBusyTimeoutMs > 0)
{
setBusyTimeout(aBusyTimeoutMs);
}
} }
// Open the provided database UTF-8 filename with SQLITE_OPEN_xxx provided flags. // Open the provided database UTF-8 filename with SQLITE_OPEN_xxx provided flags.
Database::Database(const std::string& aFilename, const int aFlags /*= SQLITE_OPEN_READONLY*/, const std::string& aVfs) : Database::Database(const std::string& aFilename,
const int aFlags /* = SQLITE_OPEN_READONLY*/,
const int aBusyTimeoutMs /* = 0 */,
const std::string& aVfs /* = "" */) :
mpSQLite(NULL), mpSQLite(NULL),
mFilename(aFilename) mFilename(aFilename)
{ {
int ret = sqlite3_open_v2(aFilename.c_str(), &mpSQLite, aFlags, aVfs.empty() ? NULL : aVfs.c_str()); const int ret = sqlite3_open_v2(aFilename.c_str(), &mpSQLite, aFlags, aVfs.empty() ? NULL : aVfs.c_str());
if (SQLITE_OK != ret) if (SQLITE_OK != ret)
{ {
std::string strerr = sqlite3_errmsg(mpSQLite); std::string strerr = sqlite3_errmsg(mpSQLite);
sqlite3_close(mpSQLite); // close is required even in case of error on opening sqlite3_close(mpSQLite); // close is required even in case of error on opening
throw SQLite::Exception(strerr); throw SQLite::Exception(strerr);
} }
if (aBusyTimeoutMs > 0)
{
setBusyTimeout(aBusyTimeoutMs);
}
} }
// Close the SQLite database connection. // Close the SQLite database connection.
Database::~Database() noexcept // nothrow Database::~Database() noexcept // nothrow
{ {
int ret = sqlite3_close(mpSQLite); const int ret = sqlite3_close(mpSQLite);
// Never throw an exception in a destructor
SQLITECPP_ASSERT(SQLITE_OK == ret, sqlite3_errmsg(mpSQLite)); // See SQLITECPP_ENABLE_ASSERT_HANDLER // Avoid unreferenced variable warning when build in release mode
(void) ret;
// Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized)
// Never throw an exception in a destructor :
SQLITECPP_ASSERT(SQLITE_OK == ret, "database is locked"); // See SQLITECPP_ENABLE_ASSERT_HANDLER
}
/**
* @brief Set a busy handler that sleeps for a specified amount of time when a table is locked.
*
* This is usefull in multithreaded program to handle case where a table is locked for writting by a thread.
* Any other thread cannot access the table and will receive a SQLITE_BUSY error:
* setting a timeout will wait and retry up to the time specified before returning this SQLITE_BUSY error.
* Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;".
* Default busy timeout is 0ms.
*
* @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY
*
* @throw SQLite::Exception in case of error
*/
void Database::setBusyTimeout(const int aBusyTimeoutMs) noexcept // nothrow
{
const int ret = sqlite3_busy_timeout(mpSQLite, aBusyTimeoutMs);
check(ret);
} }
// Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...). // Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...).
int Database::exec(const char* apQueries) int Database::exec(const char* apQueries)
{ {
int ret = sqlite3_exec(mpSQLite, apQueries, NULL, NULL, NULL); const int ret = sqlite3_exec(mpSQLite, apQueries, NULL, NULL, NULL);
check(ret); check(ret);
// Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE only) // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE only)
@ -90,19 +130,10 @@ bool Database::tableExists(const char* apTableName)
Statement query(*this, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?"); Statement query(*this, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?");
query.bind(1, apTableName); query.bind(1, apTableName);
(void)query.executeStep(); // Cannot return false, as the above query always return a result (void)query.executeStep(); // Cannot return false, as the above query always return a result
int Nb = query.getColumn(0); const int Nb = query.getColumn(0);
return (1 == Nb); return (1 == Nb);
} }
// Check if aRet equal SQLITE_OK, else throw a SQLite::Exception with the SQLite error message
void Database::check(const int aRet) const
{
if (SQLITE_OK != aRet)
{
throw SQLite::Exception(sqlite3_errmsg(mpSQLite));
}
}
// Attach a custom function to your sqlite database. Assumes UTF8 text representation. // Attach a custom function to your sqlite database. Assumes UTF8 text representation.
// Parameter details can be found here: http://www.sqlite.org/c3ref/create_function.html // Parameter details can be found here: http://www.sqlite.org/c3ref/create_function.html
void Database::createFunction(const char* apFuncName, void Database::createFunction(const char* apFuncName,
@ -119,11 +150,31 @@ void Database::createFunction(const char* apFuncName,
if (abDeterministic) { if (abDeterministic) {
TextRep = TextRep|SQLITE_DETERMINISTIC; TextRep = TextRep|SQLITE_DETERMINISTIC;
} }
int ret = sqlite3_create_function_v2(mpSQLite, apFuncName, aNbArg, TextRep, const int ret = sqlite3_create_function_v2(mpSQLite, apFuncName, aNbArg, TextRep,
apApp, apFunc, apStep, apFinal, apDestroy); apApp, apFunc, apStep, apFinal, apDestroy);
check(ret); check(ret);
} }
// Load an extension into the sqlite database. Only affects the current connection.
// Parameter details can be found here: http://www.sqlite.org/c3ref/load_extension.html
void Database::loadExtension(const char* apExtensionName,
const char *apEntryPointName)
{
#ifdef SQLITE_OMIT_LOAD_EXTENSION
#
throw std::runtime_error("sqlite extensions are disabled");
#
#else
#
int ret = sqlite3_enable_load_extension(mpSQLite, 1);
check(ret);
ret = sqlite3_load_extension(mpSQLite, apExtensionName, apEntryPointName, 0);
check(ret);
#
#endif
}
} // namespace SQLite } // namespace SQLite

View File

@ -54,67 +54,66 @@ void Statement::reset()
{ {
mbOk = false; mbOk = false;
mbDone = false; mbDone = false;
int ret = sqlite3_reset(mStmtPtr); const int ret = sqlite3_reset(mStmtPtr);
check(ret); check(ret);
} }
// Clears away all the bindings of a prepared statement. // Clears away all the bindings of a prepared statement.
// "Contrary to the intuition of many, [sqlite3_reset()] does not reset void Statement::clearBindings()
// ** the [sqlite3_bind_blob | bindings] on a [prepared statement]."
void Statement::clearBindings(void)
{ {
mbOk = false; mbOk = false;
mbDone = false; mbDone = false;
int ret = sqlite3_clear_bindings(mStmtPtr); const int ret = sqlite3_clear_bindings(mStmtPtr);
check(ret); check(ret);
} }
// Bind an int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind an int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const int aIndex, const int& aValue) void Statement::bind(const int aIndex, const int& aValue)
{ {
int ret = sqlite3_bind_int(mStmtPtr, aIndex, aValue); const int ret = sqlite3_bind_int(mStmtPtr, aIndex, aValue);
check(ret); check(ret);
} }
// Bind a 64bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a 64bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const int aIndex, const sqlite3_int64& aValue) void Statement::bind(const int aIndex, const sqlite3_int64& aValue)
{ {
int ret = sqlite3_bind_int64(mStmtPtr, aIndex, aValue); const int ret = sqlite3_bind_int64(mStmtPtr, aIndex, aValue);
check(ret); check(ret);
} }
// Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const int aIndex, const double& aValue) void Statement::bind(const int aIndex, const double& aValue)
{ {
int ret = sqlite3_bind_double(mStmtPtr, aIndex, aValue); const int ret = sqlite3_bind_double(mStmtPtr, aIndex, aValue);
check(ret); check(ret);
} }
// Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const int aIndex, const std::string& aValue) void Statement::bind(const int aIndex, const std::string& aValue)
{ {
int ret = sqlite3_bind_text(mStmtPtr, aIndex, aValue.c_str(), static_cast<int>(aValue.size()), SQLITE_TRANSIENT); const int ret = sqlite3_bind_text(mStmtPtr, aIndex, aValue.c_str(),
static_cast<int>(aValue.size()), SQLITE_TRANSIENT);
check(ret); check(ret);
} }
// Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const int aIndex, const char* apValue) void Statement::bind(const int aIndex, const char* apValue)
{ {
int ret = sqlite3_bind_text(mStmtPtr, aIndex, apValue, -1, SQLITE_TRANSIENT); const int ret = sqlite3_bind_text(mStmtPtr, aIndex, apValue, -1, SQLITE_TRANSIENT);
check(ret); check(ret);
} }
// Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const int aIndex, const void* apValue, const int aSize) void Statement::bind(const int aIndex, const void* apValue, const int aSize)
{ {
int ret = sqlite3_bind_blob(mStmtPtr, aIndex, apValue, aSize, SQLITE_TRANSIENT); const int ret = sqlite3_bind_blob(mStmtPtr, aIndex, apValue, aSize, SQLITE_TRANSIENT);
check(ret); check(ret);
} }
// Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const int aIndex) void Statement::bind(const int aIndex)
{ {
int ret = sqlite3_bind_null(mStmtPtr, aIndex); const int ret = sqlite3_bind_null(mStmtPtr, aIndex);
check(ret); check(ret);
} }
@ -122,56 +121,57 @@ void Statement::bind(const int aIndex)
// Bind an int value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind an int value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const char* apName, const int& aValue) void Statement::bind(const char* apName, const int& aValue)
{ {
int index = sqlite3_bind_parameter_index(mStmtPtr, apName); const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_int(mStmtPtr, index, aValue); const int ret = sqlite3_bind_int(mStmtPtr, index, aValue);
check(ret); check(ret);
} }
// Bind a 64bits int value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a 64bits int value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const char* apName, const sqlite3_int64& aValue) void Statement::bind(const char* apName, const sqlite3_int64& aValue)
{ {
int index = sqlite3_bind_parameter_index(mStmtPtr, apName); const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_int64(mStmtPtr, index, aValue); const int ret = sqlite3_bind_int64(mStmtPtr, index, aValue);
check(ret); check(ret);
} }
// Bind a double (64bits float) value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a double (64bits float) value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const char* apName, const double& aValue) void Statement::bind(const char* apName, const double& aValue)
{ {
int index = sqlite3_bind_parameter_index(mStmtPtr, apName); const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_double(mStmtPtr, index, aValue); const int ret = sqlite3_bind_double(mStmtPtr, index, aValue);
check(ret); check(ret);
} }
// Bind a string value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a string value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const char* apName, const std::string& aValue) void Statement::bind(const char* apName, const std::string& aValue)
{ {
int index = sqlite3_bind_parameter_index(mStmtPtr, apName); const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_text(mStmtPtr, index, aValue.c_str(), static_cast<int>(aValue.size()), SQLITE_TRANSIENT); const int ret = sqlite3_bind_text(mStmtPtr, index, aValue.c_str(),
static_cast<int>(aValue.size()), SQLITE_TRANSIENT);
check(ret); check(ret);
} }
// Bind a text value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a text value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const char* apName, const char* apValue) void Statement::bind(const char* apName, const char* apValue)
{ {
int index = sqlite3_bind_parameter_index(mStmtPtr, apName); const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_text(mStmtPtr, index, apValue, -1, SQLITE_TRANSIENT); const int ret = sqlite3_bind_text(mStmtPtr, index, apValue, -1, SQLITE_TRANSIENT);
check(ret); check(ret);
} }
// Bind a binary blob value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a binary blob value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const char* apName, const void* apValue, const int aSize) void Statement::bind(const char* apName, const void* apValue, const int aSize)
{ {
int index = sqlite3_bind_parameter_index(mStmtPtr, apName); const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_blob(mStmtPtr, index, apValue, aSize, SQLITE_TRANSIENT); const int ret = sqlite3_bind_blob(mStmtPtr, index, apValue, aSize, SQLITE_TRANSIENT);
check(ret); check(ret);
} }
// Bind a NULL value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement // Bind a NULL value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const char* apName) void Statement::bind(const char* apName)
{ {
int index = sqlite3_bind_parameter_index(mStmtPtr, apName); const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_null(mStmtPtr, index); const int ret = sqlite3_bind_null(mStmtPtr, index);
check(ret); check(ret);
} }
@ -181,7 +181,7 @@ bool Statement::executeStep()
{ {
if (false == mbDone) if (false == mbDone)
{ {
int ret = sqlite3_step(mStmtPtr); const int ret = sqlite3_step(mStmtPtr);
if (SQLITE_ROW == ret) // one row is ready : call getColumn(N) to access it if (SQLITE_ROW == ret) // one row is ready : call getColumn(N) to access it
{ {
mbOk = true; mbOk = true;
@ -200,7 +200,7 @@ bool Statement::executeStep()
} }
else else
{ {
throw SQLite::Exception("Statement needs to be reset"); throw SQLite::Exception("Statement needs to be reseted.");
} }
return mbOk; // true only if one row is accessible by getColumn(N) return mbOk; // true only if one row is accessible by getColumn(N)
@ -211,7 +211,7 @@ int Statement::exec()
{ {
if (false == mbDone) if (false == mbDone)
{ {
int ret = sqlite3_step(mStmtPtr); const int ret = sqlite3_step(mStmtPtr);
if (SQLITE_DONE == ret) // the statement has finished executing successfully if (SQLITE_DONE == ret) // the statement has finished executing successfully
{ {
mbOk = false; mbOk = false;
@ -221,7 +221,7 @@ int Statement::exec()
{ {
mbOk = false; mbOk = false;
mbDone = false; mbDone = false;
throw SQLite::Exception("exec() does not expect results"); throw SQLite::Exception("exec() does not expect results. Use executeStep.");
} }
else else
{ {
@ -232,7 +232,7 @@ int Statement::exec()
} }
else else
{ {
throw SQLite::Exception("Statement need to be reseted"); throw SQLite::Exception("Statement need to be reseted.");
} }
// Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE) // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE)
@ -243,43 +243,61 @@ int Statement::exec()
// (use the Column copy-constructor) // (use the Column copy-constructor)
Column Statement::getColumn(const int aIndex) Column Statement::getColumn(const int aIndex)
{ {
if (false == mbOk) checkRow();
{ checkIndex(aIndex);
throw SQLite::Exception("No row to get a column from");
}
else if ((aIndex < 0) || (aIndex >= mColumnCount))
{
throw SQLite::Exception("Column index out of range");
}
// Share the Statement Object handle with the new Column created // Share the Statement Object handle with the new Column created
return Column(mStmtPtr, aIndex); return Column(mStmtPtr, aIndex);
} }
// Return a copy of the column data specified by its column name starting at 0
// (use the Column copy-constructor)
Column Statement::getColumn(const char* apName)
{
checkRow();
if (mColumnNames.empty())
{
for (int i = 0; i < mColumnCount; ++i)
{
const char* pName = sqlite3_column_name(mStmtPtr, i);
mColumnNames[pName] = i;
}
}
const TColumnNames::const_iterator iIndex = mColumnNames.find(apName);
if (iIndex == mColumnNames.end())
{
throw SQLite::Exception("Unknown column name.");
}
// Share the Statement Object handle with the new Column created
return Column(mStmtPtr, (*iIndex).second);
}
// Test if the column is NULL // Test if the column is NULL
bool Statement::isColumnNull(const int aIndex) const bool Statement::isColumnNull(const int aIndex) const
{ {
if (false == mbOk) checkRow();
{ checkIndex(aIndex);
throw SQLite::Exception("No row to get a column from");
}
else if ((aIndex < 0) || (aIndex >= mColumnCount))
{
throw SQLite::Exception("Column index out of range");
}
return (SQLITE_NULL == sqlite3_column_type(mStmtPtr, aIndex)); return (SQLITE_NULL == sqlite3_column_type(mStmtPtr, aIndex));
} }
// Check if aRet equal SQLITE_OK, else throw a SQLite::Exception with the SQLite error message // Return the named assigned to the specified result column (potentially aliased)
void Statement::check(const int aRet) const char* Statement::getColumnName(const int aIndex) const
{ {
if (SQLITE_OK != aRet) checkIndex(aIndex);
{ return sqlite3_column_name(mStmtPtr, aIndex);
throw SQLite::Exception(sqlite3_errmsg(mStmtPtr));
}
} }
#ifdef SQLITE_ENABLE_COLUMN_METADATA
// Return the named assigned to the specified result column (potentially aliased)
const char* Statement::getColumnOriginName(const int aIndex) const
{
checkIndex(aIndex);
return sqlite3_column_origin_name(mStmtPtr, aIndex);
}
#endif
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Internal class : shared pointer to the sqlite3_stmt SQLite Statement Object // Internal class : shared pointer to the sqlite3_stmt SQLite Statement Object
@ -296,7 +314,7 @@ Statement::Ptr::Ptr(sqlite3* apSQLite, std::string& aQuery) :
mpStmt(NULL), mpStmt(NULL),
mpRefCount(NULL) mpRefCount(NULL)
{ {
int ret = sqlite3_prepare_v2(apSQLite, aQuery.c_str(), static_cast<int>(aQuery.size()), &mpStmt, NULL); const int ret = sqlite3_prepare_v2(apSQLite, aQuery.c_str(), static_cast<int>(aQuery.size()), &mpStmt, NULL);
if (SQLITE_OK != ret) if (SQLITE_OK != ret)
{ {
throw SQLite::Exception(sqlite3_errmsg(mpSQLite)); throw SQLite::Exception(sqlite3_errmsg(mpSQLite));

View File

@ -38,6 +38,7 @@ Transaction::~Transaction() noexcept // nothrow
catch (SQLite::Exception& e) catch (SQLite::Exception& e)
{ {
// Never throw an exception in a destructor // Never throw an exception in a destructor
(void)e; // warning proof
SQLITECPP_ASSERT(false, e.what()); // See SQLITECPP_ENABLE_ASSERT_HANDLER SQLITECPP_ASSERT(false, e.what()); // See SQLITECPP_ENABLE_ASSERT_HANDLER
} }
} }
@ -53,7 +54,7 @@ void Transaction::commit()
} }
else else
{ {
throw SQLite::Exception("Transaction already commited"); throw SQLite::Exception("Transaction already commited.");
} }
} }

169
tests/Column_test.cpp Normal file
View File

@ -0,0 +1,169 @@
/**
* @file Column_test.cpp
* @ingroup tests
* @brief Test of a SQLiteCpp Column.
*
* Copyright (c) 2012-2015 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 <SQLiteCpp/Database.h>
#include <SQLiteCpp/Statement.h>
#include <SQLiteCpp/Column.h>
#include <gtest/gtest.h>
#include <cstdio>
#include <stdint.h>
TEST(Column, basis) {
// Create a new database
SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
EXPECT_EQ(SQLITE_OK, db.getErrorCode());
EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode());
// Create a new table
EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL, binary BLOB, empty TEXT)"));
EXPECT_TRUE(db.tableExists("test"));
EXPECT_TRUE(db.tableExists(std::string("test")));
EXPECT_EQ(0, db.getLastInsertRowid());
// Create a first row (autoid: 1) with all kind of data and a null value
SQLite::Statement insert(db, "INSERT INTO test VALUES (NULL, \"first\", 123, 0.123, ?, NULL)");
// Bind the blob value to the first parameter of the SQL query
char buffer[] = "blob";
void* blob = &buffer;
int size = sizeof(buffer);
insert.bind(1, blob, size);
// Execute the one-step query to insert the row
EXPECT_EQ(1, insert.exec());
EXPECT_EQ(1, db.getLastInsertRowid());
EXPECT_EQ(1, db.getTotalChanges());
// Compile a SQL query
SQLite::Statement query(db, "SELECT * FROM test");
EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str());
EXPECT_EQ(6, query.getColumnCount ());
query.executeStep();
EXPECT_TRUE (query.isOk());
EXPECT_FALSE(query.isDone());
// validates every variant of cast operators, and conversions of types
{
sqlite3_int64 id = query.getColumn(0); // operator sqlite3_int64()
int64_t id2 = query.getColumn(0); // operator sqlite3_int64() (or long() with GCC 64bits)
long id3 = query.getColumn(0); // operator sqlite3_int64() (or long() with GCC 64bits)
const char* ptxt = query.getColumn(1); // operator const char*()
const std::string msg = query.getColumn(1); // operator std::string() (or const char* with MSVC)
const int integer = query.getColumn(2); // operator int()
const double real = query.getColumn(3); // operator double()
const void* pblob = query.getColumn(4); // operator void*()
const void* pempty = query.getColumn(5); // operator void*()
EXPECT_EQ(1, id);
EXPECT_EQ(1, id2);
EXPECT_EQ(1, id3);
EXPECT_STREQ("first", ptxt);
EXPECT_EQ("first", msg);
EXPECT_EQ(123, integer);
EXPECT_EQ(0.123, real);
EXPECT_EQ(0, memcmp("blob", pblob, size));
EXPECT_EQ(NULL, pempty);
}
// validates every variant of explicit getters
{
int64_t id = query.getColumn(0).getInt64();
const char* ptxt = query.getColumn(1).getText();
const std::string msg = query.getColumn(1).getText();
const int integer = query.getColumn(2).getInt();
const double real = query.getColumn(3).getDouble();
const void* pblob = query.getColumn(1).getBlob();
EXPECT_EQ(1, id);
EXPECT_STREQ("first", ptxt);
EXPECT_EQ("first", msg);
EXPECT_EQ(123, integer);
EXPECT_EQ(0.123, real);
EXPECT_EQ(0, memcmp("first", pblob, 5));
}
// Validate getBytes(), getType(), isInteger(), isNull()...
EXPECT_EQ(SQLITE_INTEGER, query.getColumn(0).getType());
EXPECT_EQ(true, query.getColumn(0).isInteger());
EXPECT_EQ(false, query.getColumn(0).isFloat());
EXPECT_EQ(false, query.getColumn(0).isText());
EXPECT_EQ(false, query.getColumn(0).isBlob());
EXPECT_EQ(false, query.getColumn(0).isNull());
EXPECT_STREQ("1", query.getColumn(0).getText()); // convert to string
EXPECT_EQ(1, query.getColumn(0).getBytes()); // size of the string "1" without the null terminator
EXPECT_EQ(SQLITE_TEXT, query.getColumn(1).getType());
EXPECT_EQ(false, query.getColumn(1).isInteger());
EXPECT_EQ(false, query.getColumn(1).isFloat());
EXPECT_EQ(true, query.getColumn(1).isText());
EXPECT_EQ(false, query.getColumn(1).isBlob());
EXPECT_EQ(false, query.getColumn(1).isNull());
EXPECT_STREQ("first", query.getColumn(1).getText()); // convert to string
EXPECT_EQ(5, query.getColumn(1).getBytes()); // size of the string "first"
EXPECT_EQ(SQLITE_INTEGER, query.getColumn(2).getType());
EXPECT_EQ(true, query.getColumn(2).isInteger());
EXPECT_EQ(false, query.getColumn(2).isFloat());
EXPECT_EQ(false, query.getColumn(2).isText());
EXPECT_EQ(false, query.getColumn(2).isBlob());
EXPECT_EQ(false, query.getColumn(2).isNull());
EXPECT_STREQ("123", query.getColumn(2).getText()); // convert to string
EXPECT_EQ(3, query.getColumn(2).getBytes()); // size of the string "123"
EXPECT_EQ(SQLITE_FLOAT, query.getColumn(3).getType());
EXPECT_EQ(false, query.getColumn(3).isInteger());
EXPECT_EQ(true, query.getColumn(3).isFloat());
EXPECT_EQ(false, query.getColumn(3).isText());
EXPECT_EQ(false, query.getColumn(3).isBlob());
EXPECT_EQ(false, query.getColumn(3).isNull());
EXPECT_STREQ("0.123", query.getColumn(3).getText()); // convert to string
EXPECT_EQ(5, query.getColumn(3).getBytes()); // size of the string "0.123"
EXPECT_EQ(SQLITE_BLOB, query.getColumn(4).getType());
EXPECT_EQ(false, query.getColumn(4).isInteger());
EXPECT_EQ(false, query.getColumn(4).isFloat());
EXPECT_EQ(false, query.getColumn(4).isText());
EXPECT_EQ(true, query.getColumn(4).isBlob());
EXPECT_EQ(false, query.getColumn(4).isNull());
EXPECT_STREQ("blob", query.getColumn(4).getText()); // convert to string
EXPECT_EQ(5, query.getColumn(4).getBytes()); // size of the string "blob" WITH the null terminator (blob)
EXPECT_EQ(SQLITE_NULL, query.getColumn(5).getType());
EXPECT_EQ(false, query.getColumn(5).isInteger());
EXPECT_EQ(false, query.getColumn(5).isFloat());
EXPECT_EQ(false, query.getColumn(5).isText());
EXPECT_EQ(false, query.getColumn(5).isBlob());
EXPECT_EQ(true, query.getColumn(5).isNull());
EXPECT_STREQ("", query.getColumn(5).getText()); // convert to string
EXPECT_EQ(0, query.getColumn(5).getBytes()); // size of the string "" without the null terminator
}
TEST(Column, getName) {
// Create a new database
SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT)"));
EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")"));
// Compile a SQL query, using the "id" column name as-is, but aliasing the "msg" column with new name "value"
SQLite::Statement query(db, "SELECT id, msg as value FROM test");
query.executeStep();
// Show how to get the aliased names of the result columns.
const std::string name0 = query.getColumn(0).getName();
const std::string name1 = query.getColumn(1).getName();
EXPECT_EQ("id", name0);
EXPECT_EQ("value", name1);
#ifdef SQLITE_ENABLE_COLUMN_METADATA
// Show how to get origin names of the table columns from which theses result columns come from.
// Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be
// also defined at compile times of the SQLite library itself.
const std::string oname0 = query.getColumn(0).getOriginName();
const std::string oname1 = query.getColumn(1).getOriginName();
EXPECT_EQ("id", oname0);
EXPECT_EQ("msg", oname1);
#endif
}

View File

@ -21,7 +21,7 @@ namespace SQLite
/// definition of the assertion handler enabled when SQLITECPP_ENABLE_ASSERT_HANDLER is defined in the project (CMakeList.txt) /// 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) void assertion_failed(const char* apFile, const long apLine, const char* apFunc, const char* apExpr, const char* apMsg)
{ {
// TODO test that this assertion callback get called // TODO: unit test that this assertion callback get called (already tested manually)
std::cout << "assertion_failed(" << apFile << ", " << apLine << ", " << apFunc << ", " << apExpr << ", " << apMsg << ")\n"; std::cout << "assertion_failed(" << apFile << ", " << apLine << ", " << apFunc << ", " << apExpr << ", " << apMsg << ")\n";
} }
} }
@ -32,7 +32,8 @@ TEST(Database, ctorExecCreateDropExist) {
remove("test.db3"); remove("test.db3");
{ {
// Try to open an unexisting database // Try to open an unexisting database
EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception); std::string filename = "test.db3";
EXPECT_THROW(SQLite::Database not_found(filename), SQLite::Exception);
// Create a new database // Create a new database
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
@ -54,11 +55,75 @@ TEST(Database, ctorExecCreateDropExist) {
remove("test.db3"); remove("test.db3");
} }
TEST(Database, exec) { TEST(Database, createCloseReopen) {
remove("test.db3"); remove("test.db3");
{ {
// Try to open the unexisting database
EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception);
// Create a new database // Create a new database
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); 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
}
#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());
}
}
#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 // 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, // NOTE: here exec() returns 0 only because it is the first statements since database connexion,
@ -114,16 +179,11 @@ TEST(Database, exec) {
EXPECT_EQ(4, db.getLastInsertRowid()); EXPECT_EQ(4, db.getLastInsertRowid());
EXPECT_EQ(9, db.getTotalChanges()); EXPECT_EQ(9, db.getTotalChanges());
#endif #endif
} // Close DB test.db3
remove("test.db3");
} }
TEST(Database, execAndGet) { TEST(Database, execAndGet) {
remove("test.db3");
{
// Create a new database // Create a new database
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE);
// Create a new table with an explicit "id" column aliasing the underlying rowid // 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)"); db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)");
@ -136,16 +196,12 @@ TEST(Database, execAndGet) {
// Get a single value result with an easy to use shortcut // 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("second", db.execAndGet("SELECT value FROM test WHERE id=2"));
EXPECT_STREQ("third", db.execAndGet("SELECT value FROM test WHERE weight=7")); EXPECT_STREQ("third", db.execAndGet("SELECT value FROM test WHERE weight=7"));
EXPECT_EQ(3, (int)db.execAndGet("SELECT weight FROM test WHERE value=\"first\"")); EXPECT_EQ(3, db.execAndGet("SELECT weight FROM test WHERE value=\"first\"").getInt());
} // Close DB test.db3
remove("test.db3");
} }
TEST(Database, execException) { TEST(Database, execException) {
remove("test.db3");
{
// Create a new database // Create a new database
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE);
EXPECT_EQ(SQLITE_OK, db.getErrorCode()); EXPECT_EQ(SQLITE_OK, db.getErrorCode());
EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode());
@ -170,6 +226,11 @@ TEST(Database, execException) {
EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 3)")); EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 3)"));
// exception with SQL error: "No row to get a column from" // exception with SQL error: "No row to get a column from"
EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value=\"second\""), SQLite::Exception); EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value=\"second\""), SQLite::Exception);
} // Close DB test.db3
remove("test.db3"); // 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());
} }
// TODO: test Database::createFunction()

View File

@ -18,10 +18,8 @@
TEST(Statement, invalid) { TEST(Statement, invalid) {
remove("test.db3");
{
// Create a new database // Create a new database
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
EXPECT_EQ(SQLITE_OK, db.getErrorCode()); EXPECT_EQ(SQLITE_OK, db.getErrorCode());
EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode());
@ -53,7 +51,7 @@ TEST(Statement, invalid) {
EXPECT_FALSE(query.isOk()); EXPECT_FALSE(query.isOk());
EXPECT_FALSE(query.isDone()); EXPECT_FALSE(query.isDone());
query.exec(); query.executeStep();
EXPECT_FALSE(query.isOk()); EXPECT_FALSE(query.isOk());
EXPECT_TRUE( query.isDone()); EXPECT_TRUE( query.isDone());
query.reset(); query.reset();
@ -70,10 +68,81 @@ TEST(Statement, invalid) {
EXPECT_EQ(SQLITE_RANGE, db.getErrorCode()); EXPECT_EQ(SQLITE_RANGE, db.getErrorCode());
EXPECT_EQ(SQLITE_RANGE, db.getExtendedErrorCode()); EXPECT_EQ(SQLITE_RANGE, db.getExtendedErrorCode());
query.exec(); query.exec(); // exec() instead of executeStep() as there is no result
EXPECT_THROW(query.isColumnNull(0), SQLite::Exception); EXPECT_THROW(query.isColumnNull(0), SQLite::Exception);
EXPECT_THROW(query.getColumn(0), SQLite::Exception); EXPECT_THROW(query.getColumn(0), SQLite::Exception);
} // Close DB test.db3 // Add a first row
remove("test.db3"); EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")"));
EXPECT_EQ(1, db.getLastInsertRowid());
EXPECT_EQ(1, db.getTotalChanges());
query.reset();
EXPECT_FALSE(query.isOk());
EXPECT_FALSE(query.isDone());
EXPECT_THROW(query.exec(), SQLite::Exception); // exec() shall throw as it does not expect a result
}
// TODO: test every kind of binding + clearBindings()
TEST(Statement, getColumnByName) {
// Create a new database
SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
EXPECT_EQ(SQLITE_OK, db.getErrorCode());
EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode());
// Create a new table
EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL)"));
EXPECT_EQ(SQLITE_OK, db.getErrorCode());
EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode());
// Create a first row
EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)"));
EXPECT_EQ(1, db.getLastInsertRowid());
EXPECT_EQ(1, db.getTotalChanges());
// Compile a SQL query
SQLite::Statement query(db, "SELECT * FROM test");
EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str());
EXPECT_EQ(4, query.getColumnCount());
query.executeStep();
EXPECT_TRUE (query.isOk());
EXPECT_FALSE(query.isDone());
// Look for unexisting columns
EXPECT_THROW(query.getColumn("unknown"), SQLite::Exception);
EXPECT_THROW(query.getColumn(""), SQLite::Exception);
const std::string msg = query.getColumn("msg");
const int integer = query.getColumn("int");
const double real = query.getColumn("double");
EXPECT_EQ("first", msg);
EXPECT_EQ(123, integer);
EXPECT_EQ(0.123, real);
}
TEST(Statement, getName) {
// Create a new database
SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT)"));
// Compile a SQL query, using the "id" column name as-is, but aliasing the "msg" column with new name "value"
SQLite::Statement query(db, "SELECT id, msg as value FROM test");
query.executeStep();
const std::string name0 = query.getColumnName(0);
const std::string name1 = query.getColumnName(1);
EXPECT_EQ("id", name0);
EXPECT_EQ("value", name1);
#ifdef SQLITE_ENABLE_COLUMN_METADATA
// Show how to get origin names of the table columns from which theses result columns come from.
// Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be
// also defined at compile times of the SQLite library itself.
const std::string oname0 = query.getColumnOriginName(0);
const std::string oname1 = query.getColumnOriginName(1);
EXPECT_EQ("id", oname0);
EXPECT_EQ("msg", oname1);
#endif
} }