libgda r3373 - in trunk: . doc/C doc/C/tmpl libgda libgda/providers-support libgda/sqlite libgda/thread-wrapper po providers/postgres tests/multi-threading tools



Author: vivien
Date: Tue Apr  7 19:10:50 2009
New Revision: 3373
URL: http://svn.gnome.org/viewvc/libgda?rev=3373&view=rev

Log:
2009-04-07  Vivien Malerba <malerba gnome-db org>

	* configure.in:
	* libgda/Makefile.am:
	* libgda/thread-wrapper/Makefile.am: new directory
	* libgda/thread-wrapper/gda-thread-wrapper.[ch]: new object which
	allows one to execute functions in a private sub thread from any
	other thread.
	* libgda/thread-wrapper/gda-thread-provider.[ch]:
	* libgda/thread-wrapper/gda-thread-recordset.[ch]: new built-in
	pseudo provider to constrain a GdaConnection's usage within a private
	sub thread, making its usage completely thread safe
	* libgda/gda-connection-sqlite.h: file renamed to
	libgda/gda-connection-internal.h
	* libgda/gda-connection.[ch]:
	  - added a GDA_CONNECTION_OPTIONS_THREAD_SAFE flag to use when opening
	    a connection
	  - added asynchronous statement execution methods:
	    gda_connection_async_statement_execute(), gda_connection_async_fetch_result()
	    and gda_connection_async_cancel()
	  - the GdaConnection can now be created by the new GdaThreadProvider pseudo
	    provider to wrap a real GdaConnection object in a private sub thread
	* libgda/providers-support/gda-data-select-priv.h:
	* libgda/gda-data-select.c: adaptations to be used by the new GdaThreadRecordset
	* libgda/gda-meta-store.c:
	  - now thread safe
	  - corrections to gda_meta_store_schema_get_structure()
	* libgda/gda-server-provider.[ch]: new virtual method to handle async. requests
	* libgda/sqlite/gda-sqlite-recordset.c: minor correction
	* po/POTFILES.in: added new files
	* providers/postgres/gda-postgres-util.c:
	* providers/postgres/gda-postgres.h: make sure a PostgreSQL connection is only
	used by one statement at a time
	* doc/C: doc. updates
	* tests/multi-threading/dummy-object.[ch]:
	* tests/multi-threading/check_wrapper.c: new test for the GdaThreadWrapper object
	* tests/multi-threading/check_threaded_cnc: new test for connections opened with
	the GDA_CONNECTION_OPTIONS_THREAD_SAFE flag
	* tools/web-server.c: don't check for uint8_t


Added:
   trunk/doc/C/tmpl/gda-thread-connection.sgml
   trunk/doc/C/tmpl/gda-thread-wrapper.sgml
   trunk/libgda/gda-connection-internal.h   (contents, props changed)
      - copied, changed from r3371, /trunk/libgda/gda-connection-sqlite.h
   trunk/libgda/thread-wrapper/   (props changed)
   trunk/libgda/thread-wrapper/Makefile.am
   trunk/libgda/thread-wrapper/gda-thread-provider.c
   trunk/libgda/thread-wrapper/gda-thread-provider.h
   trunk/libgda/thread-wrapper/gda-thread-recordset.c
   trunk/libgda/thread-wrapper/gda-thread-recordset.h
   trunk/libgda/thread-wrapper/gda-thread-wrapper.c
   trunk/libgda/thread-wrapper/gda-thread-wrapper.h
   trunk/libgda/thread-wrapper/thread-wrapper.dia   (contents, props changed)
   trunk/tests/multi-threading/check_threaded_cnc.c
   trunk/tests/multi-threading/check_wrapper.c
   trunk/tests/multi-threading/dummy-object.c
   trunk/tests/multi-threading/dummy-object.h
Removed:
   trunk/libgda/gda-connection-sqlite.h
Modified:
   trunk/ChangeLog
   trunk/configure.in
   trunk/doc/C/libgda-4.0-docs.sgml
   trunk/doc/C/libgda-4.0-sections.txt
   trunk/doc/C/libgda-4.0.types.in
   trunk/doc/C/tmpl/gda-connection.sgml
   trunk/doc/C/tmpl/gda-data-model-iter.sgml
   trunk/doc/C/tmpl/gda-meta-store.sgml
   trunk/doc/C/tmpl/gda-server-provider.sgml
   trunk/doc/C/tmpl/gda-sql-parser.sgml
   trunk/libgda/Makefile.am
   trunk/libgda/gda-connection.c
   trunk/libgda/gda-connection.h
   trunk/libgda/gda-data-select.c
   trunk/libgda/gda-meta-store.c
   trunk/libgda/gda-server-provider.c
   trunk/libgda/gda-server-provider.h
   trunk/libgda/libgda.symbols
   trunk/libgda/providers-support/gda-data-select-priv.h
   trunk/libgda/sqlite/gda-sqlite-recordset.c
   trunk/po/POTFILES.in
   trunk/providers/postgres/gda-postgres-util.c
   trunk/providers/postgres/gda-postgres.h
   trunk/tests/multi-threading/   (props changed)
   trunk/tests/multi-threading/Makefile.am
   trunk/tests/multi-threading/common.c
   trunk/tests/multi-threading/common.h
   trunk/tools/web-server.c

Modified: trunk/configure.in
==============================================================================
--- trunk/configure.in	(original)
+++ trunk/configure.in	Tue Apr  7 19:10:50 2009
@@ -50,6 +50,7 @@
 AC_SUBST(INTLTOOL_XML_RULE)
 AC_CHECK_SIZEOF(unsigned int,0)
 AC_CHECK_SIZEOF(unsigned long int,0)
+AC_CHECK_TYPE(uint8_t, unsigned char)
 
 dnl **********************
 dnl Cross compilation test
@@ -1485,6 +1486,7 @@
 libgda/sqlite/Makefile
 libgda/sqlite/sqlite-src/Makefile
 libgda/sqlite/virtual/Makefile
+libgda/thread-wrapper/Makefile
 providers/Makefile
 providers/bdb/Makefile
 providers/bdb/libgda-bdb-4.0.pc

Modified: trunk/doc/C/libgda-4.0-docs.sgml
==============================================================================
--- trunk/doc/C/libgda-4.0-docs.sgml	(original)
+++ trunk/doc/C/libgda-4.0-docs.sgml	Tue Apr  7 19:10:50 2009
@@ -124,6 +124,7 @@
 <!ENTITY libgda-GdaTreeMgrTables SYSTEM "xml/gda-tree-mgr-tables.xml">
 <!ENTITY libgda-GdaTreeMgrColumns SYSTEM "xml/gda-tree-mgr-columns.xml">
 <!ENTITY libgda-GdaSqlBuilder SYSTEM "xml/gda-sql-builder.xml">
+<!ENTITY libgda-GdaThreadWrapper SYSTEM "xml/gda-thread-wrapper.xml">
 ]>
 
 <book id="index">
@@ -1110,12 +1111,17 @@
       &libgda-GdaServerOperationSequences;
     </chapter>
 
+    <chapter id="multi-threading">
+      <title>Multi threading</title>
+      &libgda-GdaMutex;
+      &libgda-GdaLockable;
+      &libgda-GdaThreadWrapper;      
+    </chapter>
+
     <chapter id="misc">
       <title>Miscellaneous</title>
       &libgda-util;
       &libgda-log;
-      &libgda-GdaMutex;
-      &libgda-GdaLockable;
       &GdaAttributesManager;
     </chapter>
   </part>

Modified: trunk/doc/C/libgda-4.0-sections.txt
==============================================================================
--- trunk/doc/C/libgda-4.0-sections.txt	(original)
+++ trunk/doc/C/libgda-4.0-sections.txt	Tue Apr  7 19:10:50 2009
@@ -131,6 +131,10 @@
 gda_connection_rollback_savepoint
 gda_connection_delete_savepoint
 <SUBSECTION>
+gda_connection_async_statement_execute
+gda_connection_async_fetch_result
+gda_connection_async_cancel
+<SUBSECTION>
 gda_connection_get_transaction_status
 gda_connection_get_options
 gda_connection_get_provider
@@ -1654,3 +1658,26 @@
 GDA_TYPE_SQL_BUILDER
 gda_sql_builder_get_type
 </SECTION>
+
+<SECTION>
+<FILE>gda-thread-wrapper</FILE>
+<TITLE>GdaThreadWrapper</TITLE>
+GdaThreadWrapper
+gda_thread_wrapper_new
+GdaThreadWrapperFunc
+gda_thread_wrapper_execute
+GdaThreadWrapperVoidFunc
+gda_thread_wrapper_execute_void
+gda_thread_wrapper_iterate
+gda_thread_wrapper_fetch_result
+gda_thread_wrapper_get_waiting_size
+GdaThreadWrapperCallback
+gda_thread_wrapper_connect_raw
+gda_thread_wrapper_disconnect
+<SUBSECTION Standard>
+GDA_THREAD_WRAPPER
+GDA_THREAD_WRAPPER_GET_CLASS
+GDA_IS_THREAD_WRAPPER
+GDA_TYPE_THREAD_WRAPPER
+gda_thread_wrapper_get_type
+</SECTION>

Modified: trunk/doc/C/libgda-4.0.types.in
==============================================================================
--- trunk/doc/C/libgda-4.0.types.in	(original)
+++ trunk/doc/C/libgda-4.0.types.in	Tue Apr  7 19:10:50 2009
@@ -1,6 +1,7 @@
 #include <libgda/libgda.h>
 #include <libgda/gda-blob-op.h>
 #include <libgda/sql-parser/gda-sql-parser.h>
+#include <libgda/thread-wrapper/gda-thread-wrapper.h>
 #include <virtual/libgda-virtual.h>
 #include <engine/gda-report-engine.h>
 #include <gda-report-document.h>
@@ -64,3 +65,4 @@
 gda_tree_mgr_tables_get_type
 gda_tree_node_get_type
 gda_sql_builder_get_type
+gda_thread_wrapper_get_type
\ No newline at end of file

Modified: trunk/doc/C/tmpl/gda-connection.sgml
==============================================================================
--- trunk/doc/C/tmpl/gda-connection.sgml	(original)
+++ trunk/doc/C/tmpl/gda-connection.sgml	Tue Apr  7 19:10:50 2009
@@ -15,7 +15,34 @@
   Use the connection object to execute statements, use transactions, get meta data information, ...
 </para>
 <para>
-  The #GdaConnection object implements its own locking mechanism so it is thread-safe.
+  If supported by the database provider being used, statements can be executed asynchronously instead of
+  blocking the execution thread untill the execution of a statement is finished. Each database provider
+  is free to implement this feature as it wishes (using the API or using threads). The steps involved to
+  execute a statement are then:
+  <itemizedlist>
+    <listitem><para>Request the statement execution using
+	<link linkend="gda-connection-async-statement-execute">gda_connection_async_statement_execute() which returns an
+	  execution ID to be used to identify a specific request</link></para></listitem>
+    <listitem><para>Do some usefull things (that is why async. statements' excution are for)</para></listitem>
+    <listitem><para>Use one or more times 
+	<link linkend="gda-connection-async-fetch-result">gda_connection_async_fetch_result()</link> to see
+	if the execution is finished, using the request ID</para></listitem>
+    <listitem><para>Use <link linkend="gda-connection-async-cancel">gda_connection_async_cancel()</link> to cancel
+	the execution of a statement</para></listitem>
+  </itemizedlist>
+</para>
+<para>
+  The #GdaConnection object implements its own locking mechanism so it is thread-safe. However ad some database
+  providers rely on an API which does not support threads or supports it only partially, the connections
+  opened using those providers will only be accessible from the thread which created them (any other thread will
+  be blocked trying to access the connection, use the
+  <link linkend="gda-lockable-try-lock">gda_lockable_try_lock()</link> method to check it the connection
+  is useable from a thread).
+</para>
+<para>
+  If a connection really needs to be accessed by several threads at once, then it is possible to pass the
+  #GDA_CONNECTION_OPTIONS_THREAD_SAFE flag when opening it. This flag requests that the real connection
+  be created and really accessed in a <emphasis>private</emphasis> sub thread.
 </para>
 
 <!-- ##### SECTION See_Also ##### -->
@@ -90,6 +117,11 @@
 
 </para>
 
+<!-- ##### ARG GdaConnection:is-wrapper ##### -->
+<para>
+
+</para>
+
 <!-- ##### ARG GdaConnection:meta-store ##### -->
 <para>
 
@@ -112,11 +144,12 @@
 
 <!-- ##### ENUM GdaConnectionOptions ##### -->
 <para>
-
+  Specifies some aspects of a connection when opening it.
 </para>
 
- GDA_CONNECTION_OPTIONS_NONE: 
- GDA_CONNECTION_OPTIONS_READ_ONLY: 
+ GDA_CONNECTION_OPTIONS_NONE: no specific aspect
+ GDA_CONNECTION_OPTIONS_READ_ONLY: the connection to open should be in a read-only mode (this policy is not correctly enforced at the moment)
+ GDA_CONNECTION_OPTIONS_THREAD_SAFE: this flag specifies that the connection to open will be used by several threads at once so it has to be thread safe
 
 <!-- ##### ENUM GdaConnectionError ##### -->
 <para>
@@ -130,6 +163,8 @@
 @GDA_CONNECTION_NO_PROVIDER_SPEC_ERROR: 
 @GDA_CONNECTION_OPEN_ERROR: 
 @GDA_CONNECTION_STATEMENT_TYPE_ERROR: 
+ GDA_CONNECTION_CANT_LOCK_ERROR: 
+ GDA_CONNECTION_TASK_NOT_FOUND_ERROR: 
 
 <!-- ##### MACRO GDA_CONNECTION_ERROR ##### -->
 <para>
@@ -389,6 +424,44 @@
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_connection_async_statement_execute ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ stmt: 
+ params: 
+ model_usage: 
+ col_types: 
+ need_last_insert_row: 
+ error: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_connection_async_fetch_result ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ task_id: 
+ last_insert_row: 
+ error: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_connection_async_cancel ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ task_id: 
+ error: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_connection_get_transaction_status ##### -->
 <para>
 

Modified: trunk/doc/C/tmpl/gda-data-model-iter.sgml
==============================================================================
--- trunk/doc/C/tmpl/gda-data-model-iter.sgml	(original)
+++ trunk/doc/C/tmpl/gda-data-model-iter.sgml	Tue Apr  7 19:10:50 2009
@@ -33,7 +33,7 @@
   gda_data_model_iter_move_next() which moves to the first row).
 </para>
 <para>
-  The gda_data_model_iter_move_at_row() method, if the iterator can be moved both forward and backwards, can move the 
+  The gda_data_model_iter_move_to_row() method, if the iterator can be moved both forward and backwards, can move the 
   iterator to a specific row (sometimes faster than moving it forward or backwards a number of times).
 </para>
 <para>

Modified: trunk/doc/C/tmpl/gda-meta-store.sgml
==============================================================================
--- trunk/doc/C/tmpl/gda-meta-store.sgml	(original)
+++ trunk/doc/C/tmpl/gda-meta-store.sgml	Tue Apr  7 19:10:50 2009
@@ -18,6 +18,9 @@
   single file (using an SQLite database), an entirely in memory database (also using an SQLite database), or
   a more conventional backend such as a PostgreSQL database for a shared dictionary on a server.
 </para>
+<para>
+  The #GdaMetaStore object is thread safe.
+</para>
 
 <!-- ##### SECTION See_Also ##### -->
 <para>

Modified: trunk/doc/C/tmpl/gda-server-provider.sgml
==============================================================================
--- trunk/doc/C/tmpl/gda-server-provider.sgml	(original)
+++ trunk/doc/C/tmpl/gda-server-provider.sgml	Tue Apr  7 19:10:50 2009
@@ -65,7 +65,7 @@
 @create_connection: 
 @meta_funcs: 
 @xa_funcs: 
- _gda_reserved1: 
+ handle_async: 
 @_gda_reserved2: 
 @_gda_reserved3: 
 @_gda_reserved4: 

Modified: trunk/doc/C/tmpl/gda-sql-parser.sgml
==============================================================================
--- trunk/doc/C/tmpl/gda-sql-parser.sgml	(original)
+++ trunk/doc/C/tmpl/gda-sql-parser.sgml	Tue Apr  7 19:10:50 2009
@@ -85,7 +85,7 @@
   The #GdaSqlParser object internally uses a LEMON generated parser (the same as the one used by SQLite).
 </para>
 <para>
-  The #GdaConnection object implements its own locking mechanism so it is thread-safe.
+  The #GdaSqlParser object implements its own locking mechanism so it is thread-safe.
 </para>
 
 <!-- ##### SECTION See_Also ##### -->

Added: trunk/doc/C/tmpl/gda-thread-connection.sgml
==============================================================================
--- (empty file)
+++ trunk/doc/C/tmpl/gda-thread-connection.sgml	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,45 @@
+<!-- ##### SECTION Title ##### -->
+GdaThreadConnection
+
+<!-- ##### SECTION Short_Description ##### -->
+
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### STRUCT GdaThreadConnection ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION gda_thread_connection_update_meta_store_async ##### -->
+<para>
+
+</para>
+
+ cnc: 
+
+
+<!-- ##### FUNCTION gda_connection_get_meta_store_data_async ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ meta_type: 
+ error: 
+ nb_filters: 
+ Varargs: 
+
+

Added: trunk/doc/C/tmpl/gda-thread-wrapper.sgml
==============================================================================
--- (empty file)
+++ trunk/doc/C/tmpl/gda-thread-wrapper.sgml	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,155 @@
+<!-- ##### SECTION Title ##### -->
+GdaThreadWrapper
+
+<!-- ##### SECTION Short_Description ##### -->
+Execute functions in a sub thread
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+  The purpose of the #GdaThreadWrapper object is to execute functions in an isolated sub thread. As the
+  #GdaThreadWrapper is thread safe, one is able to isolate some code's execution is a <emphasis>private</emphasis> thread, and
+  make a non thread safe code thread safe.
+</para>
+<para>
+  The downside of this is that the actual execution of the code will be slower as it requires
+  threads to be synchronized.
+</para>
+<para>
+  Each thread using a #GdaThreadWrapper object can use it as if it was the only user: the #GdaThreadWrapper will
+  simply dispatch all the execution requests to its <emphasis>private</emphasis> sub thread and report the
+  execution's status only to the thread which made the request.
+</para>
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### STRUCT GdaThreadWrapper ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION gda_thread_wrapper_new ##### -->
+<para>
+
+</para>
+
+ Returns: 
+
+
+<!-- ##### USER_FUNCTION GdaThreadWrapperFunc ##### -->
+<para>
+  Specifies the type of function to be passed to gda_thread_wrapper_execute().
+</para>
+
+ arg: pointer to the data (which is the @arg argument passed to gda_thread_wrapper_execute_void())
+ error: a place to store errors
+ Returns: a pointer to some data which will be returned by gda_thread_wrapper_fetch_result()
+
+
+<!-- ##### FUNCTION gda_thread_wrapper_execute ##### -->
+<para>
+
+</para>
+
+ wrapper: 
+ func: 
+ arg: 
+ arg_destroy_func: 
+ error: 
+ Returns: 
+
+
+<!-- ##### USER_FUNCTION GdaThreadWrapperVoidFunc ##### -->
+<para>
+  Specifies the type of function to be passed to gda_thread_wrapper_execute_void().
+</para>
+
+ arg: a pointer to the data (which is the @arg argument passed to gda_thread_wrapper_execute_void())
+ error: a place to store errors
+
+
+<!-- ##### FUNCTION gda_thread_wrapper_execute_void ##### -->
+<para>
+
+</para>
+
+ wrapper: 
+ func: 
+ arg: 
+ arg_destroy_func: 
+ error: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_thread_wrapper_iterate ##### -->
+<para>
+
+</para>
+
+ wrapper: 
+ may_block: 
+
+
+<!-- ##### FUNCTION gda_thread_wrapper_fetch_result ##### -->
+<para>
+
+</para>
+
+ wrapper: 
+ may_lock: 
+ out_id: 
+ error: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_thread_wrapper_get_waiting_size ##### -->
+<para>
+
+</para>
+
+ wrapper: 
+ Returns: 
+
+
+<!-- ##### USER_FUNCTION GdaThreadWrapperCallback ##### -->
+<para>
+  Specifies the type of function to be passed to gda_thread_wrapper_connect_raw().
+</para>
+
+ wrapper: the #GdaThreadWrapper
+ instance: a pointer to the instance which emitted the signal
+ signame: the name of the signal being emitted
+ n_param_values: number of GValue in @param_values
+ param_values: array of @n_param_values GValue
+ gda_reserved: reserved
+ data: a pointer to the data (which is the @data argument passed to gda_thread_wrapper_connect_raw())
+
+
+<!-- ##### FUNCTION gda_thread_wrapper_connect_raw ##### -->
+<para>
+
+</para>
+
+ wrapper: 
+ instance: 
+ sig_name: 
+ callback: 
+ data: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_thread_wrapper_disconnect ##### -->
+<para>
+
+</para>
+
+ wrapper: 
+ id: 
+
+

Modified: trunk/libgda/Makefile.am
==============================================================================
--- trunk/libgda/Makefile.am	(original)
+++ trunk/libgda/Makefile.am	Tue Apr  7 19:10:50 2009
@@ -1,6 +1,6 @@
 lib_LTLIBRARIES = libgda-4.0.la
 
-SUBDIRS = sqlite handlers binreloc sql-parser providers-support
+SUBDIRS = sqlite handlers binreloc sql-parser providers-support thread-wrapper
 
 if BDB 
 GDA_BDB_H=gda-data-model-bdb.h
@@ -99,7 +99,7 @@
 	gda-config.c \
 	gda-connection.c \
 	gda-connection-event.c \
-	gda-connection-sqlite.h \
+	gda-connection-internal.h \
 	gda-custom-marshal.c \
 	gda-custom-marshal.h \
 	gda-data-comparator.c \
@@ -208,6 +208,7 @@
 	handlers/libgda_handlers-4.0.la \
 	binreloc/libgda_binreloc-4.0.la \
 	sqlite/libgda-sqlite.la \
+	thread-wrapper/libgda_threadwrapper-4.0.la \
 	$(LIBGDA_LIBS) $(FAM_LIBS) \
 	$(GIO_LIBS) $(GNOMEVFS_LIBS) 
 

Copied: trunk/libgda/gda-connection-internal.h (from r3371, /trunk/libgda/gda-connection-sqlite.h)
==============================================================================
--- /trunk/libgda/gda-connection-sqlite.h	(original)
+++ trunk/libgda/gda-connection-internal.h	Tue Apr  7 19:10:50 2009
@@ -20,10 +20,11 @@
  * Boston, MA 02111-1307, USA.
  */
 
-#ifndef __GDA_CONNECTION_SQLITE_H_
-#define __GDA_CONNECTION_SQLITE_H_
+#ifndef __GDA_CONNECTION_INTERNAL_H_
+#define __GDA_CONNECTION_INTERNAL_H_
 
 #include <libgda/gda-decl.h>
+#include <libgda/thread-wrapper/gda-thread-wrapper.h>
 
 G_BEGIN_DECLS
 
@@ -34,6 +35,19 @@
  */
 GdaConnection *_gda_open_internal_sqlite_connection (const gchar *cnc_string);
 
+
+/*
+ * Functions dedicated to implementing a GdaConnection which uses a GdaThreadWrapper around
+ * another connection to make it thread safe.
+ */
+typedef struct {
+        GdaConnection *sub_connection;
+        GdaServerProvider *cnc_provider;
+        GdaThreadWrapper *wrapper;
+	GArray *handlers_ids; /* array of gulong */
+} ThreadConnectionData; /* Per connection private data for */
+void           _gda_connection_force_transaction_status (GdaConnection *cnc, GdaConnection *wrapped_cnc);
+
 G_END_DECLS
 
 #endif

Modified: trunk/libgda/gda-connection.c
==============================================================================
--- trunk/libgda/gda-connection.c	(original)
+++ trunk/libgda/gda-connection.c	Tue Apr  7 19:10:50 2009
@@ -28,6 +28,7 @@
 #include <libgda/gda-config.h>
 #include <libgda/gda-connection.h>
 #include <libgda/gda-connection-private.h>
+#include <libgda/gda-connection-internal.h>
 #include <libgda/gda-connection-event.h>
 #include <glib/gi18n-lib.h>
 #include <libgda/gda-log.h>
@@ -44,6 +45,7 @@
 #include <libgda/gda-util.h>
 #include <libgda/gda-mutex.h>
 #include <libgda/gda-lockable.h>
+#include <libgda/thread-wrapper/gda-thread-provider.h>
 
 #define PROV_CLASS(provider) (GDA_SERVER_PROVIDER_CLASS (G_OBJECT_GET_CLASS (provider)))
 
@@ -54,6 +56,7 @@
 	gchar                *cnc_string;
 	gchar                *auth_string;
 	gboolean              is_open;
+	gboolean              is_thread_wrapper;
 
 	GdaMetaStore         *meta_store;
 
@@ -71,8 +74,36 @@
 	GCond                *unique_possible_cond;
 	GMutex               *unique_possible_mutex;
 	GdaMutex             *mutex;
+
+	/* Asynchronous statement execution */
+	guint                 next_task_id; /* starts at 1 as 0 is an error */
+	GArray               *waiting_tasks; /* array of CncTask pointers to tasks to be executed */
+	GArray               *completed_tasks; /* array of CncTask pointers to tasks already executed */
 };
 
+/* represents an asynchronous execution task */
+typedef struct {
+	guint task_id; /* ID assigned by GdaConnection object */
+	guint prov_task_id; /* ID assigned by GdaServerProvider */
+	gboolean being_processed; /* TRUE if currently being processed */
+	GMutex *mutex;
+	GdaStatement *stmt; /* statement to execute */
+	GdaStatementModelUsage model_usage;
+	GType *col_types;
+	GdaSet *params;
+	gboolean need_last_insert_row;
+	GdaSet *last_insert_row;
+	GObject *result;
+	GError *error;
+} CncTask;
+#define CNC_TASK(x) ((CncTask*)(x))
+
+static CncTask *cnc_task_new (guint id, GdaStatement *stmt, GdaStatementModelUsage model_usage, 
+			      GType *col_types, GdaSet *params, gboolean need_last_insert_row);
+static void     cnc_task_free (CncTask *task);
+#define         cnc_task_lock(task) g_mutex_lock ((task)->mutex)
+#define         cnc_task_unlock(task) g_mutex_unlock ((task)->mutex)
+
 static void gda_connection_class_init (GdaConnectionClass *klass);
 static void gda_connection_init       (GdaConnection *cnc, GdaConnectionClass *klass);
 static void gda_connection_dispose    (GObject *object);
@@ -115,11 +146,13 @@
         PROP_AUTH_STRING,
         PROP_OPTIONS,
 	PROP_META_STORE,
-	PROP_THREAD_OWNER
+	PROP_THREAD_OWNER,
+	PROP_IS_THREAD_WRAPPER
 };
 
 static GObjectClass *parent_class = NULL;
 extern GdaServerProvider *_gda_config_sqlite_provider; /* defined in gda-config.c */
+static GdaServerProvider *_gda_thread_wrapper_provider = NULL;
 
 /*
  * GdaConnection class implementation
@@ -255,6 +288,12 @@
 								 "This should only be modified by the database providers' implementation"),
 							       (G_PARAM_READABLE | G_PARAM_WRITABLE)));
 
+	g_object_class_install_property (object_class, PROP_IS_THREAD_WRAPPER,
+					 g_param_spec_boolean ("is-wrapper", NULL,
+							       _("Tells if the connection acts as a thread wrapper around another connection, making it completely thread safe"),
+							       FALSE,
+							       (G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));
+
 	
 	object_class->dispose = gda_connection_dispose;
 	object_class->finalize = gda_connection_finalize;
@@ -287,6 +326,10 @@
 	cnc->priv->events_list = NULL;
 	cnc->priv->trans_status = NULL; /* no transaction yet */
 	cnc->priv->prepared_stmts = NULL;
+
+	cnc->priv->next_task_id = 1;
+	cnc->priv->waiting_tasks = g_array_new (FALSE, FALSE, sizeof (gpointer));
+	cnc->priv->completed_tasks = g_array_new (FALSE, FALSE, sizeof (gpointer));
 }
 
 static void prepared_stms_foreach_func (GdaStatement *gda_stmt, GdaPStmt *prepared_stmt, GdaConnection *cnc);
@@ -330,6 +373,22 @@
 	        cnc->priv->meta_store = NULL;
 	}
 
+	if (cnc->priv->waiting_tasks) {
+		gint i, len = cnc->priv->waiting_tasks->len;
+		for (i = 0; i < len; i++)
+			cnc_task_free (CNC_TASK (g_array_index (cnc->priv->waiting_tasks, gpointer, i)));
+		g_array_free (cnc->priv->waiting_tasks, TRUE);
+		cnc->priv->waiting_tasks = NULL;
+	}
+
+	if (cnc->priv->completed_tasks) {
+		gint i, len = cnc->priv->completed_tasks->len;
+		for (i = 0; i < len; i++)
+			cnc_task_free (CNC_TASK (g_array_index (cnc->priv->completed_tasks, gpointer, i)));
+		g_array_free (cnc->priv->completed_tasks, TRUE);
+		cnc->priv->completed_tasks = NULL;
+	}
+
 	/* chain to parent class */
 	parent_class->dispose (object);
 }
@@ -525,8 +584,18 @@
 			cnc->priv->meta_store = g_value_get_object (value);
 			if (cnc->priv->meta_store)
 				g_object_ref (cnc->priv->meta_store);
+			if (cnc->priv->is_thread_wrapper) {
+				ThreadConnectionData *cdata;
+				cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+				if (cdata) 
+					g_object_set (G_OBJECT (cdata->sub_connection), "meta-store",
+						      cnc->priv->meta_store, NULL);
+			}
 			gda_mutex_unlock (cnc->priv->mutex);
 			break;
+		case PROP_IS_THREAD_WRAPPER:
+			cnc->priv->is_thread_wrapper = g_value_get_boolean (value);
+			break;
                 }
         }	
 }
@@ -561,11 +630,84 @@
 		case PROP_META_STORE:
 			g_value_set_object (value, cnc->priv->meta_store);
 			break;
+		case PROP_IS_THREAD_WRAPPER:
+			g_value_set_boolean (value, cnc->priv->is_thread_wrapper);
+			break;
                 }
 		gda_mutex_unlock (cnc->priv->mutex);
         }	
 }
 
+/*
+ * helper functions to manage CncTask
+ */
+static void task_stmt_reset_cb (GdaStatement *stmt, CncTask *task);
+static CncTask *
+cnc_task_new (guint id, GdaStatement *stmt, GdaStatementModelUsage model_usage, GType *col_types, 
+	      GdaSet *params, gboolean need_last_insert_row)
+{
+	CncTask *task;
+
+	task = g_new0 (CncTask, 1);
+	task->being_processed = FALSE;
+	task->task_id = id;
+	task->stmt = g_object_ref (stmt);
+	g_signal_connect (stmt, "reset", /* monitor statement changes */
+			  G_CALLBACK (task_stmt_reset_cb), task);
+	task->model_usage = model_usage;
+	if (col_types) {
+		gint i;
+		for (i = 0; ; i++) {
+			if (col_types [i] == G_TYPE_NONE)
+				break;
+		}
+		task->col_types = g_new (GType, i+1);
+		memcpy (task->col_types, col_types, (i+1) * sizeof (GType));
+	}
+	if (params)
+		task->params = gda_set_copy (params);
+	task->need_last_insert_row = need_last_insert_row;
+
+	task->mutex = g_mutex_new ();
+	return task;
+}
+
+static void
+task_stmt_reset_cb (GdaStatement *stmt, CncTask *task)
+{
+	g_mutex_lock (task->mutex);
+	g_signal_handlers_disconnect_by_func (task->stmt,
+					      G_CALLBACK (task_stmt_reset_cb), task);
+	g_object_unref (task->stmt);
+	task->stmt = NULL;
+	g_mutex_unlock (task->mutex);
+}
+
+static void
+cnc_task_free (CncTask *task)
+{
+	g_mutex_lock (task->mutex);
+	if (task->stmt) {
+		g_signal_handlers_disconnect_by_func (task->stmt,
+						      G_CALLBACK (task_stmt_reset_cb), task);
+		g_object_unref (task->stmt);
+	}
+	if (task->params)
+		g_object_unref (task->params);
+	if (task->col_types)
+		g_free (task->col_types);
+	if (task->last_insert_row)
+		g_object_unref (task->last_insert_row);
+	if (task->result)
+		g_object_unref (task->result);
+	if (task->error)
+		g_error_free (task->error);
+
+	g_mutex_unlock (task->mutex);
+	g_mutex_free (task->mutex);
+	g_free (task);
+}
+
 /**
  * gda_connection_open_from_dsn
  * @dsn: data source name.
@@ -643,6 +785,13 @@
 
 		prov = gda_config_get_provider (dsn_info->provider, error);
 		if (prov) {
+			if (options & GDA_CONNECTION_OPTIONS_THREAD_SAFE) {
+				if (!_gda_thread_wrapper_provider)
+					_gda_thread_wrapper_provider =
+						GDA_SERVER_PROVIDER (g_object_new (GDA_TYPE_THREAD_PROVIDER, 
+										   NULL));
+				prov = _gda_thread_wrapper_provider;
+			}
 			if (PROV_CLASS (prov)->create_connection) {
 				cnc = PROV_CLASS (prov)->create_connection (prov);
 				if (cnc) 
@@ -770,6 +919,19 @@
 
 		prov = gda_config_get_provider (provider_name ? provider_name : real_provider, error);
 		if (prov) {
+			if (options & GDA_CONNECTION_OPTIONS_THREAD_SAFE) {
+				gchar *tmp;
+				tmp = g_strdup_printf ("%s;PROVIDER_NAME=%s",
+						       real_cnc, gda_server_provider_get_name (prov));
+				g_free (real_cnc);
+				real_cnc = tmp;
+				if (!_gda_thread_wrapper_provider)
+					_gda_thread_wrapper_provider =
+						GDA_SERVER_PROVIDER (g_object_new (GDA_TYPE_THREAD_PROVIDER, 
+										   NULL));
+				prov = _gda_thread_wrapper_provider;
+			}
+
 			if (PROV_CLASS (prov)->create_connection) {
 				cnc = PROV_CLASS (prov)->create_connection (prov);
 				if (cnc) 
@@ -792,7 +954,7 @@
 				cnc = NULL;
 			}
 		}
-	}	
+	}
 
 	g_free (real_cnc);
 	g_free (user);
@@ -1556,6 +1718,347 @@
 	return types;
 }
 
+
+/*
+ * No locking is done here must be done before calling
+ *
+ * Returns: -1 if task not found
+ */
+static gint
+get_task_index (GdaConnection *cnc, guint task_id, gboolean *out_completed, gboolean id_is_prov)
+{
+	gint i, len;
+	CncTask *task;
+	len = cnc->priv->completed_tasks->len;
+	for (i = 0; i < len; i++) {
+		task = CNC_TASK (g_array_index (cnc->priv->completed_tasks, gpointer, i));
+		if ((! id_is_prov && (task->task_id == task_id)) ||
+		    (id_is_prov && (task->prov_task_id == task_id))) {
+			*out_completed = TRUE;
+			return i;
+		}
+	}
+
+	len = cnc->priv->waiting_tasks->len;
+	for (i = 0; i < len; i++) {
+		task = CNC_TASK (g_array_index (cnc->priv->waiting_tasks, gpointer, i));
+		if ((! id_is_prov && (task->task_id == task_id)) ||
+		    (id_is_prov && (task->prov_task_id == task_id))) {
+			*out_completed = FALSE;
+			return i;
+		}
+	}
+	
+	return -1;
+}
+
+/*
+ * This callback is called from the GdaServerProvider object
+ */
+static void
+async_stmt_exec_cb (GdaServerProvider *provider, GdaConnection *cnc, guint task_id,
+		    GObject *result_obj, const GError *error, CncTask *task)
+{
+	gint i;
+	gboolean is_completed;
+
+	gda_connection_lock (GDA_LOCKABLE (cnc));
+
+	i = get_task_index (cnc, task_id, &is_completed, FALSE);
+	if (i >= 0) {
+		CncTask *task;
+		g_assert (!is_completed);
+
+		/* complete @task and free some memory */
+		task = CNC_TASK (g_array_index (cnc->priv->waiting_tasks, gpointer, i));
+		cnc_task_lock (task);
+
+		task->being_processed = FALSE;
+		if (error)
+			task->error = g_error_copy (error);
+		if (result_obj)
+			task->result = g_object_ref (result_obj);
+		if (task->stmt) {
+			g_signal_handlers_disconnect_by_func (task->stmt,
+							      G_CALLBACK (task_stmt_reset_cb), task);
+			g_object_unref (task->stmt);
+			task->stmt = NULL;
+		}
+		if (task->params) {
+			g_object_unref (task->params);
+			task->params = NULL;
+		}
+		if (task->col_types) {
+			g_free (task->col_types);
+			task->col_types = NULL;
+		}
+
+		g_array_remove_index (cnc->priv->waiting_tasks, i);
+		g_array_append_val (cnc->priv->completed_tasks, task);
+
+		cnc_task_unlock (task);
+
+		/* execute next waiting task if there is one */
+		while (cnc->priv->waiting_tasks->len >= 1) {
+			/* execute statement now as there are no other ones to be executed */
+			GError *lerror = NULL;
+			task = CNC_TASK (g_array_index (cnc->priv->waiting_tasks, gpointer, 0));
+			task->being_processed = TRUE;
+			cnc_task_lock (task);
+			PROV_CLASS (cnc->priv->provider_obj)->statement_execute (cnc->priv->provider_obj, cnc, 
+										 task->stmt, 
+										 task->params, 
+										 task->model_usage, 
+										 task->col_types, 
+										 &(task->last_insert_row),
+										 &(task->prov_task_id),
+										 (GdaServerProviderExecCallback) async_stmt_exec_cb, 
+										 task, &lerror);
+			if (lerror) {
+				/* task execution failed => move it to completed tasks array */
+				task->error = lerror;
+				task->being_processed = FALSE;
+				g_array_remove_index (cnc->priv->waiting_tasks, 0);
+				g_array_append_val (cnc->priv->completed_tasks, task);
+			}
+			cnc_task_unlock (task);
+		}
+	}
+	g_print ("%s() called!!!\n", __FUNCTION__);
+
+	gda_connection_lock (GDA_LOCKABLE (cnc));
+}
+
+/**
+ * gda_connection_async_statement_execute
+ * @cnc: a #GdaConnection
+ * @stmt: a #GdaStatement object
+ * @params: a #GdaSet object (which can be obtained using gda_statement_get_parameters()), or %NULL
+ * @model_usage: in the case where @stmt is a SELECT statement, specifies how the returned data model will be used
+ * @col_types: an array of GType to request each returned #GdaDataModel's column's GType, terminated with the G_TYPE_NONE
+ * @need_last_insert_row: TRUE if the values of the last interted row must be computed
+ * @error: a place to store errors, or %NULL
+ *
+ * This method is somilar to gda_connection_statement_execute() but is done asynchronously as this method returns
+ * immediately a task ID. It's up to the caller to use gda_connection_async_fetch_result() regularly to check
+ * if the statement's execution is finished.
+ *
+ * It is possible to call the method several times to request several statements to be executed asynchronously, the
+ * statements will be executed in the order in which they were requested.
+ *
+ * The parameters, if present, are copied and can be discaded or modified before the statement is actually executed.
+ * The @stmt object is not copied but simply referenced (for performance reasons), and if it is modified before
+ * it is actually executed, then its execution will not occur. It is however safe to call g_object_unref() on it if
+ * it's not needed anymore.
+ *
+ * The execution failure of any statement has no impact on the execution of other statements except for example if
+ * the connection has a transaction started and the failure invalidates the transaction (as decided by the database
+ * server).
+ *
+ * Returns: a task ID, or 0 if an error occurred (not an error regarding @stmt itself as its execution has not yet started
+ * but any other error)
+ *
+ * Since: 4.2
+ */
+guint
+gda_connection_async_statement_execute (GdaConnection *cnc, GdaStatement *stmt, GdaSet *params, 
+					GdaStatementModelUsage model_usage, GType *col_types,
+					gboolean need_last_insert_row,
+					GError **error)
+{
+	guint id;
+	CncTask *task;
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), 0);
+	g_return_val_if_fail (cnc->priv, 0);
+	g_return_val_if_fail (cnc->priv->provider_obj, 0);
+	g_return_val_if_fail (GDA_IS_STATEMENT (stmt), 0);
+	g_return_val_if_fail (PROV_CLASS (cnc->priv->provider_obj)->statement_execute, 0);
+
+	if (! gda_connection_trylock ((GdaLockable*) cnc)) {
+		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_CANT_LOCK_ERROR,
+			     _("Can't obtain connection lock"));
+		return 0;
+	}
+	id = cnc->priv->next_task_id ++;
+	task = cnc_task_new (id, stmt, model_usage, col_types, params, need_last_insert_row);
+	g_array_append_val (cnc->priv->waiting_tasks, task);
+
+	if (cnc->priv->waiting_tasks->len == 1) {
+		/* execute statement now as there are no other ones to be executed */
+		GError *lerror = NULL;
+		task->being_processed = TRUE;
+		cnc_task_lock (task);
+		PROV_CLASS (cnc->priv->provider_obj)->statement_execute (cnc->priv->provider_obj, cnc, 
+									 task->stmt, task->params, 
+									 task->model_usage, task->col_types,
+									 &(task->last_insert_row),
+									 &(task->prov_task_id),
+									 (GdaServerProviderExecCallback) async_stmt_exec_cb, 
+									 task, &lerror);
+		if (lerror) {
+			/* task execution failed => move it to completed tasks array */
+			gint i;
+			gboolean is_completed;
+
+			task->error = lerror;
+			task->being_processed = FALSE;
+			i = get_task_index (cnc, id, &is_completed, FALSE);
+			g_assert ((i >= 0) && !is_completed);
+			g_array_remove_index (cnc->priv->waiting_tasks, i);
+			g_array_append_val (cnc->priv->completed_tasks, task);
+		}
+		cnc_task_unlock (task);
+	}
+
+	gda_connection_unlock ((GdaLockable*) cnc);
+
+	return id;
+}
+
+/**
+ * gda_connection_async_fetch_result
+ * @cnc: a #GdaConnection
+ * @task_id: a task ID retuned by gda_connection_async_statement_execute()
+ * @last_insert_row: a place to store a new #GdaSet object which contains the values of the last inserted row, or %NULL
+ * @error: a place to store errors, or %NULL
+ *
+ * Use this method to obtain the result of the execution of a statement which has been executed asynchronously by
+ * calling gda_connection_async_statement_execute(). This function is non locking and will return %NULL (and no
+ * error will be set) if the statement has not been executed yet.
+ *
+ * If the statement has been executed, this method returns the same value as gda_connection_statement_execute()
+ * would have if the statement had been
+ * executed synchronously.
+ *
+ * Returns: a #GObject, or %NULL if an error occurred
+ *
+ * Since: 4.2
+ */
+GObject *
+gda_connection_async_fetch_result (GdaConnection *cnc, guint task_id, GdaSet **last_insert_row, GError **error)
+{
+	gint i;
+	gboolean is_completed;
+	GObject *obj = NULL;
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), NULL);
+	g_return_val_if_fail (cnc->priv, NULL);
+
+	if (! gda_connection_trylock ((GdaLockable*) cnc)) {
+		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_CANT_LOCK_ERROR,
+			     _("Can't obtain connection lock"));
+		return NULL;
+	}
+
+	/* if provider needs to be awaken, then do it now */
+	if (PROV_CLASS (cnc->priv->provider_obj)->handle_async) {
+		if (! (PROV_CLASS (cnc->priv->provider_obj)->handle_async (cnc->priv->provider_obj, cnc, error))) {
+			gda_connection_unlock ((GdaLockable*) cnc);
+			return NULL;
+		}
+	}
+	
+	i = get_task_index (cnc, task_id, &is_completed, FALSE);
+	if (i < 0) {
+		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_TASK_NOT_FOUND_ERROR,
+		     _("Can't find task %u"), task_id);
+	}
+	else if (is_completed) {
+		/* task completed */
+		CncTask *task;
+		task = CNC_TASK (g_array_index (cnc->priv->completed_tasks, gpointer, i));
+		g_array_remove_index (cnc->priv->completed_tasks, i);
+		
+		cnc_task_lock (task);
+		if (task->result)
+			obj = g_object_ref (task->result);
+		if (task->error) {
+			g_propagate_error (error, task->error);
+			task->error = NULL;
+		}
+		if (last_insert_row) {
+			if (task->last_insert_row)
+				*last_insert_row = g_object_ref (task->last_insert_row);
+			else
+				*last_insert_row = NULL;
+		}
+		cnc_task_unlock (task);
+		cnc_task_free (task);
+	}
+	else {
+		/* task not yet completed */
+		/* nothing to do */
+	}
+
+	gda_connection_unlock ((GdaLockable*) cnc);
+	return obj;
+}
+
+/**
+ * gda_connection_async_cancel
+ * @cnc: a #GdaConnection
+ * @task_id: a task ID retuned by gda_connection_async_statement_execute()
+ * @error: a place to store errors, or %NULL
+ *
+ * Requests that a task be cancelled. This operation may of may not have any effect
+ * depending on the task's status, even if it returns %TRUE. If it returns %FALSE,
+ * then the task has not been cancelled.
+ *
+ * Returns: TRUE if no error occurred
+ *
+ * Since: 4.2
+ */
+gboolean
+gda_connection_async_cancel (GdaConnection *cnc, guint task_id, GError **error)
+{
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (cnc->priv, FALSE);
+	g_return_val_if_fail (cnc->priv->provider_obj, FALSE);
+
+	if (! gda_connection_trylock ((GdaLockable*) cnc)) {
+		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_CANT_LOCK_ERROR,
+			     _("Can't obtain connection lock"));
+		return FALSE;
+	}
+
+	gint i;
+	gboolean is_completed;
+	gboolean retval = TRUE;
+	i = get_task_index (cnc, task_id, &is_completed, FALSE);
+	if ((i >= 0) && (!is_completed)) {
+		CncTask *task;
+		task = CNC_TASK (g_array_index (cnc->priv->waiting_tasks, gpointer, i));
+		if (task->being_processed) {
+			if (PROV_CLASS (cnc->priv->provider_obj)->cancel) {
+				retval = PROV_CLASS (cnc->priv->provider_obj)->cancel (cnc->priv->provider_obj, cnc,
+										       task->prov_task_id, error);
+				if (retval) {
+					/* cancellation may have succeeded => remove this task from the tasks to execute */
+					g_array_remove_index (cnc->priv->waiting_tasks, i);
+					cnc_task_free (task);
+				}
+			}
+			else {
+				g_set_error (error, GDA_SERVER_PROVIDER_ERROR, GDA_SERVER_PROVIDER_METHOD_NON_IMPLEMENTED_ERROR,
+					     "%s", _("Provider does not support asynchronous server operation"));
+				retval = FALSE;
+			}
+		}
+		else {
+			/* simply remove this task from the tasks to execute */
+			g_array_remove_index (cnc->priv->waiting_tasks, i);
+			cnc_task_free (task);
+		}
+	}
+	else if (i < 0) {
+		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_TASK_NOT_FOUND_ERROR,
+			     _("Can't find task %u"), task_id);
+		retval = FALSE;
+	}
+
+	gda_connection_unlock ((GdaLockable*) cnc);
+	return retval;
+}
+
 /*
  * Wrapper which adds @...
  */
@@ -1910,6 +2413,37 @@
 	return model;
 }
 
+/*
+ * Forces the GdaTransactionStatus. This is reserved to connections which are thread wrappers
+ *
+ * @trans_status may be NULL
+ * if @trans_status is not NULL, then its ref count is increased.
+ */
+void
+_gda_connection_force_transaction_status (GdaConnection *cnc, GdaConnection *wrapped_cnc)
+{
+	g_assert (cnc->priv->is_thread_wrapper);
+	if (cnc->priv->trans_status) 
+		g_object_unref (cnc->priv->trans_status);
+
+	cnc->priv->trans_status = wrapped_cnc->priv->trans_status;
+	if (cnc->priv->trans_status)
+		g_object_ref (cnc->priv->trans_status);
+
+#ifdef GDA_DEBUG_signal
+        g_print (">> 'TRANSACTION_STATUS_CHANGED' from %s\n", __FUNCTION__);
+#endif
+        g_signal_emit (G_OBJECT (cnc), gda_connection_signals[TRANSACTION_STATUS_CHANGED], 0);
+#ifdef GDA_DEBUG_signal
+        g_print ("<< 'TRANSACTION_STATUS_CHANGED' from %s\n", __FUNCTION__);
+#endif
+
+#ifdef GDA_DEBUG_NO
+	if (cnc->priv->trans_status)
+		gda_transaction_status_dump (cnc->priv->trans_status, 5);
+#endif
+}
+
 /**
  * gda_connection_begin_transaction
  * @cnc: a #GdaConnection object.
@@ -2977,6 +3511,21 @@
 	return NULL;
 }
 
+typedef struct {
+	GdaConnection  *cnc;
+	GdaMetaContext *context;
+} UpdateMetaStoreData;
+
+static gpointer
+sub_thread_update_meta_store (UpdateMetaStoreData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = gda_connection_update_meta_store (data->cnc, data->context, error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
 /**
  * gda_connection_update_meta_store
  * @cnc: a #GdaConnection object.
@@ -3027,6 +3576,24 @@
 
 	gda_connection_lock ((GdaLockable*) cnc);
 
+	if (cnc->priv->is_thread_wrapper) {
+		ThreadConnectionData *cdata;
+		UpdateMetaStoreData data;
+		gpointer res;
+
+		cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+		if (!cdata) 
+			return FALSE;
+
+		data.cnc = cdata->sub_connection;
+		data.context = context;
+
+		gda_thread_wrapper_execute (cdata->wrapper, 
+					    (GdaThreadWrapperFunc) sub_thread_update_meta_store, &data, NULL, error);
+		res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, error);
+		return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+	}
+
 	/* Get or create the GdaMetaStore object */
 	store = gda_connection_get_meta_store (cnc);
 	g_assert (store);
@@ -4131,14 +4698,30 @@
 GdaMetaStore *
 gda_connection_get_meta_store (GdaConnection *cnc)
 {
-	GdaMetaStore *store;
+	GdaMetaStore *store = NULL;
 
 	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), NULL);
 	g_return_val_if_fail (cnc->priv, NULL);
 
 	gda_connection_lock ((GdaLockable*) cnc);
-	if (!cnc->priv->meta_store)
-		cnc->priv->meta_store = gda_meta_store_new (NULL);
+	if (!cnc->priv->meta_store) {
+		ThreadConnectionData *cdata = NULL;
+		if (cnc->priv->is_thread_wrapper) {
+			cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+			g_assert (cdata);
+			
+			if (cdata->sub_connection->priv->meta_store) {
+				cnc->priv->meta_store = g_object_ref (cdata->sub_connection->priv->meta_store);
+				store = cnc->priv->meta_store;
+			}
+		}
+		if (!store) {
+			cnc->priv->meta_store = gda_meta_store_new (NULL);
+			if (cnc->priv->is_thread_wrapper)
+				cdata->sub_connection->priv->meta_store = 
+					g_object_ref (cnc->priv->meta_store);
+		}
+	}
 	store = cnc->priv->meta_store;
 	gda_connection_unlock ((GdaLockable*) cnc);
 	

Modified: trunk/libgda/gda-connection.h
==============================================================================
--- trunk/libgda/gda-connection.h	(original)
+++ trunk/libgda/gda-connection.h	Tue Apr  7 19:10:50 2009
@@ -53,7 +53,9 @@
 	GDA_CONNECTION_NO_CNC_SPEC_ERROR,
 	GDA_CONNECTION_NO_PROVIDER_SPEC_ERROR,
 	GDA_CONNECTION_OPEN_ERROR,
-	GDA_CONNECTION_STATEMENT_TYPE_ERROR
+	GDA_CONNECTION_STATEMENT_TYPE_ERROR,
+	GDA_CONNECTION_CANT_LOCK_ERROR,
+	GDA_CONNECTION_TASK_NOT_FOUND_ERROR
 } GdaConnectionError;
 #define GDA_CONNECTION_NONEXIST_DSN_ERROR GDA_CONNECTION_DSN_NOT_FOUND_ERROR
 
@@ -82,7 +84,8 @@
 
 typedef enum {
         GDA_CONNECTION_OPTIONS_NONE = 0,
-	GDA_CONNECTION_OPTIONS_READ_ONLY = 1 << 0
+	GDA_CONNECTION_OPTIONS_READ_ONLY = 1 << 0,
+	GDA_CONNECTION_OPTIONS_THREAD_SAFE = 1 << 1
 } GdaConnectionOptions;
 
 typedef enum {
@@ -168,6 +171,7 @@
 gchar               *gda_connection_statement_to_sql     (GdaConnection *cnc,
 							  GdaStatement *stmt, GdaSet *params, GdaStatementSqlFlag flags,
 							  GSList **params_used, GError **error);
+/* synchronous exec */
 gboolean             gda_connection_statement_prepare    (GdaConnection *cnc,
 							  GdaStatement *stmt, GError **error);
 GObject             *gda_connection_statement_execute    (GdaConnection *cnc, GdaStatement *stmt, GdaSet *params, 
@@ -184,6 +188,15 @@
 gint                 gda_connection_statement_execute_non_select (GdaConnection *cnc, GdaStatement *stmt,
 								  GdaSet *params, GdaSet **last_insert_row, GError **error);
 
+/* Async. execution */
+guint                gda_connection_async_statement_execute (GdaConnection *cnc, GdaStatement *stmt, GdaSet *params, 
+							     GdaStatementModelUsage model_usage, GType *col_types,
+							     gboolean need_last_insert_row, 
+							     GError **error);
+GObject             *gda_connection_async_fetch_result      (GdaConnection *cnc, guint task_id, GdaSet **last_insert_row, GError **error);
+gboolean             gda_connection_async_cancel            (GdaConnection *cnc, guint task_id, GError **error);
+
+
 gboolean             gda_connection_begin_transaction    (GdaConnection *cnc, const gchar *name, 
 							  GdaTransactionIsolation level, GError **error);
 gboolean             gda_connection_commit_transaction   (GdaConnection *cnc, const gchar *name, GError **error);

Modified: trunk/libgda/gda-data-select.c
==============================================================================
--- trunk/libgda/gda-data-select.c	(original)
+++ trunk/libgda/gda-data-select.c	Tue Apr  7 19:10:50 2009
@@ -58,19 +58,13 @@
  */
 static gint external_to_internal_row (GdaDataSelect *model, gint ext_row, GError **error);
 
-/*
- * Getting a GdaRow from a model row:
- * [model row] ==(model->index)==> [index in model->rows] ==(model->rows)==> [GdaRow pointer]
- */
-struct _GdaDataSelectPrivate {
-	GdaConnection          *cnc;
+typedef struct {
 	GSList                 *columns; /* list of GdaColumn objects */
 	GArray                 *rows; /* Array of GdaRow pointers */
 	GHashTable             *index; /* key = model row number + 1, value = index in @rows array + 1*/
 
 	/* Internal iterator's information, if GDA_DATA_MODEL_CURSOR_* based access */
         gint                    iter_row; /* G_MININT if at start, G_MAXINT if at end, "external" row number */
-        GdaDataModelIter       *iter;
 
 	GdaStatement           *sel_stmt;
 	GdaSet                 *ext_params;
@@ -84,6 +78,18 @@
 	GArray                 *del_rows; /* array[index] = number of the index'th deleted row,
 					   * sorted by row number (row numbers are internal row numbers )*/
 	GHashTable             *upd_rows; /* key = internal row number + 1, value = a DelayedSelectStmt pointer */
+
+	gboolean                ref_count; /* when drop to 0 => free can be done */
+} PrivateShareable;
+
+/*
+ * Getting a GdaRow from a model row:
+ * [model row] ==(model->index)==> [index in model->rows] ==(model->rows)==> [GdaRow pointer]
+ */
+struct _GdaDataSelectPrivate {
+	GdaConnection          *cnc;
+        GdaDataModelIter       *iter;
+	PrivateShareable       *sh;
 };
 
 /* properties */
@@ -308,40 +314,43 @@
 	g_return_if_fail (GDA_IS_DATA_SELECT (model));
 	model->priv = g_new0 (GdaDataSelectPrivate, 1);
 	model->priv->cnc = NULL;
-	model->priv->rows = g_array_new (FALSE, FALSE, sizeof (GdaRow *));
-	model->priv->index = g_hash_table_new (g_direct_hash, g_direct_equal);
+	model->priv->sh = g_new0 (PrivateShareable, 1);
+	model->priv->sh->rows = g_array_new (FALSE, FALSE, sizeof (GdaRow *));
+	model->priv->sh->index = g_hash_table_new (g_direct_hash, g_direct_equal);
 	model->prep_stmt = NULL;
-	model->priv->columns = NULL;
+	model->priv->sh->columns = NULL;
 	model->nb_stored_rows = 0;
 	model->advertized_nrows = -1; /* unknown number of rows */
 
-	model->priv->sel_stmt = NULL;
-	model->priv->ext_params = NULL;
-	model->priv->reset_with_ext_params_change = FALSE;
+	model->priv->sh->sel_stmt = NULL;
+	model->priv->sh->ext_params = NULL;
+	model->priv->sh->reset_with_ext_params_change = FALSE;
 
-	model->priv->iter_row = G_MININT;
+	model->priv->sh->iter_row = G_MININT;
         model->priv->iter = NULL;
 
-	model->priv->modif_internals = g_new0 (GdaDataSelectInternals, 1);
-	model->priv->modif_internals->safely_locked = FALSE;
-	model->priv->modif_internals->unique_row_condition = NULL;
-	model->priv->modif_internals->insert_to_select_mapping = NULL;
-	model->priv->modif_internals->modif_set = NULL;
-	model->priv->modif_internals->exec_set = NULL;
+	model->priv->sh->modif_internals = g_new0 (GdaDataSelectInternals, 1);
+	model->priv->sh->modif_internals->safely_locked = FALSE;
+	model->priv->sh->modif_internals->unique_row_condition = NULL;
+	model->priv->sh->modif_internals->insert_to_select_mapping = NULL;
+	model->priv->sh->modif_internals->modif_set = NULL;
+	model->priv->sh->modif_internals->exec_set = NULL;
 	for (i = FIRST_QUERY; i < NB_QUERIES; i++)
-		model->priv->modif_internals->modif_stmts[i] = NULL;
-	model->priv->modif_internals->upd_stmts = NULL;
-	model->priv->modif_internals->ins_stmts = NULL;
-	model->priv->modif_internals->one_row_select_stmt = NULL;
+		model->priv->sh->modif_internals->modif_stmts[i] = NULL;
+	model->priv->sh->modif_internals->upd_stmts = NULL;
+	model->priv->sh->modif_internals->ins_stmts = NULL;
+	model->priv->sh->modif_internals->one_row_select_stmt = NULL;
 
-	model->priv->upd_rows = NULL;
-	model->priv->del_rows = NULL;
+	model->priv->sh->upd_rows = NULL;
+	model->priv->sh->del_rows = NULL;
+
+	model->priv->sh->ref_count = 1;
 }
 
 static void
 ext_params_holder_changed_cb (GdaSet *paramlist, GdaHolder *param, GdaDataSelect *model)
 {
-	if (model->priv->reset_with_ext_params_change) {
+	if (model->priv->sh->reset_with_ext_params_change) {
 		GdaDataSelect *new_model;
 		GdaStatement *select;
 		GError *error = NULL;
@@ -362,8 +371,8 @@
 			types [model->prep_stmt->ncols] = G_TYPE_NONE;
 		}
 		new_model = (GdaDataSelect*) gda_connection_statement_execute_select_full (model->priv->cnc, select, 
-											   model->priv->ext_params, 
-											   model->priv->usage_flags | GDA_STATEMENT_MODEL_ALLOW_NOPARAM,
+											   model->priv->sh->ext_params, 
+											   model->priv->sh->usage_flags | GDA_STATEMENT_MODEL_ALLOW_NOPARAM,
 											   types, 
 											   &error);
 		g_free (types);
@@ -394,21 +403,21 @@
 		GdaDataSelect *old_model = new_model; /* renamed for code's readability */
 		GdaDataSelectInternals *mi;
 	      
-		model->priv->reset_with_ext_params_change = old_model->priv->reset_with_ext_params_change;
-		mi = old_model->priv->modif_internals;
-		old_model->priv->modif_internals = model->priv->modif_internals;
-		model->priv->modif_internals = mi;
-
-		copy = old_model->priv->sel_stmt;
-		old_model->priv->sel_stmt = model->priv->sel_stmt;
-		model->priv->sel_stmt = (GdaStatement*) copy;
+		model->priv->sh->reset_with_ext_params_change = old_model->priv->sh->reset_with_ext_params_change;
+		mi = old_model->priv->sh->modif_internals;
+		old_model->priv->sh->modif_internals = model->priv->sh->modif_internals;
+		model->priv->sh->modif_internals = mi;
+
+		copy = old_model->priv->sh->sel_stmt;
+		old_model->priv->sh->sel_stmt = model->priv->sh->sel_stmt;
+		model->priv->sh->sel_stmt = (GdaStatement*) copy;
 
 		/* keep the same GdaColumn pointers */
 		GSList *l1, *l2;
-		l1 = old_model->priv->columns;
-		old_model->priv->columns = model->priv->columns;
-		model->priv->columns = l1;
-		for (l1 = model->priv->columns, l2 = old_model->priv->columns;
+		l1 = old_model->priv->sh->columns;
+		old_model->priv->sh->columns = model->priv->sh->columns;
+		model->priv->sh->columns = l1;
+		for (l1 = model->priv->sh->columns, l2 = old_model->priv->sh->columns;
 		     l1 && l2;
 		     l1 = l1->next, l2 = l2->next) {
 			if ((gda_column_get_g_type ((GdaColumn*) l1->data) == GDA_TYPE_NULL) &&
@@ -419,12 +428,12 @@
 
 		g_object_unref (old_model);
 
-		/* copy all the param's holders' values from model->priv->ext_params to 
-		   to model->priv->modif_internals->exec_set */
+		/* copy all the param's holders' values from model->priv->sh->ext_params to 
+		   to model->priv->sh->modif_internals->exec_set */
 		GSList *list;
-		for (list = model->priv->ext_params->holders; list; list = list->next) {
+		for (list = model->priv->sh->ext_params->holders; list; list = list->next) {
 			GdaHolder *h;
-			h = gda_set_get_holder (model->priv->modif_internals->exec_set,
+			h = gda_set_get_holder (model->priv->sh->modif_internals->exec_set,
 						gda_holder_get_id (list->data));
 			if (h) {
 				if (!gda_holder_is_valid (GDA_HOLDER (list->data))) 
@@ -446,77 +455,116 @@
 }
 
 static void
-gda_data_select_dispose (GObject *object)
+free_private_shared_data (GdaDataSelect *model)
 {
-	GdaDataSelect *model = (GdaDataSelect *) object;
-
-	g_return_if_fail (GDA_IS_DATA_SELECT (model));
-
-	/* free memory */
-	if (model->priv) {
+	if (!model->priv->sh)
+		return;
+	model->priv->sh->ref_count --;
+	if (model->priv->sh->ref_count == 0) {
 		gint i;
 
-		if (model->priv->sel_stmt) {
-			g_object_unref (model->priv->sel_stmt);
-			model->priv->sel_stmt = NULL;
+		if (model->priv->sh->sel_stmt) {
+			g_object_unref (model->priv->sh->sel_stmt);
+			model->priv->sh->sel_stmt = NULL;
 		}
 
-		if (model->priv->ext_params) {
-			g_signal_handlers_disconnect_by_func (model->priv->ext_params,
+		if (model->priv->sh->ext_params) {
+			g_signal_handlers_disconnect_by_func (model->priv->sh->ext_params,
 							      G_CALLBACK (ext_params_holder_changed_cb), model);
-			g_object_unref (model->priv->ext_params);
-			model->priv->ext_params = NULL;
+			g_object_unref (model->priv->sh->ext_params);
+			model->priv->sh->ext_params = NULL;
 		}
 
-		if (model->priv->modif_internals) {
-			_gda_data_select_internals_free (model->priv->modif_internals);
-			model->priv->modif_internals = NULL;
+		if (model->priv->sh->modif_internals) {
+			_gda_data_select_internals_free (model->priv->sh->modif_internals);
+			model->priv->sh->modif_internals = NULL;
 		}
 
-		if (model->priv->upd_rows) {
-			g_hash_table_destroy (model->priv->upd_rows);
-			model->priv->upd_rows = NULL;
+		if (model->priv->sh->upd_rows) {
+			g_hash_table_destroy (model->priv->sh->upd_rows);
+			model->priv->sh->upd_rows = NULL;
 		}
 
-		if (model->priv->del_rows) {
-			g_array_free (model->priv->del_rows, TRUE);
-			model->priv->del_rows = NULL;
+		if (model->priv->sh->del_rows) {
+			g_array_free (model->priv->sh->del_rows, TRUE);
+			model->priv->sh->del_rows = NULL;
+		}
+		if (model->priv->sh->rows) {
+			for (i = 0; i < model->priv->sh->rows->len; i++) {
+				GdaRow *prow;
+				prow = g_array_index (model->priv->sh->rows, GdaRow *, i);
+				g_object_unref (prow);
+			}
+			g_array_free (model->priv->sh->rows, TRUE);
+			model->priv->sh->rows = NULL;
+		}
+		if (model->priv->sh->index) {
+			g_hash_table_destroy (model->priv->sh->index);
+			model->priv->sh->index = NULL;
 		}
+		if (model->priv->sh->columns) {
+			g_slist_foreach (model->priv->sh->columns, (GFunc) g_object_unref, NULL);
+			g_slist_free (model->priv->sh->columns);
+			model->priv->sh->columns = NULL;
+		}
+
+		g_free (model->priv->sh);
+	}
+	model->priv->sh = NULL;
+}
+
+static void
+gda_data_select_dispose (GObject *object)
+{
+	GdaDataSelect *model = (GdaDataSelect *) object;
+
+	g_return_if_fail (GDA_IS_DATA_SELECT (model));
 
+	/* free memory */
+	if (model->priv) {
 		if (model->priv->cnc) {
 			g_object_unref (model->priv->cnc);
 			model->priv->cnc = NULL;
 		}
-
+		if (model->priv->iter) {
+			g_object_unref (model->priv->iter);
+			model->priv->iter = NULL;
+		}
 		if (model->prep_stmt) {
 			g_object_unref (model->prep_stmt);
 			model->prep_stmt = NULL;
 		}
-
-		if (model->priv->rows) {
-			for (i = 0; i < model->priv->rows->len; i++) {
-				GdaRow *prow;
-				prow = g_array_index (model->priv->rows, GdaRow *, i);
-				g_object_unref (prow);
-			}
-			g_array_free (model->priv->rows, TRUE);
-			model->priv->rows = NULL;
-		}
-		if (model->priv->index) {
-			g_hash_table_destroy (model->priv->index);
-			model->priv->index = NULL;
-		}
-		if (model->priv->columns) {
-			g_slist_foreach (model->priv->columns, (GFunc) g_object_unref, NULL);
-			g_slist_free (model->priv->columns);
-			model->priv->columns = NULL;
-		}
+		free_private_shared_data (model);
+		g_free (model->priv);
+		model->priv = NULL;
 	}
 
 	/* chain to parent class */
 	parent_class->dispose (object);
 }
 
+/*
+ * Allows 2 GdaDataSelect objects to safely share the same private data (PrivateShareable pointer).
+ * NOTE: nothing is done to prevent the master and the slave to modify the provate data at the same
+ *       time: this must be done by the user implementing the GdaDataSelect objects.
+ *
+ * This API is used by the GdaThreadRecordset object
+ *
+ * On the master side (the one from which the private data is shared), nothing special happens, except
+ * that master->priv->sh->ref_count is increased by 1.
+ *
+ * On the slave side, what happens is:
+ *    - "free" slave->priv->sh
+ *    - slave->priv->sh = master->priv->sh
+ */
+void
+_gda_data_select_share_private_data (GdaDataSelect *master, GdaDataSelect *slave)
+{
+	master->priv->sh->ref_count ++;
+	free_private_shared_data (slave);
+	slave->priv->sh = master->priv->sh;
+}
+
 void
 _gda_data_select_internals_free (GdaDataSelectInternals *inter)
 {
@@ -586,8 +634,8 @@
 _gda_data_select_internals_steal (GdaDataSelect *model)
 {
 	GdaDataSelectInternals *inter;
-	inter = model->priv->modif_internals;
-	model->priv->modif_internals = NULL;
+	inter = model->priv->sh->modif_internals;
+	model->priv->sh->modif_internals = NULL;
 
 	return inter;
 }
@@ -595,19 +643,19 @@
 void
 _gda_data_select_internals_paste (GdaDataSelect *model, GdaDataSelectInternals *inter)
 {
-	if (model->priv->modif_internals)
-		_gda_data_select_internals_free (model->priv->modif_internals);
-	model->priv->modif_internals = inter;
+	if (model->priv->sh->modif_internals)
+		_gda_data_select_internals_free (model->priv->sh->modif_internals);
+	model->priv->sh->modif_internals = inter;
 }
 
 static void
 create_columns (GdaDataSelect *model) 
 {
 	gint i;
-	if (model->priv->columns) {
-		g_slist_foreach (model->priv->columns, (GFunc) g_object_unref, NULL);
-		g_slist_free (model->priv->columns);
-		model->priv->columns = NULL;
+	if (model->priv->sh->columns) {
+		g_slist_foreach (model->priv->sh->columns, (GFunc) g_object_unref, NULL);
+		g_slist_free (model->priv->sh->columns);
+		model->priv->sh->columns = NULL;
 	}
 	if (!model->prep_stmt)
 		return;
@@ -619,7 +667,7 @@
 		/* copy template columns */
 		GSList *list;
 		for (list = model->prep_stmt->tmpl_columns; list; list = list->next)
-			model->priv->columns = g_slist_append (model->priv->columns, g_object_ref (list->data));
+			model->priv->sh->columns = g_slist_append (model->priv->sh->columns, g_object_ref (list->data));
 	}
 	else {
 		/* create columns */
@@ -628,7 +676,7 @@
 			gda_col = gda_column_new ();
 			if (model->prep_stmt->types) 
 				gda_column_set_g_type (gda_col, model->prep_stmt->types [i]);
-			model->priv->columns = g_slist_append (model->priv->columns, gda_col);
+			model->priv->sh->columns = g_slist_append (model->priv->sh->columns, gda_col);
 		}
 	}
 }
@@ -657,7 +705,7 @@
 				sel_stmt = gda_pstmt_get_gda_statement (model->prep_stmt);
 				if (sel_stmt &&
 				    gda_statement_get_statement_type (sel_stmt) == GDA_SQL_STATEMENT_SELECT) 
-					model->priv->sel_stmt = gda_statement_copy (sel_stmt);
+					model->priv->sh->sel_stmt = gda_statement_copy (sel_stmt);
 			}
 			create_columns (model);
 			break;
@@ -666,7 +714,7 @@
 			if (!(flags & GDA_DATA_MODEL_ACCESS_RANDOM) &&
 			    (flags & GDA_DATA_MODEL_ACCESS_CURSOR_BACKWARD))
 				flags = GDA_DATA_MODEL_ACCESS_CURSOR;
-			model->priv->usage_flags = flags;
+			model->priv->sh->usage_flags = flags;
 			break;
 		}
 		case PROP_ALL_STORED:
@@ -684,36 +732,36 @@
 			GdaSet *set;
 			set = g_value_get_object (value);
 			if (set) {
-				model->priv->ext_params = g_object_ref (set);
-				g_signal_connect (model->priv->ext_params, "holder-changed",
+				model->priv->sh->ext_params = g_object_ref (set);
+				g_signal_connect (model->priv->sh->ext_params, "holder-changed",
 						  G_CALLBACK (ext_params_holder_changed_cb), model);
-				model->priv->modif_internals->exec_set = gda_set_copy (set);
+				model->priv->sh->modif_internals->exec_set = gda_set_copy (set);
 			}
 			break;
 		}
 		case PROP_INS_QUERY:
-			if (model->priv->modif_internals->modif_stmts [INS_QUERY])
-				g_object_unref (model->priv->modif_internals->modif_stmts [INS_QUERY]);
-			model->priv->modif_internals->modif_stmts [INS_QUERY] = g_value_get_object (value);
-			if (model->priv->modif_internals->modif_stmts [INS_QUERY])
-				g_object_ref (model->priv->modif_internals->modif_stmts [INS_QUERY]);
+			if (model->priv->sh->modif_internals->modif_stmts [INS_QUERY])
+				g_object_unref (model->priv->sh->modif_internals->modif_stmts [INS_QUERY]);
+			model->priv->sh->modif_internals->modif_stmts [INS_QUERY] = g_value_get_object (value);
+			if (model->priv->sh->modif_internals->modif_stmts [INS_QUERY])
+				g_object_ref (model->priv->sh->modif_internals->modif_stmts [INS_QUERY]);
 			break;
 		case PROP_DEL_QUERY:
-			if (model->priv->modif_internals->modif_stmts [DEL_QUERY])
-				g_object_unref (model->priv->modif_internals->modif_stmts [DEL_QUERY]);
-			model->priv->modif_internals->modif_stmts [DEL_QUERY] = g_value_get_object (value);
-			if (model->priv->modif_internals->modif_stmts [DEL_QUERY])
-				g_object_ref (model->priv->modif_internals->modif_stmts [DEL_QUERY]);
+			if (model->priv->sh->modif_internals->modif_stmts [DEL_QUERY])
+				g_object_unref (model->priv->sh->modif_internals->modif_stmts [DEL_QUERY]);
+			model->priv->sh->modif_internals->modif_stmts [DEL_QUERY] = g_value_get_object (value);
+			if (model->priv->sh->modif_internals->modif_stmts [DEL_QUERY])
+				g_object_ref (model->priv->sh->modif_internals->modif_stmts [DEL_QUERY]);
 			break;
 		case PROP_UPD_QUERY:
-			if (model->priv->modif_internals->modif_stmts [UPD_QUERY])
-				g_object_unref (model->priv->modif_internals->modif_stmts [UPD_QUERY]);
-			model->priv->modif_internals->modif_stmts [UPD_QUERY] = g_value_get_object (value);
-			if (model->priv->modif_internals->modif_stmts [UPD_QUERY])
-				g_object_ref (model->priv->modif_internals->modif_stmts [UPD_QUERY]);
+			if (model->priv->sh->modif_internals->modif_stmts [UPD_QUERY])
+				g_object_unref (model->priv->sh->modif_internals->modif_stmts [UPD_QUERY]);
+			model->priv->sh->modif_internals->modif_stmts [UPD_QUERY] = g_value_get_object (value);
+			if (model->priv->sh->modif_internals->modif_stmts [UPD_QUERY])
+				g_object_ref (model->priv->sh->modif_internals->modif_stmts [UPD_QUERY]);
 			break;
 		case PROP_RESET_WITH_EXT_PARAM:
-			model->priv->reset_with_ext_params_change = g_value_get_boolean (value);
+			model->priv->sh->reset_with_ext_params_change = g_value_get_boolean (value);
 			break;
 		default:
 			break;
@@ -737,10 +785,10 @@
 			g_value_set_object (value, model->prep_stmt);
 			break;
 		case PROP_FLAGS:
-			g_value_set_uint (value, model->priv->usage_flags);
+			g_value_set_uint (value, model->priv->sh->usage_flags);
 			break;
 		case PROP_ALL_STORED:
-			if (!model->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)
+			if (!model->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)
 				g_warning ("Cannot set the 'store-all-rows' property when acces mode is cursor based");
 			else {
 				if ((model->advertized_nrows < 0) && CLASS (model)->fetch_nb_rows)
@@ -749,22 +797,22 @@
 			}
 			break;
 		case PROP_PARAMS:
-			g_value_set_object (value, model->priv->modif_internals->exec_set);
+			g_value_set_object (value, model->priv->sh->modif_internals->exec_set);
 			break;
 		case PROP_INS_QUERY:
-			g_value_set_object (value, model->priv->modif_internals->modif_stmts [INS_QUERY]);
+			g_value_set_object (value, model->priv->sh->modif_internals->modif_stmts [INS_QUERY]);
 			break;
 		case PROP_DEL_QUERY:
-			g_value_set_object (value, model->priv->modif_internals->modif_stmts [DEL_QUERY]);
+			g_value_set_object (value, model->priv->sh->modif_internals->modif_stmts [DEL_QUERY]);
 			break;
 		case PROP_UPD_QUERY:
-			g_value_set_object (value, model->priv->modif_internals->modif_stmts [UPD_QUERY]);
+			g_value_set_object (value, model->priv->sh->modif_internals->modif_stmts [UPD_QUERY]);
 			break;
 		case PROP_SEL_STMT: 
 			g_value_set_object (value, check_acceptable_statement (model, NULL));
 			break;
 		case PROP_RESET_WITH_EXT_PARAM:
-			g_value_set_boolean (value, model->priv->reset_with_ext_params_change);
+			g_value_set_boolean (value, model->priv->sh->reset_with_ext_params_change);
 			break;
 		default:
 			break;
@@ -787,13 +835,13 @@
 	g_return_if_fail (GDA_IS_DATA_SELECT (model));
 	g_return_if_fail (GDA_IS_ROW (row));
 
-	if (g_hash_table_lookup (model->priv->index, GINT_TO_POINTER (rownum + 1))) 
+	if (g_hash_table_lookup (model->priv->sh->index, GINT_TO_POINTER (rownum + 1))) 
 		g_error ("INTERNAL error: row %d already exists, aborting", rownum);
 
-	g_hash_table_insert (model->priv->index, GINT_TO_POINTER (rownum + 1),
-			     GINT_TO_POINTER (model->priv->rows->len + 1));
-	g_array_append_val (model->priv->rows, row);
-	model->nb_stored_rows = model->priv->rows->len;
+	g_hash_table_insert (model->priv->sh->index, GINT_TO_POINTER (rownum + 1),
+			     GINT_TO_POINTER (model->priv->sh->rows->len + 1));
+	g_array_append_val (model->priv->sh->rows, row);
+	model->nb_stored_rows = model->priv->sh->rows->len;
 }
 
 /**
@@ -812,11 +860,11 @@
 	g_return_val_if_fail (GDA_IS_DATA_SELECT (model), NULL);
 	g_return_val_if_fail (model->priv, NULL);
 
-	irow = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->index, GINT_TO_POINTER (rownum + 1)));
+	irow = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->sh->index, GINT_TO_POINTER (rownum + 1)));
 	if (irow <= 0) 
 		return NULL;
 	else 
-		return g_array_index (model->priv->rows, GdaRow *, irow - 1);
+		return g_array_index (model->priv->sh->rows, GdaRow *, irow - 1);
 }
 
 /**
@@ -838,38 +886,38 @@
 }
 
 /*
- * Add the +/-<col num> holders to model->priv->modif_internals->modif_set
+ * Add the +/-<col num> holders to model->priv->sh->modif_internals->modif_set
  */
 static gboolean
 compute_modif_set (GdaDataSelect *model, GError **error)
 {
 	gint i;
 	
-	if (model->priv->modif_internals->modif_set)
-		g_object_unref (model->priv->modif_internals->modif_set);
-	if (model->priv->modif_internals->exec_set)
-		model->priv->modif_internals->modif_set = gda_set_copy (model->priv->modif_internals->exec_set);
+	if (model->priv->sh->modif_internals->modif_set)
+		g_object_unref (model->priv->sh->modif_internals->modif_set);
+	if (model->priv->sh->modif_internals->exec_set)
+		model->priv->sh->modif_internals->modif_set = gda_set_copy (model->priv->sh->modif_internals->exec_set);
 	else
-		model->priv->modif_internals->modif_set = gda_set_new (NULL);
+		model->priv->sh->modif_internals->modif_set = gda_set_new (NULL);
 
 	for (i = 0; i < NB_QUERIES; i++) {
 		GdaSet *set;
-		if (! model->priv->modif_internals->modif_stmts [i])
+		if (! model->priv->sh->modif_internals->modif_stmts [i])
 			continue;
-		if (! gda_statement_get_parameters (model->priv->modif_internals->modif_stmts [i], &set, error)) {
-			g_object_unref (model->priv->modif_internals->modif_set);
-			model->priv->modif_internals->modif_set = NULL;
+		if (! gda_statement_get_parameters (model->priv->sh->modif_internals->modif_stmts [i], &set, error)) {
+			g_object_unref (model->priv->sh->modif_internals->modif_set);
+			model->priv->sh->modif_internals->modif_set = NULL;
 			return FALSE;
 		}
 
-		gda_set_merge_with_set (model->priv->modif_internals->modif_set, set);
+		gda_set_merge_with_set (model->priv->sh->modif_internals->modif_set, set);
 		g_object_unref (set);
 	}
 
 #ifdef GDA_DEBUG_NO
 	GSList *list;
 	g_print ("-------\n");
-	for (list = model->priv->modif_internals->modif_set->holders; list; list = list->next) {
+	for (list = model->priv->sh->modif_internals->modif_set->holders; list; list = list->next) {
 		GdaHolder *h = GDA_HOLDER (list->data);
 		g_print ("=> holder '%s'\n", gda_holder_get_id (h));
 	}
@@ -964,8 +1012,8 @@
 {
 	GdaStatement *sel_stmt;
 
-	if (model->priv->sel_stmt)
-		return model->priv->sel_stmt;
+	if (model->priv->sh->sel_stmt)
+		return model->priv->sh->sel_stmt;
 
 	if (! model->prep_stmt) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MODIFICATION_STATEMENT_ERROR,
@@ -986,8 +1034,8 @@
 		return NULL;
 	}
 
-	model->priv->sel_stmt = gda_statement_copy (sel_stmt);
-	return model->priv->sel_stmt;
+	model->priv->sh->sel_stmt = gda_statement_copy (sel_stmt);
+	return model->priv->sh->sel_stmt;
 }
 
 /**
@@ -1071,14 +1119,14 @@
 
 		mtype = DEL_QUERY;
 
-		/* if there is no WHERE part, then use model->priv->modif_internals->unique_row_condition if set */
+		/* if there is no WHERE part, then use model->priv->sh->modif_internals->unique_row_condition if set */
 		g_object_get (G_OBJECT (mod_stmt), "structure", &sqlst, NULL);
 		g_assert (sqlst);
 		del = (GdaSqlStatementDelete*) sqlst->contents;
 		if (!del->cond) {
-			if (model->priv->modif_internals->unique_row_condition) {
-				/* copy model->priv->modif_internals->unique_row_condition */
-				del->cond = gda_sql_expr_copy (model->priv->modif_internals->unique_row_condition);
+			if (model->priv->sh->modif_internals->unique_row_condition) {
+				/* copy model->priv->sh->modif_internals->unique_row_condition */
+				del->cond = gda_sql_expr_copy (model->priv->sh->modif_internals->unique_row_condition);
 				GDA_SQL_ANY_PART (del->cond)->parent = GDA_SQL_ANY_PART (del);
 				g_object_set (G_OBJECT (mod_stmt), "structure", sqlst, NULL);
 			}
@@ -1090,10 +1138,10 @@
 			}
 		}
 		else {
-			if (model->priv->modif_internals->unique_row_condition) {
-				/* replace WHERE with model->priv->modif_internals->unique_row_condition */
+			if (model->priv->sh->modif_internals->unique_row_condition) {
+				/* replace WHERE with model->priv->sh->modif_internals->unique_row_condition */
 				gda_sql_expr_free (del->cond);
-				del->cond = gda_sql_expr_copy (model->priv->modif_internals->unique_row_condition);
+				del->cond = gda_sql_expr_copy (model->priv->sh->modif_internals->unique_row_condition);
 				GDA_SQL_ANY_PART (del->cond)->parent = GDA_SQL_ANY_PART (del);
 				g_object_set (G_OBJECT (mod_stmt), "structure", sqlst, NULL);
 			}
@@ -1111,14 +1159,14 @@
 
 		mtype = UPD_QUERY;
 
-		/* if there is no WHERE part, then use model->priv->modif_internals->unique_row_condition if set */
+		/* if there is no WHERE part, then use model->priv->sh->modif_internals->unique_row_condition if set */
 		g_object_get (G_OBJECT (mod_stmt), "structure", &sqlst, NULL);
 		g_assert (sqlst);
 		upd = (GdaSqlStatementUpdate*) sqlst->contents;
 		if (!upd->cond) {
-			if (model->priv->modif_internals->unique_row_condition) {
-				/* copy model->priv->modif_internals->unique_row_condition */
-				upd->cond = gda_sql_expr_copy (model->priv->modif_internals->unique_row_condition);
+			if (model->priv->sh->modif_internals->unique_row_condition) {
+				/* copy model->priv->sh->modif_internals->unique_row_condition */
+				upd->cond = gda_sql_expr_copy (model->priv->sh->modif_internals->unique_row_condition);
 				GDA_SQL_ANY_PART (upd->cond)->parent = GDA_SQL_ANY_PART (upd);
 				g_object_set (G_OBJECT (mod_stmt), "structure", sqlst, NULL);
 			}
@@ -1130,10 +1178,10 @@
 			}
 		}	
 		else {
-			if (model->priv->modif_internals->unique_row_condition) {
-				/* replace WHERE with model->priv->modif_internals->unique_row_condition */
+			if (model->priv->sh->modif_internals->unique_row_condition) {
+				/* replace WHERE with model->priv->sh->modif_internals->unique_row_condition */
 				gda_sql_expr_free (upd->cond);
-				upd->cond = gda_sql_expr_copy (model->priv->modif_internals->unique_row_condition);
+				upd->cond = gda_sql_expr_copy (model->priv->sh->modif_internals->unique_row_condition);
 				GDA_SQL_ANY_PART (upd->cond)->parent = GDA_SQL_ANY_PART (upd);
 				g_object_set (G_OBJECT (mod_stmt), "structure", sqlst, NULL);
 			}
@@ -1153,16 +1201,16 @@
 		if (! gda_statement_check_structure (mod_stmt, error))
 			return FALSE;
 
-		if (model->priv->modif_internals->modif_stmts[mtype]) {
-			g_object_unref (model->priv->modif_internals->modif_stmts[mtype]);
-			model->priv->modif_internals->modif_stmts[mtype] = NULL;
+		if (model->priv->sh->modif_internals->modif_stmts[mtype]) {
+			g_object_unref (model->priv->sh->modif_internals->modif_stmts[mtype]);
+			model->priv->sh->modif_internals->modif_stmts[mtype] = NULL;
 		}
 
-		/* prepare model->priv->modif_internals->modif_set */
+		/* prepare model->priv->sh->modif_internals->modif_set */
 		if (!compute_modif_set (model, error))
 			return FALSE;
 
-		/* check that all the parameters required to execute @mod_stmt are in model->priv->modif_internals->modif_set */
+		/* check that all the parameters required to execute @mod_stmt are in model->priv->sh->modif_internals->modif_set */
 		GdaSet *params;
 		GSList *list;
 		if (! gda_statement_get_parameters (mod_stmt, &params, error))
@@ -1170,7 +1218,7 @@
 		for (list = params->holders; list; list = list->next) {
 			GdaHolder *holder = GDA_HOLDER (list->data);
 			GdaHolder *eholder;
-			eholder = gda_set_get_holder (model->priv->modif_internals->modif_set, gda_holder_get_id (holder));
+			eholder = gda_set_get_holder (model->priv->sh->modif_internals->modif_set, gda_holder_get_id (holder));
 			if (!eholder) {
 				gint num;
 				gboolean is_old;
@@ -1189,7 +1237,7 @@
 					g_object_unref (params);
 					return FALSE;
 				}
-				gda_set_add_holder (model->priv->modif_internals->modif_set, holder);
+				gda_set_add_holder (model->priv->sh->modif_internals->modif_set, holder);
 			}
 			else if (gda_holder_get_g_type (holder) != gda_holder_get_g_type (eholder)) {
 				g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MODIFICATION_STATEMENT_ERROR,
@@ -1203,7 +1251,7 @@
 		}
 		g_object_unref (params);
 
-		model->priv->modif_internals->modif_stmts[mtype] = mod_stmt;
+		model->priv->sh->modif_internals->modif_stmts[mtype] = mod_stmt;
 		g_object_ref (mod_stmt);
 	}
 	else {
@@ -1215,8 +1263,8 @@
 #ifdef GDA_DEBUG_NO
 	GSList *hlist;
 	g_print ("SET MODIF QUERY\n");
-	if (model->priv->modif_internals->modif_set) {
-		for (hlist = model->priv->modif_internals->modif_set->holders; hlist; hlist = hlist->next) {
+	if (model->priv->sh->modif_internals->modif_set) {
+		for (hlist = model->priv->sh->modif_internals->modif_set->holders; hlist; hlist = hlist->next) {
 			GdaHolder *h = GDA_HOLDER (hlist->data);
 			g_print ("  %s type=> %s (%d)\n", gda_holder_get_id (h), g_type_name (gda_holder_get_g_type (h)),
 				 gda_holder_get_g_type (h));
@@ -1258,9 +1306,9 @@
 		return FALSE;
 	}
 	for (mtype = FIRST_QUERY; mtype < NB_QUERIES; mtype++)
-		if (model->priv->modif_internals->modif_stmts[mtype]) {
-			g_object_unref (model->priv->modif_internals->modif_stmts[mtype]);
-			model->priv->modif_internals->modif_stmts[mtype] = NULL;
+		if (model->priv->sh->modif_internals->modif_stmts[mtype]) {
+			g_object_unref (model->priv->sh->modif_internals->modif_stmts[mtype]);
+			model->priv->sh->modif_internals->modif_stmts[mtype] = NULL;
 		}
 
 	retval = gda_compute_dml_statements (model->priv->cnc, stmt, TRUE,
@@ -1330,7 +1378,7 @@
 	if (!check_acceptable_statement (model, error))
 		return FALSE;
 
-	if (model->priv->modif_internals->unique_row_condition) {
+	if (model->priv->sh->modif_internals->unique_row_condition) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MODIFICATION_STATEMENT_ERROR,
 			      "%s", _("Unique row condition has already been specified"));
 		return FALSE;
@@ -1342,7 +1390,7 @@
 	if (!valid)
 		return FALSE;
 	
-	model->priv->modif_internals->unique_row_condition = gda_sql_expr_copy (expr);
+	model->priv->sh->modif_internals->unique_row_condition = gda_sql_expr_copy (expr);
 	return TRUE;
 }
 
@@ -1385,7 +1433,7 @@
 	if (!check_acceptable_statement (model, error))
 		return FALSE;
 
-	if (model->priv->modif_internals->unique_row_condition) {
+	if (model->priv->sh->modif_internals->unique_row_condition) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MODIFICATION_STATEMENT_ERROR,
 			      "%s", _("Unique row condition has already been specified"));
 		return FALSE;
@@ -1517,12 +1565,12 @@
 
 	retval = imodel->advertized_nrows;
 	if ((imodel->advertized_nrows < 0) && 
-	    (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) &&
+	    (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) &&
 	    CLASS (model)->fetch_nb_rows)
 		retval = CLASS (model)->fetch_nb_rows (imodel);
 		
-	if ((retval > 0) && (imodel->priv->del_rows))
-		retval -= imodel->priv->del_rows->len;
+	if ((retval > 0) && (imodel->priv->sh->del_rows))
+		retval -= imodel->priv->sh->del_rows->len;
 	return retval;
 }
 
@@ -1548,7 +1596,7 @@
 	imodel = GDA_DATA_SELECT (model);
 	g_return_val_if_fail (imodel->priv, NULL);
 
-	return g_slist_nth_data (imodel->priv->columns, col);
+	return g_slist_nth_data (imodel->priv->sh->columns, col);
 }
 
 static GdaDataModelAccessFlags
@@ -1561,21 +1609,21 @@
 	imodel = GDA_DATA_SELECT (model);
 	g_return_val_if_fail (imodel->priv, 0);
 
-	if (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)
+	if (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)
 		flags = GDA_DATA_MODEL_ACCESS_RANDOM;
-	else if (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_CURSOR_FORWARD) {
-		if (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_CURSOR_BACKWARD)
+	else if (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_CURSOR_FORWARD) {
+		if (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_CURSOR_BACKWARD)
 			flags = GDA_DATA_MODEL_ACCESS_CURSOR;
 		else
 			flags = GDA_DATA_MODEL_ACCESS_CURSOR_FORWARD;
 	}
 
-	if (! imodel->priv->modif_internals->safely_locked) {
-		if (imodel->priv->modif_internals->modif_stmts [UPD_QUERY])
+	if (! imodel->priv->sh->modif_internals->safely_locked) {
+		if (imodel->priv->sh->modif_internals->modif_stmts [UPD_QUERY])
 			flags |= GDA_DATA_MODEL_ACCESS_UPDATE;
-		if (imodel->priv->modif_internals->modif_stmts [INS_QUERY])
+		if (imodel->priv->sh->modif_internals->modif_stmts [INS_QUERY])
 			flags |= GDA_DATA_MODEL_ACCESS_INSERT;
-		if (imodel->priv->modif_internals->modif_stmts [DEL_QUERY])
+		if (imodel->priv->sh->modif_internals->modif_stmts [DEL_QUERY])
 			flags |= GDA_DATA_MODEL_ACCESS_DELETE;
 	}
 
@@ -1593,10 +1641,10 @@
 	gint int_row = ext_row;
 
 	/* row number alteration: deleted rows */
-	if (model->priv->del_rows) {
+	if (model->priv->sh->del_rows) {
 		gint i;
-		for (i = 0; i < model->priv->del_rows->len; i++) {
-			gint indexed = g_array_index (model->priv->del_rows, gint, i);
+		for (i = 0; i < model->priv->sh->del_rows->len; i++) {
+			gint indexed = g_array_index (model->priv->sh->del_rows, gint, i);
 			if (indexed <= ext_row + i)
 				int_row += 1;
 			else
@@ -1624,9 +1672,9 @@
 }
 static void dump_d (GdaDataSelect *model)
 {
-	if (model->priv->upd_rows) {
+	if (model->priv->sh->upd_rows) {
 		g_print ("Delayed SELECT for %p:\n", model);
-		g_hash_table_foreach (model->priv->upd_rows, foreach_func_dump, NULL);
+		g_hash_table_foreach (model->priv->sh->upd_rows, foreach_func_dump, NULL);
 	}
 }
 #endif
@@ -1643,7 +1691,7 @@
 	g_return_val_if_fail (imodel->priv, NULL);
 
 	/* available only if GDA_DATA_MODEL_ACCESS_RANDOM */
-	if (! (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
+	if (! (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
 		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 			      "%s", _("Data model does only support random access"));
 		return NULL;
@@ -1667,15 +1715,15 @@
 #ifdef GDA_DEBUG_NO
 	dump_d (imodel);
 #endif
-	if (imodel->priv->upd_rows)
-		dstmt = g_hash_table_lookup (imodel->priv->upd_rows, &int_row);
+	if (imodel->priv->sh->upd_rows)
+		dstmt = g_hash_table_lookup (imodel->priv->sh->upd_rows, &int_row);
 	if (dstmt) {
 		if (! dstmt->row) {
 			GdaDataModel *tmpmodel;
 			if (!dstmt->select || !dstmt->params) {
 				g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 					      "%s", _("Unable to retreive data after modifications, no further modification will be allowed"));
-				imodel->priv->modif_internals->safely_locked = TRUE;
+				imodel->priv->sh->modif_internals->safely_locked = TRUE;
 				return NULL;
 			}
 			tmpmodel = gda_connection_statement_execute_select (imodel->priv->cnc, 
@@ -1684,7 +1732,7 @@
 			if (!tmpmodel) {
 				g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 					      "%s", _("Unable to retreive data after modifications, no further modification will be allowed"));
-				imodel->priv->modif_internals->safely_locked = TRUE;
+				imodel->priv->sh->modif_internals->safely_locked = TRUE;
 				return NULL;
 			}
 
@@ -1692,7 +1740,7 @@
 				g_object_unref (tmpmodel);
 				g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 					     "%s", _("Unable to retreive data after modifications, no further modification will be allowed"));
-				imodel->priv->modif_internals->safely_locked = TRUE;
+				imodel->priv->sh->modif_internals->safely_locked = TRUE;
 				return NULL;
 			}
 
@@ -1714,7 +1762,7 @@
 						g_object_unref (prow);
 						g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 							      "%s", _("Unable to retreive data after modifications, no further modification will be allowed"));
-						imodel->priv->modif_internals->safely_locked = TRUE;
+						imodel->priv->sh->modif_internals->safely_locked = TRUE;
 						return NULL;
 					}
 				}
@@ -1729,7 +1777,7 @@
 			prow = dstmt->row;
 	}
 	else {
-		irow = GPOINTER_TO_INT (g_hash_table_lookup (imodel->priv->index, 
+		irow = GPOINTER_TO_INT (g_hash_table_lookup (imodel->priv->sh->index, 
 							     GINT_TO_POINTER (int_row + 1)));
 		if (irow <= 0) {
 			prow = NULL;
@@ -1738,7 +1786,7 @@
 				return NULL;
 		}
 		else
-			prow = g_array_index (imodel->priv->rows, GdaRow *, irow - 1);
+			prow = g_array_index (imodel->priv->sh->rows, GdaRow *, irow - 1);
 	}
 	
 	g_assert (prow);
@@ -1760,9 +1808,9 @@
 	imodel = (GdaDataSelect *) model;
 	g_return_val_if_fail (imodel->priv, 0);
 	
-	if (imodel->priv->modif_internals->safely_locked || !imodel->priv->modif_internals->modif_stmts [UPD_QUERY])
+	if (imodel->priv->sh->modif_internals->safely_locked || !imodel->priv->sh->modif_internals->modif_stmts [UPD_QUERY])
 		flags = GDA_VALUE_ATTR_NO_MODIF;
-	GdaColumn *gdacol = g_slist_nth_data (imodel->priv->columns, col);
+	GdaColumn *gdacol = g_slist_nth_data (imodel->priv->sh->columns, col);
 	if (gdacol) {
 		if (gda_column_get_allow_null (gdacol))
 			flags |= GDA_VALUE_ATTR_CAN_BE_NULL;
@@ -1780,7 +1828,7 @@
 	imodel = (GdaDataSelect *) model;
 	g_return_val_if_fail (imodel->priv, 0);
 
-	if (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) 
+	if (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) 
 		return (GdaDataModelIter *) g_object_new (GDA_TYPE_DATA_MODEL_ITER,
 							  "data-model", model, NULL);
 	else {
@@ -1788,7 +1836,7 @@
 		if (! imodel->priv->iter) {
 			imodel->priv->iter = (GdaDataModelIter *) g_object_new (GDA_TYPE_DATA_MODEL_ITER,
 										"data-model", model, NULL);
-			imodel->priv->iter_row = -1;
+			imodel->priv->sh->iter_row = -1;
 		}
 		g_object_ref (imodel->priv->iter);
 		return imodel->priv->iter;
@@ -1807,25 +1855,26 @@
 	g_return_val_if_fail (GDA_IS_DATA_SELECT (model), FALSE);
 	imodel = (GdaDataSelect *) model;
 	g_return_val_if_fail (imodel->priv, FALSE);
-	g_return_val_if_fail (CLASS (model)->fetch_next, FALSE);
 
-	if (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) 
+	if (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) 
 		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->iter_row == G_MAXINT)
+	if (imodel->priv->sh->iter_row == G_MAXINT)
 		return FALSE;
-	else if (imodel->priv->iter_row == G_MININT)
+	else if (imodel->priv->sh->iter_row == G_MININT)
 		target_iter_row = 0;
 	else
-		target_iter_row = imodel->priv->iter_row + 1;
+		target_iter_row = imodel->priv->sh->iter_row + 1;
 	int_row = external_to_internal_row (imodel, target_iter_row, NULL);
 
-	irow = GPOINTER_TO_INT (g_hash_table_lookup (imodel->priv->index, GINT_TO_POINTER (int_row + 1)));
+	irow = GPOINTER_TO_INT (g_hash_table_lookup (imodel->priv->sh->index, GINT_TO_POINTER (int_row + 1)));
 	if (irow > 0)
-		prow = g_array_index (imodel->priv->rows, GdaRow *, irow - 1);
+		prow = g_array_index (imodel->priv->sh->rows, GdaRow *, irow - 1);
 	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);
@@ -1834,13 +1883,13 @@
 	}
 	
 	if (prow) {
-		imodel->priv->iter_row = target_iter_row;
+		imodel->priv->sh->iter_row = target_iter_row;
                 return update_iter (imodel, prow);
 	}
 	else {
 		g_signal_emit_by_name (iter, "end-of-data");
                 g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
-                imodel->priv->iter_row = G_MAXINT;
+                imodel->priv->sh->iter_row = G_MAXINT;
                 return FALSE;
 	}
 }
@@ -1856,28 +1905,29 @@
 	g_return_val_if_fail (GDA_IS_DATA_SELECT (model), FALSE);
 	imodel = (GdaDataSelect *) model;
 	g_return_val_if_fail (imodel->priv, FALSE);
-	g_return_val_if_fail (CLASS (model)->fetch_prev, FALSE);
 
-	if (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) 
+	if (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM) 
 		return gda_data_model_iter_move_prev_default (model, iter);
 
+	g_return_val_if_fail (CLASS (model)->fetch_prev, FALSE);
+
         g_return_val_if_fail (iter, FALSE);
         g_return_val_if_fail (imodel->priv->iter == iter, FALSE);
 
-        if (imodel->priv->iter_row <= 0)
+        if (imodel->priv->sh->iter_row <= 0)
                 goto prev_error;
 
-        else if (imodel->priv->iter_row == G_MAXINT) {
+        else if (imodel->priv->sh->iter_row == G_MAXINT) {
                 g_assert (imodel->advertized_nrows >= 0);
                 target_iter_row = imodel->advertized_nrows - 1;
         }
         else
-                target_iter_row = imodel->priv->iter_row - 1;
+                target_iter_row = imodel->priv->sh->iter_row - 1;
 
 	int_row = external_to_internal_row (imodel, target_iter_row, NULL);
-	irow = GPOINTER_TO_INT (g_hash_table_lookup (imodel->priv->index, GINT_TO_POINTER (int_row + 1)));
+	irow = GPOINTER_TO_INT (g_hash_table_lookup (imodel->priv->sh->index, GINT_TO_POINTER (int_row + 1)));
 	if (irow > 0)
-		prow = g_array_index (imodel->priv->rows, GdaRow *, irow - 1);
+		prow = g_array_index (imodel->priv->sh->rows, GdaRow *, irow - 1);
 	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);
@@ -1886,13 +1936,13 @@
 	}
 
 	if (prow) {
-		imodel->priv->iter_row = target_iter_row;
+		imodel->priv->sh->iter_row = target_iter_row;
                 return update_iter (imodel, prow);
 	}
 
  prev_error:
         g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
-        imodel->priv->iter_row = G_MININT;
+        imodel->priv->sh->iter_row = G_MININT;
         return FALSE;
 }
 
@@ -1908,16 +1958,16 @@
 	g_return_val_if_fail (imodel->priv, FALSE);
 
 	int_row = external_to_internal_row (imodel, row, NULL);
-	if (imodel->priv->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 = GPOINTER_TO_INT (g_hash_table_lookup (imodel->priv->index, 
+	irow = GPOINTER_TO_INT (g_hash_table_lookup (imodel->priv->sh->index, 
 						     GINT_TO_POINTER (int_row + 1)));
 	if (irow > 0)
-		prow = g_array_index (imodel->priv->rows, GdaRow *, irow - 1);
+		prow = g_array_index (imodel->priv->sh->rows, GdaRow *, irow - 1);
 
 	if (CLASS (model)->fetch_at) {
 		if (!CLASS (model)->fetch_at (imodel, &prow, int_row, NULL)) {
@@ -1928,18 +1978,18 @@
 		}
 
 		if (prow) {
-			imodel->priv->iter_row = row;
+			imodel->priv->sh->iter_row = row;
 			return update_iter (imodel, prow);
 		}
 		else {
 			g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
-			imodel->priv->iter_row = G_MININT;
+			imodel->priv->sh->iter_row = G_MININT;
 			return FALSE;
 		}
 	}
 	else {
 		if (prow) {
-			imodel->priv->iter_row = row;
+			imodel->priv->sh->iter_row = row;
 			return update_iter (imodel, prow);
 		}
 		else {
@@ -2001,7 +2051,7 @@
 		}
         }
 
-	g_object_set (G_OBJECT (iter), "current-row", imodel->priv->iter_row, NULL);
+	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);
 
@@ -2009,7 +2059,7 @@
 }
 
 /*
- * creates a derivative of the model->priv->modif_internals->modif_stmts [UPD_QUERY] statement
+ * creates a derivative of the model->priv->sh->modif_internals->modif_stmts [UPD_QUERY] statement
  * where only the columns where @bv->data[colnum] is not 0 are updated.
  *
  * Returns: a new #GdaStatement, or %NULL
@@ -2022,8 +2072,8 @@
 	GdaStatement *updstmt = NULL;
 
 	/* get a copy of complete UPDATE stmt */
-	g_assert (model->priv->modif_internals->modif_stmts [UPD_QUERY]);
-	g_object_get (G_OBJECT (model->priv->modif_internals->modif_stmts [UPD_QUERY]), "structure", &sqlst, NULL);
+	g_assert (model->priv->sh->modif_internals->modif_stmts [UPD_QUERY]);
+	g_object_get (G_OBJECT (model->priv->sh->modif_internals->modif_stmts [UPD_QUERY]), "structure", &sqlst, NULL);
 	g_assert (sqlst);
 	g_free (sqlst->sql);
 	sqlst->sql = NULL;
@@ -2102,7 +2152,7 @@
 }
 
 /*
- * creates a derivative of the model->priv->modif_internals->modif_stmts [INS_QUERY] statement
+ * creates a derivative of the model->priv->sh->modif_internals->modif_stmts [INS_QUERY] statement
  * where only the columns where @bv->data[colnum] is not 0 are not mentionned.
  *
  * Returns: a new #GdaStatement, or %NULL
@@ -2115,8 +2165,8 @@
 	GdaStatement *insstmt = NULL;
 
 	/* get a copy of complete INSERT stmt */
-	g_assert (model->priv->modif_internals->modif_stmts [INS_QUERY]);
-	g_object_get (G_OBJECT (model->priv->modif_internals->modif_stmts [INS_QUERY]), "structure", &sqlst, NULL);
+	g_assert (model->priv->sh->modif_internals->modif_stmts [INS_QUERY]);
+	g_object_get (G_OBJECT (model->priv->sh->modif_internals->modif_stmts [INS_QUERY]), "structure", &sqlst, NULL);
 	g_assert (sqlst);
 	g_free (sqlst->sql);
 	sqlst->sql = NULL;
@@ -2212,20 +2262,20 @@
 	GdaSqlStatement *sel_sqlst;
 	GdaSqlExpr *row_cond = NULL;
 
-	sel_stmt = model->priv->sel_stmt;
+	sel_stmt = model->priv->sh->sel_stmt;
 	if (! sel_stmt) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MODIFICATION_STATEMENT_ERROR,
 			      "%s", _("Internal error: can't get the prepared statement's actual statement"));
 		return NULL;
 	}
 	
-	if (model->priv->modif_internals->unique_row_condition)
-		row_cond = gda_sql_expr_copy (model->priv->modif_internals->unique_row_condition);
-	else if (model->priv->modif_internals->modif_stmts [DEL_QUERY]) {
+	if (model->priv->sh->modif_internals->unique_row_condition)
+		row_cond = gda_sql_expr_copy (model->priv->sh->modif_internals->unique_row_condition);
+	else if (model->priv->sh->modif_internals->modif_stmts [DEL_QUERY]) {
 		GdaStatement *del_stmt;
 		GdaSqlStatement *del_sqlst;
 		GdaSqlStatementDelete *del;
-		del_stmt = model->priv->modif_internals->modif_stmts [DEL_QUERY];
+		del_stmt = model->priv->sh->modif_internals->modif_stmts [DEL_QUERY];
 		
 		g_object_get (G_OBJECT (del_stmt), "structure", &del_sqlst, NULL);
 		del = (GdaSqlStatementDelete*) del_sqlst->contents;
@@ -2237,11 +2287,11 @@
 			row_cond = NULL;
 		}
 	}
-	else if (model->priv->modif_internals->modif_stmts [UPD_QUERY]) {
+	else if (model->priv->sh->modif_internals->modif_stmts [UPD_QUERY]) {
 		GdaStatement *upd_stmt;
 		GdaSqlStatement *upd_sqlst;
 		GdaSqlStatementUpdate *upd;
-		upd_stmt = model->priv->modif_internals->modif_stmts [UPD_QUERY];
+		upd_stmt = model->priv->sh->modif_internals->modif_stmts [UPD_QUERY];
 		
 		g_object_get (G_OBJECT (upd_stmt), "structure", &upd_sqlst, NULL);
 		upd = (GdaSqlStatementUpdate*) upd_sqlst->contents;
@@ -2341,17 +2391,17 @@
 	/* arguments check */
 	g_assert (bv);
 
-	if (imodel->priv->modif_internals->safely_locked) {
+	if (imodel->priv->sh->modif_internals->safely_locked) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_SAFETY_LOCKED_ERROR,
 			      "%s", _("Modifications are not allowed anymore"));
 		return FALSE;
 	}
-	if (!iter && ! (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
+	if (!iter && ! (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
 		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 			     "%s", _("Data model does only support random access"));
 		return FALSE;
 	}
-	if (! imodel->priv->modif_internals->modif_stmts [UPD_QUERY]) {
+	if (! imodel->priv->sh->modif_internals->modif_stmts [UPD_QUERY]) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
 			     "%s", _("No UPDATE statement provided"));
 		return FALSE;
@@ -2365,15 +2415,15 @@
 		return FALSE;
 
 	/* compute UPDATE statement */
-	if (! imodel->priv->modif_internals->upd_stmts)
-		imodel->priv->modif_internals->upd_stmts = g_hash_table_new_full ((GHashFunc) bvector_hash, (GEqualFunc) bvector_equal, 
+	if (! imodel->priv->sh->modif_internals->upd_stmts)
+		imodel->priv->sh->modif_internals->upd_stmts = g_hash_table_new_full ((GHashFunc) bvector_hash, (GEqualFunc) bvector_equal, 
 								 (GDestroyNotify) bvector_free, g_object_unref);
-	stmt = g_hash_table_lookup (imodel->priv->modif_internals->upd_stmts, bv);
+	stmt = g_hash_table_lookup (imodel->priv->sh->modif_internals->upd_stmts, bv);
 	if (! stmt) {
 		stmt = compute_single_update_stmt (imodel, bv, error);
 		if (stmt) {
 			free_bv = FALSE;
-			g_hash_table_insert (imodel->priv->modif_internals->upd_stmts, bv, stmt);
+			g_hash_table_insert (imodel->priv->sh->modif_internals->upd_stmts, bv, stmt);
 		}
 		else {
 			bvector_free (bv);
@@ -2385,7 +2435,7 @@
 	ncols = gda_data_select_get_n_columns ((GdaDataModel*) imodel);
 	for (i = 0; i < ncols; i++) {
 		str = g_strdup_printf ("-%d", i);
-		holder = gda_set_get_holder (imodel->priv->modif_internals->modif_set, str);
+		holder = gda_set_get_holder (imodel->priv->sh->modif_internals->modif_set, str);
 		g_free (str);
 		if (holder) {
 			const GValue *cvalue;
@@ -2417,7 +2467,7 @@
 	GError *lerror = NULL;
 	sql = gda_statement_to_sql_extended (stmt,
 					     imodel->priv->cnc,
-					     imodel->priv->modif_internals->modif_set, 
+					     imodel->priv->sh->modif_internals->modif_set, 
 					     GDA_STATEMENT_SQL_PRETTY, NULL,
 					     &lerror);
 	g_print ("%s(): SQL=> %s\n", __FUNCTION__, sql);
@@ -2427,23 +2477,23 @@
 #endif
 
 	if (gda_connection_statement_execute_non_select (imodel->priv->cnc, stmt,
-							 imodel->priv->modif_internals->modif_set, NULL, error) == -1)
+							 imodel->priv->sh->modif_internals->modif_set, NULL, error) == -1)
 		return FALSE;
 	
 	/* mark that this row has been modified */
 	DelayedSelectStmt *dstmt;
 	dstmt = g_new0 (DelayedSelectStmt, 1);
-	if (! imodel->priv->modif_internals->one_row_select_stmt)
-		imodel->priv->modif_internals->one_row_select_stmt = compute_single_select_stmt (imodel, error);
-	if (imodel->priv->modif_internals->one_row_select_stmt) {
-		dstmt->select = g_object_ref (imodel->priv->modif_internals->one_row_select_stmt);
+	if (! imodel->priv->sh->modif_internals->one_row_select_stmt)
+		imodel->priv->sh->modif_internals->one_row_select_stmt = compute_single_select_stmt (imodel, error);
+	if (imodel->priv->sh->modif_internals->one_row_select_stmt) {
+		dstmt->select = g_object_ref (imodel->priv->sh->modif_internals->one_row_select_stmt);
 		gda_statement_get_parameters (dstmt->select, &(dstmt->params), NULL);
 		if (dstmt->params) {
 			GSList *list;
 			gboolean allok = TRUE;
 
 			/* overwrite old values with new values if some have been provided */
-			for (list = imodel->priv->modif_internals->modif_set->holders; list; list = list->next) {
+			for (list = imodel->priv->sh->modif_internals->modif_set->holders; list; list = list->next) {
 				GdaHolder *h = (GdaHolder*) list->data;
 				gint res;
 				gboolean old;
@@ -2451,7 +2501,7 @@
 				    param_name_to_int (gda_holder_get_id (h), &res, &old) && 
 				    !old) {
 					str = g_strdup_printf ("-%d", res);
-					holder = gda_set_get_holder (imodel->priv->modif_internals->modif_set, str);
+					holder = gda_set_get_holder (imodel->priv->sh->modif_internals->modif_set, str);
 					g_free (str);
 					if (holder &&
 					    ! gda_holder_set_value (holder, gda_holder_get_value (h), error)) {
@@ -2464,7 +2514,7 @@
 			for (list = dstmt->params->holders; list && allok; list = list->next) {
 				GdaHolder *holder = GDA_HOLDER (list->data);
 				GdaHolder *eholder;
-				eholder = gda_set_get_holder (imodel->priv->modif_internals->modif_set, 
+				eholder = gda_set_get_holder (imodel->priv->sh->modif_internals->modif_set, 
 							      gda_holder_get_id (holder));
 				if (!eholder || 
 				    ! gda_holder_set_value (holder, gda_holder_get_value (eholder), NULL)) {
@@ -2480,13 +2530,13 @@
 		}
 	}
 	dstmt->row = NULL;
-	if (! imodel->priv->upd_rows)
-		imodel->priv->upd_rows = g_hash_table_new_full (g_int_hash, g_int_equal,
+	if (! imodel->priv->sh->upd_rows)
+		imodel->priv->sh->upd_rows = g_hash_table_new_full (g_int_hash, g_int_equal,
 								g_free, 
 								(GDestroyNotify) delayed_select_stmt_free);
 	gint *tmp = g_new (gint, 1);
 	*tmp = int_row;
-	g_hash_table_insert (imodel->priv->upd_rows, tmp, dstmt);
+	g_hash_table_insert (imodel->priv->sh->upd_rows, tmp, dstmt);
 #ifdef GDA_DEBUG_NO
 	dump_d (imodel);
 #endif
@@ -2508,17 +2558,17 @@
 	imodel = (GdaDataSelect *) model;
 	g_return_val_if_fail (imodel->priv, FALSE);
 
-	if (imodel->priv->modif_internals->safely_locked) {
+	if (imodel->priv->sh->modif_internals->safely_locked) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_SAFETY_LOCKED_ERROR,
 			     "%s", _("Modifications are not allowed anymore"));
 		return FALSE;
 	}
-	if (! (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
+	if (! (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
 		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 			     "%s", _("Data model does only support random access"));
 		return FALSE;
 	}
-	if (! imodel->priv->modif_internals->modif_stmts [UPD_QUERY]) {
+	if (! imodel->priv->sh->modif_internals->modif_stmts [UPD_QUERY]) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
 			     "%s", _("No UPDATE statement provided"));
 		return FALSE;
@@ -2532,9 +2582,9 @@
 		return FALSE;
 	}
 	
-	/* invalidate all the imodel->priv->modif_internals->modif_set's value holders */
+	/* invalidate all the imodel->priv->sh->modif_internals->modif_set's value holders */
 	GSList *list;
-	for (list = imodel->priv->modif_internals->modif_set->holders; list; list = list->next) {
+	for (list = imodel->priv->sh->modif_internals->modif_set->holders; list; list = list->next) {
 		GdaHolder *h = (GdaHolder*) list->data;
 		if (param_name_to_int (gda_holder_get_id (h), NULL, NULL))
 			gda_holder_force_invalid ((GdaHolder*) list->data);
@@ -2542,7 +2592,7 @@
 
 	/* give values to params for new value */
 	str = g_strdup_printf ("+%d", col);
-	holder = gda_set_get_holder (imodel->priv->modif_internals->modif_set, str);
+	holder = gda_set_get_holder (imodel->priv->sh->modif_internals->modif_set, str);
 	g_free (str);
 	if (! holder) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
@@ -2574,12 +2624,12 @@
 	imodel = (GdaDataSelect *) model;
 	g_return_val_if_fail (imodel->priv, FALSE);
 
-	if (imodel->priv->modif_internals->safely_locked) {
+	if (imodel->priv->sh->modif_internals->safely_locked) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_SAFETY_LOCKED_ERROR,
 			     "%s", _("Modifications are not allowed anymore"));
 		return FALSE;
 	}
-	if (! imodel->priv->modif_internals->modif_stmts [UPD_QUERY]) {
+	if (! imodel->priv->sh->modif_internals->modif_stmts [UPD_QUERY]) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
 			     "%s", _("No UPDATE statement provided"));
 		return FALSE;
@@ -2593,9 +2643,9 @@
 		return FALSE;
 	}
 	
-	/* invalidate all the imodel->priv->modif_internals->modif_set's value holders */
+	/* invalidate all the imodel->priv->sh->modif_internals->modif_set's value holders */
 	GSList *list;
-	for (list = imodel->priv->modif_internals->modif_set->holders; list; list = list->next) {
+	for (list = imodel->priv->sh->modif_internals->modif_set->holders; list; list = list->next) {
 		GdaHolder *h = (GdaHolder*) list->data;
 		if (param_name_to_int (gda_holder_get_id (h), NULL, NULL))
 			gda_holder_force_invalid ((GdaHolder*) list->data);
@@ -2603,7 +2653,7 @@
 
 	/* give values to params for new value */
 	str = g_strdup_printf ("+%d", col);
-	holder = gda_set_get_holder (imodel->priv->modif_internals->modif_set, str);
+	holder = gda_set_get_holder (imodel->priv->sh->modif_internals->modif_set, str);
 	g_free (str);
 	if (! holder) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
@@ -2649,17 +2699,17 @@
 	/* arguments check */
 	g_return_val_if_fail (imodel->priv, FALSE);
 
-	if (imodel->priv->modif_internals->safely_locked) {
+	if (imodel->priv->sh->modif_internals->safely_locked) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_SAFETY_LOCKED_ERROR,
 			     "%s", _("Modifications are not allowed anymore"));
 		return FALSE;
 	}
-	if (! (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
+	if (! (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
 		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 			     "%s", _("Data model does only support random access"));
 		return FALSE;
 	}
-	if (! imodel->priv->modif_internals->modif_stmts [UPD_QUERY]) {
+	if (! imodel->priv->sh->modif_internals->modif_stmts [UPD_QUERY]) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
 			     "%s", _("No UPDATE statement provided"));
 		return FALSE;
@@ -2692,9 +2742,9 @@
 		return TRUE;
 	}
 
-	/* invalidate all the imodel->priv->modif_internals->modif_set's value holders */
+	/* invalidate all the imodel->priv->sh->modif_internals->modif_set's value holders */
 	GSList *slist;
-	for (slist = imodel->priv->modif_internals->modif_set->holders; slist; slist = slist->next) {
+	for (slist = imodel->priv->sh->modif_internals->modif_set->holders; slist; slist = slist->next) {
 		GdaHolder *h = (GdaHolder*) slist->data;
 		if (param_name_to_int (gda_holder_get_id (h), NULL, NULL))
 			gda_holder_force_invalid ((GdaHolder*) slist->data);
@@ -2706,7 +2756,7 @@
 			continue;
 
 		str = g_strdup_printf ("+%d", i);
-		holder = gda_set_get_holder (imodel->priv->modif_internals->modif_set, str);
+		holder = gda_set_get_holder (imodel->priv->sh->modif_internals->modif_set, str);
 		g_free (str);
 		if (! holder) {
 			g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
@@ -2736,17 +2786,17 @@
 
 	g_return_val_if_fail (imodel->priv, -1);
 
-	if (imodel->priv->modif_internals->safely_locked) {
+	if (imodel->priv->sh->modif_internals->safely_locked) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_SAFETY_LOCKED_ERROR,
 			     "%s", _("Modifications are not allowed anymore"));
 		return -1;
 	}
-	if (! (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
+	if (! (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
 		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 			     "%s", _("Data model does only support random access"));
 		return -1;
 	}
-	if (! imodel->priv->modif_internals->modif_stmts [INS_QUERY]) {
+	if (! imodel->priv->sh->modif_internals->modif_stmts [INS_QUERY]) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
 			     "%s", _("No INSERT statement provided"));
 		return -1;
@@ -2787,17 +2837,17 @@
 
 	/* compute INSERT statement */
 	GdaStatement *stmt;
-	if (! imodel->priv->modif_internals->ins_stmts)
-		imodel->priv->modif_internals->ins_stmts = g_hash_table_new_full ((GHashFunc) bvector_hash, 
+	if (! imodel->priv->sh->modif_internals->ins_stmts)
+		imodel->priv->sh->modif_internals->ins_stmts = g_hash_table_new_full ((GHashFunc) bvector_hash, 
 										  (GEqualFunc) bvector_equal, 
 										  (GDestroyNotify) bvector_free, 
 										  g_object_unref);
-	stmt = g_hash_table_lookup (imodel->priv->modif_internals->ins_stmts, bv);
+	stmt = g_hash_table_lookup (imodel->priv->sh->modif_internals->ins_stmts, bv);
 	if (! stmt) {
 		stmt = compute_single_insert_stmt (imodel, bv, error);
 		if (stmt) {
 			free_bv = FALSE;
-			g_hash_table_insert (imodel->priv->modif_internals->ins_stmts, bv, stmt);
+			g_hash_table_insert (imodel->priv->sh->modif_internals->ins_stmts, bv, stmt);
 		}
 		else {
 			bvector_free (bv);
@@ -2811,7 +2861,7 @@
 			continue;
 
 		str = g_strdup_printf ("+%d", i);
-		holder = gda_set_get_holder (imodel->priv->modif_internals->modif_set, str);
+		holder = gda_set_get_holder (imodel->priv->sh->modif_internals->modif_set, str);
 		g_free (str);
 		if (! holder) {
 			g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
@@ -2836,7 +2886,7 @@
 	GError *lerror = NULL;
 	sql = gda_statement_to_sql_extended (stmt,
 					     imodel->priv->cnc,
-					     imodel->priv->modif_internals->modif_set, 
+					     imodel->priv->sh->modif_internals->modif_set, 
 					     GDA_STATEMENT_SQL_PRETTY, NULL,
 					     &lerror);
 	g_print ("%s(): SQL=> %s\n", __FUNCTION__, sql);
@@ -2850,30 +2900,30 @@
 	g_free (sql);
 #endif
 
-	if (! imodel->priv->modif_internals->one_row_select_stmt) {
-		imodel->priv->modif_internals->one_row_select_stmt = compute_single_select_stmt (imodel, error);
-		if (!imodel->priv->modif_internals->one_row_select_stmt)
+	if (! imodel->priv->sh->modif_internals->one_row_select_stmt) {
+		imodel->priv->sh->modif_internals->one_row_select_stmt = compute_single_select_stmt (imodel, error);
+		if (!imodel->priv->sh->modif_internals->one_row_select_stmt)
 			return -1;
 	}
 
 	GdaSet *last_insert;
 	if (gda_connection_statement_execute_non_select (imodel->priv->cnc, stmt,
-							 imodel->priv->modif_internals->modif_set, &last_insert, error) == -1)
+							 imodel->priv->sh->modif_internals->modif_set, &last_insert, error) == -1)
 		return -1;
 
 	/* mark that this row has been modified */
 	DelayedSelectStmt *dstmt;
 	dstmt = g_new0 (DelayedSelectStmt, 1);
-	if (last_insert && imodel->priv->modif_internals->one_row_select_stmt) {
-		dstmt->select = g_object_ref (imodel->priv->modif_internals->one_row_select_stmt);
+	if (last_insert && imodel->priv->sh->modif_internals->one_row_select_stmt) {
+		dstmt->select = g_object_ref (imodel->priv->sh->modif_internals->one_row_select_stmt);
 		gda_statement_get_parameters (dstmt->select, &(dstmt->params), NULL);
 		if (dstmt->params) {
 			GSList *list;
-			if (! imodel->priv->modif_internals->insert_to_select_mapping)
-				imodel->priv->modif_internals->insert_to_select_mapping = 
+			if (! imodel->priv->sh->modif_internals->insert_to_select_mapping)
+				imodel->priv->sh->modif_internals->insert_to_select_mapping = 
 					compute_insert_select_params_mapping (dstmt->params, last_insert,
-									      imodel->priv->modif_internals->unique_row_condition);
-			if (imodel->priv->modif_internals->insert_to_select_mapping) {
+									      imodel->priv->sh->modif_internals->unique_row_condition);
+			if (imodel->priv->sh->modif_internals->insert_to_select_mapping) {
 				for (list = dstmt->params->holders; list; list = list->next) {
 					GdaHolder *holder = GDA_HOLDER (list->data);
 					GdaHolder *eholder;
@@ -2882,7 +2932,7 @@
 					g_assert (param_name_to_int (gda_holder_get_id (holder), &pos, NULL));
 					
 					eholder = g_slist_nth_data (last_insert->holders, 
-								    imodel->priv->modif_internals->insert_to_select_mapping[pos]);
+								    imodel->priv->sh->modif_internals->insert_to_select_mapping[pos]);
 					if (!eholder || 
 					    ! gda_holder_set_value (holder, gda_holder_get_value (eholder), error)) {
 						g_object_unref (dstmt->params);
@@ -2897,13 +2947,13 @@
 		g_object_unref (last_insert);
 
 	dstmt->row = NULL;
-	if (! imodel->priv->upd_rows)
-		imodel->priv->upd_rows = g_hash_table_new_full (g_int_hash, g_int_equal,
+	if (! imodel->priv->sh->upd_rows)
+		imodel->priv->sh->upd_rows = g_hash_table_new_full (g_int_hash, g_int_equal,
 								g_free, 
 								(GDestroyNotify) delayed_select_stmt_free);
 	gint *tmp = g_new (gint, 1);
 	*tmp = int_row;
-	g_hash_table_insert (imodel->priv->upd_rows, tmp, dstmt);
+	g_hash_table_insert (imodel->priv->sh->upd_rows, tmp, dstmt);
 #ifdef GDA_DEBUG_NO
 	dump_d (imodel);
 #endif
@@ -2927,17 +2977,17 @@
 
 	g_return_val_if_fail (imodel->priv, FALSE);
 
-	if (imodel->priv->modif_internals->safely_locked) {
+	if (imodel->priv->sh->modif_internals->safely_locked) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_SAFETY_LOCKED_ERROR,
 			     "%s", _("Modifications are not allowed anymore"));
 		return FALSE;
 	}
-	if (! (imodel->priv->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
+	if (! (imodel->priv->sh->usage_flags & GDA_DATA_MODEL_ACCESS_RANDOM)) {
 		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
 			     "%s", _("Data model does only support random access"));
 		return FALSE;
 	}
-	if (! imodel->priv->modif_internals->modif_stmts [DEL_QUERY]) {
+	if (! imodel->priv->sh->modif_internals->modif_stmts [DEL_QUERY]) {
 		g_set_error (error, GDA_DATA_SELECT_ERROR, GDA_DATA_SELECT_MISSING_MODIFICATION_STATEMENT_ERROR,
 			     "%s", _("No DELETE statement provided"));
 		return FALSE;
@@ -2950,7 +3000,7 @@
 	ncols = gda_data_select_get_n_columns (model);
 	for (i = 0; i < ncols; i++) {
 		str = g_strdup_printf ("-%d", i);
-		holder = gda_set_get_holder (imodel->priv->modif_internals->modif_set, str);
+		holder = gda_set_get_holder (imodel->priv->sh->modif_internals->modif_set, str);
 		g_free (str);
 		if (holder) {
 			const GValue *cvalue;
@@ -2965,9 +3015,9 @@
 #ifdef GDA_DEBUG_NO
 	gchar *sql;
 	GError *lerror = NULL;
-	sql = gda_statement_to_sql_extended (imodel->priv->modif_internals->modif_stmts [DEL_QUERY],
+	sql = gda_statement_to_sql_extended (imodel->priv->sh->modif_internals->modif_stmts [DEL_QUERY],
 					     imodel->priv->cnc,
-					     imodel->priv->modif_internals->modif_set, 
+					     imodel->priv->sh->modif_internals->modif_set, 
 					     GDA_STATEMENT_SQL_PRETTY, NULL,
 					     &lerror);
 	g_print ("%s(): SQL=> %s\n", __FUNCTION__, sql);
@@ -2976,19 +3026,19 @@
 	g_free (sql);
 #endif
 	if (gda_connection_statement_execute_non_select (imodel->priv->cnc, 
-							 imodel->priv->modif_internals->modif_stmts [DEL_QUERY],
-							 imodel->priv->modif_internals->modif_set, NULL, error) == -1)
+							 imodel->priv->sh->modif_internals->modif_stmts [DEL_QUERY],
+							 imodel->priv->sh->modif_internals->modif_set, NULL, error) == -1)
 		return FALSE;
 
 	/* mark that this row has been removed */
-	if (!imodel->priv->del_rows)
-		imodel->priv->del_rows = g_array_new (FALSE, FALSE, sizeof (gint));
-	for (index = 0, i = 0; i < imodel->priv->del_rows->len; i++, index++) {
-		if (g_array_index (imodel->priv->del_rows, gint, i) >= int_row)
+	if (!imodel->priv->sh->del_rows)
+		imodel->priv->sh->del_rows = g_array_new (FALSE, FALSE, sizeof (gint));
+	for (index = 0, i = 0; i < imodel->priv->sh->del_rows->len; i++, index++) {
+		if (g_array_index (imodel->priv->sh->del_rows, gint, i) >= int_row)
 			break;
 	}
 
-	g_array_insert_val (imodel->priv->del_rows, index, int_row);
+	g_array_insert_val (imodel->priv->sh->del_rows, index, int_row);
 	gda_data_model_row_removed (model, row);
 
 	return TRUE;
@@ -3001,7 +3051,7 @@
  *
  * The way of preceeding is: 
  *   - for each parameter required by model->one_row_select_stmt statement (the @sel_params argument), 
- *     use the model->priv->modif_internals->unique_row_condition to get the name of the corresponding column (the GdaHolder's ID
+ *     use the model->priv->sh->modif_internals->unique_row_condition to get the name of the corresponding column (the GdaHolder's ID
  *     is "-<num1>" )
  *   - from the column name get the GdaHolder in the GdaSet retruned after the INSERT statement (the
  *     @ins_values argument) using the "name" property of each GdaHolder in the GdaSet (the GdaHolder's ID
@@ -3151,7 +3201,7 @@
 
 	/* FIXME: also set some column attributes using gda_column_set_attribute() */
 
-	for (fields = select->expr_list, columns = model->priv->columns; 
+	for (fields = select->expr_list, columns = model->priv->sh->columns; 
 	     fields && columns; 
 	     fields = fields->next) {
 		GdaSqlSelectField *selfield = (GdaSqlSelectField*) fields->data;
@@ -3195,7 +3245,7 @@
 	}
 	if (fields || columns)
 		g_warning ("Internal error: GdaDataSelect has %d GdaColumns, and SELECT statement has %d expressions",
-			   g_slist_length (model->priv->columns), g_slist_length (select->expr_list));
+			   g_slist_length (model->priv->sh->columns), g_slist_length (select->expr_list));
 
  out:
 	gda_sql_statement_free (sqlst);

Modified: trunk/libgda/gda-meta-store.c
==============================================================================
--- trunk/libgda/gda-meta-store.c	(original)
+++ trunk/libgda/gda-meta-store.c	Tue Apr  7 19:10:50 2009
@@ -1,6 +1,6 @@
 /* gda-meta-store.c
  *
- * Copyright (C) 2008 Vivien Malerba
+ * Copyright (C) 2008 - 2009 Vivien Malerba
  *
  * This Library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public License as
@@ -39,7 +39,9 @@
 #include <libgda/gda-util.h>
 #include <libgda/gda-meta-struct.h>
 #include <libgda/gda-connection.h>
-#include <libgda/gda-connection-sqlite.h>
+#include <libgda/gda-connection-internal.h>
+#include <libgda/gda-lockable.h>
+#include <libgda/gda-mutex.h>
 #include "gda-types.h"
 
 /*
@@ -215,6 +217,8 @@
 	gint           max_extract_stmt; /* 0 => don't keep GdaStatement */
 	gint           current_extract_stmt;
 	GHashTable    *extract_stmt_hash; /* key = a SQL string, value = a #GdaStatement */
+
+	GdaMutex      *mutex;
 };
 
 static void db_object_free    (DbObject *dbobj);
@@ -497,6 +501,8 @@
 	store->priv->max_extract_stmt = 10;
 	store->priv->current_extract_stmt = 0;
 	store->priv->extract_stmt_hash = NULL;
+	
+	store->priv->mutex = gda_mutex_new ();
 }
 
 static GObject *
@@ -619,6 +625,15 @@
 		g_object_unref (store);
 		store = NULL;
 	}
+	else {
+		if (gda_lockable_trylock (GDA_LOCKABLE (store->priv->cnc)))
+			gda_lockable_unlock (GDA_LOCKABLE (store->priv->cnc));
+		else {
+			g_warning (_("Can't obtain connection lock"));
+			g_object_unref (store);
+			store = NULL;
+		}
+	}
 	
 	return store;
 }
@@ -658,6 +673,8 @@
 			g_object_unref (G_OBJECT (store->priv->cnc));
 			store->priv->cnc = NULL;
 		}
+
+		gda_mutex_free (store->priv->mutex);
 	}
 	
 	/* parent class */
@@ -699,7 +716,9 @@
 				cnc_string = g_value_get_string (value);
 				if (cnc_string) {
 					GdaConnection *cnc;
-					cnc = gda_connection_open_from_string (NULL, cnc_string, NULL, 0, NULL);
+					cnc = gda_connection_open_from_string (NULL, cnc_string, NULL, 
+									       GDA_CONNECTION_OPTIONS_NONE,
+									       NULL);
 					if (!cnc) {
 						if (g_ascii_strcasecmp (cnc_string, "sqlite")) {
 							/* use _gda_config_sqlite_provider */
@@ -713,7 +732,7 @@
 			break;
 		case PROP_CNC_OBJECT:
 			if (!store->priv->cnc)
-				store->priv->cnc = g_value_get_object (value);
+				store->priv->cnc = g_value_dup_object (value);
 			break;
 		case PROP_CATALOG:
 			g_free (store->priv->catalog);
@@ -2010,6 +2029,8 @@
 	g_return_val_if_fail (GDA_IS_META_STORE (store), NULL);
 	g_return_val_if_fail (store->priv, NULL);
 
+	gda_mutex_lock (store->priv->mutex);
+
 	if ((store->priv->max_extract_stmt > 0) && !store->priv->extract_stmt_hash)
 		store->priv->extract_stmt_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
 
@@ -2024,12 +2045,15 @@
 
 		klass = (GdaMetaStoreClass *) G_OBJECT_GET_CLASS (store);
 		stmt = gda_sql_parser_parse_string (klass->cpriv->parser, select_sql, &remain, error);
-		if (!stmt)
+		if (!stmt) {
+			gda_mutex_unlock (store->priv->mutex);
 			return NULL;
+		}
 		if (remain) {
 			g_set_error (error, GDA_META_STORE_ERROR, GDA_META_STORE_EXTRACT_SQL_ERROR,
 				     "%s", _("More than one SQL statement"));
 			g_object_unref (stmt);
+			gda_mutex_unlock (store->priv->mutex);
 			return NULL;
 		}
 
@@ -2042,6 +2066,7 @@
 	/* parameters */
 	if (!gda_statement_get_parameters (stmt, &params, error)) {
 		g_object_unref (stmt);
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
 	}
 	if (params) {
@@ -2062,6 +2087,7 @@
 					g_object_unref (params);
 					va_end (ap);
 					g_slist_free (params_set);
+					gda_mutex_unlock (store->priv->mutex);
 					return NULL;
 				}
 				params_set = g_slist_prepend (params_set, h);
@@ -2082,6 +2108,8 @@
 	g_object_unref (stmt);
 	if (params)
 		g_object_unref (params);
+
+	gda_mutex_unlock (store->priv->mutex);
 	return model;
 }
 
@@ -2215,11 +2243,15 @@
 	g_return_val_if_fail (gda_connection_is_opened (store->priv->cnc), FALSE);
 	g_return_val_if_fail (!new_data || GDA_IS_DATA_MODEL (new_data), FALSE);
 	
+	gda_mutex_lock (store->priv->mutex);
+
 	/* get the correct TableInfo */
 	prep = prepare_tables_infos (store, &schema_set, &custom_set, &with_cond, table_name, 
 				     condition, error, nvalues, value_names, values);
-	if (!prep)
+	if (!prep) {
+		gda_mutex_unlock (store->priv->mutex);
 		return FALSE;
+	}
 	
 	GdaDataModel *current = NULL;
 	gboolean *rows_to_del = NULL;
@@ -2234,8 +2266,10 @@
 									with_cond ? custom_set->params : NULL, 
 									GDA_STATEMENT_MODEL_RANDOM_ACCESS,	
 									schema_set->type_cols_array, error);
-		if (!current)
+		if (!current) {
+			gda_mutex_unlock (store->priv->mutex);
 			return FALSE;
+		}
 		current_n_rows = gda_data_model_get_n_rows (current);
 		current_n_cols = gda_data_model_get_n_columns (current);
 		rows_to_del = g_new (gboolean, current_n_rows);
@@ -2259,8 +2293,10 @@
 		/* remove everything from table */
 		if (gda_connection_statement_execute_non_select (store->priv->cnc,
 								 schema_set->delete_all, NULL, 
-								 NULL, error) == -1) 
+								 NULL, error) == -1) {
+			gda_mutex_unlock (store->priv->mutex);
 			return FALSE;
+		}
 	}
 
 	/* treat rows to insert / update */
@@ -2543,6 +2579,8 @@
 		g_print ("------- ROLLBACK\n");
 #endif
 	}
+
+	gda_mutex_unlock (store->priv->mutex);
 	return retval;
 }
 
@@ -2727,18 +2765,23 @@
 	if (store->priv->override_mode)
 		return TRUE;
 
+	gda_mutex_lock (store->priv->mutex);
 	if (! gda_connection_get_transaction_status (store->priv->cnc)) {
 		if (!gda_connection_begin_transaction (store->priv->cnc, NULL,
-						       GDA_TRANSACTION_ISOLATION_UNKNOWN, error))
+						       GDA_TRANSACTION_ISOLATION_UNKNOWN, error)) {
+			gda_mutex_unlock (store->priv->mutex);
 			return FALSE;
+		}
 	}
 	else {
 		g_set_error (error, GDA_META_STORE_ERROR, GDA_META_STORE_TRANSACTION_ALREADY_STARTED_ERROR,
 			     "%s", _("A transaction has already been started"));
+		gda_mutex_unlock (store->priv->mutex);
 		return FALSE;
 
 	}
 	store->priv->override_mode = TRUE;
+	gda_mutex_unlock (store->priv->mutex);
 	return TRUE;
 }
 
@@ -2754,13 +2797,19 @@
 gboolean
 _gda_meta_store_cancel_data_reset (GdaMetaStore *store, GError **error)
 {
+	gboolean retval;
 	g_return_val_if_fail (GDA_IS_META_STORE (store), FALSE);
 
-	if (!store->priv->override_mode)
+	gda_mutex_lock (store->priv->mutex);
+	if (!store->priv->override_mode) {
+		gda_mutex_unlock (store->priv->mutex);
 		return TRUE;
+	}
 	
 	store->priv->override_mode = FALSE;
-	return gda_connection_rollback_transaction (store->priv->cnc, NULL, error);
+	retval = gda_connection_rollback_transaction (store->priv->cnc, NULL, error);
+	gda_mutex_unlock (store->priv->mutex);
+	return retval;
 }
 
 /**
@@ -2777,14 +2826,20 @@
 {
 	g_return_val_if_fail (GDA_IS_META_STORE (store), FALSE);
 
-	if (!store->priv->override_mode)
+	gda_mutex_lock (store->priv->mutex);
+	if (!store->priv->override_mode) {
+		gda_mutex_unlock (store->priv->mutex);
 		return TRUE;
+	}
 
 	store->priv->override_mode = FALSE;	
-	if (!gda_connection_commit_transaction (store->priv->cnc, NULL, error))
+	if (!gda_connection_commit_transaction (store->priv->cnc, NULL, error)) {
+		gda_mutex_unlock (store->priv->mutex);
 		return FALSE;
+	}
 	else {
 		g_signal_emit (store, gda_meta_store_signals[META_RESET], 0);
+		gda_mutex_unlock (store->priv->mutex);
 		return TRUE;
 	}
 }
@@ -2831,14 +2886,18 @@
 	g_return_val_if_fail (GDA_IS_META_STORE (store), FALSE);
 	g_return_val_if_fail (table_name && *table_name, FALSE);
 
+	gda_mutex_lock (store->priv->mutex);
+
 	klass = (GdaMetaStoreClass *) G_OBJECT_GET_CLASS (store);
 	dbobj = g_hash_table_lookup (store->priv->p_db_objects_hash, table_name);
 	if (!dbobj) {
 		g_warning ("Table '%s' is not known by the GdaMetaStore", table_name);
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
 	}
 	if (dbobj->obj_type != GDA_SERVER_OPERATION_CREATE_TABLE) {
 		g_warning ("Table '%s' is not a database table in the GdaMetaStore", table_name);
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
 	}
 
@@ -2852,6 +2911,8 @@
 		gda_column_set_g_type (col, tcol->gtype);
 		gda_column_set_name (col, tcol->column_name);
 	}
+
+	gda_mutex_unlock (store->priv->mutex);
 	return model;
 }
 
@@ -2874,6 +2935,8 @@
 
 	g_return_val_if_fail (GDA_IS_META_STORE (store), NULL);
 
+	gda_mutex_lock (store->priv->mutex);
+
 	klass = (GdaMetaStoreClass *) G_OBJECT_GET_CLASS (store);
 	for (ret = NULL, list = klass->cpriv->db_objects; list; list = list->next) {
 		DbObject *dbobj = DB_OBJECT (list->data);
@@ -2886,6 +2949,8 @@
 			ret = g_slist_prepend (ret, dbobj->obj_name);
 	}
 
+	gda_mutex_unlock (store->priv->mutex);
+
 	return g_slist_reverse (ret);
 }
 
@@ -2914,9 +2979,13 @@
 	g_return_val_if_fail (table_name && *table_name, NULL);
 
 	klass = (GdaMetaStoreClass *) G_OBJECT_GET_CLASS (store);
+
+	gda_mutex_lock (store->priv->mutex);
 	dbo = g_hash_table_lookup (store->priv->p_db_objects_hash, table_name);
-	if (!dbo) 
+	if (!dbo) {
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
+	}
 
 	for (ret = NULL, list = dbo->depend_list; list; list = list->next) {
 		DbObject *dbobj = DB_OBJECT (list->data);
@@ -2924,6 +2993,8 @@
 			ret = g_slist_prepend (ret, dbobj->obj_name);
 	}
 
+	gda_mutex_unlock (store->priv->mutex);
+
 	return g_slist_reverse (ret);
 }
 
@@ -2933,7 +3004,7 @@
  * @store: a #GdaMetaStore object
  * @error: a place to store errors, or %NULL
  *
- * Creates a new #GdaMetaStruct object representing @store's interal database structure.
+ * Creates a new #GdaMetaStruct object representing @store's internal database structure.
  *
  * Returns: a new #GdaMetaStruct object, or %NULL if an error occurred
  */
@@ -2943,33 +3014,52 @@
 	GdaMetaStruct *mstruct;
 	GdaDataModel *model;
 	gint i, nrows;
+	GdaMetaStore *real_store;
 
 	g_return_val_if_fail (GDA_IS_META_STORE (store), NULL);
 
+	gda_mutex_lock (store->priv->mutex);
+
 	/* make sure the private connection's meta store is up to date */
-	if (! gda_connection_update_meta_store (store->priv->cnc, NULL, error))
+	if (! gda_connection_update_meta_store (store->priv->cnc, NULL, error)) {
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
+	}
 
 	/* create a GdaMetaStruct */
-	model = gda_meta_store_extract (store, "SELECT table_catalog, table_schema, table_name FROM _tables", 
+	real_store = gda_connection_get_meta_store (store->priv->cnc);
+	model = gda_meta_store_extract (real_store, 
+					"SELECT table_catalog, table_schema, table_name FROM _tables", 
 					error, NULL);
-	if (!model)
+	if (!model) {
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
+	}
 
-	mstruct = gda_meta_struct_new (store, GDA_META_STRUCT_FEATURE_ALL);
+	mstruct = gda_meta_struct_new (real_store, GDA_META_STRUCT_FEATURE_ALL);
 	nrows = gda_data_model_get_n_rows (model);
 	for (i = 0; i < nrows; i++) {
 		/* FIXME: only take into account the database objects which have a corresponding DbObject */
 		const GValue *cv0, *cv1, *cv2;
 		cv0 = gda_data_model_get_value_at (model, 0, i, error);
-		if (!cv0) return NULL;
+		if (!cv0) {
+			gda_mutex_unlock (store->priv->mutex);
+			return NULL;
+		}
 		cv1 = gda_data_model_get_value_at (model, 1, i, error);
-		if (!cv1) return NULL;
+		if (!cv1) {
+			gda_mutex_unlock (store->priv->mutex);
+			return NULL;
+		}
 		cv2 = gda_data_model_get_value_at (model, 2, i, error);
-		if (!cv2) return NULL;
+		if (!cv2) {
+			gda_mutex_unlock (store->priv->mutex);
+			return NULL;
+		}
 		if (!gda_meta_struct_complement (mstruct, GDA_META_DB_UNKNOWN, cv0, cv1, cv2, error)) {
 			g_object_unref (mstruct);
 			g_object_unref (model);
+			gda_mutex_unlock (store->priv->mutex);
 			return NULL;
 		}
 	}
@@ -2981,8 +3071,9 @@
 
 	klass = (GdaMetaStoreClass *) G_OBJECT_GET_CLASS (store);
 	all_db_obj_list = g_slist_copy (klass->cpriv->db_objects);
-	if (store->priv->p_db_objects)
-		all_db_obj_list = g_slist_concat (all_db_obj_list, g_slist_copy (store->priv->p_db_objects));
+	if (real_store->priv->p_db_objects)
+		all_db_obj_list = g_slist_concat (all_db_obj_list, 
+						  g_slist_copy (real_store->priv->p_db_objects));
 
 	for (list = all_db_obj_list; list; list = list->next) {
 		DbObject *dbobj = DB_OBJECT (list->data);
@@ -3010,6 +3101,8 @@
 		}
 	}
 	g_slist_free (all_db_obj_list);
+
+	gda_mutex_unlock (store->priv->mutex);
 	
 	return mstruct;
 }
@@ -3043,13 +3136,17 @@
 	g_return_val_if_fail (att_name && *att_name, FALSE);
 	g_return_val_if_fail (att_value, FALSE);
 
+	gda_mutex_lock (store->priv->mutex);
+
 	*att_value = NULL;
 	g_value_set_string ((value = gda_value_new (G_TYPE_STRING)), att_name);
 	model = gda_meta_store_extract (store, "SELECT att_value FROM _attributes WHERE att_name = ##n::string", error,
 					"n", value, NULL);
 	gda_value_free (value);
-	if (!model)
+	if (!model) {
+		gda_mutex_unlock (store->priv->mutex);
 		return FALSE;
+	}
 	nrows = gda_data_model_get_n_rows (model);
 	if (nrows < 1) 
 		g_set_error (error, GDA_META_STORE_ERROR, GDA_META_STORE_ATTRIBUTE_NOT_FOUND_ERROR,
@@ -3060,16 +3157,20 @@
 			     att_name, nrows);
 	else {
 		value = (GValue*) gda_data_model_get_value_at (model, 0, 0, error);
-		if (!value)
+		if (!value) {
+			gda_mutex_unlock (store->priv->mutex);
 			return FALSE;
+		}
 		if (G_VALUE_TYPE (value) == G_TYPE_STRING) {
 			const gchar *val;
 			val = g_value_get_string (value);
 			if (val) 
 				*att_value = g_strdup (val);
 		}
+		gda_mutex_unlock (store->priv->mutex);
 		return TRUE;
 	}
+	gda_mutex_unlock (store->priv->mutex);
 	return FALSE;
 }
 
@@ -3103,18 +3204,23 @@
 		return FALSE;
 	}
 
+	gda_mutex_lock (store->priv->mutex);
+
 	klass = (GdaMetaStoreClass *) G_OBJECT_GET_CLASS (store);
 	g_static_mutex_lock (&set_mutex);
 	if (!set) {
 		if (!gda_statement_get_parameters (klass->cpriv->prep_stmts [STMT_SET_ATT_VALUE], &set, error)) {
 			g_static_mutex_unlock (&set_mutex);
+			gda_mutex_unlock (store->priv->mutex);
 			return FALSE;
 		}
 	}
 	g_static_mutex_unlock (&set_mutex);
 
-	if (!gda_set_set_holder_value (set, error, "name", att_name)) 
+	if (!gda_set_set_holder_value (set, error, "name", att_name)) {
+		gda_mutex_unlock (store->priv->mutex);
 		return FALSE;
+	}
 	
 	/* start a transaction if possible */
 	if (! gda_connection_get_transaction_status (store->priv->cnc)) 
@@ -3143,6 +3249,7 @@
 	}
 	if (started_transaction)
 		gda_connection_commit_transaction (store->priv->cnc, NULL, NULL);
+	gda_mutex_unlock (store->priv->mutex);
 	return TRUE;
 
  onerror:
@@ -3235,6 +3342,8 @@
 	}
 	node = xmlDocGetRootElement (doc);
 
+	gda_mutex_lock (store->priv->mutex);
+
 	klass = (GdaMetaStoreClass *) G_OBJECT_GET_CLASS (store);
 
 	/* check that object name does not start with '_' */
@@ -3367,6 +3476,7 @@
 		}
 	}
 
+	gda_mutex_unlock (store->priv->mutex);
 	return TRUE;
 
  onerror:
@@ -3386,11 +3496,13 @@
 		}
 		g_slist_free (current_objects);
 	}
+	gda_mutex_unlock (store->priv->mutex);
 	g_slist_free (pre_p_db_objects);
 	if (pstore)
 		g_object_unref (pstore);
 	if (mstruct)
 		g_object_unref (mstruct);
+
 	
 	return FALSE;
 }
@@ -3411,7 +3523,10 @@
 	g_return_val_if_fail (GDA_IS_META_STORE (store), FALSE);
 	g_return_val_if_fail (obj_name && *obj_name, FALSE);
 	
+	gda_mutex_lock (store->priv->mutex);
 	TO_IMPLEMENT;
+	gda_mutex_unlock (store->priv->mutex);
+
 	return FALSE;
 }
 
@@ -3430,20 +3545,27 @@
 	GSList *list, *retlist = NULL;
 	TableInfo *tinfo;
 
+	gda_mutex_lock (store->priv->mutex);
+
 	/* find the associated DbObject */
 	dbo = g_hash_table_lookup (store->priv->p_db_objects_hash, context->table_name);
 	if (!dbo) {
 		g_set_error (error, GDA_META_STORE_ERROR, GDA_META_STORE_SCHEMA_OBJECT_NOT_FOUND_ERROR,
 			     _("Unknown database object '%s'"), context->table_name);
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
 	}
-	if (dbo->obj_type != GDA_SERVER_OPERATION_CREATE_TABLE)
+	if (dbo->obj_type != GDA_SERVER_OPERATION_CREATE_TABLE) {
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
+	}
 
 	tinfo = TABLE_INFO (dbo);
-	if (!tinfo->fk_list)
+	if (!tinfo->fk_list) {
 		/* this is not an error, just that there are no dependency */
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
+	}
 
 	/* Identify the TableFKey if @context permits it */
 	if (context->size > 0) {
@@ -3500,6 +3622,8 @@
 			retlist = g_slist_prepend (retlist, ct);
 		}
 	}
+
+	gda_mutex_unlock (store->priv->mutex);
 	return g_slist_reverse (retlist);
 }
 
@@ -3518,20 +3642,27 @@
 	GSList *list, *retlist = NULL;
 	TableInfo *tinfo;
 
+	gda_mutex_lock (store->priv->mutex);
+
 	/* find the associated DbObject */
 	dbo = g_hash_table_lookup (store->priv->p_db_objects_hash, context->table_name);
 	if (!dbo) {
 		g_set_error (error, GDA_META_STORE_ERROR, GDA_META_STORE_SCHEMA_OBJECT_NOT_FOUND_ERROR,
 			     _("Unknown database object '%s'"), context->table_name);
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
 	}
-	if (dbo->obj_type != GDA_SERVER_OPERATION_CREATE_TABLE)
+	if (dbo->obj_type != GDA_SERVER_OPERATION_CREATE_TABLE) {
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
+	}
 
 	tinfo = TABLE_INFO (dbo);
-	if (!tinfo->reverse_fk_list)
+	if (!tinfo->reverse_fk_list) {
 		/* this is not an error, just that there are no dependency */
+		gda_mutex_unlock (store->priv->mutex);
 		return NULL;
+	}
 
 	for (list = tinfo->reverse_fk_list; list; list = list->next) {
 		TableFKey *tfk = (TableFKey*) list->data;
@@ -3545,5 +3676,6 @@
 		retlist = g_slist_prepend (retlist, ct);
 	}
 
+	gda_mutex_unlock (store->priv->mutex);
 	return g_slist_reverse (retlist);
 }

Modified: trunk/libgda/gda-server-provider.c
==============================================================================
--- trunk/libgda/gda-server-provider.c	(original)
+++ trunk/libgda/gda-server-provider.c	Tue Apr  7 19:10:50 2009
@@ -112,6 +112,8 @@
 	
 	klass->is_busy = NULL;
 	klass->cancel = NULL;
+	klass->handle_async = NULL;
+
 	klass->create_connection = NULL;
 	memset (&(klass->meta_funcs), 0, sizeof (GdaServerProviderMeta));
 	klass->xa_funcs = NULL;

Modified: trunk/libgda/gda-server-provider.h
==============================================================================
--- trunk/libgda/gda-server-provider.h	(original)
+++ trunk/libgda/gda-server-provider.h	Tue Apr  7 19:10:50 2009
@@ -294,8 +294,9 @@
 	/* distributed transaction */
 	GdaServerProviderXa       *xa_funcs; /* it is a pointer! => set to %NULL if unsupported by provider */
 
+	gboolean                (*handle_async)          (GdaServerProvider *provider, GdaConnection *cnc, GError **error);
+
 	/* Padding for future expansion */
-	void                    (*_gda_reserved1)        (void);
 	void                    (*_gda_reserved2)        (void);
 	void                    (*_gda_reserved3)        (void);
 	void                    (*_gda_reserved4)        (void);

Modified: trunk/libgda/libgda.symbols
==============================================================================
--- trunk/libgda/libgda.symbols	(original)
+++ trunk/libgda/libgda.symbols	Tue Apr  7 19:10:50 2009
@@ -86,6 +86,9 @@
 	gda_connection_add_event_string
 	gda_connection_add_prepared_statement
 	gda_connection_add_savepoint
+	gda_connection_async_statement_execute
+	gda_connection_async_fetch_result
+	gda_connection_async_cancel
 	gda_connection_batch_execute
 	gda_connection_begin_transaction
 	gda_connection_clear_events_list
@@ -703,6 +706,15 @@
 	gda_string_to_binary
 	gda_string_to_blob
 	gda_text_to_alphanum
+	gda_thread_wrapper_connect_raw
+	gda_thread_wrapper_disconnect
+	gda_thread_wrapper_execute
+	gda_thread_wrapper_execute_void
+	gda_thread_wrapper_get_type
+	gda_thread_wrapper_fetch_result
+	gda_thread_wrapper_get_waiting_size
+	gda_thread_wrapper_iterate
+	gda_thread_wrapper_new
 	gda_time_copy
 	gda_time_free
 	gda_time_get_type

Modified: trunk/libgda/providers-support/gda-data-select-priv.h
==============================================================================
--- trunk/libgda/providers-support/gda-data-select-priv.h	(original)
+++ trunk/libgda/providers-support/gda-data-select-priv.h	Tue Apr  7 19:10:50 2009
@@ -39,6 +39,10 @@
 GdaRow        *gda_data_select_get_stored_row               (GdaDataSelect *model, gint rownum);
 GdaConnection *gda_data_select_get_connection               (GdaDataSelect *model);
 
+
+/* internal API */
+void           _gda_data_select_share_private_data (GdaDataSelect *master, GdaDataSelect *slave);
+
 G_END_DECLS
 
 #endif

Modified: trunk/libgda/sqlite/gda-sqlite-recordset.c
==============================================================================
--- trunk/libgda/sqlite/gda-sqlite-recordset.c	(original)
+++ trunk/libgda/sqlite/gda-sqlite-recordset.c	Tue Apr  7 19:10:50 2009
@@ -565,6 +565,11 @@
 			return FALSE;
 		}
 	}
+	if (! *prow) {
+		*prow = gda_data_select_get_stored_row (model, rownum);
+		if (!*prow)
+			return FALSE;
+	}
 
 	return TRUE;
 }

Added: trunk/libgda/thread-wrapper/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/libgda/thread-wrapper/Makefile.am	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,20 @@
+noinst_LTLIBRARIES = libgda_threadwrapper-4.0.la
+
+AM_CPPFLAGS = \
+        -I$(top_srcdir) -I$(srcdir)/.. \
+        -I$(top_builddir) \
+        $(LIBGDA_CFLAGS) 
+
+libgda_threadwrapper_headers = \
+	gda-thread-wrapper.h
+
+libgda_threadwrapperincludedir=$(includedir)/libgda-$(GDA_ABI_MAJOR_VERSION).$(GDA_ABI_MINOR_VERSION)/libgda/thread-wrapper
+libgda_threadwrapperinclude_HEADERS=$(libgda_threadwrapper_headers)
+
+libgda_threadwrapper_4_0_la_SOURCES = \
+	$(libgda_threadwrapper_headers) \
+	gda-thread-provider.h \
+	gda-thread-provider.c \
+	gda-thread-recordset.h \
+	gda-thread-recordset.c \
+	gda-thread-wrapper.c
\ No newline at end of file

Added: trunk/libgda/thread-wrapper/gda-thread-provider.c
==============================================================================
--- (empty file)
+++ trunk/libgda/thread-wrapper/gda-thread-provider.c	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,1681 @@
+/* GDA Thread provider
+ * Copyright (C) 2008 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+#include <libgda/libgda.h>
+#include <libgda/gda-data-model-private.h>
+#include <libgda/gda-server-provider-extra.h>
+#include <libgda/gda-statement-extra.h>
+#include <sql-parser/gda-sql-parser.h>
+#include <libgda/gda-connection-internal.h>
+#include "gda-thread-provider.h"
+#include "gda-thread-wrapper.h"
+#include "gda-thread-recordset.h"
+
+#define PROV_CLASS(provider) (GDA_SERVER_PROVIDER_CLASS (G_OBJECT_GET_CLASS (provider)))
+
+/*
+ * Per connection private data is defined as ThreadConnectionData
+ */
+
+/*
+ * GObject methods
+ */
+static void gda_thread_provider_class_init (GdaThreadProviderClass *klass);
+static void gda_thread_provider_init       (GdaThreadProvider *provider,
+					    GdaThreadProviderClass *klass);
+
+static GObjectClass *parent_class = NULL;
+
+/*
+ * GdaServerProvider's virtual methods
+ */
+/* connection management */
+static GdaConnection      *gda_thread_provider_create_connection (GdaServerProvider *provider);
+static gboolean            gda_thread_provider_open_connection (GdaServerProvider *provider, GdaConnection *cnc,
+							      GdaQuarkList *params, GdaQuarkList *auth,
+							      guint *task_id, GdaServerProviderAsyncCallback async_cb, gpointer cb_data);
+static gboolean            gda_thread_provider_close_connection (GdaServerProvider *provider, GdaConnection *cnc);
+static const gchar        *gda_thread_provider_get_server_version (GdaServerProvider *provider, GdaConnection *cnc);
+static const gchar        *gda_thread_provider_get_database (GdaServerProvider *provider, GdaConnection *cnc);
+
+/* DDL operations */
+static gboolean            gda_thread_provider_supports_operation (GdaServerProvider *provider, GdaConnection *cnc,
+								 GdaServerOperationType type, GdaSet *options);
+static GdaServerOperation *gda_thread_provider_create_operation (GdaServerProvider *provider, GdaConnection *cnc,
+							       GdaServerOperationType type,
+							       GdaSet *options, GError **error);
+static gchar              *gda_thread_provider_render_operation (GdaServerProvider *provider, GdaConnection *cnc,
+							       GdaServerOperation *op, GError **error);
+
+static gboolean            gda_thread_provider_perform_operation (GdaServerProvider *provider, GdaConnection *cnc,
+								GdaServerOperation *op, guint *task_id, 
+								GdaServerProviderAsyncCallback async_cb, gpointer cb_data,
+								GError **error);
+/* transactions */
+static gboolean            gda_thread_provider_begin_transaction (GdaServerProvider *provider, GdaConnection *cnc,
+								const gchar *name, GdaTransactionIsolation level, GError **error);
+static gboolean            gda_thread_provider_commit_transaction (GdaServerProvider *provider, GdaConnection *cnc,
+								 const gchar *name, GError **error);
+static gboolean            gda_thread_provider_rollback_transaction (GdaServerProvider *provider, GdaConnection * cnc,
+								   const gchar *name, GError **error);
+static gboolean            gda_thread_provider_add_savepoint (GdaServerProvider *provider, GdaConnection *cnc,
+							    const gchar *name, GError **error);
+static gboolean            gda_thread_provider_rollback_savepoint (GdaServerProvider *provider, GdaConnection *cnc,
+								 const gchar *name, GError **error);
+static gboolean            gda_thread_provider_delete_savepoint (GdaServerProvider *provider, GdaConnection *cnc,
+							       const gchar *name, GError **error);
+
+/* information retreival */
+static const gchar        *gda_thread_provider_get_version (GdaServerProvider *provider);
+static gboolean            gda_thread_provider_supports_feature (GdaServerProvider *provider, GdaConnection *cnc,
+							       GdaConnectionFeature feature);
+
+static const gchar        *gda_thread_provider_get_name (GdaServerProvider *provider);
+
+static GdaDataHandler     *gda_thread_provider_get_data_handler (GdaServerProvider *provider, GdaConnection *cnc,
+							       GType g_type, const gchar *dbms_type);
+
+static const gchar*        gda_thread_provider_get_default_dbms_type (GdaServerProvider *provider, GdaConnection *cnc,
+								    GType type);
+/* statements */
+static GdaSqlParser        *gda_thread_provider_create_parser (GdaServerProvider *provider, GdaConnection *cnc);
+static gchar               *gda_thread_provider_statement_to_sql  (GdaServerProvider *provider, GdaConnection *cnc,
+								 GdaStatement *stmt, GdaSet *params, 
+								 GdaStatementSqlFlag flags,
+								 GSList **params_used, GError **error);
+static gboolean             gda_thread_provider_statement_prepare (GdaServerProvider *provider, GdaConnection *cnc,
+								 GdaStatement *stmt, GError **error);
+static GObject             *gda_thread_provider_statement_execute (GdaServerProvider *provider, GdaConnection *cnc,
+								 GdaStatement *stmt, GdaSet *params,
+								 GdaStatementModelUsage model_usage, 
+								 GType *col_types, GdaSet **last_inserted_row, 
+								 guint *task_id, GdaServerProviderExecCallback async_cb, 
+								 gpointer cb_data, GError **error);
+
+/* distributed transactions */
+static gboolean gda_thread_provider_xa_start    (GdaServerProvider *provider, GdaConnection *cnc, 
+						   const GdaXaTransactionId *xid, GError **error);
+
+static gboolean gda_thread_provider_xa_end      (GdaServerProvider *provider, GdaConnection *cnc, 
+						   const GdaXaTransactionId *xid, GError **error);
+static gboolean gda_thread_provider_xa_prepare  (GdaServerProvider *provider, GdaConnection *cnc, 
+						   const GdaXaTransactionId *xid, GError **error);
+
+static gboolean gda_thread_provider_xa_commit   (GdaServerProvider *provider, GdaConnection *cnc, 
+						   const GdaXaTransactionId *xid, GError **error);
+static gboolean gda_thread_provider_xa_rollback (GdaServerProvider *provider, GdaConnection *cnc, 
+						   const GdaXaTransactionId *xid, GError **error);
+
+static GList   *gda_thread_provider_xa_recover  (GdaServerProvider *provider, GdaConnection *cnc, 
+						   GError **error);
+
+static void gda_thread_free_cnc_data (ThreadConnectionData *cdata);
+
+/*
+ * GdaThreadProvider class implementation
+ */
+static void
+gda_thread_provider_class_init (GdaThreadProviderClass *klass)
+{
+	GdaServerProviderClass *provider_class = GDA_SERVER_PROVIDER_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	provider_class->get_version = gda_thread_provider_get_version;
+	provider_class->get_server_version = gda_thread_provider_get_server_version;
+	provider_class->get_name = gda_thread_provider_get_name;
+	provider_class->supports_feature = gda_thread_provider_supports_feature;
+
+	provider_class->get_data_handler = gda_thread_provider_get_data_handler;
+	provider_class->get_def_dbms_type = gda_thread_provider_get_default_dbms_type;
+
+	provider_class->open_connection = gda_thread_provider_open_connection;
+	provider_class->close_connection = gda_thread_provider_close_connection;
+	provider_class->get_database = gda_thread_provider_get_database;
+
+	provider_class->supports_operation = gda_thread_provider_supports_operation;
+        provider_class->create_operation = gda_thread_provider_create_operation;
+        provider_class->render_operation = gda_thread_provider_render_operation;
+        provider_class->perform_operation = gda_thread_provider_perform_operation;
+
+	provider_class->begin_transaction = gda_thread_provider_begin_transaction;
+	provider_class->commit_transaction = gda_thread_provider_commit_transaction;
+	provider_class->rollback_transaction = gda_thread_provider_rollback_transaction;
+	provider_class->add_savepoint = gda_thread_provider_add_savepoint;
+        provider_class->rollback_savepoint = gda_thread_provider_rollback_savepoint;
+        provider_class->delete_savepoint = gda_thread_provider_delete_savepoint;
+
+	provider_class->create_parser = gda_thread_provider_create_parser;
+	provider_class->statement_to_sql = gda_thread_provider_statement_to_sql;
+	provider_class->statement_prepare = gda_thread_provider_statement_prepare;
+	provider_class->statement_execute = gda_thread_provider_statement_execute;
+
+	provider_class->is_busy = NULL;
+	provider_class->cancel = NULL;
+	provider_class->create_connection = gda_thread_provider_create_connection;
+
+	memset (&(provider_class->meta_funcs), 0, sizeof (GdaServerProviderMeta));
+
+	/* distributed transactions: if not supported, then provider_class->xa_funcs should be set to NULL */
+	provider_class->xa_funcs = g_new0 (GdaServerProviderXa, 1);
+	provider_class->xa_funcs->xa_start = gda_thread_provider_xa_start;
+	provider_class->xa_funcs->xa_end = gda_thread_provider_xa_end;
+	provider_class->xa_funcs->xa_prepare = gda_thread_provider_xa_prepare;
+	provider_class->xa_funcs->xa_commit = gda_thread_provider_xa_commit;
+	provider_class->xa_funcs->xa_rollback = gda_thread_provider_xa_rollback;
+	provider_class->xa_funcs->xa_recover = gda_thread_provider_xa_recover;
+}
+
+static void
+gda_thread_provider_init (GdaThreadProvider *thread_prv, GdaThreadProviderClass *klass)
+{
+}
+
+GType
+gda_thread_provider_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+		static GTypeInfo info = {
+			sizeof (GdaThreadProviderClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gda_thread_provider_class_init,
+			NULL, NULL,
+			sizeof (GdaThreadProvider),
+			0,
+			(GInstanceInitFunc) gda_thread_provider_init
+		};
+		g_static_mutex_lock (&registering);
+		if (type == 0)
+			type = g_type_register_static (GDA_TYPE_SERVER_PROVIDER, "GdaThreadProvider", &info, 0);
+		g_static_mutex_unlock (&registering);
+	}
+
+	return type;
+}
+
+
+/*
+ * Get provider name request
+ */
+static const gchar *
+gda_thread_provider_get_name (GdaServerProvider *provider)
+{
+	return "ThreadWrapper";
+}
+
+/* 
+ * Get provider's version, no need to change this
+ */
+static const gchar *
+gda_thread_provider_get_version (GdaServerProvider *provider)
+{
+	return PACKAGE_VERSION;
+}
+
+static GdaConnection *
+gda_thread_provider_create_connection (GdaServerProvider *provider)
+{
+	GdaConnection *cnc;
+        g_return_val_if_fail (GDA_IS_THREAD_PROVIDER (provider), NULL);
+
+        cnc = g_object_new (GDA_TYPE_CONNECTION, "provider", provider, "is-wrapper", TRUE, NULL);
+
+        return cnc;
+}
+
+/* 
+ * Open connection request
+ *
+ * Returns: TRUE if no error occurred, or FALSE otherwise (and an ERROR connection event must be added to @cnc)
+ */
+typedef struct {
+	const gchar *dsn;
+
+	const gchar *prov_name;
+	const gchar *cnc_string;
+
+	const gchar *auth_string;
+
+	GdaConnectionOptions options;
+
+	GdaServerProvider *out_cnc_provider;
+} OpenConnectionData;
+
+static GdaConnection *
+sub_thread_open_connection (OpenConnectionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	GdaConnection *cnc;
+	if (data->dsn)
+		cnc = gda_connection_open_from_dsn (data->dsn, data->auth_string, data->options, error);
+	else
+		cnc = gda_connection_open_from_string (data->prov_name, data->cnc_string, 
+						       data->auth_string, data->options, error);
+	if (cnc)
+		data->out_cnc_provider = gda_connection_get_provider (cnc);
+	g_print ("/%s() => %p\n", __FUNCTION__, cnc);
+	return cnc;
+}
+
+static void setup_signals (GdaConnection *cnc, ThreadConnectionData *cdata);
+
+static gboolean
+gda_thread_provider_open_connection (GdaServerProvider *provider, GdaConnection *cnc,
+				     GdaQuarkList *params, GdaQuarkList *auth,
+				     guint *task_id, GdaServerProviderAsyncCallback async_cb, gpointer cb_data)
+{
+	g_return_val_if_fail (GDA_IS_THREAD_PROVIDER (provider), FALSE);
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+
+	/* If asynchronous connection opening is not supported, then exit now */
+	if (async_cb) {
+		gda_connection_add_event_string (cnc, 
+						 _("Provider does not support asynchronous connection open"));
+                return FALSE;
+	}
+		
+	/* test if connection has to be opened using a DSN or a connection string */
+	gchar *dsn, *auth_string, *cnc_string;
+	GdaConnectionOptions options;
+	OpenConnectionData *data = NULL;
+	g_object_get (cnc, "dsn", &dsn, 
+		      "auth_string", &auth_string,
+		      "cnc-string", &cnc_string, 
+		      "options", &options,
+		      NULL);
+	if (dsn) {
+		data = g_new0 (OpenConnectionData, 1);
+		data->dsn = dsn;
+	}
+	else if (cnc_string) {
+		data = g_new0 (OpenConnectionData, 1);
+		data->prov_name = gda_quark_list_find (params, "PROVIDER_NAME");
+		data->cnc_string = cnc_string;
+	}
+	
+	/* open sub connection */
+	GdaThreadWrapper *wr;
+	GdaConnection *sub_cnc;
+	GError *error = NULL;
+	g_assert (data);
+	data->auth_string = auth_string;
+	data->options = options & (~GDA_CONNECTION_OPTIONS_THREAD_SAFE);
+	wr = gda_thread_wrapper_new ();
+	gda_thread_wrapper_execute (wr, (GdaThreadWrapperFunc) sub_thread_open_connection, data, NULL, NULL);
+	sub_cnc = gda_thread_wrapper_fetch_result (wr, TRUE, NULL, &error);
+	g_free (dsn);
+	g_free (cnc_string);
+	g_free (auth_string);
+	if (!sub_cnc) {
+		TO_IMPLEMENT; /* create a GdaConnectionEvent from @error using 
+				 gda_connection_add_event_string () */
+		g_object_unref (wr);
+		g_free (data);
+		return FALSE;
+	}
+	
+	ThreadConnectionData *cdata;
+	cdata = g_new0 (ThreadConnectionData, 1);
+	cdata->sub_connection = sub_cnc;
+	cdata->cnc_provider = data->out_cnc_provider;
+	cdata->wrapper = wr;
+	cdata->handlers_ids = g_array_sized_new (FALSE, FALSE, sizeof (gulong), 2);
+	g_free (data);
+	gda_connection_internal_set_provider_data (cnc, cdata, (GDestroyNotify) gda_thread_free_cnc_data);
+	setup_signals (cnc, cdata);
+
+	return TRUE;
+}
+
+static void
+sub_cnc_error_cb (GdaThreadWrapper *wrapper, GdaConnection *sub_cnc, const gchar *signal,
+		  gint n_param_values, const GValue *param_values,
+		  gpointer gda_reserved, GdaConnection *wrapper_cnc)
+{
+	GdaConnectionEvent *ev;
+	g_assert (n_param_values == 1);
+	ev = g_value_get_object (param_values);
+	g_object_ref (ev);
+	gda_connection_add_event (wrapper_cnc, ev);
+}
+
+static void
+sub_cnc_transaction_status_changed_cb (GdaThreadWrapper *wrapper, GdaConnection *sub_cnc, const gchar *signal,
+				       gint n_param_values, const GValue *param_values,
+				       gpointer gda_reserved, GdaConnection *wrapper_cnc)
+{
+	_gda_connection_force_transaction_status (wrapper_cnc, sub_cnc);
+}
+
+static void
+setup_signals (GdaConnection *cnc, ThreadConnectionData *cdata)
+{
+	gulong hid;
+	hid = gda_thread_wrapper_connect_raw (cdata->wrapper, cdata->sub_connection, "error",
+					      (GdaThreadWrapperCallback) sub_cnc_error_cb, cnc);
+	g_array_prepend_val (cdata->handlers_ids, hid);
+	hid = gda_thread_wrapper_connect_raw (cdata->wrapper, cdata->sub_connection, "transaction-status-changed",
+					      (GdaThreadWrapperCallback) sub_cnc_transaction_status_changed_cb, cnc);
+	g_array_prepend_val (cdata->handlers_ids, hid);
+}
+
+/* 
+ * Close connection request
+ *
+ * In this function, the following _must_ be done:
+ *   - Actually close the connection to the database using @cnc's associated ThreadConnectionData structure
+ *   - Free the ThreadConnectionData structure and its contents
+ *
+ * Returns: TRUE if no error occurred, or FALSE otherwise (and an ERROR gonnection event must be added to @cnc)
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+} CncProvData;
+
+static gpointer
+sub_thread_close_connection (CncProvData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->close_connection (data->prov, data->cnc);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_close_connection (GdaServerProvider *provider, GdaConnection *cnc)
+{
+	ThreadConnectionData *cdata;
+	CncProvData wdata;
+	gpointer res;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_close_connection, &wdata, NULL, NULL);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Server version request
+ *
+ * Returns the server version as a string, which should be stored in @cnc's associated ThreadConnectionData structure
+ */
+
+static const gchar *
+sub_thread_get_server_version (CncProvData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	const gchar *retval;
+	retval = PROV_CLASS (data->prov)->get_server_version (data->prov, data->cnc);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval);
+	return retval;
+}
+static const gchar *
+gda_thread_provider_get_server_version (GdaServerProvider *provider, GdaConnection *cnc)
+{
+	ThreadConnectionData *cdata;
+	CncProvData wdata;
+
+	if (!cnc) 
+		return NULL;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return NULL;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_get_server_version, &wdata, NULL, NULL);
+	return (const gchar*) gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+}
+
+/*
+ * Get database request
+ *
+ * Returns the database name as a string, which should be stored in @cnc's associated ThreadConnectionData structure
+ */
+static const gchar *
+sub_thread_get_database (CncProvData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	const gchar *retval;
+	retval = PROV_CLASS (data->prov)->get_database (data->prov, data->cnc);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval);
+	return retval;
+}
+
+static const gchar *
+gda_thread_provider_get_database (GdaServerProvider *provider, GdaConnection *cnc)
+{
+	ThreadConnectionData *cdata;
+	CncProvData wdata;
+
+	if (!cnc) 
+		return NULL;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return NULL;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_get_database, &wdata, NULL, NULL);
+	return (const gchar *) gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+}
+
+/*
+ * Support operation request
+ *
+ * Tells what the implemented server operations are. To add support for an operation, the following steps are required:
+ *   - create a thread_specs_....xml.in file describing the required and optional parameters for the operation
+ *   - add it to the Makefile.am
+ *   - make this method return TRUE for the operation type
+ *   - implement the gda_thread_provider_render_operation() and gda_thread_provider_perform_operation() methods
+ *
+ * In this example, the GDA_SERVER_OPERATION_CREATE_TABLE is implemented
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	GdaServerOperationType type;
+	GdaSet *options;
+} SupportsOperationData;
+
+static gpointer
+sub_thread_supports_operation (SupportsOperationData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->supports_operation (data->prov, data->cnc, data->type, data->options);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_supports_operation (GdaServerProvider *provider, GdaConnection *cnc,
+					GdaServerOperationType type, GdaSet *options)
+{
+	ThreadConnectionData *cdata;
+	SupportsOperationData wdata;
+	gpointer res;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_supports_operation, &wdata, NULL, NULL);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Create operation request
+ *
+ * Creates a #GdaServerOperation. The following code is generic and should only be changed
+ * if some further initialization is required, or if operation's contents is dependant on @cnc
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	GdaServerOperationType type;
+	GdaSet *options;
+} CreateOperationData;
+
+static GdaServerOperation *
+sub_thread_create_operation (CreateOperationData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	GdaServerOperation *op;
+	op = PROV_CLASS (data->prov)->create_operation (data->prov,
+							data->cnc,
+							data->type, 
+							data->options,
+							error);
+	g_print ("/%s() => %p\n", __FUNCTION__, op);
+	return op;
+}
+
+static GdaServerOperation *
+gda_thread_provider_create_operation (GdaServerProvider *provider, GdaConnection *cnc,
+				      GdaServerOperationType type, GdaSet *options, GError **error)
+{
+	ThreadConnectionData *cdata;
+	CreateOperationData wdata;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return NULL;
+	}
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return NULL;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.type = type;
+	wdata.options = options;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_create_operation, &wdata, NULL, NULL);
+	return (GdaServerOperation*) gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, error);
+}
+
+/*
+ * Render operation request
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	GdaServerOperation *op;
+} RenderOperationData;
+
+static gchar *
+sub_thread_render_operation (RenderOperationData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gchar *str;
+	str = PROV_CLASS (data->prov)->render_operation (data->prov,
+							 data->cnc,
+							 data->op, 
+							 error);
+	g_print ("/%s() => %s\n", __FUNCTION__, str);
+	return str;
+}
+
+static gchar *
+gda_thread_provider_render_operation (GdaServerProvider *provider, GdaConnection *cnc,
+				      GdaServerOperation *op, GError **error)
+{
+	ThreadConnectionData *cdata;
+	RenderOperationData wdata;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return NULL;
+	}
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return NULL;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.op = op;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_render_operation, &wdata, NULL, NULL);
+	return (gchar*) gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, error);
+}
+
+/*
+ * Perform operation request
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	GdaServerOperation *op;
+} PerformOperationData;
+
+static gpointer
+sub_thread_perform_operation (PerformOperationData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->perform_operation (data->prov, data->cnc, data->op, 
+							     NULL, NULL, NULL, error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_perform_operation (GdaServerProvider *provider, GdaConnection *cnc,
+				       GdaServerOperation *op, guint *task_id, 
+				       GdaServerProviderAsyncCallback async_cb, gpointer cb_data, GError **error)
+{
+	ThreadConnectionData *cdata;
+	PerformOperationData wdata;
+	gpointer res;
+
+	/* If asynchronous connection opening is not supported, then exit now */
+	if (async_cb) {
+		g_set_error (error, GDA_SERVER_PROVIDER_ERROR, GDA_SERVER_PROVIDER_METHOD_NON_IMPLEMENTED_ERROR,
+			     "%s", _("Provider does not support asynchronous server operation"));
+                return FALSE;
+	}
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.op = op;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_perform_operation, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Begin transaction request
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	const gchar *name; 
+	GdaTransactionIsolation level;
+} BeginTransactionData;
+
+static gpointer
+sub_thread_begin_transaction (BeginTransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->begin_transaction (data->prov, data->cnc, data->name, 
+							     data->level, error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_begin_transaction (GdaServerProvider *provider, GdaConnection *cnc,
+				       const gchar *name, GdaTransactionIsolation level,
+				       GError **error)
+{
+	ThreadConnectionData *cdata;
+	BeginTransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.name = name;
+	wdata.level = level;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_begin_transaction, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, error);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Commit transaction request
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	const gchar *name; 
+	GdaTransactionIsolation level;
+} TransactionData;
+
+static gpointer
+sub_thread_commit_transaction (TransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->commit_transaction (data->prov, data->cnc, data->name, 
+							      error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_commit_transaction (GdaServerProvider *provider, GdaConnection *cnc,
+					const gchar *name, GError **error)
+{
+	ThreadConnectionData *cdata;
+	TransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.name = name;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_commit_transaction, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Rollback transaction request
+ */
+static gpointer
+sub_thread_rollback_transaction (TransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->rollback_transaction (data->prov, data->cnc, data->name, 
+								error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_rollback_transaction (GdaServerProvider *provider, GdaConnection *cnc,
+					  const gchar *name, GError **error)
+{
+	ThreadConnectionData *cdata;
+	TransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.name = name;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_rollback_transaction, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Add savepoint request
+ */
+static gpointer
+sub_thread_add_savepoint (TransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->add_savepoint (data->prov, data->cnc, data->name, 
+							 error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_add_savepoint (GdaServerProvider *provider, GdaConnection *cnc,
+				   const gchar *name, GError **error)
+{
+	ThreadConnectionData *cdata;
+	TransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.name = name;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_add_savepoint, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Rollback savepoint request
+ */
+static gpointer
+sub_thread_rollback_savepoint (TransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->rollback_savepoint (data->prov, data->cnc, data->name, 
+							 error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_rollback_savepoint (GdaServerProvider *provider, GdaConnection *cnc,
+					const gchar *name, GError **error)
+{
+	ThreadConnectionData *cdata;
+	TransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.name = name;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_rollback_savepoint, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Delete savepoint request
+ */
+static gpointer
+sub_thread_delete_savepoint (TransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->delete_savepoint (data->prov, data->cnc, data->name, 
+							 error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_delete_savepoint (GdaServerProvider *provider, GdaConnection *cnc,
+				    const gchar *name, GError **error)
+{
+	ThreadConnectionData *cdata;
+	TransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.name = name;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_delete_savepoint, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Feature support request
+ */
+static gboolean
+gda_thread_provider_supports_feature (GdaServerProvider *provider, GdaConnection *cnc, GdaConnectionFeature feature)
+{
+	if (cnc) {
+		g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+		g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+	}
+
+	switch (feature) {
+	case GDA_CONNECTION_FEATURE_SQL :
+		return TRUE;
+	default: 
+		return FALSE;
+	}
+}
+
+/*
+ * Get data handler request
+ *
+ * This method allows one to obtain a pointer to a #GdaDataHandler object specific to @type or @dbms_type (@dbms_type
+ * must be considered only if @type is not a valid GType).
+ *
+ * A data handler allows one to convert a value between its different representations which are a human readable string,
+ * an SQL representation and a GValue.
+ *
+ * The recommended method is to create GdaDataHandler objects only when they are needed and to keep a reference to them
+ * for further usage, using the gda_server_provider_handler_declare() method.
+ *
+ * The implementation shown here does not define any specific data handler, but there should be some for at least 
+ * binary and time related types.
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	GType type;
+	const gchar *dbms_type;
+} GetDataHandlerData;
+
+static GdaDataHandler *
+sub_thread_get_data_handler (GetDataHandlerData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	GdaDataHandler *retval;
+	retval = PROV_CLASS (data->prov)->get_data_handler (data->prov, data->cnc, data->type, data->dbms_type);
+	g_print ("/%s() => %p\n", __FUNCTION__, retval);
+	return retval;
+}
+
+static GdaDataHandler *
+gda_thread_provider_get_data_handler (GdaServerProvider *provider, GdaConnection *cnc,
+				      GType type, const gchar *dbms_type)
+{
+	ThreadConnectionData *cdata;
+	GetDataHandlerData wdata;
+	GdaDataHandler *res;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.type = type;
+	wdata.dbms_type = dbms_type;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_get_data_handler, &wdata, NULL, NULL);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return res;
+}
+
+/*
+ * Get default DBMS type request
+ *
+ * This method returns the "preferred" DBMS type for GType
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	GType type;
+} GetDefaultDbmsTypeData;
+
+static const gchar *
+sub_thread_get_default_dbms_type (GetDefaultDbmsTypeData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	const gchar *retval;
+	retval = PROV_CLASS (data->prov)->get_def_dbms_type (data->prov, data->cnc, data->type);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval);
+	return retval;
+}
+
+static const gchar*
+gda_thread_provider_get_default_dbms_type (GdaServerProvider *provider, GdaConnection *cnc, GType type)
+{
+	ThreadConnectionData *cdata;
+	GetDefaultDbmsTypeData wdata;
+	const gchar *res;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.type = type;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_get_default_dbms_type, &wdata, NULL, NULL);
+	res = (const gchar *) gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return res;
+}
+
+/*
+ * Create parser request
+ *
+ * This method is responsible for creating a #GdaSqlParser object specific to the SQL dialect used
+ * by the database. See the PostgreSQL provider implementation for an example.
+ */
+
+static GdaSqlParser *
+sub_thread_create_parser (CncProvData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	GdaSqlParser *parser;
+	parser = PROV_CLASS (data->prov)->create_parser (data->prov, data->cnc);
+	g_print ("/%s() => %p\n", __FUNCTION__, parser);
+	return parser;
+}
+
+static GdaSqlParser *
+gda_thread_provider_create_parser (GdaServerProvider *provider, GdaConnection *cnc)
+{
+	ThreadConnectionData *cdata;
+	CncProvData wdata;
+
+	if (!cnc) 
+		return NULL;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return NULL;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_create_parser, &wdata, NULL, NULL);
+	return (GdaSqlParser *) gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+}
+
+/*
+ * GdaStatement to SQL request
+ * 
+ * This method renders a #GdaStatement into its SQL representation.
+ *
+ * The implementation show here simply calls gda_statement_to_sql_extended() but the rendering
+ * can be specialized to the database's SQL dialect, see the implementation of gda_statement_to_sql_extended()
+ * and SQLite's specialized rendering for more details
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	GdaStatement *stmt;
+	GdaSet *params;
+	GdaStatementSqlFlag flags;
+	GSList **params_used;
+} StmtToSqlData;
+
+static const gchar *
+sub_thread_statement_to_sql (StmtToSqlData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	const gchar *retval;
+	retval = PROV_CLASS (data->prov)->statement_to_sql (data->prov, data->cnc, data->stmt,
+							    data->params, data->flags, data->params_used, error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval);
+	return retval;
+}
+
+static gchar *
+gda_thread_provider_statement_to_sql (GdaServerProvider *provider, GdaConnection *cnc,
+				      GdaStatement *stmt, GdaSet *params, GdaStatementSqlFlag flags,
+				      GSList **params_used, GError **error)
+{
+	ThreadConnectionData *cdata;
+	StmtToSqlData wdata;
+
+	if (!cnc) 
+		return NULL;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return NULL;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.stmt = stmt;
+	wdata.params = params;
+	wdata.flags = flags;
+	wdata.params_used = params_used;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_statement_to_sql, &wdata, NULL, NULL);
+	return (gchar *) gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+}
+
+/*
+ * Statement prepare request
+ *
+ * This methods "converts" @stmt into a prepared statement. A prepared statement is a notion
+ * specific in its implementation details to the C API used here. If successfull, it must create
+ * a new #GdaThreadPStmt object and declare it to @cnc.
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	GdaStatement *stmt;
+} PrepareStatementData;
+
+static gpointer
+sub_thread_prepare_statement (PrepareStatementData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = PROV_CLASS (data->prov)->statement_prepare (data->prov,
+							     data->cnc,
+							     data->stmt, 
+							     error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_statement_prepare (GdaServerProvider *provider, GdaConnection *cnc,
+				       GdaStatement *stmt, GError **error)
+{
+	ThreadConnectionData *cdata;
+	PrepareStatementData wdata;
+	gpointer res;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+	g_return_val_if_fail (GDA_IS_STATEMENT (stmt), FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.stmt = stmt;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_prepare_statement, &wdata, NULL, NULL);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, error);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Execute statement request
+ *
+ * Executes a statement. This method should do the following:
+ *    - try to prepare the statement if not yet done
+ *    - optionnally reset the prepared statement
+ *    - bind the variables's values (which are in @params)
+ *    - add a connection event to log the execution
+ *    - execute the prepared statement
+ *
+ * If @stmt is an INSERT statement and @last_inserted_row is not NULL then additional actions must be taken to return the
+ * actual inserted row
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	GdaStatement *stmt;
+	GdaSet *params;
+	GdaStatementModelUsage model_usage;
+	GType *col_types;
+
+	GdaConnection *real_cnc;
+	GdaThreadWrapper *wrapper;
+
+	GdaSet **last_inserted_row;
+} ExecuteStatementData;
+
+static gpointer
+sub_thread_execute_statement (ExecuteStatementData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	GObject *retval;
+	retval = PROV_CLASS (data->prov)->statement_execute (data->prov,
+							     data->cnc,
+							     data->stmt,
+							     data->params,
+							     data->model_usage,
+							     data->col_types,
+							     data->last_inserted_row,
+							     NULL, NULL, NULL,
+							     error);
+	g_print ("/%s() => %p\n", __FUNCTION__, retval);
+
+	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;
+	}
+
+	return retval;
+}
+static GObject *
+gda_thread_provider_statement_execute (GdaServerProvider *provider, GdaConnection *cnc,
+				       GdaStatement *stmt, GdaSet *params,
+				       GdaStatementModelUsage model_usage, 
+				       GType *col_types, GdaSet **last_inserted_row, 
+				       guint *task_id, 
+				       GdaServerProviderExecCallback async_cb, gpointer cb_data, GError **error)
+{
+	ThreadConnectionData *cdata;
+	ExecuteStatementData wdata;
+
+	/* FIXME: handle async requests */
+	if (async_cb) {
+		TO_IMPLEMENT;
+		return NULL;
+	}
+
+	GObject *res;
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+	g_return_val_if_fail (GDA_IS_STATEMENT (stmt), FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.stmt = stmt;
+	wdata.params = params;
+	wdata.model_usage = model_usage;
+	wdata.col_types = col_types;
+	wdata.last_inserted_row = last_inserted_row;
+
+	wdata.real_cnc = cnc;
+	wdata.wrapper = cdata->wrapper;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_execute_statement, &wdata, NULL, NULL);
+	res = (GObject*) gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, error);
+
+	return res;
+}
+
+/*
+ * starts a distributed transaction: put the XA transaction in the ACTIVE state
+ */
+typedef struct {
+	GdaServerProvider *prov;
+	GdaConnection *cnc;
+	const GdaXaTransactionId *xid;
+} XaTransactionData;
+
+static gpointer
+sub_thread_xa_start (XaTransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval = FALSE;
+	retval = PROV_CLASS (data->prov)->xa_funcs->xa_start (data->prov, data->cnc, data->xid, 
+							      error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_xa_start (GdaServerProvider *provider, GdaConnection *cnc, 
+			      const GdaXaTransactionId *xid, GError **error)
+{
+	ThreadConnectionData *cdata;
+	XaTransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+	g_return_val_if_fail (xid, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.xid = xid;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_xa_start, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * put the XA transaction in the IDLE state: the connection won't accept any more modifications.
+ * This state is required by some database providers before actually going to the PREPARED state
+ */
+static gpointer
+sub_thread_xa_end (XaTransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval = FALSE;
+	retval = PROV_CLASS (data->prov)->xa_funcs->xa_end (data->prov, data->cnc, data->xid, 
+							    error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_xa_end (GdaServerProvider *provider, GdaConnection *cnc, 
+			      const GdaXaTransactionId *xid, GError **error)
+{
+	ThreadConnectionData *cdata;
+	XaTransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+	g_return_val_if_fail (xid, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.xid = xid;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_xa_end, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * prepares the distributed transaction: put the XA transaction in the PREPARED state
+ */
+static gpointer
+sub_thread_xa_prepare (XaTransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval = FALSE;
+	retval = PROV_CLASS (data->prov)->xa_funcs->xa_prepare (data->prov, data->cnc, data->xid, 
+								error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_xa_prepare (GdaServerProvider *provider, GdaConnection *cnc, 
+				const GdaXaTransactionId *xid, GError **error)
+{
+	ThreadConnectionData *cdata;
+	XaTransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+	g_return_val_if_fail (xid, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.xid = xid;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_xa_prepare, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * commits the distributed transaction: actually write the prepared data to the database and
+ * terminates the XA transaction
+ */
+static gpointer
+sub_thread_xa_commit (XaTransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval = FALSE;
+	retval = PROV_CLASS (data->prov)->xa_funcs->xa_commit (data->prov, data->cnc, data->xid, 
+								error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_xa_commit (GdaServerProvider *provider, GdaConnection *cnc, 
+				 const GdaXaTransactionId *xid, GError **error)
+{
+	ThreadConnectionData *cdata;
+	XaTransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+	g_return_val_if_fail (xid, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.xid = xid;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_xa_commit, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Rolls back an XA transaction, possible only if in the ACTIVE, IDLE or PREPARED state
+ */
+static gpointer
+sub_thread_xa_rollback (XaTransactionData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval = FALSE;
+	retval = PROV_CLASS (data->prov)->xa_funcs->xa_rollback (data->prov, data->cnc, data->xid, 
+								error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static gboolean
+gda_thread_provider_xa_rollback (GdaServerProvider *provider, GdaConnection *cnc, 
+				   const GdaXaTransactionId *xid, GError **error)
+{
+	ThreadConnectionData *cdata;
+	XaTransactionData wdata;
+	gpointer res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+	g_return_val_if_fail (xid, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+	wdata.xid = xid;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_xa_rollback, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return GPOINTER_TO_INT (res) ? TRUE : FALSE;
+}
+
+/*
+ * Lists all XA transactions that are in the PREPARED state
+ *
+ * Returns: a list of GdaXaTransactionId structures, which will be freed by the caller
+ */
+static GList *
+sub_thread_xa_recover (CncProvData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	GList *retval;
+	retval = PROV_CLASS (data->prov)->xa_funcs->xa_recover (data->prov, data->cnc, error);
+	g_print ("/%s() => %p\n", __FUNCTION__, retval);
+	return GINT_TO_POINTER (retval ? 1 : 0);
+}
+
+static GList *
+gda_thread_provider_xa_recover (GdaServerProvider *provider, GdaConnection *cnc,
+				GError **error)
+{
+	ThreadConnectionData *cdata;
+	CncProvData wdata;
+	GList *res;
+
+	if (!cnc) {
+		g_set_error (error, 0, 0, _("A connection is required"));
+		return FALSE;
+	}
+
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, FALSE);
+
+	cdata = (ThreadConnectionData*) gda_connection_internal_get_provider_data (cnc);
+	if (!cdata) 
+		return FALSE;
+	
+	wdata.prov = cdata->cnc_provider;
+	wdata.cnc = cdata->sub_connection;
+
+	gda_thread_wrapper_execute (cdata->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_xa_recover, &wdata, NULL, error);
+	res = gda_thread_wrapper_fetch_result (cdata->wrapper, TRUE, NULL, NULL);
+	return res;
+}
+
+/*
+ * Free connection's specific data
+ */
+static void
+gda_thread_free_cnc_data (ThreadConnectionData *cdata)
+{
+	if (!cdata)
+		return;
+
+	/*disconnect signals handlers */
+	gint i;
+	for (i = 0; i < cdata->handlers_ids->len; i++) {
+		gulong hid = g_array_index (cdata->handlers_ids, gulong, i);
+		gda_thread_wrapper_disconnect (cdata->wrapper, hid);
+	}
+
+	/* unref cdata->sub_connection in sub thread */
+	gda_thread_wrapper_execute_void (cdata->wrapper, 
+					 (GdaThreadWrapperVoidFunc) g_object_unref,
+					 cdata->sub_connection, NULL, NULL);
+	g_object_unref (cdata->wrapper);
+	g_free (cdata);
+}

Added: trunk/libgda/thread-wrapper/gda-thread-provider.h
==============================================================================
--- (empty file)
+++ trunk/libgda/thread-wrapper/gda-thread-provider.h	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,57 @@
+/* GDA Thread provider
+ * Copyright (C) 1998 - 2009 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *	Rodrigo Moya <rodrigo gnome-db org>
+ *	Carlos Perellórí<carlos gnome-db org>
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GDA_THREAD_PROVIDER_H__
+#define __GDA_THREAD_PROVIDER_H__
+
+#include <libgda/gda-server-provider.h>
+
+#define GDA_TYPE_THREAD_PROVIDER            (gda_thread_provider_get_type())
+#define GDA_THREAD_PROVIDER(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, GDA_TYPE_THREAD_PROVIDER, GdaThreadProvider))
+#define GDA_THREAD_PROVIDER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, GDA_TYPE_THREAD_PROVIDER, GdaThreadProviderClass))
+#define GDA_IS_THREAD_PROVIDER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, GDA_TYPE_THREAD_PROVIDER))
+#define GDA_IS_THREAD_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDA_TYPE_THREAD_PROVIDER))
+
+typedef struct _GdaThreadProvider      GdaThreadProvider;
+typedef struct _GdaThreadProviderClass GdaThreadProviderClass;
+
+struct _GdaThreadProvider {
+	GdaServerProvider provider;
+};
+
+struct _GdaThreadProviderClass {
+	GdaServerProviderClass parent_class;
+
+	/* Padding for future expansion */
+	void (*_gda_reserved1) (void);
+	void (*_gda_reserved2) (void);
+};
+
+G_BEGIN_DECLS
+
+GType              gda_thread_provider_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif

Added: trunk/libgda/thread-wrapper/gda-thread-recordset.c
==============================================================================
--- (empty file)
+++ trunk/libgda/thread-wrapper/gda-thread-recordset.c	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,331 @@
+/* GDA Library
+ * Copyright (C) 2009 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *         Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdarg.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include "gda-thread-wrapper.h"
+#include "gda-thread-recordset.h"
+#include "gda-thread-provider.h"
+#include <libgda/providers-support/gda-data-select-priv.h>
+//#include "gda-thread-blob-op.h"
+
+static void gda_thread_recordset_class_init (GdaThreadRecordsetClass *klass);
+static void gda_thread_recordset_init       (GdaThreadRecordset *recset,
+					     GdaThreadRecordsetClass *klass);
+static void gda_thread_recordset_dispose   (GObject *object);
+
+/* virtual methods */
+static gint    gda_thread_recordset_fetch_nb_rows (GdaDataSelect *model);
+static gboolean gda_thread_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
+
+static gboolean gda_thread_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
+static gboolean gda_thread_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
+static gboolean gda_thread_recordset_fetch_at (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
+
+struct _GdaThreadRecordsetPrivate {
+	GdaDataModel *sub_model;
+	GdaThreadWrapper *wrapper;
+};
+static GObjectClass *parent_class = NULL;
+
+#define DATA_SELECT_CLASS(model) (GDA_DATA_SELECT_CLASS (G_OBJECT_GET_CLASS (model)))
+#define COPY_PUBLIC_DATA(from,to) \
+	(((GdaDataSelect *) (to))->nb_stored_rows = ((GdaDataSelect *) (from))->nb_stored_rows, \
+	 ((GdaDataSelect *) (to))->prep_stmt = ((GdaDataSelect *) (from))->prep_stmt, \
+	 ((GdaDataSelect *) (to))->advertized_nrows = ((GdaDataSelect *) (from))->advertized_nrows)
+
+/*
+ * Object init and finalize
+ */
+static void
+gda_thread_recordset_init (GdaThreadRecordset *recset,
+			   GdaThreadRecordsetClass *klass)
+{
+	g_return_if_fail (GDA_IS_THREAD_RECORDSET (recset));
+	recset->priv = g_new0 (GdaThreadRecordsetPrivate, 1);
+	recset->priv->sub_model = NULL;
+	recset->priv->wrapper = NULL;
+}
+
+static void
+gda_thread_recordset_class_init (GdaThreadRecordsetClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	GdaDataSelectClass *pmodel_class = GDA_DATA_SELECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->dispose = gda_thread_recordset_dispose;
+	pmodel_class->fetch_nb_rows = gda_thread_recordset_fetch_nb_rows;
+	pmodel_class->fetch_random = gda_thread_recordset_fetch_random;
+	pmodel_class->store_all = NULL;
+
+	pmodel_class->fetch_next = gda_thread_recordset_fetch_next;
+	pmodel_class->fetch_prev = gda_thread_recordset_fetch_prev;
+	pmodel_class->fetch_at = gda_thread_recordset_fetch_at;
+}
+
+static void
+gda_thread_recordset_dispose (GObject *object)
+{
+	GdaThreadRecordset *recset = (GdaThreadRecordset *) object;
+
+	g_return_if_fail (GDA_IS_THREAD_RECORDSET (recset));
+
+	if (recset->priv) {
+		if (recset->priv->sub_model) {
+			/* unref recset->priv->sub_model in sub thread */
+			gda_thread_wrapper_execute_void (recset->priv->wrapper, 
+							 (GdaThreadWrapperFunc) g_object_unref,
+							 recset->priv->sub_model, NULL, NULL);
+			recset->priv->sub_model = NULL;
+			g_object_unref (recset->priv->wrapper);
+			recset->priv->wrapper = NULL;
+		}
+		g_free (recset->priv);
+		recset->priv = NULL;
+
+		((GdaDataSelect*) recset)->prep_stmt = NULL; /* don't unref since we don't hold a reference to it */
+	}
+
+	parent_class->dispose (object);
+}
+
+/*
+ * Public functions
+ */
+
+GType
+_gda_thread_recordset_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+		static const GTypeInfo info = {
+			sizeof (GdaThreadRecordsetClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gda_thread_recordset_class_init,
+			NULL,
+			NULL,
+			sizeof (GdaThreadRecordset),
+			0,
+			(GInstanceInitFunc) gda_thread_recordset_init
+		};
+		g_static_mutex_lock (&registering);
+		if (type == 0)
+			type = g_type_register_static (GDA_TYPE_DATA_SELECT, "GdaThreadRecordset", &info, 0);
+		g_static_mutex_unlock (&registering);
+	}
+
+	return type;
+}
+
+
+/*
+ * executed in the sub thread (the one which can manipulate @sub_model)
+ */
+GdaDataModel *
+_gda_thread_recordset_new (GdaConnection *cnc, GdaThreadWrapper *wrapper, GdaDataModel *sub_model)
+{
+	GdaThreadRecordset *model;
+
+	model = GDA_THREAD_RECORDSET (g_object_new (GDA_TYPE_THREAD_RECORDSET,
+						    "connection", cnc, NULL));
+
+	_gda_data_select_share_private_data (GDA_DATA_SELECT (sub_model), 
+					     GDA_DATA_SELECT (model));
+	model->priv->wrapper = g_object_ref (wrapper);
+	model->priv->sub_model = g_object_ref (sub_model);
+
+	COPY_PUBLIC_DATA (sub_model, model);
+
+        return GDA_DATA_MODEL (model);
+}
+
+/*
+ * GdaDataSelect virtual methods
+ */
+
+
+/*
+ * fetch nb rows
+ */
+static gpointer
+sub_thread_fetch_nb_rows (GdaDataSelect *model, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gint retval;
+	retval = DATA_SELECT_CLASS (model)->fetch_nb_rows (model);
+	g_print ("/%s() => %d\n", __FUNCTION__, retval);
+	return GINT_TO_POINTER (retval);
+}
+
+static gint
+gda_thread_recordset_fetch_nb_rows (GdaDataSelect *model)
+{
+	GdaThreadRecordset *rs = (GdaThreadRecordset*) model;
+	gint nb;
+	gda_thread_wrapper_execute (rs->priv->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_fetch_nb_rows, 
+				    rs->priv->sub_model, NULL, NULL);
+	nb = GPOINTER_TO_INT (gda_thread_wrapper_fetch_result (rs->priv->wrapper, TRUE, NULL, NULL));
+	COPY_PUBLIC_DATA (rs->priv->sub_model, rs);
+	return nb;
+}
+
+/*
+ * fetch random
+ */
+typedef struct {
+	GdaDataSelect *select;
+	gint rownum;
+	GdaRow **row;
+} ThreadData;
+
+static gpointer
+sub_thread_fetch_random (ThreadData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = DATA_SELECT_CLASS (data->select)->fetch_random (data->select, data->row, data->rownum, error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval);
+}
+
+static gboolean
+gda_thread_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
+{
+	GdaThreadRecordset *rs = (GdaThreadRecordset*) model;
+	ThreadData wdata;
+	gboolean retval;
+
+	wdata.select = (GdaDataSelect *) rs->priv->sub_model;
+	wdata.rownum = rownum;
+	wdata.row = prow;
+	gda_thread_wrapper_execute (rs->priv->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_fetch_random, 
+				    &wdata, NULL, NULL);
+
+	retval = GPOINTER_TO_INT (gda_thread_wrapper_fetch_result (rs->priv->wrapper, TRUE, NULL, error)) ? 
+		TRUE : FALSE;
+	COPY_PUBLIC_DATA (rs->priv->sub_model, rs);
+	return retval;
+}
+
+/*
+ * fetch next
+ */
+static gpointer
+sub_thread_fetch_next (ThreadData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = DATA_SELECT_CLASS (data->select)->fetch_next (data->select, data->row, data->rownum, error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval);
+}
+
+static gboolean
+gda_thread_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
+{
+	GdaThreadRecordset *rs = (GdaThreadRecordset*) model;
+	ThreadData wdata;
+	gboolean retval;
+
+	wdata.select = (GdaDataSelect *) rs->priv->sub_model;
+	wdata.rownum = rownum;
+	wdata.row = prow;
+	gda_thread_wrapper_execute (rs->priv->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_fetch_next, 
+				    &wdata, NULL, NULL);
+	retval = GPOINTER_TO_INT (gda_thread_wrapper_fetch_result (rs->priv->wrapper, TRUE, NULL, error)) ? 
+		TRUE : FALSE;
+	COPY_PUBLIC_DATA (rs->priv->sub_model, rs);
+	return retval;
+}
+
+/*
+ * fetch prev
+ */
+static gpointer
+sub_thread_fetch_prev (ThreadData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = DATA_SELECT_CLASS (data->select)->fetch_prev (data->select, data->row, data->rownum, error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval);
+}
+
+static gboolean
+gda_thread_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
+{
+	GdaThreadRecordset *rs = (GdaThreadRecordset*) model;
+	ThreadData wdata;
+	gboolean retval;
+
+	wdata.select = (GdaDataSelect *) rs->priv->sub_model;
+	wdata.rownum = rownum;
+	wdata.row = prow;
+	gda_thread_wrapper_execute (rs->priv->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_fetch_prev, 
+				    &wdata, NULL, NULL);
+	retval = GPOINTER_TO_INT (gda_thread_wrapper_fetch_result (rs->priv->wrapper, TRUE, NULL, error)) ? 
+		TRUE : FALSE;
+	COPY_PUBLIC_DATA (rs->priv->sub_model, rs);
+	return retval;
+}
+
+/*
+ * fetch at
+ */
+static gpointer
+sub_thread_fetch_at (ThreadData *data, GError **error)
+{
+	/* WARNING: function executed in sub thread! */
+	gboolean retval;
+	retval = DATA_SELECT_CLASS (data->select)->fetch_at (data->select, data->row, data->rownum, error);
+	g_print ("/%s() => %s\n", __FUNCTION__, retval ? "TRUE" : "FALSE");
+	return GINT_TO_POINTER (retval);
+}
+
+static gboolean
+gda_thread_recordset_fetch_at (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
+{
+	GdaThreadRecordset *rs = (GdaThreadRecordset*) model;
+	ThreadData wdata;
+	gboolean retval;
+
+	wdata.select = (GdaDataSelect *) rs->priv->sub_model;
+	wdata.rownum = rownum;
+	wdata.row = prow;
+	gda_thread_wrapper_execute (rs->priv->wrapper, 
+				    (GdaThreadWrapperFunc) sub_thread_fetch_at, 
+				    &wdata, NULL, NULL);
+	retval = GPOINTER_TO_INT (gda_thread_wrapper_fetch_result (rs->priv->wrapper, TRUE, NULL, error)) ? 
+		TRUE : FALSE;
+	COPY_PUBLIC_DATA (rs->priv->sub_model, rs);
+	return retval;
+}

Added: trunk/libgda/thread-wrapper/gda-thread-recordset.h
==============================================================================
--- (empty file)
+++ trunk/libgda/thread-wrapper/gda-thread-recordset.h	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,57 @@
+/* GDA Library
+ * Copyright (C) 2009 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *         Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GDA_THREAD_RECORDSET_H__
+#define __GDA_THREAD_RECORDSET_H__
+
+#include <libgda/gda-connection.h>
+#include <libgda/gda-data-select.h>
+#include <thread-wrapper/gda-thread-wrapper.h>
+
+G_BEGIN_DECLS
+
+#define GDA_TYPE_THREAD_RECORDSET            (_gda_thread_recordset_get_type())
+#define GDA_THREAD_RECORDSET(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, GDA_TYPE_THREAD_RECORDSET, GdaThreadRecordset))
+#define GDA_THREAD_RECORDSET_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, GDA_TYPE_THREAD_RECORDSET, GdaThreadRecordsetClass))
+#define GDA_IS_THREAD_RECORDSET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, GDA_TYPE_THREAD_RECORDSET))
+#define GDA_IS_THREAD_RECORDSET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDA_TYPE_THREAD_RECORDSET))
+
+typedef struct _GdaThreadRecordset        GdaThreadRecordset;
+typedef struct _GdaThreadRecordsetClass   GdaThreadRecordsetClass;
+typedef struct _GdaThreadRecordsetPrivate GdaThreadRecordsetPrivate;
+
+struct _GdaThreadRecordset {
+	GdaDataSelect                  model;
+	GdaThreadRecordsetPrivate     *priv;
+};
+
+struct _GdaThreadRecordsetClass {
+	GdaDataSelectClass             parent_class;
+};
+
+GType         _gda_thread_recordset_get_type  (void) G_GNUC_CONST;
+GdaDataModel *_gda_thread_recordset_new       (GdaConnection *cnc, GdaThreadWrapper *wrapper, 
+					       GdaDataModel *sub_model);
+
+G_END_DECLS
+
+#endif

Added: trunk/libgda/thread-wrapper/gda-thread-wrapper.c
==============================================================================
--- (empty file)
+++ trunk/libgda/thread-wrapper/gda-thread-wrapper.c	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,958 @@
+/* GDA library
+ * Copyright (C) 2009 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include "gda-thread-wrapper.h"
+#include <libgda/gda-mutex.h>
+#include <gobject/gvaluecollector.h>
+#include <libgda/gda-debug-macros.h>
+#include <libgda/gda-value.h>
+
+typedef struct _ThreadData ThreadData;
+typedef struct _SharedData SharedData;
+typedef struct _Job Job;
+typedef struct _Result Result;
+typedef struct _SignalSpec SignalSpec;
+typedef struct _SignalEmissionData SignalEmissionData;
+
+struct _GdaThreadWrapperPrivate {
+	GdaMutex    *mutex;
+	guint        next_job_id;
+	GThread     *sub_thread;
+	GAsyncQueue *to_sub_thread;
+
+	GHashTable *threads_hash; /* key = a GThread, value = a #ThreadData pointer */
+};
+
+/*
+ * Data shared between the GdaThreadWrapper (and the threads which use it) and its sub thread.
+ * It implements its own locking mechanism.
+ */
+struct _SharedData {
+        gint     nb_waiting;
+	Job     *current_job;
+	GThread *wrapper_sub_thread;
+
+        gint     ref_count;
+	GMutex  *mutex;
+};
+
+SharedData *
+shared_data_new (void)
+{
+	SharedData *shd = g_new0 (SharedData, 1);
+	shd->ref_count = 1;
+	shd->nb_waiting = 0;
+	shd->mutex = g_mutex_new ();
+	return shd;
+}
+
+static SharedData *
+shared_data_ref (SharedData *shd)
+{
+	g_mutex_lock (shd->mutex);
+	shd->ref_count++;
+	g_mutex_unlock (shd->mutex);
+	return shd;
+}
+static void
+shared_data_unref (SharedData *shd)
+{
+	g_mutex_lock (shd->mutex);
+	shd->ref_count--;
+	if (shd->ref_count == 0) {
+		g_mutex_unlock (shd->mutex);
+		g_mutex_free (shd->mutex);
+		g_free (shd);
+	}
+	else
+		g_mutex_unlock (shd->mutex);
+}
+
+static void
+shared_data_add_nb_waiting (SharedData *shd, gint to_add)
+{
+	g_mutex_lock (shd->mutex);
+	shd->nb_waiting += to_add;
+	g_mutex_unlock (shd->mutex);
+}
+
+
+/* 
+ * Jobs.
+ * Created and destroyed exclusively by the thread(s) using the GdaThreadWrapper object,
+ * except for the job where job->type == JOB_TYPE_DESTROY which is destroyed by the sub thread.
+ *
+ * Passed to the sub job through obj->to_sub_thread
+ */
+typedef enum {
+	JOB_TYPE_EXECUTE,
+	JOB_TYPE_DESTROY
+} JobType;
+struct _Job {
+	JobType                  type;
+	guint                    job_id;
+	GdaThreadWrapperFunc     func;
+	GdaThreadWrapperVoidFunc void_func;
+	gpointer                 arg;
+	GDestroyNotify           arg_destroy_func;
+	GAsyncQueue             *reply_queue; /* holds a ref to it */
+	SharedData              *shared; /* holds a ref to it */
+};
+static void
+job_free (Job *job)
+{
+	if (job->arg && job->arg_destroy_func)
+		job->arg_destroy_func (job->arg);
+	if (job->reply_queue)
+		g_async_queue_unref (job->reply_queue);
+	if (job->shared)
+		shared_data_unref (job->shared);
+	g_free (job);
+}
+
+/*
+ * Signal specification, created when using _connect().
+ *
+ * A SignalSpec only exists as long as the correcponding ThreadData exists.
+ */
+struct _SignalSpec {
+        GSignalQuery  sigprop; /* must be first */
+        gpointer      instance;
+        gulong        signal_id;
+
+        GdaThreadWrapperCallback callback;
+        gpointer                 data;
+
+	ThreadData      *td;
+};
+
+struct _SignalEmissionData {
+        guint      n_param_values;
+        GValue    *param_values; /* array of GValue structures */
+};
+
+static void
+signal_emission_data_free (SignalEmissionData *sd)
+{
+	gint i;
+	for (i = 0; i < sd->n_param_values; i++) {
+		GValue *value = sd->param_values + i;
+		if (G_VALUE_TYPE (value) != GDA_TYPE_NULL)
+			g_value_reset (value);
+	}
+	g_free (sd->param_values);
+	g_free (sd);
+}
+
+/*
+ * Result of a job executed by the sub thread.
+ * Created exclusively by the sub thread, and destroyed by the thread(s) using the GdaThreadWrapper object
+ *
+ * Passed from the sub thread through obj->from_sub_thread
+ */
+typedef enum {
+	RESULT_TYPE_EXECUTE,
+	RESULT_TYPE_SIGNAL
+} ResultType;
+struct _Result {
+	Job                 *job;
+	ResultType           type;
+	union {
+		struct {
+			gpointer             result;
+			GError              *error;
+		} exe;
+		struct {
+			SignalSpec         *spec;
+			SignalEmissionData *data;
+		} signal;
+	} u;
+};
+#define RESULT(x) ((Result*)(x))
+static void
+result_free (Result *res)
+{
+	if (res->job)
+		job_free (res->job);
+	if (res->type == RESULT_TYPE_EXECUTE) {
+		if (res->u.exe.error)
+			g_error_free (res->u.exe.error);
+	}
+	else if (res->type == RESULT_TYPE_SIGNAL) 
+		signal_emission_data_free (res->u.signal.data);
+	else
+		g_assert_not_reached ();
+	g_free (res);
+}
+
+/*
+ * Per thread accounting data.
+ * Each new job increases the ref count
+ */
+struct _ThreadData {
+	GThread *owner;
+	GSList *signals_list; /* list of SignalSpec pointers, owns all the structures */
+	GAsyncQueue *from_sub_thread; /* holds a ref to it */
+	GSList *results; /* list of Result pointers */
+	SharedData *shared; /* number of jobs waiting from that thread, holds a ref to it */
+};
+#define THREAD_DATA(x) ((ThreadData*)(x))
+
+static ThreadData *
+get_thread_data (GdaThreadWrapper *wrapper)
+{
+	ThreadData *td;
+
+	td = g_hash_table_lookup (wrapper->priv->threads_hash, g_thread_self());
+	if (!td) {
+		td = g_new0 (ThreadData, 1);
+		td->owner = g_thread_self();
+		td->from_sub_thread = g_async_queue_new ();
+		td->results = NULL;
+		td->shared = shared_data_new ();
+		td->shared->wrapper_sub_thread = wrapper->priv->sub_thread;
+
+		g_hash_table_insert (wrapper->priv->threads_hash, g_thread_self(), td);
+	}
+	return td;
+}
+
+static void
+thread_data_free (ThreadData *td)
+{
+	g_async_queue_unref (td->from_sub_thread);
+	if (td->results) {
+		g_slist_foreach (td->results, (GFunc) result_free, NULL);
+		g_slist_free (td->results);
+	}
+	if (td->signals_list) {
+		GSList *list;
+		for (list = td->signals_list; list; list = list->next) {
+			/* free each SignalSpec */
+			SignalSpec *sigspec = (SignalSpec*) list->data;
+			g_signal_handler_disconnect (sigspec->instance, sigspec->signal_id);
+			g_free (sigspec);
+		}
+		g_slist_free (td->signals_list);
+	}
+	shared_data_unref (td->shared);
+	g_free (td);
+}
+
+static void gda_thread_wrapper_class_init (GdaThreadWrapperClass *klass);
+static void gda_thread_wrapper_init       (GdaThreadWrapper *wrapper, GdaThreadWrapperClass *klass);
+static void gda_thread_wrapper_dispose    (GObject *object);
+static void gda_thread_wrapper_set_property (GObject *object,
+					     guint param_id,
+					     const GValue *value,
+					     GParamSpec *pspec);
+static void gda_thread_wrapper_get_property (GObject *object,
+					     guint param_id,
+					     GValue *value,
+					     GParamSpec *pspec);
+
+enum {
+	LAST_SIGNAL
+};
+
+static gint gda_thread_wrapper_signals[LAST_SIGNAL] = { };
+
+/* properties */
+enum {
+	PROP_0
+};
+
+static GObjectClass *parent_class = NULL;
+
+/*
+ * GdaThreadWrapper class implementation
+ * @klass:
+ */
+static void
+gda_thread_wrapper_class_init (GdaThreadWrapperClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* Properties */
+        object_class->set_property = gda_thread_wrapper_set_property;
+        object_class->get_property = gda_thread_wrapper_get_property;
+
+	object_class->dispose = gda_thread_wrapper_dispose;
+}
+
+/*
+ * Executed in the sub thread:
+ * takes a Job in (from the wrapper->priv->to_sub_thread queue) and creates a new Result which 
+ * it pushed to Job->reply_queue
+ */
+static gpointer
+sub_thread_entry_point (GAsyncQueue *to_sub_thread)
+{
+	GAsyncQueue *in;
+
+	in = to_sub_thread;
+
+	/* don't use @priv anymore */
+	while (1) {
+		Job *job;
+		
+		job = g_async_queue_pop (in);
+
+		if (job->type == JOB_TYPE_DESTROY) {
+			g_assert (! job->arg_destroy_func);
+			job_free (job);
+			/*g_print ("... exit sub thread for wrapper\n");*/
+			/* exit sub thread */
+			break;
+		}
+		else if (job->type == JOB_TYPE_EXECUTE) {
+			Result *res = g_new0 (Result, 1);
+			res->job = job;
+			res->type = RESULT_TYPE_EXECUTE;
+			job->shared->current_job = job;
+			if (job->func)
+				res->u.exe.result = job->func (job->arg, &(res->u.exe.error));
+			else {
+				res->u.exe.result = NULL;
+				job->void_func (job->arg, &(res->u.exe.error));
+			}
+			job->shared->current_job = NULL;
+			shared_data_add_nb_waiting (job->shared, -1);
+			g_async_queue_push (job->reply_queue, res);
+		}
+		else
+			g_assert_not_reached ();
+	}
+
+	g_async_queue_unref (in);
+
+	return NULL;
+}
+
+static void
+gda_thread_wrapper_init (GdaThreadWrapper *wrapper, GdaThreadWrapperClass *klass)
+{
+	g_return_if_fail (GDA_IS_THREAD_WRAPPER (wrapper));
+
+	wrapper->priv = g_new0 (GdaThreadWrapperPrivate, 1);
+	wrapper->priv->mutex = gda_mutex_new ();
+	wrapper->priv->next_job_id = 1;
+
+	wrapper->priv->threads_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) thread_data_free);
+
+	wrapper->priv->to_sub_thread = g_async_queue_new ();
+	wrapper->priv->sub_thread = g_thread_create ((GThreadFunc) sub_thread_entry_point,
+						     g_async_queue_ref (wrapper->priv->to_sub_thread), /* inc. ref for sub thread usage */
+						     FALSE, NULL);
+	/*g_print ("... new wrapper %p, sub_thread=%p\n", wrapper, wrapper->priv->sub_thread);*/
+}
+
+static void
+gda_thread_wrapper_dispose (GObject *object)
+{
+	GdaThreadWrapper *wrapper = (GdaThreadWrapper *) object;
+
+	g_return_if_fail (GDA_IS_THREAD_WRAPPER (wrapper));
+
+	if (wrapper->priv) {
+		Job *job = g_new0 (Job, 1);
+		job->type = JOB_TYPE_DESTROY;
+		g_async_queue_push (wrapper->priv->to_sub_thread, job);
+		/*g_print ("... pushed JOB_TYPE_DESTROY for wrapper %p\n", wrapper);*/
+
+		g_async_queue_unref (wrapper->priv->to_sub_thread);
+		wrapper->priv->sub_thread = NULL; /* side note: don't wait for sub thread to terminate */
+		
+		if (wrapper->priv->threads_hash)
+			g_hash_table_destroy (wrapper->priv->threads_hash);
+
+		gda_mutex_free (wrapper->priv->mutex);
+
+		g_free (wrapper->priv);
+		wrapper->priv = NULL;
+	}
+
+	/* chain to parent class */
+	parent_class->dispose (object);
+}
+
+/* module error */
+GQuark gda_thread_wrapper_error_quark (void)
+{
+        static GQuark quark;
+        if (!quark)
+                quark = g_quark_from_static_string ("gda_thread_wrapper_error");
+        return quark;
+}
+
+/**
+ * gda_thread_wrapper_get_type
+ * 
+ * Registers the #GdaThreadWrapper class on the GLib type system.
+ * 
+ * Returns: the GType identifying the class.
+ */
+GType
+gda_thread_wrapper_get_type (void)
+{
+        static GType type = 0;
+
+        if (G_UNLIKELY (type == 0)) {
+                static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+                static const GTypeInfo info = {
+                        sizeof (GdaThreadWrapperClass),
+                        (GBaseInitFunc) NULL,
+                        (GBaseFinalizeFunc) NULL,
+                        (GClassInitFunc) gda_thread_wrapper_class_init,
+                        NULL,
+                        NULL,
+                        sizeof (GdaThreadWrapper),
+                        0,
+                        (GInstanceInitFunc) gda_thread_wrapper_init
+                };
+
+                g_static_mutex_lock (&registering);
+                if (type == 0)
+                        type = g_type_register_static (G_TYPE_OBJECT, "GdaThreadWrapper", &info, 0);
+                g_static_mutex_unlock (&registering);
+        }
+        return type;
+}
+
+static void
+gda_thread_wrapper_set_property (GObject *object,
+			       guint param_id,
+			       const GValue *value,
+			       GParamSpec *pspec)
+{
+	GdaThreadWrapper *wrapper;
+
+        wrapper = GDA_THREAD_WRAPPER (object);
+        if (wrapper->priv) {
+                switch (param_id) {
+		}	
+	}
+}
+
+static void
+gda_thread_wrapper_get_property (GObject *object,
+			       guint param_id,
+			       GValue *value,
+			       GParamSpec *pspec)
+{
+	GdaThreadWrapper *wrapper;
+	
+	wrapper = GDA_THREAD_WRAPPER (object);
+	if (wrapper->priv) {
+		switch (param_id) {
+		}
+	}	
+}
+
+/**
+ * gda_thread_wrapper_new
+ *
+ * Creates a new #GdaThreadWrapper object
+ *
+ * Returns: a new #GdaThreadWrapper object
+ *
+ * Since: 4.2
+ */
+GdaThreadWrapper *
+gda_thread_wrapper_new (void)
+{
+	return (GdaThreadWrapper *) g_object_new (GDA_TYPE_THREAD_WRAPPER, NULL);
+}
+
+/**
+ * gda_thread_wrapper_execute
+ * @wrapper: a #GdaThreadWrapper object
+ * @func: the function to execute
+ * @arg: argument to pass to @func
+ * @arg_destroy_func: function to be called when the execution has finished, to destroy @arg
+ * @error: a place to store errors, for errors occurring in this method, not errors occurring while @func
+ *         is executed, or %NULL
+ *
+ * Make @wrapper execute the @func function with the @arg argument (along with a #GError which is not @error)
+ * in the sub thread managed by @wrapper. To execute a function which does not return anything,
+ * use gda_thread_wrapper_execute_void().
+ *
+ * This method returns immediately, and the caller then needs to use gda_thread_wrapper_fetch_result() to
+ * check if the execution has finished and get the result.
+ *
+ * Once @func's execution is finished, if it is not %NULL, the @arg_destroy_func destruction function is called
+ * on @arg. This occurs in the thread calling gda_thread_wrapper_fetch_result().
+ *
+ * Returns: the job ID, or 0 if an error occurred
+ *
+ * Since: 4.2
+ */
+guint
+gda_thread_wrapper_execute (GdaThreadWrapper *wrapper, GdaThreadWrapperFunc func,
+			    gpointer arg, GDestroyNotify arg_destroy_func, GError **error)
+{
+	Job *job;
+	guint id;
+	ThreadData *td;
+	
+	g_return_val_if_fail (GDA_IS_THREAD_WRAPPER (wrapper), 0);
+	g_return_val_if_fail (wrapper->priv, 0);
+	g_return_val_if_fail (func, 0);
+
+	gda_mutex_lock (wrapper->priv->mutex);
+
+	td = get_thread_data (wrapper);
+	shared_data_add_nb_waiting (td->shared, 1);
+
+	job = g_new0 (Job, 1);
+	job->type = JOB_TYPE_EXECUTE;
+	job->job_id = wrapper->priv->next_job_id++;
+	job->func = func;
+	job->void_func = NULL;
+	job->arg = arg;
+	job->arg_destroy_func = arg_destroy_func;
+	job->reply_queue = g_async_queue_ref (td->from_sub_thread);
+	job->shared = shared_data_ref (td->shared);
+
+	id = job->job_id;
+	gda_mutex_unlock (wrapper->priv->mutex);
+
+	g_async_queue_push (wrapper->priv->to_sub_thread, job);
+
+	return id;
+}
+
+/**
+ * gda_thread_wrapper_execute_void
+ * @wrapper: a #GdaThreadWrapper object
+ * @func: the function to execute
+ * @arg: argument to pass to @func
+ * @arg_destroy_func: function to be called when the execution has finished, to destroy @arg
+ * @error: a place to store errors, for errors occurring in this method, not errors occurring while @func
+ *         is executed, or %NULL
+ *
+ * Make @wrapper execute the @func function with the @arg argument (along with a #GError which is not @error)
+ * in the sub thread managed by @wrapper. To execute a function which returns some pointer,
+ * use gda_thread_wrapper_execute().
+ *
+ * This method returns immediately. Calling gda_thread_wrapper_fetch_result() is not necessary as @func
+ * does not return any result. However, it may be necessary to call gda_thread_wrapper_iterate() to give @wrapper a
+ * chance to execute the @arg_destroy_func function if not %NULL (note that gda_thread_wrapper_iterate() is
+ * called by gda_thread_wrapper_fetch_result() itself).
+ *
+ * Once @func's execution is finished, if it is not %NULL, the @arg_destroy_func destruction function is called
+ * on @arg. This occurs in the thread calling gda_thread_wrapper_fetch_result().
+ *
+ * Returns: the job ID, or 0 if an error occurred
+ *
+ * Since: 4.2
+ */
+guint
+gda_thread_wrapper_execute_void (GdaThreadWrapper *wrapper, GdaThreadWrapperVoidFunc func,
+				 gpointer arg, GDestroyNotify arg_destroy_func, GError **error)
+{
+	Job *job;
+	guint id;
+	ThreadData *td;
+
+	g_return_val_if_fail (GDA_IS_THREAD_WRAPPER (wrapper), 0);
+	g_return_val_if_fail (wrapper->priv, 0);
+	g_return_val_if_fail (func, 0);
+
+	gda_mutex_lock (wrapper->priv->mutex);
+
+	td = get_thread_data (wrapper);
+	shared_data_add_nb_waiting (td->shared, 1);
+
+	job = g_new0 (Job, 1);
+	job->type = JOB_TYPE_EXECUTE;
+	job->job_id = wrapper->priv->next_job_id++;
+	job->func = NULL;
+	job->void_func = func;
+	job->arg = arg;
+	job->arg_destroy_func = arg_destroy_func;
+	job->reply_queue = g_async_queue_ref (td->from_sub_thread);
+	job->shared = shared_data_ref (td->shared);
+
+	id = job->job_id;
+	gda_mutex_unlock (wrapper->priv->mutex);
+
+	g_async_queue_push (wrapper->priv->to_sub_thread, job);
+
+	return id;
+}
+
+/**
+ * gda_thread_wrapper_iterate
+ * @wrapper: a #GdaThreadWrapper object
+ * @may_block: whether the call may block
+ *
+ * This method gives @wrapper a chance to check if some functions to be executed have finished
+ * <emphasis>in the calling thread</emphasis>. It handles one function's execution result, and
+ * if @may_block is %TRUE, then it will block untill there is one (functions returning void are
+ * ignored).
+ *
+ * Since: 4.2
+ */
+void
+gda_thread_wrapper_iterate (GdaThreadWrapper *wrapper, gboolean may_block)
+{
+	ThreadData *td;
+	Result *res;
+
+	g_return_if_fail (GDA_IS_THREAD_WRAPPER (wrapper));
+	g_return_if_fail (wrapper->priv);
+
+	gda_mutex_lock (wrapper->priv->mutex);
+
+	td = g_hash_table_lookup (wrapper->priv->threads_hash, g_thread_self());
+	if (!td) {
+		/* nothing to be done for this thread */
+		gda_mutex_unlock (wrapper->priv->mutex);
+		return;
+	}
+
+ again:
+	if (may_block)
+		res = g_async_queue_pop (td->from_sub_thread);
+	else
+		res = g_async_queue_try_pop (td->from_sub_thread);
+	if (res) {
+		gboolean do_again = FALSE;
+		if (res->type == RESULT_TYPE_EXECUTE) {
+			if (!res->job->func) {
+				result_free (res); /* ignore as there is no result */
+				do_again = TRUE;
+			}
+			else
+				td->results = g_slist_append (td->results, res);
+		}
+		else if (res->type == RESULT_TYPE_SIGNAL) {
+			/* run callback now */
+			SignalSpec *spec = res->u.signal.spec;
+			SignalEmissionData *sd = res->u.signal.data;
+
+			spec->callback (wrapper, spec->instance, ((GSignalQuery*)spec)->signal_name,
+					sd->n_param_values, sd->param_values, NULL,
+					spec->data);
+			result_free (res);
+			do_again = TRUE;
+		}
+		else
+			g_assert_not_reached ();
+
+		if (do_again)
+			goto again;
+	}
+	
+	gda_mutex_unlock (wrapper->priv->mutex);
+}
+
+/**
+ * gda_thread_wrapper_fetch_result
+ * @wrapper: a #GdaThreadWrapper object
+ * @may_lock: TRUE if this funct must lock the caller untill a result is available
+ * @id: a place to store the job ID of the function which execution has finished
+ * @error: a place to store errors, for errors which may have occurred during the execution, or %NULL
+ *
+ * Use this method to check if the execution of a function is finished. The function's execution must have
+ * been requested using gda_thread_wrapper_execute().
+ *
+ * If @id is not %NULL, then it will contain the ID of the request if a function's execution has finished, or
+ * %0 otherwise.
+ *
+ * Returns: the pointer returned by the execution, or %NULL if no result is available
+ *
+ * Since: 4.2
+ */
+gpointer
+gda_thread_wrapper_fetch_result (GdaThreadWrapper *wrapper, gboolean may_lock, guint *out_id, GError **error)
+{
+	ThreadData *td;
+	Result *res = NULL;
+	gpointer retval = NULL;
+
+	g_return_val_if_fail (GDA_IS_THREAD_WRAPPER (wrapper), NULL);
+	g_return_val_if_fail (wrapper->priv, NULL);
+
+	gda_mutex_lock (wrapper->priv->mutex);
+	td = g_hash_table_lookup (wrapper->priv->threads_hash, g_thread_self());
+	if (!td) {
+		/* nothing to be done for this thread */
+		gda_mutex_unlock (wrapper->priv->mutex);
+		return NULL;
+	}
+	
+	gda_thread_wrapper_iterate (wrapper, may_lock);
+	if (td->results) {
+		res = RESULT (td->results->data);
+		td->results = g_slist_delete_link (td->results, td->results);
+		if (!(td->results) &&
+		    (td->shared->nb_waiting == 0) &&
+		    (g_async_queue_length (td->from_sub_thread) == 0) &&
+		    !td->signals_list) {
+			/* remove this ThreadData */
+			g_hash_table_remove (wrapper->priv->threads_hash, g_thread_self());
+		}
+	}
+
+	if (res) {
+		g_assert (res->type == RESULT_TYPE_EXECUTE);
+		if (out_id)
+			*out_id = res->job->job_id;
+		if (res->u.exe.error) {
+			g_propagate_error (error, res->u.exe.error);
+			res->u.exe.error = NULL;
+		}
+		retval = res->u.exe.result;
+		res->u.exe.result = NULL;
+		result_free (res);
+	}
+
+	gda_mutex_unlock (wrapper->priv->mutex);
+	return retval;
+}
+
+/**
+ * gda_thread_wrapper_get_waiting_size
+ * @wrapper: a #GdaThreadWrapper object
+ *
+ * Use this method to query the number of functions which have been queued to be executed
+ * but which have not yet been executed.
+ *
+ * Returns: the number of jobs not yet executed
+ *
+ * Since: 4.2
+ */
+gint
+gda_thread_wrapper_get_waiting_size (GdaThreadWrapper *wrapper)
+{
+	gint size;
+	ThreadData *td;
+
+	g_return_val_if_fail (GDA_IS_THREAD_WRAPPER (wrapper), 0);
+	g_return_val_if_fail (wrapper->priv, 0);
+
+	gda_mutex_lock (wrapper->priv->mutex);
+	td = g_hash_table_lookup (wrapper->priv->threads_hash, g_thread_self());
+	if (!td) {
+		/* nothing to be done for this thread */
+		gda_mutex_unlock (wrapper->priv->mutex);
+		return 0;
+	}
+	size = td->shared->nb_waiting;
+	gda_mutex_unlock (wrapper->priv->mutex);
+	return size;
+}
+
+/* 
+ * Executed in sub thread (or potentially in other threads, in which case will be ignored)
+ * pushes data into the queue 
+ */
+static void
+sub_thread_closure_marshal (GClosure *closure,
+			    GValue *return_value,
+			    guint n_param_values,
+			    const GValue *param_values,
+			    gpointer invocation_hint,
+			    gpointer marshal_data)
+{
+	SignalSpec *sigspec = (SignalSpec *) closure->data;
+
+	/* if the signal is not emitted from the sub thread then don't do anything */
+	if (g_thread_self () !=  sigspec->td->shared->wrapper_sub_thread)
+		return;
+
+	/* if we are not currently doing a job, then don't do anything */
+	if (!sigspec->td->shared->current_job)
+		return;
+
+	gint i;
+	/*
+	for (i = 1; i < n_param_values; i++) {
+		g_print ("\t%d => %s\n", i, gda_value_stringify (param_values + i));
+	}
+	*/
+	SignalEmissionData *sdata;
+	sdata = g_new0 (SignalEmissionData, 1);
+	sdata->n_param_values = n_param_values - 1;
+	sdata->param_values = g_new0 (GValue, sdata->n_param_values);
+	for (i = 1; i < n_param_values; i++) {
+		const GValue *src;
+		GValue *dest;
+
+		src = param_values + i;
+		dest = sdata->param_values + i - 1;
+
+		if (G_VALUE_TYPE (src) != GDA_TYPE_NULL) {
+			g_value_init (dest, G_VALUE_TYPE (src));
+			g_value_copy (src, dest);
+		}
+	}
+
+	Result *res = g_new0 (Result, 1);
+	g_assert (sigspec->td->shared);
+	res->job = NULL;
+	res->type = RESULT_TYPE_SIGNAL;
+	res->u.signal.spec = sigspec;
+	res->u.signal.data = sdata;
+	g_async_queue_push (sigspec->td->shared->current_job->reply_queue, res);
+}
+
+/**
+ * gda_thread_wrapper_connect_raw
+ * @wrapper: a #GdaThreadWrapper object
+ * @instance: the instance to connect to
+ * @sig_name: a string of the form "signal-name::detail"
+ * @callback: a #GdaThreadWrapperCallback function
+ * @data: data to pass to @callback's calls
+ *
+ * Connects a callbeck function to a signal for a particular object. The difference with g_signal_connect() and
+ * similar functions are:
+ * <itemizedlist>
+ *  <listitem><para>the @callback argument is not a #GCallback function, so the callback signature is not
+ *    dependant on the signal itself</para></listitem>
+ *  <listitem><para>the signal handler must not have to return any value</para></listitem>
+ *  <listitem><para>the @callback function will be called asynchronously, the caller may need to use 
+ *    gda_thread_wrapper_iterate() to get the notification</para></listitem>
+ *  <listitem><para>the @callback function will be called only if the signal has been emitted by @instance 
+ *    while being used in @wrapper's private sub thread (ie. used when @wrapper is executing some functions
+ *    specified by
+ *    gda_thread_wrapper_execute() or gda_thread_wrapper_execute_void() have been used)</para></listitem>
+ * </itemizedlist>
+ *
+ * To disconnect a signal handler, don't use any of the g_signal_handler_*() functions but the
+ * gda_thread_wrapper_disconnect() method.
+ *
+ * Returns: the handler ID
+ *
+ * Since: 4.2
+ */
+gulong
+gda_thread_wrapper_connect_raw (GdaThreadWrapper *wrapper,
+				gpointer instance,
+				const gchar *sig_name,
+				GdaThreadWrapperCallback callback,
+				gpointer data)
+{
+	guint sigid;
+        SignalSpec *sigspec;
+	ThreadData *td;
+
+	g_return_val_if_fail (GDA_IS_THREAD_WRAPPER (wrapper), 0);
+	g_return_val_if_fail (wrapper->priv, 0);
+
+	gda_mutex_lock (wrapper->priv->mutex);
+	
+	td = get_thread_data (wrapper);
+
+        sigid = g_signal_lookup (sig_name, /* FIXME: use g_signal_parse_name () */
+				 G_TYPE_FROM_INSTANCE (instance)); 
+        if (sigid == 0) {
+                g_warning (_("Signal does not exist\n"));
+                return 0;
+        }
+
+        sigspec = g_new0 (SignalSpec, 1);
+        g_signal_query (sigid, (GSignalQuery*) sigspec);
+
+	if (((GSignalQuery*) sigspec)->return_type != G_TYPE_NONE) {
+		g_warning (_("Signal to connect to must not have a return value\n"));
+		g_free (sigspec);
+		return 0;
+	}
+        sigspec->instance = instance;
+        sigspec->callback = callback;
+        sigspec->data = data;
+
+	GClosure *cl;
+	cl = g_closure_new_simple (sizeof (GClosure), sigspec);
+	g_closure_set_marshal (cl, (GClosureMarshal) sub_thread_closure_marshal);
+	sigspec->signal_id = g_signal_connect_closure (instance, sig_name, cl, FALSE);
+
+	sigspec->td = td;
+	td->signals_list = g_slist_append (td->signals_list, sigspec);
+
+	gda_mutex_unlock (wrapper->priv->mutex);
+
+	return sigspec->signal_id;
+}
+
+/**
+ * gda_thread_wrapper_disconnect
+ * @wrapper: a #GdaThreadWrapper object
+ * @id: a handler ID, as returned by gda_thread_wrapper_connect_raw()
+ *
+ * Disconnects the emission of a signal, does the opposite of gda_thread_wrapper_connect_raw()
+ *
+ * Since: 4.2
+ */
+void
+gda_thread_wrapper_disconnect (GdaThreadWrapper *wrapper, gulong id)
+{
+	SignalSpec *sigspec = NULL;
+	ThreadData *td;
+	GSList *list;
+
+	g_return_if_fail (GDA_IS_THREAD_WRAPPER (wrapper));
+	g_return_if_fail (wrapper->priv);
+
+	gda_mutex_lock (wrapper->priv->mutex);
+
+	td = g_hash_table_lookup (wrapper->priv->threads_hash, g_thread_self());
+	if (!td) {
+		g_warning (_("Signal does not exist\n"));
+		gda_mutex_unlock (wrapper->priv->mutex);
+		return;
+	}
+
+	for (list = td->signals_list; list; list = list->next) {
+		if (((SignalSpec*) list->data)->signal_id == id) {
+			sigspec = (SignalSpec*) list->data;
+			break;
+		}
+	}
+
+	if (!sigspec) {
+		g_warning (_("Signal does not exist\n"));
+		gda_mutex_unlock (wrapper->priv->mutex);
+		return;
+	}
+
+	td->signals_list = g_slist_remove (td->signals_list, sigspec);
+	g_signal_handler_disconnect (sigspec->instance, sigspec->signal_id);
+	g_free (sigspec);
+
+	if (!(td->results) &&
+	    (td->shared->nb_waiting == 0) &&
+	    (g_async_queue_length (td->from_sub_thread) == 0) &&
+	    !td->signals_list) {
+		/* remove this ThreadData */
+		g_hash_table_remove (wrapper->priv->threads_hash, g_thread_self());
+	}
+
+	gda_mutex_unlock (wrapper->priv->mutex);
+}

Added: trunk/libgda/thread-wrapper/gda-thread-wrapper.h
==============================================================================
--- (empty file)
+++ trunk/libgda/thread-wrapper/gda-thread-wrapper.h	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,91 @@
+/* GDA library
+ * Copyright (C) 2009 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GDA_THREAD_WRAPPER_H__
+#define __GDA_THREAD_WRAPPER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GDA_TYPE_THREAD_WRAPPER            (gda_thread_wrapper_get_type())
+#define GDA_THREAD_WRAPPER(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, GDA_TYPE_THREAD_WRAPPER, GdaThreadWrapper))
+#define GDA_THREAD_WRAPPER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, GDA_TYPE_THREAD_WRAPPER, GdaThreadWrapperClass))
+#define GDA_IS_THREAD_WRAPPER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE(obj, GDA_TYPE_THREAD_WRAPPER))
+#define GDA_IS_THREAD_WRAPPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GDA_TYPE_THREAD_WRAPPER))
+
+typedef struct _GdaThreadWrapper GdaThreadWrapper;
+typedef struct _GdaThreadWrapperClass GdaThreadWrapperClass;
+typedef struct _GdaThreadWrapperPrivate GdaThreadWrapperPrivate;
+
+/* error reporting */
+extern GQuark gda_thread_wrapper_error_quark (void);
+#define GDA_THREAD_WRAPPER_ERROR gda_thread_wrapper_error_quark ()
+
+typedef enum {
+	GDA_THREAD_WRAPPER_UNKNOWN_ERROR
+} GdaThreadWrapperError;
+
+struct _GdaThreadWrapper {
+	GObject            object;
+	GdaThreadWrapperPrivate *priv;
+};
+
+struct _GdaThreadWrapperClass {
+	GObjectClass       object_class;
+
+	/* Padding for future expansion */
+	void (*_gda_reserved1) (void);
+	void (*_gda_reserved2) (void);
+	void (*_gda_reserved3) (void);
+	void (*_gda_reserved4) (void);
+};
+
+typedef gpointer (*GdaThreadWrapperFunc) (gpointer arg, GError **error);
+typedef void (*GdaThreadWrapperVoidFunc) (gpointer arg, GError **error);
+typedef void (*GdaThreadWrapperCallback) (GdaThreadWrapper *wrapper, gpointer instance, const gchar *signame,
+					  gint n_param_values, const GValue *param_values, gpointer gda_reserved,
+					  gpointer data);
+
+GType                  gda_thread_wrapper_get_type          (void) G_GNUC_CONST;
+GdaThreadWrapper      *gda_thread_wrapper_new               (void);
+
+guint                  gda_thread_wrapper_execute           (GdaThreadWrapper *wrapper, GdaThreadWrapperFunc func,
+							     gpointer arg, GDestroyNotify arg_destroy_func, GError **error);
+guint                  gda_thread_wrapper_execute_void      (GdaThreadWrapper *wrapper, GdaThreadWrapperVoidFunc func,
+							     gpointer arg, GDestroyNotify arg_destroy_func, GError **error);
+void                   gda_thread_wrapper_iterate           (GdaThreadWrapper *wrapper, gboolean may_block);
+gpointer               gda_thread_wrapper_fetch_result      (GdaThreadWrapper *wrapper, gboolean may_lock, 
+							     guint *out_id, GError **error);
+
+gint                   gda_thread_wrapper_get_waiting_size (GdaThreadWrapper *wrapper);
+
+gulong                 gda_thread_wrapper_connect_raw       (GdaThreadWrapper *wrapper,
+							     gpointer instance,
+							     const gchar *sig_name,
+							     GdaThreadWrapperCallback callback,
+							     gpointer data);
+void                   gda_thread_wrapper_disconnect       (GdaThreadWrapper *wrapper, gulong id);
+
+G_END_DECLS
+
+#endif

Added: trunk/libgda/thread-wrapper/thread-wrapper.dia
==============================================================================
Binary file. No diff available.

Modified: trunk/po/POTFILES.in
==============================================================================
--- trunk/po/POTFILES.in	(original)
+++ trunk/po/POTFILES.in	Tue Apr  7 19:10:50 2009
@@ -63,6 +63,8 @@
 libgda/sql-parser/gda-statement-struct-parts.c
 libgda/sql-parser/gda-statement-struct-select.c
 libgda/sql-parser/gda-statement-struct-update.c
+libgda/thread-wrapper/gda-thread-provider.c
+libgda/thread-wrapper/gda-thread-wrapper.c
 libgda-report/DocBook/gda-report-docbook-document.c
 libgda-report/engine/gda-report-engine.c
 libgda-report/gda-report-document.c

Modified: trunk/providers/postgres/gda-postgres-util.c
==============================================================================
--- trunk/providers/postgres/gda-postgres-util.c	(original)
+++ trunk/providers/postgres/gda-postgres-util.c	Tue Apr  7 19:10:50 2009
@@ -104,7 +104,11 @@
         return error_ev;
 }
 
-/* to be used only while initializing a connection */
+/* 
+ * Returns NULL if an error occurred
+ *
+ * WARNING: it does not check that @pconn can safely be used
+ */
 PGresult *
 _gda_postgres_PQexec_wrap (GdaConnection *cnc, PGconn *pconn, const char *query)
 {

Modified: trunk/providers/postgres/gda-postgres.h
==============================================================================
--- trunk/providers/postgres/gda-postgres.h	(original)
+++ trunk/providers/postgres/gda-postgres.h	Tue Apr  7 19:10:50 2009
@@ -51,6 +51,7 @@
 typedef struct {
 	GdaConnection      *cnc;
         PGconn             *pconn;
+	gboolean            pconn_is_busy;
         gint                ntypes;
         GdaPostgresTypeOid *type_data;
         GHashTable         *h_table;

Modified: trunk/tests/multi-threading/Makefile.am
==============================================================================
--- trunk/tests/multi-threading/Makefile.am	(original)
+++ trunk/tests/multi-threading/Makefile.am	Tue Apr  7 19:10:50 2009
@@ -6,8 +6,8 @@
 	-DROOT_DIR=\""$(top_srcdir)"\" \
 	-DTOP_BUILD_DIR=\""$(top_builddir)"\"
 
-TESTS = check_mutex check_parser check_cnc_lock
-check_PROGRAMS = check_mutex check_parser check_cnc_lock
+TESTS = check_mutex check_parser check_cnc_lock check_threaded_cnc check_wrapper
+check_PROGRAMS = check_mutex check_parser check_cnc_lock check_threaded_cnc check_wrapper
 
 check_mutex_SOURCES = check_mutex.c
 check_mutex_LDADD = \
@@ -24,6 +24,16 @@
 	$(top_builddir)/libgda/libgda-4.0.la \
 	$(LIBGDA_LIBS)
 
+check_threaded_cnc_SOURCES = check_threaded_cnc.c common.c common.h
+check_threaded_cnc_LDADD = \
+	$(top_builddir)/libgda/libgda-4.0.la \
+	$(LIBGDA_LIBS)
+
+check_wrapper_SOURCES = check_wrapper.c dummy-object.c dummy-object.h
+check_wrapper_LDADD = \
+	$(top_builddir)/libgda/libgda-4.0.la \
+	$(LIBGDA_LIBS)
+
 EXTRA_DIST = testdb.sql
 
-DISTCLEANFILES = testdb.db
+DISTCLEANFILES = testdb.db storedb.db

Added: trunk/tests/multi-threading/check_threaded_cnc.c
==============================================================================
--- (empty file)
+++ trunk/tests/multi-threading/check_threaded_cnc.c	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,676 @@
+#include <stdio.h>
+#include <string.h>
+#include <libgda/libgda.h>
+#include <sql-parser/gda-sql-parser.h>
+#include <thread-wrapper/gda-thread-recordset.h>
+#include "common.h"
+
+#define DBDIR "."
+
+static void test_cnc_obj (GdaConnection *tcnc);
+static void test_cnc_status (GdaConnection *tcnc, gboolean opened);
+
+static gint test_select (GdaConnection *cnc, GdaConnection *tcnc);
+static gint test_select_cursor (GdaConnection *cnc, GdaConnection *tcnc);
+static gint test_insert (GdaConnection *tcnc);
+static gint test_update (GdaConnection *tcnc);
+static gint test_delete (GdaConnection *tcnc);
+static gint test_meta_store (GdaConnection *cnc);
+static gint test_meta_data (GdaConnection *cnc, GdaConnection *tcnc);
+static gint test_signals (GdaConnection *cnc, GdaConnection *tcnc);
+
+int 
+main (int argc, char** argv)
+{
+	GdaConnection *cnc, *tcnc;
+        GError *error = NULL;
+	gint failures = 0;
+	gint ntests = 0;
+	gchar *fname;
+
+	/* set up test environment */
+        g_setenv ("GDA_TOP_BUILD_DIR", TOP_BUILD_DIR, 0);
+        g_setenv ("GDA_TOP_SRC_DIR", ROOT_DIR, 0);
+        gda_init ();
+
+	/* create test DB */
+	fname = g_build_filename (ROOT_DIR, "tests", "multi-threading", "testdb.sql", NULL);
+        if (!create_sqlite_db (DBDIR, "testdb", fname, &error)) {
+                g_print ("Cannot create test database: %s\n", error && error->message ?
+                         error->message : "no detail");
+                return 1;
+        }
+        g_free (fname);
+
+
+        /* create a connection in threaded mode */
+        cnc = gda_connection_open_from_string ("SQlite", "DB_DIR=.;DB_NAME=testdb", NULL,
+					       GDA_CONNECTION_OPTIONS_NONE, &error);
+        if (!cnc) {
+                g_print ("ERROR opening connection: %s\n",
+                         error && error->message ? error->message : "No detail");
+                return 1;
+        }
+        tcnc = gda_connection_open_from_string ("SQlite", "DB_DIR=.;DB_NAME=testdb", NULL,
+					       GDA_CONNECTION_OPTIONS_THREAD_SAFE, &error);
+        if (!tcnc) {
+                g_print ("ERROR opening connection in thread safe mode: %s\n",
+                         error && error->message ? error->message : "No detail");
+                return 1;
+        }
+
+	test_cnc_obj (tcnc);
+	test_cnc_status (tcnc, TRUE);
+        gda_connection_close (tcnc);
+	test_cnc_status (tcnc, FALSE);
+        gda_connection_open (tcnc, NULL);
+	test_cnc_status (tcnc, TRUE);
+
+	ntests ++;
+        failures += test_select (cnc, tcnc);
+
+	ntests ++;
+        failures += test_select_cursor (cnc, tcnc);
+
+	ntests ++;
+        failures += test_insert (tcnc);
+        failures += test_select (cnc, tcnc);
+        failures += test_select_cursor (cnc, tcnc);
+
+	ntests ++;
+        failures += test_update (tcnc);
+        failures += test_select (cnc, tcnc);
+        failures += test_select_cursor (cnc, tcnc);
+
+	ntests ++;
+        failures += test_delete (tcnc);
+        failures += test_select (cnc, tcnc);
+        failures += test_select_cursor (cnc, tcnc);
+
+	ntests ++;
+        failures += test_meta_data (cnc, tcnc);
+
+	ntests ++;
+        failures += test_meta_store (cnc);
+
+	ntests ++;
+	failures += test_signals (cnc, tcnc);
+
+        /* get rid of connection */
+        g_object_unref (cnc);
+        g_object_unref (tcnc);
+	g_usleep (300000);
+
+	g_print ("TESTS COUNT: %d\n", ntests);
+	g_print ("FAILURES: %d\n", failures);
+  
+	return failures != 0 ? 1 : 0;
+}
+
+static void
+test_cnc_obj (GdaConnection *tcnc)
+{
+	gboolean is_wrapper;
+	g_object_get (G_OBJECT (tcnc), "is-wrapper", &is_wrapper, NULL);
+	if (!is_wrapper) {
+		g_print ("ERROR: connection is not a connection wrapper\n");
+                exit (1);
+	}
+}
+
+static void
+test_cnc_status (GdaConnection *tcnc, gboolean opened)
+{
+	if (gda_connection_is_opened (tcnc) != opened) {
+		g_print ("ERROR: connection is %s, expecting it %s\n",
+                         gda_connection_is_opened (tcnc) ? "Opened" : "Closed",
+                         opened ? "Opened" : "Closed");
+                exit (1);
+	}
+}
+
+static gint
+test_select (GdaConnection *cnc, GdaConnection *tcnc)
+{
+	/* execute statement */
+	gint failures = 0;
+	GdaDataModel *model, *tmodel;
+	
+	model = run_sql_select (cnc, "SELECT * FROM person");
+	tmodel = run_sql_select (tcnc, "SELECT * FROM person");
+	if (! data_models_equal (model, tmodel))
+		failures ++;
+
+	if (strcmp (G_OBJECT_TYPE_NAME (tmodel), "GdaThreadRecordset")) {
+		g_print ("ERROR: data model from SELECT's type is '%s' and not 'GdaThreadRecordset'\n",
+                         G_OBJECT_TYPE_NAME (tmodel));
+                exit (1);
+	}
+	if (gda_data_select_get_connection (GDA_DATA_SELECT (tmodel)) != tcnc) {
+		g_print ("ERROR: %s() returned wrong result\n", "gda_data_select_get_connection");
+		failures ++;
+	}
+
+	GdaDataModelIter *iter;
+	GdaDataModelIter *titer;
+	iter = gda_data_model_create_iter (model);
+	titer = gda_data_model_create_iter (tmodel);
+	if (!titer) {
+		g_print ("ERROR: %s() returned NULL\n", "gda_data_model_create_iter");
+		failures ++;
+	}
+	else {
+		GdaDataModel *m1, *m2;
+		g_object_get (G_OBJECT (titer), "data-model", &m1, "forced-model", &m2, NULL);
+		if (m1 != tmodel) {
+			g_print ("ERROR: \"data-model\" property of created iterator is wrong\n");
+			failures ++;
+		}
+		if (m2 != tmodel) {
+			g_print ("ERROR: \"forced-model\" property of created iterator is wrong\n");
+			failures ++;
+		}
+		g_object_unref (m1);
+		g_object_unref (m2);
+
+		/* check iter's contents */
+		gint ncols;
+		ncols = g_slist_length (GDA_SET (titer)->holders);
+		if (ncols != g_slist_length (GDA_SET (iter)->holders)) {
+			g_print ("ERROR: threaded iterator is at the wrong number of columns: "
+				 "%d when it should be at %d\n",
+				 ncols, g_slist_length (GDA_SET (iter)->holders));
+			failures ++;
+			ncols = -1;
+		}
+		else
+			g_assert (ncols > 0);
+
+		/* forward */
+		if (gda_data_model_iter_is_valid (titer)) {
+			g_print ("ERROR: threaded iterator is valid before any read\n");
+			failures ++;
+		}
+		if (gda_data_model_iter_is_valid (iter)) {
+			g_print ("ERROR: iterator is valid before any read\n");
+			failures ++;
+		}
+		gint row;
+		gboolean iterok;
+		iterok = gda_data_model_iter_move_next (iter);
+		iterok = gda_data_model_iter_move_next (titer) | iterok;
+		for (row = 0; iterok; row++) {
+			if (gda_data_model_iter_get_row (titer) != gda_data_model_iter_get_row (iter)) {
+				g_print ("ERROR: threaded iterator is at the wrong row: %d when it should be at %d\n",
+					 gda_data_model_iter_get_row (titer), gda_data_model_iter_get_row (iter));
+				failures ++;
+				break;
+			}
+			if (ncols > 0) {
+				gint i;
+				for (i = 0; i < ncols; i++) {
+					const GValue *cv, *tcv;
+					cv = gda_data_model_iter_get_value_at (iter, i);
+					tcv = gda_data_model_iter_get_value_at (titer, i);
+					if (gda_value_differ (cv, tcv)) {
+						g_print ("ERROR: values in iterators differ at line %d, colunm %d\n",
+							 row, i);
+						failures ++;
+					}
+					//g_print ("%d,%d: %s\n", row, i, gda_value_stringify (tcv));
+				}
+			}
+			gboolean v, tv;
+			v = gda_data_model_iter_move_next (iter);
+			tv = gda_data_model_iter_move_next (titer);
+			if (!v || !tv)
+				break;
+		}
+		if (gda_data_model_iter_is_valid (titer)) {
+			g_print ("ERROR: threaded iterator is still valid\n");
+			failures ++;
+		}
+		if (gda_data_model_iter_is_valid (iter)) {
+			g_print ("ERROR: iterator is still valid\n");
+			failures ++;
+		}
+
+		/* backward */
+		for (; row >= 0; row--) {
+			iterok = gda_data_model_iter_move_next (iter);
+			iterok = gda_data_model_iter_move_next (titer) | iterok;
+			if (!iterok)
+				break;
+		}
+		for (row = 0; iterok; row++) {
+			if (gda_data_model_iter_get_row (titer) != gda_data_model_iter_get_row (iter)) {
+				g_print ("ERROR: threaded iterator is at the wrong row: %d when it should be at %d\n",
+					 gda_data_model_iter_get_row (titer), gda_data_model_iter_get_row (iter));
+				failures ++;
+				break;
+			}
+			if (ncols > 0) {
+				gint i;
+				for (i = 0; i < ncols; i++) {
+					const GValue *cv, *tcv;
+					cv = gda_data_model_iter_get_value_at (iter, i);
+					tcv = gda_data_model_iter_get_value_at (titer, i);
+					if (gda_value_differ (cv, tcv)) {
+						g_print ("ERROR: values in iterators differ at line %d, colunm %d\n",
+							 row, i);
+						failures ++;
+					}
+					//g_print ("%d,%d: %s\n", row, i, gda_value_stringify (tcv));
+				}
+			}
+			gboolean v, tv;
+			v = gda_data_model_iter_move_prev (iter);
+			tv = gda_data_model_iter_move_prev (titer);
+			if (!v || !tv)
+				break;
+		}
+		if (gda_data_model_iter_is_valid (titer)) {
+			g_print ("ERROR: threaded iterator is still valid\n");
+			failures ++;
+		}
+		if (gda_data_model_iter_is_valid (iter)) {
+			g_print ("ERROR: iterator is still valid\n");
+			failures ++;
+		}
+
+		g_object_unref (iter);
+		g_object_unref (titer);
+	}
+
+	g_object_unref (model);
+	g_object_unref (tmodel);
+
+	return failures;
+}
+
+static gint
+test_select_cursor (GdaConnection *cnc, GdaConnection *tcnc)
+{
+	/* execute statement */
+	gint failures = 0;
+	GdaDataModel *model, *tmodel;
+	
+	model = run_sql_select_cursor (cnc, "SELECT * FROM person");
+	tmodel = run_sql_select_cursor (tcnc, "SELECT * FROM person");
+
+	if (gda_data_model_get_access_flags (model) !=
+	    gda_data_model_get_access_flags (tmodel)) {
+		g_print ("ERROR: data models' access flags differ: %d and %d for threaded model\n",
+                         gda_data_model_get_access_flags (model),
+			 gda_data_model_get_access_flags (tmodel));
+		failures ++;
+		return failures;
+	}
+
+	if (strcmp (G_OBJECT_TYPE_NAME (tmodel), "GdaThreadRecordset")) {
+		g_print ("ERROR: data model from SELECT's type is '%s' and not 'GdaThreadRecordset'\n",
+                         G_OBJECT_TYPE_NAME (tmodel));
+                exit (1);
+	}
+	if (gda_data_select_get_connection (GDA_DATA_SELECT (tmodel)) != tcnc) {
+		g_print ("ERROR: %s() returned wrong result\n", "gda_data_select_get_connection");
+		failures ++;
+	}
+
+	GdaDataModelIter *iter, *titer;
+	iter = gda_data_model_create_iter (model);
+	titer = gda_data_model_create_iter (tmodel);
+	if (!titer) {
+		g_print ("ERROR: %s() returned NULL\n", "gda_data_model_create_iter");
+		failures ++;
+	}
+	else {
+		/* iter's properties */
+		GdaDataModel *m1, *m2;
+		g_object_get (G_OBJECT (titer), "data-model", &m1, "forced-model", &m2, NULL);
+		if (m1 != tmodel) {
+			g_print ("ERROR: \"data-model\" property of created iterator is wrong\n");
+			failures ++;
+		}
+		if (m2 != tmodel) {
+			g_print ("ERROR: \"forced-model\" property of created iterator is wrong\n");
+			failures ++;
+		}
+		g_object_unref (m1);
+		g_object_unref (m2);
+
+		/* check iter's contents */
+		gint ncols;
+		ncols = g_slist_length (GDA_SET (titer)->holders);
+		if (ncols != g_slist_length (GDA_SET (iter)->holders)) {
+			g_print ("ERROR: threaded iterator is at the wrong number of columns: "
+				 "%d when it should be at %d\n",
+				 ncols, g_slist_length (GDA_SET (iter)->holders));
+			failures ++;
+			ncols = -1;
+		}
+		else
+			g_assert (ncols > 0);
+
+
+		if (gda_data_model_iter_is_valid (titer)) {
+			g_print ("ERROR: threaded iterator is valid before any read\n");
+			failures ++;
+		}
+		if (gda_data_model_iter_is_valid (iter)) {
+			g_print ("ERROR: iterator is valid before any read\n");
+			failures ++;
+		}
+		gint row;
+		gda_data_model_iter_move_next (iter);
+		gda_data_model_iter_move_next (titer);
+		for (row = 0; ; row++) {
+			if (gda_data_model_iter_get_row (titer) != gda_data_model_iter_get_row (iter)) {
+				g_print ("ERROR: threaded iterator is at the wrong row: %d when it should be at %d\n",
+					 gda_data_model_iter_get_row (titer), gda_data_model_iter_get_row (iter));
+				failures ++;
+				break;
+			}
+			if (ncols > 0) {
+				gint i;
+				for (i = 0; i < ncols; i++) {
+					const GValue *cv, *tcv;
+					cv = gda_data_model_iter_get_value_at (iter, i);
+					tcv = gda_data_model_iter_get_value_at (titer, i);
+					if (gda_value_differ (cv, tcv)) {
+						g_print ("ERROR: values in iterators differ at line %d, colunm %d\n",
+							 row, i);
+						failures ++;
+					}
+					//g_print ("%d,%d: %s\n", row, i, gda_value_stringify (tcv));
+				}
+			}
+			gboolean v, tv;
+			v = gda_data_model_iter_move_next (iter);
+			tv = gda_data_model_iter_move_next (titer);
+			if (!v || !tv)
+				break;
+		}
+		if (gda_data_model_iter_is_valid (titer)) {
+			g_print ("ERROR: threaded iterator is still valid\n");
+			failures ++;
+		}
+		if (gda_data_model_iter_is_valid (iter)) {
+			g_print ("ERROR: iterator is still valid\n");
+			failures ++;
+		}
+
+		g_object_unref (iter);
+		g_object_unref (titer);
+	}	
+
+	g_object_unref (model);
+	g_object_unref (tmodel);
+
+	return failures;
+}
+
+static gint
+test_insert (GdaConnection *tcnc)
+{
+	gint failures = 0;
+	if (!run_sql_non_select (tcnc, "INSERT INTO person (name, age) VALUES ('Alice', 12)")) {
+		g_print ("ERROR: can't INSERT into threaded connection\n");
+			failures ++;
+	}
+	return failures;
+}
+
+static gint
+test_update (GdaConnection *tcnc)
+{
+	gint failures = 0;
+	if (!run_sql_non_select (tcnc, "UPDATE person set name='Alf' where name='Grat'")) {
+		g_print ("ERROR: can't UPDATE threaded connection\n");
+			failures ++;
+	}
+	return failures;
+}
+
+static gint
+test_delete (GdaConnection *tcnc)
+{
+	gint failures = 0;
+	if (!run_sql_non_select (tcnc, "DELETE FROM person WHERE name='Alf'")) {
+		g_print ("ERROR: can't DELETE from threaded connection\n");
+			failures ++;
+	}
+	return failures;
+}
+
+static gint
+test_meta_store (GdaConnection *cnc)
+{
+	GdaMetaStore *store;
+	GdaConnection *tcnc;
+	GError *error = NULL;
+
+	g_print ("=== Starting test where threaded connection is used internally by a meta store\n");
+	tcnc = gda_connection_open_from_string ("SQlite", "DB_DIR=.;DB_NAME=storedb", NULL,
+						GDA_CONNECTION_OPTIONS_THREAD_SAFE, &error);
+        if (!tcnc) {
+                g_print ("ERROR opening connection in thread safe mode: %s\n",
+                         error && error->message ? error->message : "No detail");
+                return 1;
+        }
+
+	store = GDA_META_STORE (g_object_new (GDA_TYPE_META_STORE, "cnc", tcnc, NULL));
+	g_object_unref (tcnc);
+	g_object_set (G_OBJECT (cnc), "meta-store", store, NULL);
+	g_object_unref (store);
+
+	if (!gda_connection_update_meta_store (cnc, NULL, &error)) {
+		g_print ("ERROR in gda_connection_update_meta_store(): %s\n",
+                         error && error->message ? error->message : "No detail");
+                return 1;
+	}
+
+	GdaDataModel *model;
+	model = gda_meta_store_extract (store, "SELECT * FROM _tables", NULL);
+	if (gda_data_model_get_n_rows (model) != 1) {
+		g_print ("ERROR in gda_connection_update_meta_store(): the _tables table "
+			 "should have exactly 1 row\n");
+		g_object_unref (model);
+		return 1;
+	}
+	g_object_unref (model);
+
+	return 0;
+}
+
+static gint
+compare_meta_data (GdaMetaStore *store1, GdaMetaStruct *mstruct1, 
+		   GdaMetaStore *store2, GdaMetaStruct *mstruct2)
+{
+	GSList *all_dbo_list1, *all_dbo_list2, *list;
+
+	all_dbo_list1 = gda_meta_struct_get_all_db_objects (mstruct1);
+	all_dbo_list2 = gda_meta_struct_get_all_db_objects (mstruct2);
+
+	for (list = all_dbo_list1; list; list = list->next) {
+		GdaMetaDbObject *dbo1, *dbo2;
+		GValue *v1, *v2, *v3;
+		dbo1 = GDA_META_DB_OBJECT (list->data);
+
+		g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), dbo1->obj_catalog);
+		g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)), dbo1->obj_schema);
+		g_value_set_string ((v3 = gda_value_new (G_TYPE_STRING)), dbo1->obj_name);
+		dbo2 = gda_meta_struct_get_db_object (mstruct2, v1, v2, v3);
+		gda_value_free (v1);
+		gda_value_free (v2);
+		gda_value_free (v3);
+		if (!dbo2) {
+			g_print ("Error: Could not find object %s.%s.%s\n",
+				 dbo1->obj_catalog,
+				 dbo1->obj_schema,
+				 dbo1->obj_name);
+			return 1;
+		}
+
+		if (dbo1->obj_type != GDA_META_DB_TABLE)
+			continue;
+
+		g_print ("Checking meta store's table: %s\n", dbo1->obj_name);
+
+		GdaDataModel *m1, *m2;
+		gchar *str;
+		str = g_strdup_printf ("SELECT * FROM %s", dbo1->obj_name);
+		m1 = gda_meta_store_extract (store1, str, NULL);
+		m2 = gda_meta_store_extract (store2, str, NULL);
+		g_free (str);
+		
+		GdaDataComparator *cmp;
+		GError *error = NULL;
+		cmp = (GdaDataComparator*) gda_data_comparator_new (m1, m2);
+		g_object_unref (m1);
+		g_object_unref (m2);
+		if (! gda_data_comparator_compute_diff (cmp, &error)) {
+			g_print ("Can't compute data model differences for %s.%s.%s: %s\n", 
+				 dbo1->obj_catalog,
+				 dbo1->obj_schema,
+				 dbo1->obj_name,
+				 error && error->message ? error->message : "No detail");
+			g_object_unref (cmp);
+			return 1;
+		}
+		if (gda_data_comparator_get_n_diffs (cmp) != 0) {
+			g_print ("There are some data model differences!\n");
+			g_object_unref (cmp);
+			return 1;
+		}
+		g_object_unref (cmp);
+	}
+	
+	g_slist_free (all_dbo_list1);
+	g_slist_free (all_dbo_list2);
+
+	return 0;
+}
+
+static gint
+test_meta_data (GdaConnection *cnc, GdaConnection *tcnc)
+{
+	GError *error = NULL;
+
+	g_print ("=== Starting test where updating the threaded connection's meta data\n");
+	if (!gda_connection_update_meta_store (tcnc, NULL, &error)) {
+		g_print ("ERROR in gda_connection_update_meta_store() applied to threaded connection: %s\n",
+                         error && error->message ? error->message : "No detail");
+                return 1;
+	}
+	if (!gda_connection_update_meta_store (cnc, NULL, &error)) {
+		g_print ("ERROR in gda_connection_update_meta_store() applied to non threaded connection: %s\n",
+                         error && error->message ? error->message : "No detail");
+                return 1;
+	}
+
+	GdaMetaStruct *mstruct, *tmstruct;
+	mstruct = gda_meta_store_schema_get_structure (gda_connection_get_meta_store (cnc), NULL);
+	tmstruct = gda_meta_store_schema_get_structure (gda_connection_get_meta_store (tcnc), NULL);
+	g_assert (mstruct);
+	g_assert (tmstruct);
+	
+	return compare_meta_data (gda_connection_get_meta_store (cnc), mstruct, 
+				  gda_connection_get_meta_store (tcnc), tmstruct);
+}
+
+
+static void test_sig_error_cb (GdaConnection *cnc, GdaConnectionEvent *error, GdaConnectionEvent **ev);
+static void test_sig_bool_cb (GdaConnection *cnc, gboolean *out_called);
+static gint
+test_signals (GdaConnection *cnc, GdaConnection *tcnc)
+{
+	gint failures = 0;
+	
+	/* test the "error" signal */
+	GdaConnectionEvent *ev = NULL, *tev = NULL;
+	g_signal_connect (G_OBJECT (cnc), "error",
+			  G_CALLBACK (test_sig_error_cb), &ev);
+	g_signal_connect (G_OBJECT (tcnc), "error",
+			  G_CALLBACK (test_sig_error_cb), &tev);
+	run_sql_non_select (cnc, "DELETE FROM person WHERE name = ##name::string");
+	run_sql_non_select (tcnc, "DELETE FROM person WHERE name = ##name::string");
+	
+	g_assert (ev);
+	if (!tev) {
+		g_print ("ERROR: the threaded connection does not emit the \"error\" signal\n");
+		return 1;
+	}
+
+	/* test the "conn_to_close" and "conn-closed" signal */
+	gboolean called = FALSE, called2 = FALSE;
+	g_signal_connect (G_OBJECT (tcnc), "conn-to-close",
+			  G_CALLBACK (test_sig_bool_cb), &called);
+	g_signal_connect (G_OBJECT (tcnc), "conn-closed",
+			  G_CALLBACK (test_sig_bool_cb), &called2);
+	gda_connection_close (tcnc);
+	if (!called) {
+		g_print ("ERROR: the threaded connection does not emit the \"conn-to-close\" signal\n");
+		return 1;
+	}
+	if (!called2) {
+		g_print ("ERROR: the threaded connection does not emit the \"conn-closed\" signal\n");
+		return 1;
+	}
+	g_signal_handlers_disconnect_by_func (G_OBJECT (tcnc),
+					      G_CALLBACK (test_sig_bool_cb), &called);
+	g_signal_handlers_disconnect_by_func (G_OBJECT (tcnc),
+					      G_CALLBACK (test_sig_bool_cb), &called2);
+
+	/* test the "conn-opened" signal */
+	called = FALSE;
+	g_signal_connect (G_OBJECT (tcnc), "conn-opened",
+			  G_CALLBACK (test_sig_bool_cb), &called);
+	gda_connection_open (tcnc, NULL);
+	g_assert (gda_connection_is_opened (tcnc));
+	if (!called) {
+		g_print ("ERROR: the threaded connection does not emit the \"conn-opened\" signal\n");
+		return 1;
+	}
+	g_signal_handlers_disconnect_by_func (G_OBJECT (tcnc),
+					      G_CALLBACK (test_sig_bool_cb), &called);
+
+	/* test the "transaction-status-changed" signal */
+	called = FALSE;
+	g_signal_connect (G_OBJECT (tcnc), "transaction-status-changed",
+			  G_CALLBACK (test_sig_bool_cb), &called);
+	g_assert (gda_connection_begin_transaction (tcnc, NULL, GDA_TRANSACTION_ISOLATION_UNKNOWN, NULL));
+	if (!called) {
+		g_print ("ERROR: the threaded connection does not emit the \"transaction-status-changed\" signal\n");
+		return 1;
+	}
+	g_signal_handlers_disconnect_by_func (G_OBJECT (tcnc),
+					      G_CALLBACK (test_sig_bool_cb), &called);
+
+	/* test the "dsn_changed" signal */
+	called = FALSE;
+	gda_connection_close (tcnc);
+	g_signal_connect (G_OBJECT (tcnc), "dsn-changed",
+			  G_CALLBACK (test_sig_bool_cb), &called);
+	g_object_set (G_OBJECT (tcnc), "dsn", "SalesTest", NULL);
+	if (!called) {
+		g_print ("ERROR: the threaded connection does not emit the \"dsn-changed\" signal\n");
+		return 1;
+	}
+	g_signal_handlers_disconnect_by_func (G_OBJECT (tcnc),
+					      G_CALLBACK (test_sig_bool_cb), &called);
+
+	return failures;
+}
+
+static void
+test_sig_error_cb (GdaConnection *cnc, GdaConnectionEvent *error, GdaConnectionEvent **ev)
+{
+	*ev = error;
+}
+
+static void
+test_sig_bool_cb (GdaConnection *cnc, gboolean *out_called)
+{
+	*out_called = TRUE;
+}

Added: trunk/tests/multi-threading/check_wrapper.c
==============================================================================
--- (empty file)
+++ trunk/tests/multi-threading/check_wrapper.c	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,650 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <libgda/libgda.h>
+#include <thread-wrapper/gda-thread-wrapper.h>
+#include "dummy-object.h"
+#include <stdarg.h>
+
+#define NTHREADS 10
+#define NCALLS 10
+#define PRINT_CALLS
+#undef PRINT_CALLS
+
+static gint test1 (void);
+static gint test2 (void);
+
+GdaThreadWrapper *wrapper; /* global object */
+
+int 
+main (int argc, char** argv)
+{
+	gint failures = 0;
+
+	gda_init ();
+	
+	failures += test1 ();
+	failures += test2 ();
+
+	g_print ("Threads COUNT: %d, CALLS per thread:%d\n", NTHREADS, NCALLS*2 + 1);
+	g_print ("FAILURES: %d\n", failures);
+  
+	return failures != 0 ? 1 : 0;
+}
+
+
+
+/*
+ * This test creates a single GdaThreadWrapper and runs NTHREADS threads which use the
+ * same GdaThreadWrapper object (each thread executes 2*NCALLS function calls plus a random
+ * number of function calls which return void).
+ *
+ * No signal is handled in this test
+ */
+static gpointer main_thread_func (gpointer int_id);
+static gpointer exec_in_wrapped_thread (gpointer int_in, GError **error);
+static gchar *exec_in_wrapped_thread2 (gchar *str, GError **error);
+static void exec_in_wrapped_thread_v (gchar *str, GError **error);
+static gint
+test1 (void)
+{
+	gint failures = 0;
+	gint i;
+	GSList *threads = NULL;
+
+	wrapper = gda_thread_wrapper_new ();
+
+	/* use GdaThreadWrapper from several threads */
+	for (i = 0; i < NTHREADS; i++) {
+		GThread *th;
+		th = g_thread_create (main_thread_func, GINT_TO_POINTER (i), TRUE, NULL);
+		if (!th) {
+			g_print ("Can't create thread %d (not part of the test)\n", i);
+			exit (1);
+		}
+		threads = g_slist_prepend (threads, th);
+	}
+
+	/* join the threads */
+	GSList *list;
+	for (list = threads; list; list = list->next)
+		failures += GPOINTER_TO_INT (g_thread_join ((GThread*) list->data));
+	g_slist_free (threads);
+
+	g_object_unref (wrapper);
+	return failures;
+}
+
+static gpointer
+exec_in_wrapped_thread (gpointer int_in, GError **error)
+{
+	g_print ("function_1 (%d)\n", GPOINTER_TO_INT (int_in));
+	return int_in;
+}
+
+static gchar *
+exec_in_wrapped_thread2 (gchar *str, GError **error)
+{
+	g_print ("function_2 (%s)\n", str);
+	return g_strdup (str);
+}
+
+static void
+exec_in_wrapped_thread_v (gchar *str, GError **error)
+{
+	g_print ("function_VOID (%s)\n", str);
+}
+
+static void
+free_arg (gchar *str)
+{
+	g_print ("\tFreeing: %s\n", str);
+	g_free (str);
+}
+
+static gpointer
+main_thread_func (gpointer int_id)
+{
+	gint i;
+	guint *ids;
+	gchar *estr;
+
+	gint total_jobs = 0;
+	gint nfailed = 0;
+
+	g_print ("NEW thread %p <=> %d\n", g_thread_self(), GPOINTER_TO_INT (int_id));
+
+	ids = g_new0 (guint, 2*NCALLS);
+	for (i = 0; i < NCALLS; i++) {
+		guint id;
+		GError *error = NULL;
+
+		/* func1 */
+		id = gda_thread_wrapper_execute (wrapper, (GdaThreadWrapperFunc) exec_in_wrapped_thread,
+						 GINT_TO_POINTER (i + GPOINTER_TO_INT (int_id) * 1000), NULL, &error);
+		if (id == 0) {
+			g_print ("Error in %s() for thread %d: %s\n", __FUNCTION__, GPOINTER_TO_INT (int_id),
+				 error && error->message ? error->message : "No detail");
+			if (error)
+				g_error_free (error);
+			return GINT_TO_POINTER (1);
+		}
+		ids[2*i] = id;
+#ifdef PRINT_CALLS
+		g_print ("--> Thread %d jobID %u arg %d\n", GPOINTER_TO_INT (int_id), id, i + GPOINTER_TO_INT (int_id) * 1000);
+#endif
+		if (rand () >= RAND_MAX / 2.)
+			g_thread_yield ();
+
+
+		if (rand () >= RAND_MAX / 2.) {
+			id = gda_thread_wrapper_execute_void (wrapper, (GdaThreadWrapperVoidFunc) exec_in_wrapped_thread_v,
+							      g_strdup_printf ("perturbator %d.%d", GPOINTER_TO_INT (int_id), i), 
+							      (GDestroyNotify) free_arg, &error);
+			if (id == 0) {
+				g_print ("Error in %s() for thread %d: %s\n", __FUNCTION__, GPOINTER_TO_INT (int_id),
+					 error && error->message ? error->message : "No detail");
+				if (error)
+					g_error_free (error);
+				return GINT_TO_POINTER (1);
+			}
+			total_jobs ++;
+		}
+		if (rand () >= RAND_MAX / 2.)
+			g_thread_yield ();
+
+		/* func2 */
+		id = gda_thread_wrapper_execute (wrapper, (GdaThreadWrapperFunc) exec_in_wrapped_thread2,
+						 g_strdup_printf ("Hello %d.%d", GPOINTER_TO_INT (int_id), i), 
+						 (GDestroyNotify) free_arg, &error);
+		if (id == 0) {
+			g_print ("Error in %s() for thread %d: %s\n", __FUNCTION__, GPOINTER_TO_INT (int_id),
+				 error && error->message ? error->message : "No detail");
+			if (error)
+				g_error_free (error);
+			return GINT_TO_POINTER (1);
+		}
+		ids[2*i+1] = id;
+		estr = g_strdup_printf ("Hello %d.%d", GPOINTER_TO_INT (int_id), i);
+#ifdef PRINT_CALLS
+		g_print ("--> Thread %d jobID %u arg %s\n", GPOINTER_TO_INT (int_id), id, estr);
+#endif
+		g_free (estr);
+		if (rand () >= RAND_MAX / 2.)
+			g_thread_yield ();
+	}
+	total_jobs += 2*NCALLS;
+	g_thread_yield ();
+
+	/* pick up results */
+	for (i = 0; i < NCALLS; i++) {
+		guint id;
+		gpointer res;
+		res = gda_thread_wrapper_fetch_result (wrapper, TRUE, &id, NULL);
+		if (GPOINTER_TO_INT (res) != i + GPOINTER_TO_INT (int_id) * 1000) {
+			g_print ("Error in %s() for thread %d: sub thread's exec result is wrong\n",
+				 __FUNCTION__, GPOINTER_TO_INT (int_id));
+			nfailed ++;
+		}
+		if (id != ids[2*i]) {
+			g_print ("Error in %s() for thread %d: sub thread's exec result is for wrong ID\n",
+				 __FUNCTION__, GPOINTER_TO_INT (int_id));
+			nfailed ++;
+		}
+#ifdef PRINT_CALLS
+		g_print ("<-- Thread %d jobID %u arg %d\n", GPOINTER_TO_INT (int_id), id, i + GPOINTER_TO_INT (int_id) * 1000);
+#endif
+		if (rand () >= RAND_MAX / 2.)
+			g_thread_yield ();
+
+		gchar *str;
+		estr = g_strdup_printf ("Hello %d.%d", GPOINTER_TO_INT (int_id), i);
+		str = gda_thread_wrapper_fetch_result (wrapper, TRUE, &id, NULL);
+		if (!str || strcmp (str, estr)) {
+			g_print ("Error in %s() for thread %d: sub thread's exec result is wrong: got %s, exp: %s\n",
+				 __FUNCTION__, GPOINTER_TO_INT (int_id), str, estr);
+			nfailed ++;
+		}
+		g_free (estr);
+		if (id != ids[2*i+1]) {
+			g_print ("Error in %s() for thread %d: sub thread's exec result is for wrong ID\n",
+				 __FUNCTION__, GPOINTER_TO_INT (int_id));
+			nfailed ++;
+		}
+#ifdef PRINT_CALLS
+		g_print ("<-- Thread %d jobID %u arg %s\n", GPOINTER_TO_INT (int_id), id, str);
+#endif
+		g_free (str);
+		if (rand () >= RAND_MAX / 2.)
+			g_thread_yield ();
+	}
+	g_free (ids);
+
+	return GINT_TO_POINTER (nfailed);
+}
+
+
+/*
+ * This test creates a single #GdaThreadWrapper object and a single dummy object (DummyObject) which is
+ * used to emit signals by functions executed in sub threads.
+ */
+typedef struct {
+	DummyObject *dummy;
+	GSList      *signals_sent; /* list of TestSignal structures */
+} t2ExecData;
+static gpointer t2_main_thread_func (DummyObject *dummy);
+static gpointer t2_exec_in_wrapped_thread (t2ExecData *data, GError **error);
+static void t2_exec_in_wrapped_thread_v (t2ExecData *data, GError **error);
+#define INT_TOKEN 1034
+
+static gint
+test2 (void)
+{
+	gint failures = 0;
+	gint i;
+	GSList *threads = NULL;
+	DummyObject *dummy;
+
+	wrapper = gda_thread_wrapper_new ();
+	dummy = dummy_object_new ();
+
+	/* use GdaThreadWrapper from several threads */
+	for (i = 0; i < NTHREADS; i++) {
+		GThread *th;
+		th = g_thread_create ((GThreadFunc) t2_main_thread_func, dummy, TRUE, NULL);
+		if (!th) {
+			g_print ("Can't create thread %d (not part of the test)\n", i);
+			exit (1);
+		}
+		threads = g_slist_prepend (threads, th);
+	}
+
+	/* join the threads */
+	GSList *list;
+	for (list = threads; list; list = list->next) {
+		g_signal_emit_by_name (dummy, "sig0", NULL); /* this signal should be ignored */
+		failures += GPOINTER_TO_INT (g_thread_join ((GThread*) list->data));
+	}
+	g_slist_free (threads);
+
+	g_object_unref (wrapper);
+	g_object_unref (dummy);
+
+	return failures;
+}
+
+/*
+ * structure to hold information about either a signal sent or received
+ */
+typedef struct {
+	gchar  *name;
+	guint   n_param_values;
+        GValue *param_values; /* array of GValue structures */
+} TestSignal;
+
+/*
+ * ... is a list of GType and value as native type (there must be exactly @n_param_values pairs)
+ */
+static TestSignal *
+test_signal_new (const gchar *signame, guint n_param_values, ...)
+{
+	TestSignal *ts;
+	va_list ap;
+	guint i;
+
+	ts = g_new0 (TestSignal, 1);
+	ts->name = g_strdup (signame);
+	ts->n_param_values = n_param_values;
+	ts->param_values = g_new0 (GValue, n_param_values);
+
+	va_start (ap, n_param_values);
+	for (i = 0; i < n_param_values; i++) {
+		GValue *value;
+		GType type;
+		type = va_arg (ap, GType);
+
+		value = ts->param_values + i;
+		g_value_init (value, type);
+		if (type == G_TYPE_INT) {
+			gint v;
+			v = va_arg (ap, gint);
+			g_value_set_int (value, v);
+		}
+		else if (type == G_TYPE_STRING) {
+			gchar *v;
+			v = va_arg (ap, gchar *);
+			g_value_set_string (value, v);
+		}
+		else
+			g_assert_not_reached ();
+	}
+	va_end (ap);
+
+	return ts;
+}
+
+/*
+ * Returns: TRUE if lists are equal
+ */
+static gboolean
+compare_signals_lists (GSList *explist, GSList *gotlist)
+{
+	GSList *elist, *glist;
+	for (elist = explist, glist = gotlist;
+	     elist && glist;
+	     elist = elist->next, glist = glist->next) {
+		TestSignal *es = (TestSignal*) elist->data;
+		TestSignal *gs = (TestSignal*) glist->data;
+
+		if (strcmp (es->name, gs->name)) {
+			g_print ("Error: expected signal '%s', got signal '%s'\n", es->name, gs->name);
+			return FALSE;
+		}
+		if (es->n_param_values != gs->n_param_values) {
+			g_print ("Error: expected %d arguments in '%s', got %d\n", es->n_param_values, 
+				 es->name, gs->n_param_values);
+			return FALSE;
+		}
+		gint i;
+		for (i = 0; i < es->n_param_values; i++) {
+			GValue *ev, *gv;
+			ev = es->param_values + i;
+			gv = gs->param_values + i;
+			if (gda_value_differ (ev, gv)) {
+				g_print ("Error: expected value %s in '%s', got %s\n",  
+					 gda_value_stringify (ev), es->name, gda_value_stringify (gv));
+				return FALSE;
+			}
+		}
+		/*g_print ("Checked signal %s\n", es->name);*/
+	}
+
+	if (elist) {
+		g_print ("Error: Some signals have not been received\n");
+		return FALSE;
+	}
+
+	if (glist) {
+		g_print ("Error: Received too many signals\n");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gpointer
+t2_exec_in_wrapped_thread (t2ExecData *data, GError **error)
+{
+#ifdef PRINT_CALLS
+	g_print ("TH %p ** emit sig0 (dummy=>%p)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig0", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig0", 0));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig1 (dummy=>%p, i=>123)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig1", 123, NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig1", 1, G_TYPE_INT, 123));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig0 (dummy=>%p)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig0", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig0", 0));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig2 (dummy=>%p, i=>456 str=>Hello)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig2", 456, "Hello", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig2", 2, G_TYPE_INT, 456, G_TYPE_STRING, "Hello"));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig0 (dummy=>%p)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig0", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig0", 0));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig1 (dummy=>%p, i=>789)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig1", 789, NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig1", 1, G_TYPE_INT, 789));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig2 (dummy=>%p, i=>32 str=>World)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig2", 32, "World", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig2", 2, G_TYPE_INT, 32, G_TYPE_STRING, "World"));
+
+	return GINT_TO_POINTER (INT_TOKEN);
+}
+
+static void
+t2_exec_in_wrapped_thread_v (t2ExecData *data, GError **error)
+{
+#ifdef PRINT_CALLS
+	g_print ("TH %p ** emit sig0 (dummy=>%p)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig0", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig0", 0));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig1 (dummy=>%p, i=>321)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig1", 321, NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig1", 1, G_TYPE_INT, 321));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig0 (dummy=>%p)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig0", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig0", 0));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig2 (dummy=>%p, i=>654 str=>Thread)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig2", 654, "Thread", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig2", 2, G_TYPE_INT, 654, G_TYPE_STRING, "Thread"));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig0 (dummy=>%p)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig0", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig0", 0));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig1 (dummy=>%p, i=>987)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig1", 987, NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig1", 1, G_TYPE_INT, 987));
+
+#ifdef PRINT_CALLS
+        g_print ("TH %p ** emit sig2 (dummy=>%p, i=>23 str=>Thread)\n", g_thread_self (), data->dummy);
+#endif
+        g_signal_emit_by_name (data->dummy, "sig2", 23, "Thread", NULL);
+	data->signals_sent = g_slist_append (data->signals_sent,
+					     test_signal_new ("sig2", 2, G_TYPE_INT, 23, G_TYPE_STRING, "Thread"));
+}
+
+static void
+wrapper_callback (GdaThreadWrapper *wrapper, gpointer instance, const gchar *signame,
+		  gint n_param_values, const GValue *param_values, gpointer gda_reserved, GSList **sig_list)
+{
+	gint i;
+	/*g_print ("RECEIVED signal '%s' on thread %p\n", signame, g_thread_self ());
+	for (i = 0; i < n_param_values; i++) {
+		gchar *str = gda_value_stringify (param_values  + i);
+		g_print ("\tParam %d: %s\n", i, str);
+		g_free (str);
+	}
+	*/
+
+	TestSignal *ts;
+
+	ts = g_new0 (TestSignal, 1);
+	ts->name = g_strdup (signame);
+	ts->n_param_values = n_param_values;
+	ts->param_values = g_new0 (GValue, n_param_values);
+	for (i = 0; i < n_param_values; i++) {
+		GValue *dest = ts->param_values + i;
+		const GValue *src = param_values + i;
+		if (G_VALUE_TYPE (src) != GDA_TYPE_NULL) {
+			g_value_init (dest, G_VALUE_TYPE (src));
+			g_value_copy (src, dest);
+		}
+	}
+	*sig_list = g_slist_append (*sig_list, ts);
+}
+
+static gpointer
+t2_main_thread_func (DummyObject *dummy)
+{
+	gint i;
+	guint *ids;
+
+	gint total_jobs = 0;
+	gint nfailed = 0;
+
+	gulong sigid[3];
+	t2ExecData edata;
+	GSList *received_list = NULL;
+
+	/*g_print ("NEW test thread: %p\n", g_thread_self());*/
+	sigid[0] = gda_thread_wrapper_connect_raw (wrapper, dummy, "sig0",
+						   (GdaThreadWrapperCallback) wrapper_callback,
+						   &received_list);
+	sigid[1] = gda_thread_wrapper_connect_raw (wrapper, dummy, "sig1",
+						   (GdaThreadWrapperCallback) wrapper_callback,
+						   &received_list);
+	sigid[2] = gda_thread_wrapper_connect_raw (wrapper, dummy, "sig2",
+						   (GdaThreadWrapperCallback) wrapper_callback,
+						   &received_list);
+
+	edata.dummy = dummy;
+	edata.signals_sent = NULL;
+
+	ids = g_new0 (guint, NCALLS);
+	for (i = 0; i < NCALLS; i++) {
+		guint id;
+		GError *error = NULL;
+
+		/* func1 */
+		id = gda_thread_wrapper_execute (wrapper,
+						 (GdaThreadWrapperFunc) t2_exec_in_wrapped_thread,
+						 &edata, NULL, &error);
+		if (id == 0) {
+			g_print ("Error in %s() for thread %p: %s\n", __FUNCTION__, g_thread_self (),
+				 error && error->message ? error->message : "No detail");
+			if (error)
+				g_error_free (error);
+			return GINT_TO_POINTER (1);
+		}
+		ids[i] = id;
+#ifdef PRINT_CALLS
+		g_print ("--> Thread %p jobID %u\n", g_thread_self (), id);
+#endif
+		if (rand () >= RAND_MAX / 2.)
+			g_thread_yield ();
+
+
+		if (rand () >= RAND_MAX / 2.) {
+			id = gda_thread_wrapper_execute_void (wrapper,
+							      (GdaThreadWrapperVoidFunc) t2_exec_in_wrapped_thread_v,
+							      &edata, 
+							      NULL, &error);
+			if (id == 0) {
+				g_print ("Error in %s() for thread %p: %s\n", __FUNCTION__, g_thread_self (),
+					 error && error->message ? error->message : "No detail");
+				if (error)
+					g_error_free (error);
+				return GINT_TO_POINTER (1);
+			}
+			total_jobs ++;
+		}
+		if (rand () >= RAND_MAX / 2.)
+			g_thread_yield ();
+	}
+	total_jobs += NCALLS;
+	g_thread_yield ();
+
+	/* pick up results */
+	for (i = 0; i < NCALLS; i++) {
+		guint id;
+		gpointer res;
+
+		g_signal_emit_by_name (dummy, "sig0", NULL); /* this signal should be ignored */
+
+		res = gda_thread_wrapper_fetch_result (wrapper, TRUE, &id, NULL);
+		if (GPOINTER_TO_INT (res) != INT_TOKEN) {
+			g_print ("Error in %s() for thread %p: sub thread's exec result is wrong\n",
+				 __FUNCTION__, g_thread_self ());
+			nfailed ++;
+		}
+		if (id != ids[i]) {
+			g_print ("Error in %s() for thread %p: sub thread's exec result is for wrong ID\n",
+				 __FUNCTION__, g_thread_self ());
+			nfailed ++;
+		}
+#ifdef PRINT_CALLS
+		g_print ("<-- Thread %p jobID %u arg %d\n", g_thread_self (), id, INT_TOKEN);
+#endif
+		if (rand () >= RAND_MAX / 2.)
+			g_thread_yield ();
+		g_signal_emit_by_name (dummy, "sig1", 666, NULL); /* this signal should be ignored */
+	}
+
+	gda_thread_wrapper_iterate (wrapper, FALSE);
+	if (! compare_signals_lists (edata.signals_sent, received_list))
+		nfailed++;
+
+
+#ifdef PRINT_CALLS
+	g_print ("Disconnected signals\n");
+#endif
+	gda_thread_wrapper_disconnect (wrapper, sigid[0]);
+	gda_thread_wrapper_disconnect (wrapper, sigid[1]);
+	gda_thread_wrapper_disconnect (wrapper, sigid[2]);
+
+	g_free (ids);
+
+	/* we don't care about mem leaks here... */
+	received_list = NULL;
+	guint id;
+	GError *error = NULL;
+	id = gda_thread_wrapper_execute_void (wrapper,
+					      (GdaThreadWrapperVoidFunc) t2_exec_in_wrapped_thread_v,
+					      &edata, 
+					      NULL, &error);
+	if (id == 0) {
+		g_print ("Error in %s() for thread %p: %s\n", __FUNCTION__, g_thread_self (),
+			 error && error->message ? error->message : "No detail");
+		if (error)
+			g_error_free (error);
+		return GINT_TO_POINTER (1);
+	}
+	total_jobs ++;
+
+	gda_thread_wrapper_iterate (wrapper, FALSE);
+	g_usleep (100000);
+
+	if (received_list) {
+		g_print ("Error: signals should not be received anymore...\n");
+		nfailed ++;
+	}
+
+	return GINT_TO_POINTER (nfailed);
+}

Modified: trunk/tests/multi-threading/common.c
==============================================================================
--- trunk/tests/multi-threading/common.c	(original)
+++ trunk/tests/multi-threading/common.c	Tue Apr  7 19:10:50 2009
@@ -73,7 +73,7 @@
 	return retval;
 }
 
-void
+gboolean
 run_sql_non_select (GdaConnection *cnc, const gchar *sql)
 {
         GdaStatement *stmt;
@@ -82,13 +82,20 @@
         GdaSqlParser *parser;
 
         parser = gda_connection_create_parser (cnc);
+	if (!parser)
+		parser = gda_sql_parser_new ();
         stmt = gda_sql_parser_parse_string (parser, sql, NULL, NULL);
         g_object_unref (parser);
 
         nrows = gda_connection_statement_execute_non_select (cnc, stmt, NULL, NULL, &error);
         g_object_unref (stmt);
-        if (nrows == -1)
+        if (nrows == -1) {
                 g_print ("NON SELECT error: %s\n", error && error->message ? error->message : "no detail");
+		if (error)
+			g_error_free (error);
+		return FALSE;
+	}
+	return TRUE;
 }
 
 GdaDataModel *
@@ -100,6 +107,9 @@
         GdaSqlParser *parser;
 
         parser = gda_connection_create_parser (cnc);
+	if (!parser)
+		parser = gda_sql_parser_new ();
+
         stmt = gda_sql_parser_parse_string (parser, sql, NULL, NULL);
         g_object_unref (parser);
 
@@ -110,3 +120,46 @@
                          error && error->message ? error->message : "no detail");
         return res;
 }
+
+GdaDataModel *
+run_sql_select_cursor (GdaConnection *cnc, const gchar *sql)
+{
+	GdaStatement *stmt;
+        GError *error = NULL;
+        GdaDataModel *res;
+        GdaSqlParser *parser;
+
+        parser = gda_connection_create_parser (cnc);
+        stmt = gda_sql_parser_parse_string (parser, sql, NULL, NULL);
+        g_object_unref (parser);
+
+        res = gda_connection_statement_execute_select_full (cnc, stmt, NULL, GDA_STATEMENT_MODEL_CURSOR_FORWARD, 
+							    NULL, &error);
+        g_object_unref (stmt);
+        if (!res)
+                g_print ("Could not execute query in cursor mode: %s\n",
+                         error && error->message ? error->message : "no detail");
+        return res;
+}
+
+gboolean
+data_models_equal (GdaDataModel *m1, GdaDataModel *m2)
+{
+	GdaDataComparator *cmp;
+	GError *error = NULL;
+	cmp = (GdaDataComparator*) gda_data_comparator_new (m1, m2);
+	if (! gda_data_comparator_compute_diff (cmp, &error)) {
+		g_print ("Can't compare data models: %s\n", error && error->message ? error->message : "no detail");
+		if (error)
+			g_error_free (error);
+		g_object_unref (cmp);
+		return FALSE;
+	}
+	if (gda_data_comparator_get_n_diffs (cmp) != 0) {
+		g_print ("Data models differ: %d differences\n", gda_data_comparator_get_n_diffs (cmp));
+		g_object_unref (cmp);
+		return FALSE;
+	}
+	g_object_unref (cmp);
+	return TRUE;
+}

Modified: trunk/tests/multi-threading/common.h
==============================================================================
--- trunk/tests/multi-threading/common.h	(original)
+++ trunk/tests/multi-threading/common.h	Tue Apr  7 19:10:50 2009
@@ -5,6 +5,8 @@
 
 gboolean create_sqlite_db (const gchar *dir, const gchar *dbname, const gchar *sqlfile, GError **error);
 GdaDataModel *run_sql_select (GdaConnection *cnc, const gchar *sql);
-void run_sql_non_select (GdaConnection *cnc, const gchar *sql);
+GdaDataModel *run_sql_select_cursor (GdaConnection *cnc, const gchar *sql);
+gboolean run_sql_non_select (GdaConnection *cnc, const gchar *sql);
+gboolean data_models_equal (GdaDataModel *m1, GdaDataModel *m2);
 
 #endif

Added: trunk/tests/multi-threading/dummy-object.c
==============================================================================
--- (empty file)
+++ trunk/tests/multi-threading/dummy-object.c	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,161 @@
+/* dummy-object.c
+ *
+ * Copyright (C) 2009 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "dummy-object.h"
+#include <glib-object.h>
+
+/* 
+ * Main static functions 
+ */
+static void dummy_object_class_init (DummyObjectClass * class);
+static void dummy_object_init (DummyObject *object);
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass  *parent_class = NULL;
+
+/* signals */
+enum
+{
+	SIG0,
+        SIG1,
+        SIG2,
+        LAST_SIGNAL
+};
+
+static gint dummy_object_signals[LAST_SIGNAL] = { 0, 0, 0 };
+
+GType
+dummy_object_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+		static const GTypeInfo info = {
+			sizeof (DummyObjectClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) dummy_object_class_init,
+			NULL,
+			NULL,
+			sizeof (DummyObject),
+			0,
+			(GInstanceInitFunc) dummy_object_init
+		};
+		
+		g_static_mutex_lock (&registering);
+		if (type == 0)
+			type = g_type_register_static (G_TYPE_OBJECT, "DummyObject", &info, 0);
+		g_static_mutex_unlock (&registering);
+	}
+
+	return type;
+}
+
+/* VOID:STRING,INT (gda-marshal.list:39) */
+#define g_marshal_value_peek_string(v)   (char*) g_value_get_string (v)
+#define g_marshal_value_peek_int(v)      g_value_get_int (v)
+static void
+local_marshal_VOID__STRING_INT (GClosure     *closure,
+                               GValue       *return_value G_GNUC_UNUSED,
+                               guint         n_param_values,
+                               const GValue *param_values,
+                               gpointer      invocation_hint G_GNUC_UNUSED,
+                               gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__STRING_INT) (gpointer     data1,
+                                                 gpointer     arg_1,
+                                                 gint         arg_2,
+                                                 gpointer     data2);
+  register GMarshalFunc_VOID__STRING_INT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__STRING_INT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_string (param_values + 1),
+            g_marshal_value_peek_int (param_values + 2),
+            data2);
+}
+
+static void
+dummy_object_class_init (DummyObjectClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	parent_class = g_type_class_peek_parent (class);
+
+	dummy_object_signals[SIG0] =
+                g_signal_new ("sig0",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (DummyObjectClass, sig0),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+	dummy_object_signals[SIG1] =
+                g_signal_new ("sig1",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (DummyObjectClass, sig1),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
+	dummy_object_signals[SIG1] =
+                g_signal_new ("sig2",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (DummyObjectClass, sig1),
+                              NULL, NULL,
+                              local_marshal_VOID__STRING_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_STRING);
+
+        class->sig1 = NULL;
+        class->sig0 = NULL;
+        class->sig2 = NULL;
+}
+
+static void
+dummy_object_init (DummyObject *object)
+{
+}
+
+/**
+ * dummy_object_new
+ *
+ * Creates a new object of type @type
+ *
+ * Returns: a new #DummyObject object
+ */
+DummyObject *
+dummy_object_new (void)
+{
+        return (DummyObject *) g_object_new (DUMMY_TYPE_OBJECT, NULL);
+}

Added: trunk/tests/multi-threading/dummy-object.h
==============================================================================
--- (empty file)
+++ trunk/tests/multi-threading/dummy-object.h	Tue Apr  7 19:10:50 2009
@@ -0,0 +1,58 @@
+/* dummy-object.h
+ *
+ * Copyright (C) 2009 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __DUMMY_OBJECT_H_
+#define __DUMMY_OBJECT_H_
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define DUMMY_TYPE_OBJECT          (dummy_object_get_type())
+#define DUMMY_OBJECT(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, dummy_object_get_type(), DummyObject)
+#define DUMMY_OBJECT_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, dummy_object_get_type (), DummyObjectClass)
+#define DUMMY_IS_OBJECT(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, dummy_object_get_type ())
+
+typedef struct _DummyObject DummyObject;
+typedef struct _DummyObjectClass DummyObjectClass;
+
+/* struct for the object's data */
+struct _DummyObject
+{
+	GObject                 object;
+};
+
+
+/* struct for the object's class */
+struct _DummyObjectClass
+{
+	GObjectClass               parent_class;
+	void                     (*sig0)   (DummyObject *object);
+	void                     (*sig1)   (DummyObject *object, gint i);
+	void                     (*sig2)   (DummyObject *object, gint i, gchar *str);
+};
+
+GType               dummy_object_get_type                (void) G_GNUC_CONST;
+DummyObject        *dummy_object_new                     (void);
+
+G_END_DECLS
+
+#endif

Modified: trunk/tools/web-server.c
==============================================================================
--- trunk/tools/web-server.c	(original)
+++ trunk/tools/web-server.c	Tue Apr  7 19:10:50 2009
@@ -33,6 +33,8 @@
 #include "global.h"
 #include "md5.h"
 
+#include <inttypes.h>
+
 #define MAX_CHALLENGES 10
 #define MAX_AUTH_COOKIES 10
 
@@ -475,10 +477,6 @@
 	return TRUE;
 }
 
-#ifdef G_OS_WIN32
-typedef guint8 uint8_t;
-#endif
-
 #define PAD_LEN 64  /* PAD length */
 #define SIG_LEN 16  /* MD5 digest length */
 /*



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