/*
* 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 .
*/
#include
#include "db-model.h"
#include "db-row.h"
#include "db-model-private.c"
#define MODEL_NOT_READY(self) (self->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: #DbConn, #DbModelHolder
*
* 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
* a #DbIterator or another #DbModelHolder instead.
**/
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;
SqlBatch * batch;
GPtrArray * data;
DbColumn * column;
GHashTable * column_index;
DbResult * result;
guint result_pos;
DbRequest * request;
DbModelStatus status;
gint stamp;
DbRow * null_row;
GHashTable * defaults;
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;
gboolean updatable_data_allocated;
DbModelUpdateFlags user_update_flags;
DbModelUpdateFlags update_flags;
TableInfo * main_table;
DbModelMode mode;
GHashTable * tables;
gint updated_col;
GQueue * operation;
GHashTable * row_ops;
GValue * updated_value;
GSList * pending_request;
TableInfo ** column_table;
gboolean partial_delete;
GSList * join;
};
// Helper structures and methods
// Structures
typedef struct
{
DbModel * self;
DbIter * iter;
gint col;
}
JoinData;
typedef struct
{
gchar * schema;
gchar * table;
GPtrArray * name;
gboolean main;
}
DbModelField;
typedef struct
{
DbModelField * left;
DbModelField * right;
Field * master;
Field * slave;
}
DbJoin;
typedef struct
{
gint col;
gint order;
DbRow * null_row;
}
SortInfo;
enum
{
DB_MODEL_UNSORTED_SORT_COLUMN_ID = -2,
DB_MODEL_DEFAULT_SORT_COLUMN_ID
};
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Constructors
/**
* db_model_new:
* @conn:(allow-none): a #DbConn
* @stmt:(allow-none): 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:(allow-none): 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:(allow-none): 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 * self);
static void db_model_set_status (DbModel * self
,DbModelStatus status);
static void db_model_clear (DbModel * self);
static void db_model_free_operation (DbModel * self
,DbOperation * op);
static void db_model_manage_join (DbModel * self
,DbIter * iter
,gint col);
// 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 * self, DbIter * iter)
{
GValue * updated_value = self->priv->updated_value;
gint col;
if (!updated_value || !G_IS_VALUE (updated_value)) return;
col = self->priv->updated_col;
gvn_value_ccopy (updated_value, DB_ROW_FIELD (iter->data, col));
db_model_manage_join (self, iter, col);
g_value_unset (updated_value);
g_free (updated_value);
self->priv->updated_value = NULL;
}
static void db_model_on_line_deleted (DbModel * self, gint position)
{
gint r_ind;
for (r_ind = position + 1; r_ind < self->priv->result->nrows; r_ind++)
DB_ROW_POSITION (g_ptr_array_index (self->priv->data, (guint) r_ind))--;
g_ptr_array_remove_index (self->priv->data, (guint) position);
self->priv->result->nrows--;
}
// External signal handlers
static void db_model_calculate_update_flags (DbModel * self)
{
gint i;
Table table;
TableInfo * tinfo;
DbModelPrivate * priv = self->priv;
// Allocates aditional memory when the model is updatable
if (priv->user_update_flags && !priv->updatable_data_allocated)
{
priv->updatable_data_allocated = TRUE;
priv->operation = g_queue_new ();
priv->row_ops = g_hash_table_new (g_direct_hash, g_direct_equal);
priv->tables = g_hash_table_new_full (
(GHashFunc) table_hash
,(GEqualFunc) table_equal
,(GDestroyNotify) table_free
,(GDestroyNotify) table_info_free
);
}
if (priv->user_update_flags && priv->fresh && priv->result)
{
GArray * cols;
g_free (priv->column_table);
g_hash_table_remove_all (priv->tables);
priv->column_table = g_new (TableInfo *, priv->result->ncols);
for (i = 0; i < priv->result->ncols; i++)
{
DbColumn col = priv->column[i];
if (!col.table)
{
priv->column_table[i] = NULL;
continue;
}
table.name = col.table_alias;
table.schema = col.schema;
tinfo = g_hash_table_lookup (priv->tables, &table);
if (!tinfo)
{
tinfo = g_new (TableInfo, 1);
tinfo->name = g_strdup (col.table);
tinfo->schema = g_strdup (col.schema);
tinfo->alias = g_strdup (col.table_alias);
tinfo->columns = g_hash_table_new_full (
g_str_hash
,g_str_equal
,NULL
,(GDestroyNotify) g_array_free
);
tinfo->pkeys = NULL;
g_hash_table_insert (priv->tables, table_copy (&table), tinfo);
}
priv->column_table[i] = tinfo;
if (col.info & DB_COLUMN_PRI_KEY)
tinfo->pkeys = g_slist_prepend (tinfo->pkeys, GINT_TO_POINTER (i));
cols = g_hash_table_lookup (tinfo->columns, col.name);
if (!cols)
{
cols = g_array_sized_new (TRUE, FALSE, sizeof (gint), 1);
g_hash_table_insert (tinfo->columns, g_strdup (col.name), cols);
}
g_array_append_val (cols, i);
}
}
// Searchs for the main table
if (priv->user_update_flags && priv->result)
for (i = 0; i < priv->result->ncols; i++)
if (priv->column[i].info & DB_COLUMN_PRI_KEY)
{
priv->main_table = priv->column_table[i];
break;
}
// Sets the updatable flags
if (priv->main_table)
priv->update_flags = DB_MODEL_ALL & priv->user_update_flags;
else
priv->update_flags = 0;
// Sets the params editable
if (priv->result)
for (i = 0; i < priv->result->ncols; i++)
{
gboolean editable = priv->main_table
&& priv->column_table[i]
&& priv->column_table[i]->pkeys
&& priv->update_flags & DB_MODEL_UPDATE;
gvn_param_spec_set_editable (priv->column[i].spec, editable);
}
}
static void db_model_on_data_ready (DbRequest * request, DbModel * self)
{
gint i;
DbResult * r;
DbModelPrivate * priv = self->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)))
{
priv->column = r->column;
priv->data = r->data;
if (!r->data && !r->column)
{
db_result_free (r);
db_model_set_status (self, DB_MODEL_STATUS_CLEAN);
}
else
{
priv->result = r;
if (priv->fresh)
{
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));
}
else
db_model_set_sort_column_id (self,
priv->old_sort_column_id, priv->old_order);
db_model_calculate_update_flags (self);
priv->fresh = FALSE;
db_model_set_status (self, DB_MODEL_STATUS_READY);
}
}
else
db_model_set_status (self, DB_MODEL_STATUS_ERROR);
g_clear_object (&priv->request);
}
static void db_model_set_table_fields_null (DbModel * self, TableInfo * tinfo, DbIter * iter)
{
gint i;
DbModelPrivate * priv = self->priv;
for (i = 0; i < priv->result->ncols; i++)
if (priv->column_table[i] == tinfo)
{
priv->updated_col = i;
priv->updated_value =
g_value_init (g_new0 (GValue, 1), GVN_TYPE_NULL);
g_signal_emit (self, db_model_signal[LINE_UPDATED], 0, &iter);
}
}
static void db_model_process_select (DbModel * self, DbRequest * request, DbRow * row, GError * err)
{
gint i, j;
DbModelPrivate * priv = self->priv;
DbResult * result;
DbRow * req_row;
DbIter iter;
Table table;
TableInfo * tinfo;
result = db_request_fetch_result (request, &err);
if (!(result && result->data && result->ncols > 0))
goto exit;
iter.data = row;
iter.stamp = priv->stamp;
req_row = g_ptr_array_index (result->data, 0);
table.name = result->column[0].table_alias;
table.schema = result->column[0].schema;
tinfo = g_hash_table_lookup (priv->tables, &table);
if (!tinfo)
goto exit;
if (result->nrows > 0)
for (i = 0; i < result->ncols; i++)
{
GArray * cols = g_hash_table_lookup (tinfo->columns, result->column[i].name);
for (j = 0; j < cols->len; j++)
{
GValue * new_value = &req_row->value[i];
if (gvn_value_compare (new_value, DB_ROW_FIELD (row, i)))
continue;
priv->updated_value = g_new0 (GValue, 1);
g_value_init (priv->updated_value, G_VALUE_TYPE (new_value));
g_value_copy (new_value, priv->updated_value);
priv->updated_col = g_array_index (cols, gint, j);
g_signal_emit (self, db_model_signal[LINE_UPDATED], 0, &iter);
}
}
else
db_model_set_table_fields_null (self, tinfo, &iter);
exit:
db_result_free (result);
}
static void db_model_on_operations_done (DbRequest * request, DbModelRequest * data)
{
GList * l;
guint i = 0;
DbIter iter;
DbOperation * op;
GError * err = NULL;
DbModel * self = data->self;
DbModelPrivate * priv = self->priv;
for (l = data->operations->head; l; l = l->next)
{
op = l->data;
iter.stamp = priv->stamp;
iter.data = op->row;
if (op->type & DB_MODEL_ROW_OP_INSERT
&& op->type & DB_MODEL_ROW_OP_DELETE) // INSERT + DELETE
{
g_signal_emit (self,
db_model_signal[LINE_DELETED], 0, DB_ROW_POSITION (op->row));
continue;
}
if (!request || 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
{
if (!priv->partial_delete)
g_signal_emit (self,
db_model_signal[LINE_DELETED], 0, DB_ROW_POSITION (op->row));
else
db_model_set_table_fields_null (self, priv->main_table, &iter);
}
else if (op->type & DB_MODEL_ROW_OP_INSERT) // INSERT + SELECT
{
db_model_process_select (self, request, op->row, err);
}
else if (op->type & DB_MODEL_ROW_OP_UPDATE) // UPDATE || INSERT + SELECT
{
guint j;
SqlList * list;
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_select (self, request, op->row, err);
}
g_signal_emit (self, db_model_signal[LINE_UPDATED], 0, &iter);
}
i++;
}
if (request)
{
priv->pending_request =
g_slist_remove (priv->pending_request, request);
g_object_unref (request);
}
if (!err)
{
while ((op = g_queue_pop_head (data->operations)))
db_model_free_operation (self, op);
g_signal_emit (self, db_model_signal[OPERATIONS_DONE], 0);
}
}
static void db_model_on_stmt_changed (SqlStmt * stmt, DbModel * self)
{
self->priv->fresh = TRUE;
db_model_refresh (self);
}
static void db_model_execute_stmt (DbModel * self)
{
DbModelPrivate * priv = self->priv;
if (priv->conn && (priv->stmt || priv->sql))
{
if (priv->sql)
{
SqlObject * string;
if (priv->use_file)
string = db_conn_create_stmt_from_file (priv->conn, priv->sql);
else
string = sql_string_new (priv->sql);
if (string)
self->priv->stmt = g_object_ref_sink (string);
else
return;
}
if (priv->stmt)
g_signal_connect (priv->stmt, "changed",
G_CALLBACK (db_model_on_stmt_changed), self);
self->priv->fresh = TRUE;
db_model_refresh (self);
}
}
static void db_model_on_batch_changed (SqlBatch * batch, DbModel * self)
{
db_model_refresh (self);
}
static void db_model_on_join_query_done (DbRequest * request, JoinData * join_data)
{
// db_model_process_select (self, request, row, err);
}
// Private helper methods and functions
static void join_data_free (JoinData * join_data)
{
g_object_unref (join_data->self);
db_iter_free (join_data->iter);
g_free (join_data);
}
static void db_model_free_operation (DbModel * self, DbOperation * op)
{
if (op->updated)
g_slist_free_full (op->updated, (GDestroyNotify) db_updated_field_free);
g_hash_table_remove (self->priv->row_ops, op->row);
g_free (op);
}
static void db_model_clean_operations (DbModel * self)
{
DbOperation * op;
DbModelPrivate * priv = self->priv;
if (priv->operation)
while ((op = g_queue_pop_head (priv->operation)))
db_model_free_operation (self, 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->self->priv->operation, op);
}
g_queue_free (req->operations);
g_object_unref (req->self);
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);
u->column = col;
u->value = g_value_init (g_new0 (GValue, 1)
,G_VALUE_TYPE (DB_ROW_FIELD (op->row, col)));
g_value_copy (DB_ROW_FIELD (op->row, col), u->value);
op->type |= DB_MODEL_ROW_OP_UPDATE;
op->updated = g_slist_prepend (op->updated, u);
}
static gboolean db_model_set_row_operation (DbModel * self,
DbRow * row, DbModelRowOp type, gint col)
{
DbOperation * op = g_hash_table_lookup (self->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);
g_hash_table_insert (self->priv->row_ops, row, new_op);
g_queue_push_tail (self->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);
}
else
return FALSE;
return TRUE;
}
void db_model_reverse_operations (DbModel * self)
{
DbModelPrivate * priv = self->priv;
DbOperation * op;
g_return_if_fail (DB_IS_MODEL (self));
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 (self,
db_model_signal[LINE_DELETED], 0, DB_ROW_POSITION (op->row));
}
else
g_signal_emit (self, db_model_signal[LINE_TOGGLED], 0, &iter);
}
else if (op->type & DB_MODEL_ROW_OP_INSERT)
{
g_signal_emit (self,
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));
g_value_copy (u->value, priv->updated_value);
priv->updated_col = u->column;
g_signal_emit (self, db_model_signal[LINE_UPDATED], 0, &iter);
}
}
db_model_free_operation (self, op);
}
}
// Utility functions
/*
* 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);
gint res = g_utf8_collate (a_str, b_str);
g_free (a_str);
g_free (b_str);
return res;
}
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 inline gint db_model_valcmp (gpointer * a, gpointer * b, SortInfo * info)
{
gint order;
DbRow * first = *a;
DbRow * second = *b;
if (first == info->null_row)
order = -1;
else if (second == info->null_row)
order = 1;
else
{
gint dir = info->order == DB_SORT_ASCENDING ? 1 : -1;
order = dir * db_model_value_compare0 (&first->value[info->col]
,&second->value[info->col]);
}
return order;
}
static void db_model_set_status (DbModel * self, DbModelStatus status)
{
self->priv->status = status;
g_signal_emit (self, db_model_signal[STATUS_CHANGED], 0, status);
}
static void db_model_cancel_pending_requests (DbModel * self)
{
GSList * n;
for (n = self->priv->pending_request; n; n = n->next)
db_request_cancel (n->data);
}
static void db_model_add_pending_request (DbModel * self, DbRequest * request)
{
self->priv->pending_request = g_slist_prepend (self->priv->pending_request,
request);
}
static void db_model_clear (DbModel * self)
{
DbModelPrivate * priv = self->priv;
if (priv->request)
{
db_request_cancel (priv->request);
priv->request = NULL;
}
else if (priv->result)
{
db_model_clean_operations (self);
db_model_cancel_pending_requests (self);
db_result_free (priv->result);
priv->result = NULL;
priv->column = NULL;
priv->data = NULL;
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 ();
priv->main_table = NULL;
priv->update_flags = 0;
}
}
// Join related functions
static void db_model_manage_join (DbModel * self, DbIter * iter, gint col)
{
DbModelPrivate * priv = self->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));
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->self = g_object_ref (self);
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 (self, request);
}
g_object_unref (stmts);
}
}
/*
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 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 * self, SqlJoin * join,
SqlField * l_field, SqlField * r_field)
{
gint i, col = -1;
gchar * dst = NULL;
DbModelPrivate * priv = self->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 (self, dst, col);
}
static void db_model_set_join_fields (DbModel * self, 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 gboolean db_model_analyse_join_op (DbModel * self, SqlOperation * op)
{
gint i;
gint noperands;
SqlList * operands;
SqlOperationType operator = sql_operation_get_operator (op);
noperands = sql_list_length (operands);
g_object_get (op, "operands", &operands, NULL);
if (operator == SQL_OPERATION_TYPE_EQUAL && noperands == 2)
{
gpointer field1 = sql_list_get (operands, 0);
gpointer field2 = sql_list_get (operands, 1);
if (!SQL_IS_FIELD (field1) || !SQL_IS_FIELD (field2)
|| (g_strcmp0 (sql_field_get_target (field1), sql_field_get_target (field2))
&& g_strcmp0 (sql_field_get_schema (field1), sql_field_get_schema (field2))))
return FALSE;
}
else if (operator == SQL_OPERATION_TYPE_AND)
{
for (i = 0; i < noperands; i++)
if (!db_model_analyse_join_op (self, sql_list_get (operands, i)))
return FALSE;
return TRUE;
}
return FALSE;
}
static void db_model_analyse_join (DbModel * self, SqlTarget * target)
{
SqlJoin * join;
SqlTarget * left;
SqlTarget * right;
SqlOperation * on;
if (!SQL_IS_JOIN (target))
return;
join = SQL_JOIN (target);
g_object_get (join
,"target-left", &left
,"target-right", &right
,"condition", &on
,NULL
);
if (SQL_IS_TABLE (left) || SQL_IS_TABLE (right))
db_model_analyse_join_op (self, on);
switch ((gint) sql_join_get_join_type (join))
{
case SQL_JOIN_TYPE_INNER:
break;
case SQL_JOIN_TYPE_LEFT:
break;
case SQL_JOIN_TYPE_RIGHT:
break;
}
db_model_analyse_join (self, left);
db_model_analyse_join (self, right);
}
static void db_model_load_join (DbModel * self)
{
gchar * sql;
SqlObject * stmt;
SqlObject * select;
DbModelPrivate * priv = self->priv;
sql = db_conn_render (self->priv->conn, self->priv->stmt, NULL, NULL);
stmt = sql_parser_parse (sql);
g_free (sql);
if (!stmt)
return;
g_object_ref_sink (stmt);
if (SQL_IS_MULTI_STMT (stmt))
{
SqlList * stmts;
g_object_get (stmt, "stmts", &stmts, NULL);
select = sql_list_get (stmts, priv->result_pos);
}
else
select = stmt;
if (!SQL_IS_SELECT (select))
goto exit;
gint i;
SqlList * targets;
g_object_get (select, "targets", &targets, NULL);
for (i = 0; i < sql_list_length (targets); i++)
db_model_analyse_join (self, sql_list_get (targets, i));
exit:
g_object_unref (stmt);
}
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Public
// Setters & getters
/**
* db_model_set_conn:
* @self: 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 * self, DbConn * conn)
{
g_return_if_fail (DB_IS_MODEL (self));
g_return_if_fail (DB_IS_CONN (conn) || !conn);
if (conn)
{
if (!self->priv->conn)
{
self->priv->conn = g_object_ref (conn);
db_model_execute_stmt (self);
// db_model_on_stmt_changed (self->priv->stmt, self);
}
else
g_warning ("DbModel: The connection can only be set once");
}
}
/**
* db_model_get_conn:
* @self: 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 * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), NULL);
return self->priv->conn;
}
/**
* db_model_get_spec:
* @self: a #DbModel
* @col: the number of a column of @self
*
* Returns the #GvnParamSpec of a field in the position @col of @self.
*
* Return value: (transfer none): the #GvnParamSpec of the column with number @col
**/
const GvnParamSpec * db_model_get_spec (DbModel * self, gint col)
{
g_return_val_if_fail (DB_IS_MODEL (self), NULL);
if ((self->priv->result && 0 <= col
&& col < self->priv->result->ncols)
&& self->priv->column)
return self->priv->column[col].spec;
return NULL;
}
/**
* db_model_get_column_name:
* @self: a #DbModel
* @col: the number of a column of @self
*
* Retrieves the name of a field in the position @col of @self.
*
* Return value: the name of the column with number @col
**/
const gchar * db_model_get_column_name (DbModel * self, gint col)
{
g_return_val_if_fail (DB_IS_MODEL (self), NULL);
if ((self->priv->result
&& 0 <= col
&& col < self->priv->result->ncols)
&& self->priv->column)
return self->priv->column[col].alias;
return NULL;
}
/**
* db_model_get_column_index:
* @self: a #DbModel
* @name: the name of a column of @self
*
* 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 * self, const gchar * name)
{
gpointer column;
g_return_val_if_fail (DB_IS_MODEL (self), -1);
if (name && self->priv->column_index
&& g_hash_table_lookup_extended (self->priv->column_index, name, NULL, &column))
return GPOINTER_TO_INT (column);
return -1;
}
/**
* db_model_get_status:
* @self: a #DbModel
*
* Returns the current #DbModelStatus of @self.
*
* Return value: the status of @self
**/
DbModelStatus db_model_get_status (DbModel * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), -1);
return self->priv->status;
}
/**
* db_model_is_ready:
* @self: a #DbModel
*
* Checks if the model is ready.
*
* Return value: %TRUE if the model is ready, %FALSE otherwise
**/
gboolean db_model_is_ready (DbModel * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
return self->priv->status == DB_MODEL_STATUS_READY;
}
/**
* db_model_get_mode:
* @self: a #DbModel
*
* Retrieves the current working mode of @self. See #DbModelMode.
*
* Return value: the current mode
**/
DbModelMode db_model_get_mode (DbModel * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), -1);
return (self->priv->mode);
}
/**
* db_model_set_mode:
* @self: a #DbModel
* @mode: a #DbModelMode to set the model on
*
* Sets the working mode of @self to @mode. See #DbModelMode.
**/
void db_model_set_mode (DbModel * self, DbModelMode mode)
{
g_return_if_fail (DB_IS_MODEL (self));
self->priv->mode = mode;
}
/**
* db_model_toggle_mode:
* @self: a #DbModel
*
* Toogles the working mode of @self. See #DbModelMode to see the two possible
* modes.
**/
void db_model_toggle_mode (DbModel * self)
{
g_return_if_fail (DB_IS_MODEL (self));
self->priv->mode = (self->priv->mode == DB_MODEL_MODE_ON_DEMAND)?
DB_MODEL_MODE_ON_CHANGE:
DB_MODEL_MODE_ON_DEMAND;
}
/**
* db_model_set_result_pos:
* @self: 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 * self, guint pos)
{
g_return_if_fail (DB_IS_MODEL (self));
self->priv->result_pos = pos;
}
/**
* db_model_get_update_flags:
* @self: a #DbModel
*
* Retrieves the update flags of @self. See #DbModelUpdateFlags for the details
* of those flags. See also db_model_request_update_flags().
*
* Return value: the #DbModelUpdateFlags of @self
**/
DbModelUpdateFlags db_model_get_update_flags (DbModel * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
return self->priv->update_flags;
}
/**
* db_model_request_update_flags:
* @self: a #DbModel
* @flags: the set of #DbModelUpdateFlags to be set to @self
*
* Requests the update flags of @self. See #DbModelUpdateFlags for the details
* of those flags. If @self 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 * self, DbModelUpdateFlags flags)
{
g_return_if_fail (DB_IS_MODEL (self));
self->priv->user_update_flags = flags;
db_model_calculate_update_flags (self);
}
/**
* db_model_unset_update_flags:
* @self: a #DbModel
* @flags: the set of #DbModelUpdateFlags to be unset in @self
*
* Unsets the update flags of @self. 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 * self, DbModelUpdateFlags flags)
{
g_return_if_fail (DB_IS_MODEL (self));
self->priv->user_update_flags &= ~flags;
db_model_calculate_update_flags (self);
}
/**
* db_model_get_last:
* @self: a @DbModel
* @iter: (out): an unitialized #DbIter
*
* Points @iter to the last row of @self.
*
* Return value: %FALSE if @self is a valid #DbModel, %TRUE otherwise
**/
gboolean db_model_get_last (DbModel * self, DbIter * iter)
{
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
iter->data = g_ptr_array_index
(self->priv->data, (guint) self->priv->data->len - 1);
iter->stamp = self->priv->stamp;
return TRUE;
}
/**
* db_model_add_join:
* @self: a #DbModel
* @master_field: the field on the left of the join
* @slave_field: 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 (DbModel * self,
const gchar * master_field, const gchar * slave_field)
{
DbJoin * join;
g_return_if_fail (DB_IS_MODEL (self));
g_return_if_fail (master_field);
g_return_if_fail (slave_field);
join = g_new (DbJoin, 1);
join->master = field_new_from_string (master_field);
join->slave = field_new_from_string (slave_field);
self->priv->join = g_slist_prepend (self->priv->join, join);
}
/**
* db_model_get_batch:
* @self: a #DbModel
*
* Gets the batch used by the model.
*
* Return value: (transfer none): the #SqlBatch
**/
SqlBatch * db_model_get_batch (DbModel * self)
{
return self->priv->batch;
}
/**
* db_model_set_batch:
* @self: a #DbModel
* @batch: the #SqlBatch
*
* Sets the batch used by the model.
**/
void db_model_set_batch (DbModel * self, SqlBatch * batch)
{
DbModelPrivate * priv;
g_return_if_fail (DB_IS_MODEL (self));
g_return_if_fail (SQL_IS_BATCH (batch) || !batch);
priv = self->priv;
if (priv->batch)
{
g_signal_handlers_disconnect_by_func (priv->batch,
db_model_on_batch_changed, self);
g_object_unref (priv->batch);
}
if (batch)
{
g_signal_connect (batch, "changed",
G_CALLBACK (db_model_on_batch_changed), self);
g_object_ref_sink (batch);
}
priv->batch = batch;
}
static ColumnDef * db_model_create_column_def (DbModel * self, const gchar * field_str)
{
Field * field = field_new_from_string (field_str);
if (field->name && field->target && !field->schema)
{
ColumnDef * def = column_def_new ();
g_hash_table_insert (self->priv->defaults, field, def);
return def;
}
else
{
g_warning ("DbModel: Field string should specify name and table alias: %s", field_str);
field_free (field);
}
return NULL;
}
static void db_model_on_param_changed (GvnParam * param, GValue * value, DbModel * self)
{
db_model_refresh (self);
}
/**
* db_model_set_default_value_from_column:
* @self: 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 * self,
const gchar * field_str, const gchar * column_str)
{
ColumnDef * def;
g_return_if_fail (DB_IS_MODEL (self));
g_return_if_fail (field_str);
g_return_if_fail (column_str);
def = db_model_create_column_def (self, field_str);
if (def)
def->src_column = field_new_from_string (column_str);
}
/**
* db_model_set_default_value_from_param:
* @self: a #DbModel
* @field_str: the field to be set
* @param: a #GvnParam
* @link: whether to refresh when the parameter changes
*
* Get the default value for @field_str from @param.
**/
void db_model_set_default_value_from_param (DbModel * self,
const gchar * field_str, GvnParam * param, gboolean link)
{
ColumnDef * def;
g_return_if_fail (DB_IS_MODEL (self));
g_return_if_fail (field_str);
g_return_if_fail (GVN_IS_PARAM (param));
def = db_model_create_column_def (self, field_str);
if (!def)
return;
def->param = g_object_ref_sink (param);
if (link)
def->link = g_signal_connect (param, "value-changed",
G_CALLBACK (db_model_on_param_changed), self);
}
/**
* db_model_get_main_table:
* @self: a #DbModel
*
* Returns the string with the name of the main table of @self. 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 @self
**/
const gchar * db_model_get_main_table (DbModel * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), NULL);
if (self->priv->main_table)
return self->priv->main_table->name;
return NULL;
}
/**
* db_model_get_stmt:
* @self: a #DbModel
*
* Returns the #SqlStmt which queries to the database about the data of @self.
*
* Return value: (transfer none): the #SqlStmt property of @self
**/
const SqlStmt * db_model_get_stmt (DbModel * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), NULL);
return self->priv->stmt;
}
/**
* db_model_set_stmt:
* @self: a #DbModel
* @stmt: the #SqlStmt
*
* Sets the "stmt" property of the model.
**/
void db_model_set_stmt (DbModel * self, SqlStmt * stmt)
{
if (!stmt)
return;
g_return_if_fail (DB_IS_MODEL (self));
g_return_if_fail (!self->priv->stmt);
g_return_if_fail (SQL_IS_STRING (stmt) || SQL_IS_SELECT (stmt));
self->priv->stmt = g_object_ref_sink (stmt);
db_model_execute_stmt (self);
/* g_signal_connect (stmt, "changed", G_CALLBACK (db_model_on_stmt_changed), self);
db_model_on_stmt_changed (stmt, self);*/
}
/**
* db_model_set_sql:
* @self: a #DbModel
* @sql: a value for the "sql property
*
* Sets the "sql" property to @sql.
**/
void db_model_set_sql (DbModel * self, const gchar * sql)
{
// SqlObject * string;
if (!sql)
return;
g_return_if_fail (DB_IS_MODEL (self));
g_free (self->priv->sql);
self->priv->sql = g_strdup (sql);
db_model_execute_stmt (self);
/* if (self->priv->use_file)
string = db_conn_create_stmt_from_file (self->priv->conn, sql);
else
string = sql_string_new (sql);
db_model_set_stmt (self, SQL_STMT (string)); */
}
/**
* db_model_get:
* @self: 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 * self, DbIter * iter, ...)
{
va_list va;
gint column;
GValue * val;
g_return_if_fail (DB_IS_MODEL (self));
g_return_if_fail (self->priv->result);
g_return_if_fail (iter != NULL);
va_start (va, iter);
while ((column = (va_arg (va, gint))) >= 0 && column < self->priv->result->ncols)
{
val = DB_ROW_FIELD (iter->data, column);
if (gvn_value_is_null (val))
switch (gvn_param_spec_get_gtype (self->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:
* @self: 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 * self, DbIter * iter, ...)
{
gint column;
gpointer content;
GValue val = G_VALUE_INIT;
va_list va;
g_return_if_fail (DB_IS_MODEL (self));
g_return_if_fail (self->priv->result);
g_return_if_fail (iter != NULL);
if (iter->data == self->priv->null_row)
return;
va_start (va, iter);
while ((column = (va_arg (va, gint))) >= 0 && column < self->priv->result->ncols)
{
content = va_arg (va, gpointer);
gvn_value_new_with_content (&val,
gvn_param_spec_get_gtype (self->priv->column[column].spec), content);
db_model_set_value (self, iter, column, &val, NULL);
g_value_unset (&val);
}
va_end (va);
g_return_if_fail (column == -1);
}
/**
* db_model_get_value:
* @self: a #DbModel
* @iter: a #DbIter pointing to a row of @self
* @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 @self 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 * self, DbIter * iter, gint col, GError ** err)
{
g_return_val_if_fail (DB_IS_MODEL (self), NULL);
g_return_val_if_fail (self->priv->data && self->priv->result, NULL);
g_return_val_if_fail (VALID_ITER (iter, self), NULL);
if (MODEL_NOT_READY (self))
{
g_set_error (err
,DB_MODEL_LOG_DOMAIN
,DB_MODEL_ERROR_NOT_READY
,"The model is not ready");
return NULL;
}
if (0 > col || col >= self->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:
* @self: 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 return location for a #GError or %NULL to ignore
* errors
*
* Sets the value of a single field to @value on @self 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 * self, DbIter * iter, gint col, const GValue * value, GError ** err)
{
DbModelPrivate * priv = self->priv;
gboolean ret = FALSE;
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
g_return_val_if_fail (VALID_ITER (iter, self), FALSE);
if (MODEL_NOT_READY (self) || !priv->result)
{
g_set_error (err, DB_MODEL_LOG_DOMAIN
,DB_MODEL_ERROR_NOT_READY, "Model not ready");
}
else
{
GValue new_value = G_VALUE_INIT;
DbRow * row = iter->data;
GvnParamSpec * spec = priv->column[col].spec;
g_return_val_if_fail (0 <= col && col < priv->result->ncols, FALSE);
if (iter->data == self->priv->null_row)
{
return FALSE;
}
if (!gvn_param_spec_validate (spec, value, err))
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 (self, 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
{
DbOperation * operation = g_hash_table_lookup (priv->row_ops, row);
DbModelRowOp row_op = operation ? operation->type : 0;
priv->updated_col = col;
priv->updated_value = g_new0 (GValue, 1);
g_value_init (priv->updated_value, G_VALUE_TYPE (&new_value));
g_value_copy (&new_value, priv->updated_value);
g_signal_emit (self, 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 (self, FALSE);
ret = TRUE;
}
g_value_unset (&new_value);
}
return ret;
}
/**
* db_model_insert:
* @self: a #DbModel
* @iter: (out): a #DbIter that will point to the new row
*
* Inserts an empty row at the end of @self. 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 * self, DbIter * iter)
{
gint i;
DbRow * row;
DbModelPrivate * priv;
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
priv = self->priv;
if (MODEL_NOT_READY (self) || !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 * def;
const GValue * def_value = NULL;
DbColumn col = priv->column[i];
field.name = col.name;
field.target = priv->column_table[i]->alias;
field.schema = NULL;
def = g_hash_table_lookup (priv->defaults, &field);
if (def && def->param)
def_value = gvn_param_get_value (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 (self, iter->data, DB_MODEL_ROW_OP_INSERT, 0);
g_signal_emit (self, db_model_signal[LINE_INSERTED], 0, iter);
return TRUE;
}
/**
* db_model_delete:
* @self: a #DbModel
* @iter: a #DbIter pointing to the row to be deleted
*
* Deletes the row pointed to by @iter on @self, 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 @self is not deleted
* and a log message is emitted.
**/
void db_model_delete (DbModel * self, DbIter * iter)
{
g_return_if_fail (DB_IS_MODEL (self));
g_return_if_fail (VALID_ITER (iter, self));
if (MODEL_NOT_READY (self))
{
g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN)
,G_LOG_LEVEL_WARNING, "Model not ready");
return;
}
if (!(self->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 (iter->data == self->priv->null_row)
return;
if (!db_model_set_row_operation
(self, g_ptr_array_index (self->priv->data, DB_ROW_POSITION (iter->data)),
DB_MODEL_ROW_OP_DELETE, 0))
return;
g_signal_emit (self, db_model_signal[LINE_TOGGLED], 0, iter);
if (self->priv->mode == DB_MODEL_MODE_ON_CHANGE)
db_model_perform_operations (self, FALSE);
}
/**
* db_model_use_null_row:
* @self: a #DbModel
* @use: wether to use a null row
*
* Sets wether to use a null row at the end of the model. This row can't be
* modified by the methods @db_model_delete or @db_model_set_value, the only way
* to remove it is to call @db_model_use_null_row again passing #FALSE.
**/
void db_model_use_null_row (DbModel * self, gboolean use)
{
DbModelPrivate * priv;
g_return_if_fail (DB_IS_MODEL (self));
priv = self->priv;
if (MODEL_NOT_READY(self) || !priv->result
|| ((priv->null_row != NULL) == use))
return;
if (use)
{
if (!priv->null_row)
{
gint i;
DbIter iter;
DbModelPrivate * priv = self->priv;
DbRow * curr, * row = db_row_new (priv->result->ncols, 0);
gpointer * data = priv->data->pdata;
for (i = 0; i < row->len; i++)
g_value_init (&row->value[i], GVN_TYPE_NULL);
priv->null_row = row;
iter.data = row;
iter.stamp = priv->stamp;
priv->result->nrows++;
g_ptr_array_set_size (priv->data, priv->result->nrows);
curr = data[0];
data[0] = row;
for (i = 1; i < priv->result->nrows; i++)
{
DbRow * next = data[i];
DB_ROW_POSITION (curr)++;
data[i] = curr;
curr = next;
}
g_signal_emit (self, db_model_signal[LINE_INSERTED], 0, &iter);
}
}
else if (priv->null_row)
{
g_signal_emit (self, db_model_signal[LINE_DELETED], 0,
DB_ROW_POSITION (priv->null_row));
priv->null_row = NULL;
}
}
/**
* db_model_order_by:
* @self: a #DbModel
* @col: the number of the column that will be the sort criteria
* @order: the order to sort in
*
* Sorts @self in the order indicated by @order and using the data
* on the field @col.
**/
void db_model_order_by (DbModel * self, gint col, DbSortType order)
{
DbModelPrivate * priv;
SortInfo info;
g_return_if_fail (DB_IS_MODEL (self));
priv = self->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 (self, db_model_signal[SORT_CHANGED], 0, NULL);
info.col = col;
info.null_row = priv->null_row;
info.order = order;
g_ptr_array_sort_with_data (priv->data
,(GCompareDataFunc) db_model_valcmp
,&info);
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 (self, db_model_signal[LINES_REORDERED], 0, col, new_order);
}
/**
* db_model_search:
* @self: 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 * self, gint col, DbIter * iter, gpointer content)
{
gboolean ret;
GValue value = G_VALUE_INIT;
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
g_return_val_if_fail (self->priv->result
&& 0 <= self->priv->result->ncols
&& col < self->priv->result->ncols , FALSE);
gvn_value_new_with_content (&value,
gvn_param_spec_get_gtype (self->priv->column[col].spec), content);
ret = db_model_search_value (self, col, iter, &value);
g_value_unset (&value);
return ret;
}
/**
* db_model_search_value:
* @self: 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 * self, gint col, DbIter * iter, const GValue * value)
{
gint i;
GType type;
DbRow * row;
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
g_return_val_if_fail (G_IS_VALUE (value), FALSE);
g_return_val_if_fail (self->priv->result
&& 0 <= col
&& col < self->priv->result->ncols, FALSE);
type = gvn_param_spec_get_gtype (self->priv->column[col].spec);
if (gvn_value_is_null (value) || G_VALUE_TYPE (value) == type)
for (i = 0; i < self->priv->result->nrows; i++)
{
row = g_ptr_array_index (self->priv->data, i);
if (gvn_value_compare (DB_ROW_FIELD (row, col), value))
{
iter->stamp = self->priv->stamp;
iter->data = row;
return TRUE;
}
}
return FALSE;
}
/**
* db_model_get_row_operations:
* @self: 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 * self, DbIter * iter)
{
DbOperation * op = NULL;
g_return_val_if_fail (DB_IS_MODEL (self), 0);
g_return_val_if_fail (VALID_ITER (iter, self), 0);
if (self->priv->row_ops)
op = g_hash_table_lookup (self->priv->row_ops, (DbRow *) iter->data);
return op ? op->type : 0;
}
/**
* db_model_has_pending_operations:
* @self: 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 * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
if (self->priv->row_ops)
return g_hash_table_size (self->priv->row_ops) > 0;
return FALSE;
}
static SqlObject * db_model_create_where (DbModel * self,
TableInfo * tinfo, DbOperation * operation, gboolean use_new_values)
{
GSList * l;
DbUpdatedField * u;
GValue * g_value;
SqlObject * value;
DbRow * row = operation->row;
DbModelPrivate * priv = self->priv;
SqlObject * where;
SqlList * and_operands;
if (!tinfo->pkeys)
return NULL;
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);
for (l = tinfo->pkeys; l; l = l->next)
{
GSList * n;
SqlObject * equal;
SqlList * operands;
gint col = GPOINTER_TO_INT (l->data);
value = NULL;
g_value = &row->value[col];
if (!use_new_values)
{
for (n = operation->updated; n && (u = n->data); n = n->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
{
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);
else
value = sql_value_new_with_value (g_value);
}
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 * self,
TableInfo * tinfo, DbOperation * operation)
{
gint i;
DbModelPrivate * priv = self->priv;
DbRow * row = operation->row;
const GValue * value;
SqlList * targets, * stmts, * sets, * fields, * values, * select_fields;
SqlObject * target, * insert, * set, * select, * where;
GHashTableIter iter;
Field * field;
ColumnDef * def;
GArray * cols;
gchar * colname;
where = db_model_create_where (self, tinfo, operation, TRUE);
if (!where)
return NULL;
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);
target = sql_table_new (tinfo->name, tinfo->schema);
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);
target = sql_table_new (tinfo->name, tinfo->schema);
sql_target_set_alias (SQL_TARGET (target), tinfo->alias);
sql_list_add (targets, target);
select = g_object_new (SQL_TYPE_SELECT
,"fields", select_fields
,"targets", targets
,"where", where
,NULL
);
g_hash_table_iter_init (&iter, priv->defaults);
while (g_hash_table_iter_next (&iter, (gpointer) &field, (gpointer) &def))
if (!g_strcmp0 (field->target, tinfo->alias)
&& !g_hash_table_lookup (tinfo->columns, field->name))
{
if (def->param)
value = gvn_param_get_value (def->param);
else
value = NULL;
if (value)
{
sql_list_add (fields, sql_field_new (field->name));
sql_list_add (values, sql_value_new_with_value (value));
}
}
g_hash_table_iter_init (&iter, tinfo->columns);
while (g_hash_table_iter_next (&iter, (gpointer) &colname, (gpointer) &cols))
{
i = g_array_index (cols, gint, 0);
value = &row->value[i];
if (!gvn_value_is_null (value))
{
sql_list_add (fields, sql_field_new (colname));
sql_list_add (values, sql_value_new_with_value (value));
}
sql_list_add (select_fields, sql_field_new (colname));
}
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:
* @self: 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 @self 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 * self, 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 (self));
priv = self->priv;
if (!priv->update_flags)
return;
if (MODEL_NOT_READY (self))
{
g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN), G_LOG_LEVEL_WARNING,
"Model not ready");
return;
}
if (!priv->operation->length)
return;
stmts = sql_list_new (SQL_TYPE_STMT);
g_object_ref_sink (stmts);
for (l = priv->operation->head; 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 (self, 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 (self, priv->main_table, op);
if (!stmt)
error = TRUE;
}
else if (op->type & DB_MODEL_ROW_OP_UPDATE) // UPDATE || INSERT + SELECT
{
GList * l;
GSList * n;
GQueue * fields;
TableInfo * tinfo;
GHashTableIter iter;
SqlList * update_list;
DbUpdatedField * u;
GHashTable * tables = g_hash_table_new_full (
g_direct_hash
,g_direct_equal
,NULL
,(GDestroyNotify) g_queue_free
);
for (n = op->updated; n && (u = n->data); n = n->next)
{
tinfo = priv->column_table[u->column];
fields = g_hash_table_lookup (tables, tinfo);
if (!fields)
{
fields = g_queue_new ();
g_hash_table_insert (tables, tinfo, fields);
}
g_queue_push_tail (fields, u);
}
update_list = sql_list_new (SQL_TYPE_STMT);
g_hash_table_iter_init (&iter, tables);
while (g_hash_table_iter_next (&iter, (gpointer) &tinfo, (gpointer) &fields))
{
where = db_model_create_where (self, tinfo, 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 (tinfo->name, tinfo->schema));
for (l = fields->head; 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 (self, tinfo, op);
if (insert)
sql_list_add (update_list, insert);
}
}
g_hash_table_unref (tables);
stmt = g_object_new (SQL_TYPE_MULTI_STMT, "stmts", update_list, NULL);
}
if (stmt)
sql_list_add (stmts, stmt);
}
if (!error)
{
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->self = g_object_ref (self);
data->operations = ops;
data->stmts = g_object_ref_sink (stmts);
if (sql_list_length (stmts) > 0)
{
SqlObject * multi = g_object_new (
SQL_TYPE_MULTI_STMT, "stmts", stmts, NULL);
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 (self, request);
}
else
{
db_model_on_operations_done (NULL, data);
db_model_request_free (data);
}
}
else
g_warning ("DbModel: Error performing operations.");
g_object_unref (stmts);
}
/**
* db_model_refresh:
* @self: a #DbModel
*
* Executes the SELECT query and fills @self with the data returned by it.
**/
void db_model_refresh (DbModel * self)
{
DbModelPrivate * priv;
gboolean is_ready = FALSE;
g_return_if_fail (DB_IS_MODEL (self));
priv = self->priv;
db_model_clear (self);
if (priv->conn && priv->stmt)
{
Field * field;
ColumnDef * def;
SqlObject * link_op;
SqlList * link_operands;
SqlBatch * tmp_batch;
GHashTableIter iter;
// Gets all the holders from the statement
tmp_batch = sql_batch_new ();
g_object_ref_sink (tmp_batch);
sql_object_get_holders (SQL_OBJECT (priv->stmt), tmp_batch);
sql_batch_merge (tmp_batch, priv->batch);
// Creates the link operation
link_op = sql_operation_new (SQL_OPERATION_TYPE_AND);
g_object_ref_sink (link_op);
link_operands = sql_list_new (SQL_TYPE_EXPR);
sql_operation_set_operands (SQL_OPERATION (link_op), link_operands);
g_hash_table_iter_init (&iter, priv->defaults);
while (g_hash_table_iter_next (&iter, (gpointer) &field, (gpointer) &def))
if (def->link)
{
SqlObject * sql_field;
SqlObject * equal_op;
SqlList * equal_operands;
equal_op = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
sql_list_add (link_operands, equal_op);
sql_field = sql_field_new_with_target (
field->name, field->target, NULL);
equal_operands = sql_list_new (SQL_TYPE_EXPR);
sql_list_add (equal_operands, sql_field);
sql_list_add (equal_operands, sql_value_new_with_param (def->param));
sql_operation_set_operands (SQL_OPERATION (equal_op), equal_operands);
}
if (sql_list_length (link_operands) > 0)
sql_batch_add (tmp_batch, "link", link_op);
// Executes the statement if its ready
if (sql_batch_is_ready (tmp_batch))
{
is_ready = TRUE;
db_model_set_status (self, 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 (self)
,(GDestroyNotify) g_object_unref
);
}
g_object_unref (link_op);
g_object_unref (tmp_batch);
}
if (!is_ready)
db_model_set_status (self, DB_MODEL_STATUS_CLEAN);
}
/**
* db_model_get_nrows:
* @self: a #DbModel
*
* Returns the current number of rows on @self.
*
* Return value: the number of rows
**/
gint db_model_get_nrows (DbModel * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), 0);
if (self->priv->result)
return self->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 * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
return (iter && VALID_ITER (iter, self));
}
// GtkTreeModel implementation methods.
/**
* db_model_get_ncols:
* @self: a #DbModel
*
* Returns the number of columns supported by @self.
*
* Return value: the number of columns
**/
gint db_model_get_ncols (DbModel * self)
{
g_return_val_if_fail (DB_IS_MODEL (self), -1);
if (self->priv->result)
return self->priv->result->ncols;
else
return 0;
}
/**
* db_model_get_column_type:
* @self: 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 * self, gint index)
{
g_return_val_if_fail (DB_IS_MODEL (self), G_TYPE_INVALID);
g_return_val_if_fail (self->priv->result
&& 0 <= index && index < self->priv->result->ncols, G_TYPE_INVALID);
if (self->priv->column)
if (self->priv->column[index].spec)
return gvn_param_spec_get_gtype (self->priv->column[index].spec);
return G_TYPE_INVALID;
}
/**
* db_model_get_path:
* @self: 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 * self, DbIter * iter)
{
g_return_val_if_fail (DB_IS_MODEL (self), 0);
if (MODEL_NOT_READY (self)) return 0;
g_return_val_if_fail (VALID_ITER (iter, self), 0);
return DB_ROW_POSITION (iter->data);
}
/**
* db_model_get_iter:
* @self: a #DbModel
* @iter: (out): an unitialized #DbIter
* @path: the number of the row being accessed
*
* Sets @iter pointing to the row of @self specified by @path.
*
* Return value: %TRUE if the position is found, %FALSE otherwise
**/
gboolean db_model_get_iter (DbModel * self, DbIter * iter, gint path)
{
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
if ((0 > path
|| (self->priv->result && path >= self->priv->result->nrows))
|| MODEL_NOT_READY (self))
return FALSE;
iter->stamp = self->priv->stamp;
iter->data = g_ptr_array_index (self->priv->data, (guint) path);
if (iter->data)
return TRUE;
return FALSE;
}
/**
* db_model_get_iter_first:
* @self: a #DbModel
* @iter: (out): an unitialized #DbIter
*
* Sets @iter pointing to the first row of @self.
*
* Return value: %TRUE if @iter is set right
**/
gboolean db_model_get_iter_first (DbModel * self, DbIter * iter)
{
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
if (MODEL_NOT_READY(self)
|| !self->priv->result || !self->priv->result->nrows)
return FALSE;
iter->stamp = self->priv->stamp;
iter->data = g_ptr_array_index (self->priv->data, 0);
return TRUE;
}
/**
* db_model_iter_prev:
* @self: a #DbModel
* @iter: (inout): a valid #DbIter
*
* Sets @iter pointing to the previous row of @self.
*
* Return value: %TRUE if the iter has been changed to the previous row
**/
gboolean db_model_iter_prev (DbModel * self, DbIter * iter)
{
DbModelPrivate * priv;
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
priv = self->priv;
if (MODEL_NOT_READY(self)
|| !priv->result || !priv->result->nrows)
return FALSE;
g_return_val_if_fail (VALID_ITER (iter, self), FALSE);
if ((iter->data = g_ptr_array_index (priv->data, (guint) DB_ROW_POSITION (iter->data) - 1)))
return TRUE;
return FALSE;
}
/**
* db_model_iter_next:
* @self: a #DbModel
* @iter: (inout): a valid #DbIter
*
* Sets @iter pointing to the next row of @self.
*
* Return value: %TRUE if the iter has been changed to the next row
**/
gboolean db_model_iter_next (DbModel * self, DbIter * iter)
{
gint pos;
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
if (MODEL_NOT_READY(self)
|| !self->priv->result || !self->priv->result->nrows)
return FALSE;
g_return_val_if_fail (VALID_ITER (iter, self), FALSE);
pos = DB_ROW_POSITION (iter->data);
if (pos < self->priv->result->nrows-1)
{
iter->data = g_ptr_array_index (self->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:
* @self: 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 * self, gint * sort_column_id,
DbSortType * order)
{
g_return_val_if_fail (DB_IS_MODEL (self), FALSE);
if (sort_column_id)
* sort_column_id = self->priv->sort_column_id;
if (order)
* order = self->priv->order;
if (self->priv->sort_column_id == DB_MODEL_DEFAULT_SORT_COLUMN_ID
|| self->priv->sort_column_id == DB_MODEL_UNSORTED_SORT_COLUMN_ID)
return FALSE;
else
return TRUE;
}
/**
* db_model_set_sort_column_id:
* @self: 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. @self will resort itself
* to reflect this change. See #GtkTreeSortable.
**/
void db_model_set_sort_column_id (DbModel * self, gint sort_column_id,
DbSortType order)
{
g_return_if_fail (DB_IS_MODEL (self));
db_model_order_by (self, sort_column_id, order);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class
typedef enum
{
PROP_CONN = 1
,PROP_STMT
,PROP_USE_FILE
,PROP_SQL
,PROP_MAIN_TABLE
,PROP_UPDATE_FLAGS
,PROP_RESULT_POS
,PROP_PARTIAL_DELETE
,PROP_BATCH
}
DbModelProp;
static void db_model_set_property (DbModel * self, guint property_id,
const GValue * value, GParamSpec * pspec)
{
switch (property_id)
{
case PROP_CONN:
db_model_set_conn (self, g_value_get_object (value));
break;
case PROP_STMT:
db_model_set_stmt (self, g_value_get_object (value));
break;
case PROP_SQL:
db_model_set_sql (self, g_value_get_string (value));
break;
case PROP_USE_FILE:
self->priv->use_file = g_value_get_boolean (value);
break;
case PROP_UPDATE_FLAGS:
db_model_request_update_flags (self, g_value_get_flags (value));
break;
case PROP_RESULT_POS:
self->priv->result_pos = g_value_get_uint (value);
break;
case PROP_PARTIAL_DELETE:
self->priv->partial_delete = g_value_get_boolean (value);
break;
case PROP_BATCH:
db_model_set_batch (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
}
}
static void db_model_get_property (DbModel * self, guint property_id,
GValue * value, GParamSpec * pspec)
{
switch (property_id)
{
case PROP_CONN:
g_value_set_object (value, self->priv->conn);
break;
case PROP_STMT:
g_value_set_object (value, self->priv->stmt);
break;
case PROP_SQL:
g_value_set_string (value, self->priv->sql);
break;
case PROP_USE_FILE:
g_value_set_boolean (value, self->priv->use_file);
break;
case PROP_MAIN_TABLE:
g_value_set_string (value, db_model_get_main_table (self));
break;
case PROP_UPDATE_FLAGS:
g_value_set_flags (value, self->priv->update_flags);
break;
case PROP_RESULT_POS:
g_value_set_uint (value, self->priv->result_pos);
break;
case PROP_PARTIAL_DELETE:
g_value_set_boolean (value, self->priv->partial_delete);
break;
case PROP_BATCH:
g_value_set_object (value, self->priv->batch);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
}
}
static void db_model_init (DbModel * self)
{
DbModelPrivate * priv = self->priv =
G_TYPE_INSTANCE_GET_PRIVATE (self, 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->result = NULL;
priv->result_pos = 0;
priv->data = NULL;
priv->column = NULL;
priv->stamp = g_random_int ();
priv->column_index = g_hash_table_new_full (
g_str_hash,
g_str_equal,
(GDestroyNotify) g_free,
NULL
);
priv->defaults = g_hash_table_new_full (
(GHashFunc) field_hash,
(GEqualFunc) field_equal,
(GDestroyNotify) field_free,
(GDestroyNotify) column_def_free
);
priv->fresh = TRUE;
priv->null_row = NULL;
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->updatable_data_allocated = FALSE;
priv->user_update_flags = 0;
priv->update_flags = 0;
priv->main_table = NULL;
priv->mode = DB_MODEL_MODE_ON_CHANGE;
priv->operation = NULL;
priv->row_ops = NULL;
priv->pending_request = NULL;
priv->tables = NULL;
priv->column_table = NULL;
priv->partial_delete = FALSE;
priv->join = NULL;
}
static void db_model_finalize (DbModel * self)
{
DbModelPrivate * priv = self->priv;
db_model_clear (self);
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 (self, NULL);
g_hash_table_destroy (priv->defaults);
if (priv->updatable_data_allocated)
{
g_queue_free (priv->operation);
g_hash_table_unref (priv->row_ops);
g_hash_table_unref (priv->tables);
g_free (priv->column_table);
// g_slist_free_full (priv->join, (GDestroyNotify) db_join_free);
}
G_OBJECT_CLASS (db_model_parent_class)->finalize (G_OBJECT (self));
}
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 on which the signal is emitted
* @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 on which the signal is emitted
* @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 on which the signal is emitted
* @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 object on which the signal is emitted
* @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 on which the signal is emitted
* @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 on which the signal is emitted
* @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 on which the signal is emitted
*
* 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 on which the signal is emitted
* @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_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_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_MAIN_TABLE,
g_param_spec_string ("main-table"
,_("Main Table")
,_("The main table of the model")
,NULL
,G_PARAM_READABLE
));
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
,0
,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
));
g_object_class_install_property (klass, PROP_PARTIAL_DELETE,
g_param_spec_boolean ("partial-delete"
,_("Partial delete")
,_("When a row is deleted set all the fields from "
"the table to null rather than delete it.")
,FALSE
,G_PARAM_READWRITE
));
g_object_class_install_property (klass, PROP_BATCH,
g_param_spec_object ("batch"
,_("Batch")
,_("The batch assigned to the model")
,SQL_TYPE_BATCH
,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"},
{DB_MODEL_ALL, "DB_MODEL_ALL", "all"},
{0, NULL, NULL}
};
type = g_flags_register_static
(g_intern_static_string ("DbModelUpdateFlags"), values);
}
return type;
}