Files
GdCpp12/include/CSQLite/CSQLiteStatement.h

735 lines
28 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file CSQLiteStatement.h
* @ingroup CSQLite
* @brief A prepared SQLite Statement is a compiled SQL query ready to be executed, pointing to a row of result.
*
修改自SQLiteCPP的Statement类。
1. Ptr类改用_Statemen和QSharedPointer<_Statement>实现
2. 增加errCode和errMsg保存错误信息
3. 增加不同数据类型的tryBind()不抛出异常出错生成errCode和errMsg
4. Bind类函数改成用模板函数实现index版和列名称版两套函数
5. Column相关的函数去掉了checkRow()/checkIndex(aIndex)判断和抛出异常由调用者确保有记录并且index不超界;
6. bind一个u32型数据内部转成int适用于不需要排序的u32列。如果需要排序强制转成int64再bind
*/
#pragma once
#include "CSQLiteException.h"
#include "CSQLiteColumn.h"
#include <climits> // For INT_MAX
#include <map>
#include "_Statement.h"
#include "CSQLiteColumn.h"
#include "GdCPP_Exports.h"
#pragma comment(lib, "sqlite3.lib")
// Forward declaration
class CSQLiteDB;
/**
* @brief RAII encapsulation of a prepared SQLite Statement.
*
* A Statement is a compiled SQL query ready to be executed step by step
* to provide results one row at a time.
*
* Resource Acquisition Is Initialization (RAII) means that the Statement
* is compiled in the constructor and finalized in the destructor, so that there is
* no need to worry about memory management or the validity of the underlying SQLite Statement.
*
* Thread-safety: a Statement object shall not be shared by multiple threads, because :
* 1) in the SQLite "Thread Safe" mode, "SQLite can be safely used by multiple threads
* provided that no single database connection is used simultaneously in two or more threads."
* 2) the SQLite "Serialized" mode is not supported by SQLiteC++,
* because of the way it shares the underling SQLite precompiled statement
* in a custom shared pointer (See the inner class "Statement::Ptr").
*/
class GDCPP_API CSQLiteStatement
{
// friend class CSQLiteColumn; // For access to inner member
public:
/**
* @brief Compile and register the SQL query for the provided SQLite Database Connection
*
* @param[in] aDatabase the SQLite Database Connection
* @param[in] apQuery an UTF-8 encoded query string
*
* Exception is thrown in case of error, then the Statement object is NOT constructed.
*/
CSQLiteStatement(CSQLiteDB& aDatabase, const char* apQuery);
/**
* @brief Compile and register the SQL query for the provided SQLite Database Connection
*
* @param[in] aDatabase the SQLite Database Connection
* @param[in] aQuery an UTF-8 encoded query string
*
* Exception is thrown in case of error, then the Statement object is NOT constructed.
*/
CSQLiteStatement(CSQLiteDB& aDatabase, const std::string& aQuery);
/**
* @brief Move an SQLite statement.
*
* @param[in] aStatement Statement to move
*/
CSQLiteStatement(CSQLiteStatement&& aStatement) noexcept;
/// Finalize and unregister the SQL query from the SQLite Database Connection.
~CSQLiteStatement();
/// Reset the statement to make it ready for a new execution. Throws an exception on error.
inline void reset()
{
const int ret = tryReset();
check(ret);
}
/// Reset the statement. Returns the sqlite result code instead of throwing an exception on error.
inline int tryReset() noexcept
{
mbHasRow = false;
mbDone = false;
return sqlite3_reset(*mStmtPtr);
}
/**
* @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.
*/
inline void clearBindings() // throw(SQLite::Exception)
{
const int ret = sqlite3_clear_bindings(*mStmtPtr);
check(ret);
}
////////////////////////////////////////////////////////////////////////////
// Bind a value to a parameter of the SQL statement,
// in the form "?" (unnamed), "?NNN", ":VVV", "@VVV" or "$VVV".
//
// Can use the parameter index, starting from "1", to the higher NNN value,
// or the complete parameter name "?NNN", ":VVV", "@VVV" or "$VVV"
// (prefixed with the corresponding sign "?", ":", "@" or "$")
//
// Note that for text and blob values, the SQLITE_TRANSIENT flag is used,
// which tell the sqlite library to make its own copy of the data before the bind() call returns.
// This choice is done to prevent any common misuses, like passing a pointer to a
// dynamic allocated and temporary variable (a std::string for instance).
// This is under-optimized for static data (a static text define in code)
// as well as for dynamic allocated buffer which could be transfer to sqlite
// instead of being copied.
// => if you know what you are doing, use bindNoCopy() instead of bind()
/**
* @brief Bind an int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*/
inline int tryBind(const int aIndex, const int aValue)
{
errCode = sqlite3_bind_int(*mStmtPtr, aIndex, aValue);
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bind index ";
errMsg += std::to_string(aIndex);
errMsg += " with int value ";
errMsg += std::to_string(aValue);
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
/**
* @brief Bind a 32bits unsigned int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*/
inline int tryBind(const int aIndex, const unsigned aValue)
{
errCode = sqlite3_bind_int(*mStmtPtr, aIndex, int(aValue));
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bind index ";
errMsg += std::to_string(aIndex);
errMsg += " with uint value ";
errMsg += std::to_string(aValue);
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
/**
* @brief Bind a 64bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*/
inline int tryBind(const int aIndex, const int64_t aValue)
{
errCode = sqlite3_bind_int64(*mStmtPtr, aIndex, aValue);
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bind index ";
errMsg += std::to_string(aIndex);
errMsg += " with int64_t value ";
errMsg += std::to_string(aValue);
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
/**
* @brief Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*/
inline int tryBind(const int aIndex, const double aValue)
{
errCode = sqlite3_bind_double(*mStmtPtr, aIndex, aValue);
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bind index ";
errMsg += std::to_string(aIndex);
errMsg += " with double value ";
errMsg += std::to_string(aValue);
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
/**
* @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*
* @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use
*/
inline int tryBind(const int aIndex, const std::string& aValue)
{
errCode = sqlite3_bind_text(*mStmtPtr, aIndex, aValue.c_str(),
static_cast<int>(aValue.size()), SQLITE_TRANSIENT);
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bind index ";
errMsg += std::to_string(aIndex);
errMsg += " with string ";
errMsg += aValue;
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
/**
* @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*
* @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use
*/
inline int tryBind(const int aIndex, const char* apValue)
{
errCode = sqlite3_bind_text(*mStmtPtr, aIndex, apValue, -1, SQLITE_TRANSIENT);
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bind index ";
errMsg += std::to_string(aIndex);
errMsg += " with const char * ";
errMsg += apValue;
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
/**
* @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*
* @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use
*/
inline int tryBind(const int aIndex, const void* apValue, const int aSize)
{
errCode = sqlite3_bind_blob(*mStmtPtr, aIndex, apValue, aSize, SQLITE_TRANSIENT);
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bind index ";
errMsg += std::to_string(aIndex);
errMsg += " with blob ";
// errMsg += std::to_string(aValue);
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
/**
* @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1).
*
* The string can contain null characters as it is binded using its size.
*
* @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement.
*/
int tryBindNoCopy(const int aIndex, const std::string& aValue)
{
errCode = sqlite3_bind_text(*mStmtPtr, aIndex, aValue.c_str(),
static_cast<int>(aValue.size()), SQLITE_STATIC);
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bindNoCopy index ";
errMsg += std::to_string(aIndex);
errMsg += " with string ";
errMsg += aValue;
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
/**
* @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*
* Main usage is with null-terminated literal text (aka in code static strings)
*
* @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement.
*/
int tryBindNoCopy(const int aIndex, const char* apValue)
{
errCode = sqlite3_bind_text(*mStmtPtr, aIndex, apValue, -1, SQLITE_STATIC);
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bindNoCopy index ";
errMsg += std::to_string(aIndex);
errMsg += " with const char*: ";
errMsg += apValue;
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
/**
* @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*
* @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement.
*/
int tryBindNoCopy(const int aIndex, const void* apValue, const int aSize)
{
errCode = sqlite3_bind_blob(*mStmtPtr, aIndex, apValue, aSize, SQLITE_STATIC);
if (SQLITE_OK != errCode)
{
errMsg ="Fail to bindNoCopy blob to index ";
errMsg += std::to_string(aIndex);
errMsg += " with void *, size ";
errMsg += std::to_string(aSize);
errMsg += ", errCode=";
errMsg += std::to_string(errCode);
}
return errCode;
}
template<typename T>
inline void bind(const int aIndex, T aValue)
{
const int ret = tryBind(aIndex, aValue);
if (SQLITE_OK != ret)
{
throw CSQLiteException(errMsg);
}
}
template<typename T>
inline void bindNoCopy(const int aIndex, T aValue)
{
const int ret = tryBindNoCopy(aIndex, aValue);
if (SQLITE_OK != ret)
{
throw CSQLiteException(errMsg);
}
}
template<typename T>
inline void bind(const char* apName, T aValue)
{
const int index = sqlite3_bind_parameter_index(*mStmtPtr, apName);
if(index==0){
std::string errstr("sqlite3_bind_parameter_index fail for ");
errstr += apName;
throw CSQLiteException(errstr);
}
bind(index, aValue);
}
template<typename T>
inline void bindNoCopy(const char* apName, T aValue)
{
const int index = sqlite3_bind_parameter_index(*mStmtPtr, apName);
if(index==0){
std::string errstr("sqlite3_bind_parameter_index fail for ");
errstr += apName;
throw CSQLiteException(errstr);
}
bind(index, aValue);
}
template<typename T>
inline void bind(const std::string& aName, T aValue)
{
bind(aName.c_str(), aValue);
}
/**
* @brief Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1)
*
* @see clearBindings() to set all bound parameters to NULL.
*/
inline void bindNull(const int aIndex)
{
const int ret = sqlite3_bind_null(*mStmtPtr, aIndex);
check(ret);
}
inline void bindNull(const char* apName)
{
const int index = sqlite3_bind_parameter_index(*mStmtPtr, apName);
if(index==0){
std::string errstr("sqlite3_bind_parameter_index fail for ");
errstr += apName;
throw CSQLiteException(errstr);
}
bindNull(index);
}
inline void bindNull(const std::string& aName)
{
bindNull(aName.c_str());
}
////////////////////////////////////////////////////////////////////////////
/**
* @brief Execute a step of the prepared query to fetch one row of results.
*
* While true is returned, a row of results is available, and can be accessed
* thru the getColumn() method
*
* @see exec() execute a one-step prepared statement with no expected result
* @see tryExecuteStep() try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code.
* @see CSQLiteDB::exec() is a shortcut to execute one or multiple statements without results
*
* @return - true (SQLITE_ROW) if there is another row ready : you can call getColumn(N) to get it
* then you have to call executeStep() again to fetch more rows until the query is finished
* - false (SQLITE_DONE) if the query has finished executing : there is no (more) row of result
* (case of a query with no result, or after N rows fetched successfully)
*
* @throw SQLite::Exception in case of error
*/
bool executeStep();
/**
* @brief Try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code.
*
*
*
* @see exec() execute a one-step prepared statement with no expected result
* @see executeStep() execute a step of the prepared query to fetch one row of results
* @see CSQLiteDB::exec() is a shortcut to execute one or multiple statements without results
*
* @return the sqlite result code.
*/
int tryExecuteStep() noexcept;
/**
* @brief Execute a one-step query with no expected result.
*
* This method is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" :
* - Data Definition Language (DDL) statements "CREATE", "ALTER" and "DROP"
* - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE"
* - Data Control Language (DCL) statements "GRANT", "REVOKE", "COMMIT" and "ROLLBACK"
*
* It is similar to CSQLiteDB::exec(), but using a precompiled statement, it adds :
* - the ability to bind() arguments to it (best way to insert data),
* - reusing it allows for better performances (efficient for multiple insertion).
*
* @see executeStep() execute a step of the prepared query to fetch one row of results
* @see tryExecuteStep() try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code.
* @see CSQLiteDB::exec() is a shortcut to execute one or multiple statements without results
*
* @return number of row modified by this SQL statement (INSERT, UPDATE or DELETE)
*
* @throw SQLite::Exception in case of error, or if row of results are returned !
*/
int exec();
////////////////////////////////////////////////////////////////////////////
/**
* @brief Return a copy of the column data specified by its 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 index is out of bound
* - before any executeStep() call
* - after the last executeStep() returned false
* - after a reset() call
*
* Throw an exception if the specified index is out of the [0, getColumnCount()) range.
*
* @param[in] aIndex Index of the column, starting at 0
*
* @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.
*
* 注意调用者必须保证有一行数据且index在[0, getColumnCount())范围内.
* 本类只想做简单的封装,不想到处抛出异常
*/
inline CSQLiteColumn getColumn(const int aIndex)
{
// checkRow();
// checkIndex(aIndex);
// Share the Statement Object handle with the new Column created
return CSQLiteColumn(mStmtPtr, 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.
*
* Throw an exception if the specified name is not one of the aliased name of the columns in the result.
* 注意调用者必须保证有一行数据,且名称是有效的.
* 本类只想做简单的封装,不想到处抛出异常
*/
inline CSQLiteColumn getColumn(const char* apName)
{
// checkRow();
const int index = getColumnIndex(apName); //注意名字不对会抛异常
// Share the Statement Object handle with the new Column created
return CSQLiteColumn(mStmtPtr, index);
}
#if __cplusplus >= 201402L || (defined(_MSC_VER) && _MSC_VER >= 1900)
/**
* @brief Return an instance of T constructed from copies of the first N columns
*
* 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 column count is out of bound
* - before any executeStep() call
* - after the last executeStep() returned false
* - after a reset() call
*
* Throw an exception if the specified column count is out of the [0, getColumnCount()) range.
*
* @tparam T Object type to construct
* @tparam N Number of columns
*
* @note Requires std=C++14
*/
template<typename T, int N>
T getColumns();
private:
/**
* @brief Helper function used by getColumns<typename T, int N> to expand an integer_sequence used to generate
* the required Column objects
*/
template<typename T, const int... Is>
T getColumns(const std::integer_sequence<int, Is...>);
public:
#endif
/**
* @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
*
* 注意调用者必须保证有一行数据且index在[0, getColumnCount())范围内,避免抛出异常
*/
bool isColumnNull(const int aIndex) const
{
// checkRow();
// checkIndex(aIndex);
return (SQLITE_NULL == sqlite3_column_type(*mStmtPtr, aIndex));
}
/**
* @brief Test if the column value is NULL
*
* @param[in] apName Aliased name of the column, that is, the named specified in the query (not the original name)
*
* @return true if the column value is NULL
*
* 注意调用者必须保证有一行数据,且列名称是有效的,避免抛出异常
*/
bool isColumnNull(const char* apName) const
{
// checkRow();
const int index = getColumnIndex(apName); //
return (SQLITE_NULL == sqlite3_column_type(*mStmtPtr, index));
}
/**
* @brief Return a pointer to the named assigned to the specified result column (potentially aliased)
*
* @param[in] aIndex Index of the column in the range [0, getColumnCount()).
*
* @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 index of the specified (potentially aliased) column name
*
* @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.
*
* Throw an exception if the specified name is not known.
*/
int getColumnIndex(const char* apName) const;
////////////////////////////////////////////////////////////////////////////
/// Return the UTF-8 SQL Query.
inline const std::string& getQuery() const
{
return mQuery;
}
// Return a UTF-8 string containing the SQL text of prepared statement with bound parameters expanded.
std::string getExpandedSQL();
/// Return the number of columns in the result set returned by the prepared statement
inline int getColumnCount() const
{
return mColumnCount;
}
/// true when a row has been fetched with executeStep()
inline bool hasRow() const
{
return mbHasRow;
}
/// true when the last executeStep() had no more row to fetch
inline bool isDone() const
{
return mbDone;
}
inline bool isValid()
{
return bool(mStmtPtr);
}
/// Return the number of bind parameters in the statement
int getBindParameterCount() const noexcept;
/// Return the numeric result code for the most recent failed API call (if any).
int getErrorCode() const noexcept; // nothrow
/// Return the extended numeric result code for the most recent failed API call (if any).
int getExtendedErrorCode() const noexcept; // nothrow
/// Return UTF-8 encoded English language explanation of the most recent failed API call (if any).
const char* getErrorMsg() const noexcept; // nothrow
int finalize() noexcept {
return sqlite3_finalize(*mStmtPtr);
}
private:
/// @{ CSQLiteStatement must be non-copyable
CSQLiteStatement(const CSQLiteStatement&);
CSQLiteStatement& operator=(const CSQLiteStatement&);
/// @}
/**
* @brief Check if a return code equals SQLITE_OK, else throw a SQLite::Exception with the SQLite error message
*
* @param[in] aRet SQLite return code to test against the SQLITE_OK expected value
*/
inline void check(const int aRet) const
{
if (SQLITE_OK != aRet)
{
throw CSQLiteException(*mStmtPtr, aRet);
}
}
/**
* @brief Check if there is a row of result returned by executeStep(), else throw a SQLite::Exception.
*/
inline void checkRow() const
{
if (false == mbHasRow)
{
throw CSQLiteException("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 CSQLiteException("Column index out of range.");
}
}
private:
/// Map of columns index by name (mutable so getColumnIndex can be const)
typedef std::map<std::string, int> TColumnNames;
private:
std::string mQuery; //!< UTF-8 SQL Query
_StatementPtr mStmtPtr; //!< Shared Pointer to the prepared SQLite Statement Object
int mColumnCount; //!< Number of columns in the result of the prepared statement
mutable TColumnNames mColumnNames; //!< Map of columns index by name (mutable so getColumnIndex can be const)
bool mbHasRow; //!< true when a row has been fetched with executeStep()
bool mbDone; //!< true when the last executeStep() had no more row to fetch
public:
int errCode;
std::string errMsg;
};