Skip to content

Commit

Permalink
Improve column names to SOCI database types mapping with SQLite3
Browse files Browse the repository at this point in the history
Use type-appropriate names for the columns, which are still interpreted
in the same way as just "INTEGER" by SQLite3 itself, but can be mapped
back to the suitable types when reading from the database in SOCI.

Closes #1120.
  • Loading branch information
Sildra authored and vadz committed Apr 24, 2024
1 parent 8a5ed87 commit ce0630b
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 3 deletions.
10 changes: 9 additions & 1 deletion include/soci/sqlite3/soci-sqlite3.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,15 +330,23 @@ struct sqlite3_session_backend : details::session_backend
case db_double:
return "real";
case db_date:
return "integer";
case db_int8:
return "tinyint";
case db_uint8:
return "unsignedtinyint";
case db_int16:
return "smallint";
case db_uint16:
return "unsignedsmallint";
case db_int32:
return "integer";
case db_uint32:
return "unsignedint";
case db_int64:
return "bigint";
case db_uint64:
return "integer";
return "unsignedbigint";
case db_blob:
return "blob";
default:
Expand Down
11 changes: 9 additions & 2 deletions src/backends/sqlite3/statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,18 +474,25 @@ static sqlite3_data_type_map get_data_type_map()
// integer types
m["tinyint"] = db_int8;

m["unsignedtinyint"] = db_uint8;

m["smallint"] = db_int16;
m["int2"] = db_int16;

m["unsignedsmallint"] = db_uint16;

m["boolean"] = db_int32;
m["int"] = db_int32;
m["integer"] = db_int32;
m["int2"] = db_int32;
m["mediumint"] = db_int32;
m["int4"] = db_int32;

m["unsignedint"] = db_uint32;

m["bigint"] = db_int64;
m["int8"] = db_int64;

m["unsignedbigint"] = db_uint64;
m["unsignedbigint"] = db_uint64;

// db_string
m["char"] = db_string;
Expand Down
96 changes: 96 additions & 0 deletions tests/common-tests.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,105 @@ template<> struct type_conversion<PhonebookEntry3>

namespace soci
{

// Helper used by test_roundtrip() below which collects all round trip test
// data and allows to define a type conversion for it.
template<typename T>
struct Roundtrip
{
typedef T val_type;
Roundtrip(soci::db_type type, T val)
: inType(type), inVal(val) {}

soci::db_type inType;
T inVal;

soci::db_type outType;
T outVal;
};

// Test a rountrip insertion data to the current database for the arithmetic type T
// This test specifically use the dynamic bindings and the DDL creation statements.
template<typename T>
struct type_conversion<Roundtrip<T>>
{
static_assert(std::is_arithmetic<T>::value, "Roundtrip currently supported only for numeric types");
typedef soci::values base_type;
static void from_base(soci::values const &v, soci::indicator, Roundtrip<T> &t)
{
t.outType = v.get_properties(0).get_db_type();
switch (t.outType)
{
case soci::db_int8: t.outVal = static_cast<T>(v.get<std::int8_t>(0)); break;
case soci::db_uint8: t.outVal = static_cast<T>(v.get<std::uint8_t>(0)); break;
case soci::db_int16: t.outVal = static_cast<T>(v.get<std::int16_t>(0)); break;
case soci::db_uint16: t.outVal = static_cast<T>(v.get<std::uint16_t>(0)); break;
case soci::db_int32: t.outVal = static_cast<T>(v.get<std::int32_t>(0)); break;
case soci::db_uint32: t.outVal = static_cast<T>(v.get<std::uint32_t>(0)); break;
case soci::db_int64: t.outVal = static_cast<T>(v.get<std::int64_t>(0)); break;
case soci::db_uint64: t.outVal = static_cast<T>(v.get<std::uint64_t>(0)); break;
case soci::db_double: t.outVal = static_cast<T>(v.get<double>(0)); break;
default: FAIL_CHECK("Unsupported type mapped to db_type"); break;
}
}
static void to_base(Roundtrip<T> const &t, soci::values &v, soci::indicator&)
{
v.set("VAL", t.inVal);
}
};

namespace tests
{

template<typename T>
void check(soci::Roundtrip<T> const &val)
{
CHECK(val.inType == val.outType);
CHECK(val.inVal == val.outVal);
}

template<>
void check(soci::Roundtrip<double> const &val)
{
CHECK(val.inType == val.outType);
CHECK(std::fpclassify(val.inVal) == std::fpclassify(val.outVal));
if (std::isnormal(val.inVal) && std::isnormal(val.outVal))
CHECK_THAT(val.inVal, Catch::Matchers::WithinRel(val.outVal));
}

template<typename T>
void test_roundtrip(soci::session &sql, soci::db_type inputType, T inputVal)
{
try
{
Roundtrip<T> tester(inputType, inputVal);

const std::string table = "TEST_ROUNDTRIP";
sql.create_table(table).column("VAL", tester.inType);
struct table_dropper
{
table_dropper(soci::session& sql, std::string const& table)
: sql_(sql), table_(table) {}
~table_dropper() { sql_ << "DROP TABLE " << table_; }

soci::session& sql_;
const std::string table_;
} dropper(sql, table);

sql << "INSERT INTO " << table << "(VAL) VALUES (:VAL)", soci::use(const_cast<const Roundtrip<T>&>(tester));
soci::statement stmt = (sql.prepare << "SELECT * FROM " << table);
stmt.exchange(soci::into(tester));
stmt.define_and_bind();
stmt.execute();
stmt.fetch();
check(tester);
}
catch (const std::exception& e)
{
FAIL_CHECK(e.what());
}
}

// TODO: improve cleanup capabilities by subtypes, soci_test name may be omitted --mloskot
// i.e. optional ctor param accepting custom table name
class table_creator_base
Expand Down
14 changes: 14 additions & 0 deletions tests/sqlite3/test-sqlite3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,20 @@ TEST_CASE("SQLite DDL with metadata", "[sqlite][ddl]")
CHECK(ddl_t3_found == false);
}

TEST_CASE("SQLite DDL roundrip", "[sqlite][ddl][roundtrip]")
{
soci::session sql(backEnd, connectString);
test_roundtrip(sql, soci::db_double, std::numeric_limits<double>::max());
test_roundtrip(sql, soci::db_int8, std::numeric_limits<std::int8_t>::max());
test_roundtrip(sql, soci::db_int16, std::numeric_limits<std::int16_t>::max());
test_roundtrip(sql, soci::db_int32, std::numeric_limits<std::int32_t>::max());
test_roundtrip(sql, soci::db_int64, std::numeric_limits<std::int64_t>::max());
test_roundtrip(sql, soci::db_uint8, std::numeric_limits<std::uint8_t>::max());
test_roundtrip(sql, soci::db_uint16, std::numeric_limits<std::uint16_t>::max());
test_roundtrip(sql, soci::db_uint32, std::numeric_limits<std::uint32_t>::max());
test_roundtrip(sql, soci::db_uint64, std::numeric_limits<std::uint64_t>::max());
}

TEST_CASE("SQLite vector long long", "[sqlite][vector][longlong]")
{
soci::session sql(backEnd, connectString);
Expand Down

0 comments on commit ce0630b

Please sign in to comment.