[libgda] Added the GDA_STATEMENT_MODEL_OFFLINE flag and gda_data_select_prepare_for_offline()



commit b386625c607ba9d901ab85ddf90e7fdf7784cc4c
Author: Vivien Malerba <malerba gnome-db org>
Date:   Sun Jul 22 13:08:20 2012 +0200

    Added the GDA_STATEMENT_MODEL_OFFLINE flag and gda_data_select_prepare_for_offline()

 doc/C/libgda-sections.txt                   |    1 +
 doc/C/prov-writing-recordsets.xml           |   45 +++++++---
 libgda/gda-connection.c                     |   53 ++++++++---
 libgda/gda-data-model.h                     |    9 ++-
 libgda/gda-data-select.c                    |  133 +++++++++++++++++++++++----
 libgda/gda-data-select.h                    |   18 +++-
 libgda/gda-statement.h                      |    4 +-
 libgda/libgda.symbols                       |    1 +
 libgda/thread-wrapper/gda-thread-provider.c |   18 +++-
 9 files changed, 226 insertions(+), 56 deletions(-)
---
diff --git a/doc/C/libgda-sections.txt b/doc/C/libgda-sections.txt
index 8c7aed4..d216808 100644
--- a/doc/C/libgda-sections.txt
+++ b/doc/C/libgda-sections.txt
@@ -1539,6 +1539,7 @@ gda_data_select_compute_modification_statements_ext
 gda_data_select_compute_columns_attributes
 gda_data_select_rerun
 gda_data_select_add_exception
+gda_data_select_prepare_for_offline
 <SUBSECTION Standard>
 GDA_IS_DATA_SELECT
 GDA_DATA_SELECT
diff --git a/doc/C/prov-writing-recordsets.xml b/doc/C/prov-writing-recordsets.xml
index 6231779..a93104b 100644
--- a/doc/C/prov-writing-recordsets.xml
+++ b/doc/C/prov-writing-recordsets.xml
@@ -11,18 +11,18 @@
   </para>
   <para>
     That data model implementation simplifies the implementation of provider's recordsets by having them implement only a
-    few virtual methods, and offering some services.
+    few virtual methods.
   </para>
   <para>
-    The data model represents each row in a separate <link linkend="GdaRow">GdaRow</link> object, and virtual
+    The data model represents each row as a separate <link linkend="GdaRow">GdaRow</link> object, and virtual
     methods to retrieve rows have to return new (and correctly initialized) objects of that class. The referencing
     of these new objects is left up to the implementation which:
     <itemizedlist>
       <listitem>
-	<para>can rely on the <link linkend="GdaRow">GdaRow</link> implementation to keep them, using
-	  the <link linkend="gda-data-select-take-row">gda_data_select_take_row ()</link> method for each row object created which
-	  is the easiest, the fastest as once created, the rows are available for further usage, 
-	  but will consume more memory with each row read from the model.</para>
+	<para>can use the <link linkend="gda-data-select-take-row">gda_data_select_take_row ()</link> method for
+	on row object created which transfers the ownership of the <link linkend="GdaRow">GdaRow</link> object to the
+	data model (this is the easiest and the fastest as once created, the rows are available for further usage, 
+	but will consume more memory with each row read from the model).</para>
       </listitem>
       <listitem>
 	<para>can keep the references to these objects to the subclass implementation which will consume less memory but
@@ -30,7 +30,7 @@
       </listitem>
       <listitem>
 	<para>can do a mix of the two solutions above: monitor the memory usage for the data model to 
-	  enable to "cache" some rows and discard some when memory is needed</para>
+	enable to "cache" some rows and discard some when memory is needed</para>
       </listitem>
     </itemizedlist>
     Note that the methods mentioned in the 1st and 3rd items should be reserved for random data access mode, 
@@ -39,12 +39,16 @@
   <sect2>
     <title>fetch_nb_rows()</title>
     <para>
-      This method is called when the user calls <link linkend="gda-data-model-get-n-rows">gda_data_model_get_n_rows ()</link>.
+      This method is called when the user calls
+      <link linkend="gda-data-model-get-n-rows">gda_data_model_get_n_rows ()</link>.
     </para>
     <para>
-      Note that the number of rows of the data model may not be known until the cursor has reached the last row of
-      the recordset.
-      Once known, the number of rows can be stored in the <structfield>advertized_nrows</structfield>'s member of the
+      Its implementation is mandatory if the data model supports random access.
+    </para>
+    <para>
+      Note about the the data model's number of rows: it may not be known until the cursor has reached the last row of
+      the recordset, but once known, the number of rows can be stored in the
+      <structfield>advertized_nrows</structfield>'s member of the
       <link linkend="GdaDataSelect">GdaDataSelect</link> object.
     </para>
   </sect2>
@@ -56,6 +60,9 @@
       and is used only when the data access mode for the data model is random (that is not cursor based) (i.e. when data
       access is cursor based, this method will not be called).
     </para>
+    <para>
+      Its implementation is mandatory if the data model supports random access.
+    </para>
     <para>Also it is safe to assume that when called, the
     <parameter>prow</parameter> parameter will not be NULL, and that <parameter>*prow</parameter> is actually NULL.
     </para>
@@ -64,10 +71,13 @@
   <sect2>
     <title>store_all()</title>
     <para>
-      This method is called when the user sets the "store-all-rows" to TRUE. It has the effect of forcing the creation
+      This method is called when the user sets the "store-all-rows" to TRUE or calls <link linkend="gda-data-select-prepare-for-offline">gda_data_select_prepare_for_offline</link>. It has the effect of forcing the creation
       of a <link linkend="GdaRow">GdaRow</link> object for each row of the data model (thus increasing the memory consumption
       but reducing further access times). It is available only when the data access mode for the data model is random 
-      (that is not cursor based). When data access is cursor based, this method will not be called.
+      (that is not cursor based). When random data access is not supported, this method will not be called.
+    </para>
+    <para>
+      Its implementation is optional.
     </para>
   </sect2>
 
@@ -81,6 +91,9 @@
     <para>This method is not used when data is accessed in a random way. Also it is safe to assume that when called, the
     <parameter>prow</parameter> parameter will not be NULL, and that <parameter>*prow</parameter> is actually NULL.
     </para>
+    <para>
+      Its implementation is mandatory if the data model supports forward cursor access.
+    </para>
   </sect2>
 
   <sect2>
@@ -94,6 +107,9 @@
     <para>This method is not used when data is accessed in a random way. Also it is safe to assume that when called, the
     <parameter>prow</parameter> parameter will not be NULL, and that <parameter>*prow</parameter> is actually NULL.
     </para>
+    <para>
+      Its implementation is mandatory if the data model supports backward cursor access.
+    </para>
   </sect2>
 
   <sect2>
@@ -103,5 +119,8 @@
       and there is a shorter way of getting to a
       specific row than having to call the fetch_next() or fetch_prev() methods several times.
     </para>
+    <para>
+      Its implementation is mandatory if the data model supports forward or backward cursor access.
+    </para>
   </sect2>
 </chapter>
diff --git a/libgda/gda-connection.c b/libgda/gda-connection.c
index 7cfd184..5747afe 100644
--- a/libgda/gda-connection.c
+++ b/libgda/gda-connection.c
@@ -3516,6 +3516,11 @@ gda_connection_statement_execute_v (GdaConnection *cnc, GdaStatement *stmt, GdaS
 		if (timer)
 			add_exec_time_to_object (obj, timer);
 		update_meta_store_after_statement_exec (cnc, stmt, params);
+		if (GDA_IS_DATA_SELECT (obj) && (model_usage & GDA_STATEMENT_MODEL_OFFLINE) &&
+		    ! gda_data_select_prepare_for_offline ((GdaDataSelect*) obj, error)) {
+			g_object_unref (obj);
+			obj = NULL;
+		}
 	}
 	gda_connection_unlock ((GdaLockable*) cnc);
 	g_object_unref ((GObject*) cnc);
@@ -3886,12 +3891,19 @@ gda_connection_statement_execute_select_fullv (GdaConnection *cnc, GdaStatement
 		add_exec_time_to_object ((GObject*) model, timer);
 	if (timer)
 		g_timer_destroy (timer);
-	if (model && !GDA_IS_DATA_MODEL (model)) {
-		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_STATEMENT_TYPE_ERROR,
-			      "%s", _("Statement is not a selection statement"));
-		g_object_unref (model);
-		model = NULL;
-		update_meta_store_after_statement_exec (cnc, stmt, params);
+	if (model) {
+		if (GDA_IS_DATA_SELECT (model) && (model_usage & GDA_STATEMENT_MODEL_OFFLINE) &&
+		    ! gda_data_select_prepare_for_offline ((GdaDataSelect*) model, error)) {
+			g_object_unref (model);
+			model = NULL;
+		}
+		else if (!GDA_IS_DATA_MODEL (model)) {
+			g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_STATEMENT_TYPE_ERROR,
+				     "%s", _("Statement is not a selection statement"));
+			g_object_unref (model);
+			model = NULL;
+			update_meta_store_after_statement_exec (cnc, stmt, params);
+		}
 	}
 	return model;
 }
@@ -3974,12 +3986,19 @@ gda_connection_statement_execute_select_full (GdaConnection *cnc, GdaStatement *
 
 	if (model && timer)
 		add_exec_time_to_object ((GObject*) model, timer);
-	if (model && !GDA_IS_DATA_MODEL (model)) {
-		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_STATEMENT_TYPE_ERROR,
-			      "%s", _("Statement is not a selection statement"));
-		g_object_unref (model);
-		model = NULL;
-		update_meta_store_after_statement_exec (cnc, stmt, params);
+	if (model) {
+		if (GDA_IS_DATA_SELECT (model) && (model_usage & GDA_STATEMENT_MODEL_OFFLINE) &&
+		    ! gda_data_select_prepare_for_offline ((GdaDataSelect*) model, error)) {
+			g_object_unref (model);
+			model = NULL;
+		}
+		else if (!GDA_IS_DATA_MODEL (model)) {
+			g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_STATEMENT_TYPE_ERROR,
+				     "%s", _("Statement is not a selection statement"));
+			g_object_unref (model);
+			model = NULL;
+			update_meta_store_after_statement_exec (cnc, stmt, params);
+		}
 	}
 	if (timer)
 		g_timer_destroy (timer);
@@ -4081,7 +4100,15 @@ gda_connection_repetitive_statement_execute (GdaConnection *cnc, GdaRepetitiveSt
 			if (timer)
 				add_exec_time_to_object (obj, timer);
 			update_meta_store_after_statement_exec (cnc, stmt, (GdaSet*) list->data);
-			retlist = g_slist_prepend (retlist, obj);
+
+			if (GDA_IS_DATA_SELECT (obj) && (model_usage & GDA_STATEMENT_MODEL_OFFLINE) &&
+			    ! gda_data_select_prepare_for_offline ((GdaDataSelect*) obj, error)) {
+				g_object_unref (obj);
+				obj = NULL;
+			}
+
+			if (obj)
+				retlist = g_slist_prepend (retlist, obj);
 		}
 		if (timer)
 			g_timer_destroy (timer);
diff --git a/libgda/gda-data-model.h b/libgda/gda-data-model.h
index 9459e57..f853226 100644
--- a/libgda/gda-data-model.h
+++ b/libgda/gda-data-model.h
@@ -140,6 +140,10 @@ struct _GdaDataModelIface {
  * column have the same type, and all the data in each row have the same semantic meaning. The #GdaDataModel is
  * actually an interface implemented by other objects to support various kinds of data storage and operations.
  *
+ * When a SELECT statement is executed using an opened #GdaConnection, the returned value (if no error occurred)
+ * is a #GdaDataSelect object which implements the #GdaDataModel interface. Please see the #GdaDataSelect's
+ * documentation for more information.
+ *
  * Depending on the real implementation, the contents of data models may be modified by the user using functions
  * provided by the model. The actual operations a data model permits can be known using the 
  * gda_data_model_get_access_flags() method.
@@ -150,8 +154,9 @@ struct _GdaDataModelIface {
  * <itemizedlist>
  *   <listitem><para>Random access to a data model's contents is done using gda_data_model_get_value_at(), or using
  *       one or more #GdaDataModelIter object(s);</para></listitem>
- *   <listitem><para>Cursor access to a data model's contents is done using a #GdaDataModelIter object (only one can be created),
- *       it is <emphasis>not possible</emphasis> to use gda_data_model_get_value_at() in this mode.</para></listitem>
+ *   <listitem><para>Cursor access to a data model's contents is done using a #GdaDataModelIter object. If this mode is
+ *       the only supported, then only one #GdaDataModelIter object can be created and
+ *       it is <emphasis>not possible</emphasis> to use gda_data_model_get_value_at() in this case.</para></listitem>
  * </itemizedlist>
  *
  * Random access data models are easier to use since picking a value is very simple using the gda_data_model_get_value_at(),
diff --git a/libgda/gda-data-select.c b/libgda/gda-data-select.c
index def7b5b..3bf3967 100644
--- a/libgda/gda-data-select.c
+++ b/libgda/gda-data-select.c
@@ -703,15 +703,8 @@ gda_data_select_set_property (GObject *object,
 			break;
 		}
 		case PROP_ALL_STORED:
-			if (g_value_get_boolean (value)) {
-				if ((model->advertized_nrows < 0) && CLASS (model)->fetch_nb_rows)
-					CLASS (model)->fetch_nb_rows (model);
-
-				if (model->nb_stored_rows != model->advertized_nrows) {
-					if (CLASS (model)->store_all)
-						CLASS (model)->store_all (model, NULL);
-				}
-			}
+			if (g_value_get_boolean (value))
+				gda_data_select_prepare_for_offline (model, NULL);
 			break;
 		case PROP_PARAMS: {
 			GdaSet *set;
@@ -828,17 +821,24 @@ gda_data_select_get_property (GObject *object,
  *
  * Stores @row into @model, externally advertized at row number @rownum (if no row has been removed).
  * The reference to @row is stolen.
+ *
+ * This function is used by database provider's implementations
  */
 void
 gda_data_select_take_row (GdaDataSelect *model, GdaRow *row, gint rownum)
 {
 	gint tmp, *ptr;
+	GdaRow *erow;
 	g_return_if_fail (GDA_IS_DATA_SELECT (model));
 	g_return_if_fail (GDA_IS_ROW (row));
 
 	tmp = rownum;
-	if (g_hash_table_lookup (model->priv->sh->index, &tmp))
-		g_error ("INTERNAL error: row %d already exists, aborting", rownum);
+	erow = g_hash_table_lookup (model->priv->sh->index, &tmp);
+	if (erow) {
+		if (row != erow)
+			g_object_unref (row);
+		return;
+	}
 
 	ptr = g_new (gint, 2);
 	ptr [0] = rownum;
@@ -1019,7 +1019,7 @@ param_name_to_int (const gchar *pname, gint *result, gboolean *old_val)
  * gda_data_select_set_modification_statement_sql:
  * @model: a #GdaDataSelect data model
  * @sql: an SQL text
- * @error: a place to store errors, or %NULL
+ * @error: (allow-none): a place to store errors, or %NULL
  *
  * Offers the same feature as gda_data_select_set_modification_statement() but using an SQL statement.
  *
@@ -1101,7 +1101,7 @@ check_acceptable_statement (GdaDataSelect *model, GError **error)
  * gda_data_select_set_modification_statement:
  * @model: a #GdaDataSelect data model
  * @mod_stmt: a #GdaStatement (INSERT, UPDATE or DELETE)
- * @error: a place to store errors, or %NULL
+ * @error: (allow-none): a place to store errors, or %NULL
  *
  * Informs @model that it should allow modifications to the data in some columns and some rows
  * using @mod_stmt to propagate those modifications into the database.
@@ -1432,7 +1432,7 @@ gda_data_select_set_modification_statement (GdaDataSelect *model, GdaStatement *
 /**
  * gda_data_select_compute_modification_statements:
  * @model: a #GdaDataSelect data model
- * @error: a place to store errors, or %NULL
+ * @error: (allow-none): a place to store errors, or %NULL
  *
  * Makes @model try to compute INSERT, UPDATE and DELETE statements to be used when modifying @model's contents.
  * Note: any modification statement set using gda_data_select_set_modification_statement() will first be unset
@@ -1453,7 +1453,7 @@ gda_data_select_compute_modification_statements (GdaDataSelect *model, GError **
  * gda_data_select_compute_modification_statements_ext:
  * @model: a #GdaDataSelect data model
  * @cond_type: the type of condition for the modifications where one row only should be identified
- * @error: a place to store errors, or %NULL
+ * @error: (allow-none): a place to store errors, or %NULL
  *
  * Makes @model try to compute INSERT, UPDATE and DELETE statements to be used when modifying @model's contents.
  * Note: any modification statement set using gda_data_select_set_modification_statement() will first be unset
@@ -1551,7 +1551,7 @@ row_selection_condition_foreach_func (GdaSqlAnyPart *part, G_GNUC_UNUSED gpointe
  * gda_data_select_set_row_selection_condition: (skip)
  * @model: a #GdaDataSelect data model
  * @expr: (transfer none): a #GdaSqlExpr expression
- * @error: a place to store errors, or %NULL
+ * @error: (allow-none): a place to store errors, or %NULL
  *
  * Offers the same features as gda_data_select_set_row_selection_condition_sql() but using a #GdaSqlExpr
  * structure instead of an SQL syntax.
@@ -1590,7 +1590,7 @@ gda_data_select_set_row_selection_condition  (GdaDataSelect *model, GdaSqlExpr *
  * gda_data_select_set_row_selection_condition_sql:
  * @model: a #GdaDataSelect data model
  * @sql_where: an SQL condition (without the WHERE keyword)
- * @error: a place to store errors, or %NULL
+ * @error: (allow-none): a place to store errors, or %NULL
  *
  * Specifies the SQL condition corresponding to the WHERE part of a SELECT statement which would
  * return only 1 row (the expression of the primary key).
@@ -1665,7 +1665,7 @@ gda_data_select_set_row_selection_condition_sql (GdaDataSelect *model, const gch
 /**
  * gda_data_select_compute_row_selection_condition:
  * @model: a #GdaDataSelect object
- * @error: a place to store errors, or %NULL
+ * @error: (allow-none): a place to store errors, or %NULL
  *
  * Offers the same features as gda_data_select_set_row_selection_condition() but the expression
  * is computed from the meta data associated to the connection being used when @model was created.
@@ -3606,7 +3606,7 @@ set_column_properties_from_select_stmt (GdaDataSelect *model, GdaConnection *cnc
 /**
  * gda_data_select_compute_columns_attributes:
  * @model: a #GdaDataSelect data model
- * @error: a place to store errors, or %NULL
+ * @error: (allow-none): a place to store errors, or %NULL
  *
  * Computes correct attributes for each of @model's columns, which includes the "NOT NULL" attribute, the
  * default value, the precision and scale for numeric values.
@@ -3639,7 +3639,7 @@ gda_data_select_compute_columns_attributes (GdaDataSelect *model, GError **error
 /**
  * gda_data_select_rerun:
  * @model: a #GdaDataSelect data model
- * @error: a place to store errors, or %NULL
+ * @error: (allow-none): a place to store errors, or %NULL
  *
  * Requests that @model be re-run to have an updated result. If an error occurs,
  * then @model will not be changed.
@@ -3673,6 +3673,14 @@ gda_data_select_rerun (GdaDataSelect *model, GError **error)
 										   types,
 										   error);
 	g_free (types);
+
+	/* post treatment */
+	if (new_model && (model->priv->sh->usage_flags & GDA_STATEMENT_MODEL_OFFLINE) &&
+	    ! gda_data_select_prepare_for_offline (new_model, error)) {
+		g_object_unref (new_model);
+		return FALSE;
+	}
+
 	if (!new_model) {
 		/* FIXME: clear all the rows in @model, and emit the "reset" signal */
 		return FALSE;
@@ -3766,3 +3774,88 @@ gda_data_select_rerun (GdaDataSelect *model, GError **error)
 
 	return TRUE;
 }
+
+/**
+ * gda_data_select_prepare_for_offline:
+ * @model: a #GdaDataSelect object
+ * @error: (allow-none): a place to store errors, or %NULL
+ *
+ * Use this method to make sure all the data contained in the data model are stored on the client
+ * side (and that no subsquent call to the server will be necessary to access that data), at the cost of
+ * a higher memory consumption.
+ *
+ * This method is useful in the following situations:
+ * <itemizedlist>
+ *   <listitem><para>You need to disconnect from the server and continue to use the data in the data model</para></listitem>
+ *   <listitem><para>You need to make sure the data in the data model can be used even though the connection to the server may be used for other purposes (for example executing other queries)</para></listitem>
+ * </itemizedlist>
+ *
+ * Note that this method will fail if:
+ * <itemizedlist>
+ *   <listitem><para>the data model contains any blobs (because blobs reading requires acces to the server);
+ *     binary values are Ok, though.</para></listitem>
+ *   <listitem><para>the data model has been modified since it was created</para></listitem>
+ * </itemizedlist>
+ *
+ * Returns: %TRUE if no error occurred
+ *
+ * Since: 5.2.0
+ */
+gboolean
+gda_data_select_prepare_for_offline (GdaDataSelect *model, GError **error)
+{
+	g_return_val_if_fail (GDA_IS_DATA_SELECT (model), FALSE);
+
+	/* checks */
+	gint i, ncols;
+	if (! (model->priv->sh->usage_flags & GDA_STATEMENT_MODEL_RANDOM_ACCESS)) {
+		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_ACCESS_ERROR,
+			     "%s", _("Data model does not support random access"));
+		return FALSE;
+	}
+	if (model->priv->sh->upd_rows || model->priv->sh->del_rows) {
+		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_ACCESS_ERROR,
+			     "%s", _("Data model has been modified"));
+		return FALSE;
+	}
+	ncols = gda_data_model_get_n_columns ((GdaDataModel*) model);
+	for (i = 0; i < ncols; i++) {
+		GdaColumn *gdacol;
+		gdacol = gda_data_model_describe_column ((GdaDataModel*) model, i);
+		if (gda_column_get_g_type (gdacol) == GDA_TYPE_BLOB) {
+			g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_ACCESS_ERROR,
+			     "%s", _("Data model contains BLOBs"));
+			return FALSE;
+		}
+	}
+
+	/* fetching data */
+	if (model->advertized_nrows < 0) {
+		if (CLASS (model)->fetch_nb_rows)
+			CLASS (model)->fetch_nb_rows (model);
+		if (model->advertized_nrows < 0) {
+			g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_ACCESS_ERROR,
+				     "%s", _("Can't get the number of rows of data model"));
+			return FALSE;
+		}
+	}
+
+	if (model->nb_stored_rows != model->advertized_nrows) {
+		if (CLASS (model)->store_all) {
+			if (! CLASS (model)->store_all (model, error))
+				return FALSE;
+		}
+	}
+
+	/* final check/complement */
+	for (i = 0; i < model->advertized_nrows; i++) {
+		if (!g_hash_table_lookup (model->priv->sh->index, &i)) {
+			GdaRow *prow;
+			if (! CLASS (model)->fetch_at (model, &prow, i, error))
+				return FALSE;
+			g_assert (prow);
+			gda_data_select_take_row (model, prow, i);
+		}
+	}
+	return TRUE;
+}
diff --git a/libgda/gda-data-select.h b/libgda/gda-data-select.h
index e4865b9..0d580a1 100644
--- a/libgda/gda-data-select.h
+++ b/libgda/gda-data-select.h
@@ -70,7 +70,7 @@ struct _GdaDataSelect {
 	/* read only information */
 	GdaPStmt         *prep_stmt; /* use the "prepared-stmt" property to set this */
 	gint              nb_stored_rows; /* number of GdaRow objects currently stored */
-	gint              advertized_nrows; /* set when the number of rows becomes known, -1 untill then */
+	gint              advertized_nrows; /* set when the number of rows becomes known, -1 until then */
 
 	/*< private >*/
 	/* Padding for future expansion */
@@ -125,13 +125,26 @@ struct _GdaDataSelectClass {
  *  As the <link linkend="GdaDataModel">GdaDataModel</link> interface is implemented, consult the API
  *  to access and modify the data held in a <link linkend="GdaDataSelect">GdaDataSelect</link> object.
  *
- *  The default behaviour however is to disallow modifications, and this section documents how to characterize
+ * Depending on the requested data model usage (as specified by the "model_usage" parameter of the
+ * <link linkend="gda-connection-statement-execute">gda_connection_statement_execute()</link> and similar
+ * methods, the #GdaDataSelect will allow random access, cursor based access or both.
+ *
+ * Also, when later you'll be reading the data contained in a #GdaDataSelect object, depending on the actual
+ * implementation (which adapts to the API providede by the database server), some calls to the database server
+ * may be necessary to actually obtain the data. If this behaviour is not the one intended and if you need to
+ * access the data without having to contact the database server (for example for performances reasons), then
+ * you can use the <link linkend="gda_data_select_prepare_for_offline">gda_data_select_prepare_for_offline()</link>
+ * method or specify the <link linkend="GDA-STATEMENT-MODEL-OFFLINE:CAPS">GDA_STATEMENT_MODEL_OFFLINE</link>
+ * flag when executing the SELECT statement.
+ *
+ *  The default behaviour however is to disallow modifications, and this section documents how to parametrize
  *  a <link linkend="GdaDataSelect">GdaDataSelect</link> to allow modifications. Once this is done, any modification
  *  done to the data model will be propagated to the modified table in the database using INSERT, UPDATE or DELETE
  *  statements.
  *
  *  After any modification, it is still possible to read values from the data model (even values for rows which have
  *  been modified or inserted). The data model might then execute some SELECT statement to fetch some actualized values.
+ *
  *  Note: there is a corner case where a modification made to a row would make the row not selected at first in the data model
  *  (for example is the original SELECT statement included a clause <![CDATA["WHERE id < 100"]]> and the modification sets the 
  *  <![CDATA["id"]]> value to 110), then the row will still be in the data model even though it would not be if the SELECT statement
@@ -163,6 +176,7 @@ gboolean       gda_data_select_compute_columns_attributes      (GdaDataSelect *m
 GdaConnection *gda_data_select_get_connection                  (GdaDataSelect *model);
 
 gboolean       gda_data_select_rerun                           (GdaDataSelect *model, GError **error);
+gboolean       gda_data_select_prepare_for_offline             (GdaDataSelect *model, GError **error);
 
 G_END_DECLS
 
diff --git a/libgda/gda-statement.h b/libgda/gda-statement.h
index cd65dbe..70e724e 100644
--- a/libgda/gda-statement.h
+++ b/libgda/gda-statement.h
@@ -63,6 +63,7 @@ typedef enum
  * @GDA_STATEMENT_MODEL_CURSOR_BACKWARD: access to the data model will be done using a cursor moving backward
  * @GDA_STATEMENT_MODEL_CURSOR: access to the data model will be done using a cursor (moving both forward and backward)
  * @GDA_STATEMENT_MODEL_ALLOW_NOPARAM: specifies that the data model should be executed even if some parameters required to execute it are invalid (in this case the data model will have no row, and will automatically be re-run when the missing parameters are once again valid)
+ * @GDA_STATEMENT_MODEL_OFFLINE: specifies that the data model's contents will be fully loaded into the client side (the memory of the process using &libgda;), not requiring the server any more to access the data (the default behaviour is to access the server any time data is to be read, and data is cached in memory). This flag is useful only if used in conjunction with the GDA_STATEMENT_MODEL_RANDOM_ACCESS flag (otherwise an error will be returned).
  *
  * These flags specify how the #GdaDataModel returned when executing a #GdaStatement will be used
  */
@@ -71,7 +72,8 @@ typedef enum {
 	GDA_STATEMENT_MODEL_CURSOR_FORWARD  = 1 << 1,
 	GDA_STATEMENT_MODEL_CURSOR_BACKWARD = 1 << 2,
 	GDA_STATEMENT_MODEL_CURSOR          = GDA_STATEMENT_MODEL_CURSOR_FORWARD | GDA_STATEMENT_MODEL_CURSOR_BACKWARD,
-	GDA_STATEMENT_MODEL_ALLOW_NOPARAM   = 1 << 3
+	GDA_STATEMENT_MODEL_ALLOW_NOPARAM   = 1 << 3,
+	GDA_STATEMENT_MODEL_OFFLINE         = 1 << 4
 } GdaStatementModelUsage;
 
 /**
diff --git a/libgda/libgda.symbols b/libgda/libgda.symbols
index 2c70394..2ce5898 100644
--- a/libgda/libgda.symbols
+++ b/libgda/libgda.symbols
@@ -336,6 +336,7 @@
 	gda_data_select_get_connection
 	gda_data_select_get_stored_row
 	gda_data_select_get_type
+	gda_data_select_prepare_for_offline
 	gda_data_select_rerun
 	gda_data_select_set_columns
 	gda_data_select_set_modification_statement
diff --git a/libgda/thread-wrapper/gda-thread-provider.c b/libgda/thread-wrapper/gda-thread-provider.c
index 55d0061..2a8f6dd 100644
--- a/libgda/thread-wrapper/gda-thread-provider.c
+++ b/libgda/thread-wrapper/gda-thread-provider.c
@@ -1676,11 +1676,19 @@ sub_thread_execute_statement (ExecuteStatementData *data, GError **error)
 #endif
 
 	if (GDA_IS_DATA_MODEL (retval)) {
-		/* substitute the GdaDataSelect with a GdaThreadRecordset */
-		GdaDataModel *model;
-		model = _gda_thread_recordset_new (data->real_cnc, data->wrapper, GDA_DATA_MODEL (retval));
-		g_object_unref (retval);
-		retval = (GObject*) model;
+		if (GDA_IS_DATA_SELECT (retval) && (data->model_usage & GDA_STATEMENT_MODEL_OFFLINE) &&
+		    ! gda_data_select_prepare_for_offline ((GdaDataSelect*) retval, error)) {
+			g_object_unref (retval);
+			retval = NULL;
+		}
+
+		if (retval) {
+			/* substitute the GdaDataSelect with a GdaThreadRecordset */
+			GdaDataModel *model;
+			model = _gda_thread_recordset_new (data->real_cnc, data->wrapper, GDA_DATA_MODEL (retval));
+			g_object_unref (retval);
+			retval = (GObject*) model;
+		}
 	}
 
 	return retval;



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