file sqliteprinter/sqlitereader.cpp

[No description available] More…

Namespaces

Name
Gambit
TODO: see if we can use this one:
Gambit::Printers
Forward declaration.

Defines

Name
SQL_DEBUG
GET_SQL_TYPE_CASES(r, data, elem)

Detailed Description

Author: Ben Farmer (b.farmer@imperial.ac.uk))

Date: 2018 Dec

SQLite printer retriever class definitions This is a class accompanying the SQLitePrinter which takes care of reading from output created by the SQLitePrinter.


Authors (add name and date if you modify):


Macros Documentation

define SQL_DEBUG

#define SQL_DEBUG 

define GET_SQL_TYPE_CASES

#define GET_SQL_TYPE_CASES(
    r,
    data,
    elem
)
if( SQLite_equaltypes(coltype,cpp2sql<elem>()) )\
         { \
             typeID = getTypeID<elem>(); \
         } \
         else

Source code

//   GAMBIT: Global and Modular BSM Inference Tool
//   *********************************************
///  \file
///
///  SQLite printer retriever class definitions
///  This is a class accompanying the SQLitePrinter
///  which takes care of *reading* from output
///  created by the SQLitePrinter.
///
///  *********************************************
///
///  Authors (add name and date if you modify):
///
///  \author Ben Farmer
///          (b.farmer@imperial.ac.uk))
///  \date 2018 Dec
///
///  *********************************************

#include "gambit/Utils/boost_fallbacks.hpp"
#include "gambit/Printers/printers/sqlitereader.hpp"
#include "gambit/Utils/util_functions.hpp"

// Activate extra debug output on errors
#define SQL_DEBUG

namespace Gambit
{
  namespace Printers
  {
     
     SQLiteReader::SQLiteReader(const Options& options)
     : BaseReader()
     , SQLiteBase()
     , stmt(NULL)
     , current_dataset_index(0)
     , current_point(nullpoint)
     {
        // Open the database file 
        open_db(options.getValue<std::string>("file"),'r');
 
        // Tell the base class what table we want to access
        set_table_name(options.getValue<std::string>("table"));
      
        // Make sure that the requested table exists
        check_table_exists();

        // Determine the types of all columns in the input table
        column_types = get_column_info();
        build_column_map();        

        // Set up the reader loop
        reset();
     }

     SQLiteReader::~SQLiteReader()
     {
         if(not eoi() and stmt!=NULL)
         {
             // Finalise any existing loop
             sqlite3_finalize(stmt); 
         }
     }

     /// @{ Template function specialisations for retrieving SQLite column data as various types
     template<>
     long long int SQLiteReader::get_sql_col<long long int>(const std::string& col_name)
     {
         return sqlite3_column_int64(stmt, get_col_i(col_name)); 
     }

     template<>
     double SQLiteReader::get_sql_col<double>(const std::string& col_name)
     {
         return sqlite3_column_double(stmt, get_col_i(col_name)); 
     }

     template<>
     std::string SQLiteReader::get_sql_col<std::string>(const std::string& col_name)
     {
         char* p = (char*)sqlite3_column_text(stmt, get_col_i(col_name));
         if(p==NULL)
         {
             std::stringstream err;
             err<<"Pointer returned by sqlite3_column_text was NULL!";
             printer_error().raise(LOCAL_INFO, err.str());
         }
         return std::string(p); 
     }
     /// @}

     // Determine mapping from column names to indices
     void SQLiteReader::build_column_map()
     {
         // We will SELECT the columns according to the results of get_column_info,
         // so there is no need to directly inspect the table again.
         require_output_ready();
         column_map.clear();
         std::size_t i=0;
         for(auto it=column_types.begin(); it!=column_types.end(); ++it)
         {
             std::string col_name = it->first;
             column_map[col_name] = i;
             i++; 
         }
     }

     /// @{ Base class virtual interface functions

     /// Reset 'read head' position to first entry
     void SQLiteReader::reset()
     {
         if(not eoi() and stmt!=NULL)
         {
             // Finalise the previous loop so we can start a new one
             sqlite3_finalize(stmt);
         }

         // Reset loop variables
         current_dataset_index = 0;
         current_point = nullpoint;
         eoi_flag = false;

         std::stringstream sql;
         sql<<"SELECT ";
         for(auto it=column_types.begin(); it!=column_types.end(); ++it)
         {
            std::string col_name = it->first;
            sql<<"`"<<col_name<<"`"<<comma_unless_last(it,column_types);
         }  
         sql<<" FROM "<<get_table_name();
    
         /* Execute SQL statement and iterate through results*/ 
         int rc = sqlite3_prepare_v2(get_db(), sql.str().c_str(), -1, &stmt, NULL);
         if (rc != SQLITE_OK) {
             std::stringstream err;
             err<<"Encountered SQLite error while preparing to read data from previous run: "<<sqlite3_errmsg(get_db());
#ifdef SQL_DEBUG
             err << "  The attempted SQL statement was:"<<std::endl;
             err << sql.str() << std::endl; 
#endif
             printer_error().raise(LOCAL_INFO, err.str());
         }

         // Read first row
         move_to_next_point();

         if(eoi())
         {
             std::stringstream err;
             err<<"Immediately reached end of input after beginning to loop through table "<<get_table_name()<<" in file "<<get_database_file()<<"! Perhaps the table is empty?";
             printer_error().raise(LOCAL_INFO, err.str());
         }
     }

     /// Safely access the column_map and throw informative error when column is missing
     std::size_t SQLiteReader::get_col_i(const std::string& col_name)
     {
         auto it = column_map.find(col_name);
         if(it==column_map.end())
         {
             std::stringstream err;
             err<<"Attempted to retrieve data for column with name '"<<col_name<<"' using SQLiteReader, however this column does not seem to exist in the table we are reading!"; 
             printer_error().raise(LOCAL_INFO, err.str());
         }
         return it->second;
     }


     /// Get length of input dataset
     ulong SQLiteReader::get_dataset_length()
     {
         std::stringstream sql;
         sql<<"SELECT COUNT(pairID) FROM "<<get_table_name()<<";";
         sqlite3_stmt *temp_stmt;
         int rc = sqlite3_prepare_v2(get_db(), sql.str().c_str(), -1, &temp_stmt, NULL);
         if (rc != SQLITE_OK) {
             std::stringstream err;
             err<<"Encountered SQLite error while preparing to measure length of input table: "<<sqlite3_errmsg(get_db());
             printer_error().raise(LOCAL_INFO, err.str());
         }
         rc = sqlite3_step(temp_stmt);
         cout_row(temp_stmt); // DEBUG
         if (rc != SQLITE_ROW) {
             std::stringstream err;
             err<<"Encountered SQLite error while attempting to measure length of input table: "<<sqlite3_errmsg(get_db());
             printer_error().raise(LOCAL_INFO, err.str());
         }
         long long int rowcount = sqlite3_column_int64(temp_stmt, 0);
         sqlite3_finalize(temp_stmt);
         if(rowcount<0)
         {
             std::stringstream err;
             err<<"Row count for input table was measured to be negative ("<<rowcount<<")! This clearly makes no sense and is probably a bug in the SQLiteReader class, please report it.";
             printer_error().raise(LOCAL_INFO, err.str());   
         }
         return rowcount; 
     }

     /// Move the SQL loop ahead one
     void SQLiteReader::move_to_next_point()
     {
         if(eoi())
         {
             std::stringstream err;
             err<<"Attempted to move SQLiteReader to next row of input table, but eoi() has been reached! This should have been checked by whatever code called this function!";
             printer_error().raise(LOCAL_INFO, err.str());
         }
         else if(stmt==NULL)
         {
             std::stringstream err;
             err<<"Attempted to move SQLiteReader to next row of input table, but no sql statement has been prepared for iteration!";
             printer_error().raise(LOCAL_INFO, err.str()); 
         }
         else
         {
             // Process the next row
             int rc = sqlite3_step(stmt);
             if(rc==SQLITE_ROW)
             {
                 std::size_t rank = sqlite3_column_int64(stmt, get_col_i("MPIrank"));
                 std::size_t pID  = sqlite3_column_int64(stmt, get_col_i("pointID"));
                 current_point = PPIDpair(pID,rank);
             }
             else if(rc==SQLITE_DONE)
             {
                 // We are at the end of the dataset!
                 eoi_flag = true;
                 current_point = nullpoint;
                 sqlite3_finalize(stmt);
             }
             else
             {
                 // Not the next row, and not the end, something bad has happened.
                 std::stringstream err;
                 err<<"Encountered SQLite error while processing input file: "<<sqlite3_errmsg(get_db());
                 printer_error().raise(LOCAL_INFO, err.str());
             }
         }
     }

     /// Get next rank/ptID pair in data file
     PPIDpair SQLiteReader::get_next_point()
     {
        move_to_next_point();
        ++current_dataset_index;
        return get_current_point();
     }

     /// Get current rank/ptID pair in data file
     PPIDpair SQLiteReader::get_current_point()
     {
        if(eoi())
        {
          // End of data, return nullpoint;
          current_point = nullpoint;
        }
        return current_point;
     }

     // Get a linear index which corresponds to the current rank/ptID pair in the iterative sense
     ulong SQLiteReader::get_current_index()
     {
       return current_dataset_index;
     }

     /// Check if 'current point' is past the end of the datasets (and thus invalid!)
     bool SQLiteReader::eoi()
     {
        return eoi_flag;
     }

     /// Get type information for a data entry, i.e. defines the C++ type which this should be
     /// retrieved as, not what it is necessarily literally stored as in the output.
     std::size_t SQLiteReader::get_type(const std::string& label)
     {
         // Need to match SQL datatype to a printer type ID code.
         // In principle we may like to retrieve a certain type of data in a fancy way,
         // as with ModelParameters or vectors, however we can't really do that in an
         // automated way because this higher-level information is lost during output.
         // So the type matching has to be of a basic sort, i.e. individual ModelParameters
         // elements will be identified as 'double' and so on. But if they are stored that
         // way in the output, then we should be able to copy them that way too (which is
         // the main usage of this get_type function), so this should be ok to do.
         // Currently we only store data in basic types, so those are all that this
         // function needs to retrieve.

         // First find out the SQL type for the column with this label
         auto it = column_types.find(label);
         if(it==column_types.end())
         {
             std::stringstream err;
             err<<"Column with name '"<<label<<"' is not registered in the column_types map!";
             printer_error().raise(LOCAL_INFO,err.str());
         }
         std::string coltype=it->second;

         if(coltype=="NULL")
         {
             std::stringstream err;
             err<<"Column with name '"<<label<<"' is registered as having type 'NULL'! This doesn't make sense, only individual table slots with missing data should have type NULL, it should not be the 'affinity' for a whole column. This indicates a bug in the SQLiteReader object, please report it";
             printer_error().raise(LOCAL_INFO,err.str()); 
         }

         // Now match the SQL datatypes to Printer type IDs (via appropriate C++ types)
         // TODO might need more careful checking, not sure if e.g. INTEGER type name will
         // always be retrieved as INTERGER (as opposed to say INT or something else)
         std::size_t typeID=0;
         #define GET_SQL_TYPE_CASES(r,data,elem) \
         if( SQLite_equaltypes(coltype,cpp2sql<elem>()) )\
         { \
             typeID = getTypeID<elem>(); \
         } \
         else
         BOOST_PP_SEQ_FOR_EACH(GET_SQL_TYPE_CASES, _, SQLITE_CPP_TYPES)
         #undef GET_SQL_TYPE_CASES
         {
             std::ostringstream err;
             err << "Did not recognise retrieved SQL type for data label '"<<label<<"' (its SQL type is registered as '"<<coltype<<"')! This may indicate a bug in the SQLiteReader class, please report it.";
             printer_error().raise(LOCAL_INFO,err.str());
         }
         if(typeID==0)
         {
             std::ostringstream err;
             err << "Did not recognise retrieved Printer type for data label '"<<label<<"' (its SQL type is registered as '"<<coltype<<"')! This may indicate a bug in the Printer system, please report it.";
             printer_error().raise(LOCAL_INFO,err.str());
         }
         return typeID;
     }

     /// Get labels of all datasets in the linked group
     std::set<std::string> SQLiteReader::get_all_labels()
     {
         std::set<std::string> out;
         for (auto it = column_map.begin(); it != column_map.end(); ++it)
         {
             out.insert(it->first);
         }
         return out;
     }

     /// @}

  }
}

Updated on 2024-07-18 at 13:53:33 +0000