From ca10f483476aaa5774af015d5f5235d7b87253df Mon Sep 17 00:00:00 2001 From: Thibault FRESNET Date: Thu, 19 Oct 2023 01:14:56 +0200 Subject: [PATCH] Add query for column_info in sqlite3 backend --- .gitignore | 10 ++ include/soci/sqlite3/soci-sqlite3.h | 15 ++ tests/sqlite3/test-sqlite3.cpp | 227 ++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) diff --git a/.gitignore b/.gitignore index 4999550c2..e8baf9eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/include/soci/sqlite3/soci-sqlite3.h b/include/soci/sqlite3/soci-sqlite3.h index 2520dfd4b..a494a7ee0 100644 --- a/include/soci/sqlite3/soci-sqlite3.h +++ b/include/soci/sqlite3/soci-sqlite3.h @@ -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 { diff --git a/tests/sqlite3/test-sqlite3.cpp b/tests/sqlite3/test-sqlite3.cpp index dcd34dc10..880d3d801 100644 --- a/tests/sqlite3/test-sqlite3.cpp +++ b/tests/sqlite3/test-sqlite3.cpp @@ -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);