/*
 * Copyright (C) 2012 - Juan Ferrer Toribio
 *
 * This file is part of Hedera.
 *
 * Hedera 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <gvn/gvn.h>
#include "db-model.h"
#include "db-row.h"
#include "db-model-private.c"

#define MODEL_NOT_READY(obj) (obj->priv->status != DB_MODEL_STATUS_READY)
#define VALID_ITER(iter, model) (iter->data && iter->stamp == model->priv->stamp)
#define DB_ROW_FIELD(row, field) (&((DbRow *) row)->value[field])
#define DB_ROW_POSITION(row) (((DbRow *) row)->position)

/**
 * SECTION: db-model
 * @Short_description: data vinculed to a SELECT query sent to the database
 * @Title: DbModel
 * @See_also: #DbIterator, #DbConn
 * 
 * The #DbModel class gets an SQL query statement to retrieve the data from the
 * database connected by a #DbConn. It is normally used undirectly, using instead
 * #DbForm.
 **/
G_DEFINE_TYPE (DbModel, db_model, G_TYPE_OBJECT)

/*
 * DbModel:
 * @data: (element-type Db.Row):
 * @operation: (element-type Db.Operation):
 * @row_ops: (element-type Db.Row Db.Operation):
 **/
struct _DbModelPrivate
{
	DbConn * conn;
	SqlStmt * stmt;
	gchar * sql;
	gboolean use_file;
	Table * main_table;
	DbModelUpdateFlags update_flags;
	SqlBatch * batch;	

	GPtrArray * data;
	DbColumn * column;
	DbResult * result;
	DbRequest * request;
	DbModelStatus status;
	DbModelMode mode;
	gchar * user_main_table;
	DbModelUpdateFlags user_update_flags;
	guint result_pos;
	GQueue * operation;
	GHashTable * row_ops;
	gint updated_col;
	GValue * updated_value;

	GHashTable * column_index;
	GSList * pending_request;
	GSList * join;

	SqlObject * link_op;
	SqlBatch * internal_batch;
	GHashTable * column_defaults;
	GHashTable * tables;
	gboolean updatable_data_allocated;

	gint stamp;

	gboolean fresh;
	gint sort_column_id;
	gint old_sort_column_id;
	DbSortType order;
	DbSortType old_order;
	DbIterCompareFunc default_sort_func;
	gpointer default_sort_data;
	GDestroyNotify default_sort_destroy;
	DbIterCompareFunc sort_func;
	gpointer sort_data;
	GDestroyNotify sort_destroy;
};

//						Helper structures and methods

//								Structures

typedef struct
{
	DbModel * obj;
	DbIter * iter;
	gint col;
}
JoinData;

typedef struct
{
	gchar * schema;
	gchar * table;
	GPtrArray * name;
	gboolean main;
}
DbModelField;

typedef struct
{
	DbModelField * left;
	DbModelField * right;
}
DbJoin;

enum
{
	DB_MODEL_UNSORTED_SORT_COLUMN_ID = -2,
	DB_MODEL_DEFAULT_SORT_COLUMN_ID
};

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Constructors

/**
 * db_model_new:
 * @conn: a #DbConn
 * @stmt: an #SqlStmt
 * 
 * Returns the newly created #DbModel, filled with the data retrieved from the
 * database with @stmt and through @conn.
 * 
 * Return value: (transfer full): a new #DbModel
 **/
DbModel * db_model_new (DbConn * conn, SqlStmt * stmt)
{
	return g_object_new (DB_TYPE_MODEL, "conn", conn, "stmt", stmt, NULL);
}

/**
 * db_model_new_with_sql:
 * @conn: a #DbConn
 * @sql: a string containing an SQL statement
 * 
 * Returns the newly created #DbModel, filled with the data retrieved from the
 * database with @stmt and through @conn.
 * 
 * Return value: (transfer full): a new #DbModel
 **/
DbModel * db_model_new_with_sql (DbConn * conn, const gchar * sql)
{
	return g_object_new (DB_TYPE_MODEL,
		"conn", conn, "use-file", FALSE, "sql", sql, NULL);
}

/**
 * db_model_new_with_file:
 * @conn: a #DbConn
 * @file: the path to the file containing the SQL query to fill the model
 * 
 * Returns a newly created #DbModel, filled with the data retrieved from @file.
 * 
 * Return value: (transfer full): a new #DbModel
 **/
DbModel * db_model_new_with_file (DbConn * conn, const gchar * file)
{
	return g_object_new (DB_TYPE_MODEL,
		"conn", conn, "use-file", TRUE, "sql", file, NULL);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Private

//							Prototypes

static void		db_model_on_batch_changed			(SqlBatch * batch, DbModel * obj);
static void		db_model_set_status					(DbModel * obj
													,DbModelStatus status);
static void		db_model_clear						(DbModel * obj);
static void		db_model_free_operation				(DbModel * obj
													,DbOperation * op);
static void		db_model_manage_join				(DbModel * obj
													,DbIter * iter
													,gint col);
static void		db_model_post_process_query			(DbModel * obj);

// Memory allocate functions

static void db_model_alloc_link_data (DbModel * obj)
{
	SqlList * operators;
	DbModelPrivate * priv = obj->priv;
	
	if (priv->link_op)
		return;

	priv->internal_batch = g_object_ref_sink (sql_batch_new ());
	g_signal_connect (priv->internal_batch, "changed",
		G_CALLBACK (db_model_on_batch_changed), obj);

	priv->link_op = sql_operation_new (SQL_OPERATION_TYPE_AND);
	sql_batch_add (priv->internal_batch, "link", priv->link_op);
	
	operators = sql_list_new (SQL_TYPE_EXPR);
	sql_operation_set_operands (SQL_OPERATION (priv->link_op), operators);
}

static void db_model_free_stmt_data (DbModel * obj)
{
	if (obj->priv->tables)
		g_hash_table_destroy (obj->priv->tables);
}

static void db_model_alloc_stmt_data (DbModel * obj)
{
	gint i;
	Table table;
	TableData * table_data;
	DbModelPrivate * priv = obj->priv;
	
	if (!priv->updatable_data_allocated)
		return;

	db_model_free_stmt_data (obj);
		
	priv->tables = g_hash_table_new_full (
		(GHashFunc) table_hash,
		(GEqualFunc) table_equal,
		(GDestroyNotify) table_free,
		(GDestroyNotify) table_data_free
	);	

	for (i = 0; i < priv->result->ncols; i++)
	if ((priv->column[i].info & DB_COLUMN_PRI_KEY))
	{
		table.name = priv->column[i].table;
		table.schema = priv->column[i].schema;
		
		table_data = g_hash_table_lookup (priv->tables, &table);
		
		if (!table_data)
		{
			table_data = g_new0 (TableData, 1);
			table_data->pkeys = NULL;
			g_hash_table_insert (priv->tables, table_copy (&table), table_data);
		}
		
		table_data->pkeys = g_slist_prepend (table_data->pkeys, GINT_TO_POINTER (i));
		g_hash_table_insert (priv->tables, table_copy (&table), table_data);
	}
	
	for (i = 0; i < priv->result->ncols; i++)
	{
		table.name = priv->column[i].table;
		table.schema = priv->column[i].schema;

		table_data = g_hash_table_lookup (priv->tables, &table);
		
		if (table_data && table_data->pkeys)
			gvn_param_spec_set_editable (priv->column[i].spec, TRUE);
	}
}

// Signal Handlers

enum
{
	 STATUS_CHANGED
	,LINE_INSERTED
	,LINE_DELETED
	,LINE_TOGGLED
	,LINE_UPDATED
	,LINES_REORDERED
	,SORT_CHANGED
	,OPERATIONS_DONE

	,LAST_SIGNAL
};

static guint db_model_signal[LAST_SIGNAL] = {0};

// Default signal handlers

static void db_model_on_line_updated (DbModel * obj, DbIter * iter)
{
	GValue * updated_value = obj->priv->updated_value;
	gint col;

	if (!updated_value || !G_IS_VALUE (updated_value)) return;

	col = obj->priv->updated_col;
	gvn_value_ccopy (updated_value, DB_ROW_FIELD (iter->data, col));

	db_model_manage_join (obj, iter, col);

	g_value_unset (updated_value);
	g_free (updated_value);
	obj->priv->updated_value = NULL;
}

static void db_model_on_line_deleted (DbModel * obj, gint position)
{
	gint r_ind;

	for (r_ind = position + 1; r_ind < obj->priv->result->nrows; r_ind++)
		DB_ROW_POSITION (g_ptr_array_index (obj->priv->data, (guint) r_ind))--;

	g_ptr_array_remove_index (obj->priv->data, (guint) position);

	obj->priv->result->nrows--;
}

// External signal handlers

static void db_model_calculate_update_flags (DbModel * obj)
{
	gint i;
	Table table;
	DbModelPrivate * priv = obj->priv;
	
	priv->main_table = NULL;
	
	if (priv->result)
	{
		if (!priv->user_main_table)
		{
			for (i = 0; i < priv->result->ncols; i++)
			if (priv->column[i].info & DB_COLUMN_PRI_KEY)
			{
				table.name = priv->column[i].table;
				table.schema = priv->column[i].schema;
				priv->main_table = g_hash_table_lookup (priv->tables, &table);
				break;
			}
		}
		else
		{
			table_parse (&table, priv->user_main_table);
			priv->main_table = g_hash_table_lookup (priv->tables, &table);
			
			if (!priv->main_table)
				g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN), G_LOG_LEVEL_WARNING,
					"Can't set '%s' as main table", table.name);
		}
	}
	
	if (priv->main_table)
		priv->update_flags = DB_MODEL_ALL & priv->user_update_flags;
	else
		priv->update_flags = 0;
	
	if (!priv->updatable_data_allocated && priv->update_flags)
	{
		priv->updatable_data_allocated = TRUE;
		priv->operation = g_queue_new ();
		priv->row_ops = g_hash_table_new (g_direct_hash, g_direct_equal);

		priv->column_defaults = g_hash_table_new_full (
			(GHashFunc) field_hash,
			(GEqualFunc) field_equal,
			(GDestroyNotify) field_free,
			(GDestroyNotify) column_def_free
		);
	}
}

static void db_model_on_data_ready (DbRequest * request, DbModel * obj)
{
	gint i;
	DbResult * r;
	DbModelPrivate * priv = obj->priv;

	if (priv->request != request)
	{
		g_object_unref (request);
		return;
	}

	for (i = 0; i < priv->result_pos; i++)
		db_request_fetch_non_select (request, NULL);

	if ((r = db_request_fetch_result (request, NULL)))
	{
		gint i;

		priv->column = r->column;
		priv->data = r->data;

		if (!r->data && !r->column)
		{
			db_result_free (r);
			db_model_set_status (obj, DB_MODEL_STATUS_CLEAN);
		}
		else
		{
			priv->result = r;

			if (priv->fresh)
			{
				db_model_calculate_update_flags (obj);
				db_model_alloc_stmt_data (obj);

				for (i = 0; i < priv->result->ncols; i++)
					g_hash_table_insert (priv->column_index,
						g_strdup (priv->column[i].alias), GINT_TO_POINTER (i));

				db_model_post_process_query (obj);
			}
			else
				db_model_set_sort_column_id (obj,
					priv->old_sort_column_id, priv->old_order);

			db_model_set_status (obj, DB_MODEL_STATUS_READY);
		}
	}
	else
		db_model_set_status (obj, DB_MODEL_STATUS_ERROR);

	g_clear_object (&priv->request);
}

static void db_model_process_insert (DbModel * obj, DbRequest * request, DbRow * row, GError * err)
{
	gint i, j;
	DbModelPrivate * priv = obj->priv;
	DbResult * result;
	DbRow * req_row;
	DbIter iter;
	
	result = db_request_fetch_result (request, &err);

	if (result && result->data && result->nrows > 0 )
	{
		iter.stamp = priv->stamp;
		iter.data = row;
		req_row = g_ptr_array_index (result->data, 0);

		for (i = 0; i < result->ncols; i++)
		for (j = 0; j < priv->result->ncols; j++)
		if (!g_strcmp0 (priv->column[j].name, result->column[i].name)
		&& !g_strcmp0 (priv->column[j].table, result->column[i].table))
		{
			GValue * v;
			gboolean emit = TRUE;

			priv->updated_value = g_new0 (GValue, 1);

			if ((v = &req_row->value[i]) && G_IS_VALUE (v)
			&& !gvn_value_is_null (DB_ROW_FIELD (req_row, i))
			&& gvn_value_is_null (DB_ROW_FIELD (row, j)))
			{
				g_value_init (priv->updated_value, G_VALUE_TYPE (v));
				gvn_value_copy (v, priv->updated_value);
			}
			else if (gvn_value_is_null (DB_ROW_FIELD (row, j)))
			{
				g_value_init (priv->updated_value, GVN_TYPE_NULL);
			}
			else
			{
				emit = FALSE;
				g_free (priv->updated_value);
			}

			if (emit)
			{
				priv->updated_col = j;
				g_signal_emit (obj, db_model_signal[LINE_UPDATED], 0, &iter);
			}
		}
	}
	
	if (result)
		db_result_free (result);
}

static void db_model_on_operations_done (DbRequest * request, DbModelRequest * data)
{
	GList * l;
	guint i = 0;
	DbOperation * op;
	GError * err = NULL;
	DbModel * obj = data->obj; 
	DbModelPrivate * priv = obj->priv;

	priv->pending_request =
		g_slist_remove (priv->pending_request, request);
		
	l = g_queue_peek_head_link (data->operations);

	for (; l; l = l->next)
	{
		op = l->data;
		
		if (op->type & DB_MODEL_ROW_OP_DELETE
		&& op->type & DB_MODEL_ROW_OP_INSERT) // DELETE + INSERT
		{
			g_signal_emit (obj,
				db_model_signal[LINE_DELETED], 0, DB_ROW_POSITION (op->row));
			continue;
		}
		
		if (db_request_fetch_non_select (request, &err) == -1)
			break;

		g_hash_table_remove (priv->row_ops, op->row);

		if (op->type & DB_MODEL_ROW_OP_DELETE) // DELETE
		{
			g_signal_emit (obj,
				db_model_signal[LINE_DELETED], 0, DB_ROW_POSITION (op->row));
		}
		else if (op->type & DB_MODEL_ROW_OP_INSERT) // INSERT + SELECT
		{
			db_model_process_insert (obj, request, op->row, err);
		}
		else if (op->type & DB_MODEL_ROW_OP_UPDATE) // UPDATE || INSERT + SELECT
		{
			guint j;
			SqlList * list;
			DbIter iter;
			SqlObject * multi = sql_list_get (data->stmts, i);
		
			g_object_get (multi, "stmts", &list, NULL);

			for (j = 0; j < sql_list_length (list); j++)
			{
				if (j > 0)
					db_request_fetch_non_select (request, &err);

				if (G_OBJECT_TYPE (sql_list_get (list, j)) == SQL_TYPE_MULTI_STMT)
					db_model_process_insert (obj, request, op->row, err);
			}
			
			iter.stamp = priv->stamp;
			iter.data = op->row;
			g_signal_emit (obj, db_model_signal[LINE_UPDATED], 0, &iter);
		}

		i++;
	}

	if (!err)
	{
		while ((op = g_queue_pop_head (data->operations)))
			db_model_free_operation (obj, op);

		g_signal_emit (obj, db_model_signal[OPERATIONS_DONE], 0);
	}

	g_object_unref (request);
}

static void db_model_on_stmt_changed (SqlStmt * stmt, DbModel * obj)
{
	db_model_refresh (obj);
}

static void db_model_on_batch_changed (SqlBatch * batch, DbModel * obj)
{
	db_model_refresh (obj);
}

static void db_model_on_join_query_done (DbRequest * request, JoinData * join_data)
{
	gint i, j;
	DbResult * r;
	DbIter * iter = join_data->iter;
	GError * err = NULL;
	DbModel * obj = join_data->obj;
	DbModelPrivate * priv = obj->priv;
	
	while ((r = db_request_fetch_result (request, &err)))
	{
		for (i = 0; i < priv->result->ncols; i++)
		for (j = 0; j < r->ncols; j++)
			if (!g_strcmp0 (r->column[j].table, priv->column[i].table)
			&& !g_strcmp0 (r->column[j].name, priv->column[i].name))
			{
				if (r->nrows > 0)
				{
					GValue * new_value =
						DB_ROW_FIELD (g_ptr_array_index (r->data, 0), j);

					priv->updated_value = g_new0 (GValue, 1);
					g_value_init (priv->updated_value, G_VALUE_TYPE (new_value));
					gvn_value_copy (new_value, priv->updated_value);								
				}
				else
					priv->updated_value =
						g_value_init (g_new0 (GValue, 1), GVN_TYPE_NULL);

				priv->updated_col = i;
				g_signal_emit (obj, db_model_signal[LINE_UPDATED], 0, iter);
			}
			
		db_result_free (r);
	}

	if (err)
	{
		g_message ("%s", err->message);
		g_error_free (err);
	}

	priv->pending_request =
		g_slist_remove (obj->priv->pending_request, request);
	g_object_unref (request);
}

// Private helper methods and functions

static void join_data_free (JoinData * join_data)
{
	g_object_unref (join_data->obj);
	db_iter_free (join_data->iter);
	g_free (join_data);
}

static void db_model_free_operation (DbModel * obj, DbOperation * op)
{
	if (op->updated)
		g_slist_free_full (op->updated, (GDestroyNotify) db_updated_field_free);

	g_hash_table_remove (obj->priv->row_ops, op->row);

	g_free (op);
}

static void db_model_clean_operations (DbModel * obj)
{
	DbOperation * op;
	DbModelPrivate * priv = obj->priv;

	while ((op = g_queue_pop_head (priv->operation)))
		db_model_free_operation (obj, op);
}

static void db_model_request_free (DbModelRequest * req)
{
	if (req)
	{
		DbOperation * op;
	
		while ((op = g_queue_pop_head (req->operations)))
		{
			op->locked = FALSE;
			g_queue_push_tail (req->obj->priv->operation, op);
		}
	
		g_queue_free (req->operations);
		g_object_unref (req->obj);
		g_object_unref (req->stmts);
	}

	g_free (req);
}

static void db_operation_add_updated (DbOperation * op, gint col)
{
	GSList * n; 
	DbUpdatedField * u;

	for (n = op->updated; n; n = n->next)
	if (((DbUpdatedField *) n->data)->column == col)
		return;

	u = g_new (DbUpdatedField, 1);
	gvn_value_copy (DB_ROW_FIELD (op->row, col)
		,g_value_init (u->value = g_new0 (GValue, 1)
			,G_VALUE_TYPE (DB_ROW_FIELD (op->row, col))));
	u->column = col;
	op->type |= DB_MODEL_ROW_OP_UPDATE;
	op->updated = g_slist_prepend (op->updated, u);

	return;
}

static gboolean db_model_set_row_operation (DbModel * obj,
	DbRow * row, DbModelRowOp type, gint col)
{
	DbOperation * op = g_hash_table_lookup (obj->priv->row_ops, row);

	if (!op)
	{
		DbOperation * new_op = g_new (DbOperation, 1);
		new_op->locked = FALSE;
		new_op->row = row;
		new_op->updated = NULL;
		new_op->type = type;

		if (type & DB_MODEL_ROW_OP_UPDATE)
			db_operation_add_updated (new_op, col);
/*			if (!db_operation_add_updated (new_op, col))
			{
				g_free (new_op);
				return FALSE;
			}
*/
		g_hash_table_insert (obj->priv->row_ops, row, new_op);
		g_queue_push_tail (obj->priv->operation, new_op);
	}
	else if (!op->locked)
	{
		if (type & DB_MODEL_ROW_OP_DELETE)
			op->type ^= DB_MODEL_ROW_OP_DELETE;

		if (type & DB_MODEL_ROW_OP_UPDATE)
			db_operation_add_updated (op, col);
//			if (!db_operation_add_updated (op, col))
//				return FALSE;
	}
	else	
		return FALSE;

	return TRUE;
}

void db_model_reverse_operations (DbModel * obj)
{
	DbModelPrivate * priv = obj->priv; 
	DbOperation * op;

	g_return_if_fail (DB_IS_MODEL (obj));

	while ((op = g_queue_pop_tail (priv->operation)))
	{
		DbIter iter;
		iter.data = op->row;
		iter.stamp = priv->stamp;

		g_hash_table_remove (priv->row_ops, op->row);
 
		if (op->type & DB_MODEL_ROW_OP_DELETE)
		{
			if (op->type & DB_MODEL_ROW_OP_INSERT)
			{
				g_signal_emit (obj,
					db_model_signal[LINE_DELETED], 0, DB_ROW_POSITION (op->row));
			}
			else
				g_signal_emit (obj, db_model_signal[LINE_TOGGLED], 0, &iter);
		}
		else if (op->type & DB_MODEL_ROW_OP_INSERT)
		{
			g_signal_emit (obj,
				db_model_signal[LINE_DELETED], 0, DB_ROW_POSITION (op->row));
		}
		else if (op->type & DB_MODEL_ROW_OP_UPDATE)
		{
			GSList * n;

			for (n = op->updated; n; n = n->next)
			{
				DbUpdatedField * u = n->data;

				priv->updated_value = g_new0 (GValue, 1);
				g_value_init (priv->updated_value, G_VALUE_TYPE (u->value));
				gvn_value_copy (u->value, priv->updated_value);
				priv->updated_col = u->column;

				g_signal_emit (obj, db_model_signal[LINE_UPDATED], 0, &iter);
			}
		}

		db_model_free_operation (obj, op);
	}
}

static DbModelField * db_model_field_new (const gchar * table, const gchar * schema)
{
	DbModelField * field = g_new (DbModelField, 1);
	field->schema = g_strdup (schema);
	field->table = g_strdup (table);
	field->name = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
	field->main = FALSE;
	return field;
}

static DbModelField * db_model_field_new_from_string (const gchar * str)
{
	DbModelField * field = NULL;
	gchar * new_str = g_strdup (str), ** aux, ** f;
	gint i, f_len;
	g_strstrip (new_str);
	f = g_strsplit (new_str, ".", G_MAXINT);
	g_free (new_str);
	f_len = g_strv_length (f);

	for (i = 0; i < f_len; i++)
	{
		aux = g_strsplit (f[i], "\"", G_MAXINT);

		if (g_strcmp0 (aux[0], ""))
		{
			g_strfreev (aux);
			break;
		}

		g_free (f[i]);
		f[i] = g_strdup (aux[1]);
		g_strfreev (aux);
	}

	switch (f_len)
	{
		case 3:
		{
			field = db_model_field_new (f[1], f[0]);
			g_ptr_array_add (field->name, g_strdup (f[2]));
			break;
		}
		case 2:
		{
			field = db_model_field_new (f[0], NULL);
			g_ptr_array_add (field->name, g_strdup (f[1]));
			break;
		}
		case 1:
		{
			field = db_model_field_new (NULL, NULL);
			g_ptr_array_add (field->name, g_strdup (f[0]));
		}
	}

	g_strfreev (f);

	return field;
}

static void db_model_field_free (DbModelField * field)
{
	if (field)
	{
		if (field->schema)
			g_free (field->schema);
		if (field->table)
			g_free (field->table);
		if (field->name)
			g_ptr_array_free (field->name, TRUE);
	}

	g_free (field);
}

static void db_join_free (DbJoin * join)
{
	if (join)
	{
		if (join->left)
			db_model_field_free (join->left);
		if (join->right)
			db_model_field_free (join->right);
	}

	g_free (join);
}
/*
static void db_model_calculate_col_def (DbModel * obj, SqlJoin * join,
	SqlField * l_field, SqlField * r_field)
{
	gint i, col = -1;
	gchar * dst = NULL;
	DbModelPrivate * priv = obj->priv;
	SqlField * f = NULL;
	SqlTarget * l_table = join->target_left, * r_table = join->target_right; 

	for (i = 0; i < priv->result->ncols; i++)
	{
		f = NULL;

		if (!g_strcmp0 (priv->column[i].name, l_field->name))
		{
			f = l_field;
			dst = (join->type == SQL_JOIN_TYPE_RIGHT) ?
				l_field->name : r_field->name;
		}
		else if (!g_strcmp0 (priv->column[i].name, r_field->name))
		{
			f = r_field;
			dst = (join->type == SQL_JOIN_TYPE_LEFT) ?
				l_field->name : r_field->name;
		}

		if (f)
		{//TODO add schema checks
			if (f->target)
			{
				if (!g_strcmp0 (priv->column[i].table, f->target)
				|| (!g_strcmp0 (priv->column[i].table, SQL_TABLE (l_table)->name)
					&& !g_strcmp0 (f->target, l_table->alias)))
				{
					col = i;
					break;
				}
				else if (!g_strcmp0 (priv->column[i].table, SQL_TABLE (r_table)->name)
					&& !g_strcmp0 (f->target, r_table->alias))
				{
					col = i;
					break;
				}
			}
			else
			{
				col = i;
				break;
			}
		}
	}

	if (f)
		db_model_set_default_value_from_column (obj, dst, col);
}

static void db_model_set_join_fields (DbModel * obj, SqlJoin * join,
	SqlField * lsql_field, SqlField * rsql_field,
	DbModelField * lfield, DbModelField * rfield)
{
	gboolean check;
	SqlTarget * ltarget = join->target_left, * rtarget = join->target_right;

	check = !g_strcmp0 (lfield->schema, lsql_field->schema)
		|| !g_strcmp0 (lfield->table, lsql_field->target)
		|| !g_strcmp0 (ltarget->alias, lsql_field->target)
		|| !g_strcmp0 (rfield->schema, rfield->schema)
		|| !g_strcmp0 (rfield->table, rsql_field->target)
		|| !g_strcmp0 (rtarget->alias, rsql_field->target);

	g_ptr_array_add (lfield->name,
		g_strdup (check ? lsql_field->name : rsql_field->name));
	g_ptr_array_add (rfield->name,
		g_strdup (check ? rsql_field->name : lsql_field->name));
}
*/
static void db_model_post_process_query (DbModel * obj)
{
/*
//	TODO When parser gets fully functional, these 3 lines won't be needed,
//	because obj->stmt will be a parsed stmt:
	gchar * rend = db_conn_render (obj->priv->conn, obj->priv->stmt, obj->priv->batch, NULL);
	SqlObject * stmt = sql_parser_parse (rend);

	g_free (rend);

	if (stmt && SQL_IS_SELECT (stmt))
	{
		DbModelField * lfield, * rfield;
		SqlJoin * join;
		GList * n;
		SqlSelect * select = SQL_SELECT (stmt);
		gboolean calculate_join = FALSE;

		for (n = sql_list_get_items (SQL_DML (select)->targets); n; n = n->next)
			if ((join = n->data)
			&& SQL_IS_JOIN (join)
			&& SQL_IS_TABLE (join->target_left)
			&& SQL_IS_TABLE (join->target_right)
			&& SQL_IS_OPERATION (join->condition))
			{
//				DbJoin and ColDef creation
				GList * operators;
				SqlOperation * op = SQL_OPERATION (join->condition);
				SqlField * lsql_field = NULL, * rsql_field = NULL;

				lfield = db_model_field_new (SQL_TABLE (join->target_left)->name, NULL);
				rfield = db_model_field_new (SQL_TABLE (join->target_right)->name, NULL);

				if (join->type == SQL_JOIN_TYPE_RIGHT)
					rfield->main = TRUE;
				else
					lfield->main = TRUE;

				if (op->type == SQL_OPERATION_TYPE_AND)
				{
					GList * l;

					for (l = sql_list_get_items (op->operators); l; l = l->next)
					{
						SqlOperation * subop = l->data;
						operators = sql_list_get_items (subop->operators);

						if (SQL_IS_OPERATION (subop)
						&& subop->type == SQL_OPERATION_TYPE_EQUAL
					    && operators->data // Left Field
						&& operators->next && operators->next->data) // Right Field
						{
							lsql_field = SQL_FIELD (operators->data);
							rsql_field = SQL_FIELD (operators->next->data);

							db_model_set_join_fields (obj, join,
								lsql_field, rsql_field, lfield, rfield);

							calculate_join = TRUE;

							if (join->type != SQL_JOIN_TYPE_INNER)
								db_model_calculate_col_def
									(obj, join, lsql_field, rsql_field);
						}
						else
						{
							calculate_join = FALSE;
							break;
						}
					}
				}
				else 
				{
					operators = sql_list_get_items (op->operators);

					if (op->type == SQL_OPERATION_TYPE_EQUAL && operators->data
						&& operators->next && operators->next->data)
					{
						lsql_field = SQL_FIELD (operators->data);
						rsql_field = SQL_FIELD (operators->next->data);

						db_model_set_join_fields (obj, join,
							lsql_field, rsql_field, lfield, rfield);

						calculate_join = TRUE;

						if (join->type != SQL_JOIN_TYPE_INNER)
							db_model_calculate_col_def
								(obj, join ,lsql_field, rsql_field);
					}
				}

				if (calculate_join)
				{
					DbJoin * join_res = g_new (DbJoin, 1);
					join_res->left = lfield;
					join_res->right = rfield;

					obj->priv->join = g_slist_prepend (obj->priv->join, join_res);
				}
				else
				{
					db_model_field_free (lfield);
					db_model_field_free (rfield);
				}
			}
	}

	if (G_IS_OBJECT (stmt))
		g_object_unref (stmt);
*/
}

static inline gboolean stored (const gint * v, const gint length, const gint target)
{
	gint i;

	for (i = 0; i < length; i++)
		if (v[i] == target)
			return TRUE;
	return FALSE;
}

/*
 * Comparison between values, using case-insensitive and UTF-8 strings
 */
static gint db_model_value_compare0 (const GValue * a, const GValue * b)
{
	GType a_type = G_VALUE_TYPE (a);
	gboolean a_is_val = G_IS_VALUE (a);
	gboolean b_is_val = G_IS_VALUE (b);
	
	if (!(a_is_val && b_is_val))
	{
		if (a_is_val)
			return 1;
		if (b_is_val)
			return -1;
	}
	else if (a_type == G_VALUE_TYPE (b))
	{
		switch (a_type)
		{
			case G_TYPE_FLOAT:
			{
				gfloat aux = g_value_get_float (a) - g_value_get_float (b);
				return (aux > 0.0) ? 1 : (aux < 0.0) ? -1 : 0;
			}
			case G_TYPE_DOUBLE:
			{
				gdouble aux = g_value_get_double (a) - g_value_get_double (b);
				return (aux > 0.0) ? 1 : (aux < 0.0) ? -1 : 0;
			}
			case G_TYPE_INT:
				return g_value_get_int (a) - g_value_get_int (b);
			case G_TYPE_UINT:
				return (gint) (g_value_get_uint (a) - g_value_get_uint (b));
			case G_TYPE_LONG:
				return (gint) (g_value_get_long (a) - g_value_get_long (b));
			case G_TYPE_ULONG:
				return (gint) (g_value_get_ulong (a) - g_value_get_ulong (b));
			case G_TYPE_BOOLEAN:
				return (gint) (g_value_get_boolean (a) - g_value_get_boolean (b));
			case G_TYPE_CHAR:
				return (gint) (g_value_get_schar (a) - g_value_get_schar (b));
			case G_TYPE_STRING:
			{
				gchar * a_str = g_utf8_casefold (g_value_get_string (a), -1);
				gchar * b_str = g_utf8_casefold (g_value_get_string (b), -1);
				return g_utf8_collate (a_str, b_str);
			}
			default:
			if (a_type == G_TYPE_DATE)
				return g_date_compare (g_value_get_boxed (a), g_value_get_boxed (b));
			if (a_type == G_TYPE_DATE_TIME)
				return g_date_time_compare (g_value_get_boxed (a), g_value_get_boxed (b));
			else if (a_type == G_TYPE_BYTES)
				return (gint) (g_value_get_boxed (a) - g_value_get_boxed (b));
			else if (a_type == GVN_TYPE_NULL)
				return 0;
			else
				g_warning ("Attempting to compare invalid types: %s\n",
					g_type_name (a_type));
		}
	}
	else if (gvn_value_is_null (a))
		return -1;
	else if (gvn_value_is_null (b))
		return 1;

	return 1;
}

static gint db_model_valcmp_asc (gpointer * a, gpointer * b, gpointer col)
{
	DbRow * first = *a;
	DbRow * second = *b;
	return db_model_value_compare0 (&first->value[GPOINTER_TO_INT (col)]
				,&second->value[GPOINTER_TO_INT (col)]);
}

static gint db_model_valcmp_desc (gpointer * a, gpointer * b, gpointer col)
{
	DbRow * first = *a;
	DbRow * second = *b;
	return -db_model_value_compare0 (&(first->value)[GPOINTER_TO_INT (col)]
				,&(second->value)[GPOINTER_TO_INT (col)]);

}

static void db_model_set_status (DbModel * obj, DbModelStatus status)
{
	obj->priv->status = status;
	g_signal_emit (obj, db_model_signal[STATUS_CHANGED], 0, status);
}

static void db_model_cancel_pending_requests (DbModel * obj)
{
	GSList * n;

	for (n = obj->priv->pending_request; n; n = n->next)
		db_request_cancel (n->data);
}

static void db_model_add_pending_request (DbModel * obj, DbRequest * request)
{
	obj->priv->pending_request = g_slist_prepend (obj->priv->pending_request,
		request);
}

static void db_model_manage_join (DbModel * obj, DbIter * iter, gint col)
{
	DbModelPrivate * priv = obj->priv;

	if (priv->join
	&& gvn_param_spec_get_editable (priv->column[col].spec)
	&& !gvn_value_is_null (DB_ROW_FIELD (iter->data, col)))
	{
		gint i;
		GSList * n;
		gboolean send_request = FALSE, end = FALSE;
		SqlList * stmts = g_object_ref_sink (sql_list_new (SQL_TYPE_MULTI_STMT));

/*FIXME
		for (i = 0; i < obj->ncols; i++)
			// Check for multi-field pkeys to be fully set
			// need to know the total number of pkey fields
			if (i != col && obj->column[i].info & DB_COLUMN_PRI_KEY
			&& gvn_value_is_null (DB_ROW_FIELD (iter->data, i))
			&& !g_strcmp0 (obj->column[i].table, obj->column[col].table))
			{
				end = TRUE;
				break;
			}
*/
		if (!end)
		for (n = priv->join; n; n = n->next)
		{
			gint j;
			SqlObject * where, * select;
			DbModelField * main_field = NULL, * other_field = NULL;
			DbJoin * join = n->data;

			if (join->left->main)
			{
				main_field = join->left;
				other_field = join->right;
			}
			else if (join->right->main)
			{
				main_field = join->right;
				other_field = join->left;
			}

			for (i = 0; i < main_field->name->len; i++)
			if (!g_strcmp0 (priv->column[col].table, main_field->table)
			&& !g_strcmp0 (priv->column[col].name,
				g_ptr_array_index (main_field->name, i)))
			{
				send_request = TRUE;
				break;
			}

			if (!send_request)
				continue; // Continue to the next DbJoin in the list

			select  = sql_select_new ();

			sql_object_add_child (select, "targets", sql_table_new (other_field->table, NULL));

			where = sql_operation_new (SQL_OPERATION_TYPE_AND); 

			for (i = 0; i < priv->result->ncols; i++)
			if (!g_strcmp0 (priv->column[i].table, other_field->table))
			{
				sql_object_add_child (select, "fields",
					sql_field_new_with_target (priv->column[i].name, other_field->table, NULL));
			}
			else if (!g_strcmp0 (priv->column[i].table, main_field->table))
			{
				for (j = 0; j < main_field->name->len; j++)	
				if (!g_strcmp0 (priv->column[i].name,
					g_ptr_array_index (main_field->name, j)))
				{
					SqlObject * equal = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);

					sql_object_add_child (equal, "operators",
						sql_field_new_with_target (g_ptr_array_index (other_field->name, j)
							,other_field->table, NULL));

					sql_object_add_child (equal, "operators",
						sql_value_new_with_value (DB_ROW_FIELD (iter->data, i)));

					sql_object_add_child (where, "operators", equal);
				}
			}

			sql_object_set (select, "where", where);
			sql_list_add (stmts, select);
		}

		if (send_request)
		{
			JoinData * join_data;
			DbRequest * request;

			join_data = g_new (JoinData, 1);
			join_data->obj = g_object_ref (obj);
			join_data->iter = db_iter_copy (iter);
			join_data->col = col;

			request = db_conn_query_with_stmt_async (priv->conn
				,g_object_new (SQL_TYPE_MULTI_STMT, "stmts", stmts, NULL)
				,NULL
				,(DbRequestDoneCallback) db_model_on_join_query_done
				,join_data
				,(GDestroyNotify) join_data_free
			);
			db_model_add_pending_request (obj, request);
		}

		g_object_unref (stmts);
	}
}

static void db_model_clear (DbModel * obj)
{
	DbModelPrivate * priv = obj->priv;
	
	if (priv->request)
	{
		db_request_cancel (priv->request);
		priv->request = NULL;
	}
	else if (priv->result)
	{
		db_model_clean_operations (obj);
		db_model_cancel_pending_requests (obj);

		db_result_free (priv->result);
		priv->result = NULL;
		priv->column = NULL;
		priv->data = NULL;
		
		g_free (priv->main_table);
		priv->main_table = NULL;
		priv->update_flags = 0;

		priv->fresh = FALSE;
		priv->old_order = priv->order;
		priv->old_sort_column_id = priv->sort_column_id;
		priv->sort_column_id = DB_MODEL_UNSORTED_SORT_COLUMN_ID;
		priv->stamp = g_random_int ();
	}
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Public

//								Set&Get methods

/**
 * db_model_set_conn:
 * @obj: a #DbModel
 * @conn: (allow-none): a #DbConn
 * 
 * Sets the connection through which the communication is established with
 * the database.
 **/
void db_model_set_conn (DbModel * obj, DbConn * conn)
{
	g_return_if_fail (DB_IS_MODEL (obj));
	g_return_if_fail (DB_IS_CONN (conn) || !conn);

	if (conn)
	{
		if (!obj->priv->conn)
		{
			obj->priv->conn = g_object_ref (conn);
			db_model_on_stmt_changed (obj->priv->stmt, obj);
		}
		else
			g_warning ("DbModel: The connection can only be set once");
	}
}

/**
 * db_model_get_conn:
 * @obj: a #DbModel
 * 
 * Returns the connection through which the communication is established with
 * the database.
 * 
 * Return value: (transfer none): the #DbConn of the model
 **/
DbConn * db_model_get_conn (DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), NULL);	
	
	return obj->priv->conn;
}

/**
 * db_model_get_spec:
 * @obj: a #DbModel
 * @col: the number of a column of @obj
 * 
 * Returns the #GvnParamSpec of a field in the position @col of @obj.
 * 
 * Return value: (transfer none): the #GvnParamSpec of the column with number @col
 **/
const GvnParamSpec * db_model_get_spec (DbModel * obj, gint col)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), NULL);

	if ((obj->priv->result && 0 <= col
	&& col < obj->priv->result->ncols)
	&& obj->priv->column)
		return obj->priv->column[col].spec;
	return NULL;
}

/**
 * db_model_get_column_name:
 * @obj: a #DbModel
 * @col: the number of a column of @obj
 * 
 * Retrieves the name of a field in the position @col of @obj.
 * 
 * Return value: the name of the column with number @col
 **/
const gchar * db_model_get_column_name (DbModel * obj, gint col)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), NULL);

	if ((obj->priv->result
	&& 0 <= col
	&& col < obj->priv->result->ncols)
	&& obj->priv->column)
		return obj->priv->column[col].alias;
	return NULL;
}

/**
 * db_model_get_column_index:
 * @obj: a #DbModel
 * @name: the name of a column of @obj
 * 
 * Retrieves the position in the query of the column called @name.
 * 
 * Return value: the index of the column with name @name or -1 if there isn't
 * a column called @name or if @name is NULL
 **/
gint db_model_get_column_index (DbModel * obj, const gchar * name)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), -1);

	if (!name || !obj->priv->column_index)
		return -1;

	return GPOINTER_TO_INT (g_hash_table_lookup (obj->priv->column_index, name));
}

/**
 * db_model_get_status:
 * @obj: a #DbModel
 *
 * Returns the current #DbModelStatus of @obj.
 *
 * Return value: the status of @obj
 **/
DbModelStatus db_model_get_status (DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), -1);
	return obj->priv->status;
}

/**
 * db_model_get_mode:
 * @obj: a #DbModel
 * 
 * Retrieves the current working mode of @obj. See #DbModelMode.
 * 
 * Return value: the current mode
 **/
DbModelMode db_model_get_mode (DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), -1);
	return (obj->priv->mode);
}

/**
 * db_model_set_mode:
 * @obj: a #DbModel
 * @mode: a #DbModelMode to set the model on
 * 
 * Sets the working mode of @obj to @mode. See #DbModelMode.
 **/
void db_model_set_mode (DbModel * obj, DbModelMode mode)
{
	g_return_if_fail (DB_IS_MODEL (obj));
	obj->priv->mode = mode;
}

/**
 * db_model_toggle_mode:
 * @obj: a #DbModel
 * 
 * Toogles the working mode of @obj. See #DbModelMode to see the two possible
 * modes.
 **/
void db_model_toggle_mode (DbModel * obj)
{
	g_return_if_fail (DB_IS_MODEL (obj));

	obj->priv->mode = (obj->priv->mode == DB_MODEL_MODE_ON_DEMAND)?
		DB_MODEL_MODE_ON_CHANGE:
		DB_MODEL_MODE_ON_DEMAND;
}

/**
 * db_model_set_result_pos:
 * @obj: a #DbModel
 * @pos: position of the query
 *
 * Sets the position where the query that will fill @model with data will be
 * placed in a multi-query statement.
 **/
void db_model_set_result_pos (DbModel * obj, guint pos)
{
	g_return_if_fail (DB_IS_MODEL (obj));

	obj->priv->result_pos = pos;
}

/**
 * db_model_get_update_flags:
 * @obj: a #DbModel
 * 
 * Retrieves the update flags of @obj. See #DbModelUpdateFlags for the details
 * of those flags. See also db_model_request_update_flags().
 * 
 * Return value: the #DbModelUpdateFlags of @obj
 **/
DbModelUpdateFlags db_model_get_update_flags (DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);

	return obj->priv->update_flags;
}

/**
 * db_model_request_update_flags:
 * @obj: a #DbModel
 * @flags: the set of #DbModelUpdateFlags to be set to @obj
 * 
 * Requests the update flags of @obj. See #DbModelUpdateFlags for the details
 * of those flags. If @obj does not allow an operation by itself, it will
 * log a warning when trying to set this operation. If the flag set is
 * #DB_MODEL_UPDATE it may not log until trying to update a non-updatable
 * field, see db_model_set_value(). See also db_model_get_update_flags() and
 * db_model_unset_update_flags().
 **/
void db_model_request_update_flags (DbModel * obj, DbModelUpdateFlags flags)
{
	g_return_if_fail (DB_IS_MODEL (obj));

	obj->priv->user_update_flags = flags;
	db_model_calculate_update_flags (obj);
}                                                                      

/**
 * db_model_unset_update_flags:
 * @obj: a #DbModel
 * @flags: the set of #DbModelUpdateFlags to be unset in @obj
 * 
 * Unsets the update flags of @obj. See #DbModelUpdateFlags for the details
 * of those flags. See also db_model_get_update_flags() and
 * db_model_request_update_flags().
 **/
void db_model_unset_update_flags (DbModel * obj, DbModelUpdateFlags flags)
{
	g_return_if_fail (DB_IS_MODEL (obj));

	obj->priv->user_update_flags &= ~flags;
	db_model_calculate_update_flags (obj);
}

/**
 * db_model_get_last:
 * @obj: a @DbModel
 * @iter: (out): an unitialized #DbIter
 * 
 * Points @iter to the last row of @obj.
 * 
 * Return value: %FALSE if @obj is a valid #DbModel, %TRUE otherwise
 **/
gboolean db_model_get_last (DbModel * obj, DbIter * iter)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);

	iter->data =  g_ptr_array_index
		(obj->priv->data, (guint) obj->priv->data->len - 1);
	iter->stamp = obj->priv->stamp;
	return TRUE;
}

/**
 * db_model_add_join_columns:
 * @obj: a #DbModel
 * @left: the field on the left of the join
 * @right: the field on the right of the join
 * 
 * Sets the binding betwen two joined fields. This will generate a SELECT query
 * to the database to change the corresponding values in the model. Currently
 * it's used by the internal parser.
 **/
void db_model_add_join_columns (DbModel * obj, const gchar * left, const gchar * right)
{
	g_return_if_fail (DB_IS_MODEL (obj));

	DbJoin * join = g_new (DbJoin, 1);
	join->left = db_model_field_new_from_string (left);
	join->right = db_model_field_new_from_string (right);

	obj->priv->join = g_slist_prepend (obj->priv->join, join);
}

/**
 * db_model_get_batch:
 * @obj: a #DbModel
 * @return: the #SqlBatch
 * 
 * Gets the batch used by the model. 
 **/
SqlBatch * db_model_get_batch (DbModel * obj)
{
	return obj->priv->batch;
}

/**
 * db_model_set_batch:
 * @obj: a #DbModel
 * @batch: the #SqlBatch
 * 
 * Sets the batch used by the model. 
 **/
void db_model_set_batch (DbModel * obj, SqlBatch * batch)
{
	DbModelPrivate * priv;
	
	g_return_if_fail (DB_IS_MODEL (obj));
	g_return_if_fail (SQL_IS_BATCH (batch) || !batch);

	priv = obj->priv;

	if (priv->batch)
	{
		g_signal_handlers_disconnect_by_func (priv->batch,
			db_model_on_batch_changed, obj);
		g_object_unref (priv->batch);
	}
	if (batch)
	{
		g_signal_connect (batch, "changed",
			G_CALLBACK (db_model_on_batch_changed), obj);
		g_object_ref_sink (batch);
	}

	priv->batch = batch;
}

/**
 * db_model_set_default_value_from_column:
 * @obj: a #DbModel
 * @field_str: the field to be set 
 * @column_str: field from wich the value is picked
 **/
void db_model_set_default_value_from_column (DbModel * obj,
	const gchar * field_str, const gchar * column_str)
{
	Field * field;
	ColumnDef * column_def;

	g_return_if_fail (DB_IS_MODEL (obj));
	g_return_if_fail (field_str);
	g_return_if_fail (column_str);
	
	field = field_new_from_string (field_str);

	column_def = column_def_new (FIELD_DEF, field_new_from_string (column_str));
	g_hash_table_insert (obj->priv->column_defaults, field, column_def);
}

/**
 * db_model_set_default_value_from_param:
 * @obj: a #DbModel
 * @field_str: the field to be set
 * @param: a #GvnParam
 * @link: 
 * 
 * Get the default value for @dst_field from @param. 
 **/
void db_model_set_default_value_from_param (DbModel * obj,
	const gchar * field_str, GvnParam * param, gboolean link)
{
	Field * field;
	ParamDef * param_def;
	ColumnDef * column_def;
	SqlObject * equal = NULL;

	g_return_if_fail (DB_IS_MODEL (obj));
	g_return_if_fail (field_str);
	g_return_if_fail (GVN_IS_PARAM (param));
	
	field = field_new_from_string (field_str);
	
	if (link)
	{
		SqlList * operands, * link_operands;
	
		db_model_alloc_link_data (obj);
		
		link_operands = sql_operation_get_operands (SQL_OPERATION (obj->priv->link_op));

		equal = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
		sql_list_add (link_operands, equal);
		
		operands = sql_list_new (SQL_TYPE_EXPR);
		sql_list_add (operands, sql_field_new_with_target (field->name, field->target, field->schema));
		sql_list_add (operands, sql_value_new_with_param (param));
		sql_operation_set_operands (SQL_OPERATION (equal), operands);
	}

	param_def = g_new (ParamDef, 1);
	param_def->param = g_object_ref (param);
	param_def->equal_op = equal;
	param_def->link_op = obj->priv->link_op;

	column_def = column_def_new (PARAM_DEF, param_def);
	g_hash_table_insert (obj->priv->column_defaults, field, column_def);
}

/**
 * db_model_request_main_table:
 * @obj: a #DbModel
 * @table: the name of the new main table of @model
 * 
 * Requests the main table of @model. The main table is the only table on a
 * #DbModel whose rows can be deleted or inserted.
 **/
void db_model_request_main_table (DbModel * obj, const gchar * table)
{
	g_return_if_fail (DB_IS_MODEL (obj));

	g_free (obj->priv->user_main_table);
	obj->priv->user_main_table = g_strdup (table);
	db_model_calculate_update_flags (obj);
}

/**
 * db_model_get_main_table:
 * @obj: a #DbModel
 * 
 * Returns the string with the name of the main table of @obj. The main table
 * is the only table on a #DbModel whose rows can be deleted or inserted. By 
 * default the main table is set to the table the first column field belongs.
 * To override this behaviour, use @db_model_request_main_table.
 *
 * Note that the requested main table may be different fron the actual main
 * table used by the model
 * 
 * Return value: the name of the main table of @obj
 **/
const gchar * db_model_get_main_table (DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), NULL);
	
	if (obj->priv->main_table)
		return obj->priv->main_table->name;
		
	return NULL;
}

/**
 * db_model_get_stmt:
 * @obj: a #DbModel
 * 
 * Returns the #SqlStmt which queries to the database about the data of @obj.
 * 
 * Return value: (transfer none): the #SqlStmt property of @obj
 **/
const SqlStmt * db_model_get_stmt (DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), NULL);

	return obj->priv->stmt;
}

/**
 * db_model_set_stmt:
 * @obj: a #DbModel
 * @stmt: the #SqlStmt
 * 
 * Sets the "stmt" property of the model. 
 **/
void db_model_set_stmt (DbModel * obj, SqlStmt * stmt)
{
	if (!stmt)
		return;

	g_return_if_fail (!obj->priv->stmt);
	g_return_if_fail (SQL_IS_STRING (stmt) || SQL_IS_SELECT (stmt));

	obj->priv->stmt = g_object_ref_sink (stmt);
	g_signal_connect (stmt, "changed", G_CALLBACK (db_model_on_stmt_changed), obj);
	db_model_on_stmt_changed (stmt, obj);
}

/**
 * db_model_set_sql:
 * @obj: a #DbModel
 * @sql: a value for the "sql property
 * 
 * Sets the "sql" property to @sql.
 **/
void db_model_set_sql (DbModel * obj, const gchar * sql)
{
	SqlObject * string;

	if (!sql)
		return;

	g_return_if_fail (DB_IS_MODEL (obj));

	g_free (obj->priv->sql);
	obj->priv->sql = g_strdup (sql);

	if (obj->priv->use_file)
		string = db_conn_create_stmt_from_file (obj->priv->conn, sql);
	else
		string = sql_string_new (sql);

	db_model_set_stmt (obj, SQL_STMT (string));		
}

/**
 * db_model_get:
 * @obj: a #DbModel
 * @iter: a #DbIter
 * @...: (out callee-allocates): pairs of column number and value return 
 * locations, terminated by -1
 * 
 * Gets the values on the specified columns and sets the return locations
 * pointing to these values. The column numbers must be integers, while the
 * return locations must be of the same type of the value being returned.
 * 
 * Returned values of type G_TYPE_OBJECT have to be unreferenced, values of
 * type G_TYPE_STRING or G_TYPE_BOXED have to be freed. Other values are passed
 * by value.
 **/
void db_model_get (DbModel * obj, DbIter * iter, ...)
{
	va_list va;
	gint column;
	GValue * val;

	g_return_if_fail (DB_IS_MODEL (obj));
	g_return_if_fail (obj->priv->result);
	g_return_if_fail (iter != NULL);

	va_start (va, iter);

	while ((column = (va_arg (va, gint))) >= 0 && column < obj->priv->result->ncols)
	{
		val = DB_ROW_FIELD (iter->data, column);

		if (gvn_value_is_null (val))
			switch (gvn_param_spec_get_gtype (obj->priv->column[column].spec))
			{
				case G_TYPE_CHAR:
					*va_arg (va, gchar*) = 0;
					break;
				case G_TYPE_INT:
					*va_arg (va, gint*) = 0;
					break;
				case G_TYPE_LONG:
					*va_arg (va, glong*) = 0;
					break;
				case G_TYPE_FLOAT:
					*va_arg (va, gfloat*) = 0;
					break;
				case G_TYPE_DOUBLE:
					*va_arg (va, gdouble*) = 0;
					break;
				default: // Objects or chararrays.
					*va_arg (va, gpointer*) = NULL;
			}
		else
			gvn_value_get_valist (val, va);
	}

	va_end (va);
	g_return_if_fail (column == -1);
}

/**
 * db_model_set:
 * @obj: a #DbModel
 * @iter: a #DbIter
 * @...: pairs of column number and value, terminated by -1
 * 
 * Sets the values on the specified columns. The column numbers must be
 * integers, while the values must be of the same type of the value being set.
 * 
 * The value will be referenced by the model if it is a G_TYPE_OBJECT, and it
 * will be copied if it is a G_TYPE_STRING or G_TYPE_BOXED.
 **/
void db_model_set (DbModel * obj, DbIter * iter, ...)
{
	gint column;
	gpointer content;
	GValue val = {0};
	va_list va;

	g_return_if_fail (DB_IS_MODEL (obj));
	g_return_if_fail (obj->priv->result);
	g_return_if_fail (iter != NULL);

	va_start (va, iter);

	while ((column = (va_arg (va, gint))) >= 0 && column < obj->priv->result->ncols)
	{
		content = va_arg (va, gpointer);
		gvn_value_new_with_content (&val,
			gvn_param_spec_get_gtype (obj->priv->column[column].spec), content);
		gvn_value_copy (&val, DB_ROW_FIELD (iter->data, column));
		g_value_unset (&val);
	}

	va_end (va);
	g_return_if_fail (column == -1);
}

/**
 * db_model_get_value:
 * @obj: a #DbModel
 * @iter: a #DbIter pointing to a row of @obj
 * @col: the number of the field to get the value
 * @err: (out) (allow-none): a #GError or %NULL to ignore errors
 * 
 * Gets a value from @obj pointed to by @iter and @col and puts it in @err. 
 * 
 * Return value: the value pointed to by @iter and @col or %NULL in case of error
 **/
const GValue * db_model_get_value (DbModel * obj, DbIter * iter, gint col, GError ** err)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), NULL);
	g_return_val_if_fail (obj->priv->data && obj->priv->result, NULL);
	g_return_val_if_fail (VALID_ITER (iter, obj), NULL);

	if (MODEL_NOT_READY (obj))
	{
		g_set_error (err
			,DB_MODEL_LOG_DOMAIN
			,DB_MODEL_ERROR_NOT_READY
			,"The model is not ready");
		return NULL;
	}

	if (0 > col || col >= obj->priv->result->ncols)
	{
		g_set_error (err
			,DB_MODEL_LOG_DOMAIN
			,DB_MODEL_ERROR_OUT_OF_RANGE
			,"Column out of range");
		return NULL;
	}

	return DB_ROW_FIELD (iter->data, col);
}

/**
 * db_model_set_value:
 * @obj: a #DbModel
 * @iter: a #DbIter pointing to the row to be set
 * @col: the column of the field to be set
 * @value: new value for the field pointed to by @iter and @col
 * @err: (out) (allow-none): a #GError or %NULL to ignore errors
 *
 * Sets the value of a single field to @value on @obj as well as on the database.
 * If the database update fails, the model is not updated and err is set
 * if it's not %NULL.
 * If @iter is pointing a new row (inserted by db_model_insert() but not yet
 * commited with db_model_perform_operations()) db_model_set_value() will only set
 * the value on the model.
 * 
 * Return value: %TRUE on success, %FALSE otherwise
 **/
gboolean db_model_set_value (DbModel * obj, DbIter * iter, gint col, const GValue * value, GError ** err)
{
	DbModelPrivate * priv = obj->priv;
	gboolean ret = FALSE;

	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);
	g_return_val_if_fail (VALID_ITER (iter, obj), FALSE);

	if (MODEL_NOT_READY (obj) || !priv->result)
	{
		g_set_error (err, DB_MODEL_LOG_DOMAIN
			,DB_MODEL_ERROR_NOT_READY, "Model not ready");
	}
	else
	{
		g_return_val_if_fail (0 <= col && col < priv->result->ncols, FALSE);

		GValue new_value = {0};
		DbRow * row = iter->data;
		DbOperation * operation = g_hash_table_lookup (priv->row_ops, row);
		DbModelRowOp row_op = operation ? operation->type : 0;
		GvnParamSpec * spec = priv->column[col].spec;

		if (!gvn_param_spec_validate (spec, value, err))
		{
			return FALSE;
		}

		if (!(priv->update_flags & DB_MODEL_UPDATE)
		&& !(row_op & DB_MODEL_ROW_OP_INSERT))
		{
			g_set_error (err, DB_MODEL_LOG_DOMAIN
				,DB_MODEL_ERROR_NOT_UPDATABLE, "Model not updatable");
			return FALSE;
		}

		if (!gvn_value_is_null (value))
		{
			g_value_init (&new_value, gvn_param_spec_get_gtype (spec));
			g_value_transform (value, &new_value);
		}
		else
			g_value_init (&new_value, GVN_TYPE_NULL);

		if (gvn_value_compare (&new_value, DB_ROW_FIELD (iter->data, col)))
		{
			ret = TRUE;
		}
		else if (!db_model_set_row_operation (obj, row, DB_MODEL_ROW_OP_UPDATE, col))
		{
			g_set_error (err, DB_MODEL_LOG_DOMAIN
				,DB_MODEL_ERROR_NOT_UPDATABLE, "Row locked. "
					"There are one or more operations being applied over this row.");
		}
		else
		{
			priv->updated_col = col;
			priv->updated_value = g_new0 (GValue, 1);
			gvn_value_copy (&new_value
				,g_value_init (priv->updated_value, G_VALUE_TYPE (&new_value)));

			g_signal_emit (obj, db_model_signal[LINE_UPDATED], 0, iter);

			if (priv->mode == DB_MODEL_MODE_ON_CHANGE
			&& !(row_op & DB_MODEL_ROW_OP_INSERT))
				db_model_perform_operations (obj, FALSE);

			ret = TRUE;
		}

		g_value_unset (&new_value);
	}

	return ret;
}

/**
 * db_model_insert:
 * @obj: a #DbModel
 * @iter: (out): a #DbIter that will point to the new row
 *
 * Inserts an empty row at the end of @obj. The values for this row must be
 * set one by one using db_model_set_value() or all at once with db_model_set().
 * And then committed with db_model_perform_operations().
 **/
gboolean db_model_insert (DbModel * obj, DbIter * iter)
{
	gint i;
	DbRow * row;
	DbModelPrivate * priv;

	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);

	priv = obj->priv;

	if (MODEL_NOT_READY (obj) || !priv->result)
	{
		g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN)
			,G_LOG_LEVEL_WARNING, "Model not ready");
		return FALSE;
	}

	if (!(priv->update_flags & DB_MODEL_INSERT))
	{
		g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN)
			,G_LOG_LEVEL_WARNING, "Can't insert into this Model");
		return FALSE;
	}

	row = db_row_new (priv->result->ncols, priv->result->nrows);

	for (i = 0; i < row->len; i++)
	{
		Field field;
		ColumnDef * column_def;
		const GValue * def_value = NULL;
		DbColumn col = priv->column[i];
		
		field.name = col.name;
		field.target = col.table;
		field.schema = col.schema;
		
		column_def = g_hash_table_lookup (priv->column_defaults, &field);
		
		if (column_def && column_def->type == PARAM_DEF)
		{
			ParamDef * param_def = column_def->def;
			def_value = gvn_param_get_value (param_def->param);
		}
		
		if (!def_value)
			def_value = gvn_param_spec_get_default (col.spec);
		
		if (def_value && G_VALUE_TYPE (def_value) != SQL_TYPE_FUNCTION)
		{
			g_value_init (&row->value[i], G_VALUE_TYPE (def_value));
			g_value_copy (def_value, &row->value[i]);
		}
		else
			g_value_init (&row->value[i], GVN_TYPE_NULL);
	}

	g_ptr_array_add (priv->data, row);
	iter->data = g_ptr_array_index (priv->data, (guint) priv->data->len - 1);
	iter->stamp = priv->stamp;
	priv->result->nrows++;

	db_model_set_row_operation (obj, iter->data, DB_MODEL_ROW_OP_INSERT, 0);

	g_signal_emit (obj, db_model_signal[LINE_INSERTED], 0, iter);

	return TRUE;
}

/**
 * db_model_delete:
 * @obj: a #DbModel
 * @iter: a #DbIter pointing to the row to be deleted
 * 
 * Deletes the row pointed to by @iter on @obj, as well as on the database (if
 * it was already there, i.e. it's not a new row).
 * If the deletion on the database fails, then the row on @obj is not deleted
 * and a log message is emitted.
 **/
void db_model_delete (DbModel * obj, DbIter * iter)
{
	g_return_if_fail (DB_IS_MODEL (obj));
	g_return_if_fail (VALID_ITER (iter, obj));

	if (MODEL_NOT_READY (obj))
	{
		g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN)
			,G_LOG_LEVEL_WARNING, "Model not ready");
		return;
	}

	if (!(obj->priv->update_flags & DB_MODEL_DELETE))
	{
		g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN)
			,G_LOG_LEVEL_WARNING, "Can't delete from this Model");
		return;
	}

	if (!db_model_set_row_operation
		(obj, g_ptr_array_index (obj->priv->data, DB_ROW_POSITION (iter->data)),
		DB_MODEL_ROW_OP_DELETE, 0))
	    return;

	g_signal_emit (obj, db_model_signal[LINE_TOGGLED], 0, iter);

	if (obj->priv->mode == DB_MODEL_MODE_ON_CHANGE)
		db_model_perform_operations (obj, FALSE);
}

/**
 * db_model_order_by:
 * @obj: a #DbModel
 * @col: the number of the column that will be the sort criteria
 * @order: the order to sort in
 * 
 * Sorts @obj in the order indicated by @order and using the data
 * on the field @col.
 **/
void db_model_order_by (DbModel * obj, gint col, DbSortType order)
{
	DbModelPrivate * priv;
	g_return_if_fail (DB_IS_MODEL (obj));
	priv = obj->priv;
	g_return_if_fail (priv->result && col >= -2 && col < priv->result->ncols);
	g_return_if_fail (order == DB_SORT_ASCENDING || order == DB_SORT_DESCENDING);

	gint new_order[priv->result->nrows];
	gint old_col = priv->sort_column_id;
	DbRow * row_iter;
	gint r_ind, i = 0;

	if ((priv->order == order && priv->sort_column_id == col)
		|| !priv->data
		|| col == DB_MODEL_UNSORTED_SORT_COLUMN_ID)
		return;

	if (col == DB_MODEL_DEFAULT_SORT_COLUMN_ID
	&& old_col != DB_MODEL_DEFAULT_SORT_COLUMN_ID)
		col = old_col;

	priv->order = order;
	priv->sort_column_id = col;
	g_signal_emit (obj, db_model_signal[SORT_CHANGED], 0, NULL);

	if (order == DB_SORT_DESCENDING) 
		g_ptr_array_sort_with_data (priv->data
			,(GCompareDataFunc) db_model_valcmp_desc
			,GINT_TO_POINTER (col));
	else
		g_ptr_array_sort_with_data (priv->data
			,(GCompareDataFunc) db_model_valcmp_asc
			,GINT_TO_POINTER (col));

	for (r_ind = 0; r_ind < priv->result->nrows; r_ind++)
	{
		row_iter = g_ptr_array_index (priv->data, r_ind);
		new_order[i] = DB_ROW_POSITION (row_iter);
		DB_ROW_POSITION (row_iter) = i++;
	}

	g_signal_emit (obj, db_model_signal[LINES_REORDERED], 0, col, new_order);
}

/**
 * db_model_search:
 * @obj: a #DbModel
 * @col: the field to search in
 * @iter: a #DbIter that will point the first found element
 * @content: the value looked for or %NULL
 * 
 * Looks for a the passed value in the field specified by @col.
 * 
 * Return value: Returns %TRUE if the value is found and %FALSE otherwise
 **/
gboolean db_model_search (DbModel * obj, gint col, DbIter * iter, gpointer content)
{
	gboolean ret;
	GValue value = {0};

	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);
	g_return_val_if_fail (obj->priv->result
		&& 0 <= obj->priv->result->ncols
		&& col < obj->priv->result->ncols , FALSE);

	gvn_value_new_with_content (&value,
		gvn_param_spec_get_gtype (obj->priv->column[col].spec), content);

	ret = db_model_search_value (obj, col, iter, &value);

	g_value_unset (&value);
	return ret;	
}

/**
 * db_model_search_value:
 * @obj: a #DbModel
 * @col: the field to search in
 * @iter: a #DbIter that will point the first found element
 * @value: the value to search for
 * 
 * Looks for a the value pointed to by @value in the field specified by @col.
 * 
 * Return value: Returns %TRUE if the value is found and %FALSE otherwise
 **/
gboolean db_model_search_value
	(DbModel * obj, gint col, DbIter * iter, const GValue * value)
{
	gint i;
	GType type;
	DbRow * row;

	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);
	g_return_val_if_fail (G_IS_VALUE (value), FALSE);
	g_return_val_if_fail (obj->priv->result
		&& 0 <= col 
		&& col < obj->priv->result->ncols, FALSE);

	type = gvn_param_spec_get_gtype (obj->priv->column[col].spec);

	if (gvn_value_is_null (value) || G_VALUE_TYPE (value) == type)
		for (i = 0; i < obj->priv->result->nrows; i++)
		{
			row = g_ptr_array_index (obj->priv->data, i);

			if (gvn_value_compare (DB_ROW_FIELD (row, col), value))
			{
				iter->stamp = obj->priv->stamp;
				iter->data = row;
				return TRUE;
			}
		}

	return FALSE;
}

/**
 * db_model_get_row_operations:
 * @obj: a #DbModel
 * @iter: a #DbIter
 * 
 * Returns #DbModelRowOp flags indicating the operations being applied to the
 * row pointed to by @iter. If no operations are being applied on the row, it
 * returns 0.
 * 
 * Return value: the #DbModelRowOp of the row or 0
 **/
DbModelRowOp db_model_get_row_operations (DbModel * obj, DbIter * iter)
{
	DbOperation * op;

	g_return_val_if_fail (DB_IS_MODEL (obj), 0);
	g_return_val_if_fail (VALID_ITER (iter, obj), 0);

	op = g_hash_table_lookup (obj->priv->row_ops, (DbRow *) iter->data);
	return op ? op->type : 0;
}

/**
 * db_model_has_pending_operations:
 * @obj: a #DbModel
 * 
 * Returns whether there are pending operations in the model.
 * 
 * Return value: #TRUE if the model has pending operations, #FALSE otherwise
 **/
gboolean db_model_has_pending_operations (DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);

	return g_hash_table_size (obj->priv->row_ops) > 0;
}

static SqlObject * db_model_create_where (DbModel * obj,
	Table * table, DbOperation * operation, gboolean for_insert)
{	
	GSList * l;
	DbUpdatedField * u;
	GValue * g_value;
	SqlObject * value;
	DbRow * row = operation->row;
	DbModelPrivate * priv = obj->priv;
	SqlObject * where;
	SqlList * and_operands;
	
	where = sql_operation_new (SQL_OPERATION_TYPE_AND);
	
	and_operands = sql_list_new (SQL_TYPE_EXPR);
	sql_operation_set_operands (SQL_OPERATION (where), and_operands);
	
	TableData * table_data = g_hash_table_lookup (priv->tables, table);
	
	for (l = table_data->pkeys; l; l = l->next)
	{
		SqlObject * equal;
		SqlList * operands;
		gint col = GPOINTER_TO_INT (l->data);
			
		value = NULL;
		
		g_value = &row->value[col];
		
		for (l = operation->updated; l && (u = l->data); l = l->next)
		if (u->column == col)
		{
			g_value = u->value;
			break;
		}

		if (!gvn_value_is_null (g_value))
		{
			value = sql_value_new_with_value (g_value);
		}
		else if (for_insert)
		{
			const GValue * def = gvn_param_spec_get_default (priv->column[col].spec);

			if (def && G_IS_VALUE (def)
			&& G_VALUE_TYPE (def) == SQL_TYPE_FUNCTION)
				value = g_value_get_object (def);
		}

		if (!value)
		{
			g_object_unref (g_object_ref_sink (where));
			return NULL;
		}
		
		equal = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
		sql_list_add (and_operands, equal);
		
		operands = sql_list_new (SQL_TYPE_EXPR);
		sql_list_add (operands, sql_field_new (priv->column[col].name));
		sql_list_add (operands, value);
		sql_operation_set_operands (SQL_OPERATION (equal), operands);
	}
	
	return where;
}

static SqlObject * db_model_create_insert (DbModel * obj,
	Table * table, DbOperation * operation)
{
	gint i;
	DbModelPrivate * priv = obj->priv;
	DbRow * row = operation->row;
	const GValue * value;
	SqlList * targets, * stmts, * sets, * fields, * values, * select_fields;
	SqlObject * target, * insert, * set, * select;
	SqlObject * where = db_model_create_where (obj, table, operation, TRUE);
	
	if (!where)
		return NULL;

	target = sql_table_new (table->name, table->schema);
	
	fields = sql_list_new (SQL_TYPE_FIELD);

	sets = sql_list_new (SQL_TYPE_SET);
	values = sql_list_new (SQL_TYPE_EXPR);
	set = g_object_new (SQL_TYPE_SET
		,"exprs", values
		,NULL
	);
	sql_list_add (sets, set);

	insert = g_object_new (SQL_TYPE_INSERT
		,"table", target
		,"fields", fields
		,"values", sets
		,NULL
	);

	select_fields = sql_list_new (SQL_TYPE_EXPR);
	
	targets = sql_list_new (SQL_TYPE_TARGET);
	sql_list_add (targets, target);
	
	select = g_object_new (SQL_TYPE_SELECT
		,"fields", select_fields
		,"targets", targets
		,"where", where
		,NULL
	);
	
	GHashTableIter iter;
	ColumnDef * column_def;
	Field * field;
	
	g_hash_table_iter_init (&iter, priv->column_defaults);

	while (g_hash_table_iter_next (&iter, (gpointer) &field, (gpointer) &column_def))
	if (!g_strcmp0 (field->target, table->name)
	&& !g_strcmp0 (field->schema, table->schema))
	{
		switch (column_def->type)
		{
			case PARAM_DEF:
			{
				ParamDef * param_def = column_def->def;
				value = gvn_param_get_value (param_def->param);
				break;
			}
			case FIELD_DEF:
				value = NULL; // FIXME
				break;
		}
		
		if (value)
		{
			sql_list_add (fields, sql_field_new (field->name));
			sql_list_add (values, sql_value_new_with_value (value));
		}
	}

	for (i = 0; i < row->len; i++)
	if (table_has_column (table, &priv->column[i]))
	{
		value = &row->value[i];

		if (!gvn_value_is_null (value))
		{
			sql_list_add (fields, sql_field_new (priv->column[i].name));
			sql_list_add (values, sql_value_new_with_value (value));
		}

		sql_list_add (select_fields, sql_field_new (priv->column[i].name));
	}
	
	stmts = sql_list_new (SQL_TYPE_STMT);
	sql_list_add (stmts, insert);
	sql_list_add (stmts, select);
	return g_object_new (SQL_TYPE_MULTI_STMT, "stmts", stmts, NULL);
}

/**
 * db_model_perform_operations:
 * @obj: a #DbModel with a new row, not yet inserted
 * @retry: whether to retry the failed operations (not implemented)
 * 
 * Commits the changes made by db_model_set_value() to a new row, inserted
 * by db_model_insert().
 * Note that if this method is not called after db_model_insert(), all the
 * changes made on the new row will be lost.
 * If the @obj is working in the #DB_MODEL_MODE_ON_DEMAND, non-interactive,
 * mode, this method will perform every actions taken and not yet submitted.
 **/
void db_model_perform_operations (DbModel * obj, gboolean retry)
{
	GList * l;
	DbOperation * op;
	DbModelPrivate * priv;
	DbRow * row;
	SqlObject * where;
	SqlList * stmts;
	DbRequest * request;
	gboolean error = FALSE;

	g_return_if_fail (DB_IS_MODEL (obj));

	if (MODEL_NOT_READY (obj))
	{
		g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN), G_LOG_LEVEL_WARNING,
			"Model not ready");
		return;
	}

	priv = obj->priv;

	if (!priv->operation->length)
		return;
	
	stmts = sql_list_new (SQL_TYPE_STMT);
	g_object_ref_sink (stmts);

	l = g_queue_peek_head_link (priv->operation);

	for (; l && !error; l = l->next)
	{
		SqlObject * stmt = NULL;
	
		op = l->data;
		op->locked = TRUE;
		row = op->row;

		if (op->type & DB_MODEL_ROW_OP_DELETE) // DELETE
		{
			if (!(op->type & DB_MODEL_ROW_OP_INSERT))
			{
				where = db_model_create_where (obj, priv->main_table, op, FALSE);

				if (where)
				{
					SqlList * targets = sql_list_new (SQL_TYPE_TARGET);
					sql_list_add (targets, sql_table_new (
						priv->main_table->name,
						priv->main_table->schema
					));

					stmt = g_object_new (SQL_TYPE_DELETE
						,"where", where
						,"targets", targets
						,NULL
					);
				}
				else
					error = TRUE;
			}
		}
		else if (op->type & DB_MODEL_ROW_OP_INSERT) // INSERT + SELECT
		{
			stmt = db_model_create_insert (obj, priv->main_table, op);
			
			if (!stmt)
				error = TRUE;
		}
		else if (op->type & DB_MODEL_ROW_OP_UPDATE) // UPDATE || INSERT + SELECT
		{
			Table * table;
			GHashTableIter iter;
			SqlList * update_list;
	
			update_list = sql_list_new (SQL_TYPE_STMT);
			g_hash_table_iter_init (&iter, priv->tables);
			
			while (g_hash_table_iter_next (&iter, (gpointer) &table, NULL))
			{
				GSList * l;
				DbUpdatedField * u;
				GSList * fields = NULL;
					
				for (l = op->updated; l && (u = l->data); l = l->next)
				if (table_has_column (table, &priv->column[u->column]))
					fields = g_slist_prepend (fields, u);
					
				if (!fields)
					continue;

				where = db_model_create_where (obj, table, op, FALSE);
				
				if (where)
				{
					SqlList * sets = sql_list_new (SQL_TYPE_UPDATE_SET);
					SqlList * targets = sql_list_new (SQL_TYPE_TARGET);

					sql_list_add (targets, sql_table_new (table->name, table->schema));
					
					for (l = fields; l && (u = l->data); l = l->next)
					{
						GValue * new_value = DB_ROW_FIELD (row, u->column);

						sql_list_add (sets, g_object_new (SQL_TYPE_UPDATE_SET
							,"field", sql_field_new (priv->column[u->column].name)
							,"expr", sql_value_new_with_value (new_value)
							,NULL
						));
					}

					sql_list_add (update_list, g_object_new (SQL_TYPE_UPDATE
						,"where", where
						,"targets", targets
						,"sets", sets
						,NULL
					));
				}
				else
				{
					SqlObject * insert = db_model_create_insert (obj, table, op);
			
					if (insert)
						sql_list_add (update_list, insert);
				}
					
				g_slist_free (fields);
			}

			stmt = g_object_new (SQL_TYPE_MULTI_STMT, "stmts", update_list, NULL);
		}
		
		if (stmt)
			sql_list_add (stmts, stmt);
	}

	if (sql_list_length (stmts) > 0 && !error)
	{
		SqlObject * multi = g_object_new (SQL_TYPE_MULTI_STMT, "stmts", stmts, NULL);
		GQueue * ops = g_queue_new ();
		
		while ((op = g_queue_pop_head (priv->operation)))
			g_queue_push_tail (ops, op);
	
		DbModelRequest * data = g_new (DbModelRequest, 1);
		data->obj = g_object_ref (obj);
		data->operations = ops;
		data->stmts = g_object_ref_sink (stmts);

		request = db_conn_query_with_stmt_async (priv->conn
			,SQL_STMT (multi)
			,NULL
			,(DbRequestDoneCallback) db_model_on_operations_done
			,data
			,(GDestroyNotify) db_model_request_free
		);
		db_model_add_pending_request (obj, request);
	}
	else
		g_warning ("DbModel: Error performing operations");
	
	g_object_unref (stmts);
}

/**
 * db_model_refresh:
 * @obj: a #DbModel
 * 
 * Executes the SELECT query and fills @obj with the data returned by it.
 **/
void db_model_refresh (DbModel * obj)
{
	DbModelPrivate * priv;
	gboolean is_ready = FALSE;

	g_return_if_fail (DB_IS_MODEL (obj));
	
	priv = obj->priv;
	db_model_clear (obj);
	
	if (priv->conn && priv->stmt)
	{
		SqlBatch * tmp_batch = sql_batch_new ();
		sql_object_get_holders (SQL_OBJECT (priv->stmt), tmp_batch);
		sql_batch_merge (tmp_batch, priv->batch);
		sql_batch_merge (tmp_batch, priv->internal_batch);

		if (sql_batch_is_ready (tmp_batch))
		{
			is_ready = TRUE;
			db_model_set_status (obj, DB_MODEL_STATUS_LOADING);
		
			priv->request = db_conn_query_with_stmt_async (priv->conn
				,priv->stmt
				,tmp_batch
				,(DbRequestDoneCallback) db_model_on_data_ready
				,g_object_ref (obj)
				,(GDestroyNotify) g_object_unref
			);
		
			g_object_unref (g_object_ref_sink (tmp_batch));
		}
	}

	if (!is_ready)
		db_model_set_status (obj, DB_MODEL_STATUS_CLEAN);
}

/**
 * db_model_get_nrows:
 * @obj: a #DbModel
 * 
 * Returns the current number of rows on #obj.
 * 
 * Return value: the number of rows
 **/
gint db_model_get_nrows (DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), 0);

	if (obj->priv->result)
		return obj->priv->result->nrows;
	else
		return 0;
}

/**
 * db_model_iter_is_valid:
 * @iter: a #DbIter
 * @model: a #DbModel
 * 
 * Checks if @iter is a valid #DbIter pointing inside @model.
 **/
gboolean db_model_iter_is_valid (DbIter * iter, DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);

	return (iter && iter->data && obj->priv->stamp == iter->stamp);
}

// GtkTreeModel implementation methods.

/**
 * db_model_get_ncols:
 * @obj: a #DbModel
 * 
 * Returns the number of columns supported by @obj.
 * 
 * Return value: the number of columns
 **/
gint db_model_get_ncols (DbModel * obj)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), -1);

	if (obj->priv->result)
		return obj->priv->result->ncols;
	else
		return 0;
}

/**
 * db_model_get_column_type:
 * @obj: a #DbModel
 * @index: the number of the column
 * 
 * Retrieves the type of the column specified by @index.
 * 
 * Return value: the type of the column
 **/
GType db_model_get_column_type (DbModel * obj, gint index)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), G_TYPE_INVALID);
	g_return_val_if_fail (obj->priv->result
		&& 0 <= index && index < obj->priv->result->ncols, G_TYPE_INVALID);

	if (obj->priv->column)
		if (obj->priv->column[index].spec)
			return gvn_param_spec_get_gtype (obj->priv->column[index].spec);
	return G_TYPE_INVALID;
}

/**
 * db_model_get_path:
 * @obj: a #DbModel
 * @iter: a #DbIter
 * 
 * Returns the number of the row pointed to by @iter.
 * 
 * Return value: the number of the row pointed to by @iter
 **/
gint db_model_get_path (DbModel * obj, DbIter * iter)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), 0);
	if (MODEL_NOT_READY (obj)) return 0;
	g_return_val_if_fail (VALID_ITER (iter, obj), 0);

	return DB_ROW_POSITION (iter->data);
}

/**
 * db_model_get_iter:
 * @obj: a #DbModel
 * @iter: (out): an unitialized #DbIter
 * @path: the number of the row being accessed
 * 
 * Sets @iter pointing to the row of @obj specified by @path.
 * 
 * Return value: %TRUE if the position is found, %FALSE otherwise
 **/
gboolean db_model_get_iter (DbModel * obj, DbIter * iter, gint path)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);

	if ((0 > path
	|| (obj->priv->result && path >= obj->priv->result->nrows))
	|| MODEL_NOT_READY (obj))
		return FALSE;

	iter->stamp = obj->priv->stamp;
	iter->data = g_ptr_array_index (obj->priv->data, (guint) path);

	if (iter->data)
		return TRUE;
	return FALSE;
}

/**
 * db_model_get_iter_first:
 * @obj: a #DbModel
 * @iter: (out): an unitialized #DbIter
 * 
 * Sets @iter pointing to the first row of @obj.
 * 
 * Return value: %TRUE if @iter is set right
 **/
gboolean db_model_get_iter_first (DbModel * obj, DbIter * iter)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);
	if (MODEL_NOT_READY(obj)
	|| !obj->priv->result || !obj->priv->result->nrows)
		return FALSE;

	if (obj->priv->data)
	{
		iter->stamp = obj->priv->stamp;
		iter->data = g_ptr_array_index (obj->priv->data, 0);
	}
	else
		return FALSE;

	return TRUE;
}

/**
 * db_model_iter_prev:
 * @obj: a #DbModel
 * @iter: (inout): a valid #DbIter
 * 
 * Sets @iter pointing to the previous row of @obj.
 * 
 * Return value: %TRUE if the iter has been changed to the previous row
 **/
gboolean db_model_iter_prev (DbModel * obj, DbIter * iter)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);
	if (MODEL_NOT_READY(obj) 
	|| !obj->priv->result || !obj->priv->result->nrows)
		return FALSE;

	g_return_val_if_fail (VALID_ITER (iter, obj), FALSE);

	if ((iter->data = g_ptr_array_index
		(obj->priv->data, (guint) DB_ROW_POSITION (iter->data) - 1)))
		return TRUE;
	return FALSE;
}

/**
 * db_model_iter_next:
 * @obj: a #DbModel
 * @iter: (inout): a valid #DbIter
 * 
 * Sets @iter pointing to the next row of @obj.
 * 
 * Return value: %TRUE if the iter has been changed to the next row
 **/
gboolean db_model_iter_next (DbModel * obj, DbIter * iter)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);
	if (MODEL_NOT_READY(obj)
	|| !obj->priv->result || !obj->priv->result->nrows)
		return FALSE;

	g_return_val_if_fail (VALID_ITER (iter, obj), FALSE);

	gint pos = DB_ROW_POSITION (iter->data);

	if (pos < obj->priv->result->nrows-1)
	{
		iter->data =  g_ptr_array_index (obj->priv->data, (guint) pos + 1);
		return TRUE;
	}

	iter->stamp = (iter->stamp)? 0 : 1;
	iter->data = NULL;
	return FALSE;
}

// GtkTreeSortable implementation methods

/**
 * db_model_get_sort_column_id:
 * @obj: a #DbModel
 * @sort_column_id: (out): an integer
 * @order: (out): a @DbSortType
 * 
 * Fills in @sort_column_id and @order with the current sort
 * column and the order. See #GtkTreeSortable.
 * 
 * Return value: %TRUE if the sort column is not one of the special sort column
 * ids
 **/
gboolean db_model_get_sort_column_id (DbModel * obj, gint * sort_column_id,
										DbSortType * order)
{
	g_return_val_if_fail (DB_IS_MODEL (obj), FALSE);

	if (sort_column_id)
		* sort_column_id = obj->priv->sort_column_id;

	if (order)
		* order = obj->priv->order;

	if (obj->priv->sort_column_id == DB_MODEL_DEFAULT_SORT_COLUMN_ID
		|| obj->priv->sort_column_id == DB_MODEL_UNSORTED_SORT_COLUMN_ID)
		return FALSE;
	else
		return TRUE;
}

/**
 * db_model_set_sort_column_id:
 * @obj: a #DbModel
 * @sort_column_id: the column to sort by
 * @order: the order in which the sort will be done
 * 
 * Sets @sort_column_id to be the current sort column. @obj will resort itself
 * to reflect this change. See #GtkTreeSortable.
 **/
void db_model_set_sort_column_id (DbModel * obj, gint sort_column_id,
									DbSortType order)
{
	g_return_if_fail (DB_IS_MODEL (obj));
	db_model_order_by (obj, sort_column_id, order);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class

typedef enum
{
	 PROP_CONN = 1
	,PROP_STMT
	,PROP_SQL
	,PROP_USE_FILE
	,PROP_MAIN_TABLE
	,PROP_UPDATE_FLAGS
	,PROP_RESULT_POS
}
DbModelProp;

static void db_model_set_property (DbModel * obj, guint property_id,
	const GValue * value, GParamSpec * pspec)
{
	switch (property_id)
	{
		case PROP_CONN:
			db_model_set_conn (obj, g_value_get_object (value));
			break;
		case PROP_STMT:
			db_model_set_stmt (obj, g_value_get_object (value));
			break;
		case PROP_SQL:
			db_model_set_sql (obj, g_value_get_string (value));
			break;
		case PROP_USE_FILE:
			obj->priv->use_file = g_value_get_boolean (value);
			break;
		case PROP_MAIN_TABLE:
			db_model_request_main_table (obj, g_value_get_string (value));
			break;
		case PROP_UPDATE_FLAGS:
			db_model_request_update_flags (obj, g_value_get_flags (value));
			break;
		case PROP_RESULT_POS:
			obj->priv->result_pos = g_value_get_uint (value);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
	}
}

static void db_model_get_property (DbModel * obj, guint property_id,
	GValue * value, GParamSpec * pspec)
{
	switch (property_id)
	{
		case PROP_CONN:
			g_value_set_object (value, obj->priv->conn);
			break;
		case PROP_STMT:
			g_value_set_object (value, obj->priv->stmt);
			break;
		case PROP_SQL:
			g_value_set_string (value, obj->priv->sql);
			break;
		case PROP_USE_FILE:
			g_value_set_boolean (value, obj->priv->use_file);
			break;
		case PROP_MAIN_TABLE:
			g_value_set_string (value, db_model_get_main_table (obj));
			break;
		case PROP_UPDATE_FLAGS:
			g_value_set_flags (value, obj->priv->update_flags);
			break;
		case PROP_RESULT_POS:
			g_value_set_uint (value, obj->priv->result_pos);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
	}
}

static void db_model_init (DbModel *obj)
{
	DbModelPrivate * priv = obj->priv =
		G_TYPE_INSTANCE_GET_PRIVATE (obj, DB_TYPE_MODEL, DbModelPrivate);

	priv->conn = NULL;
	priv->batch = NULL;
	priv->stmt = NULL;
	priv->use_file = FALSE;
	priv->sql = NULL;
	priv->request = NULL;
	priv->status = DB_MODEL_STATUS_CLEAN;
	priv->mode = DB_MODEL_MODE_ON_CHANGE;
	priv->result = NULL;
	priv->result_pos = 0;
	priv->data = NULL;
	priv->column = NULL;
	priv->column_index = g_hash_table_new_full (
		g_str_hash,
		g_str_equal,
		(GDestroyNotify) g_free,
		NULL
	);

	priv->stamp = g_random_int ();

	priv->fresh = TRUE;
	priv->sort_column_id = DB_MODEL_UNSORTED_SORT_COLUMN_ID;
	priv->default_sort_data = NULL;
	priv->default_sort_func = NULL;
	priv->default_sort_destroy = NULL;

	priv->link_op = NULL;
	priv->internal_batch = NULL;
	
	priv->updatable_data_allocated = FALSE;
	priv->update_flags = 0;
	priv->user_update_flags = DB_MODEL_ALL;
	priv->main_table = NULL;
	priv->user_main_table = NULL;
	priv->column_defaults = NULL;
	priv->operation = NULL;
	priv->row_ops = NULL;
	priv->join = NULL;
	priv->pending_request = NULL;
	priv->tables = NULL;
}

static void db_model_finalize (DbModel * obj)
{
	DbModelPrivate * priv = obj->priv;
	GObjectClass * parent = g_type_class_peek_parent (DB_MODEL_GET_CLASS (obj));

	db_model_clear (obj);

	g_clear_object (&priv->conn);
	g_clear_object (&priv->stmt);
	g_free (priv->sql);
	g_hash_table_destroy (priv->column_index);
	db_model_set_batch (obj, NULL);

	g_clear_object (&priv->link_op);
	g_clear_object (&priv->internal_batch);

	if (priv->updatable_data_allocated)
	{
		g_free (priv->user_main_table);
		g_hash_table_destroy (priv->column_defaults);
		g_queue_free (priv->operation);
		g_hash_table_destroy (priv->row_ops);
		g_slist_free_full (priv->join, (GDestroyNotify) db_join_free);
		db_model_free_stmt_data (obj);
	}

	parent->finalize (G_OBJECT (obj));
}

static void db_model_class_init (DbModelClass *k)
{
	GObjectClass * klass = G_OBJECT_CLASS (k);
	klass->set_property = (GObjectSetPropertyFunc) db_model_set_property;
	klass->get_property = (GObjectGetPropertyFunc) db_model_get_property;
	klass->finalize = (GObjectFinalizeFunc) db_model_finalize;

	g_type_class_add_private (klass, sizeof (DbModelPrivate));

	/**
	 * DbModel::status-changed:
	 * @model: the object which received the signal
	 * @status: the current status of @model
	 * 
	 * This signal is emitted every time the status of @model changes.
	 */
	db_model_signal[STATUS_CHANGED] = g_signal_new ("status-changed"
		,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL
		,g_cclosure_marshal_VOID__INT
		,G_TYPE_NONE, 1, G_TYPE_INT
	);

	/**
	 * DbModel::line-inserted:
	 * @model: the object which received the signal
	 * @iter: a #DbIter
	 * 
	 * This signal is emitted when a new row is inserted into @model. The inserted
	 * row is pointed to by @iter.
	 */
	db_model_signal[LINE_INSERTED] = g_signal_new ("line-inserted"
		,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL
		,g_cclosure_marshal_VOID__BOXED
		,G_TYPE_NONE, 1, DB_TYPE_ITER
	);

	/**
	 * DbModel::line-deleted:
	 * @model: the object which received the signal
	 * @position: the position of the deleted line
	 * 
	 * Every time a row of @model is deleted, this signal is emitted.
	 */
	db_model_signal[LINE_DELETED] = g_signal_new_class_handler ("line-deleted"
		,DB_TYPE_MODEL, G_SIGNAL_RUN_LAST, (GCallback) db_model_on_line_deleted
		,NULL, NULL, g_cclosure_marshal_VOID__INT
		,G_TYPE_NONE, 1, G_TYPE_INT
	);

	/**
	 * DbModel::line-toggled:
	 * @model: the instance that received the signal
	 * @iter: a #DbIter
	 * 
	 * Emitted to tell that the line is going to be deleted,
	 * but the operation is still.
	 */
	db_model_signal[LINE_TOGGLED] = g_signal_new ("line-toggled"
		,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0 ,NULL, NULL
		,g_cclosure_marshal_VOID__BOXED
		,G_TYPE_NONE, 1, DB_TYPE_ITER
	);

	/**
	 * DbModel::line-updated:
	 * @model: the object which received the signal
	 * @iter: a #DbIter
	 * 
	 * This signal is emitted when any value in a row of @model is set.
	 */
	db_model_signal[LINE_UPDATED] = g_signal_new_class_handler ("line-updated"
		,DB_TYPE_MODEL, G_SIGNAL_RUN_LAST, (GCallback) db_model_on_line_updated
		,NULL, NULL, g_cclosure_marshal_VOID__BOXED
		,G_TYPE_NONE, 1, DB_TYPE_ITER
	);

	/**
	 * DbModel::lines-reordered:
	 * @model: the object which received the signal
	 * @col: the sort column
	 * @new_order: (array) (element-type gint): an array mapped with the new
	 * positions over the old ones
	 * 
	 * This signal is emitted by @model every time its rows are reordered.
	 * 
	 * The @new_order array is an array of which the positions indexed after the
	 * old order of the elements of @model, contain the new position of these
	 * elements in the reordered @model.
	 */
	db_model_signal[LINES_REORDERED] = g_signal_new ("lines-reordered"
		,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL
		,g_cclosure_marshal_VOID__UINT_POINTER
		,G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_POINTER
	);

	/**
	 * DbModel::sort-changed:
	 * @model: the object which received the signal
	 * 
	 * This signal is emitted when a new column is selected as sort criteria for
	 * @model.
	 */
	db_model_signal[SORT_CHANGED] = g_signal_new ("sort-changed"
		,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL
		,g_cclosure_marshal_VOID__VOID
		,G_TYPE_NONE, 0
	);

	/**
	 * DbModel::operations-done:
	 * @model: the object which received the signal
	 * @success: whether the operation has failed or succeded
	 * 
	 * When an operation on the model is performed on the DB, this signal
	 * is emitted.
	 */
	db_model_signal[OPERATIONS_DONE] = g_signal_new ("operations-done"
		,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL
		,g_cclosure_marshal_VOID__VOID
		,G_TYPE_NONE, 0
	);

	g_object_class_install_property (klass, PROP_CONN,
		g_param_spec_object ("conn"
							,_("Connection")
							,_("The DbConn that manages the connection to the database")
							,DB_TYPE_CONN
							,G_PARAM_READWRITE
	));

	g_object_class_install_property (klass, PROP_STMT,
		g_param_spec_object ("stmt"
							,_("Statement")
							,_("The statement which retrieves the data")
							,SQL_TYPE_STMT
							,G_PARAM_READWRITE
	));

	g_object_class_install_property (klass, PROP_SQL,
		g_param_spec_string ("sql"
							,_("SQL")
							,_("Depending on the \"use-file\" property this will "
								"be the path to a file with queries for the "
								"model or a SQL string")
							,NULL
							,G_PARAM_READWRITE
	));

	g_object_class_install_property (klass, PROP_USE_FILE,
		g_param_spec_boolean ("use-file"
							,_("Use file")
							,_("If this is set to TRUE, the \"sql\" property will "
								"hold the name of a file containing a query, if "
								"set to FALSE, \"sql\" is used as an SQL string")
							,FALSE
							,G_PARAM_READWRITE
	));

	g_object_class_install_property (klass, PROP_MAIN_TABLE,
		g_param_spec_string ("main-table"
							,_("Main Table")
							,_("The main table of the model")
							,NULL
							,G_PARAM_READWRITE
	));

	g_object_class_install_property (klass, PROP_UPDATE_FLAGS,
		g_param_spec_flags ("update-flags"
							,_("Update flags")
							,_("The flags that indicate how a model can be modified")
							,DB_TYPE_MODEL_UPDATE_FLAGS
							,DB_MODEL_ALL
							,G_PARAM_READWRITE
	));
	
	g_object_class_install_property (klass, PROP_RESULT_POS,
		g_param_spec_uint ("result-pos"
							,_("Result position")
							,_("The position where the query that will fill the "
								"model will be placed in a multi-query")
							,0
							,G_MAXUINT32
							,0
							,G_PARAM_READWRITE
	));
}

GType db_model_update_flags_get_type ()
{
	static GType type = 0;

	if (type == 0)
	{
		static const GFlagsValue values[] =
		{
			{DB_MODEL_INSERT, "DB_MODEL_INSERT", "insert"},
			{DB_MODEL_DELETE, "DB_MODEL_DELETE", "delete"},
			{DB_MODEL_UPDATE, "DB_MODEL_UPDATE", "update"},
			{0, NULL, NULL}
		};

		type = g_flags_register_static
			(g_intern_static_string ("DbModelUpdateFlags"), values);
	}

	return type;
}