diff --git a/src/libs/sqlite/sqlitebasestatement.cpp b/src/libs/sqlite/sqlitebasestatement.cpp index 5485b21b2fd..2cf355db88d 100644 --- a/src/libs/sqlite/sqlitebasestatement.cpp +++ b/src/libs/sqlite/sqlitebasestatement.cpp @@ -197,6 +197,17 @@ void BaseStatement::bind(int index, Utils::SmallStringView text) checkForBindingError(resultCode); } +void BaseStatement::bind(int index, Utils::span bytes) +{ + int resultCode = sqlite3_bind_blob64(m_compiledStatement.get(), + index, + bytes.data(), + static_cast(bytes.size()), + SQLITE_STATIC); + if (resultCode != SQLITE_OK) + checkForBindingError(resultCode); +} + void BaseStatement::bind(int index, const Value &value) { switch (value.type()) { @@ -433,8 +444,9 @@ Database &BaseStatement::database() const return m_database; } -template -static StringType textForColumn(sqlite3_stmt *sqlStatment, int column) +namespace { +template +StringType textForColumn(sqlite3_stmt *sqlStatment, int column) { const char *text = reinterpret_cast(sqlite3_column_text(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); } -template -static StringType convertToTextForColumn(sqlite3_stmt *sqlStatment, int column) +Utils::span blobForColumn(sqlite3_stmt *sqlStatment, int column) +{ + const byte *blob = reinterpret_cast(sqlite3_column_blob(sqlStatment, column)); + std::size_t size = std::size_t(sqlite3_column_bytes(sqlStatment, column)); + + return {blob, size}; +} + +Utils::span convertToBlobForColumn(sqlite3_stmt *sqlStatment, int column) +{ + int dataType = sqlite3_column_type(sqlStatment, column); + if (dataType == SQLITE_BLOB) + return blobForColumn(sqlStatment, column); + + return {}; +} + +template +StringType convertToTextForColumn(sqlite3_stmt *sqlStatment, int column) { int dataType = sqlite3_column_type(sqlStatment, column); switch (dataType) { - case SQLITE_INTEGER: - case SQLITE_FLOAT: - case SQLITE3_TEXT: return textForColumn(sqlStatment, column); - case SQLITE_BLOB: - case SQLITE_NULL: break; + case SQLITE_INTEGER: + case SQLITE_FLOAT: + case SQLITE3_TEXT: + return textForColumn(sqlStatment, column); + case SQLITE_BLOB: + case SQLITE_NULL: + break; } return StringType{"", 0}; } +} // namespace int BaseStatement::fetchIntValue(int column) const { @@ -501,6 +533,14 @@ double BaseStatement::fetchDoubleValue(int column) const return sqlite3_column_double(m_compiledStatement.get(), column); } +Utils::span BaseStatement::fetchBlobValue(int column) const +{ + checkIfIsReadyToFetchValues(); + checkColumnIsValid(column); + + return convertToBlobForColumn(m_compiledStatement.get(), column); +} + template<> double BaseStatement::fetchValue(int column) const { diff --git a/src/libs/sqlite/sqlitebasestatement.h b/src/libs/sqlite/sqlitebasestatement.h index 4c4baa11b77..027c94e54f6 100644 --- a/src/libs/sqlite/sqlitebasestatement.h +++ b/src/libs/sqlite/sqlitebasestatement.h @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -69,6 +70,7 @@ public: double fetchDoubleValue(int column) const; Utils::SmallStringView fetchSmallStringViewValue(int column) const; ValueView fetchValueView(int column) const; + Utils::span fetchBlobValue(int column) const; template Type fetchValue(int column) const; int columnCount() const; @@ -80,6 +82,7 @@ public: void bind(int index, void *pointer); void bind(int index, Utils::SmallStringView fetchValue); void bind(int index, const Value &fetchValue); + void bind(int index, Utils::span bytes); void bind(int index, uint value) { bind(index, static_cast(value)); } @@ -362,6 +365,7 @@ private: operator long long() { return statement.fetchLongLongValue(column); } operator double() { return statement.fetchDoubleValue(column); } operator Utils::SmallStringView() { return statement.fetchSmallStringViewValue(column); } + operator Utils::span() { return statement.fetchBlobValue(column); } operator ValueView() { return statement.fetchValueView(column); } StatementImplementation &statement; diff --git a/src/libs/sqlite/sqliteglobal.h b/src/libs/sqlite/sqliteglobal.h index 7a5ee14aa05..910cf78231c 100644 --- a/src/libs/sqlite/sqliteglobal.h +++ b/src/libs/sqlite/sqliteglobal.h @@ -73,4 +73,6 @@ enum class OpenMode : char enum class ChangeType : int { Delete = 9, Insert = 18, Update = 23 }; +enum class byte : unsigned char {}; + } // namespace Sqlite diff --git a/src/libs/utils/smallstringio.h b/src/libs/utils/smallstringio.h index 5a0dd71c8a1..233b9b5d97f 100644 --- a/src/libs/utils/smallstringio.h +++ b/src/libs/utils/smallstringio.h @@ -230,30 +230,6 @@ QDataStream &operator>>(QDataStream &in, vector &vector) return in; } -template -ostream &operator<<(ostream &out, const vector &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 QT_BEGIN_NAMESPACE diff --git a/tests/unit/unittest/google-using-declarations.h b/tests/unit/unittest/google-using-declarations.h index a1975ae7a8f..1a92ff33884 100644 --- a/tests/unit/unittest/google-using-declarations.h +++ b/tests/unit/unittest/google-using-declarations.h @@ -58,6 +58,7 @@ using testing::Ne; using testing::NiceMock; using testing::Not; using testing::NotNull; +using testing::Optional; using testing::Pair; using testing::PrintToString; using testing::Property; diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h index b91c8d46beb..ea33bc9d497 100644 --- a/tests/unit/unittest/gtest-creator-printing.h +++ b/tests/unit/unittest/gtest-creator-printing.h @@ -348,4 +348,24 @@ std::ostream &operator<<(std::ostream &out, const Diagnostic &diag); } // namespace Internal } // namespace CppTools +namespace std { +template +ostream &operator<<(ostream &out, const vector &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); diff --git a/tests/unit/unittest/sqlitestatement-test.cpp b/tests/unit/unittest/sqlitestatement-test.cpp index 94eb003c4d2..ce40e0c3d8b 100644 --- a/tests/unit/unittest/sqlitestatement-test.cpp +++ b/tests/unit/unittest/sqlitestatement-test.cpp @@ -38,6 +38,14 @@ #include +namespace Sqlite { +bool operator==(Utils::span first, Utils::span second) +{ + return first.size() == second.size() + && std::memcmp(first.data(), second.data(), first.size()) == 0; +} +} // namespace Sqlite + namespace { using Sqlite::Database; @@ -288,6 +296,30 @@ TEST_F(SqliteStatement, BindPointer) 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(chars); + Utils::span 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 bytes; + + statement.bind(1, bytes); + statement.next(); + + ASSERT_THAT(statement.fetchBlobValue(0), IsEmpty()); +} + TEST_F(SqliteStatement, BindIntegerByParameter) { 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); } -TEST_F(SqliteStatement, BindIndexIsTpLargeIsThrowingBindingIndexIsOutOfBoundLongLong) +TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundLongLong) { SqliteTestStatement statement("SELECT name, number FROM test WHERE number=$1", database); 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); 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); 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); 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); 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 bytes; + + ASSERT_THROW(statement.bind(2, bytes), Sqlite::BindingIndexIsOutOfRange); +} + TEST_F(SqliteStatement, WrongBindingNameThrowingBindingIndexIsOutOfBound) { 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)); } +TEST_F(SqliteStatement, WriteEmptyBlobs) +{ + SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database); + + Utils::span bytes; + + statement.write(bytes); + + ASSERT_THAT(statement.fetchBlobValue(0), IsEmpty()); +} + +class Blob +{ +public: + Blob(Utils::span bytes) + : bytes(bytes.begin(), bytes.end()) + {} + + std::vector 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(chars); + Utils::span bytes{bytePointer, sizeof(chars) - 1}; + + statement.write(bytes); + + ASSERT_THAT(readStatement.template value(), Optional(Field(&Blob::bytes, Eq(bytes)))); +} + TEST_F(SqliteStatement, BindNamedValues) { 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})); } +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(&value); + Utils::span bytes{bytePointer, 4}; + + auto values = statement.values(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(); + + 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(); + + 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(); + + ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty()))); +} + TEST_F(SqliteStatement, GetOptionalSingleValueAndMultipleQueryValue) { ReadStatement statement("SELECT name FROM test WHERE name=? AND number=? AND value=?", database); diff --git a/tests/unit/unittest/sqlitevalue-test.cpp b/tests/unit/unittest/sqlitevalue-test.cpp index ab4d628ec8f..1a90c330f07 100644 --- a/tests/unit/unittest/sqlitevalue-test.cpp +++ b/tests/unit/unittest/sqlitevalue-test.cpp @@ -85,6 +85,15 @@ TEST(SqliteValue, ConstructStringFromQString) ASSERT_THAT(value.toStringView(), Eq("foo")); } +TEST(SqliteValue, ConstructStringFromBlob) +{ + // Utils::span bytes{reinterpret_cast("abcd"), 4}; + + // Sqlite::Value value{bytes}; + + //ASSERT_THAT(value.toBlob(), Eq(bytes)); +} + TEST(SqliteValue, ConstructNullFromNullQVariant) { QVariant variant{};