diff --git a/src/core/statement.cpp b/src/core/statement.cpp index 7bf5e777c..6f5f3cd09 100644 --- a/src/core/statement.cpp +++ b/src/core/statement.cpp @@ -322,6 +322,17 @@ bool statement_impl::execute(bool withDataExchange) statement_backend::exec_fetch_result res = backEnd_->execute(num); + // another hack related to description: the first call to describe() + // above may not have done anything if we didn't have the correct + // number of columns before calling execute() as happens with at least + // the ODBC backend for some complex queries (see #1151), so call it + // again in this case + if (row_ != NULL && alreadyDescribed_ == false) + { + describe(); + define_for_row(); + } + bool gotData = false; if (res == statement_backend::ef_success) @@ -708,6 +719,13 @@ void statement_impl::describe() row_->clean_up(); int const numcols = backEnd_->prepare_for_describe(); + if (!numcols) + { + // Return without setting alreadyDescribed_ to true, we'll be called + // again in this case. + return; + } + for (int i = 1; i <= numcols; ++i) { db_type dbtype; diff --git a/tests/odbc/test-odbc-mssql.cpp b/tests/odbc/test-odbc-mssql.cpp index db7de0577..3c04c26ec 100644 --- a/tests/odbc/test-odbc-mssql.cpp +++ b/tests/odbc/test-odbc-mssql.cpp @@ -77,6 +77,68 @@ TEST_CASE("MS SQL long string", "[odbc][mssql][long]") ); } +TEST_CASE("MS SQL table records count", "[odbc][mssql][count]") +{ + soci::session sql(backEnd, connectString); + + // Execute the provided SQL query to count records in tables + std::string sql_query = R"( + SET NOCOUNT ON; + DECLARE db_cursor CURSOR FOR + SELECT name FROM sys.databases + WHERE state_desc = 'ONLINE' + AND name IN ('master'); + DECLARE @DatabaseName NVARCHAR(128); + DECLARE @outset TABLE( + INSTANCENAME varchar(50), + DATABASENAME varchar(100), + TABLENAME varchar(100), + NUMBEROFRECORDS_I bigint + ); + OPEN db_cursor; + FETCH NEXT FROM db_cursor INTO @DatabaseName; + WHILE @@FETCH_STATUS = 0 + BEGIN + DECLARE @command nvarchar(1000) = 'USE ' + QUOTENAME(@DatabaseName) + + '; SELECT @@SERVERNAME, DB_NAME(), T.NAME, P.[ROWS] FROM sys.tables T ' + + 'INNER JOIN sys.indexes I ON T.OBJECT_ID = I.OBJECT_ID ' + + 'INNER JOIN sys.partitions P ON I.OBJECT_ID = P.OBJECT_ID AND I.INDEX_ID = P.INDEX_ID ' + + 'INNER JOIN sys.allocation_units A ON P.PARTITION_ID = A.CONTAINER_ID ' + + 'WHERE T.NAME NOT LIKE ''DT%'' AND I.OBJECT_ID > 255 AND I.INDEX_ID <= 1 ' + + 'GROUP BY T.NAME, I.OBJECT_ID, I.INDEX_ID, I.NAME, P.[ROWS] ' + + 'ORDER BY OBJECT_NAME(I.OBJECT_ID)'; + INSERT INTO @outset EXEC (@command) + FETCH NEXT FROM db_cursor INTO @DatabaseName + END + CLOSE db_cursor + DEALLOCATE db_cursor + SELECT INSTANCENAME, DATABASENAME, TABLENAME, NUMBEROFRECORDS_I + FROM @outset; + )"; + + soci::rowset rs = (sql.prepare << sql_query); + + // Check that we can access the results. + for (auto it = rs.begin(); it != rs.end(); ++it) + { + soci::row const& row = *it; + std::string instance_name = row.get(0); + std::string database_name = row.get(1); + std::string table_name = row.get(2); + long long number_of_records = row.get(3); + + // Use the variables above to avoid warnings about unused variables and + // check the only one of them that we can be sure about because we have + // "name IN ('master')" in the SQL query above. + INFO("Table " << instance_name << "." << table_name << + " has " << number_of_records << " records"); + CHECK( database_name == "master" ); + return; + } + + FAIL("No tables found in the master database"); +} + // DDL Creation objects for common tests struct table_creator_one : public table_creator_base {