diff --git a/include/matador/db/postgresql/postgresql_getvalue.hpp b/include/matador/db/postgresql/postgresql_getvalue.hpp index cabf68588..3b9d105bc 100644 --- a/include/matador/db/postgresql/postgresql_getvalue.hpp +++ b/include/matador/db/postgresql/postgresql_getvalue.hpp @@ -14,6 +14,10 @@ namespace detail { template < typename T, typename std::enable_if::value && std::is_signed::value && !std::is_same::value>::type* = nullptr> void get_value(PGresult *res, size_t row, size_t col, T &val) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + val = 0; + return; + } auto value = PQgetvalue(res, (int)row, (int)col); if (strlen(value) == 0) { @@ -31,6 +35,10 @@ std::is_unsigned::value && >::type* = nullptr> void get_value(PGresult *res, size_t row, size_t col, T &val) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + val = 0; + return; + } auto value = PQgetvalue(res, (int)row, (int)col); if (strlen(value) == 0) { @@ -43,6 +51,10 @@ void get_value(PGresult *res, size_t row, size_t col, T &val) template < typename T, typename std::enable_if::value>::type* = nullptr> void get_value(PGresult *res, size_t row, size_t col, T &val) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + val = false; + return; + } auto value = PQgetvalue(res, (int)row, (int)col); if (strlen(value) == 0) { @@ -55,6 +67,10 @@ void get_value(PGresult *res, size_t row, size_t col, T &val) template < typename T, typename std::enable_if::value>::type* = nullptr> void get_value(PGresult *res, size_t row, size_t col, T &val) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + val = 0; + return; + } auto value = PQgetvalue(res, (int)row, (int)col); if (strlen(value) == 0) { @@ -67,6 +83,10 @@ void get_value(PGresult *res, size_t row, size_t col, T &val) template < typename T, typename std::enable_if::value>::type* = nullptr> void get_value(PGresult *res, size_t row, size_t col, T &val) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + val = 0; + return; + } auto value = PQgetvalue(res, (int)row, (int)col); if (strlen(value) == 0) { @@ -79,24 +99,41 @@ void get_value(PGresult *res, size_t row, size_t col, T &val) template < typename T, typename std::enable_if::value>::type* = nullptr> void get_value(PGresult *res, size_t row, size_t col, T &val) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + val.clear(); + return; + } + if (PQgetisnull(res, (int)row, (int)col) == 1) { + return; + } val = PQgetvalue(res, (int)row, (int)col); } template < typename T, typename std::enable_if::value>::type* = nullptr> void get_value(PGresult *res, size_t row, size_t col, T &val, size_t) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + val.clear(); + return; + } val = PQgetvalue(res, (int)row, (int)col); } template < typename T, typename std::enable_if::value>::type* = nullptr> void get_value(PGresult *res, size_t row, size_t col, T &val) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + return; + } val = matador::time::parse(PQgetvalue(res, (int)row, (int)col), "%Y-%m-%d %T.%f"); } template < typename T, typename std::enable_if::value>::type* = nullptr> void get_value(PGresult *res, size_t row, size_t col, T &val) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + return; + } val.set(PQgetvalue(res, (int)row, (int)col), date_format::ISO8601); } diff --git a/src/db/mssql/mssql_result.cpp b/src/db/mssql/mssql_result.cpp index fbcbadfad..0e343484e 100644 --- a/src/db/mssql/mssql_result.cpp +++ b/src/db/mssql/mssql_result.cpp @@ -172,6 +172,8 @@ void mssql_result::read_column(const char *, size_type index, std::string &val) if (SQL_SUCCEEDED(ret)) { if (info > 0) { val.assign(buf, info); + } else { + val.clear(); } } else { throw_database_error(ret, SQL_HANDLE_STMT, stmt_, "mssql"); @@ -189,6 +191,8 @@ void mssql_result::read_column(const char *, size_type index, std::string &val, if (SQL_SUCCEEDED(ret)) { if (info > 0) { val.assign(buf.data(), info); + } else { + val.clear(); } } else { throw_database_error(ret, SQL_HANDLE_STMT, stmt_, "mssql"); @@ -225,7 +229,9 @@ void mssql_result::read_column(char const *, size_type index, date &x) SQLLEN info = 0; SQLRETURN ret = SQLGetData(stmt_, static_cast(index), SQL_C_TYPE_DATE, &ds, 0, &info); if (SQL_SUCCEEDED(ret)) { - x.set(ds.day, ds.month, ds.year); + if (info != SQL_NULL_DATA) { + x.set(ds.day, ds.month, ds.year); + } } else { throw_database_error(ret, SQL_HANDLE_STMT, stmt_, "mssql"); } @@ -238,7 +244,9 @@ void mssql_result::read_column(char const *, size_type index, time &x) SQLLEN info = 0; SQLRETURN ret = SQLGetData(stmt_, static_cast(index), SQL_C_TYPE_TIMESTAMP, &ts, 0, &info); if (SQL_SUCCEEDED(ret)) { - x.set(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.fraction / 1000 / 1000); + if (info != SQL_NULL_DATA) { + x.set(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.fraction / 1000 / 1000); + } } else { throw_database_error(ret, SQL_HANDLE_STMT, stmt_, "mssql"); } diff --git a/src/db/mysql/mysql_result.cpp b/src/db/mysql/mysql_result.cpp index ad367a33c..4a0946a05 100644 --- a/src/db/mysql/mysql_result.cpp +++ b/src/db/mysql/mysql_result.cpp @@ -245,6 +245,7 @@ void mysql_result::read_value(const char */*id*/, size_type index, std::string & { char *val = row_[index]; if (val == nullptr) { + value.clear(); return; } value.assign(val); @@ -254,6 +255,7 @@ void mysql_result::read_value(const char */*id*/, size_type index, std::string & { char *val = row_[index]; if (val == nullptr) { + value.clear(); return; } value.assign(val); diff --git a/src/db/postgresql/postgresql_getvalue.cpp b/src/db/postgresql/postgresql_getvalue.cpp index 33f994aad..278560d4a 100644 --- a/src/db/postgresql/postgresql_getvalue.cpp +++ b/src/db/postgresql/postgresql_getvalue.cpp @@ -5,6 +5,9 @@ namespace detail { void get_value(PGresult *res, size_t row, size_t col, char *val, size_t s) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + return; + } auto value = PQgetvalue(res, (int)row, (int)col); size_t len = strlen(value); @@ -26,6 +29,9 @@ void get_value(PGresult *res, size_t row, size_t col, char *val, size_t s) void get_value(PGresult *res, size_t row, size_t col, unsigned char &val) { + if (PQgetisnull(res, (int)row, (int)col) == 1) { + return; + } auto value = PQgetvalue(res, (int)row, (int)col); if (strlen(value) == 0) { diff --git a/src/db/sqlite/sqlite_prepared_result.cpp b/src/db/sqlite/sqlite_prepared_result.cpp index 07e20c7f3..2646512fc 100644 --- a/src/db/sqlite/sqlite_prepared_result.cpp +++ b/src/db/sqlite/sqlite_prepared_result.cpp @@ -119,7 +119,9 @@ void sqlite_prepared_result::read_value(const char */*id*/, size_type index, std { auto s = (size_t)sqlite3_column_bytes(stmt_, index); auto *text = (const char*)sqlite3_column_text(stmt_, index); - x.assign(text, s); + if (s > 0) { + x.assign(text, s); + } } void sqlite_prepared_result::read_value(const char */*id*/, size_type index, std::string &x, size_t /*size*/) @@ -153,16 +155,22 @@ void sqlite_prepared_result::read_value(const char */*id*/, size_type index, cha void sqlite_prepared_result::read_value(const char *id, size_type index, matador::date &x) { - std::string val; - read_value(id, index, val); - x = matador::date::parse(val, date_format::ISO8601); + auto is_null = sqlite3_column_type(stmt_, index) == SQLITE_NULL; + auto s = (size_t)sqlite3_column_bytes(stmt_, index); + if (!is_null && s > 0) { + const auto *text = reinterpret_cast( sqlite3_column_text(stmt_, index)); + x = matador::date::parse(text, date_format::ISO8601); + } } void sqlite_prepared_result::read_value(const char *id, size_type index, matador::time &x) { - std::string val; - read_value(id, index, val); - x = matador::time::parse(val, "%Y-%m-%dT%T.%f"); + auto is_null = sqlite3_column_type(stmt_, index) == SQLITE_NULL; + auto s = (size_t)sqlite3_column_bytes(stmt_, index); + if (!is_null && s > 0) { + const auto *text = reinterpret_cast( sqlite3_column_text(stmt_, index)); + x = matador::time::parse(text, "%Y-%m-%dT%T.%f"); + } } bool sqlite_prepared_result::prepare_fetch() diff --git a/src/db/sqlite/sqlite_result.cpp b/src/db/sqlite/sqlite_result.cpp index 30762ea25..e67fcaeb3 100644 --- a/src/db/sqlite/sqlite_result.cpp +++ b/src/db/sqlite/sqlite_result.cpp @@ -261,13 +261,17 @@ void sqlite_result::read_value(const char */*id*/, size_type index, std::string void sqlite_result::read_value(const char */*id*/, size_type index, matador::date &x) { t_row::value_type val = result_[row_index_][index]; - x.set(val, date_format::ISO8601); + if (strlen(val) > 0) { + x.set(val, date_format::ISO8601); + } } void sqlite_result::read_value(const char */*id*/, size_type index, matador::time &x) { t_row::value_type val = result_[row_index_][index]; - x = matador::time::parse(val, "%Y-%m-%dT%T.%f"); + if (strlen(val) > 0) { + x = matador::time::parse(val, "%Y-%m-%dT%T.%f"); + } } bool sqlite_result::prepare_fetch() diff --git a/test/sql/QueryTestUnit.cpp b/test/sql/QueryTestUnit.cpp index 3ec38655a..af21fdfeb 100644 --- a/test/sql/QueryTestUnit.cpp +++ b/test/sql/QueryTestUnit.cpp @@ -624,19 +624,23 @@ void QueryTestUnit::test_null_column() q.create("person", { make_pk_column("id"), make_column("name", 63), - make_column("age") + make_column("birthday") }); q.execute(connection_); - q.insert("person", {"id", "age"}).values({1, 47}).execute(connection_); + q.insert("person", {"id", "name"}).values({1, "george"}).execute(connection_); + q.insert("person", {"id", "birthday"}).values({2, date{27, 11, 1954}}).execute(connection_); - auto res = q.select({"id", "name", "age"}).from("person").execute(connection_); + auto res = q.select({"id", "name", "birthday"}).from("person").execute(connection_); + std::vector expected_names{"george", ""}; + size_t index{0}; for (const auto& r : res) { auto name = r->at("name"); - UNIT_EXPECT_TRUE(name.empty()); + UNIT_EXPECT_EQUAL(name, expected_names[index++]); } + q.drop("person").execute(connection_); }