Sqlite: Add null value

So we can distingish between a null value and zero or an empty string.

Change-Id: I9122fdafdf85cf04dcf8bca7bf294be9b28ee251
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
Marco Bubke
2020-05-14 12:50:34 +02:00
committed by Tim Jenssen
parent d2fe9da7e7
commit 33a833d187
5 changed files with 167 additions and 27 deletions

View File

@@ -158,6 +158,13 @@ Utils::SmallStringVector BaseStatement::columnNames() const
return columnNames; return columnNames;
} }
void BaseStatement::bind(int index, NullValue)
{
int resultCode = sqlite3_bind_null(m_compiledStatement.get(), index);
if (resultCode != SQLITE_OK)
checkForBindingError(resultCode);
}
void BaseStatement::bind(int index, int value) void BaseStatement::bind(int index, int value)
{ {
int resultCode = sqlite3_bind_int(m_compiledStatement.get(), index, value); int resultCode = sqlite3_bind_int(m_compiledStatement.get(), index, value);
@@ -529,6 +536,8 @@ ValueView BaseStatement::fetchValueView(int column) const
{ {
int dataType = sqlite3_column_type(m_compiledStatement.get(), column); int dataType = sqlite3_column_type(m_compiledStatement.get(), column);
switch (dataType) { switch (dataType) {
case SQLITE_NULL:
return ValueView::create(NullValue{});
case SQLITE_INTEGER: case SQLITE_INTEGER:
return ValueView::create(fetchLongLongValue(column)); return ValueView::create(fetchLongLongValue(column));
case SQLITE_FLOAT: case SQLITE_FLOAT:
@@ -536,11 +545,10 @@ ValueView BaseStatement::fetchValueView(int column) const
case SQLITE3_TEXT: case SQLITE3_TEXT:
return ValueView::create(fetchValue<Utils::SmallStringView>(column)); return ValueView::create(fetchValue<Utils::SmallStringView>(column));
case SQLITE_BLOB: case SQLITE_BLOB:
case SQLITE_NULL:
break; break;
} }
return ValueView::create(0LL); return ValueView::create(NullValue{});
} }
} // namespace Sqlite } // namespace Sqlite

View File

@@ -74,6 +74,7 @@ public:
int columnCount() const; int columnCount() const;
Utils::SmallStringVector columnNames() const; Utils::SmallStringVector columnNames() const;
void bind(int index, NullValue);
void bind(int index, int fetchValue); void bind(int index, int fetchValue);
void bind(int index, long long fetchValue); void bind(int index, long long fetchValue);
void bind(int index, double fetchValue); void bind(int index, double fetchValue);

View File

@@ -34,13 +34,22 @@
namespace Sqlite { namespace Sqlite {
enum class ValueType : unsigned char { Integer, Float, String }; enum class ValueType : unsigned char { Null, Integer, Float, String };
class NullValue
{
friend bool operator==(NullValue, NullValue) { return false; }
};
template<typename StringType> template<typename StringType>
class ValueBase class ValueBase
{ {
public: public:
using VariantType = Utils::variant<long long, double, StringType>; using VariantType = Utils::variant<NullValue, long long, double, StringType>;
ValueBase() = default;
explicit ValueBase(NullValue) {}
explicit ValueBase(VariantType &&value) explicit ValueBase(VariantType &&value)
: value(value) : value(value)
@@ -70,6 +79,8 @@ public:
{} {}
bool isNull() const { return value.index() == 0; }
long long toInteger() const { return Utils::get<int(ValueType::Integer)>(value); } long long toInteger() const { return Utils::get<int(ValueType::Integer)>(value); }
double toFloat() const { return Utils::get<int(ValueType::Float)>(value); } double toFloat() const { return Utils::get<int(ValueType::Float)>(value); }
@@ -93,6 +104,8 @@ public:
return {}; return {};
} }
friend bool operator==(const ValueBase &first, nullptr_t) { return first.isNull(); }
friend bool operator==(const ValueBase &first, long long second) friend bool operator==(const ValueBase &first, long long second)
{ {
auto maybeInteger = Utils::get_if<int(ValueType::Integer)>(&first.value); auto maybeInteger = Utils::get_if<int(ValueType::Integer)>(&first.value);
@@ -171,10 +184,12 @@ public:
{ {
switch (value.index()) { switch (value.index()) {
case 0: case 0:
return ValueType::Integer; return ValueType::Null;
case 1: case 1:
return ValueType::Float; return ValueType::Integer;
case 2: case 2:
return ValueType::Float;
case 3:
return ValueType::String; return ValueType::String;
} }
@@ -206,6 +221,10 @@ class Value : public ValueBase<Utils::SmallString>
public: public:
using Base::Base; using Base::Base;
Value() = default;
explicit Value(NullValue) {}
explicit Value(ValueView view) explicit Value(ValueView view)
: ValueBase(convert(view)) : ValueBase(convert(view))
{} {}
@@ -265,6 +284,9 @@ public:
private: private:
static Base::VariantType convert(const QVariant &value) static Base::VariantType convert(const QVariant &value)
{ {
if (value.isNull())
return VariantType{NullValue{}};
switch (value.type()) { switch (value.type()) {
case QVariant::Int: case QVariant::Int:
return VariantType{static_cast<long long>(value.toInt())}; return VariantType{static_cast<long long>(value.toInt())};
@@ -284,6 +306,8 @@ private:
static Base::VariantType convert(ValueView view) static Base::VariantType convert(ValueView view)
{ {
switch (view.type()) { switch (view.type()) {
case ValueType::Null:
return VariantType(NullValue{});
case ValueType::Integer: case ValueType::Integer:
return VariantType{view.toInteger()}; return VariantType{view.toInteger()};
case ValueType::Float: case ValueType::Float:

View File

@@ -66,11 +66,33 @@ MATCHER_P3(HasValues, value1, value2, rowid,
&& statement.fetchSmallStringViewValue(1) == value2; && statement.fetchSmallStringViewValue(1) == value2;
} }
MATCHER_P(HasNullValues, rowid, std::string(negation ? "isn't null" : "is null"))
{
Database &database = arg.database();
SqliteTestStatement statement("SELECT name, number FROM test WHERE rowid=?", database);
statement.bind(1, rowid);
statement.next();
return statement.fetchValueView(0).isNull() && statement.fetchValueView(1).isNull();
}
class SqliteStatement : public ::testing::Test class SqliteStatement : public ::testing::Test
{ {
protected: protected:
void SetUp() override; void SetUp() override
void TearDown() override; {
database.execute("CREATE TABLE test(name TEXT UNIQUE, number NUMERIC, value NUMERIC)");
database.execute("INSERT INTO test VALUES ('bar', 'blah', 1)");
database.execute("INSERT INTO test VALUES ('foo', 23.3, 2)");
database.execute("INSERT INTO test VALUES ('poo', 40, 3)");
}
void TearDown() override
{
if (database.isOpen())
database.close();
}
protected: protected:
Database database{":memory:", Sqlite::JournalMode::Memory}; Database database{":memory:", Sqlite::JournalMode::Memory};
@@ -210,13 +232,24 @@ TEST_F(SqliteStatement, ColumnNames)
ASSERT_THAT(columnNames, ElementsAre("name", "number")); ASSERT_THAT(columnNames, ElementsAre("name", "number"));
} }
TEST_F(SqliteStatement, BindNull)
{
database.execute("INSERT INTO test VALUES (NULL, 323, 344)");
SqliteTestStatement statement("SELECT name, number FROM test WHERE name IS ?", database);
statement.bind(1, Sqlite::NullValue{});
statement.next();
ASSERT_TRUE(statement.fetchValueView(0).isNull());
ASSERT_THAT(statement.fetchValue<int>(1), 323);
}
TEST_F(SqliteStatement, BindString) TEST_F(SqliteStatement, BindString)
{ {
SqliteTestStatement statement("SELECT name, number FROM test WHERE name=?", database); SqliteTestStatement statement("SELECT name, number FROM test WHERE name=?", database);
statement.bind(1, "foo"); statement.bind(1, "foo");
statement.next(); statement.next();
ASSERT_THAT(statement.fetchSmallStringViewValue(0), "foo"); ASSERT_THAT(statement.fetchSmallStringViewValue(0), "foo");
@@ -314,6 +347,16 @@ TEST_F(SqliteStatement, BindValues)
ASSERT_THAT(statement, HasValues("see", "7.23", 1)); ASSERT_THAT(statement, HasValues("see", "7.23", 1));
} }
TEST_F(SqliteStatement, BindNullValues)
{
SqliteTestStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database);
statement.bindValues(Sqlite::NullValue{}, Sqlite::Value{}, 1);
statement.execute();
ASSERT_THAT(statement, HasNullValues(1));
}
TEST_F(SqliteStatement, WriteValues) TEST_F(SqliteStatement, WriteValues)
{ {
WriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database); WriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database);
@@ -323,6 +366,15 @@ TEST_F(SqliteStatement, WriteValues)
ASSERT_THAT(statement, HasValues("see", "7.23", 1)); ASSERT_THAT(statement, HasValues("see", "7.23", 1));
} }
TEST_F(SqliteStatement, WriteNullValues)
{
WriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database);
statement.write(Sqlite::NullValue{}, Sqlite::Value{}, 1);
ASSERT_THAT(statement, HasNullValues(1));
}
TEST_F(SqliteStatement, WriteSqliteValues) TEST_F(SqliteStatement, WriteSqliteValues)
{ {
WriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database); WriteStatement statement("UPDATE test SET name=?, number=? WHERE rowid=?", database);
@@ -407,10 +459,11 @@ public:
TEST_F(SqliteStatement, GetSingleSqliteValuesWithoutArguments) TEST_F(SqliteStatement, GetSingleSqliteValuesWithoutArguments)
{ {
ReadStatement statement("SELECT number FROM test", database); ReadStatement statement("SELECT number FROM test", database);
database.execute("INSERT INTO test VALUES (NULL, NULL, NULL)");
std::vector<FooValue> values = statement.values<FooValue>(3); std::vector<FooValue> values = statement.values<FooValue>(3);
ASSERT_THAT(values, ElementsAre(Eq("blah"), Eq(23.3), Eq(40))); ASSERT_THAT(values, ElementsAre(Eq("blah"), Eq(23.3), Eq(40), IsNull()));
} }
TEST_F(SqliteStatement, GetStructValuesWithoutArguments) TEST_F(SqliteStatement, GetStructValuesWithoutArguments)
@@ -710,19 +763,4 @@ TEST_F(SqliteStatement, ResetIfExecuteThrowsException)
ASSERT_ANY_THROW(mockStatement.execute()); ASSERT_ANY_THROW(mockStatement.execute());
} }
} // namespace
void SqliteStatement::SetUp()
{
database.execute("CREATE TABLE test(name TEXT UNIQUE, number NUMERIC, value NUMERIC)");
database.execute("INSERT INTO test VALUES ('bar', 'blah', 1)");
database.execute("INSERT INTO test VALUES ('foo', 23.3, 2)");
database.execute("INSERT INTO test VALUES ('poo', 40, 3)");
}
void SqliteStatement::TearDown()
{
if (database.isOpen())
database.close();
}
}

View File

@@ -29,6 +29,20 @@
namespace { namespace {
TEST(SqliteValue, ConstructDefault)
{
Sqlite::Value value{};
ASSERT_TRUE(value.isNull());
}
TEST(SqliteValue, ConstructNullValue)
{
Sqlite::Value value{Sqlite::NullValue{}};
ASSERT_TRUE(value.isNull());
}
TEST(SqliteValue, ConstructLongLong) TEST(SqliteValue, ConstructLongLong)
{ {
Sqlite::Value value{1LL}; Sqlite::Value value{1LL};
@@ -36,7 +50,7 @@ TEST(SqliteValue, ConstructLongLong)
ASSERT_THAT(value.toInteger(), Eq(1LL)); ASSERT_THAT(value.toInteger(), Eq(1LL));
} }
TEST(SqliteValue, Construct) TEST(SqliteValue, ConstructInteger)
{ {
Sqlite::Value value{1}; Sqlite::Value value{1};
@@ -71,6 +85,15 @@ TEST(SqliteValue, ConstructStringFromQString)
ASSERT_THAT(value.toStringView(), Eq("foo")); ASSERT_THAT(value.toStringView(), Eq("foo"));
} }
TEST(SqliteValue, ConstructNullFromNullQVariant)
{
QVariant variant{};
Sqlite::Value value{variant};
ASSERT_TRUE(value.isNull());
}
TEST(SqliteValue, ConstructStringFromIntQVariant) TEST(SqliteValue, ConstructStringFromIntQVariant)
{ {
QVariant variant{1}; QVariant variant{1};
@@ -116,6 +139,15 @@ TEST(SqliteValue, ConstructStringFromStringQVariant)
ASSERT_THAT(value.toStringView(), Eq("foo")); ASSERT_THAT(value.toStringView(), Eq("foo"));
} }
TEST(SqliteValue, ConvertToNullQVariant)
{
Sqlite::Value value{};
auto variant = QVariant{value};
ASSERT_TRUE(variant.isNull());
}
TEST(SqliteValue, ConvertToStringQVariant) TEST(SqliteValue, ConvertToStringQVariant)
{ {
Sqlite::Value value{"foo"}; Sqlite::Value value{"foo"};
@@ -192,6 +224,13 @@ TEST(SqliteValue, IntegerAndFloatAreNotEquals)
ASSERT_FALSE(isEqual); ASSERT_FALSE(isEqual);
} }
TEST(SqliteValue, NullValuesNeverEqual)
{
bool isEqual = Sqlite::Value{} == Sqlite::Value{};
ASSERT_FALSE(isEqual);
}
TEST(SqliteValue, IntegerValuesAreEquals) TEST(SqliteValue, IntegerValuesAreEquals)
{ {
bool isEqual = Sqlite::Value{1} == Sqlite::Value{1}; bool isEqual = Sqlite::Value{1} == Sqlite::Value{1};
@@ -248,6 +287,13 @@ TEST(SqliteValue, IntegersAreUnequalInverse)
ASSERT_TRUE(isUnequal); ASSERT_TRUE(isUnequal);
} }
TEST(SqliteValue, NullType)
{
auto type = Sqlite::Value{}.type();
ASSERT_THAT(type, Sqlite::ValueType::Null);
}
TEST(SqliteValue, IntegerType) TEST(SqliteValue, IntegerType)
{ {
auto type = Sqlite::Value{1}.type(); auto type = Sqlite::Value{1}.type();
@@ -269,6 +315,20 @@ TEST(SqliteValue, StringType)
ASSERT_THAT(type, Sqlite::ValueType::String); ASSERT_THAT(type, Sqlite::ValueType::String);
} }
TEST(SqliteValue, NullValueAndValueViewAreNotEqual)
{
bool isEqual = Sqlite::ValueView::create(Sqlite::NullValue{}) == Sqlite::Value{};
ASSERT_FALSE(isEqual);
}
TEST(SqliteValue, NullValueViewAndValueAreNotEqual)
{
bool isEqual = Sqlite::Value{} == Sqlite::ValueView::create(Sqlite::NullValue{});
ASSERT_FALSE(isEqual);
}
TEST(SqliteValue, StringValueAndValueViewEquals) TEST(SqliteValue, StringValueAndValueViewEquals)
{ {
bool isEqual = Sqlite::ValueView::create("foo") == Sqlite::Value{"foo"}; bool isEqual = Sqlite::ValueView::create("foo") == Sqlite::Value{"foo"};
@@ -318,6 +378,15 @@ TEST(SqliteValue, StringValueAndIntergerValueViewAreNotEqual)
ASSERT_FALSE(isEqual); ASSERT_FALSE(isEqual);
} }
TEST(SqliteValue, ConvertNullValueViewIntoValue)
{
auto view = Sqlite::ValueView::create(Sqlite::NullValue{});
Sqlite::Value value{view};
ASSERT_TRUE(value.isNull());
}
TEST(SqliteValue, ConvertStringValueViewIntoValue) TEST(SqliteValue, ConvertStringValueViewIntoValue)
{ {
auto view = Sqlite::ValueView::create("foo"); auto view = Sqlite::ValueView::create("foo");