gnucash/libgnucash/backend/dbi/gnc-dbiproviderimpl.hpp
John Ralls 1116ce909b Bug 796967 - gnclock table not removed when using PostgreSQL.
Because of https://sourceforge.net/p/libdbi-drivers/bugs/24.
This issue causes trouble in save_may_clobber_data() as well, so
work around it by using a SQL query instead of dbi_conn_get_table_list.
2018-12-28 13:22:40 -08:00

391 lines
12 KiB
C++

/************************************************************************
* gnc-dbiproviderimpl.hpp: Encapsulate differences among Dbi backends. *
* *
* Copyright 2016 John Ralls <jralls@ceridwen.us> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\***********************************************************************/
#ifndef __GNC_DBISQLPROVIDERIMPL_HPP__
#define __GNC_DBISQLPROVIDERIMPL_HPP__
#include <guid.hpp>
extern "C"
{
#include <config.h>
}
#include <string>
#include <algorithm>
#include <vector>
#include "gnc-backend-dbi.hpp"
#include "gnc-dbiprovider.hpp"
#include "gnc-backend-dbi.h"
#include <gnc-sql-column-table-entry.hpp>
using StrVec = std::vector<std::string>;
template <DbType T>
class GncDbiProviderImpl : public GncDbiProvider
{
public:
StrVec get_table_list(dbi_conn conn, const std::string& table);
void append_col_def(std::string& ddl, const GncSqlColumnInfo& info);
StrVec get_index_list (dbi_conn conn);
void drop_index(dbi_conn conn, const std::string& index);
};
template <DbType T> GncDbiProviderPtr
make_dbi_provider()
{
return GncDbiProviderPtr(new GncDbiProviderImpl<T>);
}
template<> void
GncDbiProviderImpl<DbType::DBI_SQLITE>::append_col_def(std::string& ddl,
const GncSqlColumnInfo& info)
{
const char* type_name = nullptr;
if (info.m_type == BCT_INT)
{
type_name = "integer";
}
else if (info.m_type == BCT_INT64)
{
type_name = "bigint";
}
else if (info.m_type == BCT_DOUBLE)
{
type_name = "float8";
}
else if (info.m_type == BCT_STRING || info.m_type == BCT_DATE
|| info.m_type == BCT_DATETIME)
{
type_name = "text";
}
else
{
PERR ("Unknown column type: %d\n", info.m_type);
type_name = "";
}
ddl += (info.m_name + " " + type_name);
if (info.m_size != 0)
{
ddl += "(" + std::to_string(info.m_size) + ")";
}
if (info.m_primary_key)
{
ddl += " PRIMARY KEY";
}
if (info.m_autoinc)
{
ddl += " AUTOINCREMENT";
}
if (info.m_not_null)
{
ddl += " NOT NULL";
}
}
template<> void
GncDbiProviderImpl<DbType::DBI_MYSQL>::append_col_def (std::string& ddl,
const GncSqlColumnInfo& info)
{
const char* type_name = nullptr;
if (info.m_type == BCT_INT)
{
type_name = "integer";
}
else if (info.m_type == BCT_INT64)
{
type_name = "bigint";
}
else if (info.m_type == BCT_DOUBLE)
{
type_name = "double";
}
else if (info.m_type == BCT_STRING)
{
type_name = "varchar";
}
else if (info.m_type == BCT_DATE)
{
type_name = "date";
}
else if (info.m_type == BCT_DATETIME)
{
type_name = "DATETIME NULL DEFAULT '1970-01-01 00:00:00'";
}
else
{
PERR ("Unknown column type: %d\n", info.m_type);
type_name = "";
}
ddl += info.m_name + " " + type_name;
if (info.m_size != 0 && info.m_type == BCT_STRING)
{
ddl += "(" + std::to_string(info.m_size) + ")";
}
if (info.m_unicode)
{
ddl += " CHARACTER SET utf8";
}
if (info.m_primary_key)
{
ddl += " PRIMARY KEY";
}
if (info.m_autoinc)
{
ddl += " AUTO_INCREMENT";
}
if (info.m_not_null)
{
ddl += " NOT NULL";
}
}
template<> void
GncDbiProviderImpl<DbType::DBI_PGSQL>::append_col_def (std::string& ddl,
const GncSqlColumnInfo& info)
{
const char* type_name = nullptr;
if (info.m_type == BCT_INT)
{
if (info.m_autoinc)
{
type_name = "serial";
}
else
{
type_name = "integer";
}
}
else if (info.m_type == BCT_INT64)
{
type_name = "int8";
}
else if (info.m_type == BCT_DOUBLE)
{
type_name = "double precision";
}
else if (info.m_type == BCT_STRING)
{
type_name = "varchar";
}
else if (info.m_type == BCT_DATE)
{
type_name = "date";
}
else if (info.m_type == BCT_DATETIME)
{
type_name = "timestamp without time zone";
}
else
{
PERR ("Unknown column type: %d\n", info.m_type);
type_name = "";
}
ddl += info.m_name + " " + type_name;
if (info.m_size != 0 && info.m_type == BCT_STRING)
{
ddl += "(" + std::to_string(info.m_size) + ")";
}
if (info.m_primary_key)
{
ddl += " PRIMARY KEY";
}
if (info.m_not_null)
{
ddl += " NOT NULL";
}
}
static StrVec
conn_get_table_list (dbi_conn conn, const std::string& dbname,
const std::string& table)
{
StrVec retval;
const char* tableptr = (table.empty() ? nullptr : table.c_str());
auto tables = dbi_conn_get_table_list (conn, dbname.c_str(), tableptr);
while (dbi_result_next_row (tables) != 0)
{
std::string table_name {dbi_result_get_string_idx (tables, 1)};
retval.push_back(table_name);
}
dbi_result_free (tables);
return retval;
}
template<> StrVec
GncDbiProviderImpl<DbType::DBI_SQLITE>::get_table_list (dbi_conn conn,
const std::string& table)
{
/* Return the list, but remove the tables that sqlite3 adds for
* its own use. */
std::string dbname (dbi_conn_get_option (conn, "dbname"));
auto list = conn_get_table_list (conn, dbname, table);
auto end = std::remove(list.begin(), list.end(), "sqlite_sequence");
list.erase(end, list.end());
return list;
}
template<> StrVec
GncDbiProviderImpl<DbType::DBI_MYSQL>::get_table_list (dbi_conn conn,
const std::string& table)
{
std::string dbname (dbi_conn_get_option (conn, "dbname"));
dbname.insert((std::string::size_type)0, 1, '`');
dbname += '`';
return conn_get_table_list (conn, dbname, table);
}
template<> StrVec
GncDbiProviderImpl<DbType::DBI_PGSQL>::get_table_list (dbi_conn conn,
const std::string& table)
{
const char* query_no_regex = "SELECT relname FROM pg_class WHERE relname"
"!~ '^(pg|sql)_' AND relkind = 'r' ORDER BY relname";
std::string query_with_regex = "SELECT relname FROM pg_class WHERE relname LIKE '";
query_with_regex += table + "' AND relkind = 'r' ORDER BY relname";
dbi_result result;
if (table.empty())
result = dbi_conn_query (conn, query_no_regex);
else
result = dbi_conn_query (conn, query_with_regex.c_str());
StrVec list;
const char* errmsg;
if (dbi_conn_error (conn, &errmsg) != DBI_ERROR_NONE)
{
PWARN ("Table List Retrieval Error: %s\n", errmsg);
return list;
}
while (dbi_result_next_row (result) != 0)
{
std::string index_name {dbi_result_get_string_idx (result, 1)};
list.push_back(index_name);
}
dbi_result_free (result);
return list;
}
template<> StrVec
GncDbiProviderImpl<DbType::DBI_SQLITE>::get_index_list (dbi_conn conn)
{
StrVec retval;
const char* errmsg;
dbi_result result = dbi_conn_query (conn,
"SELECT name FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_autoindex%'");
if (dbi_conn_error (conn, &errmsg) != DBI_ERROR_NONE)
{
PWARN ("Index Table Retrieval Error: %s\n", errmsg);
return retval;
}
while (dbi_result_next_row (result) != 0)
{
std::string index_name {dbi_result_get_string_idx (result, 1)};
retval.push_back(index_name);
}
dbi_result_free (result);
return retval;
}
template<> StrVec
GncDbiProviderImpl<DbType::DBI_MYSQL>::get_index_list (dbi_conn conn)
{
StrVec retval;
const char* errmsg;
auto tables = get_table_list(conn, "");
for (auto table_name : tables)
{
auto result = dbi_conn_queryf (conn,
"SHOW INDEXES IN %s WHERE Key_name != 'PRIMARY'",
table_name.c_str());
if (dbi_conn_error (conn, &errmsg) != DBI_ERROR_NONE)
{
PWARN ("Index Table Retrieval Error: %s on table %s\n",
errmsg, table_name.c_str());
continue;
}
while (dbi_result_next_row (result) != 0)
{
std::string index_name {dbi_result_get_string_idx (result, 3)};
retval.push_back(index_name + " " + table_name);
}
dbi_result_free (result);
}
return retval;
}
template<> StrVec
GncDbiProviderImpl<DbType::DBI_PGSQL>::get_index_list (dbi_conn conn)
{
StrVec retval;
const char* errmsg;
PINFO ("Retrieving postgres index list\n");
auto result = dbi_conn_query (conn,
"SELECT relname FROM pg_class AS a INNER JOIN pg_index AS b ON (b.indexrelid = a.oid) INNER JOIN pg_namespace AS c ON (a.relnamespace = c.oid) WHERE reltype = '0' AND indisprimary = 'f' AND nspname = 'public'");
if (dbi_conn_error (conn, &errmsg) != DBI_ERROR_NONE)
{
PWARN("Index Table Retrieval Error: %s\n", errmsg);
return retval;
}
while (dbi_result_next_row (result) != 0)
{
std::string index_name {dbi_result_get_string_idx (result, 1)};
retval.push_back(index_name);
}
dbi_result_free (result);
return retval;
}
template <DbType P> void
GncDbiProviderImpl<P>::drop_index(dbi_conn conn, const std::string& index)
{
dbi_result result = dbi_conn_queryf (conn, "DROP INDEX %s", index.c_str());
if (result)
dbi_result_free (result);
}
template<> void
GncDbiProviderImpl<DbType::DBI_MYSQL>::drop_index (dbi_conn conn, const std::string& index)
{
auto sep = index.find(' ', 0);
if (index.find(' ', sep + 1) != std::string::npos)
{
PWARN("Drop index error: invalid MySQL index format (<index> <table>): %s",
index.c_str());
return;
}
auto result = dbi_conn_queryf (conn, "DROP INDEX %s ON %s",
index.substr(0, sep).c_str(),
index.substr(sep + 1).c_str());
if (result)
dbi_result_free (result);
}
#endif //__GNC_DBISQLPROVIDERIMPL_HPP__