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.
call_something()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,
main(int,char **)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 */
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
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 (&registering);
108 		if (type == 0)
109 			type = g_type_register_static (G_TYPE_OBJECT, "GdaDataComparator", &info, 0);
110 		g_mutex_unlock (&registering);
111 	}
112 	return type;
113 }
114 
115 static gboolean
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
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
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
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 *
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
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
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
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
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
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
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
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
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
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
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 *
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