Sqlite: Add blob support

Change-Id: Ic2ec5f20c8585241b9e9aaa8465e70b6ab4f004c
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Marco Bubke
2020-05-26 20:31:41 +02:00
parent 22c33fb859
commit 9c44f8d88d
8 changed files with 204 additions and 38 deletions

View File

@@ -197,6 +197,17 @@ void BaseStatement::bind(int index, Utils::SmallStringView text)
checkForBindingError(resultCode); checkForBindingError(resultCode);
} }
void BaseStatement::bind(int index, Utils::span<const byte> bytes)
{
int resultCode = sqlite3_bind_blob64(m_compiledStatement.get(),
index,
bytes.data(),
static_cast<long long>(bytes.size()),
SQLITE_STATIC);
if (resultCode != SQLITE_OK)
checkForBindingError(resultCode);
}
void BaseStatement::bind(int index, const Value &value) void BaseStatement::bind(int index, const Value &value)
{ {
switch (value.type()) { switch (value.type()) {
@@ -433,8 +444,9 @@ Database &BaseStatement::database() const
return m_database; return m_database;
} }
template <typename StringType> namespace {
static StringType textForColumn(sqlite3_stmt *sqlStatment, int column) template<typename StringType>
StringType 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));
std::size_t size = std::size_t(sqlite3_column_bytes(sqlStatment, column)); std::size_t size = std::size_t(sqlite3_column_bytes(sqlStatment, column));
@@ -442,20 +454,40 @@ static StringType textForColumn(sqlite3_stmt *sqlStatment, int column)
return StringType(text, size); return StringType(text, size);
} }
template <typename StringType> Utils::span<const byte> blobForColumn(sqlite3_stmt *sqlStatment, int column)
static StringType convertToTextForColumn(sqlite3_stmt *sqlStatment, int column) {
const byte *blob = reinterpret_cast<const byte *>(sqlite3_column_blob(sqlStatment, column));
std::size_t size = std::size_t(sqlite3_column_bytes(sqlStatment, column));
return {blob, size};
}
Utils::span<const byte> convertToBlobForColumn(sqlite3_stmt *sqlStatment, int column)
{
int dataType = sqlite3_column_type(sqlStatment, column);
if (dataType == SQLITE_BLOB)
return blobForColumn(sqlStatment, column);
return {};
}
template<typename StringType>
StringType convertToTextForColumn(sqlite3_stmt *sqlStatment, int column)
{ {
int dataType = sqlite3_column_type(sqlStatment, column); int dataType = sqlite3_column_type(sqlStatment, column);
switch (dataType) { switch (dataType) {
case SQLITE_INTEGER: case SQLITE_INTEGER:
case SQLITE_FLOAT: case SQLITE_FLOAT:
case SQLITE3_TEXT: return textForColumn<StringType>(sqlStatment, column); case SQLITE3_TEXT:
return textForColumn<StringType>(sqlStatment, column);
case SQLITE_BLOB: case SQLITE_BLOB:
case SQLITE_NULL: break; case SQLITE_NULL:
break;
} }
return StringType{"", 0}; return StringType{"", 0};
} }
} // namespace
int BaseStatement::fetchIntValue(int column) const int BaseStatement::fetchIntValue(int column) const
{ {
@@ -501,6 +533,14 @@ double BaseStatement::fetchDoubleValue(int column) const
return sqlite3_column_double(m_compiledStatement.get(), column); return sqlite3_column_double(m_compiledStatement.get(), column);
} }
Utils::span<const byte> BaseStatement::fetchBlobValue(int column) const
{
checkIfIsReadyToFetchValues();
checkColumnIsValid(column);
return convertToBlobForColumn(m_compiledStatement.get(), column);
}
template<> template<>
double BaseStatement::fetchValue<double>(int column) const double BaseStatement::fetchValue<double>(int column) const
{ {

View File

@@ -33,6 +33,7 @@
#include <utils/smallstringvector.h> #include <utils/smallstringvector.h>
#include <utils/optional.h> #include <utils/optional.h>
#include <utils/span.h>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@@ -69,6 +70,7 @@ public:
double fetchDoubleValue(int column) const; double fetchDoubleValue(int column) const;
Utils::SmallStringView fetchSmallStringViewValue(int column) const; Utils::SmallStringView fetchSmallStringViewValue(int column) const;
ValueView fetchValueView(int column) const; ValueView fetchValueView(int column) const;
Utils::span<const byte> fetchBlobValue(int column) const;
template<typename Type> template<typename Type>
Type fetchValue(int column) const; Type fetchValue(int column) const;
int columnCount() const; int columnCount() const;
@@ -80,6 +82,7 @@ public:
void bind(int index, void *pointer); void bind(int index, void *pointer);
void bind(int index, Utils::SmallStringView fetchValue); void bind(int index, Utils::SmallStringView fetchValue);
void bind(int index, const Value &fetchValue); void bind(int index, const Value &fetchValue);
void bind(int index, Utils::span<const byte> bytes);
void bind(int index, uint value) { bind(index, static_cast<long long>(value)); } void bind(int index, uint value) { bind(index, static_cast<long long>(value)); }
@@ -362,6 +365,7 @@ private:
operator long long() { return statement.fetchLongLongValue(column); } operator long long() { return statement.fetchLongLongValue(column); }
operator double() { return statement.fetchDoubleValue(column); } operator double() { return statement.fetchDoubleValue(column); }
operator Utils::SmallStringView() { return statement.fetchSmallStringViewValue(column); } operator Utils::SmallStringView() { return statement.fetchSmallStringViewValue(column); }
operator Utils::span<const Sqlite::byte>() { return statement.fetchBlobValue(column); }
operator ValueView() { return statement.fetchValueView(column); } operator ValueView() { return statement.fetchValueView(column); }
StatementImplementation &statement; StatementImplementation &statement;

View File

@@ -73,4 +73,6 @@ enum class OpenMode : char
enum class ChangeType : int { Delete = 9, Insert = 18, Update = 23 }; enum class ChangeType : int { Delete = 9, Insert = 18, Update = 23 };
enum class byte : unsigned char {};
} // namespace Sqlite } // namespace Sqlite

View File

@@ -230,30 +230,6 @@ QDataStream &operator>>(QDataStream &in, vector<Type> &vector)
return in; return in;
} }
template <typename T>
ostream &operator<<(ostream &out, const vector<T> &vector)
{
out << "[";
for (auto current = vector.begin(); current != vector.end(); ++current) {
std::ostringstream entryStream;
entryStream << *current;
std::string entryString = entryStream.str();
if (entryString.size() > 4)
out << "\n\t";
out << entryString;
if (std::next(current) != vector.end())
out << ", ";
}
out << "]";
return out;
}
} // namespace std } // namespace std
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE

View File

@@ -58,6 +58,7 @@ using testing::Ne;
using testing::NiceMock; using testing::NiceMock;
using testing::Not; using testing::Not;
using testing::NotNull; using testing::NotNull;
using testing::Optional;
using testing::Pair; using testing::Pair;
using testing::PrintToString; using testing::PrintToString;
using testing::Property; using testing::Property;

View File

@@ -348,4 +348,24 @@ std::ostream &operator<<(std::ostream &out, const Diagnostic &diag);
} // namespace Internal } // namespace Internal
} // namespace CppTools } // namespace CppTools
namespace std {
template<typename T>
ostream &operator<<(ostream &out, const vector<T> &vector)
{
out << "[";
for (auto current = vector.begin(); current != vector.end(); ++current) {
out << *current;
if (std::next(current) != vector.end())
out << ", ";
}
out << "]";
return out;
}
} // namespace std
void setFilePathCache(ClangBackEnd::FilePathCaching *filePathCache); void setFilePathCache(ClangBackEnd::FilePathCaching *filePathCache);

View File

@@ -38,6 +38,14 @@
#include <vector> #include <vector>
namespace Sqlite {
bool operator==(Utils::span<const byte> first, Utils::span<const byte> second)
{
return first.size() == second.size()
&& std::memcmp(first.data(), second.data(), first.size()) == 0;
}
} // namespace Sqlite
namespace { namespace {
using Sqlite::Database; using Sqlite::Database;
@@ -288,6 +296,30 @@ TEST_F(SqliteStatement, BindPointer)
ASSERT_THAT(statement.fetchIntValue(0), 1); ASSERT_THAT(statement.fetchIntValue(0), 1);
} }
TEST_F(SqliteStatement, BindBlob)
{
SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database);
const unsigned char chars[] = "aaafdfdlll";
auto bytePointer = reinterpret_cast<const Sqlite::byte *>(chars);
Utils::span<const Sqlite::byte> bytes{bytePointer, sizeof(chars) - 1};
statement.bind(1, bytes);
statement.next();
ASSERT_THAT(statement.fetchBlobValue(0), Eq(bytes));
}
TEST_F(SqliteStatement, BindEmptyBlob)
{
SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database);
Utils::span<const Sqlite::byte> bytes;
statement.bind(1, bytes);
statement.next();
ASSERT_THAT(statement.fetchBlobValue(0), IsEmpty());
}
TEST_F(SqliteStatement, BindIntegerByParameter) TEST_F(SqliteStatement, BindIntegerByParameter)
{ {
SqliteTestStatement statement("SELECT name, number FROM test WHERE number=@number", database); SqliteTestStatement statement("SELECT name, number FROM test WHERE number=@number", database);
@@ -332,41 +364,49 @@ TEST_F(SqliteStatement, BindIndexIsZeroIsThrowingBindingIndexIsOutOfBoundNull)
ASSERT_THROW(statement.bind(0, Sqlite::NullValue{}), Sqlite::BindingIndexIsOutOfRange); ASSERT_THROW(statement.bind(0, Sqlite::NullValue{}), Sqlite::BindingIndexIsOutOfRange);
} }
TEST_F(SqliteStatement, BindIndexIsTpLargeIsThrowingBindingIndexIsOutOfBoundLongLong) TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundLongLong)
{ {
SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database);
ASSERT_THROW(statement.bind(2, 40LL), Sqlite::BindingIndexIsOutOfRange); ASSERT_THROW(statement.bind(2, 40LL), Sqlite::BindingIndexIsOutOfRange);
} }
TEST_F(SqliteStatement, BindIndexIsTpLargeIsThrowingBindingIndexIsOutOfBoundStringView) TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundStringView)
{ {
SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database);
ASSERT_THROW(statement.bind(2, "foo"), Sqlite::BindingIndexIsOutOfRange); ASSERT_THROW(statement.bind(2, "foo"), Sqlite::BindingIndexIsOutOfRange);
} }
TEST_F(SqliteStatement, BindIndexIsTpLargeIsThrowingBindingIndexIsOutOfBoundStringFloat) TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundStringFloat)
{ {
SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database);
ASSERT_THROW(statement.bind(2, 2.), Sqlite::BindingIndexIsOutOfRange); ASSERT_THROW(statement.bind(2, 2.), Sqlite::BindingIndexIsOutOfRange);
} }
TEST_F(SqliteStatement, BindIndexIsTpLargeIsThrowingBindingIndexIsOutOfBoundPointer) TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundPointer)
{ {
SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database);
ASSERT_THROW(statement.bind(2, nullptr), Sqlite::BindingIndexIsOutOfRange); ASSERT_THROW(statement.bind(2, nullptr), Sqlite::BindingIndexIsOutOfRange);
} }
TEST_F(SqliteStatement, BindIndexIsTpLargeIsThrowingBindingIndexIsOutOfBoundValue) TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundValue)
{ {
SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database);
ASSERT_THROW(statement.bind(2, Sqlite::Value{1}), Sqlite::BindingIndexIsOutOfRange); ASSERT_THROW(statement.bind(2, Sqlite::Value{1}), Sqlite::BindingIndexIsOutOfRange);
} }
TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundBlob)
{
SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database);
Utils::span<const Sqlite::byte> bytes;
ASSERT_THROW(statement.bind(2, bytes), Sqlite::BindingIndexIsOutOfRange);
}
TEST_F(SqliteStatement, WrongBindingNameThrowingBindingIndexIsOutOfBound) TEST_F(SqliteStatement, WrongBindingNameThrowingBindingIndexIsOutOfBound)
{ {
SqliteTestStatement statement("SELECT name, number FROM test WHERE number=@name", database); SqliteTestStatement statement("SELECT name, number FROM test WHERE number=@name", database);
@@ -431,6 +471,40 @@ TEST_F(SqliteStatement, WriteSqliteValues)
ASSERT_THAT(statement, HasValues("see", "7.23", 1)); ASSERT_THAT(statement, HasValues("see", "7.23", 1));
} }
TEST_F(SqliteStatement, WriteEmptyBlobs)
{
SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database);
Utils::span<const Sqlite::byte> bytes;
statement.write(bytes);
ASSERT_THAT(statement.fetchBlobValue(0), IsEmpty());
}
class Blob
{
public:
Blob(Utils::span<const Sqlite::byte> bytes)
: bytes(bytes.begin(), bytes.end())
{}
std::vector<Sqlite::byte> bytes;
};
TEST_F(SqliteStatement, WriteBlobs)
{
SqliteTestStatement statement("INSERT INTO test VALUES ('blob', 40, ?)", database);
SqliteTestStatement readStatement("SELECT value FROM test WHERE name = 'blob'", database);
const unsigned char chars[] = "aaafdfdlll";
auto bytePointer = reinterpret_cast<const Sqlite::byte *>(chars);
Utils::span<const Sqlite::byte> bytes{bytePointer, sizeof(chars) - 1};
statement.write(bytes);
ASSERT_THAT(readStatement.template value<Blob>(), Optional(Field(&Blob::bytes, Eq(bytes))));
}
TEST_F(SqliteStatement, BindNamedValues) TEST_F(SqliteStatement, BindNamedValues)
{ {
SqliteTestStatement statement("UPDATE test SET name=@name, number=@number WHERE rowid=@id", database); SqliteTestStatement statement("UPDATE test SET name=@name, number=@number WHERE rowid=@id", database);
@@ -633,6 +707,46 @@ TEST_F(SqliteStatement, GetStructOutputValuesAndContainerQueryTupleValues)
Output{"bar", "blah", 1})); Output{"bar", "blah", 1}));
} }
TEST_F(SqliteStatement, GetBlobValues)
{
database.execute("INSERT INTO test VALUES ('blob', 40, x'AABBCCDD')");
ReadStatement statement("SELECT value FROM test WHERE name='blob'", database);
const int value = 0xDDCCBBAA;
auto bytePointer = reinterpret_cast<const Sqlite::byte *>(&value);
Utils::span<const Sqlite::byte> bytes{bytePointer, 4};
auto values = statement.values<Blob>(1);
ASSERT_THAT(values, ElementsAre(Field(&Blob::bytes, Eq(bytes))));
}
TEST_F(SqliteStatement, GetEmptyBlobValueForInteger)
{
ReadStatement statement("SELECT value FROM test WHERE name='poo'", database);
auto value = statement.value<Blob>();
ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty())));
}
TEST_F(SqliteStatement, GetEmptyBlobValueForFloat)
{
ReadStatement statement("SELECT number FROM test WHERE name='foo'", database);
auto value = statement.value<Blob>();
ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty())));
}
TEST_F(SqliteStatement, GetEmptyBlobValueForText)
{
ReadStatement statement("SELECT number FROM test WHERE name='bar'", database);
auto value = statement.value<Blob>();
ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty())));
}
TEST_F(SqliteStatement, GetOptionalSingleValueAndMultipleQueryValue) TEST_F(SqliteStatement, GetOptionalSingleValueAndMultipleQueryValue)
{ {
ReadStatement statement("SELECT name FROM test WHERE name=? AND number=? AND value=?", database); ReadStatement statement("SELECT name FROM test WHERE name=? AND number=? AND value=?", database);

View File

@@ -85,6 +85,15 @@ TEST(SqliteValue, ConstructStringFromQString)
ASSERT_THAT(value.toStringView(), Eq("foo")); ASSERT_THAT(value.toStringView(), Eq("foo"));
} }
TEST(SqliteValue, ConstructStringFromBlob)
{
// Utils::span<const Sqlite::byte> bytes{reinterpret_cast<const Sqlite::byte *>("abcd"), 4};
// Sqlite::Value value{bytes};
//ASSERT_THAT(value.toBlob(), Eq(bytes));
}
TEST(SqliteValue, ConstructNullFromNullQVariant) TEST(SqliteValue, ConstructNullFromNullQVariant)
{ {
QVariant variant{}; QVariant variant{};