Skip to content
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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,31 @@ tmp

# Files generated by CMake
Makefile
include/soci/soci-config.h
src/core/soci_backends_config.h
tests/odbc/test-access.dsn
tests/odbc/test-mysql.dsn
tests/odbc/test-mssql.dsn
*.dir/

# ... and the rest of CMake spam
CMakeFiles/
CMakeCache.txt
CTestTestfile.cmake
cmake_install.cmake
src/core/SOCIConfig.cmake
src/core/SOCIConfigVersion.cmake

# Visual Studio / Visual Studio Code
*.opensdf
*.sdf
*.suo
/*.vs*
*.vcxproj*
SOCI.sln
bin/
lib/
x64/

# KDevelop
*.kate-swp
Expand Down
15 changes: 15 additions & 0 deletions include/soci/sqlite3/soci-sqlite3.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_descriptions_query() const override
{
return "select name as 'COLUMN_NAME',"
Copy link
Member

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"(...)").

Copy link
Contributor Author

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.

Copy link
Contributor Author

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 :(

Copy link
Contributor Author

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.

" 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 type"
" end as 'DATA_TYPE',"
" case when \"notnull\" = 0 then 'YES' else 'NO' end as 'IS_NULLABLE'"
" from (select name, lower(type) as type, \"notnull\" from pragma_table_info(:t))";
}
std::string create_column_type(data_type dt,
int , int ) override
{
Expand Down
227 changes: 227 additions & 0 deletions tests/sqlite3/test-sqlite3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,233 @@ TEST_CASE("SQLite long long", "[sqlite][longlong]")
CHECK(v2 == v1);
}

// Test the DDL and metadata functionality
TEST_CASE("SQLite DDL with metadata", "[sqlite][ddl]")
{
if (sqlite_api::sqlite3_libversion_number() < 3036000) {
if (sqlite_api::sqlite3_libversion_number() < 3014000) {
WARN("SQLite requires at least version 3.14.0 for column description, detected " << sqlite_api::sqlite3_libversion());
}
WARN("SQLite requires at least version 3.36.0 for drop column, detected " << sqlite_api::sqlite3_libversion());
return;
}
soci::session sql(backEnd, connectString);

// note: prepare_column_descriptions expects l-value
std::string ddl_t1 = "DDL_T1";
std::string ddl_t2 = "DDL_T2";
std::string ddl_t3 = "DDL_T3";

// single-expression variant:
sql.create_table(ddl_t1).column("I", soci::dt_integer).column("J", soci::dt_integer);

// check whether this table was created:

bool ddl_t1_found = false;
bool ddl_t2_found = false;
bool ddl_t3_found = false;
std::string table_name;
soci::statement st = (sql.prepare_table_names(), into(table_name));
st.execute();
while (st.fetch())
{
if (table_name == ddl_t1) { ddl_t1_found = true; }
if (table_name == ddl_t2) { ddl_t2_found = true; }
if (table_name == ddl_t3) { ddl_t3_found = true; }
}

CHECK(ddl_t1_found);
CHECK(ddl_t2_found == false);
CHECK(ddl_t3_found == false);

// check whether ddl_t1 has the right structure:

bool i_found = false;
bool j_found = false;
bool other_found = false;
soci::column_info ci;
soci::statement st1 = (sql.prepare_column_descriptions(ddl_t1), into(ci));
st1.execute();
while (st1.fetch())
{
if (ci.name == "I")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
i_found = true;
}
else if (ci.name == "J")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
j_found = true;
}
else
{
other_found = true;
}
}

CHECK(i_found);
CHECK(j_found);
CHECK(other_found == false);

// two more tables:

// separately defined columns:
// (note: statement is executed when ddl object goes out of scope)
{
soci::ddl_type ddl = sql.create_table(ddl_t2);
ddl.column("I", soci::dt_integer);
ddl.column("J", soci::dt_integer);
ddl.column("K", soci::dt_integer)("not null");
ddl.primary_key("t2_pk", "J");
}

sql.add_column(ddl_t1, "K", soci::dt_integer);
sql.add_column(ddl_t1, "BIG", soci::dt_string, 0); // "unlimited" length -> CLOB
sql.drop_column(ddl_t1, "I");

// or with constraint as in t2:
sql.add_column(ddl_t2, "M", soci::dt_integer)("not null");

// third table with a foreign key to the second one
{
soci::ddl_type ddl = sql.create_table(ddl_t3);
ddl.column("X", soci::dt_integer);
ddl.column("Y", soci::dt_integer);
ddl.foreign_key("t3_fk", "X", ddl_t2, "J");
}

// check if all tables were created:

ddl_t1_found = false;
ddl_t2_found = false;
ddl_t3_found = false;
soci::statement st2 = (sql.prepare_table_names(), into(table_name));
st2.execute();
while (st2.fetch())
{
if (table_name == ddl_t1) { ddl_t1_found = true; }
if (table_name == ddl_t2) { ddl_t2_found = true; }
if (table_name == ddl_t3) { ddl_t3_found = true; }
}

CHECK(ddl_t1_found);
CHECK(ddl_t2_found);
CHECK(ddl_t3_found);

// check if ddl_t1 has the right structure (it was altered):

i_found = false;
j_found = false;
bool k_found = false;
bool big_found = false;
other_found = false;
soci::statement st3 = (sql.prepare_column_descriptions(ddl_t1), into(ci));
st3.execute();
while (st3.fetch())
{
if (ci.name == "J")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
j_found = true;
}
else if (ci.name == "K")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
k_found = true;
}
else if (ci.name == "BIG")
{
CHECK(ci.type == soci::dt_string);
CHECK(ci.precision == 0); // "unlimited" for strings
big_found = true;
}
else
{
other_found = true;
}
}

CHECK(i_found == false);
CHECK(j_found);
CHECK(k_found);
CHECK(big_found);
CHECK(other_found == false);

// check if ddl_t2 has the right structure:

i_found = false;
j_found = false;
k_found = false;
bool m_found = false;
other_found = false;
soci::statement st4 = (sql.prepare_column_descriptions(ddl_t2), into(ci));
st4.execute();
while (st4.fetch())
{
if (ci.name == "I")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
i_found = true;
}
else if (ci.name == "J")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable == true); // primary key -> SQLite default behavior
j_found = true;
}
else if (ci.name == "K")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable == false);
k_found = true;
}
else if (ci.name == "M")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable == false);
m_found = true;
}
else
{
other_found = true;
}
}

CHECK(i_found);
CHECK(j_found);
CHECK(k_found);
CHECK(m_found);
CHECK(other_found == false);

sql.drop_table(ddl_t1);
sql.drop_table(ddl_t3); // note: this must be dropped before ddl_t2
sql.drop_table(ddl_t2);

// check if all tables were dropped:

ddl_t1_found = false;
ddl_t2_found = false;
ddl_t3_found = false;
st2 = (sql.prepare_table_names(), into(table_name));
st2.execute();
while (st2.fetch())
{
if (table_name == ddl_t1) { ddl_t1_found = true; }
if (table_name == ddl_t2) { ddl_t2_found = true; }
if (table_name == ddl_t3) { ddl_t3_found = true; }
}

CHECK(ddl_t1_found == false);
CHECK(ddl_t2_found == false);
CHECK(ddl_t3_found == false);
}

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