1 /***********************************************************************\
2  * gnc-sql-column-table-entry.hpp: Column Specification for SQL Table. *
3  *                                                                     *
4  * Copyright 2016 John Ralls <jralls@ceridwen.us>                      *
5  *                                                                     *
6  * This program is free software; you can redistribute it and/or       *
7  * modify it under the terms of the GNU General Public License as      *
8  * published by the Free Software Foundation; either version 2 of      *
9  * the License, or (at your option) any later version.                 *
10  *                                                                     *
11  * This program is distributed in the hope that it will be useful,     *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of      *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       *
14  * GNU General Public License for more details.                        *
15  *                                                                     *
16  * You should have received a copy of the GNU General Public License   *
17  * along with this program; if not, contact:                           *
18  *                                                                     *
19  * Free Software Foundation           Voice:  +1-617-542-5942          *
20  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652          *
21  * Boston, MA  02110-1301,  USA       gnu@gnu.org                      *
22 \***********************************************************************/
23 
24 #ifndef __GNC_SQL_COLUMN_TABLE_ENTRY_HPP__
25 #define __GNC_SQL_COLUMN_TABLE_ENTRY_HPP__
26 
27 extern "C"
28 {
29 #include <qof.h>
30 }
31 #include <memory>
32 #include <vector>
33 #include <iostream>
34 #include <iomanip>
35 
36 #include "gnc-sql-result.hpp"
37 
38 struct GncSqlColumnInfo;
39 using ColVec = std::vector<GncSqlColumnInfo>;
40 using PairVec = std::vector<std::pair<std::string, std::string>>;
41 using InstanceVec = std::vector<QofInstance*>;
42 using uint_t = unsigned int;
43 class GncSqlBackend;
44 
45 /**
46  * Basic column type
47  */
48 typedef enum
49 {
50     BCT_STRING,
51     BCT_INT,
52     BCT_INT64,
53     BCT_DATE,
54     BCT_DOUBLE,
55     BCT_DATETIME
56 } GncSqlBasicColumnType;
57 
58 enum ColumnFlags : int
59 {
60     COL_NO_FLAG = 0,
61     COL_PKEY = 0x01,    /**< The column is a primary key */
62     COL_NNUL = 0x02,    /**< The column may not contain a NULL value */
63     COL_UNIQUE = 0x04,  /**< The column must contain unique values */
64     COL_AUTOINC = 0x08  /**< The column is an auto-incrementing int */
65 };
66 
67 // Type for conversion of db row to object.
68 enum GncSqlObjectType
69 {
70     CT_STRING,
71     CT_GUID,
72     CT_INT,
73     CT_INT64,
74     CT_TIME,
75     CT_GDATE,
76     CT_NUMERIC,
77     CT_DOUBLE,
78     CT_BOOLEAN,
79     CT_ACCOUNTREF,
80     CT_BUDGETREF,
81     CT_COMMODITYREF,
82     CT_LOTREF,
83     CT_TXREF,
84     CT_ADDRESS,
85     CT_BILLTERMREF,
86     CT_INVOICEREF,
87     CT_ORDERREF,
88     CT_OWNERREF,
89     CT_TAXTABLEREF
90 };
91 
92 static inline std::string
quote_string(const std::string & str)93 quote_string(const std::string& str)
94 {
95     if (str == "NULL" || str == "null") return "NULL";
96     /* FIXME: This is here because transactions.num has a NULL
97      * constraint, which is dumb; it's often empty.
98      */
99     if (str.empty()) return "''";
100     std::string retval;
101     retval.reserve(str.length() + 2);
102     retval.insert(0, 1, '\'');
103     for (auto c = str.begin(); c != str.end(); ++c)
104     {
105         if (*c == '\'')
106             retval += *c;
107         retval += *c;
108     }
109     retval += '\'';
110     return retval;
111 }
112 
113 /**
114  * Contains all of the information required to copy information between an
115  * object and the database for a specific object property.
116  *
117  * If an entry contains a gobj_param_name value, this string is used as the
118  * property name for a call to g_object_get() or g_object_set().  If the
119  * gobj_param_name value is NULL but qof_param_name is not NULL, this value
120  * is used as the parameter name for a call to
121  * qof_class_get_parameter_getter().  If both of these values are NULL, getter
122  * and setter are the addresses of routines to return or set the parameter
123  * value, respectively.
124  *
125  * The database description for an object consists of an array of
126  * GncSqlColumnTableEntry objects, with a final member having col_name == NULL.
127  */
128 
129 class GncSqlColumnTableEntry
130 {
131 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)132     GncSqlColumnTableEntry (const char* name, const GncSqlObjectType type,
133                             unsigned int s,
134                             int f, const char* gobj_name = nullptr,
135                             const char* qof_name = nullptr,
136                             QofAccessFunc get = nullptr,
137                             QofSetterFunc set = nullptr) :
138         m_col_name{name}, m_col_type{type}, m_size{s},
139         m_flags{static_cast<ColumnFlags>(f)},
140         m_gobj_param_name{gobj_name}, m_qof_param_name{qof_name}, m_getter{get},
141         m_setter{set} {}
142     virtual ~GncSqlColumnTableEntry() = default;
143 
144     /**
145      * Load a value into an object from the database row.
146      */
147     virtual void load(const GncSqlBackend* sql_be, GncSqlRow& row,
148                       QofIdTypeConst obj_name, void* pObject) const noexcept = 0;
149     /**
150      * Add a GncSqlColumnInfo structure for the column type to a
151      * ColVec.
152      */
153     virtual void add_to_table(ColVec& vec) const noexcept = 0;
154     /**
155      * Add a pair of the table column heading and object's value's string
156      * representation to a PairVec; used for constructing WHERE clauses and
157      * UPDATE statements.
158      */
159     virtual void add_to_query(QofIdTypeConst obj_name,
160                               void* pObject, PairVec& vec) const noexcept = 0;
161     /**
162      * Retrieve the getter function depending on whether it's an auto-increment
163      * field, a QofClass getter, or a function passed to the constructor.
164      */
165     QofAccessFunc get_getter(QofIdTypeConst obj_name) const noexcept;
166     /**
167      * Retrieve the setter function depending on whether it's an auto-increment
168      * field, a QofClass getter, or a function passed to the constructor.
169      */
170     QofSetterFunc get_setter(QofIdTypeConst obj_name) const noexcept;
171     /**
172      * Retrieve the field name so that we don't need to make
173      * create_single_col_select_statement and friend.
174      */
name() const175     const char* name() const noexcept { return m_col_name; }
176     /**
177      * Report if the entry is an auto-increment field.
178      */
is_autoincr() const179     bool is_autoincr() const noexcept { return m_flags & COL_AUTOINC; }
180     /* On the other hand, our implementation class and GncSqlColumnInfo need to
181      * be able to read our member variables.
182      */
183     template<GncSqlObjectType Otype> friend class GncSqlColumnTableEntryImpl;
184     friend struct GncSqlColumnInfo;
load_from_guid_ref(GncSqlRow & row,QofIdTypeConst obj_name,void * pObject,T get_ref) const185     template<typename T> void load_from_guid_ref(GncSqlRow& row,
186                                                  QofIdTypeConst obj_name,
187                                                  void* pObject, T get_ref)
188         const noexcept
189         {
190             g_return_if_fail (pObject != NULL);
191 
192             try
193             {
194                 GncGUID guid;
195                 auto val = row.get_string_at_col (m_col_name);
196                 if (string_to_guid (val.c_str(), &guid))
197                 {
198                     auto target = get_ref(&guid);
199                     if (target != nullptr)
200                         set_parameter (pObject, target, get_setter(obj_name),
201                                        m_gobj_param_name);
202                 }
203             }
204             catch (std::invalid_argument&) {}
205         }
206 
207 
208 protected:
209     template <typename T> T
210     get_row_value_from_object(QofIdTypeConst obj_name, const void* pObject) const;
211     template <typename T> void
212     add_value_to_vec(QofIdTypeConst obj_name,
213                      const void* pObject, PairVec& vec) const;
214 /**
215  * Adds a name/guid std::pair to a PairVec for creating a query.
216  *
217  * @param sql_be SQL backend struct
218  * @param obj_name QOF object type name
219  * @param pObject Object
220  * @param pList List
221  */
222     void add_objectref_guid_to_query (QofIdTypeConst obj_name,
223                                       const void* pObject,
224                                       PairVec& vec) const noexcept;
225 /**
226  * Adds a column info structure for an object reference GncGUID to a ColVec.
227  *
228  * @param sql_be SQL backend struct
229  * @param pList List
230  */
231     void add_objectref_guid_to_table (ColVec& vec) const noexcept;
232 private:
233     const char* m_col_name = nullptr;        /**< Column name */
234     const GncSqlObjectType m_col_type;        /**< Column type */
235     unsigned int m_size;       /**< Column size in bytes, for string columns */
236     ColumnFlags m_flags;           /**< Column flags */
237     const char* m_gobj_param_name = nullptr; /**< If non-null, g_object param name */
238     const char* m_qof_param_name = nullptr;  /**< If non-null, qof parameter name */
239     QofAccessFunc m_getter;        /**< General access function */
240     QofSetterFunc m_setter;        /**< General setter function */
241     template <typename T> T get_row_value_from_object(QofIdTypeConst obj_name,
242                                                       const void* pObject,
243                                                       std::true_type) const;
244     template <typename T> T get_row_value_from_object(QofIdTypeConst obj_name,
245                                                       const void* pObject,
246                                                       std::false_type) const;
247     template <typename T> void add_value_to_vec(QofIdTypeConst obj_name,
248                                                 const void* pObject,
249                                                 PairVec& vec, std::true_type) const;
250     template <typename T> void add_value_to_vec(QofIdTypeConst obj_name,
251                                                 const void* pObject,
252                                                 PairVec& vec, std::false_type) const;
253 
254 };
255 
256 template <GncSqlObjectType Type>
257 class GncSqlColumnTableEntryImpl final : public GncSqlColumnTableEntry
258 {
259 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)260     GncSqlColumnTableEntryImpl (const char* name, const GncSqlObjectType type,
261                                 unsigned int s,
262                                 int f, const char* gobj_name = nullptr,
263                                 const char* qof_name = nullptr,
264                                 QofAccessFunc get = nullptr,
265                                 QofSetterFunc set = nullptr) :
266         GncSqlColumnTableEntry (name, type, s, f, gobj_name,qof_name, get, set)
267         {}
268 
269     void load(const GncSqlBackend* sql_be, GncSqlRow& row,  QofIdTypeConst obj_name,
270               void* pObject) const noexcept override;
271     void add_to_table(ColVec& vec) const noexcept override;
272     void add_to_query(QofIdTypeConst obj_name, void* pObject, PairVec& vec)
273         const noexcept override;
274 };
275 
276 using GncSqlColumnTableEntryPtr = std::shared_ptr<GncSqlColumnTableEntry>;
277 using EntryVec = std::vector<GncSqlColumnTableEntryPtr>;
278 
279 template <GncSqlObjectType Type>
280 std::shared_ptr<GncSqlColumnTableEntryImpl<Type>>
gnc_sql_make_table_entry(const char * name,unsigned int s,int f)281 gnc_sql_make_table_entry(const char* name, unsigned int s, int f)
282 {
283     return std::make_shared<GncSqlColumnTableEntryImpl<Type>>(name, Type, s, f);
284 }
285 
286 template <GncSqlObjectType Type>
287 std::shared_ptr<GncSqlColumnTableEntryImpl<Type>>
gnc_sql_make_table_entry(const char * name,unsigned int s,int f,const char * param)288 gnc_sql_make_table_entry(const char* name, unsigned int s, int f,
289                          const char* param)
290 {
291     return std::make_shared<GncSqlColumnTableEntryImpl<Type>>(name, Type, s,
292                                                               f, param);
293 }
294 
295 class is_qof : public std::true_type {};
296 
297 template <GncSqlObjectType Type>
298 std::shared_ptr<GncSqlColumnTableEntryImpl<Type>>
gnc_sql_make_table_entry(const char * name,unsigned int s,int f,const char * param,bool qofp)299 gnc_sql_make_table_entry(const char* name, unsigned int s, int f,
300                          const char* param, bool qofp)
301 {
302     return std::make_shared<GncSqlColumnTableEntryImpl<Type>>(name, Type, s,
303                                                               f, nullptr,
304                                                               param);
305 }
306 
307 template <GncSqlObjectType Type>
308 std::shared_ptr<GncSqlColumnTableEntryImpl<Type>>
gnc_sql_make_table_entry(const char * name,unsigned int s,int f,QofAccessFunc get,QofSetterFunc set)309 gnc_sql_make_table_entry(const char* name, unsigned int s, int f,
310                          QofAccessFunc get, QofSetterFunc set)
311 {
312     return std::make_shared<GncSqlColumnTableEntryImpl<Type>>(
313         name, Type, s, f, nullptr, nullptr, get, set);
314 }
315 
316 
317 template <typename T> T
get_row_value_from_object(QofIdTypeConst obj_name,const void * pObject) const318 GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name,
319                                                   const void* pObject) const
320 {
321     return get_row_value_from_object<T>(obj_name, pObject,
322                                         std::is_pointer<T>());
323 }
324 
325 template <typename T> T
get_row_value_from_object(QofIdTypeConst obj_name,const void * pObject,std::true_type) const326 GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name,
327                                                   const void* pObject,
328                                                   std::true_type) const
329 {
330     g_return_val_if_fail(obj_name != nullptr && pObject != nullptr, nullptr);
331     T result = nullptr;
332     if (m_gobj_param_name != nullptr)
333         g_object_get(const_cast<void*>(pObject), m_gobj_param_name,
334                      &result, nullptr);
335     else
336     {
337         QofAccessFunc getter = get_getter(obj_name);
338         if (getter != nullptr)
339             result = reinterpret_cast<T>((getter)(const_cast<void*>(pObject),
340                                                   nullptr));
341     }
342     return result;
343 }
344 
345 template <typename T> T
get_row_value_from_object(QofIdTypeConst obj_name,const void * pObject,std::false_type) const346 GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name,
347                                                   const void* pObject,
348                                                   std::false_type) const
349 {
350     g_return_val_if_fail(obj_name != nullptr && pObject != nullptr,
351                          static_cast<T>(0));
352     T result = static_cast<T>(0);
353     if (m_gobj_param_name != nullptr)
354         g_object_get(const_cast<void*>(pObject), m_gobj_param_name,
355                      &result, nullptr);
356     else
357     {
358         QofAccessFunc getter = get_getter(obj_name);
359         if (getter != nullptr)
360             result = reinterpret_cast<T>((getter)(const_cast<void*>(pObject),
361                                                   nullptr));
362     }
363     return result;
364 }
365 
366 template <typename T> void
add_value_to_vec(QofIdTypeConst obj_name,const void * pObject,PairVec & vec) const367 GncSqlColumnTableEntry::add_value_to_vec(QofIdTypeConst obj_name,
368                                          const void* pObject,
369                                          PairVec& vec) const
370 {
371     add_value_to_vec<T>(obj_name, pObject, vec, std::is_pointer<T>());
372 }
373 
374 template <typename T> void
add_value_to_vec(QofIdTypeConst obj_name,const void * pObject,PairVec & vec,std::true_type) const375 GncSqlColumnTableEntry::add_value_to_vec(QofIdTypeConst obj_name,
376                                          const void* pObject,
377                                          PairVec& vec, std::true_type) const
378 {
379     T s = get_row_value_from_object<T>(obj_name, pObject);
380 
381     if (s != nullptr)
382     {
383         std::ostringstream stream;
384         stream << *s;
385         vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str()));
386         return;
387     }
388 }
389 
390 template <> inline  void
add_value_to_vec(QofIdTypeConst obj_name,const void * pObject,PairVec & vec,std::true_type) const391 GncSqlColumnTableEntry::add_value_to_vec<double*>(QofIdTypeConst obj_name,
392                                          const void* pObject,
393                                          PairVec& vec, std::true_type) const
394 {
395     double* s = get_row_value_from_object<double*>(obj_name, pObject);
396 
397     if (s != nullptr)
398     {
399         std::ostringstream stream;
400         stream << std::setprecision(12) << std::fixed << *s;
401         vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str()));
402         return;
403     }
404 }
405 
406 template <typename T> void
add_value_to_vec(QofIdTypeConst obj_name,const void * pObject,PairVec & vec,std::false_type) const407 GncSqlColumnTableEntry::add_value_to_vec(QofIdTypeConst obj_name,
408                                          const void* pObject,
409                                          PairVec& vec, std::false_type) const
410 {
411     T s = get_row_value_from_object<T>(obj_name, pObject);
412 
413     std::ostringstream stream;
414     stream << s;
415     vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str()));
416     return;
417 }
418 
419 template <> inline void
add_value_to_vec(QofIdTypeConst obj_name,const void * pObject,PairVec & vec,std::false_type) const420 GncSqlColumnTableEntry::add_value_to_vec<double>(QofIdTypeConst obj_name,
421                                          const void* pObject,
422                                          PairVec& vec, std::false_type) const
423 {
424     double s = *get_row_value_from_object<double*>(obj_name, pObject);
425 
426     std::ostringstream stream;
427     stream << std::setprecision(12) << std::fixed << s;
428     vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str()));
429     return;
430 }
431 
432 /**
433  * Load an arbitrary object from a result row.
434  *
435  * @param sql_be: GncSqlBackend*, pass-through to the implementation loader.
436  * @param row: The GncSqlResult
437  * @param obj_name: The object-name with which to retrieve the setter func.
438  * @param pObject: The target object being loaded.
439  * @param table: The table description to interpret the row.
440  */
441 void gnc_sql_load_object (const GncSqlBackend* sql_be, GncSqlRow& row,
442                           QofIdTypeConst obj_name, gpointer pObject,
443                           const EntryVec& table);
444 /**
445  * Create a GncGUID from a guid stored in a row.
446  *
447  * @param sql_be: The active GncSqlBackend. Pass-throug to gnc_sql_load_object.
448  * @param row: The GncSqlResult row.
449  */
450 const GncGUID*
451 gnc_sql_load_guid (const GncSqlBackend* sql_be, GncSqlRow& row);
452 
453 /**
454  * Append the GUIDs of QofInstances to a SQL query.
455  *
456  * @param sql: The SQL Query in progress to which the GncGUIDS should be appended.
457  * @param instances: The QofInstances
458  * @return The number of instances
459  */
460 uint_t gnc_sql_append_guids_to_sql (std::stringstream& sql,
461                                     const InstanceVec& instances);
462 
463 /**
464  *  information required to create a column in a table.
465  */
466 struct GncSqlColumnInfo
467 {
GncSqlColumnInfoGncSqlColumnInfo468     GncSqlColumnInfo (std::string&& name, GncSqlBasicColumnType type,
469                       unsigned int size = 0, bool unicode = false,
470                       bool autoinc = false, bool primary = false,
471                       bool not_null = false) :
472         m_name{name}, m_type{type}, m_size{size}, m_unicode{unicode},
473         m_autoinc{autoinc}, m_primary_key{primary}, m_not_null{not_null}
474         {}
GncSqlColumnInfoGncSqlColumnInfo475     GncSqlColumnInfo(const GncSqlColumnTableEntry& e, GncSqlBasicColumnType t,
476                      unsigned int size = 0, bool unicode = true) :
477         m_name{e.m_col_name}, m_type{t}, m_size{size}, m_unicode{unicode},
478         m_autoinc(e.m_flags & COL_AUTOINC),
479         m_primary_key(e.m_flags & COL_PKEY),
480         m_not_null(e.m_flags & COL_NNUL) {}
481     std::string m_name; /**< Column name */
482     GncSqlBasicColumnType m_type; /**< Column basic type */
483     unsigned int m_size; /**< Column size (string types) */
484     bool m_unicode; /**< Column is unicode (string types) */
485     bool m_autoinc; /**< Column is autoinc (int type) */
486     bool m_primary_key; /**< Column is the primary key */
487     bool m_not_null; /**< Column forbids NULL values */
488 };
489 
operator ==(const GncSqlColumnInfo & l,const GncSqlColumnInfo & r)490 inline bool operator==(const GncSqlColumnInfo& l,
491                        const GncSqlColumnInfo& r)
492 {
493     return l.m_name == r.m_name && l.m_type == r.m_type;
494 }
495 
operator !=(const GncSqlColumnInfo & l,const GncSqlColumnInfo & r)496 inline bool operator!=(const GncSqlColumnInfo& l,
497                        const GncSqlColumnInfo& r)
498 {
499     return !(l == r);
500 }
501 
502 /**
503  * Set an object property with a setter function.
504  * @param pObject void* to the object being set.
505  * @param item the value to be set in the property.
506  * @param setter The function to set the property.
507  * The void* is an obvious wart occasioned by the fact that we're using GLists
508  * to hold objects. As the rewrite progresses we'll replace that with another
509  * template parameter.
510  */
511 template <typename T, typename P, typename F>
set_parameter(T object,P item,F & setter)512 void set_parameter(T object, P item, F& setter)
513 {
514     (*setter)(object, item);
515 }
516 
517 template <typename T, typename P>
set_parameter(T object,P item,QofSetterFunc setter,std::true_type)518 void set_parameter(T object, P item, QofSetterFunc setter, std::true_type)
519 {
520     (*setter)(object, (void*)item);
521 }
522 
523 template <typename T, typename P>
set_parameter(T object,P item,QofSetterFunc setter,std::false_type)524 void set_parameter(T object, P item, QofSetterFunc setter, std::false_type)
525 {
526     (*setter)(object, (void*)(&item));
527 }
528 
529 template <typename T, typename P>
set_parameter(T object,P item,QofSetterFunc setter)530 void set_parameter(T object, P item, QofSetterFunc setter)
531 {
532     set_parameter(object, item, setter, std::is_pointer<P>());
533 }
534 
535 /**
536  * Set an object property with g_object_set.
537  * @param pObject void* to the object being set.
538  * @param item the value to set in the property.
539  * @param property the property name.
540  * The void* is an obvious wart. So is g_object_set, partly because it's GObject
541  * but mostly because it works off of string comparisons.
542  */
543 template <typename T, typename P>
set_parameter(T object,P item,const char * property)544 void set_parameter(T object, P item, const char* property)
545 {
546     // Properly use qof_begin_edit and qof_commit_edit{_part2}
547     // here. This is needed to reset the infant state of objects
548     // when loading them initially from sql. Failing to do so
549     // could prevent future editing of these objects
550     // Example of this is https://bugs.gnucash.org/show_bug.cgi?id=795944
551     qof_begin_edit(QOF_INSTANCE(object));
552     g_object_set(object, property, item, nullptr);
553     if (!qof_commit_edit(QOF_INSTANCE(object))) return;
554     // FIXME I can't use object specific callbacks in generic code
555     // so for now these will silently fail. As the GObject based method
556     // of setting qof objects should go away eventually I won't bother
557     // finding a proper solution for this.
558     qof_commit_edit_part2(QOF_INSTANCE(object), nullptr, nullptr, nullptr);
559 };
560 
561 /**
562  * Set an object property with either a g_object_set or a setter.
563  *
564  * See previous templates for the parameter meanings. This is clunky but fits in
565  * the current architecture for refactoring.
566  */
567 template <typename T, typename P, typename F>
set_parameter(T object,P item,F setter,const char * property)568 void set_parameter(T object, P item, F setter, const char* property)
569 {
570     if (property)
571         set_parameter(object, item, property);
572     else
573         set_parameter(object, item, setter);
574 }
575 
576 #endif //__GNC_SQL_COLUMN_TABLE_ENTRY_HPP__
577