[Gnucash-changes] initial checkin of core generic object ampping
routine to CVS repository
Linas Vepstas
linas at cvs.gnucash.org
Sat Jun 12 16:29:06 EDT 2004
Log Message:
-----------
initial checkin of core generic object ampping routine to CVS repository
Added Files:
-----------
gnucash/src/backend/dwi:
qofmap.c
qofmap.h
Revision Data
-------------
--- /dev/null
+++ src/backend/dwi/qofmap.h
@@ -0,0 +1,96 @@
+/********************************************************************\
+ * qofmap.h -- Map QOF object to SQL tables, and back. *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org> *
+ * http://dwi.sourceforge.net *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Lesser General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2.1 of the License, or (at your option) any later version.
+ * *
+ * This library 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 Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+\********************************************************************/
+/**
+ * @file qofmap.h
+ * Prototype for a new QOF DWI backend .... under development.
+ *
+ * A QofMap defines how the properties of a QOF Object are mapped
+ * to an SQL table. The QofMap is used store specific Qof Instances
+ * as SQL records, and vice-versa: create Qof Instances from SQL
+ * records. The QofMap API consists of two parts: a set of routines
+ * used to define the map itself, and a second set of routines to
+ * use that map in various ways.
+ */
+
+#ifndef QOF_MAP_H_
+#define QOF_MAP_H_
+
+#include <qof/qofbook.h>
+#include <qof/qofinstance.h>
+#include <qof/guid.h>
+#include "database.h"
+
+typedef struct QofMap_s QofMap;
+
+/** Arguments: name of entity (entity type) and SQL table name */
+QofMap * qof_map_new (const char *etype, const char *tabname);
+
+/** @{
+ * Routines to define a QofMap
+ */
+/** Add a link between an SQL fieldname (SQL column name) and QOF property
+ name. These will be linked so that the one is stored into the other
+ when the object is sync to the database.
+ */
+void qof_map_add_field (QofMap *qm, const char *fieldname, const char * property);
+
+/** Add a term that uniquely identifies the QOF Object & SQL record.
+ * This will be the GUID (that is all that is supported at this time).
+ */
+void qof_map_add_match (QofMap *qm, const char *fieldname, const char * property);
+
+/** Add a term that indicates the version of the QOF Object/SQL record.
+ * This is used to determine whether the object in local memory is newer
+ * or older than the corresponding record in the database. Currently,
+ * the only supported type of the version field is a date-time stamp.
+ */
+void qof_map_add_version_cmp (QofMap *qm, const char *fieldname, const char * property);
+
+/** @} */
+
+/** Set the book in which the object instance will live.
+ * The book can be set at any time, there is little penalty
+ * to changing the book often.
+ */
+void qof_map_set_book (QofMap *qm, QofBook *book);
+
+/** Specify the database that will eb used to make the connection */
+void qof_map_set_database (QofMap *qm, DuiDatabase *db);
+
+/** Get record from DB, find or create the matching QOF object. */
+void qof_map_copy_from_db (QofMap *qm, const GUID *guid);
+
+/** Execute the SQL statement, which is expected to return zero
+ * or more records holding QOF objects. Find or create the
+ * the corresponding QOF objects.
+ */
+void qof_map_copy_multiple_from_db (QofMap *qm, const char * sql_stmt);
+
+/** 'Copy' a QOF Entity from local memory to the SQL database.
+ * Pass in the Guid of the entity that is to be saved to SQL.
+ * 'how' should be either "insert" or "update", corresponding to
+ * the SQL INSERT or UPDATE query types.
+ */
+void qof_map_copy_to_db(QofMap *qm, const GUID *guid, const char * how);
+
+#endif /* QOF_MAP_H_ */
--- /dev/null
+++ src/backend/dwi/qofmap.c
@@ -0,0 +1,415 @@
+/********************************************************************\
+ * qofmap.c -- Map QOF object to SQL tables, and back. *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org> *
+ * http://dwi.sourceforge.net *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Lesser General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2.1 of the License, or (at your option) any later version.
+ * *
+ * This library 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 Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+\********************************************************************/
+/*
+ * @file qofmap.c
+ * Prototype for a new QOF DWI backend .... under development.
+ *
+ * Basic parsing works basic, copyin, copyout works.
+ */
+
+#include <qof/qofinstance-p.h>
+#include "qofmap.h"
+
+#include "database.h"
+#include "duifield-qof.h"
+#include "duifield-sql.h"
+#include "duifieldmap.h"
+#include "duiresolver.h"
+#include "duitxnquery.h"
+#include "duitxnreport.h"
+#include "perr.h"
+
+/* ============================================================== */
+
+struct QofMap_s
+{
+ char *entity_type; /**< instance type */
+ char *table_name; /**< SQL table name */
+
+ /** The 'txnquery' will be used when writing to the database,
+ * we use it to construct the SQL for us, such as INSERT INTO
+ * and UPDATE .. WHERE
+ */
+ DuiTxnQuery *goto_db;
+
+ /** The 'txnreport' will be used when fetching from the DB.
+ * We will manually generate the SELECT * FROM, and use the
+ * report to copy from the sql recordset to the QOF object.
+ */
+ DuiTxnReport *from_db;
+
+ /** When going to/from the database, we need to match records.
+ * These will always be GUID's on the object side. We use
+ * the match_fieldname to store teh name of the SQL field that
+ * holds the GUID. */
+ char *match_fieldname;
+ char *match_property;
+ DuiFieldMap *goto_db_sqlmatch;
+ DuiFieldMap *goto_db_qofmatch;
+ DuiFieldMap *from_db_qofmatch;
+
+ /** Versioning */
+ DuiFieldMap *xxxfrom_db_version;
+
+ QofBook *book; /**< The book in which instance lives */
+ DuiDatabase *db; /**< Handle to the SQL database */
+ DuiDBConnection *db_conn;
+ DuiResolver *resolver;
+
+ QofBook *tmp_book; /**< Temp staging area for write-outs */
+};
+
+/* ============================================================== */
+
+QofMap *
+qof_map_new (const char *etype, const char *tabname)
+{
+ QofMap *qm;
+
+ if (!etype || !tabname) return NULL;
+
+ qm = g_new0 (QofMap, 1);
+ qm->entity_type = g_strdup (etype);
+ qm->table_name = g_strdup (tabname);
+
+ qm->goto_db = dui_txnquery_new();
+ qm->from_db = dui_txnreport_new("from", 0);
+
+ qm->goto_db_sqlmatch = NULL;
+ qm->goto_db_qofmatch = NULL;
+ qm->from_db_qofmatch = NULL;
+
+ dui_txnquery_set_tablename (qm->goto_db, tabname);
+
+ qm->resolver = dui_resolver_new();
+ dui_txnquery_set_resolver (qm->goto_db, qm->resolver);
+ dui_txnreport_set_resolver (qm->from_db, qm->resolver);
+
+ /* Initialize the more complex parts */
+ qm->tmp_book = qof_book_new();
+
+ return qm;
+}
+
+/* ============================================================== */
+
+void
+qof_map_destroy (QofMap *qm)
+{
+ if (!qm) return;
+
+ if (qm->entity_type) g_free (qm->entity_type);
+ if (qm->table_name) g_free (qm->table_name);
+ if (qm->match_fieldname) g_free (qm->match_fieldname);
+ if (qm->match_property) g_free (qm->match_property);
+
+ if (qm->goto_db) dui_txnquery_destroy (qm->goto_db);
+ if (qm->from_db) dui_txnreport_destroy (qm->from_db);
+ if (qm->resolver) dui_resolver_destroy (qm->resolver);
+
+ if (qm->goto_db_sqlmatch) dui_field_map_destroy (qm->goto_db_sqlmatch);
+ if (qm->goto_db_qofmatch) dui_field_map_destroy (qm->goto_db_qofmatch);
+ if (qm->from_db_qofmatch) dui_field_map_destroy (qm->from_db_qofmatch);
+
+ // XXX close db connection ???
+
+ qof_book_destroy (qm->tmp_book);
+ g_free (qm);
+}
+
+/* ============================================================== */
+
+void
+qof_map_add_field (QofMap *qm, const char *fieldname, const char * property)
+{
+ /* Create bi-drectional set of maps */
+ DuiFieldMap *from_db_fm = dui_field_map_new();
+ DuiFieldMap *goto_db_fm = dui_field_map_new();
+
+ dui_field_set_qof (&from_db_fm->target, qm->entity_type, property);
+ dui_field_set_qof (&goto_db_fm->source, qm->entity_type, property);
+
+ dui_field_set_sql (&from_db_fm->source, fieldname);
+ dui_field_set_sql (&goto_db_fm->target, fieldname);
+
+ dui_txnreport_add_term (qm->from_db, from_db_fm);
+ dui_txnquery_add_term (qm->goto_db, goto_db_fm);
+}
+
+/* ============================================================== */
+
+void
+qof_map_add_match (QofMap *qm, const char *fieldname, const char * property)
+{
+ if (!qm || !fieldname) return;
+
+ if (NULL != qm->goto_db_sqlmatch)
+ {
+ PERR ("Only one match term per map is supported");
+ return;
+ }
+
+ qm->match_fieldname = g_strdup (fieldname);
+ qm->match_property = g_strdup (property);
+
+ qm->goto_db_sqlmatch = dui_field_map_new();
+ qm->goto_db_qofmatch = dui_field_map_new();
+ qm->from_db_qofmatch = dui_field_map_new();
+
+ /* When copying to the DB, we need to indicate which object
+ * is to be copied. The source for the match will be set
+ * during actual runtime
+ */
+ DuiFieldMap *to_sqlmatch = qm->goto_db_sqlmatch;
+ DuiFieldMap *to_qofmatch = qm->goto_db_qofmatch;
+ dui_field_set_where (&to_sqlmatch->target, fieldname, "=");
+ dui_field_set_qof_match (&to_qofmatch->target, qm->entity_type, property);
+
+ dui_txnquery_add_source_match_term (qm->goto_db, to_qofmatch);
+ dui_txnquery_add_term (qm->goto_db, to_sqlmatch);
+
+ /* As above, but for copying from the DB. */
+ DuiFieldMap *fr_match = qm->from_db_qofmatch;
+ dui_field_set_qof_match (&fr_match->target, qm->entity_type, property);
+ dui_txnreport_add_match_term (qm->from_db, fr_match);
+}
+
+/* ============================================================== */
+
+void
+qof_map_add_version_cmp (QofMap *qm, const char *fieldname, const char * property)
+{
+ if (!qm || !fieldname) return;
+
+ /* XXX At this time, this behaves just like an ordinary field ... */
+ qof_map_add_field (qm, fieldname, property);
+}
+
+/* ============================================================== */
+
+void
+qof_map_set_book (QofMap *qm, QofBook *book)
+{
+ qm->book = book;
+
+ /* Each qof field needs to know the book as well. */
+ dui_resolver_resolve_qof (qm->resolver, book);
+}
+
+void
+qof_map_set_database (QofMap *qm, DuiDatabase *db)
+{
+ qm->db = db;
+
+ /* The SQL writer needs to know the database */
+ dui_txnquery_set_database (qm->goto_db, db);
+
+ /* Open the db connection too. */
+ qm->db_conn = dui_database_do_realize (db);
+}
+
+/* ============================================================== */
+/* Get possibly multiple records from DB, and update or create the
+ * matching QOF objects. */
+
+void
+qof_map_copy_multiple_from_db (QofMap *qmap, const char * sql_stmt)
+{
+ if (!qmap || !sql_stmt || 0==sql_stmt[0]) return;
+
+ if (NULL == qmap->from_db_qofmatch)
+ {
+ PERR ("No match term specified, can't copy");
+ return;
+ }
+
+ /* Fetch all from the requested table */
+ DuiDBRecordSet *recs;
+ recs = dui_connection_exec(qmap->db_conn, sql_stmt);
+
+ if (!recs) return;
+ if (0 == dui_recordset_rewind (recs))
+ {
+ dui_recordset_free (recs);
+ return;
+ }
+
+ /* Take the returned SQL records, and update or create the
+ * matching QOF objects. We do this with a duitxnreport report.
+ */
+ DuiField *fld;
+ fld = &qmap->from_db_qofmatch->source;
+ dui_field_set_sql (fld, qmap->match_fieldname);
+
+ dui_txnreport_run (qmap->from_db, recs);
+ dui_recordset_free (recs);
+}
+
+/* ============================================================== */
+/* Get record from DB, find or create the matching QOF object. */
+
+void
+qof_map_copy_from_db (QofMap *qmap, const GUID *guid)
+{
+ if (!qmap || !guid) return;
+
+ if (NULL == qmap->from_db_qofmatch)
+ {
+ PERR ("No match term specified, can't copy");
+ return;
+ }
+
+ char guidstr[GUID_ENCODING_LENGTH+1];
+ guid_to_string_buff (guid, guidstr);
+
+ char * query = g_strdup_printf (
+ "SELECT * FROM %s WHERE %s=\'%s\';",
+ qmap->table_name,
+ qmap->match_fieldname,
+ guidstr);
+
+ /* Fetch all from the requested table */
+ DuiDBRecordSet *recs;
+ recs = dui_connection_exec(qmap->db_conn, query);
+ g_free (query);
+
+ if (!recs) return;
+ if (0 == dui_recordset_rewind (recs))
+ {
+ dui_recordset_free (recs);
+ return;
+ }
+
+ /* Take the returned SQL record, and create a matching QOF object.
+ * We do this with a duitxnreport report.
+ */
+ DuiField *fld;
+ fld = &qmap->from_db_qofmatch->source;
+ dui_field_set_const (fld, guidstr);
+
+ dui_txnreport_run (qmap->from_db, recs);
+ dui_recordset_free (recs);
+}
+
+/* ============================================================== */
+/* 'Copy' a QOF Entity from local memory to the SQL database.
+ * Pass in the Guid of the entity that is to be saved to SQL.
+ */
+void
+qof_map_copy_to_db(QofMap *qmap, const GUID *guid, const char *how)
+{
+ if (!qmap || !guid) return;
+
+ if (NULL == qmap->goto_db_qofmatch)
+ {
+ PERR ("No match term specified, can't copy");
+ return;
+ }
+ /* 'how' should be either "insert" or "update" */
+ dui_txnquery_set_querytype (qmap->goto_db, how);
+
+ char buff[GUID_ENCODING_LENGTH+1];
+ guid_to_string_buff (guid, buff);
+
+ /* We use the GUID as our primary key for all lookups/matches */
+ DuiField *fld;
+ fld = &qmap->goto_db_qofmatch->source;
+ dui_field_set_const (fld, buff);
+
+ fld = &qmap->goto_db_sqlmatch->source;
+ dui_field_set_const (fld, buff);
+
+ /* Do it: issue the SQL insert/update. */
+ DuiDBRecordSet *recs = dui_txnquery_run (qmap->goto_db);
+ dui_recordset_free (recs);
+}
+
+/* ============================================================== */
+/*
+write-out sequence:
+
+lock table
+get version
+if (not exist) insert ;
+else
+ cmp version
+ if (newer) update;
+ else copy-from, including rollback.
+
+unlock table
+*/
+
+void
+write_out (QofMap *qm, QofInstance *inst)
+{
+ if (!inst) return;
+
+ GUID *guid = &QOF_ENTITY(inst)->guid;
+
+ dui_connection_lock(qm->db_conn, qm->table_name);
+ /* Use a temp book when loading from the database */
+ dui_resolver_resolve_qof (qm->resolver, qm->tmp_book);
+ qof_map_copy_from_db (qm, guid);
+
+ /* restore the 'real' book */
+ dui_resolver_resolve_qof (qm->resolver, qm->book);
+
+ /* See if we got something back from the DB */
+ QofCollection *col;
+ col = qof_book_get_collection (qm->tmp_book, QOF_ENTITY(inst)->e_type);
+ QofEntity * db_ent = qof_collection_lookup_entity (col, guid);
+ QofInstance *db_inst = QOF_INSTANCE(db_ent);
+
+ /* If its not already in the database, then insert it in */
+ if (NULL == db_ent)
+ {
+ struct timespec ts = dui_connection_get_now(qm->db_conn);
+ Timespec qts;
+ qts.tv_sec = ts.tv_sec;
+ qts.tv_nsec = ts.tv_nsec;
+ qof_instance_set_last_update (inst, qts);
+ qof_map_copy_to_db (qm, guid, "insert");
+ }
+ else
+ {
+ /* Found it in the database; but who's got the newer version? */
+ int cmp = qof_instance_version_cmp (db_inst, inst);
+ if (0 >= cmp)
+ {
+ struct timespec ts = dui_connection_get_now(qm->db_conn);
+ Timespec qts;
+ qts.tv_sec = ts.tv_sec;
+ qts.tv_nsec = ts.tv_nsec;
+ qof_instance_set_last_update (inst, qts);
+ qof_map_copy_to_db (qm, guid, "update");
+ }
+ else
+ {
+printf ("duude rollback\n");
+ }
+ }
+ dui_connection_unlock(qm->db_conn, qm->table_name);
+}
+
+/* ======================== END OF FILE ========================= */
+/* ============================================================== */
More information about the gnucash-changes
mailing list