mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-11-29 20:24:25 -06:00
577 lines
21 KiB
C++
577 lines
21 KiB
C++
/***********************************************************************\
|
|
* gnc-sql-column-table-entry.hpp: Column Specification for SQL Table. *
|
|
* *
|
|
* 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_SQL_COLUMN_TABLE_ENTRY_HPP__
|
|
#define __GNC_SQL_COLUMN_TABLE_ENTRY_HPP__
|
|
|
|
extern "C"
|
|
{
|
|
#include <qof.h>
|
|
}
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
|
|
#include "gnc-sql-result.hpp"
|
|
|
|
struct GncSqlColumnInfo;
|
|
using ColVec = std::vector<GncSqlColumnInfo>;
|
|
using PairVec = std::vector<std::pair<std::string, std::string>>;
|
|
using InstanceVec = std::vector<QofInstance*>;
|
|
using uint_t = unsigned int;
|
|
class GncSqlBackend;
|
|
|
|
/**
|
|
* Basic column type
|
|
*/
|
|
typedef enum
|
|
{
|
|
BCT_STRING,
|
|
BCT_INT,
|
|
BCT_INT64,
|
|
BCT_DATE,
|
|
BCT_DOUBLE,
|
|
BCT_DATETIME
|
|
} GncSqlBasicColumnType;
|
|
|
|
enum ColumnFlags : int
|
|
{
|
|
COL_NO_FLAG = 0,
|
|
COL_PKEY = 0x01, /**< The column is a primary key */
|
|
COL_NNUL = 0x02, /**< The column may not contain a NULL value */
|
|
COL_UNIQUE = 0x04, /**< The column must contain unique values */
|
|
COL_AUTOINC = 0x08 /**< The column is an auto-incrementing int */
|
|
};
|
|
|
|
// Type for conversion of db row to object.
|
|
enum GncSqlObjectType
|
|
{
|
|
CT_STRING,
|
|
CT_GUID,
|
|
CT_INT,
|
|
CT_INT64,
|
|
CT_TIME,
|
|
CT_GDATE,
|
|
CT_NUMERIC,
|
|
CT_DOUBLE,
|
|
CT_BOOLEAN,
|
|
CT_ACCOUNTREF,
|
|
CT_BUDGETREF,
|
|
CT_COMMODITYREF,
|
|
CT_LOTREF,
|
|
CT_TXREF,
|
|
CT_ADDRESS,
|
|
CT_BILLTERMREF,
|
|
CT_INVOICEREF,
|
|
CT_ORDERREF,
|
|
CT_OWNERREF,
|
|
CT_TAXTABLEREF
|
|
};
|
|
|
|
static inline std::string
|
|
quote_string(const std::string& str)
|
|
{
|
|
if (str == "NULL" || str == "null") return "NULL";
|
|
/* FIXME: This is here because transactions.num has a NULL
|
|
* constraint, which is dumb; it's often empty.
|
|
*/
|
|
if (str.empty()) return "''";
|
|
std::string retval;
|
|
retval.reserve(str.length() + 2);
|
|
retval.insert(0, 1, '\'');
|
|
for (auto c = str.begin(); c != str.end(); ++c)
|
|
{
|
|
if (*c == '\'')
|
|
retval += *c;
|
|
retval += *c;
|
|
}
|
|
retval += '\'';
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Contains all of the information required to copy information between an
|
|
* object and the database for a specific object property.
|
|
*
|
|
* If an entry contains a gobj_param_name value, this string is used as the
|
|
* property name for a call to g_object_get() or g_object_set(). If the
|
|
* gobj_param_name value is NULL but qof_param_name is not NULL, this value
|
|
* is used as the parameter name for a call to
|
|
* qof_class_get_parameter_getter(). If both of these values are NULL, getter
|
|
* and setter are the addresses of routines to return or set the parameter
|
|
* value, respectively.
|
|
*
|
|
* The database description for an object consists of an array of
|
|
* GncSqlColumnTableEntry objects, with a final member having col_name == NULL.
|
|
*/
|
|
|
|
class GncSqlColumnTableEntry
|
|
{
|
|
public:
|
|
GncSqlColumnTableEntry (const char* name, const GncSqlObjectType type,
|
|
unsigned int s,
|
|
int f, const char* gobj_name = nullptr,
|
|
const char* qof_name = nullptr,
|
|
QofAccessFunc get = nullptr,
|
|
QofSetterFunc set = nullptr) :
|
|
m_col_name{name}, m_col_type{type}, m_size{s},
|
|
m_flags{static_cast<ColumnFlags>(f)},
|
|
m_gobj_param_name{gobj_name}, m_qof_param_name{qof_name}, m_getter{get},
|
|
m_setter{set} {}
|
|
virtual ~GncSqlColumnTableEntry() = default;
|
|
|
|
/**
|
|
* Load a value into an object from the database row.
|
|
*/
|
|
virtual void load(const GncSqlBackend* sql_be, GncSqlRow& row,
|
|
QofIdTypeConst obj_name, void* pObject) const noexcept = 0;
|
|
/**
|
|
* Add a GncSqlColumnInfo structure for the column type to a
|
|
* ColVec.
|
|
*/
|
|
virtual void add_to_table(ColVec& vec) const noexcept = 0;
|
|
/**
|
|
* Add a pair of the table column heading and object's value's string
|
|
* representation to a PairVec; used for constructing WHERE clauses and
|
|
* UPDATE statements.
|
|
*/
|
|
virtual void add_to_query(QofIdTypeConst obj_name,
|
|
void* pObject, PairVec& vec) const noexcept = 0;
|
|
/**
|
|
* Retrieve the getter function depending on whether it's an auto-increment
|
|
* field, a QofClass getter, or a function passed to the constructor.
|
|
*/
|
|
QofAccessFunc get_getter(QofIdTypeConst obj_name) const noexcept;
|
|
/**
|
|
* Retrieve the setter function depending on whether it's an auto-increment
|
|
* field, a QofClass getter, or a function passed to the constructor.
|
|
*/
|
|
QofSetterFunc get_setter(QofIdTypeConst obj_name) const noexcept;
|
|
/**
|
|
* Retrieve the field name so that we don't need to make
|
|
* create_single_col_select_statement and friend.
|
|
*/
|
|
const char* name() const noexcept { return m_col_name; }
|
|
/**
|
|
* Report if the entry is an auto-increment field.
|
|
*/
|
|
bool is_autoincr() const noexcept { return m_flags & COL_AUTOINC; }
|
|
/* On the other hand, our implementation class and GncSqlColumnInfo need to
|
|
* be able to read our member variables.
|
|
*/
|
|
template<GncSqlObjectType Otype> friend class GncSqlColumnTableEntryImpl;
|
|
friend struct GncSqlColumnInfo;
|
|
template<typename T> void load_from_guid_ref(GncSqlRow& row,
|
|
QofIdTypeConst obj_name,
|
|
void* pObject, T get_ref)
|
|
const noexcept
|
|
{
|
|
g_return_if_fail (pObject != NULL);
|
|
|
|
try
|
|
{
|
|
GncGUID guid;
|
|
auto val = row.get_string_at_col (m_col_name);
|
|
if (string_to_guid (val.c_str(), &guid))
|
|
{
|
|
auto target = get_ref(&guid);
|
|
if (target != nullptr)
|
|
set_parameter (pObject, target, get_setter(obj_name),
|
|
m_gobj_param_name);
|
|
}
|
|
}
|
|
catch (std::invalid_argument&) {}
|
|
}
|
|
|
|
|
|
protected:
|
|
template <typename T> T
|
|
get_row_value_from_object(QofIdTypeConst obj_name, const void* pObject) const;
|
|
template <typename T> void
|
|
add_value_to_vec(QofIdTypeConst obj_name,
|
|
const void* pObject, PairVec& vec) const;
|
|
/**
|
|
* Adds a name/guid std::pair to a PairVec for creating a query.
|
|
*
|
|
* @param sql_be SQL backend struct
|
|
* @param obj_name QOF object type name
|
|
* @param pObject Object
|
|
* @param pList List
|
|
*/
|
|
void add_objectref_guid_to_query (QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
PairVec& vec) const noexcept;
|
|
/**
|
|
* Adds a column info structure for an object reference GncGUID to a ColVec.
|
|
*
|
|
* @param sql_be SQL backend struct
|
|
* @param pList List
|
|
*/
|
|
void add_objectref_guid_to_table (ColVec& vec) const noexcept;
|
|
private:
|
|
const char* m_col_name = nullptr; /**< Column name */
|
|
const GncSqlObjectType m_col_type; /**< Column type */
|
|
unsigned int m_size; /**< Column size in bytes, for string columns */
|
|
ColumnFlags m_flags; /**< Column flags */
|
|
const char* m_gobj_param_name = nullptr; /**< If non-null, g_object param name */
|
|
const char* m_qof_param_name = nullptr; /**< If non-null, qof parameter name */
|
|
QofAccessFunc m_getter; /**< General access function */
|
|
QofSetterFunc m_setter; /**< General setter function */
|
|
template <typename T> T get_row_value_from_object(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
std::true_type) const;
|
|
template <typename T> T get_row_value_from_object(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
std::false_type) const;
|
|
template <typename T> void add_value_to_vec(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
PairVec& vec, std::true_type) const;
|
|
template <typename T> void add_value_to_vec(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
PairVec& vec, std::false_type) const;
|
|
|
|
};
|
|
|
|
template <GncSqlObjectType Type>
|
|
class GncSqlColumnTableEntryImpl final : public GncSqlColumnTableEntry
|
|
{
|
|
public:
|
|
GncSqlColumnTableEntryImpl (const char* name, const GncSqlObjectType type,
|
|
unsigned int s,
|
|
int f, const char* gobj_name = nullptr,
|
|
const char* qof_name = nullptr,
|
|
QofAccessFunc get = nullptr,
|
|
QofSetterFunc set = nullptr) :
|
|
GncSqlColumnTableEntry (name, type, s, f, gobj_name,qof_name, get, set)
|
|
{}
|
|
|
|
void load(const GncSqlBackend* sql_be, GncSqlRow& row, QofIdTypeConst obj_name,
|
|
void* pObject) const noexcept override;
|
|
void add_to_table(ColVec& vec) const noexcept override;
|
|
void add_to_query(QofIdTypeConst obj_name, void* pObject, PairVec& vec)
|
|
const noexcept override;
|
|
};
|
|
|
|
using GncSqlColumnTableEntryPtr = std::shared_ptr<GncSqlColumnTableEntry>;
|
|
using EntryVec = std::vector<GncSqlColumnTableEntryPtr>;
|
|
|
|
template <GncSqlObjectType Type>
|
|
std::shared_ptr<GncSqlColumnTableEntryImpl<Type>>
|
|
gnc_sql_make_table_entry(const char* name, unsigned int s, int f)
|
|
{
|
|
return std::make_shared<GncSqlColumnTableEntryImpl<Type>>(name, Type, s, f);
|
|
}
|
|
|
|
template <GncSqlObjectType Type>
|
|
std::shared_ptr<GncSqlColumnTableEntryImpl<Type>>
|
|
gnc_sql_make_table_entry(const char* name, unsigned int s, int f,
|
|
const char* param)
|
|
{
|
|
return std::make_shared<GncSqlColumnTableEntryImpl<Type>>(name, Type, s,
|
|
f, param);
|
|
}
|
|
|
|
class is_qof : public std::true_type {};
|
|
|
|
template <GncSqlObjectType Type>
|
|
std::shared_ptr<GncSqlColumnTableEntryImpl<Type>>
|
|
gnc_sql_make_table_entry(const char* name, unsigned int s, int f,
|
|
const char* param, bool qofp)
|
|
{
|
|
return std::make_shared<GncSqlColumnTableEntryImpl<Type>>(name, Type, s,
|
|
f, nullptr,
|
|
param);
|
|
}
|
|
|
|
template <GncSqlObjectType Type>
|
|
std::shared_ptr<GncSqlColumnTableEntryImpl<Type>>
|
|
gnc_sql_make_table_entry(const char* name, unsigned int s, int f,
|
|
QofAccessFunc get, QofSetterFunc set)
|
|
{
|
|
return std::make_shared<GncSqlColumnTableEntryImpl<Type>>(
|
|
name, Type, s, f, nullptr, nullptr, get, set);
|
|
}
|
|
|
|
|
|
template <typename T> T
|
|
GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name,
|
|
const void* pObject) const
|
|
{
|
|
return get_row_value_from_object<T>(obj_name, pObject,
|
|
std::is_pointer<T>());
|
|
}
|
|
|
|
template <typename T> T
|
|
GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
std::true_type) const
|
|
{
|
|
g_return_val_if_fail(obj_name != nullptr && pObject != nullptr, nullptr);
|
|
T result = nullptr;
|
|
if (m_gobj_param_name != nullptr)
|
|
g_object_get(const_cast<void*>(pObject), m_gobj_param_name,
|
|
&result, nullptr);
|
|
else
|
|
{
|
|
QofAccessFunc getter = get_getter(obj_name);
|
|
if (getter != nullptr)
|
|
result = reinterpret_cast<T>((getter)(const_cast<void*>(pObject),
|
|
nullptr));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <typename T> T
|
|
GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
std::false_type) const
|
|
{
|
|
g_return_val_if_fail(obj_name != nullptr && pObject != nullptr,
|
|
static_cast<T>(0));
|
|
T result = static_cast<T>(0);
|
|
if (m_gobj_param_name != nullptr)
|
|
g_object_get(const_cast<void*>(pObject), m_gobj_param_name,
|
|
&result, nullptr);
|
|
else
|
|
{
|
|
QofAccessFunc getter = get_getter(obj_name);
|
|
if (getter != nullptr)
|
|
result = reinterpret_cast<T>((getter)(const_cast<void*>(pObject),
|
|
nullptr));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <typename T> void
|
|
GncSqlColumnTableEntry::add_value_to_vec(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
PairVec& vec) const
|
|
{
|
|
add_value_to_vec<T>(obj_name, pObject, vec, std::is_pointer<T>());
|
|
}
|
|
|
|
template <typename T> void
|
|
GncSqlColumnTableEntry::add_value_to_vec(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
PairVec& vec, std::true_type) const
|
|
{
|
|
T s = get_row_value_from_object<T>(obj_name, pObject);
|
|
|
|
if (s != nullptr)
|
|
{
|
|
std::ostringstream stream;
|
|
stream << *s;
|
|
vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
template <> inline void
|
|
GncSqlColumnTableEntry::add_value_to_vec<double*>(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
PairVec& vec, std::true_type) const
|
|
{
|
|
double* s = get_row_value_from_object<double*>(obj_name, pObject);
|
|
|
|
if (s != nullptr)
|
|
{
|
|
std::ostringstream stream;
|
|
stream << std::setprecision(12) << std::fixed << *s;
|
|
vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
template <typename T> void
|
|
GncSqlColumnTableEntry::add_value_to_vec(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
PairVec& vec, std::false_type) const
|
|
{
|
|
T s = get_row_value_from_object<T>(obj_name, pObject);
|
|
|
|
std::ostringstream stream;
|
|
stream << s;
|
|
vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str()));
|
|
return;
|
|
}
|
|
|
|
template <> inline void
|
|
GncSqlColumnTableEntry::add_value_to_vec<double>(QofIdTypeConst obj_name,
|
|
const void* pObject,
|
|
PairVec& vec, std::false_type) const
|
|
{
|
|
double s = *get_row_value_from_object<double*>(obj_name, pObject);
|
|
|
|
std::ostringstream stream;
|
|
stream << std::setprecision(12) << std::fixed << s;
|
|
vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str()));
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Load an arbitrary object from a result row.
|
|
*
|
|
* @param sql_be: GncSqlBackend*, pass-through to the implementation loader.
|
|
* @param row: The GncSqlResult
|
|
* @param obj_name: The object-name with which to retrieve the setter func.
|
|
* @param pObject: The target object being loaded.
|
|
* @param table: The table description to interpret the row.
|
|
*/
|
|
void gnc_sql_load_object (const GncSqlBackend* sql_be, GncSqlRow& row,
|
|
QofIdTypeConst obj_name, gpointer pObject,
|
|
const EntryVec& table);
|
|
/**
|
|
* Create a GncGUID from a guid stored in a row.
|
|
*
|
|
* @param sql_be: The active GncSqlBackend. Pass-throug to gnc_sql_load_object.
|
|
* @param row: The GncSqlResult row.
|
|
*/
|
|
const GncGUID*
|
|
gnc_sql_load_guid (const GncSqlBackend* sql_be, GncSqlRow& row);
|
|
|
|
/**
|
|
* Append the GUIDs of QofInstances to a SQL query.
|
|
*
|
|
* @param sql: The SQL Query in progress to which the GncGUIDS should be appended.
|
|
* @param instances: The QofInstances
|
|
* @return The number of instances
|
|
*/
|
|
uint_t gnc_sql_append_guids_to_sql (std::stringstream& sql,
|
|
const InstanceVec& instances);
|
|
|
|
/**
|
|
* information required to create a column in a table.
|
|
*/
|
|
struct GncSqlColumnInfo
|
|
{
|
|
GncSqlColumnInfo (std::string&& name, GncSqlBasicColumnType type,
|
|
unsigned int size = 0, bool unicode = false,
|
|
bool autoinc = false, bool primary = false,
|
|
bool not_null = false) :
|
|
m_name{name}, m_type{type}, m_size{size}, m_unicode{unicode},
|
|
m_autoinc{autoinc}, m_primary_key{primary}, m_not_null{not_null}
|
|
{}
|
|
GncSqlColumnInfo(const GncSqlColumnTableEntry& e, GncSqlBasicColumnType t,
|
|
unsigned int size = 0, bool unicode = true) :
|
|
m_name{e.m_col_name}, m_type{t}, m_size{size}, m_unicode{unicode},
|
|
m_autoinc(e.m_flags & COL_AUTOINC),
|
|
m_primary_key(e.m_flags & COL_PKEY),
|
|
m_not_null(e.m_flags & COL_NNUL) {}
|
|
std::string m_name; /**< Column name */
|
|
GncSqlBasicColumnType m_type; /**< Column basic type */
|
|
unsigned int m_size; /**< Column size (string types) */
|
|
bool m_unicode; /**< Column is unicode (string types) */
|
|
bool m_autoinc; /**< Column is autoinc (int type) */
|
|
bool m_primary_key; /**< Column is the primary key */
|
|
bool m_not_null; /**< Column forbids NULL values */
|
|
};
|
|
|
|
inline bool operator==(const GncSqlColumnInfo& l,
|
|
const GncSqlColumnInfo& r)
|
|
{
|
|
return l.m_name == r.m_name && l.m_type == r.m_type;
|
|
}
|
|
|
|
inline bool operator!=(const GncSqlColumnInfo& l,
|
|
const GncSqlColumnInfo& r)
|
|
{
|
|
return !(l == r);
|
|
}
|
|
|
|
/**
|
|
* Set an object property with a setter function.
|
|
* @param pObject void* to the object being set.
|
|
* @param item the value to be set in the property.
|
|
* @param setter The function to set the property.
|
|
* The void* is an obvious wart occasioned by the fact that we're using GLists
|
|
* to hold objects. As the rewrite progresses we'll replace that with another
|
|
* template parameter.
|
|
*/
|
|
template <typename T, typename P, typename F>
|
|
void set_parameter(T object, P item, F& setter)
|
|
{
|
|
(*setter)(object, item);
|
|
}
|
|
|
|
template <typename T, typename P>
|
|
void set_parameter(T object, P item, QofSetterFunc setter, std::true_type)
|
|
{
|
|
(*setter)(object, (void*)item);
|
|
}
|
|
|
|
template <typename T, typename P>
|
|
void set_parameter(T object, P item, QofSetterFunc setter, std::false_type)
|
|
{
|
|
(*setter)(object, (void*)(&item));
|
|
}
|
|
|
|
template <typename T, typename P>
|
|
void set_parameter(T object, P item, QofSetterFunc setter)
|
|
{
|
|
set_parameter(object, item, setter, std::is_pointer<P>());
|
|
}
|
|
|
|
/**
|
|
* Set an object property with g_object_set.
|
|
* @param pObject void* to the object being set.
|
|
* @param item the value to set in the property.
|
|
* @param property the property name.
|
|
* The void* is an obvious wart. So is g_object_set, partly because it's GObject
|
|
* but mostly because it works off of string comparisons.
|
|
*/
|
|
template <typename T, typename P>
|
|
void set_parameter(T object, P item, const char* property)
|
|
{
|
|
// Properly use qof_begin_edit and qof_commit_edit{_part2}
|
|
// here. This is needed to reset the infant state of objects
|
|
// when loading them initially from sql. Failing to do so
|
|
// could prevent future editing of these objects
|
|
// Example of this is https://bugs.gnucash.org/show_bug.cgi?id=795944
|
|
qof_begin_edit(QOF_INSTANCE(object));
|
|
g_object_set(object, property, item, nullptr);
|
|
if (!qof_commit_edit(QOF_INSTANCE(object))) return;
|
|
// FIXME I can't use object specific callbacks in generic code
|
|
// so for now these will silently fail. As the GObject based method
|
|
// of setting qof objects should go away eventually I won't bother
|
|
// finding a proper solution for this.
|
|
qof_commit_edit_part2(QOF_INSTANCE(object), nullptr, nullptr, nullptr);
|
|
};
|
|
|
|
/**
|
|
* Set an object property with either a g_object_set or a setter.
|
|
*
|
|
* See previous templates for the parameter meanings. This is clunky but fits in
|
|
* the current architecture for refactoring.
|
|
*/
|
|
template <typename T, typename P, typename F>
|
|
void set_parameter(T object, P item, F setter, const char* property)
|
|
{
|
|
if (property)
|
|
set_parameter(object, item, property);
|
|
else
|
|
set_parameter(object, item, setter);
|
|
}
|
|
|
|
#endif //__GNC_SQL_COLUMN_TABLE_ENTRY_HPP__
|