/* * Copyright (C) 2012 - Juan Ferrer Toribio * * This file is part of Hedera. * * Hedera is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "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: #DbConn, #DbModelHolder * * The #DbModel class gets an SQL query statement to retrieve the data from the * database connected by a #DbConn. It is normally used undirectly, using * a #DbIterator or another #DbModelHolder instead. **/ /* * 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; DbRow * null_row; 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; }; 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:(allow-none): a #DbConn * @stmt:(allow-none): an #SqlStmt * * Returns the newly created #DbModel, filled with the data retrieved from the * database with @stmt and through @conn. * * Return value: (transfer full): a new #DbModel **/ DbModel * db_model_new (DbConn * conn, SqlStmt * stmt) { return g_object_new (DB_TYPE_MODEL, "conn", conn, "stmt", stmt, NULL); } /** * db_model_new_with_sql: * @conn:(allow-none): a #DbConn * @sql: a string containing an SQL statement * * Returns the newly created #DbModel, filled with the data retrieved from the * database with @stmt and through @conn. * * Return value: (transfer full): a new #DbModel **/ DbModel * db_model_new_with_sql (DbConn * conn, const gchar * sql) { return g_object_new (DB_TYPE_MODEL, "conn", conn, "use-file", FALSE, "sql", sql, NULL); } /** * db_model_new_with_file: * @conn:(allow-none): a #DbConn * @file: the path to the file containing the SQL query to fill the model * * Returns a newly created #DbModel, filled with the data retrieved from @file. * * Return value: (transfer full): a new #DbModel **/ DbModel * db_model_new_with_file (DbConn * conn, const gchar * file) { return g_object_new (DB_TYPE_MODEL, "conn", conn, "use-file", TRUE, "sql", file, NULL); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Private // 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 { SqlField * dst; GvnParam * param; } DbParamDef; typedef struct { gint count; gint * index; } DbModelPKey; typedef struct { gint col; gint order; DbRow * null_row; } SortInfo; 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++) { //FIXME Set fields editable iff ALL primary keys are selected 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 (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); } } } } 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); 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); } 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>priv->stmt will be a parsed stmt: gchar * rend = db_conn_render (obj->priv->conn, obj->priv->stmt, NULL); SqlObject * stmt = sql_parser_parse (rend, NULL); //Uncomment to print de resulting stmt rendered: /*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; GSList * n; SqlSelect * select = SQL_SELECT (stmt); gboolean calculate_join = FALSE; for (n = SQL_DML (select)->target; 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 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) { GSList * l; for (l = op->expr; l; l = l->next) { SqlOperation * subop; if (SQL_IS_OPERATION (subop = l->data) && subop->type == SQL_OPERATION_TYPE_EQUAL && subop->expr->data // Left Field && subop->expr->next && subop->expr->next->data) // Right Field { lsql_field = SQL_FIELD (subop->expr->data); rsql_field = SQL_FIELD (subop->expr->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 if (op->type == SQL_OPERATION_TYPE_EQUAL && op->expr->data && op->expr->next && op->expr->next->data) { lsql_field = SQL_FIELD (op->expr->data); rsql_field = SQL_FIELD (op->expr->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); gint res = g_utf8_collate (a_str, b_str); g_free (a_str); g_free (b_str); return res; } default: if (a_type == G_TYPE_DATE) return g_date_compare (g_value_get_boxed (a), g_value_get_boxed (b)); if (a_type == G_TYPE_DATE_TIME) return g_date_time_compare (g_value_get_boxed (a), g_value_get_boxed (b)); else if (a_type == G_TYPE_BYTES) return (gint) (g_value_get_boxed (a) - g_value_get_boxed (b)); else if (a_type == GVN_TYPE_NULL) return 0; else g_warning ("Attempting to compare invalid types: %s\n", g_type_name (a_type)); } } else if (gvn_value_is_null (a)) return -1; else if (gvn_value_is_null (b)) return 1; return 1; } static inline gint db_model_valcmp (gpointer * a, gpointer * b, SortInfo * info) { gint order; DbRow * first = *a; DbRow * second = *b; if (first == info->null_row) order = -1; else if (second == info->null_row) order = 1; else { gint dir = info->order == DB_SORT_ASCENDING ? 1 : -1; order = dir * db_model_value_compare0 (&first->value[info->col] ,&second->value[info->col]); } return order; } static void db_model_set_status (DbModel * 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))))XXX WHY WAS THIS HERE? it IS tested in set_value { gint i; GSList * n; gboolean send_request = FALSE, end = FALSE; SqlMultiStmt * multi_stmt = sql_multi_stmt_new (); /*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; SqlSelect * select; SqlOperation * where; DbModelField * main_field = NULL, * other_field = NULL; DbJoin * join = (DbJoin *) 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_dml_add_target (SQL_DML (select), 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_select_add_expr (select, 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))) { SqlOperation * equal = sql_operation_new (SQL_OPERATION_TYPE_EQUAL); sql_operation_add_expr (equal, sql_field_new (g_ptr_array_index (other_field->name, j) ,other_field->table, NULL )); sql_operation_add_expr (equal, sql_value_new_with_value (DB_ROW_FIELD (iter->data, i))); sql_operation_add_expr (where, SQL_EXPR (equal)); } sql_dml_set_where (SQL_DML (select), SQL_EXPR (where)); sql_multi_stmt_add_stmt (multi_stmt, SQL_STMT (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 ,SQL_STMT (multi_stmt) ,(DbRequestDoneCallback) db_model_on_join_query_done ,join_data ,(GDestroyNotify) join_data_free ); db_model_add_pending_request (obj, request); } g_object_unref (multi_stmt); } } 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; priv->null_row = 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) { DbModelPrivate * priv; g_return_if_fail (DB_IS_MODEL (obj)); g_return_if_fail (DB_IS_CONN (conn) || !conn); priv = obj->priv; if (conn) { if (!priv->conn) { priv->conn = g_object_ref (conn); if (priv->use_file && priv->sql) priv->stmt = SQL_STMT (db_conn_create_stmt_from_file (conn, priv->sql)); db_model_on_stmt_changed (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_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 = NULL; 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) { if (obj->priv->conn) 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_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_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_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 * * **/ void db_model_set_default_value_from_param (DbModel * obj, const gchar * dst_field, GvnParam * param) { SqlExpr * value; SqlField * field; SqlOperation * op; 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); field = sql_parser_parse_field (dst_field); sql_operation_add_expr (op, SQL_EXPR (field)); value = sql_value_new (); sql_value_set_param (SQL_VALUE (value), param); sql_operation_add_expr (op, value); sql_string_add_expr (SQL_STRING (obj->priv->stmt), SQL_EXPR (op)); DbParamDef * def = g_new (DbParamDef, 1); def->dst = g_object_ref_sink (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 * @param: the #GvnParam to add * * Links the statement in the model to @param. **/ void db_model_add_param (DbModel * obj, 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), 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: * @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 = G_VALUE_INIT; va_list va; g_return_if_fail (DB_IS_MODEL (obj)); g_return_if_fail (obj->priv->result); g_return_if_fail (iter != NULL); if (iter->data == obj->priv->null_row) return; 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 return location for 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 { GValue new_value = G_VALUE_INIT; 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; g_return_val_if_fail (0 <= col && col < priv->result->ncols, FALSE); if (iter->data == obj->priv->null_row) { return FALSE; } 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->name, 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) { def = gvn_param_spec_get_default (priv->column[i].spec); if (def && G_VALUE_TYPE (def) != SQL_TYPE_FUNCTION) { g_value_init (&row->value[i], G_VALUE_TYPE (def)); gvn_value_copy (def, &row->value[i]); } else g_value_init (&row->value[i], GVN_TYPE_NULL); } } g_ptr_array_add (priv->data, row); iter->data = g_ptr_array_index (priv->data, (guint) priv->data->len - 1); iter->stamp = priv->stamp; priv->result->nrows++; db_model_set_row_operation (obj, iter->data, DB_MODEL_ROW_OP_INSERT, 0); g_signal_emit (obj, db_model_signal[LINE_INSERTED], 0, iter); return TRUE; } /** * db_model_delete: * @obj: a #DbModel * @iter: a #DbIter pointing to the row to be deleted * * Deletes the row pointed to by @iter on @obj, as well as on the database (if * it was already there, i.e. it's not a new row). * If the deletion on the database fails, then the row on @obj is not deleted * and a log message is emitted. **/ void db_model_delete (DbModel * obj, DbIter * iter) { g_return_if_fail (DB_IS_MODEL (obj)); g_return_if_fail (VALID_ITER (iter, obj)); if (MODEL_NOT_READY (obj)) { g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN) ,G_LOG_LEVEL_WARNING, "Model not ready"); return; } if (!(obj->priv->update_flags & DB_MODEL_DELETE)) { g_log (g_quark_to_string (DB_MODEL_LOG_DOMAIN) ,G_LOG_LEVEL_WARNING, "Can't delete from this Model"); return; } if (iter->data == obj->priv->null_row) 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_use_null_row: * @obj: a #DbModel * @use: wether to use a null row * * Sets wether to use a null row at the end of the model. This row can't be * modified by the methods @db_model_delete or @db_model_set_value, the only way * to remove it is to call @db_model_use_null_row again passing #FALSE. **/ void db_model_use_null_row (DbModel * obj, gboolean use) { DbModelPrivate * priv; g_return_if_fail (DB_IS_MODEL (obj)); priv = obj->priv; if (MODEL_NOT_READY(obj) || !priv->result || ((priv->null_row != NULL) == use)) return; if (use) { if (!priv->null_row) { gint i; DbIter iter; DbModelPrivate * priv = obj->priv; DbRow * curr, * row = db_row_new (priv->result->ncols, 0); gpointer * data = priv->data->pdata; for (i = 0; i < row->len; i++) g_value_init (&row->value[i], GVN_TYPE_NULL); priv->null_row = row; iter.data = row; iter.stamp = priv->stamp; priv->result->nrows++; g_ptr_array_set_size (priv->data, priv->result->nrows); curr = data[0]; data[0] = row; for (i = 1; i < priv->result->nrows; i++) { DbRow * next = data[i]; DB_ROW_POSITION (curr)++; data[i] = curr; curr = next; } g_signal_emit (obj, db_model_signal[LINE_INSERTED], 0, &iter); } } else if (priv->null_row) { g_signal_emit (obj, db_model_signal[LINE_DELETED], 0, DB_ROW_POSITION (priv->null_row)); priv->null_row = NULL; } } /** * 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; SortInfo info; 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); info.col = col; info.null_row = priv->null_row; info.order = order; g_ptr_array_sort_with_data (priv->data ,(GCompareDataFunc) db_model_valcmp ,&info); for (r_ind = 0; r_ind < priv->result->nrows; r_ind++) { row_iter = g_ptr_array_index (priv->data, r_ind); new_order[i] = DB_ROW_POSITION (row_iter); DB_ROW_POSITION (row_iter) = i++; } g_signal_emit (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 = G_VALUE_INIT; 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; GSList * sl = NULL; SqlOperation * op = NULL, * equal_op; SqlExpr * val = NULL; SqlSelect * select; SqlMultiStmt * multi; 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; multi = sql_multi_stmt_new (); 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 (multi); multi = NULL; } } else { SqlDelete * delete = sql_delete_new (); sql_dml_add_target (SQL_DML (delete), sql_table_new (priv->main_table)); op = 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 (sql_operation_new (SQL_OPERATION_TYPE_EQUAL)); sql_operation_add_expr (equal_op, sql_field_new (priv->column[pkey->index[i]].name, NULL, NULL)); sql_operation_add_expr (equal_op, sql_value_new_with_value (&row->value[pkey->index[i]])); sql_operation_add_expr (op, SQL_EXPR (equal_op)); } db_model_pkey_free (pkey); sql_dml_set_where (SQL_DML (delete), SQL_EXPR (op)); sql_multi_stmt_add_stmt (multi, SQL_STMT (delete)); render_ops = TRUE; } } else if (op_elem->type & DB_MODEL_ROW_OP_INSERT) // INSERT { SqlInsert * insert; SqlTarget * table = sql_table_new (priv->main_table); insert = sql_insert_new (); sql_insert_add_row (insert); sql_insert_set_table (insert, SQL_TABLE (table)); select = sql_select_new (); sql_dml_add_target (SQL_DML (select), table); op = 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; SqlExpr * field; for (sl = insert->field; sl; sl = sl->next) if (!g_strcmp0 (((SqlField*) sl->data)->name, priv->column[i].name) && ((!((SqlField*) sl->data)->target) || !g_strcmp0 (((SqlField*) sl->data)->target, priv->column[i].table))) { cont = TRUE; break; } if (cont) continue; field = sql_field_new (priv->column[i].name, NULL, NULL); sql_insert_add_field (insert, SQL_FIELD (field)); val = sql_value_new_with_value (&row->value[i]); sql_insert_add_expr (insert, gvn_value_is_null (&row->value[i]) ? NULL : val); sql_select_add_expr (select, 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 = G_VALUE_INIT; SqlExpr * eq_value; equal_op = SQL_OPERATION (sql_operation_new (SQL_OPERATION_TYPE_EQUAL)); sql_operation_add_expr (equal_op, 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 = SQL_EXPR (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_operation_add_expr (equal_op, eq_value); if (unset) g_value_unset (&value); sql_operation_add_expr (op, SQL_EXPR (equal_op)); } } sql_dml_set_where (SQL_DML (select), SQL_EXPR(op)); for (sl = priv->param_default; sl; sl = sl->next) { gboolean cont = FALSE; GSList * m; DbParamDef * param_def = sl->data; for (m = insert->field; m; m = m->next) if (!g_strcmp0 (((SqlField*) m->data)->name, param_def->dst->name)) { cont = TRUE; break; } if (cont) continue; val = sql_value_new_with_value (gvn_param_get_value (param_def->param)); sql_insert_add_field (insert, param_def->dst); sql_insert_add_expr (insert, val); } sql_multi_stmt_add_stmt (multi, SQL_STMT (insert)); sql_multi_stmt_add_stmt (multi, SQL_STMT (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; SqlUpdate * update = NULL; SqlInsert * insert = 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) { update = sql_update_new (); op = sql_operation_new (SQL_OPERATION_TYPE_AND); } else { GSList * l; SqlTable * t; for (l = prev_updates; l; l = l->next) if (SQL_IS_UPDATE (l->data) && SQL_IS_TABLE (t = SQL_DML (l->data)->target->data) && !g_strcmp0 (SQL_TABLE (t)->name, priv->column[i].table)) { update = SQL_UPDATE (l->data); break; } } sql_update_add_set (update, SQL_FIELD (sql_field_new (priv->column[i].name, NULL, NULL)), sql_value_new_with_value (new_value)); if (!is_added) { guint j; DbModelPKey * pkey = db_model_get_primary_key (obj, priv->column[i].table); sql_dml_add_target (SQL_DML (update), 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_operation_add_expr (SQL_OPERATION (equal_op) ,sql_field_new (priv->column[key_col].name ,priv->column[key_col].table, NULL)); val = sql_value_new_with_value (primary); sql_operation_add_expr (equal_op, val); if (op) sql_operation_add_expr (op, SQL_EXPR (equal_op)); } sql_dml_set_where (SQL_DML (update), SQL_EXPR (op)); sql_multi_stmt_add_stmt (multi, SQL_STMT (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 (!insert->table) { SqlTarget * table = sql_table_new (priv->column[i].table); sql_insert_set_table (insert, SQL_TABLE (table)); sql_dml_add_target (SQL_DML (select), table); 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_insert_add_field (insert, SQL_FIELD (sql_field_new (dst, NULL, NULL))); sql_insert_add_expr (insert, sql_value_new_with_value (DB_ROW_FIELD (row, col_def))); } } if (insert_set) { val = sql_value_new_with_value (new_value); sql_insert_add_field (insert, SQL_FIELD (sql_field_new (priv->column[i].name, NULL, NULL))); sql_insert_add_expr (insert, val); } } } g_slist_free (prev_tables); g_slist_free (prev_updates); if (insert) { op = sql_operation_new (SQL_OPERATION_TYPE_AND); for (i = 0; i < row->len; i++) if (!g_strcmp0 (SQL_TABLE (SQL_DML (select)->target->data)->name ,priv->column[i].table)) { SqlExpr * field = SQL_EXPR (sql_field_new (priv->column[i].name, NULL, NULL)); sql_select_add_expr (select, 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_operation_add_expr (equal_op, field); if (gvn_value_is_null (&row->value[i])) { if (def && G_VALUE_TYPE (def) == SQL_TYPE_FUNCTION) sql_operation_add_expr (equal_op, SQL_EXPR (g_value_get_object (def))); } else sql_operation_add_expr (equal_op, val); sql_operation_add_expr (op, SQL_EXPR (equal_op)); } } } sql_dml_set_where (SQL_DML (select), SQL_EXPR (op)); sql_multi_stmt_add_stmt (multi, SQL_STMT (insert)); sql_multi_stmt_add_stmt (multi, SQL_STMT (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; 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 && VALID_ITER (iter, obj)); } // 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, 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; iter->stamp = obj->priv->stamp; iter->data = g_ptr_array_index (obj->priv->data, 0); 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) { DbModelPrivate * priv; g_return_val_if_fail (DB_IS_MODEL (obj), FALSE); priv = obj->priv; if (MODEL_NOT_READY(obj) || !priv->result || !priv->result->nrows) return FALSE; g_return_val_if_fail (VALID_ITER (iter, obj), FALSE); if ((iter->data = g_ptr_array_index (priv->data, (guint) DB_ROW_POSITION (iter->data) - 1))) return TRUE; return FALSE; } /** * db_model_iter_next: * @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) { gint pos; 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); 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_USE_FILE ,PROP_SQL ,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_USE_FILE: obj->priv->use_file = g_value_get_boolean (value); break; case PROP_SQL: db_model_set_sql (obj, g_value_get_string (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_USE_FILE: g_value_set_boolean (value, obj->priv->use_file); break; case PROP_SQL: g_value_set_string (value, obj->priv->sql); 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->null_row = NULL; obj->priv->stamp = g_random_int (); obj->priv->fresh = TRUE; obj->priv->sort_column_id = DB_MODEL_UNSORTED_SORT_COLUMN_ID; } static void db_model_finalize (DbModel * obj) { GSList * n; 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) { DbParamDef * pd = n->data; g_object_unref (pd->param); g_object_unref (pd->dst); g_free (pd); } g_slist_free (obj->priv->param_default); G_OBJECT_CLASS (db_model_parent_class)->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 on which the signal is emitted * @status: the current status of @model * * This signal is emitted every time the status of @model changes. */ db_model_signal[STATUS_CHANGED] = g_signal_new ("status-changed" ,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL ,g_cclosure_marshal_VOID__INT ,G_TYPE_NONE, 1, G_TYPE_INT ); /** * DbModel::line-inserted: * @model: the object on which the signal is emitted * @iter: a #DbIter * * This signal is emitted when a new row is inserted into @model. The inserted * row is pointed to by @iter. */ db_model_signal[LINE_INSERTED] = g_signal_new ("line-inserted" ,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL ,g_cclosure_marshal_VOID__BOXED ,G_TYPE_NONE, 1, DB_TYPE_ITER ); /** * DbModel::line-deleted: * @model: the object on which the signal is emitted * @position: the position of the deleted line * * Every time a row of @model is deleted, this signal is emitted. */ db_model_signal[LINE_DELETED] = g_signal_new_class_handler ("line-deleted" ,DB_TYPE_MODEL, G_SIGNAL_RUN_LAST, (GCallback) db_model_on_line_deleted ,NULL, NULL, g_cclosure_marshal_VOID__INT ,G_TYPE_NONE, 1, G_TYPE_INT ); /** * DbModel::line-toggled: * @model: the object on which the signal is emitted * @iter: a #DbIter * * Emitted to tell that the line is going to be deleted, * but the operation is still. */ db_model_signal[LINE_TOGGLED] = g_signal_new ("line-toggled" ,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0 ,NULL, NULL ,g_cclosure_marshal_VOID__BOXED ,G_TYPE_NONE, 1, DB_TYPE_ITER ); /** * DbModel::line-updated: * @model: the object on which the signal is emitted * @iter: a #DbIter * * This signal is emitted when any value in a row of @model is set. */ db_model_signal[LINE_UPDATED] = g_signal_new_class_handler ("line-updated" ,DB_TYPE_MODEL, G_SIGNAL_RUN_LAST, (GCallback) db_model_on_line_updated ,NULL, NULL, g_cclosure_marshal_VOID__BOXED ,G_TYPE_NONE, 1, DB_TYPE_ITER ); /** * DbModel::lines-reordered: * @model: the object on which the signal is emitted * @col: the sort column * @new_order: (array) (element-type gint): an array mapped with the new * positions over the old ones * * This signal is emitted by @model every time its rows are reordered. * * The @new_order array is an array of which the positions indexed after the * old order of the elements of @model, contain the new position of these * elements in the reordered @model. */ db_model_signal[LINES_REORDERED] = g_signal_new ("lines-reordered" ,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL ,g_cclosure_marshal_VOID__UINT_POINTER ,G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_POINTER ); /** * DbModel::sort-changed: * @model: the object on which the signal is emitted * * This signal is emitted when a new column is selected as sort criteria for * @model. */ db_model_signal[SORT_CHANGED] = g_signal_new ("sort-changed" ,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL ,g_cclosure_marshal_VOID__VOID ,G_TYPE_NONE, 0 ); /** * DbModel::operations-done: * @model: the object on which the signal is emitted * @success: whether the operation has failed or succeded * * When an operation on the model is performed on the DB, this signal * is emitted. */ db_model_signal[OPERATIONS_DONE] = g_signal_new ("operations-done" ,DB_TYPE_MODEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL ,g_cclosure_marshal_VOID__VOID ,G_TYPE_NONE, 0 ); g_object_class_install_property (klass, PROP_CONN, g_param_spec_object ("conn" ,_("Connection") ,_("The DbConn that manages the connection to the database") ,DB_TYPE_CONN ,G_PARAM_READWRITE )); g_object_class_install_property (klass, PROP_STMT, g_param_spec_object ("stmt" ,_("Statement") ,_("The statement which retrieves the data") ,SQL_TYPE_STMT ,G_PARAM_READWRITE )); g_object_class_install_property (klass, PROP_USE_FILE, g_param_spec_boolean ("use-file" ,_("Use file") ,_("If this is set to TRUE, the \"sql\" property will " "hold the name of a file containing a query, if " "set to FALSE, \"sql\" is used as an SQL string") ,FALSE ,G_PARAM_READWRITE )); g_object_class_install_property (klass, PROP_SQL, g_param_spec_string ("sql" ,_("SQL") ,_("Depending on the \"use-file\" property this will " "be the path to a file with queries for the " "model or a SQL string") ,NULL ,G_PARAM_READWRITE )); g_object_class_install_property (klass, PROP_MAIN_TABLE, g_param_spec_string ("main-table" ,_("Main Table") ,_("The main table of the model") ,NULL ,G_PARAM_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; }