3445 lines
86 KiB
C
3445 lines
86 KiB
C
/*
|
|
* 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"
|
|
|
|
/**
|
|
* 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.
|
|
**/
|
|
|
|
/*
|
|
* 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;
|
|
gchar * main_table;
|
|
DbModelUpdateFlags update_flags;
|
|
|
|
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;
|
|
GSList * column_default;
|
|
GSList * param_default;
|
|
|
|
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;
|
|
};
|
|
|
|
G_DEFINE_TYPE (DbModel, db_model, G_TYPE_OBJECT)
|
|
|
|
#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)
|
|
|
|
// 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
|
|
|
|
// Helper structures and methods
|
|
|
|
// Structures
|
|
|
|
/*
|
|
* DbUpdatedField:
|
|
* @column: the position of the field in the row
|
|
* @value: the old value of the updated field
|
|
*
|
|
* Previous value of an updated field.
|
|
**/
|
|
typedef struct
|
|
{
|
|
gint column;
|
|
GValue * value;
|
|
}
|
|
DbUpdatedField;
|
|
|
|
/*
|
|
* DbOperation:
|
|
* @type: #DbModelRowOp flags
|
|
* @locked: %TRUE while the operation is being performed
|
|
* @row: the #DbRow over which the operation has been performed
|
|
* @updated: (element-type Db.UpdatedField): old values for the updated fields
|
|
* in @row
|
|
* @request: #DbRequest associated to the operation, once performed
|
|
*
|
|
* A structure explaining the operations performed over each #DbRow.
|
|
**/
|
|
typedef struct
|
|
{
|
|
DbModelRowOp type;
|
|
gboolean locked;
|
|
DbRow * row;
|
|
GSList * updated;
|
|
}
|
|
DbOperation;
|
|
|
|
/*
|
|
* DbModelRequest:
|
|
* @request: a DbRequest being performed
|
|
* @operations: a GQueue of operations being performed
|
|
* @model: the DbModel over which the operations are being performed
|
|
*
|
|
* This struct holds the information of a request performed but not yet
|
|
* finalized.
|
|
**/
|
|
typedef struct
|
|
{
|
|
DbModel * obj;
|
|
GQueue * operations;
|
|
}
|
|
DbModelRequest;
|
|
|
|
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;
|
|
|
|
typedef struct
|
|
{
|
|
gchar * dst;
|
|
gint col;
|
|
}
|
|
DbColDef;
|
|
|
|
typedef struct
|
|
{
|
|
gchar * dst;
|
|
GvnParam * param;
|
|
}
|
|
DbParamDef;
|
|
|
|
typedef struct
|
|
{
|
|
gint count;
|
|
gint * index;
|
|
}
|
|
DbModelPKey;
|
|
|
|
enum
|
|
{
|
|
DB_MODEL_UNSORTED_SORT_COLUMN_ID = -2,
|
|
DB_MODEL_DEFAULT_SORT_COLUMN_ID
|
|
};
|
|
|
|
// Prototypes
|
|
|
|
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);
|
|
static void db_model_finish_insert (DbModel * obj
|
|
,DbRow * req_row, gint i
|
|
,DbRow * row, gint j
|
|
,DbIter * iter);
|
|
static gboolean db_model_table_row_all_null (DbModel * model
|
|
,DbRow * row
|
|
,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 * 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;
|
|
gchar * main_table = NULL;
|
|
DbModelPrivate * priv = obj->priv;
|
|
|
|
if (priv->result)
|
|
for (i = 0; i < priv->result->ncols && !main_table; i++)
|
|
if (priv->column[i].info & DB_COLUMN_PRI_KEY)
|
|
{
|
|
if (!priv->user_main_table)
|
|
main_table = priv->column[i].table;
|
|
else if (!g_strcmp0 (priv->user_main_table, priv->column[i].table))
|
|
main_table = priv->user_main_table;
|
|
}
|
|
|
|
g_free (priv->main_table);
|
|
|
|
if (main_table)
|
|
{
|
|
priv->update_flags = DB_MODEL_ALL & priv->user_update_flags;
|
|
priv->main_table = g_strdup (main_table);
|
|
}
|
|
else
|
|
{
|
|
if (priv->user_main_table && priv->result)
|
|
g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN), G_LOG_LEVEL_WARNING,
|
|
"The requested table can't be set as main table");
|
|
|
|
priv->update_flags = 0;
|
|
priv->main_table = NULL;
|
|
}
|
|
}
|
|
|
|
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, j;
|
|
|
|
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
|
|
{
|
|
GSList * t = NULL;
|
|
|
|
priv->result = r;
|
|
|
|
if (priv->fresh)
|
|
priv->column_index = g_hash_table_new_full (g_str_hash,
|
|
g_str_equal, (GDestroyNotify) g_free, NULL);
|
|
|
|
for (i = 0; i < priv->result->ncols; i++)
|
|
{
|
|
// Set fields editable if *all* primary keys are selected FIXME
|
|
if (priv->column[i].info & DB_COLUMN_PRI_KEY
|
|
&& !g_slist_find_custom (t, priv->column[i].table, (GCompareFunc) g_strcmp0))
|
|
for (j = 0; j < priv->result->ncols; j++)
|
|
if (!g_strcmp0 (priv->column[i].table, priv->column[j].table))
|
|
{
|
|
gvn_param_spec_set_editable (priv->column[j].spec, TRUE);
|
|
t = g_slist_prepend (t, priv->column[j].table);
|
|
}
|
|
|
|
if (priv->fresh && priv->column_index)
|
|
g_hash_table_insert (priv->column_index,
|
|
g_strdup (priv->column[i].display), GINT_TO_POINTER (i));
|
|
}
|
|
|
|
g_slist_free (t);
|
|
|
|
db_model_calculate_update_flags (obj);
|
|
|
|
if (!priv->fresh)
|
|
db_model_set_sort_column_id (obj,
|
|
priv->old_sort_column_id, priv->old_order);
|
|
else
|
|
db_model_post_process_query (obj);
|
|
|
|
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_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);
|
|
}
|
|
|
|
static void db_model_on_stmt_changed (SqlStmt * stmt, DbModel * obj)
|
|
{
|
|
if (obj->priv->conn && stmt)
|
|
{
|
|
if (sql_object_is_ready (SQL_OBJECT (stmt)))
|
|
db_model_refresh (obj);
|
|
else
|
|
{
|
|
db_model_clear (obj);
|
|
db_model_set_status (obj, DB_MODEL_STATUS_CLEAN);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void db_model_on_operations_done (DbRequest * request, DbModelRequest * data)
|
|
{
|
|
DbOperation * op;
|
|
GError * err = NULL;
|
|
DbModel * obj = data->obj;
|
|
DbModelPrivate * priv = obj->priv;
|
|
|
|
obj->priv->pending_request =
|
|
g_slist_remove (priv->pending_request, request);
|
|
|
|
while (db_request_fetch_non_select (request, &err) != -1)
|
|
{
|
|
gint i, j;
|
|
DbResult * result;
|
|
DbRow * req_row, * row;
|
|
|
|
op = g_queue_pop_head (data->operations);
|
|
row = op->row;
|
|
|
|
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 (row));
|
|
}
|
|
else if (op->type & DB_MODEL_ROW_OP_INSERT) // INSERT
|
|
{ // Catch the SELECT associated with each INSERT
|
|
|
|
result = db_request_fetch_result (request, &err);
|
|
|
|
if (result)
|
|
{
|
|
if (result->data && result->nrows > 0)
|
|
{
|
|
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, priv->main_table))
|
|
{
|
|
DbIter iter;
|
|
db_model_finish_insert (obj, req_row, i, row, j, &iter);
|
|
}
|
|
}
|
|
|
|
db_result_free (result);
|
|
}
|
|
}
|
|
else if (op->type & DB_MODEL_ROW_OP_UPDATE)// UPDATE
|
|
if (priv->join)
|
|
{
|
|
GSList * n;
|
|
DbUpdatedField * u;
|
|
DbIter iter;
|
|
iter.stamp = priv->stamp;
|
|
iter.data = row;
|
|
|
|
for (n = op->updated; n; n = n->next)
|
|
{
|
|
u = n->data;
|
|
|
|
/* if (r->next && r->next->data
|
|
&& ((DbResult *) r->next->data)->column)*/
|
|
if (priv->column_default
|
|
&& gvn_value_is_null (u->value)
|
|
&& g_strcmp0 (priv->column[u->column].table, priv->main_table)
|
|
&& db_model_table_row_all_null (obj, row, u->column))
|
|
{ // INSERT + SELECT "update"
|
|
|
|
result = db_request_fetch_result (request, &err);
|
|
|
|
if (result)
|
|
{
|
|
if (result->data && result->nrows > 0)
|
|
{
|
|
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)
|
|
|| !result->column[i].table))
|
|
db_model_finish_insert (obj, req_row, i, row, j, &iter);
|
|
}
|
|
|
|
db_result_free (result);
|
|
}
|
|
}
|
|
/*
|
|
if (n->next && (u = ((DbUpdatedField *) n->next->data))
|
|
&& priv->column_default
|
|
&& gvn_value_is_null (u->value)
|
|
&& g_strcmp0 (priv->column[u->column].table, priv->main_table)
|
|
&& db_model_table_row_all_null (obj, row, u->column))
|
|
r = r->next;
|
|
*/
|
|
}
|
|
}
|
|
|
|
db_model_free_operation (obj, op);
|
|
}
|
|
|
|
//XXX Iterate both the result list and the queue at the same time
|
|
// when the Request has its Results set on errors(future?).
|
|
// Currently, if something fails in the plugin's code, it returns NULL
|
|
|
|
if (err)
|
|
{
|
|
while ((op = g_queue_pop_head (data->operations)))
|
|
{
|
|
op->locked = FALSE;
|
|
g_queue_push_tail (priv->operation, op);
|
|
}
|
|
}
|
|
else
|
|
g_signal_emit (obj, db_model_signal[OPERATIONS_DONE], 0);
|
|
|
|
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_updated_field_free (DbUpdatedField * u)
|
|
{
|
|
if (u && u->value)
|
|
{
|
|
g_value_unset (u->value);
|
|
g_free (u->value);
|
|
}
|
|
|
|
g_free (u);
|
|
u = NULL;
|
|
}
|
|
|
|
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)
|
|
{
|
|
g_queue_free (req->operations);
|
|
g_object_unref (req->obj);
|
|
}
|
|
|
|
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)))
|
|
{
|
|
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
|
|
{
|
|
DbIter iter;
|
|
iter.data = op->row;
|
|
iter.stamp = priv->stamp;
|
|
|
|
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;
|
|
DbUpdatedField * u;
|
|
|
|
for (n = op->updated; n; n = n->next)
|
|
{
|
|
DbIter iter;
|
|
iter.stamp = priv->stamp;
|
|
iter.data = op->row;
|
|
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, NULL);
|
|
SqlObject * stmt = sql_parser_parse (rend);
|
|
/*gchar * rend2 = NULL;
|
|
if (stmt && (rend2 = db_conn_render (obj->priv->conn, stmt, NULL)))
|
|
g_message ("SQL + Parser + Render =\n %s", rend2);
|
|
g_free (rend2);*/
|
|
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;
|
|
operators = sql_list_get_items (subop->operators);
|
|
|
|
if (SQL_IS_OPERATION (subop = l->data)
|
|
&& 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;
|
|
}
|
|
|
|
static DbModelPKey * db_model_get_primary_key (DbModel *obj, gchar * table)
|
|
{
|
|
gint i, j;
|
|
DbModelPKey * pkey = g_new (DbModelPKey, 1);
|
|
pkey->count = 0;
|
|
// Get number of pkeys
|
|
for (i = 0; i < obj->priv->result->ncols; i++)
|
|
if ((obj->priv->column[i].info & DB_COLUMN_PRI_KEY)
|
|
&& !g_strcmp0 (obj->priv->column[i].table, table))
|
|
pkey->count++;
|
|
|
|
pkey->index = g_new (gint, pkey->count);
|
|
// Get pkey/s
|
|
for (i = 0; i < pkey->count; i++)
|
|
for (j = 0; j < obj->priv->result->ncols; j++)
|
|
if (!g_strcmp0 (obj->priv->column[j].table, table)
|
|
&& (obj->priv->column[j].info & DB_COLUMN_PRI_KEY)
|
|
&& !stored (pkey->index, i+1, j))
|
|
{
|
|
pkey->index[i] = j;
|
|
break;
|
|
}
|
|
|
|
return pkey;
|
|
}
|
|
|
|
static void db_model_pkey_free (DbModelPKey * pkey)
|
|
{
|
|
g_free (pkey->index);
|
|
g_free (pkey);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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));
|
|
|
|
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, "exprs",
|
|
sql_field_new (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 (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)
|
|
,(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_finish_insert (DbModel * obj,
|
|
DbRow * req_row, gint i, DbRow * row, gint j, DbIter * iter)
|
|
{
|
|
GValue * v;
|
|
gboolean emit = TRUE;
|
|
iter->stamp = obj->priv->stamp;
|
|
iter->data = row;
|
|
|
|
obj->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 (obj->priv->updated_value, G_VALUE_TYPE (v));
|
|
gvn_value_copy (v, obj->priv->updated_value);
|
|
}
|
|
else if (gvn_value_is_null (DB_ROW_FIELD (row, j)))
|
|
{
|
|
g_value_init (obj->priv->updated_value, GVN_TYPE_NULL);
|
|
}
|
|
else
|
|
{
|
|
emit = FALSE;
|
|
g_free (obj->priv->updated_value);
|
|
}
|
|
|
|
if (emit)
|
|
{
|
|
obj->priv->updated_col = j;
|
|
g_signal_emit (obj, db_model_signal[LINE_UPDATED], 0, iter);
|
|
}
|
|
}
|
|
|
|
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 ();
|
|
}
|
|
}
|
|
|
|
static gboolean db_model_table_row_all_null (DbModel * obj, DbRow * row, gint col)
|
|
{
|
|
DbUpdatedField * u;
|
|
GSList * n;
|
|
gboolean updated;
|
|
gint i;
|
|
|
|
for (i = 0; i < row->len; i++)
|
|
{
|
|
updated = FALSE;
|
|
|
|
if (!g_strcmp0 (obj->priv->column[col].table, obj->priv->column[i].table)
|
|
&& i != col)
|
|
{
|
|
DbOperation * operation = g_hash_table_lookup (obj->priv->row_ops, row);
|
|
|
|
if (operation)
|
|
for (n = operation->updated; n; n = n->next)
|
|
if ((u = (DbUpdatedField *) n->data) && u->column == i)
|
|
{
|
|
updated = TRUE;
|
|
|
|
if (!gvn_value_is_null (u->value))
|
|
return FALSE;
|
|
}
|
|
|
|
if (!updated && !gvn_value_is_null (DB_ROW_FIELD (row, i)))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++ 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].display;
|
|
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_set_default_value_from_column:
|
|
* @obj: a #DbModel
|
|
* @dst_field: the field to be set
|
|
* @src_column: field from wich the value is picked
|
|
*
|
|
* TODO: cambiar src_column a string y almacenarlo, cuando se vaya a usar, cambiarlo
|
|
por el entero correspondiente
|
|
**/
|
|
void db_model_set_default_value_from_column (DbModel * obj,
|
|
const gchar * dst_field, gint src_column)
|
|
{
|
|
g_return_if_fail (DB_IS_MODEL (obj));
|
|
if (!obj->priv->result || 0 > src_column || src_column >= obj->priv->result->ncols)
|
|
return;
|
|
|
|
DbColDef * def = g_new (DbColDef, 1);
|
|
def->dst = g_strdup (dst_field);
|
|
def->col = src_column;
|
|
|
|
obj->priv->column_default = g_slist_prepend (obj->priv->column_default, def);
|
|
}
|
|
|
|
/**
|
|
* db_model_set_default_value_from_param:
|
|
* @obj: a #DbModel
|
|
* @dst_field: the field to be set
|
|
* @param: a #GvnParam
|
|
* @id: an identifier string for @param
|
|
*
|
|
* Get the default value for @dst_field from @param.
|
|
**/
|
|
void db_model_set_default_value_from_param (DbModel * obj,
|
|
const gchar * dst_field, GvnParam * param, const gchar * id)
|
|
{
|
|
SqlObject * op, * value;
|
|
|
|
g_return_if_fail (DB_IS_MODEL (obj));
|
|
g_return_if_fail (GVN_IS_PARAM (param));
|
|
g_return_if_fail (dst_field);
|
|
|
|
op = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
|
|
sql_object_add_child (op, "operators", sql_field_new (dst_field, NULL, NULL));
|
|
|
|
value = sql_value_new ();
|
|
sql_value_set_param (SQL_VALUE (value), param);
|
|
sql_object_add_child (op, "operators", value);
|
|
|
|
sql_object_add_held_object (SQL_OBJECT (obj->priv->stmt), id, op);
|
|
|
|
DbParamDef * def = g_new (DbParamDef, 1);
|
|
def->dst = g_strdup (dst_field);
|
|
def->param = g_object_ref_sink (param);
|
|
|
|
obj->priv->param_default = g_slist_prepend (obj->priv->param_default, def);
|
|
}
|
|
|
|
/**
|
|
* db_model_add_param:
|
|
* @obj: a #DbModel
|
|
* @id: an identifier string for @param
|
|
* @param: the #GvnParam to add
|
|
*
|
|
* Links the statement in the model to @param.
|
|
**/
|
|
void db_model_add_param (DbModel * obj, const gchar * id, GvnParam * param)
|
|
{
|
|
g_return_if_fail (DB_IS_MODEL (obj));
|
|
g_return_if_fail (GVN_IS_PARAM (param));
|
|
|
|
sql_string_add_param (SQL_STRING (obj->priv->stmt), id, param);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
return obj->priv->main_table;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
SqlString * 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++)
|
|
{
|
|
GSList * p;
|
|
const GValue * def = NULL;
|
|
|
|
for (p = priv->param_default; p; p = p->next)
|
|
{
|
|
DbParamDef * param_def = p->data;
|
|
|
|
if (!g_strcmp0 (param_def->dst, priv->column[i].name))
|
|
{
|
|
def = gvn_param_get_value (param_def->param);
|
|
g_value_init (&row->value[i], G_VALUE_TYPE (def));
|
|
gvn_value_copy (def, &row->value[i]);
|
|
}
|
|
}
|
|
|
|
if (!def)
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
DbModelPrivate * priv;
|
|
gboolean render_ops = FALSE, transaction = FALSE;
|
|
gint i;
|
|
DbRow * row;
|
|
DbOperation * op_elem;
|
|
GList * l = NULL;
|
|
GSList * sl = NULL;
|
|
SqlObject * multi = NULL,
|
|
* equal_op, * val = NULL,
|
|
* insert, * set,
|
|
* select, * where = NULL;
|
|
SqlList * stmts;
|
|
DbRequest * request;
|
|
GQueue * req_ops = NULL;
|
|
|
|
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;
|
|
|
|
if (priv->operation->length > 1)
|
|
transaction = TRUE;
|
|
|
|
stmts = sql_list_new (SQL_TYPE_STMT);
|
|
|
|
while ((op_elem = g_queue_pop_head (priv->operation)))
|
|
{
|
|
const GValue * def;
|
|
|
|
op_elem->locked = TRUE;
|
|
row = op_elem->row;
|
|
|
|
if (op_elem->type & DB_MODEL_ROW_OP_DELETE) // DELETE
|
|
{
|
|
if (op_elem->type & DB_MODEL_ROW_OP_INSERT)
|
|
{
|
|
db_model_free_operation (obj, op_elem);
|
|
op_elem = NULL;
|
|
g_signal_emit (obj,
|
|
db_model_signal[LINE_DELETED], 0, DB_ROW_POSITION (row));
|
|
|
|
if (!transaction)
|
|
{
|
|
g_object_unref (stmts);
|
|
stmts = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SqlObject * delete = sql_delete_new ();
|
|
sql_object_add_child (delete, "targets",
|
|
sql_table_new (priv->main_table, NULL));
|
|
|
|
where = sql_operation_new (SQL_OPERATION_TYPE_AND);
|
|
|
|
DbModelPKey * pkey =
|
|
db_model_get_primary_key (obj, priv->main_table);
|
|
|
|
for (i = 0; i < pkey->count; i++)
|
|
{
|
|
equal_op = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
|
|
|
|
sql_object_add_child (equal_op, "operators",
|
|
sql_field_new (priv->column[pkey->index[i]].name, NULL, NULL));
|
|
sql_object_add_child (equal_op, "operators",
|
|
sql_value_new_with_value (&row->value[pkey->index[i]]));
|
|
|
|
sql_object_add_child (where, "operators", equal_op);
|
|
}
|
|
|
|
db_model_pkey_free (pkey);
|
|
|
|
sql_object_set (delete, "where", where);
|
|
|
|
sql_list_add (stmts, delete);
|
|
render_ops = TRUE;
|
|
}
|
|
}
|
|
else if (op_elem->type & DB_MODEL_ROW_OP_INSERT) // INSERT
|
|
{
|
|
SqlObject * target = sql_table_new (priv->main_table);
|
|
|
|
insert = g_object_new (SQL_TYPE_INSERT, "table", target, NULL);
|
|
set = g_object_new (SQL_TYPE_SET, NULL);
|
|
sql_object_add_child (insert, "values", set);
|
|
|
|
select = sql_select_new ();
|
|
sql_object_add_child (select, "targets", target);
|
|
where = sql_operation_new (SQL_OPERATION_TYPE_AND);
|
|
|
|
for (i = 0; i < row->len; i++)
|
|
if (!g_strcmp0 (priv->column[i].table, priv->main_table))
|
|
{
|
|
gboolean cont = FALSE;
|
|
SqlObject * field = NULL;
|
|
|
|
l = sql_list_get_items (SQL_INSERT (insert)->fields);
|
|
|
|
for (; l; l = l->next)
|
|
if (!g_strcmp0 (((SqlField*) l->data)->name, priv->column[i].name)
|
|
&& ((!((SqlField*) l->data)->target)
|
|
|| !g_strcmp0 (((SqlField*) l->data)->target, priv->column[i].table)))
|
|
{
|
|
cont = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (cont)
|
|
continue;
|
|
|
|
val = sql_value_new_with_value (&row->value[i]);
|
|
|
|
sql_object_add_child (insert, "fields",
|
|
sql_field_new (priv->column[i].name, NULL, NULL));
|
|
sql_object_add_child (set,
|
|
"exprs", gvn_value_is_null (&row->value[i]) ? NULL : val);
|
|
|
|
sql_object_add_child (select, "exprs",
|
|
sql_field_new (priv->column[i].name,
|
|
priv->column[i].table, NULL));
|
|
|
|
// Set the filter conditions
|
|
if (priv->column[i].info & DB_COLUMN_PRI_KEY)
|
|
{
|
|
gboolean unset = FALSE;
|
|
GValue value = {0};
|
|
SqlObject * eq_value;
|
|
|
|
equal_op = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
|
|
sql_object_add_child (equal_op, "operators", field);
|
|
|
|
def = gvn_param_spec_get_default (priv->column[i].spec);
|
|
|
|
if (!gvn_value_is_null (&row->value[i]))
|
|
{
|
|
eq_value = val;
|
|
}
|
|
else if (def && G_IS_VALUE (def))
|
|
{
|
|
if (G_VALUE_TYPE (def) == SQL_TYPE_FUNCTION)
|
|
eq_value = g_value_get_object (def);
|
|
else
|
|
eq_value = sql_value_new_with_value (def);
|
|
}
|
|
else
|
|
{
|
|
gvn_value_set_null (&value);
|
|
eq_value = sql_value_new_with_value (&value);
|
|
unset = TRUE;
|
|
}
|
|
|
|
sql_object_add_child (equal_op, "operators", eq_value);
|
|
|
|
if (unset)
|
|
g_value_unset (&value);
|
|
|
|
sql_object_add_child (where, "operators", equal_op);
|
|
}
|
|
}
|
|
|
|
sql_object_set (select, "where", where);
|
|
|
|
for (sl = priv->param_default; sl; sl = sl->next)
|
|
{
|
|
gboolean cont = FALSE;
|
|
DbParamDef * param_def = sl->data;
|
|
|
|
l = sql_list_get_items (SQL_INSERT (insert)->fields);
|
|
|
|
for (; l; l = l->next)
|
|
if (!g_strcmp0 (((SqlField*) l->data)->name, param_def->dst))
|
|
{
|
|
cont = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (cont) continue;
|
|
|
|
val = sql_value_new_with_value
|
|
(gvn_param_get_value (param_def->param));
|
|
|
|
sql_object_add_child (insert, "fields",
|
|
sql_field_new (param_def->dst, NULL, NULL));
|
|
sql_object_add_child (set, "exprs", val);
|
|
}
|
|
|
|
sql_list_add (stmts, insert);
|
|
sql_list_add (stmts, select);
|
|
render_ops = TRUE;
|
|
}
|
|
else if (op_elem->type & DB_MODEL_ROW_OP_UPDATE) // UPDATE
|
|
{
|
|
// Depending on the field, generate an UPDATE or INSERT+SELECT
|
|
GSList * m;
|
|
GValue * new_value;
|
|
SqlObject * update = NULL;
|
|
GSList * prev_tables = NULL;
|
|
GSList * prev_updates = NULL;
|
|
gboolean insert_set = FALSE;
|
|
select = NULL;
|
|
|
|
for (m = op_elem->updated; m; m = m->next)
|
|
{
|
|
DbUpdatedField * u = m->data;
|
|
i = u->column;
|
|
|
|
new_value = DB_ROW_FIELD (row, i);
|
|
|
|
if (!priv->column_default
|
|
|| !gvn_value_is_null (u->value)
|
|
|| !g_strcmp0 (priv->column[i].table, priv->main_table)
|
|
|| !db_model_table_row_all_null (obj, row, i))
|
|
{
|
|
gboolean is_added = NULL != g_slist_find_custom (prev_tables,
|
|
priv->column[i].table, (GCompareFunc) g_strcmp0);
|
|
|
|
if (!is_added)
|
|
{
|
|
where = sql_operation_new (SQL_OPERATION_TYPE_AND);
|
|
update = g_object_new (SQL_TYPE_UPDATE,
|
|
"where", where, NULL);
|
|
}
|
|
else
|
|
{
|
|
GSList * l;
|
|
SqlTable * t;
|
|
|
|
for (l = prev_updates; l; l = l->next)
|
|
if (SQL_IS_UPDATE (l->data)
|
|
&& SQL_IS_TABLE (t = sql_list_get_items (SQL_DML (l->data)->targets)->data)
|
|
&& !g_strcmp0 (SQL_TABLE (t)->name, priv->column[i].table))
|
|
{
|
|
update = l->data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
sql_object_add_child (update, "sets",
|
|
g_object_new (SQL_TYPE_UPDATE_SET,
|
|
"field", sql_field_new (priv->column[i].name, NULL, NULL),
|
|
"expr", sql_value_new_with_value (new_value),
|
|
NULL));
|
|
|
|
if (!is_added)
|
|
{
|
|
guint j;
|
|
DbModelPKey * pkey =
|
|
db_model_get_primary_key (obj, priv->column[i].table);
|
|
|
|
sql_object_add_child (update, "targets",
|
|
sql_table_new (priv->column[i].table));
|
|
|
|
for (j = 0; j < pkey->count; j++)
|
|
{
|
|
gint key_col = pkey->index[j];
|
|
GValue * primary = NULL;
|
|
GSList * n;
|
|
|
|
if (key_col == i)
|
|
primary = u->value;
|
|
else
|
|
{
|
|
for (n = op_elem->updated; n; n = n->next)
|
|
if (((DbUpdatedField *)n->data)->column == key_col)
|
|
break;
|
|
|
|
if (n)
|
|
primary = ((DbUpdatedField *) n->data)->value;
|
|
else
|
|
primary = DB_ROW_FIELD (row, key_col);
|
|
}
|
|
|
|
equal_op = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
|
|
|
|
sql_object_add_child (equal_op, "operators",
|
|
sql_field_new (priv->column[key_col].name,
|
|
priv->column[key_col].table, NULL));
|
|
|
|
val = sql_value_new_with_value (primary);
|
|
sql_object_add_child (equal_op, "operators", val);
|
|
|
|
if (where)
|
|
sql_object_add_child (where, "operators", equal_op);
|
|
}
|
|
|
|
sql_object_set (update, "where", where);
|
|
|
|
sql_list_add (stmts, update);
|
|
db_model_pkey_free (pkey);
|
|
}
|
|
|
|
prev_tables = g_slist_prepend (prev_tables, priv->column[i].table);
|
|
prev_updates = g_slist_prepend (prev_updates, update);
|
|
}
|
|
else
|
|
{
|
|
GSList * n;
|
|
|
|
if (!insert)
|
|
{
|
|
insert = sql_insert_new ();
|
|
select = sql_select_new ();
|
|
}
|
|
|
|
if (!SQL_INSERT (insert)->table)
|
|
{
|
|
SqlObject * table = sql_table_new (priv->column[i].table);
|
|
sql_object_set (insert, "table", table);
|
|
sql_object_add_child (select, "targets", table);
|
|
|
|
set = g_object_new (SQL_TYPE_SET, NULL);
|
|
sql_object_add_child (insert, "values", set);
|
|
|
|
insert_set = TRUE;
|
|
|
|
for (n = priv->column_default; n; n = n->next)
|
|
{
|
|
gchar * dst = ((DbColDef *) n->data)->dst;
|
|
gint col_def = ((DbColDef *) n->data)->col;
|
|
|
|
if (!g_strcmp0 (priv->column[i].table,
|
|
priv->column[col_def].table))
|
|
continue;
|
|
|
|
sql_object_add_child (insert, "fields",
|
|
sql_field_new (dst, NULL, NULL));
|
|
|
|
sql_object_add_child (set, "exprs"
|
|
,sql_value_new_with_value (DB_ROW_FIELD (row, col_def)));
|
|
}
|
|
}
|
|
|
|
if (insert_set)
|
|
{
|
|
val = sql_value_new_with_value (new_value);
|
|
|
|
sql_object_add_child (insert, "fields",
|
|
sql_field_new (priv->column[i].name, NULL, NULL));
|
|
sql_object_add_child (set, "exprs", val);
|
|
}
|
|
}
|
|
}
|
|
|
|
g_slist_free (prev_tables);
|
|
g_slist_free (prev_updates);
|
|
|
|
if (insert)
|
|
{
|
|
where = sql_operation_new (SQL_OPERATION_TYPE_AND);
|
|
|
|
for (i = 0; i < row->len; i++)
|
|
if (!g_strcmp0 (SQL_TABLE (sql_list_get_items (SQL_DML (select)->targets)->data)->name
|
|
,priv->column[i].table))
|
|
{
|
|
SqlObject * field = SQL_OBJECT
|
|
(sql_field_new (priv->column[i].name, NULL, NULL));
|
|
sql_object_add_child (select, "fields", field);
|
|
|
|
if (priv->column[i].info & DB_COLUMN_PRI_KEY)
|
|
{
|
|
def = gvn_param_spec_get_default (priv->column[i].spec);
|
|
|
|
if (!gvn_value_is_null (&row->value[i])
|
|
|| (def && G_VALUE_TYPE (def) == SQL_TYPE_FUNCTION))
|
|
{
|
|
equal_op = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
|
|
|
|
sql_object_add_child (equal_op, "operators", field);
|
|
|
|
if (gvn_value_is_null (&row->value[i]))
|
|
{
|
|
if (def && G_VALUE_TYPE (def) == SQL_TYPE_FUNCTION)
|
|
sql_object_add_child (equal_op, "operators",
|
|
g_value_get_object (def));
|
|
}
|
|
else
|
|
sql_object_add_child (equal_op, "operators", val);
|
|
|
|
sql_object_add_child (where, "operators", equal_op);
|
|
}
|
|
}
|
|
}
|
|
|
|
sql_object_set (select, "where", where);
|
|
|
|
sql_list_add (stmts, insert);
|
|
sql_list_add (stmts, select);
|
|
}
|
|
|
|
render_ops = TRUE;
|
|
}
|
|
|
|
if (op_elem)
|
|
{
|
|
if (!req_ops)
|
|
req_ops = g_queue_new ();
|
|
|
|
g_queue_push_tail (req_ops, op_elem);
|
|
}
|
|
}
|
|
|
|
if (render_ops)
|
|
{
|
|
if (multi)
|
|
{
|
|
DbModelRequest * data = g_new (DbModelRequest, 1);
|
|
data->obj = g_object_ref (obj);
|
|
data->operations = req_ops;
|
|
multi = g_object_new (SQL_TYPE_MULTI_STMT, "stmts", stmts, NULL);
|
|
|
|
request = db_conn_query_with_stmt_async (priv->conn
|
|
,SQL_STMT (multi)
|
|
,(DbRequestDoneCallback) db_model_on_operations_done
|
|
,data
|
|
,(GDestroyNotify) db_model_request_free
|
|
);
|
|
db_model_add_pending_request (obj, request);
|
|
}
|
|
else
|
|
{
|
|
g_queue_free (req_ops);
|
|
db_model_clean_operations (obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
g_return_if_fail (DB_IS_MODEL (obj));
|
|
priv = obj->priv;
|
|
g_return_if_fail (priv->stmt);
|
|
|
|
db_model_clear (obj);
|
|
db_model_set_status (obj, DB_MODEL_STATUS_LOADING);
|
|
|
|
priv->request = db_conn_query_with_stmt_async (priv->conn
|
|
,priv->stmt
|
|
,(DbRequestDoneCallback) db_model_on_data_ready
|
|
,g_object_ref (obj)
|
|
,(GDestroyNotify) g_object_unref
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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, obj->priv->main_table);
|
|
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)
|
|
{
|
|
obj->priv = G_TYPE_INSTANCE_GET_PRIVATE (obj, DB_TYPE_MODEL, DbModelPrivate);
|
|
|
|
obj->priv->conn = NULL;
|
|
obj->priv->stmt = NULL;
|
|
obj->priv->use_file = FALSE;
|
|
obj->priv->sql = NULL;
|
|
obj->priv->main_table = NULL;
|
|
obj->priv->update_flags = 0;
|
|
obj->priv->user_main_table = NULL;
|
|
obj->priv->user_update_flags = DB_MODEL_ALL;
|
|
obj->priv->request = NULL;
|
|
obj->priv->status = DB_MODEL_STATUS_CLEAN;
|
|
obj->priv->mode = DB_MODEL_MODE_ON_CHANGE;
|
|
obj->priv->result = NULL;
|
|
obj->priv->result_pos = 0;
|
|
obj->priv->data = NULL;
|
|
obj->priv->column = NULL;
|
|
obj->priv->operation = g_queue_new ();
|
|
obj->priv->row_ops = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
obj->priv->column_index = NULL;
|
|
obj->priv->join = NULL;
|
|
obj->priv->column_default = NULL;
|
|
obj->priv->param_default = NULL;
|
|
obj->priv->pending_request = NULL;
|
|
|
|
obj->priv->stamp = g_random_int ();
|
|
|
|
obj->priv->fresh = TRUE;
|
|
obj->priv->sort_column_id = DB_MODEL_UNSORTED_SORT_COLUMN_ID;
|
|
obj->priv->default_sort_data = NULL;
|
|
obj->priv->default_sort_func = NULL;
|
|
obj->priv->default_sort_destroy = NULL;
|
|
}
|
|
|
|
static void db_model_finalize (DbModel * obj)
|
|
{
|
|
GSList * n;
|
|
GObjectClass * parent;
|
|
parent = g_type_class_peek_parent (DB_MODEL_GET_CLASS (obj));
|
|
|
|
db_model_clear (obj);
|
|
|
|
g_clear_object (&obj->priv->conn);
|
|
g_clear_object (&obj->priv->stmt);
|
|
|
|
if (obj->priv->join)
|
|
g_slist_free_full (obj->priv->join, (GDestroyNotify) db_join_free);
|
|
|
|
g_hash_table_destroy (obj->priv->row_ops);
|
|
g_queue_free (obj->priv->operation);
|
|
|
|
g_free (obj->priv->sql);
|
|
g_free (obj->priv->main_table);
|
|
g_free (obj->priv->user_main_table);
|
|
|
|
if (obj->priv->column_index)
|
|
g_hash_table_destroy (obj->priv->column_index);
|
|
|
|
for (n = obj->priv->column_default; n; n = n->next)
|
|
{
|
|
g_free (((DbColDef *) n->data)->dst);
|
|
g_free (n->data);
|
|
}
|
|
|
|
g_slist_free (obj->priv->column_default);
|
|
|
|
for (n = obj->priv->param_default; n; n = n->next)
|
|
{
|
|
g_object_unref (((DbParamDef *) n->data)->param);
|
|
g_free (((DbParamDef *) n->data)->dst);
|
|
g_free (n->data);
|
|
}
|
|
|
|
g_slist_free (obj->priv->param_default);
|
|
|
|
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;
|
|
}
|