Commit a41183f6 authored by Marco Bubke's avatar Marco Bubke

Sqlite: Add variadic bind and write functions

You can now write

SqliteWriteStatement statement("UPDATE test SET name=?, number=?
                                WHERE rowid=?", database);

statement.write("see", 7.23, 1);

and

SqliteWriteStatement statement("UPDATE test SET name=@name, number=@number
                                WHERE rowid=@id", database);

statement.writeNamed("@name", "see", "@number", 7.23, "@id", 1);

This is more type safe than using variants and performant too.

Change-Id: Ie1ed2a6d326b956be5c4ec056214f3f5b1531f45
Reviewed-by: Tim Jenssen's avatarTim Jenssen <tim.jenssen@qt.io>
parent 71e1f662
...@@ -30,7 +30,8 @@ ...@@ -30,7 +30,8 @@
namespace Sqlite { namespace Sqlite {
SqliteDatabase::SqliteDatabase() SqliteDatabase::SqliteDatabase()
: m_journalMode(JournalMode::Wal) : m_databaseBackend(*this),
m_journalMode(JournalMode::Wal)
{ {
} }
......
...@@ -41,6 +41,7 @@ class SQLITE_EXPORT SqliteDatabase ...@@ -41,6 +41,7 @@ class SQLITE_EXPORT SqliteDatabase
{ {
friend class SqliteAbstractTransaction; friend class SqliteAbstractTransaction;
friend class SqliteStatement; friend class SqliteStatement;
friend class SqliteBackend;
public: public:
SqliteDatabase(); SqliteDatabase();
...@@ -66,9 +67,10 @@ public: ...@@ -66,9 +67,10 @@ public:
void execute(Utils::SmallStringView sqlStatement); void execute(Utils::SmallStringView sqlStatement);
SqliteDatabaseBackend &backend();
private: private:
void initializeTables(); void initializeTables();
SqliteDatabaseBackend &backend();
private: private:
......
...@@ -48,8 +48,9 @@ ...@@ -48,8 +48,9 @@
namespace Sqlite { namespace Sqlite {
SqliteDatabaseBackend::SqliteDatabaseBackend() SqliteDatabaseBackend::SqliteDatabaseBackend(SqliteDatabase &database)
: m_databaseHandle(nullptr), : m_database(database),
m_databaseHandle(nullptr),
m_cachedTextEncoding(Utf8) m_cachedTextEncoding(Utf8)
{ {
} }
...@@ -124,8 +125,7 @@ sqlite3 *SqliteDatabaseBackend::sqliteDatabaseHandle() ...@@ -124,8 +125,7 @@ sqlite3 *SqliteDatabaseBackend::sqliteDatabaseHandle()
void SqliteDatabaseBackend::setPragmaValue(Utils::SmallStringView pragmaKey, Utils::SmallStringView newPragmaValue) void SqliteDatabaseBackend::setPragmaValue(Utils::SmallStringView pragmaKey, Utils::SmallStringView newPragmaValue)
{ {
SqliteReadWriteStatement statement(Utils::SmallString{"PRAGMA ", pragmaKey, "='", newPragmaValue, "'"}, *this); execute(Utils::SmallString{"PRAGMA ", pragmaKey, "='", newPragmaValue, "'"});
statement.step();
Utils::SmallString pragmeValueInDatabase = toValue<Utils::SmallString>("PRAGMA " + pragmaKey); Utils::SmallString pragmeValueInDatabase = toValue<Utils::SmallString>("PRAGMA " + pragmaKey);
checkPragmaValue(pragmeValueInDatabase, newPragmaValue); checkPragmaValue(pragmeValueInDatabase, newPragmaValue);
...@@ -160,7 +160,7 @@ TextEncoding SqliteDatabaseBackend::textEncoding() ...@@ -160,7 +160,7 @@ TextEncoding SqliteDatabaseBackend::textEncoding()
Utils::SmallStringVector SqliteDatabaseBackend::columnNames(Utils::SmallStringView tableName) Utils::SmallStringVector SqliteDatabaseBackend::columnNames(Utils::SmallStringView tableName)
{ {
SqliteReadWriteStatement statement("SELECT * FROM " + tableName, *this); SqliteReadWriteStatement statement("SELECT * FROM " + tableName, m_database);
return statement.columnNames(); return statement.columnNames();
} }
...@@ -176,7 +176,7 @@ int SqliteDatabaseBackend::totalChangesCount() ...@@ -176,7 +176,7 @@ int SqliteDatabaseBackend::totalChangesCount()
void SqliteDatabaseBackend::execute(Utils::SmallStringView sqlStatement) void SqliteDatabaseBackend::execute(Utils::SmallStringView sqlStatement)
{ {
SqliteReadWriteStatement statement(sqlStatement, *this); SqliteReadWriteStatement statement(sqlStatement, m_database);
statement.step(); statement.step();
} }
...@@ -391,7 +391,7 @@ void SqliteDatabaseBackend::throwException(const char *whatHasHappens) const ...@@ -391,7 +391,7 @@ void SqliteDatabaseBackend::throwException(const char *whatHasHappens) const
template <typename Type> template <typename Type>
Type SqliteDatabaseBackend::toValue(Utils::SmallStringView sqlStatement) Type SqliteDatabaseBackend::toValue(Utils::SmallStringView sqlStatement)
{ {
SqliteReadWriteStatement statement(sqlStatement, *this); SqliteReadWriteStatement statement(sqlStatement, m_database);
statement.next(); statement.next();
......
...@@ -33,13 +33,17 @@ struct sqlite3; ...@@ -33,13 +33,17 @@ struct sqlite3;
namespace Sqlite { namespace Sqlite {
class SqliteDatabase;
class SQLITE_EXPORT SqliteDatabaseBackend class SQLITE_EXPORT SqliteDatabaseBackend
{ {
public: public:
SqliteDatabaseBackend(SqliteDatabase &database);
SqliteDatabaseBackend();
~SqliteDatabaseBackend(); ~SqliteDatabaseBackend();
SqliteDatabaseBackend(const SqliteDatabase &database) = delete;
SqliteDatabase &operator=(const SqliteDatabase &database) = delete;
void setMmapSize(qint64 defaultSize, qint64 maximumSize); void setMmapSize(qint64 defaultSize, qint64 maximumSize);
void activateMultiThreading(); void activateMultiThreading();
void activateLogging(); void activateLogging();
...@@ -59,8 +63,6 @@ public: ...@@ -59,8 +63,6 @@ public:
void setTextEncoding(TextEncoding textEncoding); void setTextEncoding(TextEncoding textEncoding);
TextEncoding textEncoding(); TextEncoding textEncoding();
Utils::SmallStringVector columnNames(Utils::SmallStringView tableName); Utils::SmallStringVector columnNames(Utils::SmallStringView tableName);
int changesCount(); int changesCount();
...@@ -105,6 +107,7 @@ protected: ...@@ -105,6 +107,7 @@ protected:
Q_NORETURN void throwException(const char *whatHasHappens) const; Q_NORETURN void throwException(const char *whatHasHappens) const;
private: private:
SqliteDatabase &m_database;
sqlite3 *m_databaseHandle; sqlite3 *m_databaseHandle;
TextEncoding m_cachedTextEncoding; TextEncoding m_cachedTextEncoding;
......
...@@ -42,10 +42,13 @@ public: ...@@ -42,10 +42,13 @@ public:
using SqliteStatement::columnCount; using SqliteStatement::columnCount;
using SqliteStatement::columnNames; using SqliteStatement::columnNames;
using SqliteStatement::bind; using SqliteStatement::bind;
using SqliteStatement::bindValues;
using SqliteStatement::bindNameValues;
using SqliteStatement::bindingIndexForName; using SqliteStatement::bindingIndexForName;
using SqliteStatement::setBindingColumnNames; using SqliteStatement::setBindingColumnNames;
using SqliteStatement::bindingColumnNames; using SqliteStatement::bindingColumnNames;
using SqliteStatement::toValue; using SqliteStatement::toValue;
using SqliteStatement::database;
protected: protected:
void checkIsReadOnlyStatement(); void checkIsReadOnlyStatement();
......
...@@ -33,10 +33,4 @@ SqliteReadWriteStatement::SqliteReadWriteStatement(Utils::SmallStringView sqlSta ...@@ -33,10 +33,4 @@ SqliteReadWriteStatement::SqliteReadWriteStatement(Utils::SmallStringView sqlSta
{ {
} }
SqliteReadWriteStatement::SqliteReadWriteStatement(Utils::SmallStringView sqlStatement,
SqliteDatabaseBackend &backend)
: SqliteStatement(sqlStatement, backend)
{
}
} // namespace Sqlite } // namespace Sqlite
...@@ -37,9 +37,11 @@ public: ...@@ -37,9 +37,11 @@ public:
SqliteReadWriteStatement(Utils::SmallStringView sqlStatement, SqliteDatabase &database); SqliteReadWriteStatement(Utils::SmallStringView sqlStatement, SqliteDatabase &database);
using SqliteStatement::next; using SqliteStatement::next;
using SqliteStatement::step; using SqliteStatement::execute;
using SqliteStatement::reset; using SqliteStatement::reset;
using SqliteStatement::bind; using SqliteStatement::bind;
using SqliteStatement::bindValues;
using SqliteStatement::bindNameValues;
using SqliteStatement::bindingIndexForName; using SqliteStatement::bindingIndexForName;
using SqliteStatement::setBindingColumnNames; using SqliteStatement::setBindingColumnNames;
using SqliteStatement::bindingColumnNames; using SqliteStatement::bindingColumnNames;
...@@ -49,10 +51,9 @@ public: ...@@ -49,10 +51,9 @@ public:
using SqliteStatement::columnCount; using SqliteStatement::columnCount;
using SqliteStatement::columnNames; using SqliteStatement::columnNames;
using SqliteStatement::toValue; using SqliteStatement::toValue;
using SqliteStatement::database;
private: using SqliteStatement::write;
explicit SqliteReadWriteStatement(Utils::SmallStringView sqlStatement, using SqliteStatement::writeNamed;
SqliteDatabaseBackend &backend);
}; };
} // namespace Sqlite } // namespace Sqlite
...@@ -43,14 +43,8 @@ ...@@ -43,14 +43,8 @@
namespace Sqlite { namespace Sqlite {
SqliteStatement::SqliteStatement(Utils::SmallStringView sqlStatement, SqliteDatabase &database) SqliteStatement::SqliteStatement(Utils::SmallStringView sqlStatement, SqliteDatabase &database)
: SqliteStatement(sqlStatement, database.backend())
{
}
SqliteStatement::SqliteStatement(Utils::SmallStringView sqlStatement, SqliteDatabaseBackend &databaseBackend)
: m_compiledStatement(nullptr, deleteCompiledStatement), : m_compiledStatement(nullptr, deleteCompiledStatement),
m_databaseBackend(databaseBackend), m_database(database),
m_bindingParameterCount(0), m_bindingParameterCount(0),
m_columnCount(0), m_columnCount(0),
m_isReadyToFetchValues(false) m_isReadyToFetchValues(false)
...@@ -146,6 +140,11 @@ void SqliteStatement::step() const ...@@ -146,6 +140,11 @@ void SqliteStatement::step() const
next(); next();
} }
void SqliteStatement::execute() const
{
next();
}
int SqliteStatement::columnCount() const int SqliteStatement::columnCount() const
{ {
return m_columnCount; return m_columnCount;
...@@ -203,7 +202,7 @@ template SQLITE_EXPORT void SqliteStatement::bind(Utils::SmallStringView name, q ...@@ -203,7 +202,7 @@ template SQLITE_EXPORT void SqliteStatement::bind(Utils::SmallStringView name, q
template SQLITE_EXPORT void SqliteStatement::bind(Utils::SmallStringView name, double value); template SQLITE_EXPORT void SqliteStatement::bind(Utils::SmallStringView name, double value);
template SQLITE_EXPORT void SqliteStatement::bind(Utils::SmallStringView name, Utils::SmallStringView text); template SQLITE_EXPORT void SqliteStatement::bind(Utils::SmallStringView name, Utils::SmallStringView text);
int SqliteStatement::bindingIndexForName(Utils::SmallStringView name) int SqliteStatement::bindingIndexForName(Utils::SmallStringView name) const
{ {
return sqlite3_bind_parameter_index(m_compiledStatement.get(), name.data()); return sqlite3_bind_parameter_index(m_compiledStatement.get(), name.data());
} }
...@@ -241,12 +240,12 @@ void SqliteStatement::prepare(Utils::SmallStringView sqlStatement) ...@@ -241,12 +240,12 @@ void SqliteStatement::prepare(Utils::SmallStringView sqlStatement)
sqlite3 *SqliteStatement::sqliteDatabaseHandle() const sqlite3 *SqliteStatement::sqliteDatabaseHandle() const
{ {
return m_databaseBackend.sqliteDatabaseHandle(); return m_database.backend().sqliteDatabaseHandle();
} }
TextEncoding SqliteStatement::databaseTextEncoding() TextEncoding SqliteStatement::databaseTextEncoding()
{ {
return m_databaseBackend.textEncoding(); return m_database.backend().textEncoding();
} }
bool SqliteStatement::checkForStepError(int resultCode) const bool SqliteStatement::checkForStepError(int resultCode) const
...@@ -359,6 +358,11 @@ QString SqliteStatement::columnName(int column) const ...@@ -359,6 +358,11 @@ QString SqliteStatement::columnName(int column) const
return QString::fromUtf8(sqlite3_column_name(m_compiledStatement.get(), column)); return QString::fromUtf8(sqlite3_column_name(m_compiledStatement.get(), column));
} }
SqliteDatabase &SqliteStatement::database() const
{
return m_database;
}
static Utils::SmallString textForColumn(sqlite3_stmt *sqlStatment, int column) static Utils::SmallString textForColumn(sqlite3_stmt *sqlStatment, int column)
{ {
const char *text = reinterpret_cast<const char*>(sqlite3_column_text(sqlStatment, column)); const char *text = reinterpret_cast<const char*>(sqlite3_column_text(sqlStatment, column));
......
...@@ -51,6 +51,7 @@ protected: ...@@ -51,6 +51,7 @@ protected:
bool next() const; bool next() const;
void step() const; void step() const;
void execute() const;
void reset() const; void reset() const;
template<typename Type> template<typename Type>
...@@ -64,10 +65,36 @@ protected: ...@@ -64,10 +65,36 @@ protected:
void bind(int index, double value); void bind(int index, double value);
void bind(int index, Utils::SmallStringView value); void bind(int index, Utils::SmallStringView value);
template<typename ... Values>
void bindValues(Values ... values)
{
bindValuesByIndex(1, values...);
}
template<typename ... Values>
void write(Values ... values)
{
bindValuesByIndex(1, values...);
execute();
}
template<typename ... Values>
void bindNameValues(Values ... values)
{
bindValuesByName(values...);
}
template<typename ... Values>
void writeNamed(Values ... values)
{
bindValuesByName(values...);
execute();
}
template <typename Type> template <typename Type>
void bind(Utils::SmallStringView name, Type value); void bind(Utils::SmallStringView name, Type value);
int bindingIndexForName(Utils::SmallStringView name); int bindingIndexForName(Utils::SmallStringView name) const;
void setBindingColumnNames(const Utils::SmallStringVector &bindingColumnNames); void setBindingColumnNames(const Utils::SmallStringVector &bindingColumnNames);
const Utils::SmallStringVector &bindingColumnNames() const; const Utils::SmallStringVector &bindingColumnNames() const;
...@@ -107,14 +134,43 @@ protected: ...@@ -107,14 +134,43 @@ protected:
QString columnName(int column) const; QString columnName(int column) const;
SqliteDatabase &database() const;
protected: protected:
explicit SqliteStatement(Utils::SmallStringView sqlStatement, explicit SqliteStatement(Utils::SmallStringView sqlStatement,
SqliteDatabaseBackend &databaseBackend); SqliteDatabaseBackend &databaseBackend);
private:
template<typename Type>
void bindValuesByIndex(int index, Type value)
{
bind(index, value);
}
template<typename Type, typename ... Value>
void bindValuesByIndex(int index, Type value, Value ... values)
{
bind(index, value);
bindValuesByIndex(index + 1, values...);
}
template<typename Type>
void bindValuesByName(Utils::SmallStringView name, Type value)
{
bind(bindingIndexForName(name), value);
}
template<typename Type, typename ... Values>
void bindValuesByName(Utils::SmallStringView name, Type value, Values ... values)
{
bind(bindingIndexForName(name), value);
bindValuesByName(values...);
}
private: private:
std::unique_ptr<sqlite3_stmt, void (*)(sqlite3_stmt*)> m_compiledStatement; std::unique_ptr<sqlite3_stmt, void (*)(sqlite3_stmt*)> m_compiledStatement;
Utils::SmallStringVector m_bindingColumnNames; Utils::SmallStringVector m_bindingColumnNames;
SqliteDatabaseBackend &m_databaseBackend; SqliteDatabase &m_database;
int m_bindingParameterCount; int m_bindingParameterCount;
int m_columnCount; int m_columnCount;
mutable bool m_isReadyToFetchValues; mutable bool m_isReadyToFetchValues;
......
...@@ -34,12 +34,17 @@ class SQLITE_EXPORT SqliteWriteStatement : private SqliteStatement ...@@ -34,12 +34,17 @@ class SQLITE_EXPORT SqliteWriteStatement : private SqliteStatement
public: public:
explicit SqliteWriteStatement(Utils::SmallStringView sqlStatement, SqliteDatabase &database); explicit SqliteWriteStatement(Utils::SmallStringView sqlStatement, SqliteDatabase &database);
using SqliteStatement::step; using SqliteStatement::execute;
using SqliteStatement::reset; using SqliteStatement::reset;
using SqliteStatement::bind; using SqliteStatement::bind;
using SqliteStatement::bindValues;
using SqliteStatement::bindNameValues;
using SqliteStatement::bindingIndexForName; using SqliteStatement::bindingIndexForName;
using SqliteStatement::setBindingColumnNames; using SqliteStatement::setBindingColumnNames;
using SqliteStatement::bindingColumnNames; using SqliteStatement::bindingColumnNames;
using SqliteStatement::database;
using SqliteStatement::write;
using SqliteStatement::writeNamed;
protected: protected:
void checkIsWritableStatement(); void checkIsWritableStatement();
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "googletest.h" #include "googletest.h"
#include <sqlitedatabase.h>
#include <sqlitedatabasebackend.h> #include <sqlitedatabasebackend.h>
#include <sqliteexception.h> #include <sqliteexception.h>
#include <sqlitewritestatement.h> #include <sqlitewritestatement.h>
...@@ -43,7 +44,8 @@ protected: ...@@ -43,7 +44,8 @@ protected:
void TearDown() override; void TearDown() override;
Utils::PathString databaseFilePath = QDir::tempPath() + "/SqliteDatabaseBackendTest.db"; Utils::PathString databaseFilePath = QDir::tempPath() + "/SqliteDatabaseBackendTest.db";
Sqlite::SqliteDatabaseBackend databaseBackend; Sqlite::SqliteDatabase database;
Sqlite::SqliteDatabaseBackend &databaseBackend = database.backend();
}; };
using SqliteDatabaseBackendSlowTest = SqliteDatabaseBackend; using SqliteDatabaseBackendSlowTest = SqliteDatabaseBackend;
......
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
#include <vector> #include <vector>
namespace { namespace {
using testing::ElementsAre; using testing::ElementsAre;
using testing::PrintToString;
using Sqlite::SqliteException; using Sqlite::SqliteException;
using Sqlite::SqliteDatabase; using Sqlite::SqliteDatabase;
...@@ -44,12 +44,30 @@ using Sqlite::SqliteReadStatement; ...@@ -44,12 +44,30 @@ using Sqlite::SqliteReadStatement;
using Sqlite::SqliteReadWriteStatement; using Sqlite::SqliteReadWriteStatement;
using Sqlite::SqliteWriteStatement; using Sqlite::SqliteWriteStatement;
MATCHER_P3(HasValues, value1, value2, rowid,
std::string(negation ? "isn't" : "is")
+ PrintToString(value1)
+ ", " + PrintToString(value2)
+ " and " + PrintToString(rowid)
)
{
SqliteDatabase &database = arg.database();
SqliteReadStatement statement("SELECT name, number FROM test WHERE rowid=?", database);
statement.bind(1, rowid);
statement.next();
return statement.text(0) == value1 && statement.text(1) == value2;
}
class SqliteStatement : public ::testing::Test class SqliteStatement : public ::testing::Test
{ {
protected: protected:
void SetUp() override; void SetUp() override;
void TearDown() override; void TearDown() override;
protected:
SqliteDatabase database; SqliteDatabase database;
}; };
...@@ -250,6 +268,44 @@ TEST_F(SqliteStatement, RequestBindingNamesFromStatement) ...@@ -250,6 +268,44 @@ TEST_F(SqliteStatement, RequestBindingNamesFromStatement)
ASSERT_THAT(statement.bindingColumnNames(), ElementsAre("name", "number", "id")); ASSERT_THAT(statement.bindingColumnNames(), ElementsAre("name", "number", "id"));
} }
TEST_F(SqliteStatement, BindValues)
{
SqliteWriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database);
statement.bindValues("see", 7.23, 1);
statement.execute();
ASSERT_THAT(statement, HasValues("see", "7.23", 1));
}
TEST_F(SqliteStatement, WriteValues)
{
SqliteWriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database);
statement.write("see", 7.23, 1);
ASSERT_THAT(statement, HasValues("see", "7.23", 1));
}
TEST_F(SqliteStatement, BindNamedValues)
{
SqliteWriteStatement statement("UPDATE test SET name=@name, number=@number WHERE rowid=@id", database);
statement.bindNameValues("@name", "see", "@number", 7.23, "@id", 1);
statement.execute();
ASSERT_THAT(statement, HasValues("see", "7.23", 1));
}
TEST_F(SqliteStatement, WriteNamedValues)
{
SqliteWriteStatement statement("UPDATE test SET name=@name, number=@number WHERE rowid=@id", database);
statement.writeNamed("@name", "see", "@number", 7.23, "@id", 1);
ASSERT_THAT(statement, HasValues("see", "7.23", 1));
}
TEST_F(SqliteStatement, ClosedDatabase) TEST_F(SqliteStatement, ClosedDatabase)
{ {
database.close(); database.close();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment