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
Renamed Column::getName() to Column::getOriginName()
Added a new Column::getName()
Added Column::getName()
Version 0.7.0 - January 9 2014
Added a new Database::createFunction() API
Added Database::createFunction()
Added std::string version of existing APIs
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)
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.
#
# 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)
if (NOT BIICODE)
cmake_minimum_required(VERSION 2.6)
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:
if (WIN32)
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")
# 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)
# then Compiler/IDE differences:
if (MSVC)
set(CPPLINT_ARG_OUTPUT "--output=vs7")
@ -69,6 +53,22 @@ set(CPPLINT_ARG_VERBOSE "--verbose=3")
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 ##
# 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
set(SQLITECPP_TESTS
tests/Column_test.cpp
tests/Database_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"))
# SQLite3 library (Windows only)
# SQLite3 library (mostly usefull under Windows)
option (SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ON)
if (WIN32)
if (SQLITECPP_INTERNAL_SQLITE)
# build the SQLite3 C library for Windows (for ease of use) versus Linux sqlite3-dev package
add_subdirectory(sqlite3)
include_directories("${PROJECT_SOURCE_DIR}/sqlite3")
endif (SQLITECPP_INTERNAL_SQLITE)
endif (WIN32)
option(SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ${SQLITECPP_INTERNAL_SQLITE_DEFAULT})
if (SQLITECPP_INTERNAL_SQLITE)
# build the SQLite3 C library for Windows (for ease of use) versus Linux sqlite3-dev package
add_subdirectory(sqlite3)
include_directories("${PROJECT_SOURCE_DIR}/sqlite3")
endif (SQLITECPP_INTERNAL_SQLITE)
# Optional additional targets:
option(SQLITECPP_RUN_CPPLINT "Run cpplint.py tool for Google C++ StyleGuide." ON)
if (SQLITECPP_RUN_CPPLINT)
find_package(PythonLibs)
if (PYTHONLIBS_FOUND)
find_package(PythonInterp)
if (PYTHONINTERP_FOUND)
# add a cpplint target to the "all" target
add_custom_target(SQLiteCpp_cpplint
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)
message(STATUS "SQLITECPP_RUN_CPPLINT OFF")
endif (SQLITECPP_RUN_CPPLINT)
@ -205,9 +205,9 @@ if (SQLITECPP_BUILD_EXAMPLES)
# add the basic example executable
add_executable(SQLiteCpp_example1 ${SQLITECPP_EXAMPLES})
target_link_libraries(SQLiteCpp_example1 SQLiteCpp sqlite3)
else(SQLITECPP_BUILD_EXAMPLES)
else (SQLITECPP_BUILD_EXAMPLES)
message(STATUS "SQLITECPP_BUILD_EXAMPLES OFF")
endif(SQLITECPP_BUILD_EXAMPLES)
endif (SQLITECPP_BUILD_EXAMPLES)
option(SQLITECPP_BUILD_TESTS "Build and run tests." OFF)
if (SQLITECPP_BUILD_TESTS)
@ -232,9 +232,25 @@ if (SQLITECPP_BUILD_TESTS)
if (SQLITECPP_BUILD_EXAMPLES)
# does the example1 runs successfully?
add_test(Example1Run SQLiteCpp_example1)
endif(SQLITECPP_BUILD_EXAMPLES)
endif (SQLITECPP_BUILD_EXAMPLES)
else (SQLITECPP_BUILD_TESTS)
message(STATUS "SQLITECPP_BUILD_TESTS OFF")
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++
---------
[![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")
[![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.

View File

@ -1,28 +1,34 @@
Add a full googletest suite
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
Add a Tutorial for SQLite newbies
Add a real example (rework current example?)
Improve Github Wiki pages with the FAQ: Installation, Examples, Tutorial, How to contribute
Publish the Doxygen Documentation in the Github Pages (gh-pages branch)
Missing features in v0.9.9:
- getColumnByName() (issue #23) ? std::map getRow() ?
Missing features in v1.1.0:
- bind a SQLITE_STATIC value (string/blob)
- 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
- 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:
- #39: SAVEPOINT https://www.sqlite.org/lang_savepoint.html
- backup support to/from file/:memory:
- Function ?
- Add optional usage of experimental sqlite3_trace() function to enable statistics
- Agregate ?
- support for different transaction mode ? NO: too specific
- operator<< binding ? NO: redundant with bind()
- 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)
_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__':

View File

@ -2,9 +2,9 @@
* @file main.cpp
* @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
* or copy at http://opensource.org/licenses/MIT)
@ -83,7 +83,11 @@ private:
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
{
// 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";
// 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";
// 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;
}
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)
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
while (query.executeStep())
{
// Demonstrate how to get some typed column value (and the equivalent explicit call)
int id = query.getColumn(0); // = query.getColumn(0).getInt()
//const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText()
std::string value2 = query.getColumn(1); // = query.getColumn(1).getText()
int bytes = query.getColumn(1).getBytes();
double weight = query.getColumn(2); // = query.getColumn(2).getInt()
static bool bFirst = true;
if (bFirst)
{
// Show how to get the aliased names of the result columns.
std::string name0 = query.getColumn(0).getName();
std::string name1 = query.getColumn(1).getName();
std::string name2 = query.getColumn(2).getName();
std::cout << "aliased result [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n";
#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.
name0 = query.getColumn(0).getOriginName();
name1 = query.getColumn(1).getOriginName();
name2 = query.getColumn(2).getOriginName();
std::cout << "origin table 'test' [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n";
#endif
bFirst = false;
}
std::cout << "row (" << id << ", \"" << value2.c_str() << "\" " << bytes << " bytes, " << weight << ")\n";
// Demonstrates how to get some typed column value (and the equivalent explicit call)
const int id = query.getColumn(0); // = query.getColumn(0).getInt();
//const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText();
const std::string value = query.getColumn(1); // = query.getColumn(1).getText();
const int bytes = query.getColumn(1).size(); // .getColumn(1).getBytes();
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";
// Show how to get the aliased names of the result columns.
const std::string name0 = query.getColumnName(0);
const std::string name1 = query.getColumnName(1);
const std::string name2 = query.getColumnName(2);
std::cout << "aliased result [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n";
#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);
const std::string oname2 = query.getColumnOriginName(2);
std::cout << "origin table 'test' [\"" << oname0.c_str() << "\", \"" << oname1.c_str() << "\", \"" << oname2.c_str() << "\"]\n";
#endif
// 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";
}
///// 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
query.reset();
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
query.bind(1, "6");
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())
{
// Demonstrate that inserting column value in a std:ostream is natural
std::cout << "row (" << query.getColumn(0) << ", \"" << query.getColumn(1) << "\", " << query.getColumn(2) << ")\n";
id = query.getColumn(0).getInt();
value = query.getColumn(1).getText();
weight = query.getColumn(2).getInt();
std::cout << "row (" << id << ", \"" << value << "\", " << weight << ")\n";
}
}
catch (std::exception& e)
@ -157,13 +208,13 @@ int main ()
}
////////////////////////////////////////////////////////////////////////////
// Object Oriented Basic example (2/6) :
// Object Oriented Basic example (3/7) :
try
{
// Open the database and compile the query
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(6);
example.ListGreaterThan(2);
@ -174,7 +225,7 @@ int main ()
return EXIT_FAILURE; // unexpected error : exit the example program
}
// The execAndGet wrapper example (3/6) :
// The execAndGet wrapper example (4/7) :
try
{
// 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
{
// Open a database file in create/write mode
@ -235,7 +286,7 @@ int main ()
remove("test.db3");
////////////////////////////////////////////////////////////////////////////
// RAII transaction example (5/6) :
// RAII transaction example (6/7) :
try
{
// Open a database file in create/write mode
@ -303,7 +354,7 @@ int main ()
remove("transaction.db3");
////////////////////////////////////////////////////////////////////////////
// Binary blob and in-memory database example (6/6) :
// Binary blob and in-memory database example (7/7) :
try
{
// Open a database file in create/write mode

View File

@ -40,7 +40,7 @@ namespace SQLite
#else
// 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)
#endif

View File

@ -3,7 +3,7 @@
* @ingroup SQLiteCpp
* @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
* or copy at http://opensource.org/licenses/MIT)
@ -62,11 +62,13 @@ public:
// 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
#ifdef SQLITE_ENABLE_COLUMN_METADATA
#ifdef SQLITE_ENABLE_COLUMN_METADATA
/**
* @brief Return a pointer to the table column name that is the origin of this result column
*
@ -184,8 +186,9 @@ public:
{
return getBlob();
}
#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>})
// 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>'
@ -196,6 +199,16 @@ public:
return getText();
}
#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.
inline const char* errmsg() const

View File

@ -53,13 +53,17 @@ public:
*
* Exception is thrown in case of error, then the Database object is NOT constructed.
*
* @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] apVfs UTF-8 name of custom VFS to use, or nullptr for sqlite3 default
* @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] 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
*
* @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.
@ -71,13 +75,17 @@ public:
*
* Exception is thrown in case of error, then the Database object is NOT constructed.
*
* @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] aVfs UTF-8 name of custom VFS to use, or empty string for sqlite3 default
* @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] 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
*
* @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.
@ -89,6 +97,21 @@ public:
*/
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.
*
@ -206,16 +229,6 @@ public:
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.
*
@ -323,6 +336,25 @@ public:
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:
/// @{ Database must be non-copyable
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
*/
void check(const int aRet) const;
inline void check(const int aRet) const
{
if (SQLITE_OK != aRet)
{
throw SQLite::Exception(sqlite3_errmsg(mpSQLite));
}
}
private:
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.
*
* 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
* 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
* numbers used in [SQLITECPP_VERSION].
*/
#define SQLITECPP_VERSION "0.9.9"
#define SQLITECPP_VERSION_NUMBER 0009009
#define SQLITECPP_VERSION "1.1.0"
#define SQLITECPP_VERSION_NUMBER 1001000

View File

@ -12,6 +12,7 @@
#include <sqlite3.h>
#include <string>
#include <map>
#include <SQLiteCpp/Exception.h>
@ -78,8 +79,12 @@ public:
/**
* @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,
@ -274,7 +279,8 @@ public:
* 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 :
* Throw an exception if there is no row to return a Column from:
* - if provided index is out of bound
* - before any executeStep() call
* - after the last executeStep() returned false
* - after a reset() call
@ -283,8 +289,7 @@ public:
*
* @param[in] aIndex Index of the column, starting at 0
*
* @note This method is no more const, starting in v0.5,
* which reflects the fact that the returned Column object will
* @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".
@ -295,15 +300,68 @@ public:
*/
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
*
* @param[in] aIndex Index of the column, starting at 0
*
* @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;
/**
* @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.
@ -395,12 +453,44 @@ private:
*
* @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:
std::string mQuery; //!< UTF-8 SQL Query
Ptr mStmtPtr; //!< Shared Pointer to the prepared SQLite Statement Object
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 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)
#
# 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
# or copy at http://opensource.org/licenses/MIT)

View File

@ -3,7 +3,7 @@
* @ingroup SQLiteCpp
* @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
* or copy at http://opensource.org/licenses/MIT)
@ -31,14 +31,14 @@ Column::~Column() noexcept // nothrow
}
// Return the named assigned to this result column (potentially aliased)
const char * Column::getName() const noexcept // nothrow
const char* Column::getName() const noexcept // nothrow
{
return sqlite3_column_name(mStmtPtr, mIndex);
}
#ifdef SQLITE_ENABLE_COLUMN_METADATA
// Return the name of the table column that is the origin of this result column
const char * Column::getOriginName() const noexcept // nothrow
const char* Column::getOriginName() const noexcept // nothrow
{
return sqlite3_column_origin_name(mStmtPtr, mIndex);
}

View File

@ -26,45 +26,85 @@ namespace SQLite
// 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),
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)
{
std::string strerr = sqlite3_errmsg(mpSQLite);
sqlite3_close(mpSQLite); // close is required even in case of error on opening
throw SQLite::Exception(strerr);
}
if (aBusyTimeoutMs > 0)
{
setBusyTimeout(aBusyTimeoutMs);
}
}
// 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),
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)
{
std::string strerr = sqlite3_errmsg(mpSQLite);
sqlite3_close(mpSQLite); // close is required even in case of error on opening
throw SQLite::Exception(strerr);
}
if (aBusyTimeoutMs > 0)
{
setBusyTimeout(aBusyTimeoutMs);
}
}
// Close the SQLite database connection.
Database::~Database() noexcept // nothrow
{
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
const int ret = sqlite3_close(mpSQLite);
// 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...).
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);
// 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=?");
query.bind(1, apTableName);
(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);
}
// 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.
// Parameter details can be found here: http://www.sqlite.org/c3ref/create_function.html
void Database::createFunction(const char* apFuncName,
@ -119,11 +150,31 @@ void Database::createFunction(const char* apFuncName,
if (abDeterministic) {
TextRep = TextRep|SQLITE_DETERMINISTIC;
}
int ret = sqlite3_create_function_v2(mpSQLite, apFuncName, aNbArg, TextRep,
apApp, apFunc, apStep, apFinal, apDestroy);
const int ret = sqlite3_create_function_v2(mpSQLite, apFuncName, aNbArg, TextRep,
apApp, apFunc, apStep, apFinal, apDestroy);
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

View File

@ -54,67 +54,66 @@ void Statement::reset()
{
mbOk = false;
mbDone = false;
int ret = sqlite3_reset(mStmtPtr);
const int ret = sqlite3_reset(mStmtPtr);
check(ret);
}
// Clears away all the bindings of a prepared statement.
// "Contrary to the intuition of many, [sqlite3_reset()] does not reset
// ** the [sqlite3_bind_blob | bindings] on a [prepared statement]."
void Statement::clearBindings(void)
void Statement::clearBindings()
{
mbOk = false;
mbDone = false;
int ret = sqlite3_clear_bindings(mStmtPtr);
const int ret = sqlite3_clear_bindings(mStmtPtr);
check(ret);
}
// 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)
{
int ret = sqlite3_bind_int(mStmtPtr, aIndex, aValue);
const int ret = sqlite3_bind_int(mStmtPtr, aIndex, aValue);
check(ret);
}
// 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)
{
int ret = sqlite3_bind_int64(mStmtPtr, aIndex, aValue);
const int ret = sqlite3_bind_int64(mStmtPtr, aIndex, aValue);
check(ret);
}
// 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)
{
int ret = sqlite3_bind_double(mStmtPtr, aIndex, aValue);
const int ret = sqlite3_bind_double(mStmtPtr, aIndex, aValue);
check(ret);
}
// 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)
{
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);
}
// 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)
{
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);
}
// 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)
{
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);
}
// Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const int aIndex)
{
int ret = sqlite3_bind_null(mStmtPtr, aIndex);
const int ret = sqlite3_bind_null(mStmtPtr, aIndex);
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
void Statement::bind(const char* apName, const int& aValue)
{
int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_int(mStmtPtr, index, aValue);
const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
const int ret = sqlite3_bind_int(mStmtPtr, index, aValue);
check(ret);
}
// 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)
{
int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_int64(mStmtPtr, index, aValue);
const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
const int ret = sqlite3_bind_int64(mStmtPtr, index, aValue);
check(ret);
}
// 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)
{
int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_double(mStmtPtr, index, aValue);
const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
const int ret = sqlite3_bind_double(mStmtPtr, index, aValue);
check(ret);
}
// 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)
{
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 index = sqlite3_bind_parameter_index(mStmtPtr, apName);
const int ret = sqlite3_bind_text(mStmtPtr, index, aValue.c_str(),
static_cast<int>(aValue.size()), SQLITE_TRANSIENT);
check(ret);
}
// 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)
{
int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_text(mStmtPtr, index, apValue, -1, SQLITE_TRANSIENT);
const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
const int ret = sqlite3_bind_text(mStmtPtr, index, apValue, -1, SQLITE_TRANSIENT);
check(ret);
}
// 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)
{
int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_blob(mStmtPtr, index, apValue, aSize, SQLITE_TRANSIENT);
const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
const int ret = sqlite3_bind_blob(mStmtPtr, index, apValue, aSize, SQLITE_TRANSIENT);
check(ret);
}
// Bind a NULL value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
void Statement::bind(const char* apName)
{
int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
int ret = sqlite3_bind_null(mStmtPtr, index);
const int index = sqlite3_bind_parameter_index(mStmtPtr, apName);
const int ret = sqlite3_bind_null(mStmtPtr, index);
check(ret);
}
@ -181,7 +181,7 @@ bool Statement::executeStep()
{
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
{
mbOk = true;
@ -200,7 +200,7 @@ bool Statement::executeStep()
}
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)
@ -211,7 +211,7 @@ int Statement::exec()
{
if (false == mbDone)
{
int ret = sqlite3_step(mStmtPtr);
const int ret = sqlite3_step(mStmtPtr);
if (SQLITE_DONE == ret) // the statement has finished executing successfully
{
mbOk = false;
@ -221,7 +221,7 @@ int Statement::exec()
{
mbOk = false;
mbDone = false;
throw SQLite::Exception("exec() does not expect results");
throw SQLite::Exception("exec() does not expect results. Use executeStep.");
}
else
{
@ -232,7 +232,7 @@ int Statement::exec()
}
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)
@ -243,43 +243,61 @@ int Statement::exec()
// (use the Column copy-constructor)
Column Statement::getColumn(const int aIndex)
{
if (false == mbOk)
{
throw SQLite::Exception("No row to get a column from");
}
else if ((aIndex < 0) || (aIndex >= mColumnCount))
{
throw SQLite::Exception("Column index out of range");
}
checkRow();
checkIndex(aIndex);
// Share the Statement Object handle with the new Column created
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
bool Statement::isColumnNull(const int aIndex) const
{
if (false == mbOk)
{
throw SQLite::Exception("No row to get a column from");
}
else if ((aIndex < 0) || (aIndex >= mColumnCount))
{
throw SQLite::Exception("Column index out of range");
}
checkRow();
checkIndex(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
void Statement::check(const int aRet)
// Return the named assigned to the specified result column (potentially aliased)
const char* Statement::getColumnName(const int aIndex) const
{
if (SQLITE_OK != aRet)
{
throw SQLite::Exception(sqlite3_errmsg(mStmtPtr));
}
checkIndex(aIndex);
return sqlite3_column_name(mStmtPtr, aIndex);
}
#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
@ -296,7 +314,7 @@ Statement::Ptr::Ptr(sqlite3* apSQLite, std::string& aQuery) :
mpStmt(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)
{
throw SQLite::Exception(sqlite3_errmsg(mpSQLite));

View File

@ -38,6 +38,7 @@ Transaction::~Transaction() noexcept // nothrow
catch (SQLite::Exception& e)
{
// Never throw an exception in a destructor
(void)e; // warning proof
SQLITECPP_ASSERT(false, e.what()); // See SQLITECPP_ENABLE_ASSERT_HANDLER
}
}
@ -53,7 +54,7 @@ void Transaction::commit()
}
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)
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";
}
}
@ -32,7 +32,8 @@ TEST(Database, ctorExecCreateDropExist) {
remove("test.db3");
{
// 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
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
@ -54,122 +55,182 @@ TEST(Database, ctorExecCreateDropExist) {
remove("test.db3");
}
TEST(Database, exec) {
TEST(Database, createCloseReopen) {
remove("test.db3");
{
// Try to open the unexisting 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);
// 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.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.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(2, db.getLastInsertRowid());
EXPECT_EQ(2, db.getTotalChanges());
// third row : insert the "third" text value into new row of id 3
EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")"));
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(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(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");
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
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(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(4, db.getLastInsertRowid());
EXPECT_EQ(9, db.getTotalChanges());
#endif
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
// 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.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.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(2, db.getLastInsertRowid());
EXPECT_EQ(2, db.getTotalChanges());
// third row : insert the "third" text value into new row of id 3
EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")"));
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(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(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(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(4, db.getLastInsertRowid());
EXPECT_EQ(9, db.getTotalChanges());
#endif
}
TEST(Database, execAndGet) {
remove("test.db3");
{
// Create a new database
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
// 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)");
// 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)"));
// 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"));
EXPECT_EQ(3, (int)db.execAndGet("SELECT weight FROM test WHERE value=\"first\""));
} // Close DB test.db3
remove("test.db3");
// 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"));
EXPECT_EQ(3, db.execAndGet("SELECT weight FROM test WHERE value=\"first\"").getInt());
}
TEST(Database, execException) {
remove("test.db3");
{
// Create a new database
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
EXPECT_EQ(SQLITE_OK, db.getErrorCode());
EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode());
// 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());
// 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());
// 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());
// 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());
// 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());
// 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());
// exception with SQL error: "No row to get a column from"
EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value=\"first\""), SQLite::Exception);
// 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);
} // Close DB test.db3
remove("test.db3");
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());
}
// TODO: test Database::createFunction()

View File

@ -18,62 +18,131 @@
TEST(Statement, invalid) {
remove("test.db3");
{
// Create a new database
SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
EXPECT_EQ(SQLITE_OK, db.getErrorCode());
EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode());
// 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());
// Compile a SQL query, but without any table in the database
EXPECT_THROW(SQLite::Statement query(db, "SELECT * FROM test"), SQLite::Exception);
EXPECT_EQ(SQLITE_ERROR, db.getErrorCode());
EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode());
// Compile a SQL query, but without any table in the database
EXPECT_THROW(SQLite::Statement query(db, "SELECT * FROM test"), SQLite::Exception);
EXPECT_EQ(SQLITE_ERROR, db.getErrorCode());
EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode());
EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"));
EXPECT_EQ(SQLITE_OK, db.getErrorCode());
EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode());
EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"));
EXPECT_EQ(SQLITE_OK, db.getErrorCode());
EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode());
// Compile a SQL query with no parameter
SQLite::Statement query(db, "SELECT * FROM test");
EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str());
EXPECT_EQ(2, query.getColumnCount ());
EXPECT_FALSE(query.isOk());
EXPECT_FALSE(query.isDone());
EXPECT_THROW(query.isColumnNull(-1), SQLite::Exception);
EXPECT_THROW(query.isColumnNull(0), SQLite::Exception);
EXPECT_THROW(query.isColumnNull(1), SQLite::Exception);
EXPECT_THROW(query.isColumnNull(2), SQLite::Exception);
EXPECT_THROW(query.getColumn(-1), SQLite::Exception);
EXPECT_THROW(query.getColumn(0), SQLite::Exception);
EXPECT_THROW(query.getColumn(1), SQLite::Exception);
EXPECT_THROW(query.getColumn(2), SQLite::Exception);
// Compile a SQL query with no parameter
SQLite::Statement query(db, "SELECT * FROM test");
EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str());
EXPECT_EQ(2, query.getColumnCount ());
EXPECT_FALSE(query.isOk());
EXPECT_FALSE(query.isDone());
EXPECT_THROW(query.isColumnNull(-1), SQLite::Exception);
EXPECT_THROW(query.isColumnNull(0), SQLite::Exception);
EXPECT_THROW(query.isColumnNull(1), SQLite::Exception);
EXPECT_THROW(query.isColumnNull(2), SQLite::Exception);
EXPECT_THROW(query.getColumn(-1), SQLite::Exception);
EXPECT_THROW(query.getColumn(0), SQLite::Exception);
EXPECT_THROW(query.getColumn(1), SQLite::Exception);
EXPECT_THROW(query.getColumn(2), SQLite::Exception);
query.reset();
EXPECT_FALSE(query.isOk());
EXPECT_FALSE(query.isDone());
query.reset();
EXPECT_FALSE(query.isOk());
EXPECT_FALSE(query.isDone());
query.exec();
EXPECT_FALSE(query.isOk());
EXPECT_TRUE( query.isDone());
query.reset();
EXPECT_FALSE(query.isOk());
EXPECT_FALSE(query.isDone());
query.executeStep();
EXPECT_FALSE(query.isOk());
EXPECT_TRUE( query.isDone());
query.reset();
EXPECT_FALSE(query.isOk());
EXPECT_FALSE(query.isDone());
query.reset();
EXPECT_THROW(query.bind(-1, 123), SQLite::Exception);
EXPECT_THROW(query.bind(0, 123), SQLite::Exception);
EXPECT_THROW(query.bind(1, 123), SQLite::Exception);
EXPECT_THROW(query.bind(2, 123), SQLite::Exception);
EXPECT_THROW(query.bind(0, "abc"), SQLite::Exception);
EXPECT_THROW(query.bind(0), SQLite::Exception);
EXPECT_EQ(SQLITE_RANGE, db.getErrorCode());
EXPECT_EQ(SQLITE_RANGE, db.getExtendedErrorCode());
query.reset();
EXPECT_THROW(query.bind(-1, 123), SQLite::Exception);
EXPECT_THROW(query.bind(0, 123), SQLite::Exception);
EXPECT_THROW(query.bind(1, 123), SQLite::Exception);
EXPECT_THROW(query.bind(2, 123), SQLite::Exception);
EXPECT_THROW(query.bind(0, "abc"), SQLite::Exception);
EXPECT_THROW(query.bind(0), SQLite::Exception);
EXPECT_EQ(SQLITE_RANGE, db.getErrorCode());
EXPECT_EQ(SQLITE_RANGE, db.getExtendedErrorCode());
query.exec();
EXPECT_THROW(query.isColumnNull(0), SQLite::Exception);
EXPECT_THROW(query.getColumn(0), SQLite::Exception);
query.exec(); // exec() instead of executeStep() as there is no result
EXPECT_THROW(query.isColumnNull(0), SQLite::Exception);
EXPECT_THROW(query.getColumn(0), SQLite::Exception);
} // Close DB test.db3
remove("test.db3");
// Add a first row
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
}