-
Notifications
You must be signed in to change notification settings - Fork 479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SOCI doesn't support automatic type conversion #1088
Comments
Before discussing this further, I'd like to understand whether this is really a problem. What conversions exactly do you think should exist and why? |
column type int, target type long long :
column type long long, target type int :
|
All of the types are really inconsistent between backends with a lot of data truncation or miss-handling :
#include "soci/soci.h"
#include "soci/sqlite3/soci-sqlite3.h"
#include "soci/oracle/soci-oracle.h"
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include <exception>
#include <chrono>
#include <cstdio>
template<typename T>
struct myType {
typedef T val_type;
soci::data_type inType;
soci::data_type outType;
T inMax = std::numeric_limits<T>::max();
T outMax;
};
namespace soci {
template<typename T>
void get_safe_data(T& t, const soci::values& v) {
t.outType = v.get_properties("MAX_").get_data_type();
switch (t.outType) {
case soci::dt_string: break;
case soci::dt_double:
t.outMax = static_cast<typename T::val_type>(v.get<double>("MAX_"));
break;
case soci::dt_integer:
t.outMax = static_cast<typename T::val_type>(v.get<int>("MAX_"));
break;
case soci::dt_unsigned_long_long:
t.outMax = static_cast<typename T::val_type>(v.get<unsigned long long>("MAX_"));
break;
case soci::dt_long_long:
t.outMax = static_cast<typename T::val_type>(v.get<long long>("MAX_"));
break;
default: break;
}
}
template<typename T>
struct type_conversion<myType<T>> {
typedef soci::values base_type;
static void from_base(const soci::values& v, soci::indicator ind, myType<T>& t)
{ get_safe_data(t, v); }
static void to_base(const myType<T>& t, soci::values& v, soci::indicator& ind)
{ v.set("MAX_", t.inMax); }
};
}
std::string to_str(soci::data_type t) {
switch (t) {
case soci::dt_string: return "(" + std::to_string((int)t) + ")string ";
case soci::dt_double: return "(" + std::to_string((int)t) + ")double ";
case soci::dt_integer: return "(" + std::to_string((int)t) + ")integer ";
case soci::dt_long_long: return "(" + std::to_string((int)t) + ")long_long ";
case soci::dt_unsigned_long_long: return "(" + std::to_string((int)t) + ")ulong_long";
default: return "(" + std::to_string((int)t) + ")unknown";
}
}
template<typename T>
void testType(soci::data_type t) {
myType<T> val;
val.inType = t;
for (const auto& str : { "sqlite3://db=:memory:", "oracle:" /*???*/}) {
try {
std::cout << (str[0] == 's' ? "sqlite" : "oracle");
std::string table = "TEST_";
soci::session sql(str);
try { sql << "DROP TABLE " << table; } catch(...) {}
sql.create_table(table).column("MAX_", val.inType);
sql << "INSERT INTO " << table << "(MAX_) VALUES (:MAX_)", soci::use(val.inMax);
soci::statement stmt = (sql.prepare << "SELECT * FROM " << table);
stmt.exchange(soci::into(val));
stmt.define_and_bind();
stmt.execute();
stmt.fetch();
std::cout << " " << to_str(val.inType)
<< " " << to_str(val.outType)
<< " " << val.inMax
<< " " << val.outMax
<< "\n";
} catch (std::exception& e) {
std::cout << " Error: " << e.what() << "\n";
}
}
}
int main()
{
soci::register_factory_sqlite3();
soci::register_factory_oracle();
testType<double>(soci::dt_double);
testType<int>(soci::dt_integer);
testType<long long>(soci::dt_long_long);
testType<unsigned long long>(soci::dt_unsigned_long_long);
return 0;
} |
Are you testing all this with or without #954?
As SQLite IOW maybe the real title of this issue should be "SQLite backend doesn't support automatic type conversions"? Sorry but I don't have time to dive into this now, but I'd just like to at least formulate the problem exactly before -- hopefully -- fixing it. |
Unfortunately, github is not previewing the code but it actually the same mess that is used here. Introducing the new types will actually make the conversion even more complicated. The current behavior is either you get the data in the native format or you get a bad_cast. Maybe introducing a safe_get would be better but in this case you will never have a reason to use the unsafe get. https://github.com/SOCI/soci/pull/954/files#r1355873352 Regarding sqlite having a polymorphic integer is great but the underlying My previous comment was actually not accurate, with oracle, long long type is treated as double meaning that when you get data from the database, you loose precision in soci. |
This commit is an implementation of automatic conversion : Sildra@10d2f77 It works with all the c++ numeric types by performing the appropriate conversions and will reject unknown types with a static assert. Strings are not implicitly converted and will throw the usual bad_cast + some details. The implem is faster than the 4.0.3 implementation with the dynamic cast and should be no less safe than the master implementation with the typeid vodoo. |
I'm sorry if I missed something because there are several semi-related or at least overlapping issues/PRs, but I still don't understand what are we even trying to do here. Could you please explain what is your desired behaviour? |
Actually, the commit doesn't contain unrelated issue. It is all about not having useless bad_cast when retrieving data from the db. If I do this : sql.create_table("TEST").column("VAL", soci::dt_long_long);
sql << "INSERT INTO TEST VALUES (1234)"; In oracle : soci::row r;
sql << "SELECT VAL FROM TEST", soci::into(r);
r.get<long long>(0); // std::bad_cast -> why ? I have declared my column as a long long in the DDL
r.get<double>(0); // OK -> why ? It is not event an integral type ? In sqlite : soci::row r;
sql << "SELECT VAL FROM TEST", soci::into(r);
r.get<long long>(0); // std::bad_cast -> why ? I have declared my column as a long long in the DDL
r.get<int>(0); // OK -> why ? SOCI is truncating my data by itself After my proposal, whatever the DB, because I don't really care about my source type : soci::row r;
sql << "SELECT VAL FROM TEST", soci::into(r);
r.get<long long>(0); // OK
r.get<int>(0); // OK
r.get<double>(0); // OK
r.get<int32_t>(0); // OK
r.get<int64_t>(0); // OK
r.get<bool>(0); // OK
r.get<std::string>(0); // bad_cast: source type is <depedent on the DB>, target type is std::string And if I still want to perform checks : soci::row r;
sql << "SELECT VAL FROM TEST", soci::into(r);
auto props = r.get_properties(0); // OK, the source type from the DB is <depedent on the DB> and I may want to check overflow/underflow/signedness/whatever, but everyone know that I will just perform a static_cast
switch (props.get_data_type()) {
// Usual code that is currently repeated in every code that is using SOCI just to perform a static_cast
} |
Thanks, so your idea is that creating a column with I don't really know what is the right way to map |
Personally, I think merging #954 before this is actually worse because you will have a bunch of types that will never have any equivalent in major DB. You can distinguish uint64 and int64 because they don't have the same number of digits, but you won't be able to do this with int32 and uint32. As a result, you will always get a bad_cast when performing What I did is the most compliant between numeric types buy I can separate floating and integral types and keep the bad_cast. The reason why I am proposing this is that integrating #1087 or #1085 will break existing code that don't use the properties to get the values. Remember that |
I think what you're proposing is an alternative solution to the problem with #954 solves and at least so far I don't see why is it better. C++ tries to be a strongly typed language and so it makes much more sense to me to insist on strict mapping between C++ and database types rather than converting between them willy-nilly. I do hope to merge #954 soon and I believe |
Actually, I am proposing a fix for #954 that is likely to break everything. When you are using the Current code uses a switch over the switch (row.describe_column(0).get_data_type()) {
case dt_integer: row.get<int>(0);
} New code need to use the switch (row.describe_column(0).get_db_type()) {
case db_int8: row.get<int8_t>(0);
} See the problem here ? One of them will throw a |
OK, I do see a problem. It seems like we need to make both of them work, which could be achieved by allowing promotion to the bigger type, i.e. Is this problem specific to |
I need to check the code as I am not familiar with 'normal' binding but I think it is using the |
Currently, SOCI doesn't support any kind of automatic conversion between integrals.
This issue forces developpers to perform database-specific conversions notably when using sqlite and another db simultaneously.
Providing a feature performing this automatic conversion between int and long long will enable sqlite to change it's default type from 32bit to 64bit and use dates without breaking existing current user code.
I kind of attempted to add this feature by performing a dynamic cast + virtual call and it is working as intended.
The text was updated successfully, but these errors were encountered: