/********************************************************************
 * gnc-backend-sql.c: load and save data to SQL                     *
 *                                                                  *
 * 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                   *
\********************************************************************/
/** @file gnc-backend-sql.c
 *  @brief load and save data to SQL
 *  @author Copyright (c) 2006-2008 Phil Longstaff <plongstaff@rogers.com>
 *
 * This file implements the top-level QofBackend API for saving/
 * restoring data to/from an SQL db
 */

#include <stdlib.h>
#include "config.h"

#include <errno.h>
#include <stdint.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>

#include "qof.h"
#include "qofquery-p.h"
#include "qofquerycore-p.h"
#include "Account.h"
#include "TransLog.h"
#include "gnc-engine.h"
#include "SX-book.h"
#include "Recurrence.h"
#include "gncBillTerm.h"
#include "gncTaxTable.h"
#include "gncInvoice.h"

#include "gnc-backend-sql.h"

#include "gnc-account-sql.h"
#include "gnc-book-sql.h"
#include "gnc-budget-sql.h"
#include "gnc-commodity-sql.h"
#include "gnc-lots-sql.h"
#include "gnc-price-sql.h"
#include "gnc-pricedb.h"
#include "gnc-recurrence-sql.h"
#include "gnc-schedxaction-sql.h"
#include "gnc-slots-sql.h"
#include "gnc-transaction-sql.h"

#include "gnc-address-sql.h"
#include "gnc-bill-term-sql.h"
#include "gnc-customer-sql.h"
#include "gnc-employee-sql.h"
#include "gnc-entry-sql.h"
#include "gnc-invoice-sql.h"
#include "gnc-job-sql.h"
#include "gnc-order-sql.h"
#include "gnc-owner-sql.h"
#include "gnc-tax-table-sql.h"
#include "gnc-vendor-sql.h"

#include "gnc-prefs.h"

#if defined( S_SPLINT_S )
#include "splint-defs.h"
#endif

static void gnc_sql_init_object_handlers( void );
static void update_progress( GncSqlBackend* be );
static void finish_progress( GncSqlBackend* be );
static void register_standard_col_type_handlers( void );
static gboolean reset_version_info( GncSqlBackend* be );
/*@ null @*/
static GncSqlStatement* build_insert_statement( GncSqlBackend* be,
        const gchar* table_name,
        QofIdTypeConst obj_name, gpointer pObject,
        const GncSqlColumnTableEntry* table );
/*@ null @*/
static GncSqlStatement* build_update_statement( GncSqlBackend* be,
        const gchar* table_name,
        QofIdTypeConst obj_name, gpointer pObject,
        const GncSqlColumnTableEntry* table );
/*@ null @*/
static GncSqlStatement* build_delete_statement( GncSqlBackend* be,
        const gchar* table_name,
        QofIdTypeConst obj_name, gpointer pObject,
        const GncSqlColumnTableEntry* table );

static GList *post_load_commodities = NULL;

#define TRANSACTION_NAME "trans"

typedef struct
{
    /*@ dependent @*/ QofIdType searchObj;
    /*@ dependent @*/
    gpointer pCompiledQuery;
} gnc_sql_query_info;

/* callback structure */
typedef struct
{
    gboolean is_known;
    gboolean is_ok;
    /*@ dependent @*/
    GncSqlBackend* be;
    /*@ dependent @*/
    QofInstance* inst;
    /*@ dependent @*/
    QofQuery* pQuery;
    /*@ dependent @*/
    gpointer pCompiledQuery;
    /*@ owned @*/
    gnc_sql_query_info* pQueryInfo;
} sql_backend;

static QofLogModule log_module = G_LOG_DOMAIN;

#define SQLITE_PROVIDER_NAME "SQLite"

/* ================================================================= */

void
gnc_sql_init( /*@ unused @*/ GncSqlBackend* be )
{
    static gboolean initialized = FALSE;

    if ( !initialized )
    {
        register_standard_col_type_handlers();
        gnc_sql_init_object_handlers();
        initialized = TRUE;
    }
}

/* ================================================================= */

static void
create_tables_cb( const gchar* type, gpointer data_p, gpointer be_p )
{
    GncSqlObjectBackend* pData = data_p;
    GncSqlBackend* be = be_p;

    g_return_if_fail( type != NULL && data_p != NULL && be_p != NULL );
    g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION );

    if ( pData->create_tables != NULL )
    {
        update_progress( be );
        (pData->create_tables)( be );
    }
}

/* ================================================================= */

/* Main object load order */
static const gchar* fixed_load_order[] =
{ GNC_ID_BOOK, GNC_ID_COMMODITY, GNC_ID_ACCOUNT, GNC_ID_LOT, NULL };

/* Load order for objects from other modules */
static const gchar** other_load_order = NULL;

void
gnc_sql_set_load_order( const gchar** load_order )
{
    other_load_order = load_order;
}

static void
initial_load_cb( const gchar* type, gpointer data_p, gpointer be_p )
{
    GncSqlObjectBackend* pData = data_p;
    GncSqlBackend* be = be_p;
    gint i;

    g_return_if_fail( type != NULL && data_p != NULL && be_p != NULL );
    g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION );

    // Don't need to load anything if it has already been loaded with the fixed order
    for ( i = 0; fixed_load_order[i] != NULL; i++ )
    {
        update_progress( be );
        if ( g_ascii_strcasecmp( type, fixed_load_order[i] ) == 0 ) return;
    }
    if ( other_load_order != NULL )
    {
        for ( i = 0; other_load_order[i] != NULL; i++ )
        {
            update_progress( be );
            if ( g_ascii_strcasecmp( type, other_load_order[i] ) == 0 ) return;
        }
    }

    if ( pData->initial_load != NULL )
    {
        (pData->initial_load)( be );
    }
}

void
gnc_sql_push_commodity_for_postload_processing (GncSqlBackend *be,
						gpointer *comm)
{
    post_load_commodities = g_list_prepend(post_load_commodities, comm);
}

static void
commit_commodity (gpointer data)
{
    gnc_commodity *comm = GNC_COMMODITY (data);
    gnc_sql_commit_commodity (comm);
}

void
gnc_sql_load( GncSqlBackend* be, /*@ dependent @*/ QofBook *book, QofBackendLoadType loadType )
{
    GncSqlObjectBackend* pData;
    gint i;
    Account* root;

    g_return_if_fail( be != NULL );
    g_return_if_fail( book != NULL );

    ENTER( "be=%p, book=%p", be, book );

    be->loading = TRUE;

    if ( loadType == LOAD_TYPE_INITIAL_LOAD )
    {
        g_assert( be->book == NULL );
        be->book = book;

        /* Load any initial stuff. Some of this needs to happen in a certain order */
        for ( i = 0; fixed_load_order[i] != NULL; i++ )
        {
            pData = qof_object_lookup_backend( fixed_load_order[i], GNC_SQL_BACKEND );
            if ( pData->initial_load != NULL )
            {
                update_progress( be );
                (pData->initial_load)( be );
            }
        }
        if ( other_load_order != NULL )
        {
            for ( i = 0; other_load_order[i] != NULL; i++ )
            {
                pData = qof_object_lookup_backend( other_load_order[i], GNC_SQL_BACKEND );
                if ( pData->initial_load != NULL )
                {
                    update_progress( be );
                    (pData->initial_load)( be );
                }
            }
        }

        root = gnc_book_get_root_account( book );
        gnc_account_foreach_descendant( root, (AccountCb)xaccAccountBeginEdit, NULL );

        qof_object_foreach_backend( GNC_SQL_BACKEND, initial_load_cb, be );

        gnc_account_foreach_descendant( root, (AccountCb)xaccAccountCommitEdit, NULL );
    }
    else if ( loadType == LOAD_TYPE_LOAD_ALL )
    {
        // Load all transactions
        gnc_sql_transaction_load_all_tx( be );
    }

    be->loading = FALSE;
    g_list_free_full (post_load_commodities, commit_commodity);
    post_load_commodities = NULL;

    /* Mark the sessoion as clean -- though it should never be marked
     * dirty with this backend
     */
    qof_book_mark_session_saved( book );
    finish_progress( be );

    LEAVE( "" );
}

/* ================================================================= */

static gboolean
write_account_tree( GncSqlBackend* be, Account* root )
{
    GList* descendants;
    /*@ dependent @*/
    GList* node;
    gboolean is_ok = TRUE;

    g_return_val_if_fail( be != NULL, FALSE );
    g_return_val_if_fail( root != NULL, FALSE );

    is_ok = gnc_sql_save_account( be, QOF_INSTANCE(root) );
    if ( is_ok )
    {
        descendants = gnc_account_get_descendants( root );
        for ( node = descendants; node != NULL && is_ok; node = g_list_next(node) )
        {
            is_ok = gnc_sql_save_account( be, QOF_INSTANCE(GNC_ACCOUNT(node->data)) );
            if ( !is_ok ) break;
        }
        g_list_free( descendants );
    }
    update_progress( be );

    return is_ok;
}

static gboolean
write_accounts( GncSqlBackend* be )
{
    gboolean is_ok;

    g_return_val_if_fail( be != NULL, FALSE );

    update_progress( be );
    is_ok = write_account_tree( be, gnc_book_get_root_account( be->book ) );
    if ( is_ok )
    {
        update_progress( be );
        is_ok = write_account_tree( be, gnc_book_get_template_root( be->book ) );
    }

    return is_ok;
}

static int
write_tx( Transaction* tx, gpointer data )
{
    write_objects_t* s = (write_objects_t*)data;

    g_return_val_if_fail( tx != NULL, 0 );
    g_return_val_if_fail( data != NULL, 0 );

    s->is_ok = gnc_sql_save_transaction( s->be, QOF_INSTANCE(tx) );
    update_progress( s->be );

    if ( s->is_ok )
    {
        return 0;
    }
    else
    {
        return 1;
    }
}

static gboolean
write_transactions( GncSqlBackend* be )
{
    write_objects_t data;

    g_return_val_if_fail( be != NULL, FALSE );

    data.be = be;
    data.is_ok = TRUE;
    (void)xaccAccountTreeForEachTransaction(
        gnc_book_get_root_account( be->book ), write_tx, &data );
    update_progress( be );
    return data.is_ok;
}

static gboolean
write_template_transactions( GncSqlBackend* be )
{
    Account* ra;
    write_objects_t data;

    g_return_val_if_fail( be != NULL, FALSE );

    data.is_ok = TRUE;
    data.be = be;
    ra = gnc_book_get_template_root( be->book );
    if ( gnc_account_n_descendants( ra ) > 0 )
    {
        (void)xaccAccountTreeForEachTransaction( ra, write_tx, &data );
        update_progress( be );
    }

    return data.is_ok;
}

static gboolean
write_schedXactions( GncSqlBackend* be )
{
    GList* schedXactions;
    SchedXaction* tmpSX;
    gboolean is_ok = TRUE;

    g_return_val_if_fail( be != NULL, FALSE );

    schedXactions = gnc_book_get_schedxactions( be->book )->sx_list;

    for ( ; schedXactions != NULL && is_ok; schedXactions = schedXactions->next )
    {
        tmpSX = schedXactions->data;
        is_ok = gnc_sql_save_schedxaction( be, QOF_INSTANCE( tmpSX ) );
    }
    update_progress( be );

    return is_ok;
}

static void
write_cb( const gchar* type, gpointer data_p, gpointer be_p )
{
    GncSqlObjectBackend* pData = data_p;
    GncSqlBackend* be = (GncSqlBackend*)be_p;

    g_return_if_fail( type != NULL && data_p != NULL && be_p != NULL );
    g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION );

    if ( pData->write != NULL )
    {
        (void)(pData->write)( be );
        update_progress( be );
    }
}

static void
update_progress( GncSqlBackend* be )
{
    if ( be->be.percentage != NULL )
        (be->be.percentage)( NULL, 101.0 );
}

static void
finish_progress( GncSqlBackend* be )
{
    if ( be->be.percentage != NULL )
        (be->be.percentage)( NULL, -1.0 );
}

void
gnc_sql_sync_all( GncSqlBackend* be, /*@ dependent @*/ QofBook *book )
{
    gboolean is_ok;

    g_return_if_fail( be != NULL );
    g_return_if_fail( book != NULL );

    ENTER( "book=%p, be->book=%p", book, be->book );
    update_progress( be );
    (void)reset_version_info( be );

    /* Create new tables */
    be->is_pristine_db = TRUE;
    qof_object_foreach_backend( GNC_SQL_BACKEND, create_tables_cb, be );

    /* Save all contents */
    be->book = book;
    be->obj_total = 0;
    be->obj_total += 1 + gnc_account_n_descendants( gnc_book_get_root_account( book ) );
    be->obj_total += gnc_book_count_transactions( book );
    be->operations_done = 0;

    is_ok = gnc_sql_connection_begin_transaction( be->conn );

    // FIXME: should write the set of commodities that are used
    //write_commodities( be, book );
    if ( is_ok )
    {
        is_ok = gnc_sql_save_book( be, QOF_INSTANCE(book) );
    }
    if ( is_ok )
    {
        is_ok = write_accounts( be );
    }
    if ( is_ok )
    {
        is_ok = write_transactions( be );
    }
    if ( is_ok )
    {
        is_ok = write_template_transactions( be );
    }
    if ( is_ok )
    {
        is_ok = write_schedXactions( be );
    }
    if ( is_ok )
    {
        qof_object_foreach_backend( GNC_SQL_BACKEND, write_cb, be );
    }
    if ( is_ok )
    {
        is_ok = gnc_sql_connection_commit_transaction( be->conn );
    }
    if ( is_ok )
    {
        be->is_pristine_db = FALSE;

        /* Mark the session as clean -- though it shouldn't ever get
	 * marked dirty with this backend
	 */
        qof_book_mark_session_saved( book );
    }
    else
    {
        if (!qof_backend_check_error ((QofBackend*)be))
            qof_backend_set_error( (QofBackend*)be, ERR_BACKEND_SERVER_ERR );
        is_ok = gnc_sql_connection_rollback_transaction( be->conn );
    }
    finish_progress( be );
    LEAVE( "book=%p", book );
}

/* ================================================================= */
/* Routines to deal with the creation of multiple books. */

void
gnc_sql_begin_edit( GncSqlBackend *be, QofInstance *inst )
{
    g_return_if_fail( be != NULL );
    g_return_if_fail( inst != NULL );

    ENTER( " " );
    LEAVE( "" );
}

void
gnc_sql_rollback_edit( GncSqlBackend *be, QofInstance *inst )
{
    g_return_if_fail( be != NULL );
    g_return_if_fail( inst != NULL );

    ENTER( " " );
    LEAVE( "" );
}

static void
commit_cb( const gchar* type, gpointer data_p, gpointer be_data_p )
{
    GncSqlObjectBackend* pData = data_p;
    sql_backend* be_data = be_data_p;

    g_return_if_fail( type != NULL && pData != NULL && be_data != NULL );
    g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION );

    /* If this has already been handled, or is not the correct handler, return */
    if ( strcmp( pData->type_name, be_data->inst->e_type ) != 0 ) return;
    if ( be_data->is_known ) return;

    if ( pData->commit != NULL )
    {
        be_data->is_ok = (pData->commit)( be_data->be, be_data->inst );
        be_data->is_known = TRUE;
    }
}

/* Commit_edit handler - find the correct backend handler for this object
 * type and call its commit handler
 */
void
gnc_sql_commit_edit( GncSqlBackend *be, QofInstance *inst )
{
    sql_backend be_data;
    gboolean is_dirty;
    gboolean is_destroying;
    gboolean is_infant;

    g_return_if_fail( be != NULL );
    g_return_if_fail( inst != NULL );

    if ( qof_book_is_readonly( be->book ) )
    {
        qof_backend_set_error( (QofBackend*)be, ERR_BACKEND_READONLY );
        (void)gnc_sql_connection_rollback_transaction( be->conn );
        return;
    }
    /* During initial load where objects are being created, don't commit
    anything, but do mark the object as clean. */
    if ( be->loading )
    {
        qof_instance_mark_clean( inst );
        return;
    }

    // The engine has a PriceDB object but it isn't in the database
    if ( strcmp( inst->e_type, "PriceDB" ) == 0 )
    {
        qof_instance_mark_clean( inst );
        qof_book_mark_session_saved( be->book );
        return;
    }

    ENTER( " " );

    is_dirty = qof_instance_get_dirty_flag( inst );
    is_destroying = qof_instance_get_destroying( inst );
    is_infant = qof_instance_get_infant( inst );

    DEBUG( "%s dirty = %d, do_free = %d, infant = %d\n",
    (inst->e_type ? inst->e_type : "(null)"),
    is_dirty, is_destroying, is_infant );

    if ( !is_dirty && !is_destroying )
    {
        LEAVE( "!dirty OR !destroying" );
        return;
    }

    if ( !gnc_sql_connection_begin_transaction( be->conn ) )
    {
        PERR( "gnc_sql_commit_edit(): begin_transaction failed\n" );
        LEAVE( "Rolled back - database transaction begin error" );
        return;
    }

    be_data.is_known = FALSE;
    be_data.be = be;
    be_data.inst = inst;
    be_data.is_ok = TRUE;

    qof_object_foreach_backend( GNC_SQL_BACKEND, commit_cb, &be_data );

    if ( !be_data.is_known )
    {
        PERR( "gnc_sql_commit_edit(): Unknown object type '%s'\n", inst->e_type );
        (void)gnc_sql_connection_rollback_transaction( be->conn );

        // Don't let unknown items still mark the book as being dirty
        qof_book_mark_session_saved( be->book );
        qof_instance_mark_clean(inst);
        LEAVE( "Rolled back - unknown object type" );
        return;
    }
    if ( !be_data.is_ok )
    {
        // Error - roll it back
        (void)gnc_sql_connection_rollback_transaction( be->conn );

        // This *should* leave things marked dirty
        LEAVE( "Rolled back - database error" );
        return;
    }

    (void)gnc_sql_connection_commit_transaction( be->conn );

    qof_book_mark_session_saved( be->book );
    qof_instance_mark_clean(inst);

    LEAVE( "" );
}
/* ---------------------------------------------------------------------- */

/* Query processing */
static void
handle_and_term( QofQueryTerm* pTerm, GString* sql )
{
    GSList* pParamPath;
    QofQueryPredData* pPredData;
    gboolean isInverted;
    GSList* name;
    gchar val[G_ASCII_DTOSTR_BUF_SIZE];

    g_return_if_fail( pTerm != NULL );
    g_return_if_fail( sql != NULL );

    pParamPath = qof_query_term_get_param_path( pTerm );
    pPredData = qof_query_term_get_pred_data( pTerm );
    isInverted = qof_query_term_is_inverted( pTerm );

    if ( strcmp( pPredData->type_name, QOF_TYPE_GUID ) == 0 )
    {
        query_guid_t guid_data = (query_guid_t)pPredData;
        GList* guid_entry;

        for ( name = pParamPath; name != NULL; name = name->next )
        {
            if ( name != pParamPath ) g_string_append( sql, "." );
            g_string_append( sql, name->data );
        }

        if ( guid_data->options == QOF_GUID_MATCH_ANY )
        {
            if ( isInverted ) g_string_append( sql, " NOT " );
            g_string_append( sql, " IN (" );
        }
        for ( guid_entry = guid_data->guids; guid_entry != NULL; guid_entry = guid_entry->next )
        {
            if ( guid_entry != guid_data->guids ) g_string_append( sql, "." );
            (void)guid_to_string_buff( guid_entry->data, val );
            g_string_append( sql, "'" );
            g_string_append( sql, val );
            g_string_append( sql, "'" );
        }
        if ( guid_data->options == QOF_GUID_MATCH_ANY )
        {
            g_string_append( sql, ")" );
        }
    }

    g_string_append( sql, "(" );
    if ( isInverted )
    {
        g_string_append( sql, "!" );
    }

    for ( name = pParamPath; name != NULL; name = name->next )
    {
        if ( name != pParamPath ) g_string_append( sql, "." );
        g_string_append( sql, name->data );
    }

    if ( pPredData->how == QOF_COMPARE_LT )
    {
        g_string_append( sql, "<" );
    }
    else if ( pPredData->how == QOF_COMPARE_LTE )
    {
        g_string_append( sql, "<=" );
    }
    else if ( pPredData->how == QOF_COMPARE_EQUAL )
    {
        g_string_append( sql, "=" );
    }
    else if ( pPredData->how == QOF_COMPARE_GT )
    {
        g_string_append( sql, ">" );
    }
    else if ( pPredData->how == QOF_COMPARE_GTE )
    {
        g_string_append( sql, ">=" );
    }
    else if ( pPredData->how == QOF_COMPARE_NEQ )
    {
        g_string_append( sql, "~=" );
    }
    else
    {
        g_string_append( sql, "??" );
    }

    if ( strcmp( pPredData->type_name, "string" ) == 0 )
    {
        query_string_t pData = (query_string_t)pPredData;
        g_string_append( sql, "'" );
        g_string_append( sql, pData->matchstring );
        g_string_append( sql, "'" );
    }
    else if ( strcmp( pPredData->type_name, "date" ) == 0 )
    {
        query_date_t pData = (query_date_t)pPredData;

        (void)gnc_timespec_to_iso8601_buff( pData->date, val );
        g_string_append( sql, "'" );
        //g_string_append( sql, val, 4+1+2+1+2 );
        g_string_append( sql, "'" );
    }
    else if ( strcmp( pPredData->type_name, "numeric" ) == 0 )
    {
        /* query_numeric_t pData = (query_numeric_t)pPredData; */

        g_string_append( sql, "numeric" );
    }
    else if ( strcmp( pPredData->type_name, QOF_TYPE_GUID ) == 0 )
    {
    }
    else if ( strcmp( pPredData->type_name, "gint32" ) == 0 )
    {
        query_int32_t pData = (query_int32_t)pPredData;

        sprintf( val, "%d", pData->val );
        g_string_append( sql, val );
    }
    else if ( strcmp( pPredData->type_name, "gint64" ) == 0 )
    {
        query_int64_t pData = (query_int64_t)pPredData;

        sprintf( val, "%" G_GINT64_FORMAT, pData->val );
        g_string_append( sql, val );
    }
    else if ( strcmp( pPredData->type_name, "double" ) == 0 )
    {
        query_double_t pData = (query_double_t)pPredData;

        g_ascii_dtostr( val, sizeof(val), pData->val );
        g_string_append( sql, val );
    }
    else if ( strcmp( pPredData->type_name, "boolean" ) == 0 )
    {
        query_boolean_t pData = (query_boolean_t)pPredData;

        sprintf( val, "%d", pData->val );
        g_string_append( sql, val );
    }
    else
    {
        g_assert( FALSE );
    }

    g_string_append( sql, ")" );
}

static void
compile_query_cb( const gchar* type, gpointer data_p, gpointer be_data_p )
{
    GncSqlObjectBackend* pData = data_p;
    sql_backend* be_data = be_data_p;

    g_return_if_fail( type != NULL && pData != NULL && be_data != NULL );
    g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION );

    // Is this the right item?
    if ( strcmp( type, be_data->pQueryInfo->searchObj ) != 0 ) return;
    if ( be_data->is_ok ) return;

    if ( pData->compile_query != NULL )
    {
        be_data->pQueryInfo->pCompiledQuery = (pData->compile_query)(
            be_data->be,
            be_data->pQuery );
        be_data->is_ok = TRUE;
    }
}

gchar* gnc_sql_compile_query_to_sql( GncSqlBackend* be, QofQuery* query );

/*@ null @*/
gpointer
gnc_sql_compile_query( QofBackend* pBEnd, QofQuery* pQuery )
{
    GncSqlBackend *be = (GncSqlBackend*)pBEnd;
    QofIdType searchObj;
    sql_backend be_data;
    gnc_sql_query_info* pQueryInfo;

    g_return_val_if_fail( pBEnd != NULL, NULL );
    g_return_val_if_fail( pQuery != NULL, NULL );

    ENTER( " " );

//gnc_sql_compile_query_to_sql( be, pQuery );
    searchObj = qof_query_get_search_for( pQuery );

    pQueryInfo = g_malloc( (gsize)sizeof( gnc_sql_query_info ) );
    g_assert( pQueryInfo != NULL );
    pQueryInfo->pCompiledQuery = NULL;
    pQueryInfo->searchObj = searchObj;

    // Try various objects first
    be_data.is_ok = FALSE;
    be_data.be = be;
    be_data.pQuery = pQuery;
    be_data.pQueryInfo = pQueryInfo;

    qof_object_foreach_backend( GNC_SQL_BACKEND, compile_query_cb, &be_data );
    if ( be_data.is_ok )
    {
        LEAVE( "" );
        return be_data.pQueryInfo;
    }

    LEAVE( "" );

    return pQueryInfo;
}

static const gchar*
convert_search_obj( QofIdType objType )
{
    return (gchar*)objType;
}

gchar*
gnc_sql_compile_query_to_sql( GncSqlBackend* be, QofQuery* query )
{
    QofIdType searchObj;
    GString* sql;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( query != NULL, NULL );

    searchObj = qof_query_get_search_for( query );

    /* Convert search object type to table name */
    sql = g_string_new( "" );
    g_string_append( sql, "SELECT * FROM " );
    g_string_append( sql, convert_search_obj( searchObj ) );
    if ( !qof_query_has_terms( query ) )
    {
        g_string_append( sql, ";" );
    }
    else
    {
        GList* orterms = qof_query_get_terms( query );
        GList* orTerm;

        g_string_append( sql, " WHERE " );

        for ( orTerm = orterms; orTerm != NULL; orTerm = orTerm->next )
        {
            GList* andterms = (GList*)orTerm->data;
            GList* andTerm;

            if ( orTerm != orterms ) g_string_append( sql, " OR " );
            g_string_append( sql, "(" );
            for ( andTerm = andterms; andTerm != NULL; andTerm = andTerm->next )
            {
                if ( andTerm != andterms ) g_string_append( sql, " AND " );
                handle_and_term( (QofQueryTerm*)andTerm->data, sql );
            }
            g_string_append( sql, ")" );
        }
    }

    DEBUG( "Compiled: %s\n", sql->str );
    return g_string_free( sql, FALSE );
}

static void
free_query_cb( const gchar* type, gpointer data_p, gpointer be_data_p )
{
    GncSqlObjectBackend* pData = data_p;
    sql_backend* be_data = be_data_p;

    g_return_if_fail( type != NULL && pData != NULL && be_data != NULL );
    g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION );
    if ( be_data->is_ok ) return;
    if ( strcmp( type, be_data->pQueryInfo->searchObj ) != 0 ) return;

    if ( pData->free_query != NULL )
    {
        (pData->free_query)( be_data->be, be_data->pCompiledQuery );
        be_data->is_ok = TRUE;
    }
}

void
gnc_sql_free_query( QofBackend* pBEnd, gpointer pQuery )
{
    GncSqlBackend *be = (GncSqlBackend*)pBEnd;
    gnc_sql_query_info* pQueryInfo = (gnc_sql_query_info*)pQuery;
    sql_backend be_data;

    g_return_if_fail( pBEnd != NULL );
    g_return_if_fail( pQuery != NULL );

    ENTER( " " );

    // Try various objects first
    be_data.is_ok = FALSE;
    be_data.be = be;
    be_data.pCompiledQuery = pQuery;
    be_data.pQueryInfo = pQueryInfo;

    qof_object_foreach_backend( GNC_SQL_BACKEND, free_query_cb, &be_data );
    if ( be_data.is_ok )
    {
        LEAVE( "" );
        return;
    }

    if ( pQueryInfo->pCompiledQuery != NULL )
    {
        DEBUG( "%s\n", (gchar*)pQueryInfo->pCompiledQuery );
        g_free( pQueryInfo->pCompiledQuery );
    }
    g_free( pQueryInfo );

    LEAVE( "" );
}

static void
run_query_cb( const gchar* type, gpointer data_p, gpointer be_data_p )
{
    GncSqlObjectBackend* pData = data_p;
    sql_backend* be_data = be_data_p;

    g_return_if_fail( type != NULL && pData != NULL && be_data != NULL );
    g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION );
    if ( be_data->is_ok ) return;

    // Is this the right item?
    if ( strcmp( type, be_data->pQueryInfo->searchObj ) != 0 ) return;

    if ( pData->run_query != NULL )
    {
        (pData->run_query)( be_data->be, be_data->pCompiledQuery );
        be_data->is_ok = TRUE;
    }
}

void
gnc_sql_run_query( QofBackend* pBEnd, gpointer pQuery )
{
    GncSqlBackend *be = (GncSqlBackend*)pBEnd;
    gnc_sql_query_info* pQueryInfo = (gnc_sql_query_info*)pQuery;
    sql_backend be_data;

    g_return_if_fail( pBEnd != NULL );
    g_return_if_fail( pQuery != NULL );
    g_return_if_fail( !be->in_query );

    ENTER( " " );

    be->loading = TRUE;
    be->in_query = TRUE;

    qof_event_suspend();

    // Try various objects first
    be_data.is_ok = FALSE;
    be_data.be = be;
    be_data.pCompiledQuery = pQueryInfo->pCompiledQuery;
    be_data.pQueryInfo = pQueryInfo;

    qof_object_foreach_backend( GNC_SQL_BACKEND, run_query_cb, &be_data );
    be->loading = FALSE;
    be->in_query = FALSE;
    qof_event_resume();
//    if( be_data.is_ok ) {
//        LEAVE( "" );
//        return;
//    }

    // Mark the book as clean
    qof_instance_mark_clean( QOF_INSTANCE(be->book) );

//    DEBUG( "%s\n", (gchar*)pQueryInfo->pCompiledQuery );

    LEAVE( "" );
}

/* ================================================================= */
/* Order in which business objects need to be loaded */
static const gchar* business_fixed_load_order[] =
{ GNC_ID_BILLTERM, GNC_ID_TAXTABLE, GNC_ID_INVOICE, NULL };

static void
business_core_sql_init(void)
{
    /* Initialize our pointers into the backend subsystem */
    gnc_address_sql_initialize();
    gnc_billterm_sql_initialize();
    gnc_customer_sql_initialize();
    gnc_employee_sql_initialize();
    gnc_entry_sql_initialize();
    gnc_invoice_sql_initialize();
    gnc_job_sql_initialize();
    gnc_order_sql_initialize();
    gnc_owner_sql_initialize();
    gnc_taxtable_sql_initialize();
    gnc_vendor_sql_initialize();

    gnc_sql_set_load_order( business_fixed_load_order );
}

static void
gnc_sql_init_object_handlers( void )
{
    gnc_sql_init_book_handler();
    gnc_sql_init_commodity_handler();
    gnc_sql_init_account_handler();
    gnc_sql_init_budget_handler();
    gnc_sql_init_price_handler();
    gnc_sql_init_transaction_handler();
    gnc_sql_init_slots_handler();
    gnc_sql_init_recurrence_handler();
    gnc_sql_init_schedxaction_handler();
    gnc_sql_init_lot_handler();

    /* And the business objects */
    business_core_sql_init();
}

/* ================================================================= */

gint64
gnc_sql_get_integer_value( const GValue* value )
{
    g_return_val_if_fail( value != NULL, 0 );

    if ( G_VALUE_HOLDS_INT(value) )
    {
        return (gint64)g_value_get_int( value );
    }
    else if ( G_VALUE_HOLDS_UINT(value) )
    {
        return (gint64)g_value_get_uint( value );
    }
    else if ( G_VALUE_HOLDS_LONG(value) )
    {
        return (gint64)g_value_get_long( value );
    }
    else if ( G_VALUE_HOLDS_ULONG(value) )
    {
        return (gint64)g_value_get_ulong( value );
    }
    else if ( G_VALUE_HOLDS_INT64(value) )
    {
        return g_value_get_int64( value );
    }
    else if ( G_VALUE_HOLDS_UINT64(value) )
    {
        return (gint64)g_value_get_uint64( value );
    }
    else if ( G_VALUE_HOLDS_STRING( value ) )
    {
        return g_ascii_strtoll( g_value_get_string( value ), NULL, 10 );
    }
    else
    {
        PWARN( "Unknown type: %s", G_VALUE_TYPE_NAME( value ) );
    }

    return 0;
}

/* ----------------------------------------------------------------- */
/*@ null @*/ static gpointer
get_autoinc_id()
{
    // Just need a 0 to force a new autoinc value
    return (gpointer)0;
}

static void
set_autoinc_id()
{
    // Nowhere to put the ID
}

/*@ null @*/ QofAccessFunc
gnc_sql_get_getter( QofIdTypeConst obj_name, const GncSqlColumnTableEntry* table_row )
{
    QofAccessFunc getter;

    g_return_val_if_fail( obj_name != NULL, NULL );
    g_return_val_if_fail( table_row != NULL, NULL );

    if ( (table_row->flags & COL_AUTOINC) != 0 )
    {
        getter = get_autoinc_id;
    }
    else if ( table_row->qof_param_name != NULL )
    {
        getter = qof_class_get_parameter_getter( obj_name,
        table_row->qof_param_name );
    }
    else
    {
        getter = table_row->getter;
    }

    return getter;
}

/* ----------------------------------------------------------------- */
void
gnc_sql_add_colname_to_list( const GncSqlColumnTableEntry* table_row, GList** pList )
{
    (*pList) = g_list_append( (*pList), g_strdup( table_row->col_name ) );
}

/* ----------------------------------------------------------------- */
void
gnc_sql_add_subtable_colnames_to_list( const GncSqlColumnTableEntry* table_row, const GncSqlColumnTableEntry* subtable,
GList** pList )
{
    const GncSqlColumnTableEntry* subtable_row;
    gchar* buf;

    for ( subtable_row = subtable; subtable_row->col_name != NULL; subtable_row++ )
    {
        buf = g_strdup_printf( "%s_%s", table_row->col_name, subtable_row->col_name );
        (*pList) = g_list_append( (*pList), buf );
    }
}

static GncSqlColumnInfo*
create_column_info( const GncSqlColumnTableEntry* table_row, GncSqlBasicColumnType type,
gint size, gboolean is_unicode )
{
    GncSqlColumnInfo* info;

    info = g_new0( GncSqlColumnInfo, 1 );
    g_assert( info != NULL );
    info->name = g_strdup( table_row->col_name );
    info->type = type;
    info->size = size;
    info->is_primary_key = ((table_row->flags & COL_PKEY) != 0) ? TRUE : FALSE;
    info->null_allowed = ((table_row->flags & COL_NNUL) != 0) ? FALSE : TRUE;
    info->is_unicode = is_unicode;
    info->is_autoinc = ((table_row->flags & COL_AUTOINC) != 0) ? TRUE : FALSE;

    return info;
}

/* ----------------------------------------------------------------- */
static void
load_string( const GncSqlBackend* be, GncSqlRow* row,
/*@ null @*/ QofSetterFunc setter, gpointer pObject,
const GncSqlColumnTableEntry* table_row )
{
    const GValue* val;
    const gchar* s;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL );

    val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name );
    g_return_if_fail( val != NULL );
    s = g_value_get_string( val );
    if ( table_row->gobj_param_name != NULL )
    {
        g_object_set( pObject, table_row->gobj_param_name, s, NULL );
    }
    else
    {
        g_return_if_fail( setter != NULL );
        (*setter)( pObject, (const gpointer)s );
    }
}

static void
add_string_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row,
GList** pList )
{
    GncSqlColumnInfo* info;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    info = create_column_info( table_row, BCT_STRING, table_row->size, TRUE );

    *pList = g_list_append( *pList, info );
}

static void
add_gvalue_string_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    QofAccessFunc getter;
    gchar* s = NULL;
    GValue* value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    value = g_new0( GValue, 1 );
    g_assert( value != NULL );
    memset( value, 0, sizeof( GValue ) );
    if ( table_row->gobj_param_name != NULL )
    {
        g_object_get( pObject, table_row->gobj_param_name, &s, NULL );
    }
    else
    {
        getter = gnc_sql_get_getter( obj_name, table_row );
        if ( getter != NULL )
        {
            s = (gchar*)(*getter)( pObject, NULL );
            if ( s != NULL )
            {
                s = g_strdup( s );
            }
        }
    }
    (void)g_value_init( value, G_TYPE_STRING );
    if ( s )
    {
        g_value_take_string( value, s );
    }

    (*pList) = g_slist_append( (*pList), value );
}

static GncSqlColumnTypeHandler string_handler
=
{
    load_string,
    add_string_col_info_to_list,
    gnc_sql_add_colname_to_list,
    add_gvalue_string_to_slist
};
/* ----------------------------------------------------------------- */
typedef gint (*IntAccessFunc)( const gpointer );
typedef void (*IntSetterFunc)( const gpointer, gint );

static void
load_int( const GncSqlBackend* be, GncSqlRow* row,
          /*@ null @*/ QofSetterFunc setter, gpointer pObject,
          const GncSqlColumnTableEntry* table_row )
{
    const GValue* val;
    gint int_value;
    IntSetterFunc i_setter;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL );

    val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name );
    if ( val == NULL )
    {
        int_value = 0;
    }
    else
    {
        int_value = (gint)gnc_sql_get_integer_value( val );
    }
    if ( table_row->gobj_param_name != NULL )
    {
        g_object_set( pObject, table_row->gobj_param_name, int_value, NULL );
    }
    else
    {
        g_return_if_fail( setter != NULL );
        i_setter = (IntSetterFunc)setter;
        (*i_setter)( pObject, int_value );
    }
}

static void
add_int_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row,
                          GList** pList )
{
    GncSqlColumnInfo* info;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    info = create_column_info( table_row, BCT_INT, 0, FALSE );

    *pList = g_list_append( *pList, info );
}

static void
add_gvalue_int_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
                         const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    gint int_value = 0;
    IntAccessFunc i_getter;
    GValue* value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    value = g_new0( GValue, 1 );
    g_assert( value != NULL );
    (void)g_value_init( value, G_TYPE_INT );

    if ( table_row->gobj_param_name != NULL )
    {
        g_object_get_property( pObject, table_row->gobj_param_name, value );
    }
    else
    {
        i_getter = (IntAccessFunc)gnc_sql_get_getter( obj_name, table_row );
        if ( i_getter != NULL )
        {
            int_value = (*i_getter)( pObject );
        }
        g_value_set_int( value, int_value );
    }

    (*pList) = g_slist_append( (*pList), value );
}

static GncSqlColumnTypeHandler int_handler
=
{
    load_int,
    add_int_col_info_to_list,
    gnc_sql_add_colname_to_list,
    add_gvalue_int_to_slist
};
/* ----------------------------------------------------------------- */
typedef gboolean (*BooleanAccessFunc)( const gpointer );
typedef void (*BooleanSetterFunc)( const gpointer, gboolean );

static void
load_boolean( const GncSqlBackend* be, GncSqlRow* row,
              /*@ null @*/ QofSetterFunc setter, gpointer pObject,
              const GncSqlColumnTableEntry* table_row )
{
    const GValue* val;
    gint int_value;
    BooleanSetterFunc b_setter;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL );

    val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name );
    if ( val == NULL )
    {
        int_value = 0;
    }
    else
    {
        int_value = (gint)gnc_sql_get_integer_value( val );
    }
    if ( table_row->gobj_param_name != NULL )
    {
        g_object_set( pObject, table_row->gobj_param_name, int_value, NULL );
    }
    else
    {
        g_return_if_fail( setter != NULL );
        b_setter = (BooleanSetterFunc)setter;
        (*b_setter)( pObject, (int_value != 0) ? TRUE : FALSE );
    }
}

static void
add_boolean_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row,
                              GList** pList )
{
    GncSqlColumnInfo* info;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    info = create_column_info( table_row, BCT_INT, 0, FALSE );

    *pList = g_list_append( *pList, info );
}

static void
add_gvalue_boolean_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
                             const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    gint int_value = 0;
    BooleanAccessFunc b_getter;
    GValue* value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    value = g_new0( GValue, 1 );
    g_assert( value != NULL );

    if ( table_row->gobj_param_name != NULL )
    {
        g_object_get( pObject, table_row->gobj_param_name, &int_value, NULL );
    }
    else
    {
        b_getter = (BooleanAccessFunc)gnc_sql_get_getter( obj_name, table_row );
        if ( b_getter != NULL )
        {
            int_value = ((*b_getter)( pObject )) ? 1 : 0;
        }
    }
    (void)g_value_init( value, G_TYPE_INT );
    g_value_set_int( value, int_value );

    (*pList) = g_slist_append( (*pList), value );
}

static GncSqlColumnTypeHandler boolean_handler
=
{
    load_boolean,
    add_boolean_col_info_to_list,
    gnc_sql_add_colname_to_list,
    add_gvalue_boolean_to_slist
};
/* ----------------------------------------------------------------- */
typedef gint64 (*Int64AccessFunc)( const gpointer );
typedef void (*Int64SetterFunc)( const gpointer, gint64 );

static void
load_int64( const GncSqlBackend* be, GncSqlRow* row,
            /*@ null @*/ QofSetterFunc setter, gpointer pObject,
            const GncSqlColumnTableEntry* table_row )
{
    const GValue* val;
    gint64 i64_value = 0;
    Int64SetterFunc i64_setter = (Int64SetterFunc)setter;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL );

    val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name );
    if ( val != NULL )
    {
        i64_value = gnc_sql_get_integer_value( val );
    }
    if ( table_row->gobj_param_name != NULL )
    {
        g_object_set( pObject, table_row->gobj_param_name, i64_value, NULL );
    }
    else
    {
        (*i64_setter)( pObject, i64_value );
    }
}

static void
add_int64_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row,
                            GList** pList )
{
    GncSqlColumnInfo* info;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    info = create_column_info( table_row, BCT_INT64, 0, FALSE );

    *pList = g_list_append( *pList, info );
}

static void
add_gvalue_int64_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
                           const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    gint64 i64_value = 0;
    Int64AccessFunc getter;
    GValue* value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    value = g_new0( GValue, 1 );
    g_assert( value != NULL );
    if ( table_row->gobj_param_name != NULL )
    {
        g_object_get( pObject, table_row->gobj_param_name, &i64_value, NULL );
    }
    else
    {
        getter = (Int64AccessFunc)gnc_sql_get_getter( obj_name, table_row );
        if ( getter != NULL )
        {
            i64_value = (*getter)( pObject );
        }
    }
    (void)g_value_init( value, G_TYPE_INT64 );
    g_value_set_int64( value, i64_value );

    (*pList) = g_slist_append( (*pList), value );
}

static GncSqlColumnTypeHandler int64_handler
=
{
    load_int64,
    add_int64_col_info_to_list,
    gnc_sql_add_colname_to_list,
    add_gvalue_int64_to_slist
};
/* ----------------------------------------------------------------- */

static void
load_double( const GncSqlBackend* be, GncSqlRow* row,
             /*@ null @*/ QofSetterFunc setter, gpointer pObject,
             const GncSqlColumnTableEntry* table_row )
{
    const GValue* val;
    gdouble d_value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL );

    val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name );
    if ( val == NULL )
    {
        (*setter)( pObject, (gpointer)NULL );
    }
    else
    {
        if ( G_VALUE_HOLDS(val, G_TYPE_INT) )
        {
            d_value = (gdouble)g_value_get_int( val );
        }
        else if ( G_VALUE_HOLDS(val, G_TYPE_FLOAT) )
        {
            d_value = g_value_get_float( val );
        }
        else if (G_VALUE_HOLDS(val, G_TYPE_DOUBLE) )
        {
            d_value = g_value_get_double( val );
        }
        else
        {
            PWARN( "Unknown float value type: %s\n", g_type_name( G_VALUE_TYPE(val) ) );
            d_value = 0;
        }
        if ( table_row->gobj_param_name != NULL )
        {
            g_object_set( pObject, table_row->gobj_param_name, d_value, NULL );
        }
        else
        {
            (*setter)( pObject, (gpointer)&d_value );
        }
    }
}

static void
add_double_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row,
                             GList** pList )
{
    GncSqlColumnInfo* info;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    info = create_column_info( table_row, BCT_DOUBLE, 0, FALSE );

    *pList = g_list_append( *pList, info );
}

static void
add_gvalue_double_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
                            const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    QofAccessFunc getter;
    gdouble* pDouble = NULL;
    gdouble d_value;
    GValue* value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );

    value = g_new0( GValue, 1 );
    g_assert( value != NULL );
    getter = gnc_sql_get_getter( obj_name, table_row );
    if ( getter != NULL )
    {
        pDouble = (*getter)( pObject, NULL );
    }
    if ( pDouble != NULL )
    {
        d_value = *pDouble;
        (void)g_value_init( value, G_TYPE_DOUBLE );
        g_value_set_double( value, d_value );
    }
    else
    {
        (void)g_value_init( value, G_TYPE_DOUBLE );
        g_value_set_double( value, 0.0 );
    }

    (*pList) = g_slist_append( (*pList), value );
}

static GncSqlColumnTypeHandler double_handler
=
{
    load_double,
    add_double_col_info_to_list,
    gnc_sql_add_colname_to_list,
    add_gvalue_double_to_slist
};
/* ----------------------------------------------------------------- */

static void
load_guid( const GncSqlBackend* be, GncSqlRow* row,
           /*@ null @*/ QofSetterFunc setter, gpointer pObject,
           const GncSqlColumnTableEntry* table_row )
{
    const GValue* val;
    GncGUID guid;
    const GncGUID* pGuid;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL );

    val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name );
    if ( val == NULL || g_value_get_string( val ) == NULL )
    {
        pGuid = NULL;
    }
    else
    {
        (void)string_to_guid( g_value_get_string( val ), &guid );
        pGuid = &guid;
    }
    if ( pGuid != NULL )
    {
        if ( table_row->gobj_param_name != NULL )
        {
            g_object_set( pObject, table_row->gobj_param_name, pGuid, NULL );
        }
        else
        {
            g_return_if_fail( setter != NULL );
            (*setter)( pObject, (const gpointer)pGuid );
        }
    }
}

static void
add_guid_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row,
                           GList** pList )
{
    GncSqlColumnInfo* info;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    info = create_column_info( table_row, BCT_STRING, GUID_ENCODING_LENGTH, FALSE );

    *pList = g_list_append( *pList, info );
}

static void
add_gvalue_guid_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
                          const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    QofAccessFunc getter;
    GncGUID* guid = NULL;
    gchar guid_buf[GUID_ENCODING_LENGTH+1];
    GValue* value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );

    value = g_new0( GValue, 1 );
    g_assert( value != NULL );
    if ( table_row->gobj_param_name != NULL )
    {
        g_object_get( pObject, table_row->gobj_param_name, &guid, NULL );
    }
    else
    {
        getter = gnc_sql_get_getter( obj_name, table_row );
        if ( getter != NULL )
        {
            guid = (*getter)( pObject, NULL );
        }
    }
    (void)g_value_init( value, G_TYPE_STRING );
    if ( guid != NULL )
    {
        (void)guid_to_string_buff( guid, guid_buf );
        g_value_set_string( value, guid_buf );
    }

    (*pList) = g_slist_append( (*pList), value );

}

static GncSqlColumnTypeHandler guid_handler
=
{
    load_guid,
    add_guid_col_info_to_list,
    gnc_sql_add_colname_to_list,
    add_gvalue_guid_to_slist
};
/* ----------------------------------------------------------------- */

void
gnc_sql_add_gvalue_objectref_guid_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
        const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    QofAccessFunc getter;
    const GncGUID* guid = NULL;
    gchar guid_buf[GUID_ENCODING_LENGTH+1];
    QofInstance* inst = NULL;
    GValue* value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );

    value = g_new0( GValue, 1 );
    g_assert( value != NULL );
    if ( table_row->gobj_param_name != NULL )
    {
        g_object_get( pObject, table_row->gobj_param_name, &inst, NULL );
    }
    else
    {
        getter = gnc_sql_get_getter( obj_name, table_row );
        if ( getter != NULL )
        {
            inst = (*getter)( pObject, NULL );
        }
    }
    if ( inst != NULL )
    {
        guid = qof_instance_get_guid( inst );
    }
    (void)g_value_init( value, G_TYPE_STRING );
    if ( guid != NULL )
    {
        (void)guid_to_string_buff( guid, guid_buf );
        g_value_set_string( value, guid_buf );
    }

    (*pList) = g_slist_append( (*pList), value );
}

void
gnc_sql_add_objectref_guid_col_info_to_list( const GncSqlBackend* be,
        const GncSqlColumnTableEntry* table_row,
        GList** pList )
{
    add_guid_col_info_to_list( be, table_row, pList );
}

/* ----------------------------------------------------------------- */
typedef Timespec (*TimespecAccessFunc)( const gpointer );
typedef void (*TimespecSetterFunc)( const gpointer, Timespec );

#define TIMESPEC_STR_FORMAT "%04d%02d%02d%02d%02d%02d"
#define TIMESPEC_ALT_FORMAT "%04d-%02d%-02d %02d:%02d:%02d"
#define TIMESPEC_COL_SIZE (4+2+2+2+2+2)
#define TIMESPEC_ALT_COL_SIZE (4+3+3+3+3+3)

/* This is required because we're passing be->timespace_format to
 * g_strdup_printf.
 */
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
gchar*
gnc_sql_convert_timespec_to_string( const GncSqlBackend* be, Timespec ts )
{
    time64 time;
    struct tm* tm;
    gint year;
    gchar* datebuf;

    time = timespecToTime64( ts );
    tm = gnc_gmtime( &time );

    year = tm->tm_year + 1900;

    datebuf = g_strdup_printf( be->timespec_format,
                               year, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec );
    gnc_tm_free (tm);
    return datebuf;
}
#pragma GCC diagnostic warning "-Wformat-nonliteral"

static void
load_timespec( const GncSqlBackend* be, GncSqlRow* row,
               /*@ null @*/ QofSetterFunc setter, gpointer pObject,
               const GncSqlColumnTableEntry* table_row )
{
    const GValue* val;
    Timespec ts = {0, 0};
    TimespecSetterFunc ts_setter;
    gboolean isOK = FALSE;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL );

    ts_setter = (TimespecSetterFunc)setter;
    val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name );
    if ( val == NULL )
    {
        isOK = TRUE;
    }
    else
    {
        /* MySQL's TIMESTAMP type is not compliant with the SQL
         * standard in that it is valid only from 1970-01-01 00:00:01
         * to 2038-01-15 23:59:59. Times outside that range are set to
         * "0000-00-00 00:00:00"; depending on the libdbi version we
         * may get either the string value or the time64 representing
         * it. In either case leave the timespec at 0.
         */
        if ( G_VALUE_HOLDS_INT64( val ) )
        {
            const time64 MINTIME = INT64_C(-62135596800);
            const time64 MAXTIME = INT64_C(253402300799);
            const time64 t = (time64)(g_value_get_int64 (val));
            if (t >= MINTIME && t <= MAXTIME)
            {
                timespecFromTime64 (&ts, t);
            }
            isOK = TRUE;
	}
	else if (G_VALUE_HOLDS_STRING (val))
	{
            const gchar* s = g_value_get_string( val );
            if ( s != NULL && strlen(s) == TIMESPEC_COL_SIZE)
            {
                gchar* buf;
                buf = g_strdup_printf( "%c%c%c%c-%c%c-%c%c %c%c:%c%c:%c%c",
                                       s[0], s[1], s[2], s[3],
                                       s[4], s[5],
                                       s[6], s[7],
                                       s[8], s[9],
                                       s[10], s[11],
                                       s[12], s[13] );
                ts = gnc_iso8601_to_timespec_gmt( buf );
                g_free( buf );
                isOK = TRUE;
            }
            else if (! g_str_has_prefix (s, "0000-00-00"))
            {
                ts = gnc_iso8601_to_timespec_gmt (s);
                isOK = TRUE;
            }
        }
        else
        {
            PWARN( "Unknown timespec type: %s", G_VALUE_TYPE_NAME( val ) );
        }
    }
    if ( isOK )
    {
        if (table_row->gobj_param_name != NULL)
        {
            g_object_set( pObject, table_row->gobj_param_name, &ts, NULL );
        }
        else
        {
            (*ts_setter)( pObject, ts );
        }
    }
}

static void
add_timespec_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row,
                               GList** pList )
{
    GncSqlColumnInfo* info;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    info = create_column_info( table_row, BCT_DATETIME, TIMESPEC_COL_SIZE, FALSE );

    *pList = g_list_append( *pList, info );
}

static void
add_gvalue_timespec_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
                              const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    TimespecAccessFunc ts_getter;
    Timespec ts;
    gchar* datebuf;
    GValue* value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    if ( table_row->gobj_param_name != NULL )
    {
        Timespec* pts;
        g_object_get( pObject, table_row->gobj_param_name, &pts, NULL );
        ts = *pts;
    }
    else
    {
        ts_getter = (TimespecAccessFunc)gnc_sql_get_getter( obj_name, table_row );
        g_return_if_fail( ts_getter != NULL );
        ts = (*ts_getter)( pObject );
    }

    value = g_new0( GValue, 1 );
    g_assert( value != NULL );
    (void)g_value_init( value, G_TYPE_STRING );
    datebuf = gnc_sql_convert_timespec_to_string( be, ts );
    g_value_take_string( value, datebuf );

    (*pList) = g_slist_append( (*pList), value );
}

static GncSqlColumnTypeHandler timespec_handler
=
{
    load_timespec,
    add_timespec_col_info_to_list,
    gnc_sql_add_colname_to_list,
    add_gvalue_timespec_to_slist
};
/* ----------------------------------------------------------------- */
#define DATE_COL_SIZE 8

static void
load_date( const GncSqlBackend* be, GncSqlRow* row,
           /*@ null @*/ QofSetterFunc setter, gpointer pObject,
           const GncSqlColumnTableEntry* table_row )
{
    const GValue* val;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL );

    val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name );
    if ( val != NULL )
    {
	if (G_VALUE_HOLDS_INT64 (val))
	{
	    gint64 time = g_value_get_int64 (val);
	    GDateTime *gdt = g_date_time_new_from_unix_utc (time);
	    gint day, month, year;
	    GDate *date;
	    g_date_time_get_ymd (gdt, &year, &month, &day);
	    date = g_date_new_dmy (day, month, year);
	    g_date_time_unref (gdt);
	    if ( table_row->gobj_param_name != NULL )
	    {
		g_object_set( pObject, table_row->gobj_param_name, date, NULL );
	    }
	    else
	    {
		(*setter)( pObject, date );
	    }
	    g_date_free( date );
	}
        else if ( G_VALUE_HOLDS_STRING( val ) )
        {
            // Format of date is YYYYMMDD
            const gchar* s = g_value_get_string( val );
	    GDate *date;
            if ( s != NULL )
            {
                gchar buf[5];
                GDateDay day;
                guint month;
                GDateYear year;

                strncpy( buf, &s[0], 4 );
                buf[4] = '\0';
                year = (GDateYear)atoi( buf );
                strncpy( buf, &s[4], 2 );
                buf[2] = '\0';
                month = (guint)atoi( buf );
                strncpy( buf, &s[6], 2 );
                day = (GDateDay)atoi( buf );

                if ( year != 0 || month != 0 || day != (GDateDay)0 )
                {
                    date = g_date_new_dmy( day, month, year );
                    if ( table_row->gobj_param_name != NULL )
                    {
                        g_object_set( pObject, table_row->gobj_param_name, date, NULL );
                    }
                    else
                    {
                        (*setter)( pObject, date );
                    }
                    g_date_free( date );
                }
            }
        }
        else
        {
            PWARN( "Unknown date type: %s", G_VALUE_TYPE_NAME( val ) );
        }
    }
}

static void
add_date_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row,
                           GList** pList )
{
    GncSqlColumnInfo* info;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    info = create_column_info( table_row, BCT_DATE, DATE_COL_SIZE, FALSE );

    *pList = g_list_append( *pList, info );
}

static void
add_gvalue_date_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
                          const gpointer pObject,
                          const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    GDate* date = NULL;
    QofAccessFunc getter;
    gchar* buf;
    GValue* value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );

    value = g_new0( GValue, 1 );
    g_assert( value != NULL );
    (void)g_value_init( value, G_TYPE_STRING );
    if ( table_row->gobj_param_name != NULL )
    {
        g_object_get( pObject, table_row->gobj_param_name, &date, NULL );
    }
    else
    {
        getter = gnc_sql_get_getter( obj_name, table_row );
        if ( getter != NULL )
        {
            date = (GDate*)(*getter)( pObject, NULL );
        }
    }
    if ( date && g_date_valid( date ) )
    {
        buf = g_strdup_printf( "%04d%02d%02d",
                               g_date_get_year( date ), g_date_get_month( date ), g_date_get_day( date ) );
        g_value_take_string( value, buf );
    }

    (*pList) = g_slist_append( (*pList), value );
}

static GncSqlColumnTypeHandler date_handler
=
{
    load_date,
    add_date_col_info_to_list,
    gnc_sql_add_colname_to_list,
    add_gvalue_date_to_slist
};
/* ----------------------------------------------------------------- */
typedef gnc_numeric (*NumericGetterFunc)( const gpointer );
typedef void (*NumericSetterFunc)( gpointer, gnc_numeric );

static const GncSqlColumnTableEntry numeric_col_table[] =
{
    /*@ -full_init_block @*/
    { "num",    CT_INT64, 0, COL_NNUL, "guid" },
    { "denom",  CT_INT64, 0, COL_NNUL, "guid" },
    { NULL }
    /*@ +full_init_block @*/
};

static void
load_numeric( const GncSqlBackend* be, GncSqlRow* row,
              /*@ null @*/ QofSetterFunc setter, gpointer pObject,
              const GncSqlColumnTableEntry* table_row )
{
    const GValue* val;
    gchar* buf;
    gint64 num, denom;
    gnc_numeric n;
    gboolean isNull = FALSE;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL );

    buf = g_strdup_printf( "%s_num", table_row->col_name );
    val = gnc_sql_row_get_value_at_col_name( row, buf );
    g_free( buf );
    if ( val == NULL )
    {
        isNull = TRUE;
        num = 0;
    }
    else
    {
        num = gnc_sql_get_integer_value( val );
    }
    buf = g_strdup_printf( "%s_denom", table_row->col_name );
    val = gnc_sql_row_get_value_at_col_name( row, buf );
    g_free( buf );
    if ( val == NULL )
    {
        isNull = TRUE;
        denom = 1;
    }
    else
    {
        denom = gnc_sql_get_integer_value( val );
    }
    n = gnc_numeric_create( num, denom );
    if ( !isNull )
    {
        if ( table_row->gobj_param_name != NULL )
        {
            g_object_set( pObject, table_row->gobj_param_name, &n, NULL );
        }
        else
        {
            NumericSetterFunc n_setter = (NumericSetterFunc)setter;
            (*n_setter)( pObject, n );
        }
    }
}

static void
add_numeric_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row,
                              GList** pList )
{
    GncSqlColumnInfo* info;
    gchar* buf;
    const GncSqlColumnTableEntry* subtable_row;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_row != NULL );
    g_return_if_fail( pList != NULL );

    for ( subtable_row = numeric_col_table; subtable_row->col_name != NULL; subtable_row++ )
    {
        buf = g_strdup_printf( "%s_%s", table_row->col_name, subtable_row->col_name );
        info = g_new0( GncSqlColumnInfo, 1 );
        g_assert( info != NULL );
        info->name = buf;
        info->type = BCT_INT64;
        info->is_primary_key = ((table_row->flags & COL_PKEY) != 0) ? TRUE : FALSE;
        info->null_allowed = ((table_row->flags & COL_NNUL) != 0) ? FALSE : TRUE;
        info->is_unicode = FALSE;
        *pList = g_list_append( *pList, info );
    }
}

static void
add_numeric_colname_to_list( const GncSqlColumnTableEntry* table_row, GList** pList )
{
    gnc_sql_add_subtable_colnames_to_list( table_row, numeric_col_table, pList );
}

static void
add_gvalue_numeric_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name,
                             const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList )
{
    NumericGetterFunc getter;
    gnc_numeric n;
    GValue* num_value;
    GValue* denom_value;

    g_return_if_fail( be != NULL );
    g_return_if_fail( obj_name != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table_row != NULL );

    if ( table_row->gobj_param_name != NULL )
    {
        gnc_numeric *s;
        g_object_get( pObject, table_row->gobj_param_name, &s, NULL );
        n = *s;
    }
    else
    {
        getter = (NumericGetterFunc)gnc_sql_get_getter( obj_name, table_row );
        if ( getter != NULL )
        {
            n = (*getter)( pObject );
        }
        else
        {
            n = gnc_numeric_zero();
        }
    }

    num_value = g_new0( GValue, 1 );
    g_assert( num_value != NULL );
    (void)g_value_init( num_value, G_TYPE_INT64 );
    g_value_set_int64( num_value, gnc_numeric_num( n ) );
    denom_value = g_new0( GValue, 1 );
    g_assert( denom_value != NULL );
    (void)g_value_init( denom_value, G_TYPE_INT64 );
    g_value_set_int64( denom_value, gnc_numeric_denom( n ) );

    (*pList) = g_slist_append( (*pList), num_value );
    (*pList) = g_slist_append( (*pList), denom_value );
}

static GncSqlColumnTypeHandler numeric_handler
= { load_numeric,
    add_numeric_col_info_to_list,
    add_numeric_colname_to_list,
    add_gvalue_numeric_to_slist
  };
/* ================================================================= */

static /*@ null @*//*@ only @*/ GHashTable* g_columnTypeHash = NULL;

void
gnc_sql_register_col_type_handler( const gchar* colType, const GncSqlColumnTypeHandler* handler )
{
    g_return_if_fail( colType != NULL );
    g_return_if_fail( handler != NULL );

    if ( g_columnTypeHash == NULL )
    {
        g_columnTypeHash = g_hash_table_new( g_str_hash, g_str_equal );
        g_assert( g_columnTypeHash != NULL );
    }

    DEBUG( "Col type %s registered\n", colType );
    g_hash_table_insert( g_columnTypeHash, (gpointer)colType, (gpointer)handler );
}

/*@ dependent @*//*@ null @*/ static GncSqlColumnTypeHandler*
get_handler( const GncSqlColumnTableEntry* table_row )
{
    GncSqlColumnTypeHandler* pHandler;

    g_return_val_if_fail( table_row != NULL, NULL );
    g_return_val_if_fail( table_row->col_type != NULL, NULL );

    if ( g_columnTypeHash != NULL )
    {
        pHandler = g_hash_table_lookup( g_columnTypeHash, table_row->col_type );
        g_assert( pHandler != NULL );
    }
    else
    {
        pHandler = NULL;
    }

    return pHandler;
}

static void
register_standard_col_type_handlers( void )
{
    gnc_sql_register_col_type_handler( CT_STRING, &string_handler );
    gnc_sql_register_col_type_handler( CT_BOOLEAN, &boolean_handler );
    gnc_sql_register_col_type_handler( CT_INT, &int_handler );
    gnc_sql_register_col_type_handler( CT_INT64, &int64_handler );
    gnc_sql_register_col_type_handler( CT_DOUBLE, &double_handler );
    gnc_sql_register_col_type_handler( CT_GUID, &guid_handler );
    gnc_sql_register_col_type_handler( CT_TIMESPEC, &timespec_handler );
    gnc_sql_register_col_type_handler( CT_GDATE, &date_handler );
    gnc_sql_register_col_type_handler( CT_NUMERIC, &numeric_handler );
}

void
_retrieve_guid_( gpointer pObject, /*@ null @*/ gpointer pValue )
{
    GncGUID* pGuid = (GncGUID*)pObject;
    GncGUID* guid = (GncGUID*)pValue;

    g_return_if_fail( pObject != NULL );
    g_return_if_fail( pValue != NULL );

    memcpy( pGuid, guid, sizeof( GncGUID ) );
}


// Table to retrieve just the guid
static GncSqlColumnTableEntry guid_table[] =
{
    /*@ -full_init_block @*/
    { "guid", CT_GUID, 0, 0, NULL, NULL, NULL, _retrieve_guid_ },
    { NULL }
    /*@ +full_init_block @*/
};

/*@ null @*/
const GncGUID*
gnc_sql_load_guid( const GncSqlBackend* be, GncSqlRow* row )
{
    static GncGUID guid;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( row != NULL, NULL );

    gnc_sql_load_object( be, row, NULL, &guid, guid_table );

    return &guid;
}

// Table to retrieve just the guid
static GncSqlColumnTableEntry tx_guid_table[] =
{
    /*@ -full_init_block @*/
    { "tx_guid", CT_GUID, 0, 0, NULL, NULL, NULL, _retrieve_guid_ },
    { NULL }
    /*@ +full_init_block @*/
};

/*@ null @*//*@ dependent @*/
const GncGUID*
gnc_sql_load_tx_guid( const GncSqlBackend* be, GncSqlRow* row )
{
    static GncGUID guid;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( row != NULL, NULL );

    gnc_sql_load_object( be, row, NULL, &guid, tx_guid_table );

    return &guid;
}

void
gnc_sql_load_object( const GncSqlBackend* be, GncSqlRow* row,
                     /*@ null @*/ QofIdTypeConst obj_name, gpointer pObject,
                     const GncSqlColumnTableEntry* table )
{
    QofSetterFunc setter;
    GncSqlColumnTypeHandler* pHandler;
    const GncSqlColumnTableEntry* table_row;

    g_return_if_fail( be != NULL );
    g_return_if_fail( row != NULL );
    g_return_if_fail( pObject != NULL );
    g_return_if_fail( table != NULL );

    for ( table_row = table; table_row->col_name != NULL; table_row++ )
    {
        if ( (table_row->flags & COL_AUTOINC) != 0 )
        {
            setter = set_autoinc_id;
        }
        else if ( table_row->qof_param_name != NULL )
        {
            g_assert( obj_name != NULL );
            setter = qof_class_get_parameter_setter( obj_name,
                     table_row->qof_param_name );
        }
        else
        {
            setter = table_row->setter;
        }
        pHandler = get_handler( table_row );
        g_assert( pHandler != NULL );
        pHandler->load_fn( be, row, setter, pObject, table_row );
    }
}

/* ================================================================= */
/*@ null @*/ GncSqlStatement*
gnc_sql_create_select_statement( GncSqlBackend* be, const gchar* table_name )
{
    gchar* sql;
    GncSqlStatement* stmt;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( table_name != NULL, NULL );

    sql = g_strdup_printf( "SELECT * FROM %s", table_name );
    stmt = gnc_sql_create_statement_from_sql( be, sql );
    g_free( sql );
    return stmt;
}

/*@ null @*/ static GncSqlStatement*
create_single_col_select_statement( GncSqlBackend* be,
                                    const gchar* table_name,
                                    const GncSqlColumnTableEntry* table_row )
{
    gchar* sql;
    GncSqlStatement* stmt;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( table_name != NULL, NULL );
    g_return_val_if_fail( table_row != NULL, NULL );

    sql = g_strdup_printf( "SELECT %s FROM %s", table_row->col_name, table_name );
    stmt = gnc_sql_create_statement_from_sql( be, sql );
    g_free( sql );
    return stmt;
}

/* ================================================================= */

/*@ null @*/ GncSqlResult*
gnc_sql_execute_select_statement( GncSqlBackend* be, GncSqlStatement* stmt )
{
    GncSqlResult* result;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( stmt != NULL, NULL );

    result = gnc_sql_connection_execute_select_statement( be->conn, stmt );
    if ( result == NULL )
    {
        PERR( "SQL error: %s\n", gnc_sql_statement_to_sql( stmt ) );
        qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR );
    }

    return result;
}

/*@ null @*/ GncSqlStatement*
gnc_sql_create_statement_from_sql( GncSqlBackend* be, const gchar* sql )
{
    GncSqlStatement* stmt;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( sql != NULL, NULL );

    stmt = gnc_sql_connection_create_statement_from_sql( be->conn, sql );
    if ( stmt == NULL )
    {
        PERR( "SQL error: %s\n", sql );
        qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR );
    }

    return stmt;
}

/*@ null @*/ GncSqlResult*
gnc_sql_execute_select_sql( GncSqlBackend* be, const gchar* sql )
{
    GncSqlStatement* stmt;
    GncSqlResult* result = NULL;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( sql != NULL, NULL );

    stmt = gnc_sql_create_statement_from_sql( be, sql );
    if ( stmt == NULL )
    {
        return NULL;
    }
    result = gnc_sql_connection_execute_select_statement( be->conn, stmt );
    gnc_sql_statement_dispose( stmt );
    if ( result == NULL )
    {
        PERR( "SQL error: %s\n", sql );
        qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR );
    }

    return result;
}

gint
gnc_sql_execute_nonselect_sql( GncSqlBackend* be, const gchar* sql )
{
    GncSqlStatement* stmt;
    gint result;

    g_return_val_if_fail( be != NULL, 0 );
    g_return_val_if_fail( sql != NULL, 0 );

    stmt = gnc_sql_create_statement_from_sql( be, sql );
    if ( stmt == NULL )
    {
        return -1;
    }
    result = gnc_sql_connection_execute_nonselect_statement( be->conn, stmt );
    gnc_sql_statement_dispose( stmt );
    return result;
}

static guint
execute_statement_get_count( GncSqlBackend* be, GncSqlStatement* stmt )
{
    GncSqlResult* result;
    guint count = 0;

    g_return_val_if_fail( be != NULL, 0 );
    g_return_val_if_fail( stmt != NULL, 0 );

    result = gnc_sql_execute_select_statement( be, stmt );
    if ( result != NULL )
    {
        count = gnc_sql_result_get_num_rows( result );
        gnc_sql_result_dispose( result );
    }

    return count;
}

guint
gnc_sql_append_guid_list_to_sql( GString* sql, GList* list, guint maxCount )
{
    gchar guid_buf[GUID_ENCODING_LENGTH+1];
    gboolean first_guid = TRUE;
    guint count;

    g_return_val_if_fail( sql != NULL, 0 );

    if ( list == NULL ) return 0;

    for ( count = 0; list != NULL && count < maxCount; list = list->next, count++ )
    {
        QofInstance* inst = QOF_INSTANCE(list->data);
        (void)guid_to_string_buff( qof_instance_get_guid( inst ), guid_buf );

        if ( !first_guid )
        {
            (void)g_string_append( sql, "," );
        }
        (void)g_string_append( sql, "'" );
        (void)g_string_append( sql, guid_buf );
        (void)g_string_append( sql, "'" );
        first_guid = FALSE;
    }

    return count;
}
/* ================================================================= */

gboolean
gnc_sql_object_is_it_in_db( GncSqlBackend* be, const gchar* table_name,
                            QofIdTypeConst obj_name, gpointer pObject,
                            const GncSqlColumnTableEntry* table )
{
    GncSqlStatement* sqlStmt;
    guint count;
    GncSqlColumnTypeHandler* pHandler;
    GSList* list = NULL;

    g_return_val_if_fail( be != NULL, FALSE );
    g_return_val_if_fail( table_name != NULL, FALSE );
    g_return_val_if_fail( obj_name != NULL, FALSE );
    g_return_val_if_fail( pObject != NULL, FALSE );
    g_return_val_if_fail( table != NULL, FALSE );

    /* SELECT * FROM */
    sqlStmt = create_single_col_select_statement( be, table_name, table );
    g_assert( sqlStmt != NULL );

    /* WHERE */
    pHandler = get_handler( table );
    g_assert( pHandler != NULL );
    pHandler->add_gvalue_to_slist_fn( be, obj_name, pObject, table, &list );
    g_assert( list != NULL );
    gnc_sql_statement_add_where_cond( sqlStmt, obj_name, pObject, &table[0], (GValue*)(list->data) );

    count = execute_statement_get_count( be, sqlStmt );
    gnc_sql_statement_dispose( sqlStmt );
    if ( count == 0 )
    {
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

gboolean
gnc_sql_do_db_operation( GncSqlBackend* be,
                         E_DB_OPERATION op,
                         const gchar* table_name,
                         QofIdTypeConst obj_name, gpointer pObject,
                         const GncSqlColumnTableEntry* table )
{
    GncSqlStatement* stmt = NULL;
    gboolean ok = FALSE;

    g_return_val_if_fail( be != NULL, FALSE );
    g_return_val_if_fail( table_name != NULL, FALSE );
    g_return_val_if_fail( obj_name != NULL, FALSE );
    g_return_val_if_fail( pObject != NULL, FALSE );
    g_return_val_if_fail( table != NULL, FALSE );

    if ( op == OP_DB_INSERT )
    {
        stmt = build_insert_statement( be, table_name, obj_name, pObject, table );
    }
    else if ( op == OP_DB_UPDATE )
    {
        stmt = build_update_statement( be, table_name, obj_name, pObject, table );
    }
    else if ( op == OP_DB_DELETE )
    {
        stmt = build_delete_statement( be, table_name, obj_name, pObject, table );
    }
    else
    {
        g_assert( FALSE );
    }
    if ( stmt != NULL )
    {
        gint result;

        result = gnc_sql_connection_execute_nonselect_statement( be->conn, stmt );
        if ( result == -1 )
        {
            PERR( "SQL error: %s\n", gnc_sql_statement_to_sql( stmt ) );
            qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR );
        }
        else
        {
            ok = TRUE;
        }
        gnc_sql_statement_dispose( stmt );
    }

    return ok;
}

static GSList*
create_gslist_from_values( GncSqlBackend* be,
                           QofIdTypeConst obj_name, gpointer pObject,
                           const GncSqlColumnTableEntry* table )
{
    GSList* list = NULL;
    GncSqlColumnTypeHandler* pHandler;
    const GncSqlColumnTableEntry* table_row;

    for ( table_row = table; table_row->col_name != NULL; table_row++ )
    {
        if (( table_row->flags & COL_AUTOINC ) == 0 )
        {
            pHandler = get_handler( table_row );
            g_assert( pHandler != NULL );
            pHandler->add_gvalue_to_slist_fn( be, obj_name, pObject, table_row, &list );
        }
    }

    g_assert( list != NULL );
    return list;
}

gchar*
gnc_sql_get_sql_value( const GncSqlConnection* conn, const GValue* value )
{
    if ( value != NULL && G_IS_VALUE( value ) )
    {
        GType type = G_VALUE_TYPE(value);

        if ( G_VALUE_HOLDS_STRING(value) )
        {
            if ( g_value_get_string( value ) != NULL )
            {
                gchar* before_str;
                gchar* after_str;
                before_str = g_value_dup_string( value );
                after_str = gnc_sql_connection_quote_string( conn, before_str );
                g_free( before_str );
                return after_str;
            }
            else
            {
                return g_strdup( "NULL" );
            }
        }
        else if ( type == G_TYPE_INT64 )
        {
            return g_strdup_printf( "%" G_GINT64_FORMAT, g_value_get_int64( value ) );

        }
        else if ( type == G_TYPE_INT )
        {
            return g_strdup_printf( "%d", g_value_get_int( value ) );

        }
        else if ( type == G_TYPE_DOUBLE )
        {
            gchar doublestr[G_ASCII_DTOSTR_BUF_SIZE];
            g_ascii_dtostr( doublestr, sizeof(doublestr),
                            g_value_get_double( value ));
            return g_strdup( doublestr );

        }
        else if ( g_value_type_transformable( type, G_TYPE_STRING ) )
        {
            GValue* string;
            gchar* str;

            string = g_new0( GValue, 1 );
            g_assert( string != NULL );
            (void)g_value_init( string, G_TYPE_STRING );
            (void)g_value_transform( value, string );
            str = g_value_dup_string( string );
            g_value_unset( string );
            g_free( string );
            PWARN( "using g_value_transform(), gtype = '%s'\n", g_type_name( type ) );
            return str;
        }
        else
        {
            PWARN( "not transformable, gtype = '%s'\n", g_type_name( type ) );
            return g_strdup( "$$$" );
        }
    }
    else
    {
        PWARN( "value is NULL or not G_IS_VALUE()\n" );
        return g_strdup( "" );
    }
}

static void
free_gvalue_list( GSList* list )
{
    GSList* node;
    GValue* value;

    for ( node = list; node != NULL; node = node->next )
    {
        value = (GValue*)node->data;

        g_value_unset( value );
        g_free( value );
    }
    g_slist_free( list );
}

/*@ null @*/ static GncSqlStatement*
build_insert_statement( GncSqlBackend* be,
                        const gchar* table_name,
                        QofIdTypeConst obj_name, gpointer pObject,
                        const GncSqlColumnTableEntry* table )
{
    GncSqlStatement* stmt;
    GString* sql;
    GSList* values;
    GSList* node;
    gchar* sqlbuf;
    GList* colnames = NULL;
    GList* colname;
    const GncSqlColumnTableEntry* table_row;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( table_name != NULL, NULL );
    g_return_val_if_fail( obj_name != NULL, NULL );
    g_return_val_if_fail( pObject != NULL, NULL );
    g_return_val_if_fail( table != NULL, NULL );

    sqlbuf = g_strdup_printf( "INSERT INTO %s(", table_name );
    sql = g_string_new( sqlbuf );
    g_free( sqlbuf );

    // Get all col names and all values
    for ( table_row = table; table_row->col_name != NULL; table_row++ )
    {
        if (( table_row->flags & COL_AUTOINC ) == 0 )
        {
            GncSqlColumnTypeHandler* pHandler;

            // Add col names to the list
            pHandler = get_handler( table_row );
            g_assert( pHandler != NULL );
            pHandler->add_colname_to_list_fn( table_row, &colnames );
        }
    }
    g_assert( colnames != NULL );

    for ( colname = colnames; colname != NULL; colname = colname->next )
    {
        if ( colname != colnames )
        {
            g_string_append( sql, "," );
        }
        g_string_append( sql, (gchar*)colname->data );
        g_free( colname->data );
    }
    g_list_free( colnames );

    g_string_append( sql, ") VALUES(" );
    values = create_gslist_from_values( be, obj_name, pObject, table );
    for ( node = values; node != NULL; node = node->next )
    {
        GValue* value = (GValue*)node->data;
        gchar* value_str;
        if ( node != values )
        {
            (void)g_string_append( sql, "," );
        }
        value_str = gnc_sql_get_sql_value( be->conn, value );
        (void)g_string_append( sql, value_str );
        g_free( value_str );
        (void)g_value_reset( value );
    }
    free_gvalue_list( values );
    (void)g_string_append( sql, ")" );

    stmt = gnc_sql_connection_create_statement_from_sql( be->conn, sql->str );
    (void)g_string_free( sql, TRUE );

    return stmt;
}

/*@ null @*/ static GncSqlStatement*
build_update_statement( GncSqlBackend* be,
                        const gchar* table_name,
                        QofIdTypeConst obj_name, gpointer pObject,
                        const GncSqlColumnTableEntry* table )
{
    GncSqlStatement* stmt;
    GString* sql;
    GSList* values;
    GList* colnames = NULL;
    GSList* value;
    GList* colname;
    gboolean firstCol;
    const GncSqlColumnTableEntry* table_row;
    gchar* sqlbuf;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( table_name != NULL, NULL );
    g_return_val_if_fail( obj_name != NULL, NULL );
    g_return_val_if_fail( pObject != NULL, NULL );
    g_return_val_if_fail( table != NULL, NULL );

    // Get all col names and all values
    for ( table_row = table; table_row->col_name != NULL; table_row++ )
    {
        if (( table_row->flags & COL_AUTOINC ) == 0 )
        {
            GncSqlColumnTypeHandler* pHandler;

            // Add col names to the list
            pHandler = get_handler( table_row );
            g_assert( pHandler != NULL );
            pHandler->add_colname_to_list_fn( table_row, &colnames );
        }
    }
    g_assert( colnames != NULL );
    values = create_gslist_from_values( be, obj_name, pObject, table );

    // Create the SQL statement
    sqlbuf = g_strdup_printf( "UPDATE %s SET ", table_name );
    sql = g_string_new( sqlbuf );
    g_free( sqlbuf );

    firstCol = TRUE;
    for ( colname = colnames->next, value = values->next;
            colname != NULL && value != NULL;
            colname = colname->next, value = value->next )
    {
        gchar* value_str;
        if ( !firstCol )
        {
            (void)g_string_append( sql, "," );
        }
        (void)g_string_append( sql, (gchar*)colname->data );
        (void)g_string_append( sql, "=" );
        value_str = gnc_sql_get_sql_value( be->conn, (GValue*)(value->data) );
        (void)g_string_append( sql, value_str );
        g_free( value_str );
        firstCol = FALSE;
    }
    for ( colname = colnames; colname != NULL; colname = colname->next )
    {
        g_free( colname->data );
    }
    g_list_free( colnames );
    if ( value != NULL || colname != NULL )
    {
        PERR( "Mismatch in number of column names and values" );
    }

    stmt = gnc_sql_connection_create_statement_from_sql( be->conn, sql->str );
    gnc_sql_statement_add_where_cond( stmt, obj_name, pObject, &table[0], (GValue*)(values->data) );
    free_gvalue_list( values );
    (void)g_string_free( sql, TRUE );

    return stmt;
}

/*@ null @*/ static GncSqlStatement*
build_delete_statement( GncSqlBackend* be,
                        const gchar* table_name,
                        QofIdTypeConst obj_name, gpointer pObject,
                        const GncSqlColumnTableEntry* table )
{
    GncSqlStatement* stmt;
    GncSqlColumnTypeHandler* pHandler;
    GSList* list = NULL;
    gchar* sqlbuf;

    g_return_val_if_fail( be != NULL, NULL );
    g_return_val_if_fail( table_name != NULL, NULL );
    g_return_val_if_fail( obj_name != NULL, NULL );
    g_return_val_if_fail( pObject != NULL, NULL );
    g_return_val_if_fail( table != NULL, NULL );

    sqlbuf = g_strdup_printf( "DELETE FROM %s ", table_name );
    stmt = gnc_sql_connection_create_statement_from_sql( be->conn, sqlbuf );
    g_free( sqlbuf );

    /* WHERE */
    pHandler = get_handler( table );
    g_assert( pHandler != NULL );
    pHandler->add_gvalue_to_slist_fn( be, obj_name, pObject, table, &list );
    g_assert( list != NULL );
    gnc_sql_statement_add_where_cond( stmt, obj_name, pObject, &table[0], (GValue*)(list->data) );
    free_gvalue_list( list );

    return stmt;
}

/* ================================================================= */
gboolean
gnc_sql_commit_standard_item( GncSqlBackend* be, QofInstance* inst, const gchar* tableName,
                              QofIdTypeConst obj_name, const GncSqlColumnTableEntry* col_table )
{
    const GncGUID* guid;
    gboolean is_infant;
    gint op;
    gboolean is_ok;

    is_infant = qof_instance_get_infant( inst );
    if ( qof_instance_get_destroying( inst ) )
    {
        op = OP_DB_DELETE;
    }
    else if ( be->is_pristine_db || is_infant )
    {
        op = OP_DB_INSERT;
    }
    else
    {
        op = OP_DB_UPDATE;
    }
    is_ok = gnc_sql_do_db_operation( be, op, tableName, obj_name, inst, col_table );

    if ( is_ok )
    {
        // Now, commit any slots
        guid = qof_instance_get_guid( inst );
        if ( !qof_instance_get_destroying(inst) )
        {
            is_ok = gnc_sql_slots_save( be, guid, is_infant, qof_instance_get_slots( inst ) );
        }
        else
        {
            is_ok = gnc_sql_slots_delete( be, guid );
        }
    }

    return is_ok;
}

/* ================================================================= */

static gboolean
do_create_table( const GncSqlBackend* be, const gchar* table_name,
                 const GncSqlColumnTableEntry* col_table )
{
    GList* col_info_list = NULL;
    gboolean ok = FALSE;

    g_return_val_if_fail( be != NULL, FALSE );
    g_return_val_if_fail( table_name != NULL, FALSE );
    g_return_val_if_fail( col_table != NULL, FALSE );

    for ( ; col_table->col_name != NULL; col_table++ )
    {
        GncSqlColumnTypeHandler* pHandler;

        pHandler = get_handler( col_table );
        g_assert( pHandler != NULL );
        pHandler->add_col_info_to_list_fn( be, col_table, &col_info_list );
    }
    g_assert( col_info_list != NULL );
    ok = gnc_sql_connection_create_table( be->conn, table_name, col_info_list );
    return ok;
}

gboolean
gnc_sql_create_table( GncSqlBackend* be, const gchar* table_name,
                      gint table_version, const GncSqlColumnTableEntry* col_table )
{
    gboolean ok;

    g_return_val_if_fail( be != NULL, FALSE );
    g_return_val_if_fail( table_name != NULL, FALSE );
    g_return_val_if_fail( col_table != NULL, FALSE );

    DEBUG( "Creating %s table\n", table_name );

    ok = do_create_table( be, table_name, col_table );
    if ( ok )
    {
        ok = gnc_sql_set_table_version( be, table_name, table_version );
    }
    return ok;
}

gboolean
gnc_sql_create_temp_table( const GncSqlBackend* be, const gchar* table_name,
                           const GncSqlColumnTableEntry* col_table )
{
    g_return_val_if_fail( be != NULL, FALSE );
    g_return_val_if_fail( table_name != NULL, FALSE );
    g_return_val_if_fail( col_table != NULL, FALSE );

    return do_create_table( be, table_name, col_table );
}

gboolean
gnc_sql_create_index( const GncSqlBackend* be, const gchar* index_name,
                      const gchar* table_name,
                      const GncSqlColumnTableEntry* col_table )
{
    gboolean ok;

    g_return_val_if_fail( be != NULL, FALSE );
    g_return_val_if_fail( index_name != NULL, FALSE );
    g_return_val_if_fail( table_name != NULL, FALSE );
    g_return_val_if_fail( col_table != NULL, FALSE );

    ok = gnc_sql_connection_create_index( be->conn, index_name, table_name,
                                          col_table );
    return ok;
}

gint
gnc_sql_get_table_version( const GncSqlBackend* be, const gchar* table_name )
{
    g_return_val_if_fail( be != NULL, 0 );
    g_return_val_if_fail( table_name != NULL, 0 );

    /* If the db is pristine because it's being saved, the table does not exist. */
    if ( be->is_pristine_db )
    {
        return 0;
    }

    return GPOINTER_TO_INT(g_hash_table_lookup( be->versions, table_name ));
}

/* Create a temporary table, copy the data from the old table, delete the
   old table, then rename the new one. */
void
gnc_sql_upgrade_table( GncSqlBackend* be, const gchar* table_name,
                       const GncSqlColumnTableEntry* col_table )
{
    gchar* sql;
    gchar* temp_table_name;

    g_return_if_fail( be != NULL );
    g_return_if_fail( table_name != NULL );
    g_return_if_fail( col_table != NULL );

    DEBUG( "Upgrading %s table\n", table_name );

    temp_table_name = g_strdup_printf( "%s_new", table_name );
    (void)gnc_sql_create_temp_table( be, temp_table_name, col_table );
    sql = g_strdup_printf( "INSERT INTO %s SELECT * FROM %s",
                           temp_table_name, table_name );
    (void)gnc_sql_execute_nonselect_sql( be, sql );
    g_free( sql );

    sql = g_strdup_printf( "DROP TABLE %s", table_name );
    (void)gnc_sql_execute_nonselect_sql( be, sql );
    g_free( sql );

    sql = g_strdup_printf( "ALTER TABLE %s RENAME TO %s", temp_table_name, table_name );
    (void)gnc_sql_execute_nonselect_sql( be, sql );
    g_free( sql );
    g_free( temp_table_name );
}

/* Adds one or more columns to an existing table. */
gboolean gnc_sql_add_columns_to_table( GncSqlBackend* be, const gchar* table_name,
                                       const GncSqlColumnTableEntry* new_col_table )
{
    GList* col_info_list = NULL;
    gboolean ok = FALSE;

    g_return_val_if_fail( be != NULL, FALSE );
    g_return_val_if_fail( table_name != NULL, FALSE );
    g_return_val_if_fail( new_col_table != NULL, FALSE );

    for ( ; new_col_table->col_name != NULL; new_col_table++ )
    {
        GncSqlColumnTypeHandler* pHandler;

        pHandler = get_handler( new_col_table );
        g_assert( pHandler != NULL );
        pHandler->add_col_info_to_list_fn( be, new_col_table, &col_info_list );
    }
    g_assert( col_info_list != NULL );
    ok = gnc_sql_connection_add_columns_to_table( be->conn, table_name, col_info_list );
    return ok;
}

/* ================================================================= */
#define VERSION_TABLE_NAME "versions"
#define MAX_TABLE_NAME_LEN 50
#define TABLE_COL_NAME "table_name"
#define VERSION_COL_NAME "table_version"

static GncSqlColumnTableEntry version_table[] =
{
    /*@ -full_init_block @*/
    { TABLE_COL_NAME,   CT_STRING, MAX_TABLE_NAME_LEN, COL_PKEY | COL_NNUL },
    { VERSION_COL_NAME, CT_INT,    0,                  COL_NNUL },
    { NULL }
    /*@ +full_init_block @*/
};

/**
 * Sees if the version table exists, and if it does, loads the info into
 * the version hash table.  Otherwise, it creates an empty version table.
 *
 * @param be Backend struct
 */
void
gnc_sql_init_version_info( GncSqlBackend* be )
{
    g_return_if_fail( be != NULL );

    if ( be->versions != NULL )
    {
        g_hash_table_destroy( be->versions );
    }
    be->versions = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, NULL );

    if ( gnc_sql_connection_does_table_exist( be->conn, VERSION_TABLE_NAME ) )
    {
        GncSqlResult* result;
        gchar* sql;

        sql = g_strdup_printf( "SELECT * FROM %s", VERSION_TABLE_NAME );
        result = gnc_sql_execute_select_sql( be, sql );
        g_free( sql );
        if ( result != NULL )
        {
            const GValue* name;
            const GValue* version;
            GncSqlRow* row;

            row = gnc_sql_result_get_first_row( result );
            while ( row != NULL )
            {
                name = gnc_sql_row_get_value_at_col_name( row, TABLE_COL_NAME );
                version = gnc_sql_row_get_value_at_col_name( row, VERSION_COL_NAME );
                g_hash_table_insert( be->versions,
                                     g_strdup( g_value_get_string( name ) ),
                                     GINT_TO_POINTER((gint)g_value_get_int64( version )) );
                row = gnc_sql_result_get_next_row( result );
            }
            gnc_sql_result_dispose( result );
        }
    }
    else
    {
        do_create_table( be, VERSION_TABLE_NAME, version_table );
	gnc_sql_set_table_version( be, "Gnucash",
				   gnc_prefs_get_long_version() );
	gnc_sql_set_table_version( be, "Gnucash-Resave",
				   GNUCASH_RESAVE_VERSION );
    }
}

/**
 * Resets the version table information by removing all version table info.
 * It also recreates the version table in the db.
 *
 * @param be Backend struct
 * @return TRUE if successful, FALSE if error
 */
static gboolean
reset_version_info( GncSqlBackend* be )
{
    gboolean ok;

    g_return_val_if_fail( be != NULL, FALSE );

    ok = do_create_table( be, VERSION_TABLE_NAME, version_table );
    if ( be->versions == NULL )
    {
        be->versions = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, NULL );
    }
    else
    {
        g_hash_table_remove_all( be->versions );
    }

    gnc_sql_set_table_version( be, "Gnucash", gnc_prefs_get_long_version() );
    gnc_sql_set_table_version( be, "Gnucash-Resave", GNUCASH_RESAVE_VERSION );
    return ok;
}

/**
 * Finalizes the version table info by destroying the hash table.
 *
 * @param be Backend struct
 */
void
gnc_sql_finalize_version_info( GncSqlBackend* be )
{
    g_return_if_fail( be != NULL );

    if ( be->versions != NULL )
    {
        g_hash_table_destroy( be->versions );
        be->versions = NULL;
    }
}

/**
 * Registers the version for a table.  Registering involves updating the
 * db version table and also the hash table.
 *
 * @param be Backend struct
 * @param table_name Table name
 * @param version Version number
 * @return TRUE if successful, FALSE if unsuccessful
 */
gboolean
gnc_sql_set_table_version( GncSqlBackend* be, const gchar* table_name, gint version )
{
    gchar* sql;
    gint cur_version;
    gint status;

    g_return_val_if_fail( be != NULL, FALSE );
    g_return_val_if_fail( table_name != NULL, FALSE );
    g_return_val_if_fail( version > 0, FALSE );

    cur_version = gnc_sql_get_table_version( be, table_name );
    if ( cur_version != version )
    {
        if ( cur_version == 0 )
        {
            sql = g_strdup_printf( "INSERT INTO %s VALUES('%s',%d)", VERSION_TABLE_NAME,
                                   table_name, version );
        }
        else
        {
            sql = g_strdup_printf( "UPDATE %s SET %s=%d WHERE %s='%s'", VERSION_TABLE_NAME,
                                   VERSION_COL_NAME, version,
                                   TABLE_COL_NAME, table_name );
        }
        status = gnc_sql_execute_nonselect_sql( be, sql );
        if ( status == -1 )
        {
            PERR( "SQL error: %s\n", sql );
            qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR );
        }
        g_free( sql );
    }

    g_hash_table_insert( be->versions, g_strdup( table_name ), GINT_TO_POINTER(version) );

    return TRUE;
}

/* ========================== END OF FILE ===================== */
