Skip to content

Commit

Permalink
Add query for column_info in sqlite3 backend
Browse files Browse the repository at this point in the history
  • Loading branch information
Sildra committed Oct 18, 2023
1 parent 2e4b44b commit 4b0883c
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 0 deletions.
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',"
" 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 (sqlite3_libversion_number() < 3036000) {
if (sqlite3_libversion_number() < 3014000) {
WARN("SQLite requires at least version 3.14.0 for column description, detected " SQLITE_VERSION);
}
WARN("SQLite requires at least version 3.36.0 for drop column, detected " SQLITE_VERSION);
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

0 comments on commit 4b0883c

Please sign in to comment.