-
Notifications
You must be signed in to change notification settings - Fork 478
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
Add column_info query for SQLite backend #1079
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this, it would indeed be nice to have this functionality for SQLite, but it would be even nicer if we could avoid introducing special API just for it.
Could we define a type_conversion
specialization for column_info
that would use backend-specific functions to perform the translation instead perhaps?
include/soci/session.h
Outdated
@@ -138,13 +138,16 @@ class SOCI_DECL session | |||
// it makes sense to bind std::string for the output field. | |||
details::prepare_temp_type prepare_table_names(); | |||
|
|||
[[deprecated("Use the override prepare_column_descriptions with column_info to support more backends (i.e. sqlite)")]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"overload" was probably meant instead of "override".
src/backends/sqlite3/session.cpp
Outdated
details::prepare_temp_type prepare_column_descriptions_statement( | ||
details::prepare_type& prepare, std::string & table_name, soci::column_info& info) | ||
{ | ||
// trick the compiler to use the static type sqlite_column_info instead of the standard one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cast is undefined behaviour and really shouldn't be done. With the current API the obvious alternative is to create a local sqlite_column_info
object and copy it to info
later, but it would be great if we could have a nicer API not requiring passing info
here at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will change it to reinterpret_cast<sqlite_&>
instead, which is not ub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's still UB because the static type doesn't match the cast target.
But the problem is not "just" the UB, it's really problematic to perform a cast to a wrong type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely not an UB, the case is described in cppreference : https://en.cppreference.com/w/cpp/language/reinterpret_cast#Notes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will correct the code this week-end and propose others implementations but they will likely be worse and introduce larger API changes.
In a static context the only solution I have in mind is :
- Calling another function (what I am doing with the reinterpret cast)
- Having dynamic type in the parameters that are aware of the backend :
- By having a field in the column_info (or another type)
- By making the colum_info virtual (or another type)
In the client, it will be something like this :
namespace soci {
struct dynamic_column_info : public column_info {
virtual void from_base(values const & v, indicator ind) {
soci::type_conversion<column_info>::from_base(v, ind, &this);
}
template<>
struct type_conversion<dynamic_column_info> {
typedef values base_type;
static void from_base(values const & v, indicator ind, dynamic_column_info & ci) {
ci.from_base(v, ind);
}
} /* struct tc */
} /* !namespace soci */
std::string tableName;
std::unique_ptr<dynamic_column_info> ci = sql.create_backend_typed_column_info();
soci::statement stmt = (sql.prepare << sql.prepare_column_descriptions(tableName), soci::into(*ci));
I admit, it will be cleaner in the backend but in the user code, not so much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, where do you see anything allowing to cast a pointer to an object to a pointer of a type different from the actual run-time type in these notes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct S { int x; };
struct S1 : S {}; // standard-layout
S1 s1 = {};
auto p1 = reinterpret_cast<S*>(&s1); // value of p1 is "pointer to the S subobject of s1"
auto i = p1->x; // OK
p1->x = 1; // OK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cast is in another direction. Of course you can cast a pointer to the derived class to the pointer of a base class. But you're doing it the other way round and when the object being pointed to is not an object of the derived class.
I will try to check if it is possible to go back to the current backend in the I still need to solve the compilation issues. |
After really looking at this I wonder why can't we use |
It works but it doesn't solve the fact that type parsing will be missmatched between creation and retrieval of the column info. |
It looks like the approach taken so far is to recognize all types for all databases in |
select name as 'COLUMN_NAME',
0 as 'CHARACTER_MAXIMUM_LENGTH',
0 as 'NUMERIC_PRECISION',
CASE WHEN type LIKE '%REAL%' OR type LIKE '%FLOAT%' OR type LIKE '%DOUBLE%' THEN 255 ELSE 0 END as 'NUMERIC_SCALE',
CASE
WHEN type LIKE 'TEXT' OR type LIKE 'CLOB' OR type LIKE '%CHAR%' THEN 'TEXT'
WHEN type LIKE '%INT%' OR type LIKE '%NUMBER%' OR type LIKE '%NUMERIC%' THEN 'INTEGER'
WHEN type LIKE '%REAL%' OR type LIKE '%FLOAT%' OR type LIKE '%DOUBLE%' THEN 'NUMBER'
ELSE UPPER(type)
END as 'DATA_TYPE',
CASE WHEN "notnull" = 0 THEN 'YES' ELSE 'NO' END as 'IS_NULLABLE'
from pragma_table_info(:t); Should do the trick, I don't have access to git for the PR ATM. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, thanks!
I can merge it as is or we can switch to using raw string if you prefer.
@@ -307,6 +307,21 @@ struct sqlite3_session_backend : details::session_backend | |||
return "select name as \"TABLE_NAME\"" | |||
" from sqlite_master where type = 'table'"; | |||
} | |||
std::string get_column_description_query() | |||
{ | |||
return "select name as 'COLUMN_NAME'," |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could be a bit more readable using raw C++11 string literals (R"(...)"
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you want, I missed the override specifier on the function definition.
For the string, the only change will be the escaped quote around notnull
, which is fairly contained at the end of the query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed the const too, because it doesn't override at the moment :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will fix it and add some tests after.
e64c2b1
to
6f8efb6
Compare
Appveyor uses sqlite version 3.12 and support for the pragma has been added in 3.14, I will skip the test in this case. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update and the test!
It looks like find_column()
-related changes are independent of the DDL ones, so it would be great to extract them into a separate commit, but if it's too difficult to do, we can merge this as is.
tests/sqlite3/test-sqlite3.cpp
Outdated
{ | ||
// Macro assigned to variable to avoid constant expression in if (disables warnings) | ||
int sqliteVersion = SQLITE_VERSION_NUMBER; | ||
if (sqliteVersion < 3'014'000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need to support C++11.
if (sqliteVersion < 3'014'000) | |
if (sqliteVersion < 3014000) |
It looks like SQLite under macOS might be too old? |
Unexpectedly, SQLite requires version 3.36 (3.35 in the doc, but it had some bugs in it) for the drop column feature, I am adding it to the prechecks of the test. |
0607ab4
to
d830008
Compare
@vadz can you merge the PR if everything is good ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the (unrelated, AFAICS) commit refactoring values
code broke Oracle tests, could you please check this? I'd be fine with just excluding this commit from this PR too (and would actually prefer it).
TIA!
.gitignore
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm not going to merge this. I don't think we should encourage building in the source directory.
tests/sqlite3/test-sqlite3.cpp
Outdated
#if SQLITE_VERSION_NUMBER < 3036000 | ||
#if SQLITE_VERSION_NUMBER < 3014000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't those be run-time, rather than compile-time, tests?
commit removed & compile checks replaced with runtime checks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update but the build needs to be fixed now.
tests/sqlite3/test-sqlite3.cpp
Outdated
// Test the DDL and metadata functionality | ||
TEST_CASE("SQLite DDL with metadata", "[sqlite][ddl]") | ||
{ | ||
if (sqlite3_libversion_number() < 3036000) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know why this doesn't compile in the CI builds, maybe sqlite.h
is not included from here?
We could wrap this function at the backend level or just do select sqlite_version();
here perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is a namespace wrapper in the inclusion of the sqlite header, I will just add the namespace and do your proposed change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested the compil and test on my side, it is now working as intended.
Thanks! Will merge in a moment. |
As SQLite doesn't have a dedicated table scheme we use the associated pragma to get those info.
We defer the column description statement preparation to the backend to take into account others data structures.