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 (®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 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