1 /*
2 * Copyright (C) 2008 - 2011 Murray Cumming <murrayc@murrayc.com>
3 * Copyright (C) 2008 - 2011 Vivien Malerba <malerba@gnome-db.org>
4 * Copyright (C) 2009 Bas Driessen <bas.driessen@xobas.com>
5 * Copyright (C) 2010 David King <davidk@openismus.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23 #include <string.h>
24 #include <glib/gi18n-lib.h>
25 #include "gda-data-comparator.h"
26 #include "gda-marshal.h"
27 #include "gda-data-model.h"
28
29 /*
30 * Main static functions
31 */
32 static void gda_data_comparator_class_init (GdaDataComparatorClass * class);
33 static void gda_data_comparator_init (GdaDataComparator *srv);
34 static void gda_data_comparator_dispose (GObject *object);
35 static void gda_data_comparator_finalize (GObject *object);
36
37 static void gda_data_comparator_set_property (GObject *object,
38 guint param_id,
39 const GValue *value,
40 GParamSpec *pspec);
41 static void gda_data_comparator_get_property (GObject *object,
42 guint param_id,
43 GValue *value,
44 GParamSpec *pspec);
45 /* get a pointer to the parents to be able to call their destructor */
46 static GObjectClass *parent_class = NULL;
47
48 static void gda_diff_free (GdaDiff *diff);
49
50 /* signals */
51 enum
52 {
53 DIFF_COMPUTED,
54 LAST_SIGNAL
55 };
56
57 static gint gda_data_comparator_signals[LAST_SIGNAL] = { 0 };
58
59
60 /* properties */
61 enum
62 {
63 PROP_0,
64 PROP_OLD_MODEL,
65 PROP_NEW_MODEL
66 };
67
68 struct _GdaDataComparatorPrivate
69 {
70 GdaDataModel *old_model;
71 GdaDataModel *new_model;
72 gint nb_key_columns;
73 gint *key_columns;
74 GArray *diffs; /* array of GdaDiff pointers */
75 };
76
77
78 /* module error */
gda_data_comparator_error_quark(void)79 GQuark gda_data_comparator_error_quark (void)
80 {
81 static GQuark quark;
82 if (!quark)
83 quark = g_quark_from_static_string ("gda_data_comparator_error");
84 return quark;
85 }
86
87 GType
gda_data_comparator_get_type(void)88 gda_data_comparator_get_type (void)
89 {
90 static GType type = 0;
91
92 if (G_UNLIKELY (type == 0)) {
93 static GMutex registering;
94 static const GTypeInfo info = {
95 sizeof (GdaDataComparatorClass),
96 (GBaseInitFunc) NULL,
97 (GBaseFinalizeFunc) NULL,
98 (GClassInitFunc) gda_data_comparator_class_init,
99 NULL,
100 NULL,
101 sizeof (GdaDataComparator),
102 0,
103 (GInstanceInitFunc) gda_data_comparator_init,
104 0
105 };
106
107 g_mutex_lock (®istering);
108 if (type == 0)
109 type = g_type_register_static (G_TYPE_OBJECT, "GdaDataComparator", &info, 0);
110 g_mutex_unlock (®istering);
111 }
112 return type;
113 }
114
115 static gboolean
diff_computed_accumulator(G_GNUC_UNUSED GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,G_GNUC_UNUSED gpointer data)116 diff_computed_accumulator (G_GNUC_UNUSED GSignalInvocationHint *ihint,
117 GValue *return_accu,
118 const GValue *handler_return,
119 G_GNUC_UNUSED gpointer data)
120 {
121 gboolean thisvalue;
122
123 thisvalue = g_value_get_boolean (handler_return);
124 g_value_set_boolean (return_accu, thisvalue);
125
126 return !thisvalue; /* stop signal if 'thisvalue' is FALSE */
127 }
128
129 static gboolean
m_diff_computed(G_GNUC_UNUSED GdaDataComparator * comparator,G_GNUC_UNUSED GdaDiff * diff)130 m_diff_computed (G_GNUC_UNUSED GdaDataComparator *comparator, G_GNUC_UNUSED GdaDiff *diff)
131 {
132 return FALSE; /* default is to allow differences computing to proceed (understand it as: FALSE => don't stop) */
133 }
134
135 static void
gda_data_comparator_class_init(GdaDataComparatorClass * class)136 gda_data_comparator_class_init (GdaDataComparatorClass *class)
137 {
138 GObjectClass *object_class = G_OBJECT_CLASS (class);
139
140 parent_class = g_type_class_peek_parent (class);
141
142 /* signals */
143
144 gda_data_comparator_signals [DIFF_COMPUTED] =
145 g_signal_new ("diff-computed",
146 G_TYPE_FROM_CLASS (object_class),
147 G_SIGNAL_RUN_LAST,
148 G_STRUCT_OFFSET (GdaDataComparatorClass, diff_computed),
149 diff_computed_accumulator, NULL,
150 _gda_marshal_BOOLEAN__POINTER, G_TYPE_BOOLEAN, 1, G_TYPE_POINTER);
151
152 class->diff_computed = m_diff_computed;
153
154 /* virtual functions */
155 object_class->dispose = gda_data_comparator_dispose;
156 object_class->finalize = gda_data_comparator_finalize;
157
158 /* Properties */
159 object_class->set_property = gda_data_comparator_set_property;
160 object_class->get_property = gda_data_comparator_get_property;
161
162 g_object_class_install_property (object_class, PROP_OLD_MODEL,
163 g_param_spec_object ("old-model", _("Old data model"), NULL,
164 GDA_TYPE_DATA_MODEL,
165 (G_PARAM_READABLE | G_PARAM_WRITABLE)));
166 g_object_class_install_property (object_class, PROP_NEW_MODEL,
167 g_param_spec_object ("new-model", _("New data model"), NULL,
168 GDA_TYPE_DATA_MODEL,
169 (G_PARAM_READABLE | G_PARAM_WRITABLE)));
170 }
171
172 static void
gda_data_comparator_init(GdaDataComparator * comparator)173 gda_data_comparator_init (GdaDataComparator *comparator)
174 {
175 comparator->priv = g_new0 (GdaDataComparatorPrivate, 1);
176 comparator->priv->diffs = g_array_new (FALSE, FALSE, sizeof (GdaDiff *));
177 }
178
179 /**
180 * gda_data_comparator_new:
181 * @old_model: Data model to which the modifications should be applied
182 * @new_model: Target data model.
183 *
184 * Creates a new comparator to compute the differences from @old_model to @new_model: if one applies
185 * all the computed differences (as #GdaDiff structures) to @old_model, the resulting data model
186 * should have the same contents as @new_model.
187 *
188 * Returns: (type GdaDataComparator) (transfer full): a new #GdaDataComparator object
189 */
190 GObject *
gda_data_comparator_new(GdaDataModel * old_model,GdaDataModel * new_model)191 gda_data_comparator_new (GdaDataModel *old_model, GdaDataModel *new_model)
192 {
193 GObject *obj;
194
195 g_return_val_if_fail (GDA_IS_DATA_MODEL (old_model), NULL);
196 g_return_val_if_fail (GDA_IS_DATA_MODEL (new_model), NULL);
197
198 obj = g_object_new (GDA_TYPE_DATA_COMPARATOR, "old-model", old_model, "new-model", new_model, NULL);
199
200 return obj;
201 }
202
203 static void
clean_diff(GdaDataComparator * comparator)204 clean_diff (GdaDataComparator *comparator)
205 {
206 if (comparator->priv->diffs) {
207 gsize i;
208 for (i = 0; i < comparator->priv->diffs->len; i++) {
209 GdaDiff *diff = g_array_index (comparator->priv->diffs, GdaDiff *, i);
210 gda_diff_free (diff);
211 }
212 g_array_free (comparator->priv->diffs, TRUE);
213 }
214 comparator->priv->diffs = g_array_new (FALSE, FALSE, sizeof (GdaDiff *));
215 }
216
217 static void
gda_data_comparator_dispose(GObject * object)218 gda_data_comparator_dispose (GObject *object)
219 {
220 GdaDataComparator *comparator;
221
222 g_return_if_fail (GDA_IS_DATA_COMPARATOR (object));
223
224 comparator = GDA_DATA_COMPARATOR (object);
225 if (comparator->priv) {
226 if (comparator->priv->old_model) {
227 g_object_unref (comparator->priv->old_model);
228 comparator->priv->old_model = NULL;
229 }
230 if (comparator->priv->new_model) {
231 g_object_unref (comparator->priv->new_model);
232 comparator->priv->new_model = NULL;
233 }
234 clean_diff (comparator);
235 g_free (comparator->priv->key_columns);
236 g_array_free (comparator->priv->diffs, TRUE);
237 }
238
239 /* parent class */
240 parent_class->dispose (object);
241 }
242
243 static void
gda_data_comparator_finalize(GObject * object)244 gda_data_comparator_finalize (GObject *object)
245 {
246 GdaDataComparator *comparator;
247
248 g_return_if_fail (object != NULL);
249 g_return_if_fail (GDA_IS_DATA_COMPARATOR (object));
250
251 comparator = GDA_DATA_COMPARATOR (object);
252 if (comparator->priv) {
253 g_free (comparator->priv);
254 comparator->priv = NULL;
255 }
256
257 /* parent class */
258 parent_class->finalize (object);
259 }
260
261
262 static void
gda_data_comparator_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)263 gda_data_comparator_set_property (GObject *object,
264 guint param_id,
265 const GValue *value,
266 GParamSpec *pspec)
267 {
268 GdaDataComparator *comparator;
269 comparator = GDA_DATA_COMPARATOR (object);
270 if (comparator->priv) {
271 GdaDataModel *model;
272
273 switch (param_id) {
274 case PROP_OLD_MODEL:
275 model = (GdaDataModel*) g_value_get_object (value);
276 if (comparator->priv->old_model && (comparator->priv->old_model != model)) {
277 /* re-init */
278 clean_diff (comparator);
279 g_object_unref (comparator->priv->old_model);
280 g_free (comparator->priv->key_columns);
281 comparator->priv->key_columns = NULL;
282 }
283 comparator->priv->old_model = model;
284 if (model)
285 g_object_ref (model);
286 break;
287 case PROP_NEW_MODEL:
288 model = (GdaDataModel*) g_value_get_object (value);
289 if (comparator->priv->new_model && (comparator->priv->new_model != model)) {
290 /* re-init */
291 clean_diff (comparator);
292 g_object_unref (comparator->priv->new_model);
293 g_free (comparator->priv->key_columns);
294 comparator->priv->key_columns = NULL;
295 }
296 comparator->priv->new_model = model;
297 if (model)
298 g_object_ref (model);
299 break;
300 default:
301 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
302 break;
303 }
304 }
305 }
306
307 static void
gda_data_comparator_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)308 gda_data_comparator_get_property (GObject *object,
309 guint param_id,
310 GValue *value,
311 GParamSpec *pspec)
312 {
313 GdaDataComparator *comparator;
314
315 comparator = GDA_DATA_COMPARATOR (object);
316 if (comparator->priv) {
317 switch (param_id) {
318 case PROP_OLD_MODEL:
319 g_value_set_object (value, comparator->priv->old_model);
320 break;
321 case PROP_NEW_MODEL:
322 g_value_set_object (value, comparator->priv->new_model);
323 break;
324 default:
325 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
326 break;
327 }
328 }
329 }
330
331 static void
gda_diff_free(GdaDiff * diff)332 gda_diff_free (GdaDiff *diff)
333 {
334 g_hash_table_destroy (diff->values);
335 g_free (diff);
336 }
337
338 /**
339 * gda_data_comparator_set_key_columns:
340 * @comp: a #GdaDataComparator object
341 * @nb_cols: the size of the @col_numbers array
342 * @col_numbers: (array length=nb_cols): an array of @nb_cols values
343 *
344 * Defines the columns which will be used as a key when searching data. This is not mandatory but
345 * will speed things up as less data will be processed.
346 */
347 void
gda_data_comparator_set_key_columns(GdaDataComparator * comp,const gint * col_numbers,gint nb_cols)348 gda_data_comparator_set_key_columns (GdaDataComparator *comp, const gint *col_numbers, gint nb_cols)
349 {
350 g_return_if_fail (GDA_IS_DATA_COMPARATOR (comp));
351 g_return_if_fail (comp->priv);
352
353 g_free (comp->priv->key_columns);
354 comp->priv->key_columns = NULL;
355 if (nb_cols > 0) {
356 comp->priv->nb_key_columns = nb_cols;
357 comp->priv->key_columns = g_new (gint, nb_cols);
358 memcpy (comp->priv->key_columns, col_numbers, sizeof (gint) * nb_cols); /* Flawfinder: ignore */
359 }
360 }
361
362 /*
363 * Find the row in @comp->priv->old_model from the values of @comp->priv->new_model at line @row
364 * It is assumed that both data model have the same number of columns and of "compatible" types.
365 *
366 * Returns:
367 * -2 if an error occurred
368 * -1 if not found,
369 * >=0 if found (if changes need to be made, then @out_has_changed is set to TRUE).
370 */
371 static gint
find_row_in_model(GdaDataComparator * comp,gint row,gboolean * out_has_changed,GError ** error)372 find_row_in_model (GdaDataComparator *comp, gint row, gboolean *out_has_changed, GError **error)
373 {
374 gint i, erow;
375 gint ncols;
376 GSList *values = NULL;
377
378 *out_has_changed = FALSE;
379 ncols = gda_data_model_get_n_columns (comp->priv->old_model);
380 if (!comp->priv->key_columns) {
381 comp->priv->nb_key_columns = ncols;
382 comp->priv->key_columns = g_new (gint, ncols);
383 for (i = 0; i < ncols; i++)
384 comp->priv->key_columns [i] = i;
385 }
386
387 for (i = 0; i < comp->priv->nb_key_columns; i++) {
388 const GValue *cvalue;
389 cvalue = gda_data_model_get_value_at (comp->priv->new_model, comp->priv->key_columns[i], row, error);
390 if (!cvalue) {
391 if (values)
392 g_slist_free (values);
393 return -2;
394 }
395 values = g_slist_append (values, (gpointer) cvalue);
396 }
397
398 erow = gda_data_model_get_row_from_values (comp->priv->old_model, values, comp->priv->key_columns);
399 g_slist_free (values);
400
401 if (erow >= 0) {
402 gboolean changed = FALSE;
403 for (i = 0; i < ncols; i++) {
404 const GValue *v1, *v2;
405 v1 = gda_data_model_get_value_at (comp->priv->old_model, i, erow, error);
406 if (!v1)
407 return -2;
408 v2 = gda_data_model_get_value_at (comp->priv->new_model, i, row, error);
409 if (!v2)
410 return -2;
411 if (gda_value_compare (v1, v2)) {
412 changed = TRUE;
413 break;
414 }
415 }
416 *out_has_changed = changed;
417 }
418
419 return erow;
420 }
421
422 /**
423 * gda_data_comparator_compute_diff:
424 * @comp: a #GdaDataComparator object
425 * @error: a place to store errors, or %NULL
426 *
427 * Actually computes the differences between the data models for which @comp is defined.
428 *
429 * For each difference computed, stored in a #GdaDiff structure, the "diff-computed" signal is emitted.
430 * If one connects to this signal and returns FALSE in the signal handler, then computing differences will be
431 * stopped and an error will be returned.
432 *
433 * Returns: TRUE if all the differences have been successfully computed, and FALSE if an error occurred
434 */
435 gboolean
gda_data_comparator_compute_diff(GdaDataComparator * comp,GError ** error)436 gda_data_comparator_compute_diff (GdaDataComparator *comp, GError **error)
437 {
438 gint oncols, nncols, i;
439 gint onrows, nnrows;
440 gboolean *rows_to_del = NULL;
441
442 g_return_val_if_fail (GDA_IS_DATA_COMPARATOR (comp), FALSE);
443 g_return_val_if_fail (comp->priv, FALSE);
444
445 clean_diff (comp);
446
447 /* check setup */
448 if (!comp->priv->old_model) {
449 g_set_error (error, GDA_DATA_COMPARATOR_ERROR, GDA_DATA_COMPARATOR_MISSING_DATA_MODEL_ERROR,
450 "%s", _("Missing original data model"));
451 return FALSE;
452 }
453 if (!comp->priv->new_model) {
454 g_set_error (error, GDA_DATA_COMPARATOR_ERROR, GDA_DATA_COMPARATOR_MISSING_DATA_MODEL_ERROR,
455 "%s", _("Missing new data model"));
456 return FALSE;
457 }
458 if (! (gda_data_model_get_access_flags (comp->priv->old_model) & GDA_DATA_MODEL_ACCESS_RANDOM) ||
459 ! (gda_data_model_get_access_flags (comp->priv->new_model) & GDA_DATA_MODEL_ACCESS_RANDOM)) {
460 g_set_error (error, GDA_DATA_COMPARATOR_ERROR, GDA_DATA_COMPARATOR_MODEL_ACCESS_ERROR,
461 "%s", _("Data models must support random access model"));
462 return FALSE;
463 }
464
465 /* compare columns */
466 oncols = gda_data_model_get_n_columns (comp->priv->old_model);
467 nncols = gda_data_model_get_n_columns (comp->priv->new_model);
468 if (oncols != nncols) {
469 g_set_error (error, GDA_DATA_COMPARATOR_ERROR, GDA_DATA_COMPARATOR_MISSING_DATA_MODEL_ERROR,
470 "%s", _("Data models to compare don't have the same number of columns"));
471 return FALSE;
472 }
473
474 for (i = 0; i < oncols; i++) {
475 GdaColumn *ocol, *ncol;
476 ocol = gda_data_model_describe_column (comp->priv->old_model, i);
477 ncol = gda_data_model_describe_column (comp->priv->new_model, i);
478 if (gda_column_get_g_type (ocol) != gda_column_get_g_type (ncol)) {
479 g_set_error (error, GDA_DATA_COMPARATOR_ERROR,
480 GDA_DATA_COMPARATOR_COLUMN_TYPES_MISMATCH_ERROR,
481 _("Type mismatch for column %d: '%s' and '%s'"), i,
482 g_type_name (gda_column_get_g_type (ocol)),
483 g_type_name (gda_column_get_g_type (ncol)));
484 return FALSE;
485 }
486 }
487
488 /* actual differences computations : rows to insert / update */
489 onrows = gda_data_model_get_n_rows (comp->priv->old_model);
490 if (onrows < 0) {
491 g_set_error (error, GDA_DATA_COMPARATOR_ERROR, GDA_DATA_COMPARATOR_MODEL_ACCESS_ERROR,
492 "%s", _("Can't get the number of rows of data model to compare from"));
493 return FALSE;
494 }
495 nnrows = gda_data_model_get_n_rows (comp->priv->new_model);
496 if (nnrows < 0) {
497 g_set_error (error, GDA_DATA_COMPARATOR_ERROR, GDA_DATA_COMPARATOR_MODEL_ACCESS_ERROR,
498 "%s", _("Can't get the number of rows of data model to compare to"));
499 return FALSE;
500 }
501 if (onrows > 0) {
502 rows_to_del = g_new (gboolean, onrows);
503 memset (rows_to_del, TRUE, sizeof (gboolean) * onrows);
504 }
505 for (i = 0; i < nnrows; i++) {
506 gint erow = -1;
507 gboolean has_changed = FALSE;
508 GdaDiff *diff = NULL;
509 gboolean stop;
510
511 erow = find_row_in_model (comp, i, &has_changed, error);
512
513 #ifdef DEBUG_STORE_MODIFY
514 g_print ("FIND row %d returned row %d (%s)\n", i, erow,
515 has_changed ? "CHANGED" : "unchanged");
516 #endif
517 if (erow == -1) {
518 gint j;
519 diff = g_new0 (GdaDiff, 1);
520 diff->type = GDA_DIFF_ADD_ROW;
521 diff->old_row = -1;
522 diff->new_row = i;
523 diff->values = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free,
524 (GDestroyNotify) gda_value_free);
525 for (j = 0; j < oncols; j++) {
526 const GValue *cvalue;
527 cvalue = gda_data_model_get_value_at (comp->priv->new_model, j, i, error);
528 if (!cvalue) {
529 /* an error occurred */
530 g_free (rows_to_del);
531 gda_diff_free (diff);
532 return FALSE;
533 }
534 g_hash_table_insert (diff->values, g_strdup_printf ("+%d", j),
535 gda_value_copy (cvalue));
536 }
537 }
538 else if (erow < -1) {
539 /* an error occurred */
540 g_free (rows_to_del);
541 return FALSE;
542 }
543 else if (has_changed) {
544 gint j;
545 diff = g_new0 (GdaDiff, 1);
546 diff->type = GDA_DIFF_MODIFY_ROW;
547 rows_to_del [erow] = FALSE;
548 diff->old_row = erow;
549 diff->new_row = i;
550 diff->values = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free,
551 (GDestroyNotify) gda_value_free);
552 for (j = 0; j < oncols; j++) {
553 const GValue *cvalue;
554 cvalue = gda_data_model_get_value_at (comp->priv->new_model, j, i, error);
555 if (!cvalue) {
556 /* an error occurred */
557 g_free (rows_to_del);
558 gda_diff_free (diff);
559 return FALSE;
560 }
561 g_hash_table_insert (diff->values, g_strdup_printf ("+%d", j),
562 gda_value_copy (cvalue));
563 cvalue = gda_data_model_get_value_at (comp->priv->old_model, j, i, error);
564 if (!cvalue) {
565 /* an error occurred */
566 g_free (rows_to_del);
567 gda_diff_free (diff);
568 return FALSE;
569 }
570 g_hash_table_insert (diff->values, g_strdup_printf ("-%d", j),
571 gda_value_copy (cvalue));
572 }
573 }
574 else
575 rows_to_del [erow] = FALSE; /* row has not been changed */
576
577 if (diff) {
578 g_array_append_val (comp->priv->diffs, diff);
579 g_signal_emit (comp, gda_data_comparator_signals [DIFF_COMPUTED], 0, diff, &stop);
580 if (stop) {
581 g_set_error (error, GDA_DATA_COMPARATOR_ERROR,
582 GDA_DATA_COMPARATOR_USER_CANCELLED_ERROR,
583 "%s", _("Differences computation cancelled on signal handling"));
584 g_free (rows_to_del);
585 return FALSE;
586 }
587 }
588 }
589
590 /* actual differences computations : rows to delete */
591 for (i = 0; i < onrows; i++) {
592 GdaDiff *diff = NULL;
593 gboolean stop;
594 if (rows_to_del [i]) {
595 gint j;
596 diff = g_new0 (GdaDiff, 1);
597 diff->type = GDA_DIFF_REMOVE_ROW;
598 diff->old_row = i;
599 diff->new_row = -1;
600 diff->values = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free,
601 (GDestroyNotify) gda_value_free);
602 for (j = 0; j < oncols; j++) {
603 const GValue *cvalue;
604 cvalue = gda_data_model_get_value_at (comp->priv->old_model, j, i, error);
605 if (!cvalue) {
606 /* an error occurred */
607 g_free (rows_to_del);
608 gda_diff_free (diff);
609 return FALSE;
610 }
611 g_hash_table_insert (diff->values, g_strdup_printf ("-%d", j),
612 gda_value_copy (cvalue));
613 }
614 g_array_append_val (comp->priv->diffs, diff);
615 g_signal_emit (comp, gda_data_comparator_signals [DIFF_COMPUTED], 0, diff, &stop);
616 if (stop) {
617 g_set_error (error, GDA_DATA_COMPARATOR_ERROR,
618 GDA_DATA_COMPARATOR_USER_CANCELLED_ERROR,
619 "%s", _("Differences computation cancelled on signal handling"));
620 g_free (rows_to_del);
621 return FALSE;
622 }
623 }
624 }
625
626 g_free (rows_to_del);
627 return TRUE;
628 }
629
630 /**
631 * gda_data_comparator_get_n_diffs:
632 * @comp: a #GdaDataComparator object
633 *
634 * Get the number of differences as computed by the last time gda_data_comparator_compute_diff() was called.
635 *
636 * Returns: the number of computed differences
637 */
638 gint
gda_data_comparator_get_n_diffs(GdaDataComparator * comp)639 gda_data_comparator_get_n_diffs (GdaDataComparator *comp)
640 {
641 g_return_val_if_fail (GDA_IS_DATA_COMPARATOR (comp), 0);
642 g_return_val_if_fail (comp->priv, 0);
643
644 return comp->priv->diffs->len;
645 }
646
647 /**
648 * gda_data_comparator_get_diff:
649 * @comp: a #GdaDataComparator object
650 * @pos: the requested difference number (starting at 0)
651 *
652 * Get a pointer to the #GdaDiff structure representing the difference which number is @pos
653 *
654 * Returns: (transfer none): a pointer to a #GdaDiff, or %NULL if @pos is invalid
655 */
656 const GdaDiff *
gda_data_comparator_get_diff(GdaDataComparator * comp,gint pos)657 gda_data_comparator_get_diff (GdaDataComparator *comp, gint pos)
658 {
659 g_return_val_if_fail (GDA_IS_DATA_COMPARATOR (comp), NULL);
660 g_return_val_if_fail (comp->priv, NULL);
661
662 return g_array_index (comp->priv->diffs, GdaDiff*, pos);
663 }
664