file sqliteprinter/sqlitebase.cpp
[No description available] More…
Namespaces
Name |
---|
Gambit TODO: see if we can use this one: |
Gambit::Printers Forward declaration. |
Defines
Name | |
---|---|
SQL_DEBUG |
Detailed Description
Author: Ben Farmer (b.farmer@imperial.ac.uk)
Date: 2018 Dec
SQLite printer/reader base class member function definitions
Authors (add name and date if you modify):
Macros Documentation
define SQL_DEBUG
#define SQL_DEBUG
Source code
// GAMBIT: Global and Modular BSM Inference Tool
// *********************************************
/// \file
///
/// SQLite printer/reader base class member function definitions
///
/// *********************************************
///
/// Authors (add name and date if you modify):
///
/// \author Ben Farmer
/// (b.farmer@imperial.ac.uk)
/// \date 2018 Dec
///
/// *********************************************
#include <iostream>
#include <sstream>
#include <chrono>
#include <thread>
// SQLite3 C interface
#include <sqlite3.h>
#include "gambit/Printers/printers/sqlitebase.hpp"
// Activate extra debugging output on error
#define SQL_DEBUG
namespace Gambit
{
namespace Printers
{
/* Callback function for retrieving the column names and types of a table
* Called for each row of the results table
*
* Arguments:
*
* list - Pointer to a map from column names to types
* count - The number of columns in the result set
* data - The row's data
* columns - The column names
*/
int col_name_callback(void* colmap_in, int /*count*/, char** data, char** /* columns */)
{
typedef std::map<std::string, std::string, Utils::ci_less> mymaptype;
mymaptype *colmap = static_cast<mymaptype*>(colmap_in);
// We know that the column name is the second column of the results set, and the
// type is the third column
std::string column_name(data[1]);
std::string column_type(data[2]);
//std::cout<<"Reading existing columns: name: "<<column_name<<", type: "<<column_type<<std::endl;
// Add to map
(*colmap)[column_name] = column_type;
return 0;
}
// Matching of SQL types to C++ types
template<> std::string cpp2sql<long long int>(){return "INTEGER";}
template<> std::string cpp2sql<double>() {return "REAL";}
template<> std::string cpp2sql<std::string>() {return "TEXT";}
// Matching of SQLite "type codes" to SQL types
// Names should match results of cpp2sql above
// Codes should match https://www.sqlite.org/c3ref/c_blob.html
std::map<unsigned int,std::string> define_typecodes()
{
std::map<unsigned int,std::string> out;
out[1] = "INTEGER";
out[2] = "FLOAT";
out[3] = "TEXT";
out[4] = "BLOB";
out[5] = "NULL";
return out;
}
const std::map<unsigned int,std::string> typecode2sql(define_typecodes());
// Map from all SQLite "suggestion" types into the five "base" SQLite types
// Used for type comparisons.
std::map<std::string,std::string,Utils::ci_less> fill_SQLtype_to_basic()
{
std::map<std::string,std::string,Utils::ci_less> out;
out["INT"] = "INTEGER";
out["INTEGER"] = "INTEGER";
out["TINYINT"] = "INTEGER";
out["SMALLINT"] = "INTEGER";
out["MEDIUMINT"] = "INTEGER";
out["BIGINT"] = "INTEGER";
out["UNSIGNED BIG INT"] = "INTEGER";
out["INT2"] = "INTEGER";
out["INT8"] = "INTEGER";
out["CHARACTER(20)"] = "TEXT";
out["VARCHAR(255)"] = "TEXT";
out["VARYING CHARACTER(255)"] = "TEXT";
out["NCHAR(55)"] = "TEXT";
out["NATIVE CHARACTER(70)"] = "TEXT";
out["NVARCHAR(100)"] = "TEXT";
out["CLOB"] = "TEXT";
out["TEXT"] = "TEXT";
out["BLOB"] = "NONE";
out["NONE"] = "NONE";
out["DOUBLE"] = "REAL";
out["DOUBLE PRECISION"] = "REAL";
out["FLOAT"] = "REAL";
out["REAL"] = "REAL";
out["DECIMAL(10,5)"] = "NUMERIC";
out["BOOLEAN"] = "NUMERIC";
out["DATE"] = "NUMERIC";
out["DATETIME"] = "NUMERIC";
out["NUMERIC"] = "NUMERIC";
return out;
}
const std::map<std::string,std::string,Utils::ci_less> SQLtype_to_basic(fill_SQLtype_to_basic());
// Compare SQLite data types to see if they are equivalent to the same basic 'affinity' for a column
bool SQLite_equaltypes(const std::string& type1, const std::string& type2)
{
// There are five "basic" types in SQLite, but many
// "aliases" for them. We will need to map each input string
// to its "basic" type, and then check if those are the same
// for each input type.
auto it1 = SQLtype_to_basic.find(type1);
auto it2 = SQLtype_to_basic.find(type2);
if(it1==SQLtype_to_basic.end())
{
std::stringstream err;
err<<"Could not determine a basic SQLite 'affinity' type for data type named '"<<type1<<"' (first argument to this type checking dunction)";
printer_error().raise(LOCAL_INFO,err.str());
}
if(it2==SQLtype_to_basic.end())
{
std::stringstream err;
err<<"Could not determine a basic SQLite 'affinity' type for data type named '"<<type2<<"' (second argument to this type checking dunction)";
printer_error().raise(LOCAL_INFO,err.str());
}
return (it1->second) == (it2->second);
}
// SQLite base class for both reader and writer (Constructor)
SQLiteBase::SQLiteBase()
: database_file("uninitialised")
, table_name("uninitialised")
, metadata_table_name("uninitialised")
, db(NULL)
, db_is_open(false)
, table_exists(false)
{}
// Destructor
SQLiteBase::~SQLiteBase()
{
close_db();
}
// Open database and 'attach' it to this object
void SQLiteBase::open_db(const std::string& path, char access)
{
// Check if we already have an open database
if(db!=NULL)
{
std::stringstream err;
err << "Refused to open database file '"<<path<<"'; a database file pointer has already been attached to the SQLite printer!";
printer_error().raise(LOCAL_INFO,err.str());
}
if(db_is_open)
{
std::stringstream err;
err << "Refused to open database file '"<<path<<"'; a database file is already flagged by the SQLite printer as open!";
printer_error().raise(LOCAL_INFO,err.str());
}
int sql_access;
switch(access) {
case 'r' :
sql_access = SQLITE_OPEN_READONLY;
break;
case 'w':
sql_access = SQLITE_OPEN_READWRITE;
break;
case '+':
sql_access = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
break;
}
int rc; // return code
//rc = sqlite3_open(path.c_str(), &db);
rc = sqlite3_open_v2(path.c_str(), &db, sql_access, NULL);
if( rc )
{
std::stringstream err;
err << "Failed to open database file '"<<path<<"' (are you sure it exists?). SQLite error was: " << sqlite3_errmsg(db);
printer_error().raise(LOCAL_INFO, err.str());
}
else
{
db_is_open = true;
database_file = path;
}
}
void SQLiteBase::close_db()
{
sqlite3_close(get_db()); // Check error code?
db_is_open = false;
}
sqlite3* SQLiteBase::get_db()
{
if(db==NULL)
{
std::stringstream err;
err << "Attempted to access SQLite database pointer, but it is NULL! This means that some SQLitePrinter/Reader routine called 'get_db()' before the database was opened (or after it was closed)! This is a bug, please report it.";
printer_error().raise(LOCAL_INFO,err.str());
}
return db;
}
// Make sure the outbase database is open and the results table exists
// Throws an error if it isn't
void SQLiteBase::require_output_ready()
{
if(db==NULL || !db_is_open || !table_exists)
{
std::stringstream err;
// Something was not ready, check further and throw an error
if(db==NULL)
{
err << "Output readiness check failed! Database pointer was NULL!";
printer_error().raise(LOCAL_INFO,err.str());
}
if(!db_is_open)
{
err << "Output readiness check failed! Database is not flagged as open!";
printer_error().raise(LOCAL_INFO,err.str());
}
if(!table_exists)
{
err << "Output readiness check failed! Results table is not flagged as existing!";
printer_error().raise(LOCAL_INFO,err.str());
}
}
// Else we are good to go!
}
// Function to repeatedly attempt an SQLite statement if the database is locked/busy
int SQLiteBase::submit_sql(const std::string& local_info, const std::string& sqlstr, bool allow_fail, sql_callback_fptr callback, void* data, char **zErrMsg_in)
{
int rc;
char *zErrMsg;
char **zErrMsg_ptr;
if(zErrMsg_in==NULL)
{
zErrMsg_ptr = &zErrMsg;
}
else
{
zErrMsg_ptr = zErrMsg_in;
}
do
{
rc = sqlite3_exec(get_db(), sqlstr.c_str(), callback, data, zErrMsg_ptr);
if(rc==SQLITE_BUSY)
{
// Wait at least a short time to avoid slamming the filesystem too much
std::chrono::milliseconds timespan(10);
std::this_thread::sleep_for(timespan);
}
}
while (rc == SQLITE_BUSY);
// if allow_fail is true then we don't catch this error, we allow the caller of this function to hander it.
if( (rc != SQLITE_OK) and not allow_fail ){
std::stringstream err;
err << "SQL error: " << *zErrMsg_ptr << std::endl;
#ifdef SQL_DEBUG
err << "The attempted SQL statement was:"<<std::endl;
err << sqlstr << std::endl;;
#endif
sqlite3_free(*zErrMsg_ptr);
printer_error().raise(local_info,err.str());
}
return rc;
}
// Get names and types for all columns in the target table
// (Output is a map from names to types)
std::map<std::string, std::string, Utils::ci_less> SQLiteBase::get_column_info()
{
std::stringstream sql;
sql<<"PRAGMA table_info("<<get_table_name()<<");";
/* Execute SQL statement */
int rc;
char *zErrMsg = 0;
std::map<std::string, std::string, Utils::ci_less> colnames; // Will be passed to and filled by the callback function
rc = submit_sql(LOCAL_INFO, sql.str(), true, &col_name_callback, &colnames, &zErrMsg);
if( rc != SQLITE_OK ){
std::stringstream err;
err << "Failed to retrieve information about SQL column names in target table!"<<std::endl;
err << " The attempted SQL statement was:"<<std::endl;
err << sql.str() << std::endl;
sqlite3_free(zErrMsg);
printer_error().raise(LOCAL_INFO,err.str());
}
return colnames;
}
std::string SQLiteBase::get_database_file() {return database_file;}
std::string SQLiteBase::get_table_name() {return table_name;}
std::string SQLiteBase::get_metadata_table_name() {return metadata_table_name;}
void SQLiteBase::set_table_name(const std::string& t) {table_name=t;}
void SQLiteBase::set_metadata_table_name(const std::string& t) {metadata_table_name=t;}
// For debugging: dump a row of a results table to std::cout
void SQLiteBase::cout_row(sqlite3_stmt* tmp_stmt)
{
int ncols = sqlite3_data_count(tmp_stmt);
for(int i=0; i<ncols; i++)
{
std::cout<<" "<<sqlite3_column_text(tmp_stmt, i)<<",";
}
}
// Check that the required table exists
// Sets 'table_exists' to true if successful, otherwise throws error
void SQLiteBase::check_table_exists()
{
std::stringstream sql;
sql<<"SELECT name FROM sqlite_master WHERE type='table' AND name='"<<get_table_name()<<"';";
std::size_t count(0);
/* Execute SQL statement and iterate through results*/
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 statement to check if table '"<<get_table_name()<<"' exists in file '"<<get_database_file()<<"': "<<sqlite3_errmsg(get_db());
printer_error().raise(LOCAL_INFO, err.str());
}
while ((rc = sqlite3_step(temp_stmt)) == SQLITE_ROW) {
count+=1; // Will be one row for each table with a name matching 'get_table_name()'. Should be 1 or 0.
}
if (rc != SQLITE_DONE) {
std::stringstream err;
err<<"Encountered SQLite error while checking if table '"<<get_table_name()<<"' exists in file '"<<get_database_file()<<": "<<sqlite3_errmsg(get_db());
printer_error().raise(LOCAL_INFO, err.str());
}
sqlite3_finalize(temp_stmt);
if(count==0)
{
std::stringstream err;
err<<"Requested input table '"<<get_table_name()<<"' could not be found in file '"<<get_database_file()<<"! Please check that the requested table name is correct!";
printer_error().raise(LOCAL_INFO, err.str());
}
else if(count>1)
{
std::stringstream err;
err<<"Weird error encountered while checking that input table '"<<get_table_name()<<"' exists in file '"<<get_database_file()<<". We apparently found "<<count<<" tables with this name! This doesn't make sense, so there is probably a bug in the SQLiteBase class, please report it.";
printer_error().raise(LOCAL_INFO, err.str());
}
// Else the table exists, no problem.
set_table_exists();
}
// Sets the 'table_exists' flag to true
void SQLiteBase::set_table_exists() {table_exists=true;}
}
}
Updated on 2024-07-18 at 13:53:33 +0000