[libgda] Virtual connections: major rework for better performances and resilience



commit 96d92ca24cca4e037cb082df149a081914c5933e
Author: Vivien Malerba <malerba gnome-db org>
Date:   Thu Mar 1 20:51:12 2012 +0100

    Virtual connections: major rework for better performances and resilience

 libgda/sqlite/virtual/gda-vprovider-data-model.c |  912 +++++++++++++---------
 tests/data-models/check_model_errors.c           |    9 +-
 tests/data-models/check_vcnc.c                   |    2 +
 tests/data-models/check_virtual.c                |   86 ++-
 4 files changed, 646 insertions(+), 363 deletions(-)
---
diff --git a/libgda/sqlite/virtual/gda-vprovider-data-model.c b/libgda/sqlite/virtual/gda-vprovider-data-model.c
index 3266c37..b2ad2a2 100644
--- a/libgda/sqlite/virtual/gda-vprovider-data-model.c
+++ b/libgda/sqlite/virtual/gda-vprovider-data-model.c
@@ -59,6 +59,9 @@ static GObject        *gda_vprovider_data_model_statement_execute (GdaServerProv
 								   gpointer cb_data, GError **error);
 static const gchar   *gda_vprovider_data_model_get_name (GdaServerProvider *provider);
 
+static GValue **create_gvalues_array_from_sqlite3_array (int argc, sqlite3_value **argv);
+
+
 /*
  * GdaVproviderDataModel class implementation
  */
@@ -168,14 +171,10 @@ gda_vprovider_data_model_new (void)
  *  - xSync (table)
  *  - xCommit (table)
  *
- * This does not work well with Libgda because it does not know how to define a unique RowID for
- * a table: it defines the RowID as being the position of the row in the data model, which changes
- * each time the data model used for the virtuel table changes.
- *
- * Moreover, when the cursor has reached the end of the data model, it may not be possible to move
- * it backwards and so the data at any given row is not accessible anymore. The solution to this
- * problem is, each time the xRowid() is called, to copy the row in memory, using the table->rowid_hash
- * hash table.
+ * This does not work well with Libgda because RowID are not pre-defined. To circumvent this problem,
+ * for each data model, the RowID as returned for each cursor is the row number in the data model, plus
+ * an offset defined uniquely for each table:
+ *   RowID (guint 64) = ID (guint32) << 32 + row (guint 31)
  */
 
 #ifdef GDA_DEBUG_VIRTUAL
@@ -184,22 +183,128 @@ gda_vprovider_data_model_new (void)
 #define TRACE(table,cursor)
 #endif
 
-typedef struct {
+typedef struct VirtualTable VirtualTable;
+typedef struct VirtualCursor VirtualCursor;
+typedef struct VirtualFilteredData VirtualFilteredData;
+
+struct VirtualTable {
 	sqlite3_vtab                 base;
 	GdaVconnectionDataModel     *cnc;
 	GdaVConnectionTableData     *td;
+	gboolean                     locked;
 
-	GdaDataModel                *rowid_hash_model; /* data model used to build the rowid_hash's
-							* contents. No ref held there as it's never
-							* dereferenced */
-	GHashTable                  *rowid_hash; /* key = a gint64 rowId, value = a GdaRow */
-} VirtualTable;
+	guint32                      rows_offset;
+	GSList                      *all_data; /* list of #VirtualFilteredData, refs held here */
+};
 
-typedef struct {
+struct VirtualCursor {
 	sqlite3_vtab_cursor      base; /* base.pVtab is a pointer to the sqlite3_vtab virtual table */
-	GdaDataModel            *model; /* in which @iter iterates */
-	GdaDataModelIter        *iter;
-} VirtualCursor;
+	VirtualFilteredData     *data; /* no ref held, ref is held in table->all_data */
+	gint                     row; /* starts at 0 */
+};
+
+/*
+ * This structure holds data to be used by a virtual cursor, for a specific filter
+ */
+struct VirtualFilteredData {
+	/* status */
+	guint8 refcount;
+	gboolean reuseable;
+
+	/* filter */
+	int idxNum;
+	char *idxStr;
+	int argc;
+	GValue **argv;
+
+	/* row numbers offset */
+	guint32 rowid_offset;
+
+	/* data */
+	GdaDataModel *model;
+	GdaDataModelIter *iter; /* not NULL while nrows == -1 */
+	GValueArray  *values;
+	gint          ncols;
+	gint          nrows; /* -1 until known */
+};
+
+static VirtualFilteredData *
+virtual_filtered_data_new (VirtualTable *vtable, GdaDataModel *model,
+			   int idxNum, const char *idxStr, int argc, sqlite3_value **argv)
+{
+	VirtualFilteredData *data;
+
+	g_assert (model);
+	data = g_new0 (VirtualFilteredData, 1);
+	data->refcount = 1;
+	data->reuseable = TRUE;
+	data->idxNum = idxNum;
+	data->idxStr = idxStr ? g_strdup (idxStr) : NULL;
+	data->argc = argc;
+	data->argv = create_gvalues_array_from_sqlite3_array (argc, argv);
+	data->model = g_object_ref (model);
+	if (GDA_IS_DATA_PROXY (model))
+		data->iter = g_object_new (GDA_TYPE_DATA_MODEL_ITER,
+					   "data-model", model, NULL);
+	else
+		data->iter = gda_data_model_create_iter (model);
+	g_object_set (data->iter, "validate-changes", FALSE, NULL);
+
+	gint n;
+	n = gda_data_model_get_n_columns (model);
+	n = (n >= 0) ? n : 1;
+	data->values = g_value_array_new (n);
+	data->ncols = gda_data_model_get_n_columns (model);
+	data->nrows = -1;
+	data->rowid_offset = vtable->rows_offset;
+	vtable->rows_offset ++;
+	
+	return data;
+}
+
+static void
+virtual_filtered_data_free (VirtualFilteredData *data)
+{
+	if (data->argv) {
+		int i;
+		for (i = 0; i < data->argc; i++)
+			gda_value_free (data->argv [i]);
+		g_free (data->argv);
+	}
+	g_free (data->idxStr);
+	g_object_unref (data->model);
+	if (data->iter)
+		g_object_unref (data->iter);
+
+	if (data->values)
+		g_value_array_free (data->values);
+	g_free (data);
+}
+
+static VirtualFilteredData *
+virtual_filtered_data_ref (VirtualFilteredData *data)
+{
+	data->refcount ++;
+	return data;
+}
+
+static void
+virtual_filtered_data_unref (VirtualFilteredData *data)
+{
+	data->refcount --;
+	if (data->refcount == 0)
+		virtual_filtered_data_free (data);
+}
+
+static void
+virtual_cursor_free (VirtualCursor *cursor)
+{
+	if (!cursor)
+		return;
+
+	virtual_filtered_data_unref (cursor->data);
+	g_free (cursor);
+}
 
 /* module creation */
 static int virtualCreate (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr);
@@ -247,48 +352,6 @@ static sqlite3_module Module = {
 	NULL                          /* xRollbackTo */
 };
 
-/*
- * handle data model exceptions and return appropriate code
- */
-static int
-handle_data_model_exception (sqlite3_vtab *pVtab, GdaDataModel *model)
-{
-	GError **exceptions;
-	gint i;
-	exceptions = gda_data_model_get_exceptions (model);
-	if (!exceptions)
-		return SQLITE_OK;
-
-	GError *trunc_error = NULL;
-	GError *fatal_error = NULL;
-	for (i = 0; exceptions [i]; i++) {
-		GError *e;
-		e = exceptions [i];
-		if ((e->domain == GDA_DATA_MODEL_ERROR) &&
-		    (e->code == GDA_DATA_MODEL_TRUNCATED_ERROR))
-			trunc_error = e;
-		else {
-			fatal_error = e;
-			break;
-		}
-	}
-	if (fatal_error || trunc_error) {
-		GError *e;
-		e = fatal_error;
-		if (!e)
-			e = trunc_error;
-		if (pVtab->zErrMsg)
-			SQLITE3_CALL (sqlite3_free) (pVtab->zErrMsg);
-		pVtab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-			("%s", e->message ? e->message : _("No detail"));
-		if (fatal_error)
-			return SQLITE_ERROR;
-		else
-			return SQLITE_IOERR_TRUNCATE;
-	}
-	return SQLITE_OK;
-}
-
 static GdaConnection *
 gda_vprovider_data_model_create_connection (GdaServerProvider *provider)
 {
@@ -605,6 +668,7 @@ virtualCreate (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlit
 	vtable = g_new0 (VirtualTable, 1);
 	vtable->cnc = cnc;
 	vtable->td = td;
+	vtable->rows_offset = 0;
 	*ppVtab = &(vtable->base);
 
 	if (SQLITE3_CALL (sqlite3_declare_vtab) (db, sql->str) != SQLITE_OK) {
@@ -636,9 +700,13 @@ virtualDisconnect (sqlite3_vtab *pVtab)
 
 	TRACE (pVtab, NULL);
 
-	if (vtable->rowid_hash)
-		g_hash_table_destroy (vtable->rowid_hash);
+	if (vtable->all_data) {
+		g_slist_foreach (vtable->all_data, (GFunc) virtual_filtered_data_unref, NULL);
+		g_slist_free (vtable->all_data);
+		vtable->all_data = NULL;
+	}
 	g_free (vtable);
+
 	return SQLITE_OK;
 }
 
@@ -667,15 +735,11 @@ static int
 virtualClose (sqlite3_vtab_cursor *cur)
 {
 	VirtualCursor *cursor = (VirtualCursor*) cur;
+	VirtualTable *vtable = (VirtualTable*) cur->pVtab;
 
 	TRACE (cur->pVtab, cur);
 
-	if (cursor->iter)
-		g_object_unref (cursor->iter);
-	if (cursor->model)
-		g_object_unref (cursor->model);
-
-	g_free (cur);
+	virtual_cursor_free (cursor);
 
 	return SQLITE_OK;
 }
@@ -687,28 +751,161 @@ virtualEof (sqlite3_vtab_cursor *cur)
 
 	TRACE (cur->pVtab, cur);
 
-	if (gda_data_model_iter_is_valid (cursor->iter))
+	if (cursor->data->iter)
 		return FALSE;
-	else
-		return TRUE;
+	else {
+		if (cursor->row >= cursor->data->nrows)
+			return TRUE;
+		else
+			return FALSE;
+	}
+}
+
+/*
+ * handle data model exceptions and return appropriate code
+ */
+static int
+handle_data_model_exception (sqlite3_vtab *pVtab, GdaDataModel *model)
+{
+	GError **exceptions;
+	gint i;
+	exceptions = gda_data_model_get_exceptions (model);
+	if (!exceptions)
+		return SQLITE_OK;
+
+	GError *trunc_error = NULL;
+	GError *fatal_error = NULL;
+	for (i = 0; exceptions [i]; i++) {
+		GError *e;
+		e = exceptions [i];
+		if ((e->domain == GDA_DATA_MODEL_ERROR) &&
+		    (e->code == GDA_DATA_MODEL_TRUNCATED_ERROR))
+			trunc_error = e;
+		else {
+			fatal_error = e;
+			break;
+		}
+	}
+	if (fatal_error || trunc_error) {
+		GError *e;
+		e = fatal_error;
+		if (!e)
+			e = trunc_error;
+		if (pVtab->zErrMsg)
+			SQLITE3_CALL (sqlite3_free) (pVtab->zErrMsg);
+		pVtab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+			("%s", e->message ? e->message : _("No detail"));
+		if (fatal_error)
+			return SQLITE_ERROR;
+		else
+			return SQLITE_IOERR_TRUNCATE;
+	}
+	return SQLITE_OK;
 }
 
 static int
 virtualNext (sqlite3_vtab_cursor *cur)
 {
 	VirtualCursor *cursor = (VirtualCursor*) cur;
+	VirtualFilteredData *data;
 	/*VirtualTable *vtable = (VirtualTable*) cur->pVtab;*/
 
 	TRACE (cur->pVtab, cur);
 
-	if (gda_data_model_get_exceptions (cursor->model))
-		return SQLITE_IOERR_TRUNCATE;
+	data = cursor->data;
+	if (!data) {
+		cur->pVtab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+			(_("Internal SQLite error: no data to iterate on"));
+		return SQLITE_MISUSE;
+	}
+
+	cursor->row ++;
+	if (data->iter && (gda_data_model_iter_get_row (data->iter) < cursor->row)) {
+		/* move to next row */
+		if (gda_data_model_iter_move_next (data->iter)) {
+			int exc_res;
+			exc_res = handle_data_model_exception (cur->pVtab, data->model);
+			if (exc_res != SQLITE_OK)
+				goto onerror;
+
+			/* load data for row */
+			GSList *list;
+			gint count;
+			for (count = 0, list = ((GdaSet*) data->iter)->holders; list;
+			     count++, list = list->next) {
+				GdaHolder *h = (GdaHolder*) list->data;
+				GError *lerror = NULL;
+				if (! gda_holder_is_valid_e (h, &lerror)) {
+					GValue value = {0};
+					g_value_init (&value, G_TYPE_ERROR);
+					g_value_take_boxed (&value, lerror);
+					g_value_array_append (data->values, &value);
+					g_value_unset (&value);
+				}
+				else {
+					const GValue *cvalue;
+					cvalue = gda_holder_get_value (h);
+					g_value_array_append (data->values, cvalue);
+				}
+			}
+			g_assert (count == data->ncols);
+		}
+		else {
+			/* end of data */
+			g_object_unref (data->iter);
+			data->iter = NULL;
+			data->nrows = cursor->row;
+		}
+	}
+	return SQLITE_OK;
+
+ onerror:
+	cursor->row--;
+	g_object_unref (data->iter);
+	data->iter = NULL;
+	data->nrows = cursor->row;
+	return SQLITE_ERROR;
+}
+
+/*
+ * @cursor may be %NULL
+ */
+static const GValue *
+get_data_value (VirtualTable *vtable, VirtualCursor *cursor, gint row, gint64 rowid, gint col, GError **error)
+{
+	VirtualFilteredData *data = NULL;
+	const GValue *value = NULL;
+
+	if ((col < 0) || (col >= vtable->td->n_columns)) {
+		g_set_error (error, 0, 0, _("Column %d out of range (0-%d)"), col, vtable->td->n_columns - 1);
+		return NULL;
+	}
+	if (cursor) {
+		data = cursor->data;
+		g_assert (data);
+	}
+	else {
+		g_assert (row < 0);
+		row = (gint) (rowid & 0xFFFFFFFF);
 
-	if (!gda_data_model_iter_move_next (cursor->iter)) {
-		if (gda_data_model_iter_is_valid (cursor->iter))
-			return SQLITE_IOERR;
+		GSList *list;
+		for (list = vtable->all_data; list; list = list->next) {
+			VirtualFilteredData *vd = (VirtualFilteredData*) list->data;
+			if (vd->rowid_offset == (guint32) (rowid >> 32)) {
+				data = vd;
+				break;
+			}
+		}
 	}
-	return handle_data_model_exception (cur->pVtab, cursor->model);
+
+	if (data)
+		value = g_value_array_get_nth (data->values, row * data->ncols + col);
+
+	if (!value)
+		g_set_error (error, 0, 0,
+			     _("Could not find requested value at row %d and col %d"),
+			     row, col);
+	return value;
 }
 
 static int
@@ -717,101 +914,74 @@ virtualColumn (sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i)
 	VirtualCursor *cursor = (VirtualCursor*) cur;
 
 	TRACE (cur->pVtab, cur);
-
-	GdaHolder *param;
 	
 	if (i == ((VirtualTable*) cur->pVtab)->td->n_columns) {
 		/* private hidden column, which returns the row number */
-		SQLITE3_CALL (sqlite3_result_int) (ctx, gda_data_model_iter_get_row (cursor->iter));
+		SQLITE3_CALL (sqlite3_result_int) (ctx, cursor->row);
 		return SQLITE_OK;
 	}
 
-	param = gda_data_model_iter_get_holder_for_field (cursor->iter, i);
-	if (!param) {
+	if (i >= cursor->data->ncols) {
 		SQLITE3_CALL (sqlite3_result_text) (ctx, _("Column not found"), -1, SQLITE_TRANSIENT);
-		return SQLITE_EMPTY;
+		return SQLITE_MISUSE;
+	}
+	
+	const GValue *value;
+	GError *lerror = NULL;
+	value = get_data_value ((VirtualTable*) cur->pVtab, cursor, cursor->row, 0, i, &lerror);
+	if (! value) {
+		g_hash_table_insert (error_blobs_hash, lerror, GINT_TO_POINTER (1));
+		SQLITE3_CALL (sqlite3_result_blob) (ctx, lerror, sizeof (GError), NULL);
+	}
+	else if (G_VALUE_TYPE (value) == G_TYPE_ERROR) {
+		GError *lerror;
+		lerror = g_value_get_boxed (value);
+		//SQLITE3_CALL (sqlite3_result_error) (ctx, lerror && lerror->message ? lerror->message : _("No detail"), -1);
+		if (lerror)
+			lerror = g_error_copy (lerror);
+		g_hash_table_insert (error_blobs_hash, lerror, GINT_TO_POINTER (1));
+		SQLITE3_CALL (sqlite3_result_blob) (ctx, lerror, sizeof (GError), NULL);
+	}
+	else if (!value || gda_value_is_null (value))
+		SQLITE3_CALL (sqlite3_result_null) (ctx);
+	else  if (G_VALUE_TYPE (value) == G_TYPE_INT) 
+		SQLITE3_CALL (sqlite3_result_int) (ctx, g_value_get_int (value));
+	else if (G_VALUE_TYPE (value) == G_TYPE_INT64) 
+		SQLITE3_CALL (sqlite3_result_int64) (ctx, g_value_get_int64 (value));
+	else if (G_VALUE_TYPE (value) == G_TYPE_DOUBLE) 
+		SQLITE3_CALL (sqlite3_result_double) (ctx, g_value_get_double (value));
+	else if (G_VALUE_TYPE (value) == GDA_TYPE_BLOB) {
+		GdaBlob *blob;
+		GdaBinary *bin;
+		blob = (GdaBlob *) gda_value_get_blob (value);
+		bin = (GdaBinary *) blob;
+		if (blob->op &&
+		    (bin->binary_length != gda_blob_op_get_length (blob->op)))
+			gda_blob_op_read_all (blob->op, blob);
+		SQLITE3_CALL (sqlite3_result_blob) (ctx, blob->data.data, blob->data.binary_length, SQLITE_TRANSIENT);
+	}
+	else if (G_VALUE_TYPE (value) == GDA_TYPE_BINARY) {
+		const GdaBinary *bin;
+		bin = gda_value_get_binary (value);
+		SQLITE3_CALL (sqlite3_result_blob) (ctx, bin->data, bin->binary_length, SQLITE_TRANSIENT);
 	}
 	else {
-		const GValue *value;
-		GError *lerror = NULL;
-		value = gda_holder_get_value (param);
-		if (! gda_holder_is_valid_e (param, &lerror)) {
-			g_hash_table_insert (error_blobs_hash, lerror, GINT_TO_POINTER (1));
-			SQLITE3_CALL (sqlite3_result_blob) (ctx, lerror, sizeof (GError), NULL);
-		}
-		else if (!value || gda_value_is_null (value))
-			SQLITE3_CALL (sqlite3_result_null) (ctx);
-		else  if (G_VALUE_TYPE (value) == G_TYPE_INT) 
-			SQLITE3_CALL (sqlite3_result_int) (ctx, g_value_get_int (value));
-		else if (G_VALUE_TYPE (value) == G_TYPE_INT64) 
-			SQLITE3_CALL (sqlite3_result_int64) (ctx, g_value_get_int64 (value));
-		else if (G_VALUE_TYPE (value) == G_TYPE_DOUBLE) 
-			SQLITE3_CALL (sqlite3_result_double) (ctx, g_value_get_double (value));
-		else if (G_VALUE_TYPE (value) == GDA_TYPE_BLOB) {
-			GdaBlob *blob;
-			GdaBinary *bin;
-			blob = (GdaBlob *) gda_value_get_blob (value);
-			bin = (GdaBinary *) blob;
-			if (blob->op &&
-			    (bin->binary_length != gda_blob_op_get_length (blob->op)))
-				gda_blob_op_read_all (blob->op, blob);
-			SQLITE3_CALL (sqlite3_result_blob) (ctx, blob->data.data, blob->data.binary_length, SQLITE_TRANSIENT);
-		}
-		else if (G_VALUE_TYPE (value) == GDA_TYPE_BINARY) {
-			const GdaBinary *bin;
-			bin = gda_value_get_binary (value);
-			SQLITE3_CALL (sqlite3_result_blob) (ctx, bin->data, bin->binary_length, SQLITE_TRANSIENT);
-		}
-		else {
-			gchar *str = gda_value_stringify (value);
-			SQLITE3_CALL (sqlite3_result_text) (ctx, str, -1, SQLITE_TRANSIENT);
-			g_free (str);
-		}
-		return SQLITE_OK;
+		gchar *str = gda_value_stringify (value);
+		SQLITE3_CALL (sqlite3_result_text) (ctx, str, -1, SQLITE_TRANSIENT);
+		g_free (str);
 	}
+
+	return SQLITE_OK;
 }
 
 static int
 virtualRowid (sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid)
 {
 	VirtualCursor *cursor = (VirtualCursor*) cur;
-	VirtualTable *vtable = (VirtualTable*) cur->pVtab;
 
-	TRACE (vtable, cur);
-
-	*pRowid = gda_data_model_iter_get_row (cursor->iter);
-	if (! vtable->rowid_hash || (vtable->rowid_hash_model == vtable->td->real_model)) {
-		if (! vtable->rowid_hash) {
-			vtable->rowid_hash = g_hash_table_new_full (g_int64_hash, g_int64_equal,
-								    g_free,
-								    (GDestroyNotify) g_object_unref);
-			vtable->rowid_hash_model = vtable->td->real_model;
-		}
-
-		GdaRow *grow;
-		gint i, ncols;
-		gint64 *hid;
-		ncols = g_list_length (vtable->td->columns);
-		
-		grow = gda_row_new (ncols);
-		for (i = 0; i < ncols; i++) {
-			const GValue *cvalue;
-			GValue *gvalue;
-			cvalue = gda_data_model_iter_get_value_at (cursor->iter, i);
-			gvalue = gda_row_get_value (grow, i);
-			if (cvalue) {
-				gda_value_reset_with_type (gvalue, G_VALUE_TYPE (cvalue));
-				g_value_copy (cvalue, gvalue);
-			}
-			else
-				gda_row_invalidate_value (grow, gvalue);
-		}
-		
-		hid = g_new (gint64, 1);
-		*hid = *pRowid;
-		g_hash_table_insert (vtable->rowid_hash, hid, grow);
-	}
+	TRACE ((VirtualTable*) cur->pVtab, cur);
 
+	*pRowid = ((sqlite_int64) cursor->row) + (((sqlite_int64) cursor->data->rowid_offset) << 32);
 	return SQLITE_OK;
 }
 
@@ -878,6 +1048,8 @@ static void
 virtual_table_manage_real_data_model (VirtualTable *vtable, int idxNum, const char *idxStr,
 				      int argc, sqlite3_value **argv)
 {
+	/*g_print ("================== %s (VTable=> %p, %s)\n", __FUNCTION__,
+	  vtable, vtable->td->table_name);*/
 	if (!vtable->td->spec->create_filtered_model_func && !vtable->td->spec->create_model_func)
 		return;
 
@@ -904,7 +1076,7 @@ virtual_table_manage_real_data_model (VirtualTable *vtable, int idxNum, const ch
 		vtable->td->real_model = vtable->td->spec->create_model_func (vtable->td->spec);
 	if (! vtable->td->real_model)
 		return;
-	
+
 	/* columns if not yet created */
 	if (! vtable->td->columns && vtable->td->spec->create_columns_func)
 		vtable->td->columns = vtable->td->spec->create_columns_func (vtable->td->spec, NULL);
@@ -925,7 +1097,9 @@ virtual_table_manage_real_data_model (VirtualTable *vtable, int idxNum, const ch
 		}
 	}
 
-	/*g_print ("Created real model %p for table %s\n", vtable->td->real_model, vtable->td->table_name);*/
+#ifdef GDA_DEBUG_VIRTUAL
+	g_print ("Created real model %p for table %s\n", vtable->td->real_model, vtable->td->table_name);
+#endif
 }
 
 static int
@@ -935,43 +1109,69 @@ virtualFilter (sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr,
 	VirtualTable *vtable = (VirtualTable*) pVtabCursor->pVtab;
 
 	TRACE (pVtabCursor->pVtab, pVtabCursor);
+#ifdef GDA_DEBUG_VIRTUAL
+	g_print ("\tidxStr=[%s], idxNum=[%d]\n", idxStr, idxNum);
+#endif
 
-	virtual_table_manage_real_data_model (vtable, idxNum, idxStr, argc, argv);
-	if (! vtable->td->real_model)
-		return SQLITE_ERROR;
-
-	/* initialize cursor */
-	if (GDA_IS_DATA_PROXY (vtable->td->real_model))
-		cursor->iter = g_object_new (GDA_TYPE_DATA_MODEL_ITER,
-					     "data-model", vtable->td->real_model, NULL);
-	else
-		cursor->iter = gda_data_model_create_iter (vtable->td->real_model);
-
-	if (gda_data_model_iter_is_valid (cursor->iter) &&
-	    (gda_data_model_iter_get_row (cursor->iter) > 0)) {
-		/*g_print ("@%d, rewinding...", gda_data_model_iter_get_row (cursor->iter));*/
-		for (;gda_data_model_iter_move_prev (cursor->iter););
-		/*g_print ("done: @%d\n", gda_data_model_iter_get_row (cursor->iter));*/
-		if (gda_data_model_iter_is_valid (cursor->iter))
-			goto onerror;
+	/* find a VirtualFilteredData corresponding to this filter */
+	VirtualFilteredData *data;
+	data = NULL;
+	if (vtable->all_data) {
+		GSList *list;
+		for (list = vtable->all_data; list; list = list->next) {
+			VirtualFilteredData *vd = (VirtualFilteredData*) list->data;
+			if (vd->reuseable &&
+			    (vd->idxNum == idxNum) &&
+			    (argc == argc) &&
+			    ((!idxStr && !vd->idxStr) || (idxStr && vd->idxStr && !strcmp (idxStr, vd->idxStr)))) {
+				GValue **avalues;
+				gint i;
+				gboolean equal = TRUE;
+				avalues = create_gvalues_array_from_sqlite3_array (argc, argv);
+				for (i = 0; i < argc; i++) {
+					GValue *v1, *v2;
+					v1 = vd->argv [i];
+					v2 = avalues [i];
+
+					if (! ((!v1 && !v2) ||
+					       (v1 && v2 && (G_VALUE_TYPE (v1) == G_VALUE_TYPE (v2)) &&
+						!gda_value_differ (v1, v2)))) {
+						equal = FALSE;
+						break;
+					}
+				}
+				for (i = 0; i < argc; i++) {
+					GValue *v2;
+					v2 = avalues [i];
+					if (v2)
+						gda_value_free (v2);
+				}
+				g_free (avalues);
+				
+				if (equal) {
+					data = vd;
+					break;
+				}
+			}
+		}
 	}
-	if (! gda_data_model_iter_is_valid (cursor->iter)) {
-		gda_data_model_iter_move_next (cursor->iter);
-		if (! gda_data_model_iter_is_valid (cursor->iter))
-			goto onerror;
+
+	if (!data) {
+		virtual_table_manage_real_data_model (vtable, idxNum, idxStr, argc, argv);
+		if (! vtable->td->real_model)
+			return SQLITE_ERROR;
+		data = virtual_filtered_data_new (vtable, vtable->td->real_model, idxNum, idxStr, argc, argv);
+		vtable->all_data = g_slist_prepend (vtable->all_data, data);
 	}
-	cursor->model = g_object_ref (vtable->td->real_model);
 
-	return handle_data_model_exception (pVtabCursor->pVtab, cursor->model);
+	if (cursor->data)
+		virtual_filtered_data_unref (cursor->data);
+	cursor->data = virtual_filtered_data_ref (data);
 
- onerror:
-	g_object_unref (cursor->iter);
-	cursor->iter = NULL;
-	if (pVtabCursor->pVtab->zErrMsg)
-			SQLITE3_CALL (sqlite3_free) (pVtabCursor->pVtab->zErrMsg);
-	pVtabCursor->pVtab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-		("%s", _("Can't obtain an iterator located on the first row"));
-	return SQLITE_ERROR;
+	/* initialize cursor */
+	cursor->row = -1;
+
+	return virtualNext (pVtabCursor);
 }
 
 #ifdef GDA_DEBUG_VIRTUAL
@@ -1141,6 +1341,152 @@ param_name_to_number (gint maxrows, const gchar *str)
 }
 
 /*
+ * optype value: see virtualUpdate()
+ */
+static int
+update_data_select_model (sqlite3_vtab *tab, gint optype, int nData, sqlite3_value **apData)
+{
+	VirtualTable *vtable = (VirtualTable *) tab;
+
+	/* determine parameters required to execute MOD statement */
+	GdaStatement *stmt = NULL;
+	ParamType ptype;
+	switch (optype) {
+	case 1:
+		ptype = PARAMS_DELETE;
+		if (! vtable->td->modif_stmt [ptype])
+			g_object_get (vtable->td->real_model, "delete-stmt", &stmt, NULL);
+		break;
+	case 2:
+		ptype = PARAMS_INSERT;
+		if (! vtable->td->modif_stmt [ptype])
+			g_object_get (vtable->td->real_model, "insert-stmt", &stmt, NULL);
+		break;
+	case 3:
+		ptype = PARAMS_UPDATE;
+		if (! vtable->td->modif_stmt [ptype])
+			g_object_get (vtable->td->real_model, "update-stmt", &stmt, NULL);
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+		
+	if (! vtable->td->modif_stmt [ptype]) {
+		if (! stmt) {
+			tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+				(_("No statement specified to modify the data"));
+			return SQLITE_READONLY;
+		}
+		
+		GdaSet *params;
+		if (! gda_statement_get_parameters (stmt, &params, NULL) || !params) {
+			tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+				(_("Invalid statement specified to modify the data"));
+			g_object_unref (stmt);
+			return SQLITE_READONLY;
+		}
+		vtable->td->modif_stmt [ptype] = stmt;
+		vtable->td->modif_params [ptype] = params;
+	}
+	stmt = vtable->td->modif_stmt [ptype];
+		
+	/* bind parameters */
+	GSList *list;
+
+	for (list = vtable->td->modif_params [ptype]->holders; list; list = list->next) {
+		const gchar *id;
+		GdaHolder *holder = GDA_HOLDER (list->data);
+		gboolean holder_value_set = FALSE;
+			
+		id = gda_holder_get_id (holder);
+		if (!id) {
+			tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+				(_("Invalid parameter in statement to modify the data"));
+			return SQLITE_READONLY;
+		}
+		if (*id == '+' && id[1]) {
+			long int i;
+			i = param_name_to_number (vtable->td->n_columns, id+1);
+			if (i >= 0) {
+				GType type;
+				GValue *value;
+				type = gda_column_get_g_type (gda_data_model_describe_column (vtable->td->real_model, i));
+				if ((type != GDA_TYPE_NULL) && SQLITE3_CALL (sqlite3_value_text) (apData [i+2]))
+					value = gda_value_new_from_string ((const gchar*) SQLITE3_CALL (sqlite3_value_text) (apData [i+2]), type);
+				else
+					value = gda_value_new_null ();
+				if (gda_holder_take_value (holder, value, NULL))
+					holder_value_set = TRUE;
+			}
+		}
+		else if (*id == '-') {
+			gint64 rowid = SQLITE3_CALL (sqlite3_value_int64) (apData [0]);
+			long int i;
+			const GValue *value;
+
+			i = param_name_to_number (vtable->td->n_columns, id+1);
+			value = get_data_value (vtable, NULL, -1, rowid, i, NULL);
+			if (value && gda_holder_set_value (holder, value, NULL))
+				holder_value_set = TRUE;
+		}
+			
+		if (! holder_value_set) {
+			GdaSet *exec_set;
+			GdaHolder *eh;
+			g_object_get (vtable->td->real_model, "exec-params",
+				      &exec_set, NULL);
+			if (! exec_set) {
+				/* can't give value to param named @id */
+				tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+					(_("Invalid parameter in statement to modify the data"));
+				return SQLITE_READONLY;
+			}
+			eh = gda_set_get_holder (exec_set, id);
+			if (! eh ||
+			    ! gda_holder_set_bind (holder, eh, NULL)) {
+				/* can't give value to param named @id */
+				tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+					(_("Invalid parameter in statement to modify the data"));
+				return SQLITE_READONLY;
+			}
+		}
+	}
+
+	GdaConnection *cnc;
+	cnc = gda_data_select_get_connection (GDA_DATA_SELECT (vtable->td->real_model));
+
+	GError *lerror = NULL;
+#ifdef GDA_DEBUG_NO
+	gchar *sql;
+	sql = gda_statement_to_sql (stmt, NULL, NULL);
+	g_print ("SQL: [%s] ", sql);
+	g_free (sql);
+	sql = gda_statement_to_sql_extended (stmt, cnc, vtable->td->modif_params [ptype], GDA_STATEMENT_SQL_PRETTY, NULL, &lerror);
+	if (sql) {
+		g_print ("With params: [%s]\n", sql);
+		g_free (sql);
+	}
+	else {
+		g_print ("params ERROR [%s]\n", lerror && lerror->message ? lerror->message : "No detail");
+	}
+	g_clear_error (&lerror);
+#endif
+		
+	if (!cnc ||
+	    (gda_connection_statement_execute_non_select (cnc, stmt,
+							  vtable->td->modif_params [ptype],
+							  NULL, &lerror) == -1)) {
+		/* failed to execute */
+		tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+			(_("Failed to modify data: %s"),
+			 lerror && lerror->message ? lerror->message : _("No detail"));
+		g_clear_error (&lerror);
+		return SQLITE_READONLY;
+	}
+	return SQLITE_OK;
+}
+
+/*
  *    apData[0]  apData[1]  apData[2..]
  *
  *    INTEGER                              DELETE            
@@ -1163,6 +1509,12 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 
 	TRACE (tab, NULL);
 
+	GSList *list;
+	for (list = vtable->all_data; list; list = list->next) {
+		VirtualFilteredData *vd = (VirtualFilteredData*) list->data;
+		vd->reuseable = FALSE;
+	}
+
 	/* determine operation type */
 	if (nData == 1)
 		optype = 1;
@@ -1172,8 +1524,7 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 			/* argc>1 and argv[0] is not NULL: rowid is imposed by SQLite
 			 * which is not supported */
 			return SQLITE_READONLY;
-		}
-	}
+		}	}
 	else if ((nData > 1) && (SQLITE3_CALL (sqlite3_value_type) (apData[0]) == SQLITE_INTEGER)) {
 		optype = 3;
 		if (SQLITE3_CALL (sqlite3_value_int) (apData[0]) != 
@@ -1199,163 +1550,14 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 	    ((optype == 2) && ! (access_flags & GDA_DATA_MODEL_ACCESS_INSERT)) ||
 	    ((optype == 3) && ! (access_flags & GDA_DATA_MODEL_ACCESS_UPDATE))) {
 		/* we can't use vtable->td->real_model because it can't be accessed correctly */
-		if (! GDA_IS_DATA_SELECT (vtable->td->real_model)) {
-			tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-				(_("Data model representing the table is read only"));
-			return SQLITE_READONLY;
-		}
-		
-		/* determine parameters required to execute MOD statement */
-		GdaStatement *stmt = NULL;
-		ParamType ptype;
-		switch (optype) {
-		case 1:
-			ptype = PARAMS_DELETE;
-			if (! vtable->td->modif_stmt [ptype])
-				g_object_get (vtable->td->real_model, "delete-stmt", &stmt, NULL);
-			break;
-		case 2:
-			ptype = PARAMS_INSERT;
-			if (! vtable->td->modif_stmt [ptype])
-				g_object_get (vtable->td->real_model, "insert-stmt", &stmt, NULL);
-			break;
-		case 3:
-			ptype = PARAMS_UPDATE;
-			if (! vtable->td->modif_stmt [ptype])
-				g_object_get (vtable->td->real_model, "update-stmt", &stmt, NULL);
-			break;
-		default:
-			g_assert_not_reached ();
-		}
-		
-		if (! vtable->td->modif_stmt [ptype]) {
-			if (! stmt) {
-				tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-					(_("No statement provided to modify the data"));
-				return SQLITE_READONLY;
-			}
-		
-			GdaSet *params;
-			if (! gda_statement_get_parameters (stmt, &params, NULL) || !params) {
-				tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-					(_("Invalid statement provided to modify the data"));
-				g_object_unref (stmt);
-				return SQLITE_READONLY;
-			}
-			vtable->td->modif_stmt [ptype] = stmt;
-			vtable->td->modif_params [ptype] = params;
-		}
-		stmt = vtable->td->modif_stmt [ptype];
-		
-		/* bind parameters */
-		GSList *list;
-		for (list = vtable->td->modif_params [ptype]->holders; list; list = list->next) {
-			const gchar *id;
-			GdaHolder *holder = GDA_HOLDER (list->data);
-			gboolean holder_value_set = FALSE;
-			
-			id = gda_holder_get_id (holder);
-			if (!id) {
-				tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-					(_("Invalid parameter in statement to modify the data"));
-				return SQLITE_READONLY;
-			}
-			if (*id == '+' && id[1]) {
-				long int i;
-				i = param_name_to_number (vtable->td->n_columns, id+1);
-				if (i >= 0) {
-					GType type;
-					GValue *value;
-					type = gda_column_get_g_type (gda_data_model_describe_column (vtable->td->real_model, i));
-					if ((type != GDA_TYPE_NULL) && SQLITE3_CALL (sqlite3_value_text) (apData [i+2]))
-						value = gda_value_new_from_string ((const gchar*) SQLITE3_CALL (sqlite3_value_text) (apData [i+2]), type);
-					else
-						value = gda_value_new_null ();
-					if (gda_holder_take_value (holder, value, NULL))
-						holder_value_set = TRUE;
-				}
-			}
-			else if (*id == '-') {
-				if (! vtable->rowid_hash) {
-					tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-						(_("Could not retreive row to delete"));
-					return SQLITE_READONLY;
-				}
-
-				gint64 rowid = SQLITE3_CALL (sqlite3_value_int64) (apData [0]);
-				GdaRow *grow = NULL;
-				if (vtable->rowid_hash_model == vtable->td->real_model)
-					grow = g_hash_table_lookup (vtable->rowid_hash, &rowid);
-				if (!grow) {
-					tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-						(_("Could not retreive row to delete"));
-					return SQLITE_READONLY;
-				}
-
-				long int i;
-				i = param_name_to_number (vtable->td->n_columns, id+1);
-				if (i >= 0) {
-					GValue *value;
-					value = gda_row_get_value (grow, i);
-					if (gda_holder_set_value (holder, value, NULL))
-						holder_value_set = TRUE;
-				}
-			}
-			
-			if (! holder_value_set) {
-				GdaSet *exec_set;
-				GdaHolder *eh;
-				g_object_get (vtable->td->real_model, "exec-params",
-					      &exec_set, NULL);
-				if (! exec_set) {
-					/* can't give value to param named @id */
-					tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-						(_("Invalid parameter in statement to modify the data"));
-					return SQLITE_READONLY;
-				}
-				eh = gda_set_get_holder (exec_set, id);
-				if (! eh ||
-				    ! gda_holder_set_bind (holder, eh, NULL)) {
-					/* can't give value to param named @id */
-					tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-						(_("Invalid parameter in statement to modify the data"));
-					return SQLITE_READONLY;
-				}
-			}
-		}
-
-		GdaConnection *cnc;
-		cnc = gda_data_select_get_connection (GDA_DATA_SELECT (vtable->td->real_model));
-
-		GError *lerror = NULL;
-#ifdef GDA_DEBUG_NO
-		gchar *sql;
-		sql = gda_statement_to_sql (stmt, NULL, NULL);
-		g_print ("SQL: [%s] ", sql);
-		g_free (sql);
-		sql = gda_statement_to_sql_extended (stmt, cnc, vtable->td->modif_params [ptype], GDA_STATEMENT_SQL_PRETTY, NULL, &lerror);
-		if (sql) {
-			g_print ("With params: [%s]\n", sql);
-			g_free (sql);
-		}
+		if (GDA_IS_DATA_SELECT (vtable->td->real_model))
+			return update_data_select_model (tab, optype, nData, apData);
 		else {
-			g_print ("params ERROR [%s]\n", lerror && lerror->message ? lerror->message : "No detail");
-		}
-		g_clear_error (&lerror);
-#endif
-		
-		if (!cnc ||
-		    (gda_connection_statement_execute_non_select (cnc, stmt,
-								  vtable->td->modif_params [ptype],
-								  NULL, &lerror) == -1)) {
-			/* failed to execute */
+			TO_IMPLEMENT;
 			tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
-				(_("Failed to modify data: %s"),
-				 lerror && lerror->message ? lerror->message : _("No detail"));
-			g_clear_error (&lerror);
+				(_("Data model representing the table is read only"));
 			return SQLITE_READONLY;
 		}
-		return SQLITE_OK;
 	}
 
 	/* REM: when using the values of apData[], the limit is
@@ -1422,7 +1624,8 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 							   value, &error);
 			gda_value_free (value);
 			if (!res) {
-				g_print ("Error: %s\n", error && error->message ? error->message : "???");
+				/*g_print ("Error: %s\n", error && error->message ? error->message : "???");*/
+				g_clear_error (&error);
 				return SQLITE_READONLY;
 			}
 		}
@@ -1444,9 +1647,15 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 static int
 virtualBegin (G_GNUC_UNUSED sqlite3_vtab *tab)
 {
+	VirtualTable *vtable = (VirtualTable *) tab;
 	TRACE (tab, NULL);
-	/* no documentation currently available, don't do anything */
-	return SQLITE_OK;
+
+	if (vtable->locked)
+		return SQLITE_ERROR;
+	else {
+		vtable->locked = TRUE;
+		return SQLITE_OK;
+	}
 }
 
 static int
@@ -1463,20 +1672,17 @@ virtualCommit (G_GNUC_UNUSED sqlite3_vtab *tab)
 	VirtualTable *vtable = (VirtualTable *) tab;
 	TRACE (tab, NULL);
 
-	if (vtable->rowid_hash) {
-		g_hash_table_destroy (vtable->rowid_hash);
-		vtable->rowid_hash = NULL;
-		vtable->rowid_hash_model = NULL;
-	}
-
+	vtable->locked = FALSE;
 	return SQLITE_OK;
 }
 
 static int
 virtualRollback (G_GNUC_UNUSED sqlite3_vtab *tab)
 {	
+	VirtualTable *vtable = (VirtualTable *) tab;
 	TRACE (tab, NULL);
-	/* no documentation currently available, don't do anything */
+
+	vtable->locked = FALSE;
 	return SQLITE_OK;
 }
 
diff --git a/tests/data-models/check_model_errors.c b/tests/data-models/check_model_errors.c
index e879ea2..036cf09 100644
--- a/tests/data-models/check_model_errors.c
+++ b/tests/data-models/check_model_errors.c
@@ -328,7 +328,6 @@ test2 (GdaConnection *cnc)
 #endif
 		goto out;
 	}
-	dump_data_model (model);
 
 	GdaVirtualProvider *virtual_provider;
 	GError *lerror = NULL;
@@ -371,6 +370,8 @@ test2 (GdaConnection *cnc)
 		goto out;
 	}
 
+	dump_data_model (model);
+
 	/* iterate forward */
 	gint i, row;
 	iter = gda_data_model_create_iter (model);
@@ -474,8 +475,10 @@ test2 (GdaConnection *cnc)
 	}
 
  out:
-	g_object_unref (iter);
-	g_object_unref (model);
+	if (iter)
+		g_object_unref (iter);
+	if (model)
+		g_object_unref (model);
 	g_object_unref (vcnc);
 	g_object_unref (virtual_provider);
 	
diff --git a/tests/data-models/check_vcnc.c b/tests/data-models/check_vcnc.c
index c21265b..decb4ce 100644
--- a/tests/data-models/check_vcnc.c
+++ b/tests/data-models/check_vcnc.c
@@ -98,6 +98,8 @@ main (int argc, char *argv[])
 
         gda_connection_close (virtual);
         gda_connection_close (out_cnc);
+
+	g_print ("All Ok\n");
         return 0;
 }
 
diff --git a/tests/data-models/check_virtual.c b/tests/data-models/check_virtual.c
index f559599..c5ad416 100644
--- a/tests/data-models/check_virtual.c
+++ b/tests/data-models/check_virtual.c
@@ -23,17 +23,39 @@
 
 static GdaDataModel *run_sql_select (GdaConnection *cnc, const gchar *sql);
 static gboolean run_sql_non_select (GdaConnection *cnc, const gchar *sql);
+static gboolean test1 (void);
+static gboolean test2 (void);
 
 int 
 main (int argc, char **argv)
 {
+	gint nfailed = 0;
+	gda_init ();
+
+	if (! test1 ())
+		nfailed++;
+	if (! test2 ())
+		nfailed++;
+
+	if (nfailed == 0) {
+		g_print ("Ok, all tests passed\n");
+		return EXIT_SUCCESS;
+	}
+	else {
+		g_print ("%d failed\n", nfailed);
+		return EXIT_FAILURE;
+	} 
+}
+
+static gboolean
+test1 (void)
+{
 	GError *error = NULL;	
 	GdaConnection *cnc;
 	GdaVirtualProvider *provider;
 	GdaDataModel *rw_model;
 	gchar *file;
-	
-	gda_init ();
+	gboolean retval = FALSE;
 
 	provider = gda_vprovider_data_model_new ();
 	cnc = gda_virtual_connection_open (provider, NULL);
@@ -53,6 +75,7 @@ main (int argc, char **argv)
 	file = g_build_filename (CHECK_FILES, "tests", "data-models", "country.csv", NULL);
 	country_model = gda_data_model_import_new_file (file, TRUE, options);
 	g_free (file);
+	file = NULL;
 	g_object_unref (options);
 
 	/* Add data models to connection */
@@ -83,11 +106,14 @@ main (int argc, char **argv)
 						  NULL, 0, NULL, 0, NULL);
 	file = g_build_filename (CHECK_FILES, "tests", "data-models", "check_virtual.csv", NULL);
 	if (!g_file_get_contents (file, &expected, NULL, NULL))
-		return EXIT_FAILURE;
-	g_free (file);
+		goto out;
 	if (strcmp (export, expected))
-		return EXIT_FAILURE;
+		goto out;
+
+	retval = TRUE;
+ out:
 
+	g_free (file);
 	g_free (export);
 	g_free (expected);
 	g_object_unref (city_model);
@@ -95,8 +121,54 @@ main (int argc, char **argv)
 	g_object_unref (cnc);
 	g_object_unref (provider);
 
-	g_print ("Ok.\n");
-	return EXIT_SUCCESS;
+	return retval;
+}
+
+static gboolean
+test2 (void)
+{
+	GError *error = NULL;	
+	GdaConnection *cnc;
+	GdaVirtualProvider *provider;
+	gchar *file;
+	gboolean retval = FALSE;
+
+	provider = gda_vprovider_data_model_new ();
+	cnc = gda_virtual_connection_open (provider, NULL);
+	g_assert (cnc);
+
+	/* load CSV data models */
+	GdaDataModel *country_model, *city_model;
+	GdaSet *options = gda_set_new_inline (1, "TITLE_AS_FIRST_LINE", G_TYPE_BOOLEAN, TRUE);
+	file = g_build_filename (CHECK_FILES, "tests", "data-models", "city.csv", NULL);
+	city_model = gda_data_model_import_new_file (file, TRUE, options);
+	g_free (file);
+	file = g_build_filename (CHECK_FILES, "tests", "data-models", "country.csv", NULL);
+	country_model = gda_data_model_import_new_file (file, TRUE, options);
+	g_free (file);
+	g_object_unref (options);
+
+	/* Add data models to connection */
+	if (!gda_vconnection_data_model_add_model (GDA_VCONNECTION_DATA_MODEL (cnc), city_model, "city", &error)) 
+		g_error ("Add city model error: %s\n", error && error->message ? error->message : "no detail");
+	if (!gda_vconnection_data_model_add_model (GDA_VCONNECTION_DATA_MODEL (cnc), country_model, "country", &error)) 
+		g_error ("Add country model error: %s\n", error && error->message ? error->message : "no detail");
+
+	/* test */
+	GdaDataModel *model;
+	model = run_sql_select (cnc, "SELECT c.*, o.name FROM city c INNER JOIN country o ON (c.countrycode = o.code)");
+	gda_data_model_dump (model, stdout);
+	g_object_unref (model);
+
+	retval = TRUE;
+ out:
+
+	g_object_unref (city_model);
+	g_object_unref (country_model);
+	g_object_unref (cnc);
+	g_object_unref (provider);
+
+	return retval;
 }
 
 static GdaDataModel *



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