[libgda/LIBGDA_4.2] Cleaned GdaDataModelIter implementations



commit effae56538e0d56b339dbbcae04f16a18ac00080
Author: Vivien Malerba <malerba gnome-db org>
Date:   Wed Sep 21 17:45:03 2011 +0200

    Cleaned GdaDataModelIter implementations

 libgda/gda-data-model-import.c                     |   43 ++----
 libgda/gda-data-model-iter.c                       |  152 +++++++++++---------
 libgda/gda-data-select.c                           |  130 +++++++----------
 libgda/sqlite/gda-sqlite-recordset.c               |   37 ++---
 providers/firebird/gda-firebird-recordset.c        |   73 +++++-----
 providers/jdbc/gda-jdbc-recordset.c                |   39 +++---
 providers/ldap/gdaprov-data-model-ldap.c           |    6 +-
 providers/mysql/gda-mysql-recordset.c              |   96 +++++--------
 providers/oracle/gda-oracle-recordset.c            |  129 +++--------------
 providers/postgres/gda-postgres-recordset.c        |   35 +----
 .../skel-implementation/capi/gda-capi-recordset.c  |   74 +++++-----
 11 files changed, 320 insertions(+), 494 deletions(-)
---
diff --git a/libgda/gda-data-model-import.c b/libgda/gda-data-model-import.c
index 9d4008c..a7107cb 100644
--- a/libgda/gda-data-model-import.c
+++ b/libgda/gda-data-model-import.c
@@ -1927,8 +1927,10 @@ gda_data_model_import_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
 		break;
 
 	case FORMAT_CSV:
-		if (! imodel->priv->extract.csv.rows_read)
+		if (! imodel->priv->extract.csv.rows_read) {
+			gda_data_model_iter_invalidate_contents (iter);
 			return FALSE;
+		}
 
 		if (gda_data_model_iter_is_valid (iter) &&
 		    (imodel->priv->extract.csv.rows_read->len > 0)) {
@@ -1957,27 +1959,17 @@ gda_data_model_import_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
 		GSList *plist;
 		GSList *vlist;
 		gboolean update_model;
-		gboolean allok = TRUE;
-		
 		g_object_get (G_OBJECT (iter), "update-model", &update_model, NULL);
 		g_object_set (G_OBJECT (iter), "update-model", FALSE, NULL);
 		for (plist = ((GdaSet *) iter)->holders, vlist = next_values;
 		     plist && vlist;
 		     plist = plist->next, vlist = vlist->next) {
 			GError *lerror = NULL;
-			if (! gda_holder_set_value (GDA_HOLDER (plist->data), 
-						    (GValue *) vlist->data, &lerror)) {
-				gchar *tmp;
-				tmp = g_strdup_printf (_("Could not set iterator's value: %s"),
-						       lerror && lerror->message ? lerror->message : _("No detail"));
-				add_error (imodel, tmp);
-				g_free (tmp);
-				allok = FALSE;
-			}
+			if (! gda_holder_set_value (GDA_HOLDER (plist->data),
+						    (GValue *) vlist->data, &lerror))
+				gda_holder_force_invalid_e (GDA_HOLDER (plist->data), lerror);
 		}
 		if (plist || vlist) {
-			if (imodel->priv->strict)
-				allok = FALSE;
 			if (plist) {
 				add_error_too_few_values (imodel);
 				for (; plist; plist = plist->next) {
@@ -1998,11 +1990,12 @@ gda_data_model_import_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
 		g_object_set (G_OBJECT (iter), "current-row", imodel->priv->iter_row, 
 			      "update-model", update_model, NULL);
 
-		return allok;
+		return TRUE;
 	}
 	else {
-		g_signal_emit_by_name (iter, "end-of-data");
+		gda_data_model_iter_invalidate_contents (iter);
 		g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
+		g_signal_emit_by_name (iter, "end-of-data");
 		return FALSE;
 	}
 }
@@ -2035,7 +2028,6 @@ gda_data_model_import_iter_prev (GdaDataModel *model, GdaDataModelIter *iter)
 		GSList *plist;
 		GSList *vlist;
 		gboolean update_model;
-		gboolean allok = TRUE;
 
 		g_object_get (G_OBJECT (iter), "update-model", &update_model, NULL);
 		g_object_set (G_OBJECT (iter), "update-model", FALSE, NULL);
@@ -2043,19 +2035,11 @@ gda_data_model_import_iter_prev (GdaDataModel *model, GdaDataModelIter *iter)
 		     plist && vlist;
 		     plist = plist->next, vlist = vlist->next) {
 			GError *lerror = NULL;
-			if (! gda_holder_set_value (GDA_HOLDER (plist->data), 
-						    (GValue *) vlist->data, &lerror)) {
-				gchar *tmp;
-				tmp = g_strdup_printf (_("Could not set iterator's value: %s"),
-						       lerror && lerror->message ? lerror->message : _("No detail"));
-				add_error (imodel, tmp);
-				g_free (tmp);
-				allok = FALSE;
-			}
+			if (! gda_holder_set_value (GDA_HOLDER (plist->data),
+						    (GValue *) vlist->data, &lerror))
+				gda_holder_force_invalid_e (GDA_HOLDER (plist->data), lerror);
 		}
 		if (plist || vlist) {
-			if (imodel->priv->strict)
-				allok = FALSE;
 			if (plist) {
 				add_error_too_few_values (imodel);
 				for (; plist; plist = plist->next) {
@@ -2075,9 +2059,10 @@ gda_data_model_import_iter_prev (GdaDataModel *model, GdaDataModelIter *iter)
 		g_assert (imodel->priv->iter_row >= 0);
 		g_object_set (G_OBJECT (iter), "current-row", imodel->priv->iter_row, 
 			      "update-model", update_model, NULL);
-		return allok;
+		return TRUE;
 	}
 	else {
+		gda_data_model_iter_invalidate_contents (iter);
 		g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
 		return FALSE;
 	}
diff --git a/libgda/gda-data-model-iter.c b/libgda/gda-data-model-iter.c
index e2da293..087a635 100644
--- a/libgda/gda-data-model-iter.c
+++ b/libgda/gda-data-model-iter.c
@@ -577,14 +577,18 @@ gda_data_model_iter_get_property (GObject *object,
  *
  * Synchronizes the values of the parameters in @iter with the values at the @row row.
  *
- * If @row is not a valid row, then the returned value is FALSE, and the "current-row"
- * property is set to -1 (which means that gda_data_model_iter_is_valid() would return FALSE)
+ * If @row is not a valid row, then the returned value is %FALSE, and the "current-row"
+ * property is set to -1 (which means that gda_data_model_iter_is_valid() would return %FALSE),
+ * with the exception that if @row is -1, then the returned value is %TRUE.
  *
- * If any other error occurred then the returned value is FALSE, but the "current-row"
- * property is set to the @row row.  In this case
- * each #GdaHolder composing @iter for which an error occurred will be invalid (see gda_holder_is_valid()).
+ * This function can return %FALSE if it was not allowed to be moved (as it emits the
+ * "validate-set" signal before being moved).
  *
- * Returns: TRUE if no error occurred
+ * When this function returns %TRUE, then @iter has actually been moved to the next row,
+ * but some values may not have been read correctly in the row, in which case the
+ * correcsponding #GdaHolder will be left invalid.
+ *
+ * Returns: %TRUE if no error occurred
  */
 gboolean
 gda_data_model_iter_move_to_row (GdaDataModelIter *iter, gint row)
@@ -592,8 +596,15 @@ gda_data_model_iter_move_to_row (GdaDataModelIter *iter, gint row)
 	g_return_val_if_fail (GDA_IS_DATA_MODEL_ITER (iter), FALSE);
 	g_return_val_if_fail (iter->priv, FALSE);
 
+	if ((gda_data_model_iter_get_row (iter) >= 0) &&
+	    (gda_data_model_iter_get_row (iter) == row))
+		return TRUE; /* nothing to do! */
+
 	if (row < 0) {
-		if (iter->priv->row != -1) {
+		if (gda_data_model_iter_get_row (iter) >= 0) {
+			if (! _gda_set_validate ((GdaSet*) iter, NULL))
+				return FALSE;
+
 			iter->priv->row = -1;
 			g_signal_emit (G_OBJECT (iter),
 				       gda_data_model_iter_signals[ROW_CHANGED],
@@ -603,44 +614,18 @@ gda_data_model_iter_move_to_row (GdaDataModelIter *iter, gint row)
 	}
 	else {
 		GdaDataModel *model;
-		if ((gda_data_model_iter_get_row (iter) >= 0) &&
-		    (gda_data_model_iter_get_row (iter) != row) && 
-		    ! _gda_set_validate ((GdaSet*) iter, NULL)) {
-			return FALSE;
-		}
-
-		gboolean *null_oks = NULL;
-		if (GDA_SET (iter)->holders) {
-			gint i;
-			GSList *list;
-
-			null_oks = g_new (gboolean, g_slist_length (GDA_SET (iter)->holders));
-			for (i = 0, list = GDA_SET (iter)->holders; list; i++, list = list->next) {
-				null_oks[i] = gda_holder_get_not_null ((GdaHolder*) list->data);
-				gda_holder_set_not_null ((GdaHolder*) list->data, FALSE);
-			}
-		}
-
-		gboolean move_ok;
 		model = iter->priv->data_model;
 		g_return_val_if_fail (iter->priv->data_model, FALSE);
 
-		if (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_at_row)
-			move_ok = (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_at_row) (model, iter, row);
-		else {
-			/* default method */
-			move_ok = gda_data_model_iter_move_to_row_default (model, iter, row);
+		if (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_at_row) {
+			if ((gda_data_model_iter_get_row (iter) >= 0) &&
+			    ! _gda_set_validate ((GdaSet*) iter, NULL))
+				return FALSE;
+			return (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_at_row) (model, iter,
+										  row);
 		}
-		
-		if (null_oks) {
-			gint i;
-			GSList *list;
-			for (i = 0, list = GDA_SET (iter)->holders; list; i++, list = list->next)
-				gda_holder_set_not_null ((GdaHolder*) list->data, null_oks[i]);
-			g_free (null_oks);
-		}
-		
-		return move_ok;
+		else
+			return gda_data_model_iter_move_to_row_default (model, iter, row);
 	}
 }
 
@@ -658,6 +643,13 @@ set_param_attributes (GdaHolder *holder, GdaValueAttribute flags)
 
 /**
  * gda_data_model_iter_move_to_row_default: (skip)
+ * @model: a #GdaDataModel
+ * @iter: a #GdaDataModelIter iterating in @model
+ * @row: the requested row
+ * 
+ * Method reserved to #GdaDataModelIter implementations, should not be called directly.
+ *
+ * Returns: %TRUE if @iter was moved correctly.
  */
 gboolean
 gda_data_model_iter_move_to_row_default (GdaDataModel *model, GdaDataModelIter *iter, gint row)
@@ -668,7 +660,9 @@ gda_data_model_iter_move_to_row_default (GdaDataModel *model, GdaDataModelIter *
 	GdaDataModel *test;
 	gboolean update_model;
 	
-	g_return_val_if_fail (GDA_IS_DATA_MODEL (model), FALSE);
+	if ((gda_data_model_iter_get_row (iter) >= 0) &&
+	    ! _gda_set_validate ((GdaSet*) iter, NULL))
+		return FALSE;
 
 	if (! (gda_data_model_get_access_flags (model) & GDA_DATA_MODEL_ACCESS_RANDOM))
 		return FALSE;
@@ -713,14 +707,17 @@ gda_data_model_iter_move_to_row_default (GdaDataModel *model, GdaDataModelIter *
  * (synchronizes the values of the parameters in @iter with the values at the new row).
  *
  * If the iterator was on the data model's last row, then it can't be moved forward
- * anymore, and the returned value is FALSE; nore also that the "current-row" property
- * is set to -1 (which means that gda_data_model_iter_is_valid() would return FALSE)
+ * anymore, and the returned value is %FALSE; note also that the "current-row" property
+ * is set to -1 (which means that gda_data_model_iter_is_valid() would return %FALSE)
+ *
+ * This function can return %FALSE if it was not allowed to be moved (as it emits the
+ * "validate-set" signal before being moved).
  *
- * If any other error occurred then the returned value is FALSE, but the "current-row"
- * property is set to the new current row (one row more than it was before the call). In this case
- * each #GdaHolder composing @iter for which an error occurred will be invalid (see gda_holder_is_valid()).
+ * When this function returns %TRUE, then @iter has actually been moved to the next row,
+ * but some values may not have been read correctly in the row, in which case the
+ * correcsponding #GdaHolder will be left invalid.
  *
- * Returns: TRUE if the iterator is now at the next row
+ * Returns: %TRUE if the iterator is now at the next row
  */
 gboolean
 gda_data_model_iter_move_next (GdaDataModelIter *iter)
@@ -730,13 +727,13 @@ gda_data_model_iter_move_next (GdaDataModelIter *iter)
 	g_return_val_if_fail (iter->priv, FALSE);
 	g_return_val_if_fail (iter->priv->data_model, FALSE);
 
-	if ((gda_data_model_iter_get_row (iter) >= 0) &&
-	    ! _gda_set_validate ((GdaSet*) iter, NULL))
-		return FALSE;
-
 	model = iter->priv->data_model;
-	if (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_next)
+	if (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_next) {
+		if ((gda_data_model_iter_get_row (iter) >= 0) &&
+		    ! _gda_set_validate ((GdaSet*) iter, NULL))
+			return FALSE;
 		return (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_next) (model, iter);
+	}
 	else 
 		/* default method */
 		return gda_data_model_iter_move_next_default (model, iter);
@@ -744,6 +741,12 @@ gda_data_model_iter_move_next (GdaDataModelIter *iter)
 
 /**
  * gda_data_model_iter_move_next_default: (skip)
+ * @model: a #GdaDataModel
+ * @iter: a #GdaDataModelIter iterating in @model
+ * 
+ * Method reserved to #GdaDataModelIter implementations, should not be called directly.
+ *
+ * Returns: %TRUE if @iter was moved correctly.
  */
 gboolean
 gda_data_model_iter_move_next_default (GdaDataModel *model, GdaDataModelIter *iter)
@@ -753,8 +756,11 @@ gda_data_model_iter_move_next_default (GdaDataModel *model, GdaDataModelIter *it
 	gint row;
 	GdaDataModel *test;
 	gboolean update_model;
-	
-	/* validity tests */
+
+	if ((gda_data_model_iter_get_row (iter) >= 0) &&
+	    ! _gda_set_validate ((GdaSet*) iter, NULL))
+		return FALSE;
+
 	if (! (gda_data_model_get_access_flags (model) & GDA_DATA_MODEL_ACCESS_RANDOM))
 		return FALSE;
 	
@@ -799,14 +805,17 @@ gda_data_model_iter_move_next_default (GdaDataModel *model, GdaDataModelIter *it
  * with the values at the new row).
  *
  * If the iterator was on the data model's first row, then it can't be moved backwards
- * anymore, and the returned value is FALSE; note also that the "current-row" property
- * is set to -1 (which means that gda_data_model_iter_is_valid() would return FALSE).
+ * anymore, and the returned value is %FALSE; note also that the "current-row" property
+ * is set to -1 (which means that gda_data_model_iter_is_valid() would return %FALSE).
+ *
+ * This function can return %FALSE if it was not allowed to be moved (as it emits the
+ * "validate-set" signal before being moved).
  *
- * If any other error occurred then the returned value is FALSE, but the "current-row"
- * property is set to the new current row (one row less than it was before the call).  In this case
- * each #GdaHolder composing @iter for which an error occurred will be invalid (see gda_holder_is_valid()).
+ * When this function returns %TRUE, then @iter has actually been moved to the next row,
+ * but some values may not have been read correctly in the row, in which case the
+ * correcsponding #GdaHolder will be left invalid.
  *
- * Returns: TRUE if the iterator is now at the previous row
+ * Returns: %TRUE if the iterator is now at the previous row
  */
 gboolean
 gda_data_model_iter_move_prev (GdaDataModelIter *iter)
@@ -817,13 +826,13 @@ gda_data_model_iter_move_prev (GdaDataModelIter *iter)
 	g_return_val_if_fail (iter->priv, FALSE);
 	g_return_val_if_fail (iter->priv->data_model, FALSE);
 
-	if ((gda_data_model_iter_get_row (iter) >= 0) &&
-	    ! _gda_set_validate ((GdaSet*) iter, NULL))
-		return FALSE;
-
 	model = iter->priv->data_model;
-	if (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_prev)
+	if (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_prev) {
+		if ((gda_data_model_iter_get_row (iter) >= 0) &&
+		    ! _gda_set_validate ((GdaSet*) iter, NULL))
+			return FALSE;
 		return (GDA_DATA_MODEL_GET_CLASS (model)->i_iter_prev) (model, iter);
+	}
 	else 
 		/* default method */
 		return gda_data_model_iter_move_prev_default (model, iter);
@@ -831,6 +840,12 @@ gda_data_model_iter_move_prev (GdaDataModelIter *iter)
 
 /**
  * gda_data_model_iter_move_prev_default: (skip)
+ * @model: a #GdaDataModel
+ * @iter: a #GdaDataModelIter iterating in @model
+ * 
+ * Method reserved to #GdaDataModelIter implementations, should not be called directly.
+ *
+ * Returns: %TRUE if @iter was moved correctly.
  */
 gboolean
 gda_data_model_iter_move_prev_default (GdaDataModel *model, GdaDataModelIter *iter)
@@ -841,7 +856,10 @@ gda_data_model_iter_move_prev_default (GdaDataModel *model, GdaDataModelIter *it
 	GdaDataModel *test;
 	gboolean update_model;
 	
-	/* validity tests */
+	if ((gda_data_model_iter_get_row (iter) >= 0) &&
+	    ! _gda_set_validate ((GdaSet*) iter, NULL))
+		return FALSE;
+
 	if (! (gda_data_model_get_access_flags (model) & GDA_DATA_MODEL_ACCESS_RANDOM))
 		return FALSE;
 	
diff --git a/libgda/gda-data-select.c b/libgda/gda-data-select.c
index 01e735f..aa66e49 100644
--- a/libgda/gda-data-select.c
+++ b/libgda/gda-data-select.c
@@ -1923,20 +1923,12 @@ gda_data_select_get_value_at (GdaDataModel *model, gint col, gint row, GError **
 			prow = dstmt->row;
 	}
 	else {
-		gint *ptr;
-		irow = int_row;
-		ptr = g_hash_table_lookup (imodel->priv->sh->index, &irow);
-		if (!ptr) {
-			prow = NULL;
-			if (CLASS (model)->fetch_random && 
-			    !CLASS (model)->fetch_random (imodel, &prow, int_row, error))
-				return NULL;
-		}
-		else
-			prow = g_array_index (imodel->priv->sh->rows, GdaRow *, *ptr);
+		prow = gda_data_select_get_stored_row (imodel, int_row);
+		if (!prow && CLASS (model)->fetch_random)
+			CLASS (model)->fetch_random (imodel, &prow, int_row, error);
 	}
-	
-	g_assert (prow);
+	if (!prow)
+		return NULL;
 
 	GValue *retval = gda_row_get_value (prow, col);
 	if (gda_row_value_is_valid_e (prow, retval, error))
@@ -2011,7 +2003,7 @@ gda_data_select_create_iter (GdaDataModel *model)
 	}
 }
 
-static gboolean update_iter (GdaDataSelect *imodel, GdaRow *prow);
+static void update_iter (GdaDataSelect *imodel, GdaRow *prow);
 static gboolean
 gda_data_select_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
 {
@@ -2027,37 +2019,33 @@ gda_data_select_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
 		return gda_data_model_iter_move_next_default (model, iter);
 
 	g_return_val_if_fail (CLASS (model)->fetch_next, FALSE);
-
 	g_return_val_if_fail (iter, FALSE);
         g_return_val_if_fail (imodel->priv->iter == iter, FALSE);
 
-	if (imodel->priv->sh->iter_row == G_MAXINT)
+	if (imodel->priv->sh->iter_row == G_MAXINT) {
+		gda_data_model_iter_invalidate_contents (iter);
 		return FALSE;
+	}
 	else if (imodel->priv->sh->iter_row == G_MININT)
 		target_iter_row = 0;
 	else
 		target_iter_row = imodel->priv->sh->iter_row + 1;
+
 	int_row = external_to_internal_row (imodel, target_iter_row, NULL);
+	prow = gda_data_select_get_stored_row (model, int_row);
+	if (!prow)
+		CLASS (model)->fetch_next (imodel, &prow, int_row, NULL);
 
-	gint *ptr;
-	irow = int_row;
-	ptr = g_hash_table_lookup (imodel->priv->sh->index, &irow);
-	if (ptr)
-		prow = g_array_index (imodel->priv->sh->rows, GdaRow *, *ptr);
-	else if (!CLASS (model)->fetch_next (imodel, &prow, int_row, NULL)) {
-		/* an error occurred */
-		g_object_set (G_OBJECT (iter), "current-row", target_iter_row, NULL);
-		return FALSE;
-	}
-	
 	if (prow) {
 		imodel->priv->sh->iter_row = target_iter_row;
-                return update_iter (imodel, prow);
+                update_iter (imodel, prow);
+		return TRUE;
 	}
 	else {
-		g_signal_emit_by_name (iter, "end-of-data");
-                g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
+		gda_data_model_iter_invalidate_contents (iter);
                 imodel->priv->sh->iter_row = G_MAXINT;
+                g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
+		g_signal_emit_by_name (iter, "end-of-data");
                 return FALSE;
 	}
 }
@@ -2076,8 +2064,10 @@ gda_data_select_iter_prev (GdaDataModel *model, GdaDataModelIter *iter)
 	if (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) 
 		return gda_data_model_iter_move_prev_default (model, iter);
 
-	if (! CLASS (model)->fetch_prev)
+	if (! CLASS (model)->fetch_prev) {
+		gda_data_model_iter_invalidate_contents (iter);
 		return FALSE;
+	}
 
         g_return_val_if_fail (iter, FALSE);
         g_return_val_if_fail (imodel->priv->iter == iter, FALSE);
@@ -2092,26 +2082,21 @@ gda_data_select_iter_prev (GdaDataModel *model, GdaDataModelIter *iter)
         else
                 target_iter_row = imodel->priv->sh->iter_row - 1;
 
-	gint *ptr;
 	int_row = external_to_internal_row (imodel, target_iter_row, NULL);
-	irow = int_row;
-	ptr = g_hash_table_lookup (imodel->priv->sh->index, &irow);
-	if (ptr)
-		prow = g_array_index (imodel->priv->sh->rows, GdaRow *, *ptr);
-	else if (!CLASS (model)->fetch_prev (imodel, &prow, int_row, NULL)) {
-		/* an error occurred */
-		g_object_set (G_OBJECT (iter), "current-row", target_iter_row, NULL);
-		return FALSE;
-	}
+	prow = gda_data_select_get_stored_row (model, int_row);
+	if (!prow)
+		CLASS (model)->fetch_prev (imodel, &prow, int_row, NULL);
 
 	if (prow) {
 		imodel->priv->sh->iter_row = target_iter_row;
-                return update_iter (imodel, prow);
+                update_iter (imodel, prow);
+		return TRUE;
 	}
 
  prev_error:
         g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
         imodel->priv->sh->iter_row = G_MININT;
+	gda_data_model_iter_invalidate_contents (iter);
         return FALSE;
 }
 
@@ -2125,39 +2110,34 @@ gda_data_select_iter_at_row (GdaDataModel *model, GdaDataModelIter *iter, gint r
 	imodel = (GdaDataSelect *) model;
 	g_return_val_if_fail (imodel->priv, FALSE);
 
-	int_row = external_to_internal_row (imodel, row, NULL);
-	if (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) 
+	if (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)
 		return gda_data_model_iter_move_to_row_default (model, iter, row);
 
         g_return_val_if_fail (iter, FALSE);
         g_return_val_if_fail (imodel->priv->iter == iter, FALSE);
 
-	irow = int_row;
-	ptr = g_hash_table_lookup (imodel->priv->sh->index, &irow);
-	if (ptr)
-		prow = g_array_index (imodel->priv->sh->rows, GdaRow *, *ptr);
-
-	if (CLASS (model)->fetch_at) {
-		if (!CLASS (model)->fetch_at (imodel, &prow, int_row, NULL)) {
-			/* an error occurred */
-			g_object_set (G_OBJECT (iter), "current-row", row, NULL);
-			return FALSE;
-		}
+	int_row = external_to_internal_row (imodel, row, NULL);
+	prow = gda_data_select_get_stored_row (model, int_row);
 
-		if (prow) {
-			imodel->priv->sh->iter_row = row;
-			return update_iter (imodel, prow);
-		}
-		else {
-			g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
-			imodel->priv->sh->iter_row = G_MININT;
-			return FALSE;
-		}
+	if (prow) {
+		imodel->priv->sh->iter_row = row;
+		update_iter (imodel, prow);
+		return TRUE;
 	}
 	else {
-		if (prow) {
-			imodel->priv->sh->iter_row = row;
-			return update_iter (imodel, prow);
+		if (CLASS (model)->fetch_at) {
+			CLASS (model)->fetch_at (imodel, &prow, int_row, NULL);
+			if (prow) {
+				imodel->priv->sh->iter_row = row;
+				update_iter (imodel, prow);
+				return TRUE;
+			}
+			else {
+				g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
+				imodel->priv->sh->iter_row = G_MININT;
+				gda_data_model_iter_invalidate_contents (iter);
+				return FALSE;
+			}
 		}
 		else {
 			/* implementation of fetch_at() is optional */
@@ -2181,15 +2161,13 @@ gda_data_select_iter_at_row (GdaDataModel *model, GdaDataModelIter *iter, gint r
 	}
 }
 
-static gboolean
+static void
 update_iter (GdaDataSelect *imodel, GdaRow *prow)
 {
         gint i;
 	GdaDataModelIter *iter = imodel->priv->iter;
 	GSList *plist;
 	gboolean update_model;
-	gboolean retval = TRUE;
-	
 	g_object_get (G_OBJECT (iter), "update-model", &update_model, NULL);
 	if (update_model)
 		g_object_set (G_OBJECT (iter), "update-model", FALSE, NULL);
@@ -2199,7 +2177,6 @@ update_iter (GdaDataSelect *imodel, GdaRow *prow)
 	     i++, plist = plist->next) {
 		GValue *value;
 		GError *lerror = NULL;
-		gboolean pok = TRUE;
 		value = gda_row_get_value (prow, i);
 
 		if (!gda_row_value_is_valid_e (prow, value, &lerror)) {
@@ -2213,7 +2190,7 @@ update_iter (GdaDataSelect *imodel, GdaRow *prow)
 			    gda_value_is_null (value)) {
 				gda_holder_set_not_null ((GdaHolder*) plist->data, FALSE);
 				if (! gda_holder_set_value ((GdaHolder*) plist->data, value, NULL)) {
-					pok = FALSE;
+					gda_holder_force_invalid_e ((GdaHolder*) plist->data, lerror);
 					g_warning (_("Could not change iter's value for column %d: %s"), i,
 						   lerror && lerror->message ? lerror->message : _("No detail"));
 					gda_holder_set_not_null ((GdaHolder*) plist->data, TRUE);
@@ -2223,23 +2200,18 @@ update_iter (GdaDataSelect *imodel, GdaRow *prow)
 						     "to be updated"));
 			}
 			else {
-				pok = FALSE;
+				gda_holder_force_invalid_e ((GdaHolder*) plist->data, lerror);
 				g_warning (_("Could not change iter's value for column %d: %s"), i,
 					   lerror && lerror->message ? lerror->message : _("No detail"));
 			}
 		}
-		if (!pok) {
-			retval = FALSE;
-			gda_holder_force_invalid_e ((GdaHolder*) plist->data, lerror);
-		}
         }
 
 	g_object_set (G_OBJECT (iter), "current-row", imodel->priv->sh->iter_row, NULL);
 	if (update_model)
 		g_object_set (G_OBJECT (iter), "update-model", update_model, NULL);
 
-	g_print ("%s(%p) => %d, current-row =>%d advertized_nrows => %d\n", __FUNCTION__, imodel, retval, imodel->priv->sh->iter_row, imodel->advertized_nrows);
-	return retval;
+	g_print ("%s(%p), current-row =>%d advertized_nrows => %d\n", __FUNCTION__, imodel, imodel->priv->sh->iter_row, imodel->advertized_nrows);
 }
 
 /*
diff --git a/libgda/sqlite/gda-sqlite-recordset.c b/libgda/sqlite/gda-sqlite-recordset.c
index 5663ff8..b54109b 100644
--- a/libgda/sqlite/gda-sqlite-recordset.c
+++ b/libgda/sqlite/gda-sqlite-recordset.c
@@ -639,35 +639,24 @@ gda_sqlite_recordset_fetch_nb_rows (GdaDataSelect *model)
 /*
  * Create a new filled #GdaRow object for the row at position @rownum.
  *
- * Each new #GdaRow created is "given" to the #GdaDataSelect implementation using gda_data_select_take_row ().
+ * Each new #GdaRow created needs to be "given" to the #GdaDataSelect implementation using
+ * gda_data_select_take_row() because backward iterating is not supported.
  */
 static gboolean
 gda_sqlite_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
 {
 	GdaSqliteRecordset *imodel;
 
-	if (*prow)
-		return TRUE;
-
 	imodel = GDA_SQLITE_RECORDSET (model);
-	for (; imodel->priv->next_row_num <= rownum; ) {
-		*prow = fetch_next_sqlite_row (imodel, TRUE, error);
-		if (!*prow) {
-			/*if (GDA_DATA_SELECT (model)->advertized_nrows >= 0), it's not an error */
-			if ((GDA_DATA_SELECT (model)->advertized_nrows >= 0) && 
-			    (imodel->priv->next_row_num < rownum)) {
-				g_set_error (error, 0,
-					     GDA_DATA_MODEL_ROW_OUT_OF_RANGE_ERROR,
-					     _("Row %d not found"), rownum);
-			}
-			return FALSE;
-		}
-	}
-	if (! *prow) {
-		*prow = gda_data_select_get_stored_row (model, rownum);
-		if (!*prow)
-			return FALSE;
+	if (imodel->priv->next_row_num >= rownum) {
+		g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
+			     GDA_SERVER_PROVIDER_INTERNAL_ERROR, 
+			     "%s", _("Requested row could not be found"));
+		return TRUE;
 	}
+	for (*prow = fetch_next_sqlite_row (imodel, TRUE, error);
+	     *prow && (imodel->priv->next_row_num < rownum);
+	     *prow = fetch_next_sqlite_row (imodel, TRUE, error));
 
 	return TRUE;
 }
@@ -676,16 +665,16 @@ gda_sqlite_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint row
  * Create a new filled #GdaRow object for the next cursor row
  *
  * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
- * never keeps a reference to it). Before a new #GdaRow gets created, the previous one, if set, is discarded.
+ * never keeps a reference to it).
+ * Before a new #GdaRow gets created, the previous one, if set, is discarded.
  */
 static gboolean
 gda_sqlite_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, G_GNUC_UNUSED gint rownum, GError **error)
 {
 	GdaSqliteRecordset *imodel = (GdaSqliteRecordset*) model;
 
-	if (imodel->priv->tmp_row) 
+	if (imodel->priv->tmp_row)
 		g_object_unref (imodel->priv->tmp_row);
-
 	*prow = fetch_next_sqlite_row (imodel, FALSE, error);
 	imodel->priv->tmp_row = *prow;
 
diff --git a/providers/firebird/gda-firebird-recordset.c b/providers/firebird/gda-firebird-recordset.c
index 669ac2d..050bfb1 100644
--- a/providers/firebird/gda-firebird-recordset.c
+++ b/providers/firebird/gda-firebird-recordset.c
@@ -238,17 +238,15 @@ gda_firebird_recordset_fetch_nb_rows (GdaDataSelect *model)
 /*
  * Create a new filled #GdaRow object for the row at position @rownum, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row(). If new row objects are "given" to the GdaDataSelect implemantation
- * using that method, then this method should detect when all the data model rows have been analyzed
- * (when model->nb_stored_rows == model->advertized_nrows) and then possibly discard the API handle
- * as it won't be used anymore to fetch rows.
+ * NOTES:
+ * - @prow will NOT be NULL, but *prow WILL be NULL.
+ * - a new #GdaRow object has to be created.
+ * - memory management for that new GdaRow object is left to the implementation, which
+ *   can use gda_data_select_take_row() to "give" the GdaRow to @model (in this case
+ *   this method won't be called anymore for the same @rownum), or may decide to
+ *   keep a cache of GdaRow object and "recycle" them.
+ * - implementing this method is MANDATORY if the data model supports random access
+ * - this method is only called when data model is used in random access mode
  */
 static gboolean 
 gda_firebird_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
@@ -285,14 +283,15 @@ gda_firebird_recordset_store_all (GdaDataSelect *model, GError **error)
 /*
  * Create a new filled #GdaRow object for the next cursor row, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * NOTES:
+ * - @prow will NOT be NULL, but *prow WILL be NULL.
+ * - a new #GdaRow object has to be created.
+ * - memory management for that new GdaRow object is left to the implementation, which
+ *   can use gda_data_select_take_row() to "give" the GdaRow to @model (in this case
+ *   this method won't be called anymore for the same @rownum), or may decide to
+ *   keep a cache of GdaRow object and "recycle" them.
+ * - implementing this method is MANDATORY
+ * - this method is only called when data model is used in cursor access mode
  */
 static gboolean 
 gda_firebird_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
@@ -307,14 +306,16 @@ gda_firebird_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint row
 /*
  * Create a new filled #GdaRow object for the previous cursor row, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * NOTES:
+ * - @prow will NOT be NULL, but *prow WILL be NULL.
+ * - a new #GdaRow object has to be created.
+ * - memory management for that new GdaRow object is left to the implementation, which
+ *   can use gda_data_select_take_row() to "give" the GdaRow to @model (in this case
+ *   this method won't be called anymore for the same @rownum), or may decide to
+ *   keep a cache of GdaRow object and "recycle" them.
+ * - implementing this method is OPTIONAL (in this case the data model is assumed not to
+ *   support moving iterators backward)
+ * - this method is only called when data model is used in cursor access mode
  */
 static gboolean 
 gda_firebird_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
@@ -329,14 +330,16 @@ gda_firebird_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint row
 /*
  * Create a new filled #GdaRow object for the cursor row at position @rownum, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * NOTES:
+ * - @prow will NOT be NULL, but *prow WILL be NULL.
+ * - a new #GdaRow object has to be created.
+ * - memory management for that new GdaRow object is left to the implementation, which
+ *   can use gda_data_select_take_row() to "give" the GdaRow to @model (in this case
+ *   this method won't be called anymore for the same @rownum), or may decide to
+ *   keep a cache of GdaRow object and "recycle" them.
+ * - implementing this method is OPTIONAL and usefull only if there is a method quicker
+ *   than iterating one step at a time to the correct position.
+ * - this method is only called when data model is used in cursor access mode
  */
 static gboolean 
 gda_firebird_recordset_fetch_at (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
diff --git a/providers/jdbc/gda-jdbc-recordset.c b/providers/jdbc/gda-jdbc-recordset.c
index 1841372..de070ce 100644
--- a/providers/jdbc/gda-jdbc-recordset.c
+++ b/providers/jdbc/gda-jdbc-recordset.c
@@ -410,11 +410,13 @@ fetch_next_jdbc_row (GdaJdbcRecordset *model, JNIEnv *jenv, gboolean do_store, G
 	prow = gda_row_new (_GDA_PSTMT (ps)->ncols);
 
 	jexec_res = jni_wrapper_method_call (jenv, GdaJResultSet__fillNextRow,
-					     model->priv->rs_value, &error_code, &sql_state, &lerror, (jlong)GPOINTER_TO_INT(prow));
+					     model->priv->rs_value, &error_code, &sql_state, &lerror,
+					     (jlong)GPOINTER_TO_INT(prow));
 	if (!jexec_res) {
 		if (error && lerror)
 			*error = g_error_copy (lerror);
 		_gda_jdbc_make_error (model->priv->cnc, error_code, sql_state, lerror);
+		g_object_unref ((GObject*) prow);
 		return NULL;
 	}
 
@@ -422,6 +424,7 @@ fetch_next_jdbc_row (GdaJdbcRecordset *model, JNIEnv *jenv, gboolean do_store, G
 	gda_value_free (jexec_res);
 	if (! row_found) {
 		GDA_DATA_SELECT (model)->advertized_nrows = model->priv->next_row_num;
+		g_object_unref ((GObject*) prow);
 		return NULL;
 	}
 
@@ -465,7 +468,8 @@ gda_jdbc_recordset_fetch_nb_rows (GdaDataSelect *model)
 /*
  * Create a new filled #GdaRow object for the row at position @rownum.
  *
- * Each new #GdaRow created is "given" to the #GdaDataSelect implementation using gda_data_select_take_row ().
+ * Each new #GdaRow created is "given" to the #GdaDataSelect implementation
+ * using gda_data_select_take_row ().
  */
 static gboolean 
 gda_jdbc_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
@@ -476,23 +480,18 @@ gda_jdbc_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownu
 
 	jenv = _gda_jdbc_get_jenv (&jni_detach, NULL);
 	if (!jenv)
-		return FALSE;
+		return TRUE;
 
 	imodel = GDA_JDBC_RECORDSET (model);
-        for (; imodel->priv->next_row_num <= rownum; ) {
-                *prow = fetch_next_jdbc_row (imodel, jenv, TRUE, error);
-                if (!*prow) {
-                        /*if (GDA_DATA_SELECT (model)->advertized_nrows >= 0), it's not an error */
-                        if ((GDA_DATA_SELECT (model)->advertized_nrows >= 0) &&
-                            (imodel->priv->next_row_num < rownum)) {
-                                g_set_error (error, 0,
-                                             GDA_DATA_MODEL_ROW_OUT_OF_RANGE_ERROR,
-                                             _("Row %d not found"), rownum);
-                        }
-			_gda_jdbc_release_jenv (jni_detach);
-                        return FALSE;
-                }
-        }
+	if (imodel->priv->next_row_num >= rownum) {
+		g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
+			     GDA_SERVER_PROVIDER_INTERNAL_ERROR, 
+			     "%s", _("Requested row could not be found"));
+		return TRUE;
+	}
+	for (*prow = fetch_next_jdbc_row (imodel, jenv, TRUE, error);
+	     *prow && (imodel->priv->next_row_num < rownum);
+	     *prow = fetch_next_jdbc_row (imodel, jenv, TRUE, error));
 
 	_gda_jdbc_release_jenv (jni_detach);
         return TRUE;
@@ -502,7 +501,8 @@ gda_jdbc_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownu
  * Create a new filled #GdaRow object for the next cursor row, and put it into *prow.
  *
  * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
- * never keeps a reference to it). Before a new #GdaRow gets created, the previous one, if set, is discarded.
+ * never keeps a reference to it). Before a new #GdaRow gets created, the previous one,
+ * if set, is discarded.
  */
 static gboolean 
 gda_jdbc_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, G_GNUC_UNUSED gint rownum, GError **error)
@@ -515,9 +515,6 @@ gda_jdbc_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, G_GNUC_UNUSE
 	if (!jenv)
 		return FALSE;
 
-	if (*prow)
-                return TRUE;
-
 	if (imodel->priv->tmp_row)
                 g_object_unref (imodel->priv->tmp_row);
 
diff --git a/providers/ldap/gdaprov-data-model-ldap.c b/providers/ldap/gdaprov-data-model-ldap.c
index 4c6f8a3..a4ff290 100644
--- a/providers/ldap/gdaprov-data-model-ldap.c
+++ b/providers/ldap/gdaprov-data-model-ldap.c
@@ -1045,12 +1045,14 @@ gda_data_model_ldap_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
 
 	if (! imodel->priv->cnc) {
 		/* error */
+		gda_data_model_iter_invalidate_contents (iter);
 		return FALSE;
 	}
 
 	cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (imodel->priv->cnc));
 	if (!cdata) {
 		/* error */
+		gda_data_model_iter_invalidate_contents (iter);
 		return FALSE;
 	}
 
@@ -1071,6 +1073,7 @@ gda_data_model_ldap_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
 		cpart = imodel->priv->current_exec;
 		if (! cpart->ldap_msg) {
 			/* error somewhere */
+			gda_data_model_iter_invalidate_contents (iter);
 			return FALSE;
 		}
 
@@ -1101,7 +1104,7 @@ gda_data_model_ldap_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
 
 	if (!imodel->priv->current_exec) {
 		/* execution is over */
-		g_signal_emit_by_name (iter, "end-of-data");
+		gda_data_model_iter_invalidate_contents (iter);
                 g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
 		if (imodel->priv->truncated) {
 			GError *e;
@@ -1110,6 +1113,7 @@ gda_data_model_ldap_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
 				     _("Truncated result because LDAP server limit encountered"));
 			add_exception (imodel, e);
 		}
+		g_signal_emit_by_name (iter, "end-of-data");
                 return FALSE;
 	}
 
diff --git a/providers/mysql/gda-mysql-recordset.c b/providers/mysql/gda-mysql-recordset.c
index 3ffb940..33a222a 100644
--- a/providers/mysql/gda-mysql-recordset.c
+++ b/providers/mysql/gda-mysql-recordset.c
@@ -1002,17 +1002,7 @@ new_row_from_mysql_stmt (GdaMysqlRecordset *imodel, G_GNUC_UNUSED gint rownum, G
 /*
  * Create a new filled #GdaRow object for the row at position @rownum, and put it into *row.
  *
- * WARNING: @row will NOT be NULL, but *row may or may not be NULL:
- *  -  If *row is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *row contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row(). If new row objects are "given" to the GdaDataSelect implemantation
- * using that method, then this method should detect when all the data model rows have been analyzed
- * (when model->nb_stored_rows == model->advertized_nrows) and then possibly discard the API handle
- * as it won't be used anymore to fetch rows.
+ * Each new GdaRow is given to @model using gda_data_select_take_row().
  */
 static gboolean 
 gda_mysql_recordset_fetch_random (GdaDataSelect  *model,
@@ -1024,12 +1014,9 @@ gda_mysql_recordset_fetch_random (GdaDataSelect  *model,
 
 	imodel = GDA_MYSQL_RECORDSET (model);
 
-	if (*row)
-		return TRUE;
-	
 	*row = new_row_from_mysql_stmt (imodel, rownum, error);
 	if (!*row)
-		return FALSE;
+		return TRUE;
 
 	gda_data_select_take_row (model, *row, rownum);
 	
@@ -1045,14 +1032,9 @@ gda_mysql_recordset_fetch_random (GdaDataSelect  *model,
 /*
  * Create a new filled #GdaRow object for the next cursor row, and put it into *row.
  *
- * WARNING: @row will NOT be NULL, but *row may or may not be NULL:
- *  -  If *row is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *row contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
+ * never keeps a reference to it).
+ * Before a new #GdaRow gets created, the previous one, if set, is discarded.
  */
 static gboolean 
 gda_mysql_recordset_fetch_next (GdaDataSelect  *model,
@@ -1062,43 +1044,33 @@ gda_mysql_recordset_fetch_next (GdaDataSelect  *model,
 {
 	GdaMysqlRecordset *imodel = (GdaMysqlRecordset*) model;
 
-	// TO_IMPLEMENT;
-
-	// gda_data_select_iter_next () increments rownum
-
-	if (imodel->priv->tmp_row != NULL) {
+	if (imodel->priv->tmp_row)
 		g_object_unref (G_OBJECT(imodel->priv->tmp_row));
-		imodel->priv->tmp_row = NULL;
-	}
-	
 	*row = new_row_from_mysql_stmt (imodel, rownum, error);
-
 	imodel->priv->tmp_row = *row;
 
-	return *row ? TRUE : FALSE;
+	return TRUE;
 }
 
 /*
- * Create a new filled #GdaRow object for the previous cursor row, and put it into *row.
- *
- * WARNING: @row will NOT be NULL, but *row may or may not be NULL:
- *  -  If *row is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *row contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
+ * Create a new filled #GdaRow object for the previous cursor row, and put it into *prow.
  *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
+ * never keeps a reference to it).
+ * Before a new #GdaRow gets created, the previous one, if set, is discarded.
  */
 static gboolean 
-gda_mysql_recordset_fetch_prev (G_GNUC_UNUSED GdaDataSelect  *model,
-				G_GNUC_UNUSED GdaRow        **row,
-				G_GNUC_UNUSED gint            rownum,
-				G_GNUC_UNUSED GError        **error)
+gda_mysql_recordset_fetch_prev (GdaDataSelect  *model,
+				GdaRow        **row,
+				gint            rownum,
+				GError        **error)
 {
-	/*GdaMysqlRecordset *imodel = (GdaMysqlRecordset*) model;*/
+	GdaMysqlRecordset *imodel = (GdaMysqlRecordset*) model;
 
-	TO_IMPLEMENT;
+	if (imodel->priv->tmp_row)
+		g_object_unref (G_OBJECT(imodel->priv->tmp_row));
+	*row = new_row_from_mysql_stmt (imodel, rownum, error);
+	imodel->priv->tmp_row = *row;
 
 	return TRUE;
 }
@@ -1106,24 +1078,22 @@ gda_mysql_recordset_fetch_prev (G_GNUC_UNUSED GdaDataSelect  *model,
 /*
  * Create a new filled #GdaRow object for the cursor row at position @rownum, and put it into *row.
  *
- * WARNING: @row will NOT be NULL, but *row may or may not be NULL:
- *  -  If *row is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *row contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
+ * never keeps a reference to it).
+ * Before a new #GdaRow gets created, the previous one, if set, is discarded.
  */
 static gboolean 
-gda_mysql_recordset_fetch_at (G_GNUC_UNUSED GdaDataSelect  *model,
-			      G_GNUC_UNUSED GdaRow        **row,
-			      G_GNUC_UNUSED gint            rownum,
-			      G_GNUC_UNUSED GError        **error)
+gda_mysql_recordset_fetch_at (GdaDataSelect  *model,
+			      GdaRow        **row,
+			      gint            rownum,
+			      GError        **error)
 {
-	/*GdaMysqlRecordset *imodel = (GdaMysqlRecordset*) model;*/
-	
-	TO_IMPLEMENT;
+	GdaMysqlRecordset *imodel = (GdaMysqlRecordset*) model;
+
+	if (imodel->priv->tmp_row)
+		g_object_unref (G_OBJECT(imodel->priv->tmp_row));
+	*row = new_row_from_mysql_stmt (imodel, rownum, error);
+	imodel->priv->tmp_row = *row;
 
 	return TRUE;
 }
diff --git a/providers/oracle/gda-oracle-recordset.c b/providers/oracle/gda-oracle-recordset.c
index 55b3a92..6cc63ac 100644
--- a/providers/oracle/gda-oracle-recordset.c
+++ b/providers/oracle/gda-oracle-recordset.c
@@ -48,8 +48,6 @@ static void gda_oracle_recordset_dispose   (GObject *object);
 static gint     gda_oracle_recordset_fetch_nb_rows (GdaDataSelect *model);
 static gboolean gda_oracle_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
 static gboolean gda_oracle_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
-static gboolean gda_oracle_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
-static gboolean gda_oracle_recordset_fetch_at (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
 
 static GdaRow  *new_row (GdaDataSelect *imodel, GdaConnection *cnc, GdaOraclePStmt *ps);
 
@@ -88,8 +86,8 @@ gda_oracle_recordset_class_init (GdaOracleRecordsetClass *klass)
 	pmodel_class->fetch_random = gda_oracle_recordset_fetch_random;
 
 	pmodel_class->fetch_next = gda_oracle_recordset_fetch_next;
-	pmodel_class->fetch_prev = gda_oracle_recordset_fetch_prev;
-	pmodel_class->fetch_at = gda_oracle_recordset_fetch_at;
+	pmodel_class->fetch_prev = NULL;
+	pmodel_class->fetch_at = NULL;
 }
 
 static void
@@ -502,7 +500,7 @@ gda_oracle_recordset_new (GdaConnection *cnc, GdaOraclePStmt *ps, GdaSet *exec_p
 }
 
 static GdaRow *
-fetch_next_oracle_row (GdaOracleRecordset *model, gboolean do_store, GError **error)
+fetch_next_oracle_row (GdaOracleRecordset *model, G_GNUC_UNUSED gboolean do_store, GError **error)
 {
 	int result;
 	GdaRow *prow = NULL;
@@ -583,17 +581,8 @@ gda_oracle_recordset_fetch_nb_rows (GdaDataSelect *model)
 /*
  * Create a new filled #GdaRow object for the row at position @rownum, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row(). If new row objects are "given" to the GdaDataSelect implemantation
- * using that method, then this method should detect when all the data model rows have been analyzed
- * (when model->nb_stored_rows == model->advertized_nrows) and then possibly discard the API handle
- * as it won't be used anymore to fetch rows.
+ * Each new #GdaRow created needs to be "given" to the #GdaDataSelect implementation using
+ * gda_data_select_take_row() because backward iterating is not supported.
  */
 static gboolean 
 gda_oracle_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
@@ -602,120 +591,38 @@ gda_oracle_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint row
 	OracleConnectionData *cdata;
 	GdaConnection *cnc;
 
-	if (*prow)
-                return TRUE;
-
 	imodel = GDA_ORACLE_RECORDSET (model);
 	cnc = gda_data_select_get_connection (model);
 	cdata = (OracleConnectionData*)	gda_connection_internal_get_provider_data (cnc);
 	if (!cdata)
-		return FALSE;
-
-	for (; imodel->priv->next_row_num <= rownum; ) {
-		*prow = fetch_next_oracle_row (imodel, TRUE, error);
-		if (!*prow) {
-			/*if (GDA_DATA_SELECT (model)->advertized_nrows >= 0), it's not an error */
-			if ((GDA_DATA_SELECT (model)->advertized_nrows >= 0) &&
-			    (imodel->priv->next_row_num < rownum)) {
-				g_set_error (error, 0,
-					     GDA_DATA_MODEL_ROW_OUT_OF_RANGE_ERROR,
-					     _("Row %d not found"), rownum);
-			}
-			return FALSE;
-		}
-	}
-	return TRUE;
-}
+		return TRUE;
 
-/*
- * Create and "give" filled #GdaRow object for all the rows in the model
- */
-static gboolean
-gda_oracle_recordset_store_all (GdaDataSelect *model, GError **error)
-{
-	GdaOracleRecordset *imodel;
-	gint i;
-
-	imodel = GDA_ORACLE_RECORDSET (model);
-
-	/* default implementation */
-	for (i = 0; i < model->advertized_nrows; i++) {
-		GdaRow *prow;
-		if (! gda_oracle_recordset_fetch_random (model, &prow, i, error))
-			return FALSE;
+	if (imodel->priv->next_row_num >= rownum) {
+		g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
+			     GDA_SERVER_PROVIDER_INTERNAL_ERROR, 
+			     "%s", _("Requested row could not be found"));
+		return TRUE;
 	}
+	for (*prow = fetch_next_oracle_row (imodel, TRUE, error);
+	     *prow && (imodel->priv->next_row_num < rownum);
+	     *prow = fetch_next_oracle_row (imodel, TRUE, error));
+
 	return TRUE;
 }
 
 /*
  * Create a new filled #GdaRow object for the next cursor row, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * Each new #GdaRow created needs to be "given" to the #GdaDataSelect implementation using
+ * gda_data_select_take_row() because backward iterating is not supported.
  */
 static gboolean 
 gda_oracle_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
 {
-	if (*prow)
-                return TRUE;
-
 	*prow = fetch_next_oracle_row ((GdaOracleRecordset*) model, TRUE, error);
-	return *prow ? TRUE : FALSE;
-}
-
-/*
- * Create a new filled #GdaRow object for the previous cursor row, and put it into *prow.
- *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
- */
-static gboolean 
-gda_oracle_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
-{
-	OracleConnectionData *cdata;
-	GdaConnection *cnc;
-
-	if (*prow)
-                return TRUE;
-
-	cnc = gda_data_select_get_connection (model);
-	cdata = (OracleConnectionData*)	gda_connection_internal_get_provider_data (cnc);
-	if (!cdata)
-		return FALSE;
-
-	g_warning ("Internal implementation error: non scrollable cursor requested to go backward!\n");
-	return FALSE;
+	return TRUE;
 }
 
-/*
- * Create a new filled #GdaRow object for the cursor row at position @rownum, and put it into *prow.
- *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
- */
-static gboolean 
-gda_oracle_recordset_fetch_at (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
-{
-	return gda_oracle_recordset_fetch_random (model, prow, rownum, error);
-}
 
 /* create a new GdaRow from the last read row */
 static GdaRow *
diff --git a/providers/postgres/gda-postgres-recordset.c b/providers/postgres/gda-postgres-recordset.c
index 9564519..b5bfce0 100644
--- a/providers/postgres/gda-postgres-recordset.c
+++ b/providers/postgres/gda-postgres-recordset.c
@@ -437,13 +437,10 @@ gda_postgres_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint r
 {
 	GdaPostgresRecordset *imodel = (GdaPostgresRecordset *) model;
 
-	if (*prow)
-		return TRUE;
-
 	if (!imodel->priv->pg_res) {
 		g_set_error (error, GDA_SERVER_PROVIDER_ERROR, GDA_SERVER_PROVIDER_INTERNAL_ERROR,
 			     "%s", _("Internal error"));
-		return FALSE;
+		return TRUE;
 	}
 
 	*prow = new_row_from_pg_res (imodel, rownum, error);
@@ -486,23 +483,19 @@ gda_postgres_recordset_store_all (GdaDataSelect *model, GError **error)
  * Create a new filled #GdaRow object for the next cursor row, and put it into *prow.
  *
  * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
- * never keeps a reference to it). Before a new #GdaRow gets created, the previous one, if set, is discarded.
+ * never keeps a reference to it).
  */
 static gboolean
 gda_postgres_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
 {
 	GdaPostgresRecordset *imodel = (GdaPostgresRecordset*) model;
 
-	if (*prow)
-		return TRUE;
-
 	if (row_is_in_current_pg_res (imodel, rownum)) {
 		if (imodel->priv->tmp_row)
 			set_prow_with_pg_res (imodel, imodel->priv->tmp_row, rownum - imodel->priv->pg_res_inf, error);
 		else
 			imodel->priv->tmp_row = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
 		*prow = imodel->priv->tmp_row;
-		return TRUE;
 	}
 	else {
 		gboolean fetch_error = FALSE;
@@ -512,34 +505,28 @@ gda_postgres_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint row
 			else
 				imodel->priv->tmp_row = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
 			*prow = imodel->priv->tmp_row;
-			return TRUE;
 		}
-		else
-			return !fetch_error;
 	}
+	return TRUE;
 }
 
 /*
  * Create a new filled #GdaRow object for the previous cursor row, and put it into *prow.
  *
  * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
- * never keeps a reference to it). Before a new #GdaRow gets created, the previous one, if set, is discarded.
+ * never keeps a reference to it).
  */
 static gboolean
 gda_postgres_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
 {
 	GdaPostgresRecordset *imodel = (GdaPostgresRecordset*) model;
 
-	if (*prow)
-		return TRUE;
-
 	if (row_is_in_current_pg_res (imodel, rownum)) {
 		if (imodel->priv->tmp_row)
 			set_prow_with_pg_res (imodel, imodel->priv->tmp_row, rownum - imodel->priv->pg_res_inf, error);
 		else
 			imodel->priv->tmp_row = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
 		*prow = imodel->priv->tmp_row;
-		return TRUE;
 	}
 	else {
 		gboolean fetch_error = FALSE;
@@ -549,27 +536,22 @@ gda_postgres_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint row
 			else
 				imodel->priv->tmp_row = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
 			*prow = imodel->priv->tmp_row;
-			return TRUE;
 		}
-		else
-			return !fetch_error;
 	}
+	return TRUE;
 }
 
 /*
  * Create a new filled #GdaRow object for the cursor row at position @rownum, and put it into *prow.
  *
  * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
- * never keeps a reference to it). Before a new #GdaRow gets created, the previous one, if set, is discarded.
+ * never keeps a reference to it).
  */
 static gboolean
 gda_postgres_recordset_fetch_at (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
 {
 	GdaPostgresRecordset *imodel = (GdaPostgresRecordset*) model;
 
-	if (*prow)
-		return TRUE;
-
 	if (imodel->priv->tmp_row) {
 		g_object_unref (imodel->priv->tmp_row);
 		imodel->priv->tmp_row = NULL;
@@ -578,18 +560,15 @@ gda_postgres_recordset_fetch_at (GdaDataSelect *model, GdaRow **prow, gint rownu
 	if (row_is_in_current_pg_res (imodel, rownum)) {
 		*prow = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
 		imodel->priv->tmp_row = *prow;
-		return TRUE;
 	}
 	else {
 		gboolean fetch_error = FALSE;
 		if (fetch_row_number_chunk (imodel, rownum, &fetch_error, error)) {
 			*prow = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
 			imodel->priv->tmp_row = *prow;
-			return TRUE;
 		}
-		else
-			return !fetch_error;
 	}
+	return TRUE;
 }
 
 
diff --git a/providers/skel-implementation/capi/gda-capi-recordset.c b/providers/skel-implementation/capi/gda-capi-recordset.c
index 5785419..3868299 100644
--- a/providers/skel-implementation/capi/gda-capi-recordset.c
+++ b/providers/skel-implementation/capi/gda-capi-recordset.c
@@ -244,17 +244,15 @@ gda_capi_recordset_fetch_nb_rows (GdaDataSelect *model)
 /*
  * Create a new filled #GdaRow object for the row at position @rownum, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row(). If new row objects are "given" to the GdaDataSelect implemantation
- * using that method, then this method should detect when all the data model rows have been analyzed
- * (when model->nb_stored_rows == model->advertized_nrows) and then possibly discard the API handle
- * as it won't be used anymore to fetch rows.
+ * NOTES:
+ * - @prow will NOT be NULL, but *prow WILL be NULL.
+ * - a new #GdaRow object has to be created.
+ * - memory management for that new GdaRow object is left to the implementation, which
+ *   can use gda_data_select_take_row() to "give" the GdaRow to @model (in this case
+ *   this method won't be called anymore for the same @rownum), or may decide to
+ *   keep a cache of GdaRow object and "recycle" them.
+ * - implementing this method is MANDATORY if the data model supports random access
+ * - this method is only called when data model is used in random access mode
  */
 static gboolean 
 gda_capi_recordset_fetch_random (GdaDataSelect *model, G_GNUC_UNUSED GdaRow **prow, G_GNUC_UNUSED gint rownum,
@@ -295,14 +293,15 @@ gda_capi_recordset_store_all (GdaDataSelect *model, GError **error)
 /*
  * Create a new filled #GdaRow object for the next cursor row, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * NOTES:
+ * - @prow will NOT be NULL, but *prow WILL be NULL.
+ * - a new #GdaRow object has to be created.
+ * - memory management for that new GdaRow object is left to the implementation, which
+ *   can use gda_data_select_take_row() to "give" the GdaRow to @model (in this case
+ *   this method won't be called anymore for the same @rownum), or may decide to
+ *   keep a cache of GdaRow object and "recycle" them.
+ * - implementing this method is MANDATORY
+ * - this method is only called when data model is used in cursor access mode
  */
 static gboolean 
 gda_capi_recordset_fetch_next (G_GNUC_UNUSED GdaDataSelect *model, G_GNUC_UNUSED GdaRow **prow,
@@ -318,14 +317,16 @@ gda_capi_recordset_fetch_next (G_GNUC_UNUSED GdaDataSelect *model, G_GNUC_UNUSED
 /*
  * Create a new filled #GdaRow object for the previous cursor row, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * NOTES:
+ * - @prow will NOT be NULL, but *prow WILL be NULL.
+ * - a new #GdaRow object has to be created.
+ * - memory management for that new GdaRow object is left to the implementation, which
+ *   can use gda_data_select_take_row() to "give" the GdaRow to @model (in this case
+ *   this method won't be called anymore for the same @rownum), or may decide to
+ *   keep a cache of GdaRow object and "recycle" them.
+ * - implementing this method is OPTIONAL (in this case the data model is assumed not to
+ *   support moving iterators backward)
+ * - this method is only called when data model is used in cursor access mode
  */
 static gboolean 
 gda_capi_recordset_fetch_prev (G_GNUC_UNUSED GdaDataSelect *model, G_GNUC_UNUSED GdaRow **prow,
@@ -341,14 +342,16 @@ gda_capi_recordset_fetch_prev (G_GNUC_UNUSED GdaDataSelect *model, G_GNUC_UNUSED
 /*
  * Create a new filled #GdaRow object for the cursor row at position @rownum, and put it into *prow.
  *
- * WARNING: @prow will NOT be NULL, but *prow may or may not be NULL:
- *  -  If *prow is NULL then a new #GdaRow object has to be created, 
- *  -  and otherwise *prow contains a #GdaRow object which has already been created 
- *     (through a call to this very function), and in this case it should not be modified
- *     but the function may return FALSE if an error occurred.
- *
- * Memory management for that new GdaRow object is left to the implementation, which
- * can use gda_data_select_take_row().
+ * NOTES:
+ * - @prow will NOT be NULL, but *prow WILL be NULL.
+ * - a new #GdaRow object has to be created.
+ * - memory management for that new GdaRow object is left to the implementation, which
+ *   can use gda_data_select_take_row() to "give" the GdaRow to @model (in this case
+ *   this method won't be called anymore for the same @rownum), or may decide to
+ *   keep a cache of GdaRow object and "recycle" them.
+ * - implementing this method is OPTIONAL and usefull only if there is a method quicker
+ *   than iterating one step at a time to the correct position.
+ * - this method is only called when data model is used in cursor access mode
  */
 static gboolean 
 gda_capi_recordset_fetch_at (G_GNUC_UNUSED GdaDataSelect *model, G_GNUC_UNUSED GdaRow **prow,
@@ -360,4 +363,3 @@ gda_capi_recordset_fetch_at (G_GNUC_UNUSED GdaDataSelect *model, G_GNUC_UNUSED G
 
 	return TRUE;
 }
-



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]