[libgda/gtk3] Virtual connection: corrections for the INSERT, DELETE and UPDATE operations



commit 79c44a02e31f94e5911119580a8254bbf0b8e1ee
Author: Vivien Malerba <malerba gnome-db org>
Date:   Mon Jan 31 19:29:36 2011 +0100

    Virtual connection: corrections for the INSERT, DELETE and UPDATE operations
    
    also added a new test

 libgda/sqlite/gda-sqlite-recordset.c               |    3 +-
 .../virtual/gda-vconnection-data-model-private.h   |    2 +-
 libgda/sqlite/virtual/gda-vprovider-data-model.c   |  388 +++++++++++++++---
 tests/data-models/.gitignore                       |    1 +
 tests/data-models/Makefile.am                      |   16 +-
 tests/data-models/check_vcnc.c                     |  449 ++++++++++++++++++++
 tests/data-models/cities1.xml                      |   83 ++++
 tests/data-models/cities2.xml                      |   33 ++
 tests/data-models/cities3.xml                      |   28 ++
 tests/data-models/countriesA.xml                   |   75 ++++
 tests/data-models/countriesB.xml                   |   31 ++
 11 files changed, 1048 insertions(+), 61 deletions(-)
---
diff --git a/libgda/sqlite/gda-sqlite-recordset.c b/libgda/sqlite/gda-sqlite-recordset.c
index 9f06d47..c6f2c61 100644
--- a/libgda/sqlite/gda-sqlite-recordset.c
+++ b/libgda/sqlite/gda-sqlite-recordset.c
@@ -1,5 +1,5 @@
 /* GDA SQLite provider
- * Copyright (C) 1998 - 2010 The GNOME Foundation.
+ * Copyright (C) 1998 - 2011 The GNOME Foundation.
  *
  * AUTHORS:
  *	   Rodrigo Moya <rodrigo gnome-db org>
@@ -539,6 +539,7 @@ fetch_next_sqlite_row (GdaSqliteRecordset *model, gboolean do_store, GError **er
 		GDA_DATA_SELECT (model)->advertized_nrows = model->priv->next_row_num;
 		SQLITE3_CALL (sqlite3_reset) (ps->sqlite_stmt);
 		break;
+	case SQLITE_READONLY:
 	case SQLITE_MISUSE:
 		g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
 			     GDA_SERVER_PROVIDER_INTERNAL_ERROR, 
diff --git a/libgda/sqlite/virtual/gda-vconnection-data-model-private.h b/libgda/sqlite/virtual/gda-vconnection-data-model-private.h
index a07e392..271b4fd 100644
--- a/libgda/sqlite/virtual/gda-vconnection-data-model-private.h
+++ b/libgda/sqlite/virtual/gda-vconnection-data-model-private.h
@@ -29,7 +29,7 @@ typedef struct {
 	GdaVconnectionDataModelSpec *spec;
 	GDestroyNotify               spec_free_func;
 
-	GdaDataModel                *real_model; /* data model really being used, a reference count is kept on it */
+	GdaDataModel                *real_model; /* data model really being used, a reference is kept on it */
 	GList                       *columns;
 	gchar                       *table_name;
 	gchar                       *unique_name;
diff --git a/libgda/sqlite/virtual/gda-vprovider-data-model.c b/libgda/sqlite/virtual/gda-vprovider-data-model.c
index 7a0c051..eab1afa 100644
--- a/libgda/sqlite/virtual/gda-vprovider-data-model.c
+++ b/libgda/sqlite/virtual/gda-vprovider-data-model.c
@@ -1,6 +1,6 @@
 /* 
  * GDA common library
- * Copyright (C) 2007 - 2010 The GNOME Foundation.
+ * Copyright (C) 2007 - 2011 The GNOME Foundation.
  *
  * AUTHORS:
  *      Vivien Malerba <malerba gnome-db org>
@@ -147,6 +147,54 @@ gda_vprovider_data_model_new (void)
         return provider;
 }
 
+/*
+ * Note about RowIDs and how SQLite uses them:
+ *
+ * SQLite considers that each virtual table has unique row IDs, absolute value available all the time.
+ * When an UPDATE or DELETE statement is executed, SQLite does the following:
+ *  - xBegin (table): request a transaction start
+ *  - xOpen (table): create a new cursor
+ *  - xFilter (cursor): initialize the cursor
+ *  - moves the cursor one step at a time up to the end (xEof, xColumn and xNext). If it finds a
+ *    row needing to be updated or deleted, it calls xRowid (cursor) to get the RowID
+ *  - xClose (cursor): free the now useless cursor
+ *  - calls xUpdate (table) as many times as needed (one time for each row where xRowid was called)
+ *  - 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.
+ */
+
+#ifdef GDA_DEBUG_VIRTUAL
+#define TRACE(table,cursor) g_print ("== %s (table=>%p cursor=>%p)\n", __FUNCTION__, (table), (cursor))
+#else
+#define TRACE(table,cursor)
+#endif
+
+typedef struct {
+	sqlite3_vtab                 base;
+	GdaVconnectionDataModel     *cnc;
+	GdaVConnectionTableData     *td;
+
+	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;
+
+typedef struct {
+	sqlite3_vtab_cursor      base; /* base.pVtab is a pointer to the sqlite3_vtab virtual table */
+	GdaDataModelIter        *iter;
+	gint                     ncols;
+} VirtualCursor;
+
 /* module creation */
 static int virtualCreate (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr);
 static int virtualConnect (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr);
@@ -272,25 +320,6 @@ gda_vprovider_data_model_get_name (G_GNUC_UNUSED GdaServerProvider *provider)
 	return "Virtual";
 }
 
-/* module implementation */
-#ifdef GDA_DEBUG_VIRTUAL
-  #define TRACE(table) g_print ("== %s (table=>%p)\n", __FUNCTION__, (table))
-#else
-  #define TRACE(table)
-#endif
-
-typedef struct {
-	sqlite3_vtab                 base;
-	GdaVconnectionDataModel     *cnc;
-	GdaVConnectionTableData     *td;
-} VirtualTable;
-
-typedef struct {
-	sqlite3_vtab_cursor      base; /* base.pVtab is a pointer to the sqlite3_vtab virtual table */
-	GdaDataModelIter        *iter;
-	gint                     ncols;
-} VirtualCursor;
-
 static int
 virtualCreate (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr)
 {
@@ -300,7 +329,7 @@ virtualCreate (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlit
 	gchar *spec_name, *tmp;
 	GdaVConnectionTableData *td;
 
-	TRACE (NULL);
+	TRACE (NULL, NULL);
 
 	/* find Spec */
 	g_assert (argc == 4);
@@ -429,7 +458,7 @@ virtualCreate (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlit
 		return SQLITE_ERROR;
 	}
 
-	/*g_print ("VIRTUAL TABLE: %s\n", sql->str);*/
+	/*g_print ("VIRTUAL TABLE [%p]: %s\n", vtable, sql->str);*/
 	g_string_free (sql, TRUE);
 
 	return SQLITE_OK;
@@ -438,7 +467,7 @@ virtualCreate (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlit
 static int
 virtualConnect (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr)
 {
-	TRACE (NULL);
+	TRACE (NULL, NULL);
 
 	return virtualCreate (db, pAux, argc, argv, ppVtab, pzErr);
 }
@@ -448,8 +477,10 @@ virtualDisconnect (sqlite3_vtab *pVtab)
 {
 	VirtualTable *vtable = (VirtualTable *) pVtab;
 
-	TRACE (pVtab);
+	TRACE (pVtab, NULL);
 
+	if (vtable->rowid_hash)
+		g_hash_table_destroy (vtable->rowid_hash);
 	g_free (vtable);
 	return SQLITE_OK;
 }
@@ -457,7 +488,7 @@ virtualDisconnect (sqlite3_vtab *pVtab)
 static int
 virtualDestroy (sqlite3_vtab *pVtab)
 {
-	TRACE (pVtab);
+	TRACE (pVtab, NULL);
 
 	return virtualDisconnect (pVtab);
 }
@@ -467,7 +498,7 @@ virtualOpen (sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor)
 {
 	VirtualCursor *cursor;
 
-	TRACE (pVTab);
+	TRACE (pVTab, NULL);
 
 	/* create empty cursor */
 	cursor = g_new0 (VirtualCursor, 1);
@@ -481,11 +512,11 @@ virtualClose (sqlite3_vtab_cursor *cur)
 {
 	VirtualCursor *cursor = (VirtualCursor*) cur;
 
-	TRACE (cur->pVtab);
+	TRACE (cur->pVtab, cur);
 
 	if (cursor->iter)
 		g_object_unref (cursor->iter);
-	/* FIXME: destroy table->spec->model and table->wrapper */
+
 	g_free (cur);
 	return SQLITE_OK;
 }
@@ -495,7 +526,7 @@ virtualEof (sqlite3_vtab_cursor *cur)
 {
 	VirtualCursor *cursor = (VirtualCursor*) cur;
 
-	TRACE (cur->pVtab);
+	TRACE (cur->pVtab, cur);
 
 	if (gda_data_model_iter_is_valid (cursor->iter))
 		return FALSE;
@@ -509,7 +540,7 @@ virtualNext (sqlite3_vtab_cursor *cur)
 	VirtualCursor *cursor = (VirtualCursor*) cur;
 	/*VirtualTable *vtable = (VirtualTable*) cur->pVtab;*/
 
-	TRACE (cur->pVtab);
+	TRACE (cur->pVtab, cur);
 
 	if (!gda_data_model_iter_move_next (cursor->iter)) {
 		if (gda_data_model_iter_is_valid (cursor->iter))
@@ -525,7 +556,7 @@ virtualColumn (sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i)
 {
 	VirtualCursor *cursor = (VirtualCursor*) cur;
 
-	TRACE (cur->pVtab);
+	TRACE (cur->pVtab, cur);
 
 	GdaHolder *param;
 	
@@ -580,10 +611,41 @@ static int
 virtualRowid (sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid)
 {
 	VirtualCursor *cursor = (VirtualCursor*) cur;
+	VirtualTable *vtable = (VirtualTable*) cur->pVtab;
 
-	TRACE (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_int_hash, g_int_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 (G_VALUE_TYPE (cvalue) != GDA_TYPE_NULL) {
+				g_value_init (gvalue, G_VALUE_TYPE (cvalue));
+				g_value_copy (cvalue, gvalue);
+			}
+		}
+		
+		hid = g_new (gint64, 1);
+		*hid = *pRowid;
+		g_hash_table_insert (vtable->rowid_hash, hid, grow);
+	}
+
 	return SQLITE_OK;
 }
 
@@ -705,7 +767,7 @@ virtualFilter (sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr,
 	VirtualCursor *cursor = (VirtualCursor*) pVtabCursor;
 	VirtualTable *vtable = (VirtualTable*) pVtabCursor->pVtab;
 
-	TRACE (pVtabCursor->pVtab);
+	TRACE (pVtabCursor->pVtab, pVtabCursor);
 
 	virtual_table_manage_real_data_model (vtable, idxNum, idxStr, argc, argv);
 	if (! vtable->td->real_model)
@@ -850,7 +912,7 @@ virtualBestIndex (sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo)
 {
 	VirtualTable *vtable = (VirtualTable *) tab;
 
-	TRACE (tab);
+	TRACE (tab, NULL);
 #ifdef GDA_DEBUG_VIRTUAL
 	index_info_dump (pIdxInfo, FALSE);
 #endif
@@ -870,6 +932,22 @@ virtualBestIndex (sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo)
 }
 
 /*
+ *
+ * Returns: >= 0 if Ok, -1 on error
+ */
+static gint
+param_name_to_number (gint maxrows, const gchar *str)
+{
+	gchar *endptr [1];
+	long int i;
+	i = strtol (str, endptr, 10);
+	if ((**endptr == '\0') && (i < maxrows) && (i >= 0))
+		return i;
+	else
+		return -1;
+}
+
+/*
  *    apData[0]  apData[1]  apData[2..]
  *
  *    INTEGER                              DELETE            
@@ -885,35 +963,227 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 {
 	VirtualTable *vtable = (VirtualTable *) tab;
 	const gchar *api_misuse_error = NULL;
+	gint optype; /* 1 => DELETE
+		      * 2 => INSERT
+		      * 3 => UPDATE
+		      */
+
+	TRACE (tab, NULL);
 
-	TRACE (tab);
+	/* determine operation type */
+	if (nData == 1)
+		optype = 1;
+	else if ((nData > 1) && (SQLITE3_CALL (sqlite3_value_type) (apData[0]) == SQLITE_NULL)) {
+		optype = 2;
+		if (SQLITE3_CALL (sqlite3_value_type) (apData[1]) != SQLITE_NULL) {
+			/* 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]) != 
+		    SQLITE3_CALL (sqlite3_value_int) (apData[1])) {
+			/* argc>1 and argv[0]==argv[1]: rowid is imposed by SQLite 
+			 * which is not supported */
+			return SQLITE_READONLY;
+		}
+	}
+	else
+		return SQLITE_MISUSE;
+
+	/* handle data model */
+	if (! vtable->td->real_model) {
+		virtual_table_manage_real_data_model (vtable, -1, NULL, 0, NULL);
+		if (! vtable->td->real_model)
+			return SQLITE_ERROR;
+	}
+
+	GdaDataModelAccessFlags access_flags;
+	access_flags = gda_data_model_get_access_flags (vtable->td->real_model);
+	if (((optype == 1) && ! (access_flags & GDA_DATA_MODEL_ACCESS_DELETE)) ||
+	    ((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;
+		}
+		
+		GdaStatement *stmt;
+		switch (optype) {
+		case 1:
+			g_object_get (vtable->td->real_model, "delete-stmt", &stmt, NULL);
+			break;
+		case 2:
+			g_object_get (vtable->td->real_model, "insert-stmt", &stmt, NULL);
+			break;
+		case 3:
+			g_object_get (vtable->td->real_model, "update-stmt", &stmt, NULL);
+			break;
+		default:
+			g_assert_not_reached ();
+		}
+		
+		if (! stmt) {
+			tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+				(_("No statement provided to modify the data model representing the table"));
+			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 model representing the table"));
+			g_object_unref (stmt);
+			return SQLITE_READONLY;
+		}
+		
+		GSList *list;
+		for (list = params->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) {
+				g_object_unref (params);
+				g_object_unref (stmt);
+				tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+					(_("Invalid parameter in statement provided to modify "
+					   "the data model representing the table"));
+				return SQLITE_READONLY;
+			}
+			if (*id == '+' && id[1]) {
+				long int i;
+				i = param_name_to_number (gda_data_model_get_n_columns (vtable->td->real_model),
+							  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 (gda_data_model_get_n_columns (vtable->td->real_model),
+							  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 */
+					g_object_unref (params);
+					g_object_unref (stmt);
+					tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+						(_("Invalid parameter in statement provided to modify "
+						   "the data model representing the table"));
+					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 */
+					g_object_unref (params);
+					g_object_unref (stmt);
+					tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+						(_("Invalid parameter in statement provided to modify "
+						   "the data model representing the table"));
+					return SQLITE_READONLY;
+				}
+			}
+		}
+
+		GdaConnection *cnc;
+		cnc = gda_data_select_get_connection (GDA_DATA_SELECT (vtable->td->real_model));
+		
+#ifdef GDA_DEBUG_NO
+		gchar *sql;
+		GError *lerror = NULL;
+		sql = gda_statement_to_sql (stmt, NULL, NULL);
+		g_print ("SQL: [%s] ", sql);
+		g_free (sql);
+		sql = gda_statement_to_sql_extended (stmt, cnc, params, 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, params,
+								  NULL, NULL) == -1)) {
+			/* failed to execute */
+			g_object_unref (params);
+			g_object_unref (stmt);
+			tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+				(_("Failed to execute the statement provided to modify "
+				   "the data model representing the table"));
+			return SQLITE_READONLY;
+		}
+		return SQLITE_OK;
+	}
 
 	/* REM: when using the values of apData[], the limit is
 	 * (nData -1 ) and not nData because the last column of the corresponding CREATE TABLE ...
 	 * is an internal hidden field which does not correspond to any column of the real data model
 	 */
 
-	if (nData == 1) {
+	if (optype == 1) {
 		/* DELETE */
 		if (SQLITE3_CALL (sqlite3_value_type) (apData[0]) == SQLITE_INTEGER) {
 			gint rowid = SQLITE3_CALL (sqlite3_value_int) (apData [0]);
-			return gda_data_model_remove_row (vtable->td->real_model, rowid, NULL) ? SQLITE_OK : SQLITE_READONLY;
+			return gda_data_model_remove_row (vtable->td->real_model, rowid, NULL) ?
+				SQLITE_OK : SQLITE_READONLY;
 		}
 		else {
 			api_misuse_error = "argc==1 and argv[0] is not an integer";
 			goto api_misuse;
 		}
 	}
-	else if ((nData > 1) && (SQLITE3_CALL (sqlite3_value_type) (apData[0]) == SQLITE_NULL)) {
+	else if (optype == 2) {
 		/* INSERT */
 		gint newrow, i;
 		GList *values = NULL;
 		
-		if (SQLITE3_CALL (sqlite3_value_type) (apData[1]) != SQLITE_NULL) {
-			/* argc>1 and argv[0] is not NULL: rowid is imposed by SQLite which is not supported */
-			return SQLITE_READONLY;
-		}
-
 		for (i = 2; i < (nData - 1); i++) {
 			GType type;
 			GValue *value;
@@ -922,7 +1192,8 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 				value = gda_value_new_from_string ((const gchar*) SQLITE3_CALL (sqlite3_value_text) (apData [i]), type);
 			else
 				value = gda_value_new_null ();
-			/*g_print ("TXT #%s# => value %p (type=%s) apData[]=%p\n", SQLITE3_CALL (sqlite3_value_text) (apData [i]), value,
+			/*g_print ("TXT #%s# => value %p (type=%s) apData[]=%p\n",
+			  SQLITE3_CALL (sqlite3_value_text) (apData [i]), value,
 			  g_type_name (type), apData[i]);*/
 			values = g_list_prepend (values, value);
 		}
@@ -936,14 +1207,11 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 
 		*pRowid = newrow;
 	}
-	else if ((nData > 1) && (SQLITE3_CALL (sqlite3_value_type) (apData[0])==SQLITE_INTEGER)) {
+	else if (optype == 3) {
 		/* UPDATE */
 		gint i;
 
-		if (SQLITE3_CALL (sqlite3_value_int) (apData[0]) != SQLITE3_CALL (sqlite3_value_int) (apData[1])) {
-			/* argc>1 and argv[0]==argv[1]: rowid is imposed by SQLite which is not supported */
-			return SQLITE_READONLY;
-		}
+		
 		for (i = 2; i < (nData - 1); i++) {
 			GValue *value;
 			GType type;
@@ -954,7 +1222,8 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 			/*g_print ("%d => %s\n", i, SQLITE3_CALL (sqlite3_value_text) (apData [i]));*/
 			type = gda_column_get_g_type (gda_data_model_describe_column (vtable->td->real_model, i - 2));
 			value = gda_value_new_from_string ((const gchar*) SQLITE3_CALL (sqlite3_value_text) (apData [i]), type);
-			res = gda_data_model_set_value_at (vtable->td->real_model, i - 2, rowid, value, &error);
+			res = gda_data_model_set_value_at (vtable->td->real_model, i - 2, rowid,
+							   value, &error);
 			gda_value_free (value);
 			if (!res) {
 				g_print ("Error: %s\n", error && error->message ? error->message : "???");
@@ -979,7 +1248,7 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 static int
 virtualBegin (sqlite3_vtab *tab)
 {
-	TRACE (tab);
+	TRACE (tab, NULL);
 	/* no documentation currently available, don't do anything */
 	return SQLITE_OK;
 }
@@ -987,7 +1256,7 @@ virtualBegin (sqlite3_vtab *tab)
 static int
 virtualSync (G_GNUC_UNUSED sqlite3_vtab *tab)
 {
-	TRACE (tab);
+	TRACE (tab, NULL);
 	/* no documentation currently available, don't do anything */
 	return SQLITE_OK;
 }
@@ -995,15 +1264,22 @@ virtualSync (G_GNUC_UNUSED sqlite3_vtab *tab)
 static int
 virtualCommit (G_GNUC_UNUSED sqlite3_vtab *tab)
 {
-	TRACE (tab);
-	/* no documentation currently available, don't do anything */
+	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;
+	}
+
 	return SQLITE_OK;
 }
 
 static int
 virtualRollback (G_GNUC_UNUSED sqlite3_vtab *tab)
 {	
-	TRACE (tab);
+	TRACE (tab, NULL);
 	/* no documentation currently available, don't do anything */
 	return SQLITE_OK;
 }
@@ -1011,7 +1287,7 @@ virtualRollback (G_GNUC_UNUSED sqlite3_vtab *tab)
 static int
 virtualRename(sqlite3_vtab *pVtab, const char *zNew)
 {
-	TRACE (pVtab);
+	TRACE (pVtab, NULL);
 	/* not yet analysed and implemented */
 	return SQLITE_OK;
 }
diff --git a/tests/data-models/.gitignore b/tests/data-models/.gitignore
index f21f8d3..1629cb3 100644
--- a/tests/data-models/.gitignore
+++ b/tests/data-models/.gitignore
@@ -5,4 +5,5 @@ check_model_copy
 check_pmodel
 check_empty_rs
 check_model_errors
+check_vcnc
 *.db
diff --git a/tests/data-models/Makefile.am b/tests/data-models/Makefile.am
index 1cd0291..7024556 100644
--- a/tests/data-models/Makefile.am
+++ b/tests/data-models/Makefile.am
@@ -8,8 +8,8 @@ AM_CPPFLAGS = \
 	-DTOP_SRC_DIR=\""$(top_srcdir)"\" \
 	-DTOP_BUILD_DIR=\""$(top_builddir)"\"
 
-check_PROGRAMS = check_model_import check_virtual check_data_proxy check_model_copy check_pmodel check_empty_rs check_model_errors
-TESTS = check_model_import check_virtual check_data_proxy check_model_copy check_pmodel check_empty_rs check_model_errors
+check_PROGRAMS = check_model_import check_virtual check_data_proxy check_model_copy check_pmodel check_empty_rs check_model_errors check_vcnc
+TESTS = check_model_import check_virtual check_data_proxy check_model_copy check_pmodel check_empty_rs check_model_errors check_vcnc
 
 common_sources = 
 
@@ -57,6 +57,15 @@ check_model_errors_LDADD = \
 	$(top_builddir)/tests/libgda-test-5.0.la \
 	$(LIBGDA_LIBS)
 
+check_vcnc_SOURCES = $(common_sources) check_vcnc.c
+check_vcnc_CFLAGS = \
+	-I$(top_srcdir)/libgda/sqlite \
+	-I$(top_srcdir)/libgda/sqlite/vcnc
+check_vcnc_LDADD = \
+	$(top_builddir)/libgda/libgda-4.0.la \
+	$(LIBGDA_LIBS)
+
+
 EXTRA_DIST = \
 	check_virtual.csv \
 	city.csv \
@@ -67,4 +76,5 @@ EXTRA_DIST = \
 	pmodel_data_locations.xml
 
 CLEANFILES = \
-	pmodel.db pmodel.db-journal
+	pmodel.db pmodel.db-journal \
+	vcnc.db vcnc.db-journal
diff --git a/tests/data-models/check_vcnc.c b/tests/data-models/check_vcnc.c
new file mode 100644
index 0000000..813e21e
--- /dev/null
+++ b/tests/data-models/check_vcnc.c
@@ -0,0 +1,449 @@
+#include <libgda/libgda.h>
+#include <virtual/libgda-virtual.h>
+#include <sql-parser/gda-sql-parser.h>
+#include <string.h>
+
+typedef enum {
+	ACTION_COMPARE,
+	ACTION_EXPORT
+} Action;
+
+static GdaDataModel *run_sql_select (GdaConnection *cnc, const gchar *sql,
+				     gboolean iter_only, GError **error);
+static gboolean run_sql_non_select (GdaConnection *cnc, const gchar *sql, GError **error);
+static GdaDataModel *assert_run_sql_select (GdaConnection *cnc, const gchar *sql,
+					    Action action, const gchar *compare_file);
+static void assert_run_sql_non_select (GdaConnection *cnc, const gchar *sql);
+GdaDataModel *load_from_file (const gchar *filename);
+static void assert_data_model_equel (GdaDataModel *model, GdaDataModel *ref);
+
+static GdaConnection *open_destination_connection (void);
+static void check_update_delete (GdaConnection *virtual);
+static void check_simultanous_select_random (GdaConnection *virtual);
+static void check_simultanous_select_forward (GdaConnection *virtual);
+
+int
+main (int argc, char *argv[])
+{
+	GError *error = NULL;	
+	GdaConnection *virtual, *out_cnc;
+	GdaVirtualProvider *provider;
+	gchar *file;
+	
+	/* set up test environment */
+        g_setenv ("GDA_TOP_BUILD_DIR", TOP_BUILD_DIR, 0);
+        g_setenv ("GDA_TOP_SRC_DIR", TOP_SRC_DIR, TRUE);
+	gda_init ();
+
+	provider = gda_vprovider_hub_new ();
+	virtual = gda_virtual_connection_open (provider, NULL);
+	g_assert (virtual);
+
+	/* load CSV data models */
+	GdaDataModel *country_model, *city_model;
+	GdaSet *options = gda_set_new_inline (2, "TITLE_AS_FIRST_LINE", G_TYPE_BOOLEAN, TRUE,
+					      "G_TYPE_2", G_TYPE_GTYPE, G_TYPE_INT);
+	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 (virtual), 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 (virtual), country_model, "country", &error)) 
+		g_error ("Add country model error: %s\n", error && error->message ? error->message : "no detail");
+
+	/* SQLite connection for outputs */
+        out_cnc = open_destination_connection ();
+
+	/* adding connections to the virtual connection */
+        if (!gda_vconnection_hub_add (GDA_VCONNECTION_HUB (virtual), out_cnc, "out", &error)) {
+                g_print ("Could not add connection to virtual connection: %s\n",
+                         error && error->message ? error->message : "No detail");
+                exit (1);
+        }
+
+	check_update_delete (virtual);
+
+	g_print ("*** Copying data into 'countries' virtual table...\n");
+	assert_run_sql_non_select (virtual, "INSERT INTO out.countries SELECT * FROM country");	
+
+	check_simultanous_select_random (virtual);
+	check_simultanous_select_forward (virtual);
+
+        gda_connection_close (virtual);
+        gda_connection_close (out_cnc);
+        return 0;
+}
+
+GdaConnection *
+open_destination_connection (void)
+{
+        /* create connection */
+        GdaConnection *cnc;
+        GError *error = NULL;
+        cnc = gda_connection_open_from_string ("SQLite", "DB_DIR=.;DB_NAME=vcnc",
+                                               NULL,
+                                               GDA_CONNECTION_OPTIONS_NONE,
+                                               &error);
+        if (!cnc) {
+                g_print ("Could not open connection to local SQLite database: %s\n",
+                         error && error->message ? error->message : "No detail");
+                exit (1);
+        }
+
+        /* table "cities" */
+        assert_run_sql_non_select (cnc, "DROP table IF EXISTS cities");
+        assert_run_sql_non_select (cnc, "CREATE table cities (name string not NULL primary key, countrycode string not null, population int)");
+
+        /* table "countries" */
+        assert_run_sql_non_select (cnc, "DROP table IF EXISTS countries");
+        assert_run_sql_non_select (cnc, "CREATE table countries (code string not null primary key, name string not null)");
+
+        return cnc;
+}
+
+static GdaDataModel *
+run_sql_select (GdaConnection *cnc, const gchar *sql, gboolean iter_only, GError **error)
+{
+	GdaStatement *stmt;
+	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,
+							    iter_only ? GDA_STATEMENT_MODEL_CURSOR_FORWARD :
+							    GDA_STATEMENT_MODEL_RANDOM_ACCESS,
+							    NULL, error);
+        g_object_unref (stmt);
+	return res;
+}
+
+
+static gboolean
+run_sql_non_select (GdaConnection *cnc, const gchar *sql, GError **error)
+{
+	GdaStatement *stmt;
+        gint nrows;
+        GdaSqlParser *parser;
+
+        parser = gda_connection_create_parser (cnc);
+        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)
+		return FALSE;
+	else
+		return TRUE;
+}
+
+static void
+assert_data_model_equel (GdaDataModel *model, GdaDataModel *ref)
+{
+	GdaDataComparator *comp;
+	GError *error = NULL;
+	comp = GDA_DATA_COMPARATOR (gda_data_comparator_new (ref, model));
+	if (! gda_data_comparator_compute_diff (comp, &error)) {
+		g_print ("Error comparing data models: %s\n",
+			 error && error->message ? error->message : "No detail");
+		exit (1);
+	}
+	if (gda_data_comparator_get_n_diffs (comp) > 0) {
+		g_print ("Data models differ!\n");
+		g_print ("Expected:\n");
+		gda_data_model_dump (ref, NULL);
+		g_print ("Got:\n");
+		gda_data_model_dump (model, NULL);
+		exit (1);
+	}
+	g_object_unref (comp);
+}
+
+static GdaDataModel *
+assert_run_sql_select (GdaConnection *cnc, const gchar *sql, Action action, const gchar *compare_file)
+{
+	GError *error = NULL;
+	GdaDataModel *model = run_sql_select (cnc, sql, FALSE, &error);
+	if (!model) {
+		g_print ("Error executing [%s]: %s\n",
+			 sql,
+			 error && error->message ? error->message : "No detail");
+		exit (1);
+	}
+
+	gda_data_model_dump (model, NULL);
+	if (compare_file) {
+		gchar *file;
+		file = g_build_filename (CHECK_FILES, "tests", "data-models", compare_file, NULL);
+		if (action == ACTION_EXPORT) {
+			if (! gda_data_model_export_to_file (model, GDA_DATA_MODEL_IO_DATA_ARRAY_XML,
+							     file,
+							     NULL, 0, NULL, 0, NULL, &error)) {
+				g_print ("Error exporting to file '%s': %s\n",
+					 file,
+					 error && error->message ? error->message : "No detail");
+				exit (1);
+			}
+			g_print ("Generated '%s'\n", file);
+		}
+		else if (action == ACTION_COMPARE) {
+			GdaDataModel *ref;
+			ref = load_from_file (compare_file);
+			assert_data_model_equel (model, ref);
+			g_object_unref (ref);
+		}
+		else
+			g_assert_not_reached ();
+		g_free (file);
+	}
+
+	return model;
+}
+
+static void
+assert_run_sql_non_select (GdaConnection *cnc, const gchar *sql)
+{
+	GError *error = NULL;
+	if (! run_sql_non_select (cnc, sql, &error)) {
+		g_print ("Error executing [%s]: %s\n",
+			 sql,
+			 error && error->message ? error->message : "No detail");
+		exit (1);
+	}
+}
+
+GdaDataModel *
+load_from_file (const gchar *filename)
+{
+	GdaDataModel *model;
+	gchar *file;
+
+	file = g_build_filename (CHECK_FILES, "tests", "data-models", filename, NULL);
+	model = gda_data_model_import_new_file (file, TRUE, NULL);
+	if (gda_data_model_import_get_errors (GDA_DATA_MODEL_IMPORT (model))) {
+		g_print ("Error loading file '%s'\n", file);
+		exit (1);
+	}
+	g_free (file);
+	return model;
+}
+
+static void
+move_iter_forward (GdaDataModelIter *iter, const gchar *iter_name, gint nb, GdaDataModel *ref, gint start_row)
+{
+	gint i;
+	for (i = 0; i < nb; i++) {
+		g_print ("*** moving iter %s forward... ", iter_name);
+		if (! gda_data_model_iter_move_next (iter)) {
+			g_print ("Could not move forward at step %d\n", i);
+			exit (1);
+		}
+		else {
+			const GValue *cvalue;
+			gchar *str;
+			cvalue = gda_data_model_iter_get_value_at (iter, 0);
+			str = gda_value_stringify (cvalue);
+			g_print ("Col0=[%s]", str);
+			g_free (str);
+
+			if (ref) {
+				if (gda_data_model_iter_get_row (iter) != (start_row + i)) {
+					g_print (" Wrong reported row %d instead of %d\n",
+						 gda_data_model_iter_get_row (iter),  start_row + i);
+					exit (1);
+				}
+				const GValue *rvalue;
+				rvalue = gda_data_model_get_value_at (ref, 0, start_row + i, NULL);
+				g_assert (rvalue);
+				gchar *str1, *str2;
+				str1 = gda_value_stringify (cvalue);
+				str2 = gda_value_stringify (rvalue);
+				if (strcmp (str1, str2)) {
+					g_print (" Wrong reported value [%s] instead of [%s]\n",
+						 str1, str2);
+					exit (1);
+				}
+				else
+					g_print (" Value Ok.");
+				g_free (str1);
+				g_free (str2);
+			}
+			g_print ("\n");
+		}
+	}
+}
+
+static void
+check_simultanous_select_random (GdaConnection *virtual)
+{
+	GdaDataModel *m1, *m2;
+	GdaDataModel *refA = NULL, *refB = NULL;
+	GdaDataModelIter *iter1, *iter2;
+	GError *error = NULL;
+
+	g_print ("*** simultaneous SELECT RANDOM 1\n");
+	m1 = run_sql_select (virtual, "SELECT * FROM countries WHERE code LIKE 'A%' ORDER BY code",
+			     FALSE, &error);
+	if (!m1) {
+		g_print ("Could not execute SELECT with forward iter only (1): %s\n",
+                         error && error->message ? error->message : "No detail");
+                exit (1);
+	}
+
+	g_print ("*** simultaneous SELECT RANDOM 2\n");
+	m2 = run_sql_select (virtual, "SELECT * FROM countries WHERE code LIKE 'B%' ORDER BY code",
+			     FALSE, &error);
+	if (!m2) {
+		g_print ("Could not execute SELECT with forward iter only (2): %s\n",
+                         error && error->message ? error->message : "No detail");
+                exit (1);
+	}
+
+	gda_data_model_dump (m2, NULL);
+	gda_data_model_dump (m1, NULL);
+
+/*#define EXPORT*/
+#ifdef EXPORT
+	gchar *file;
+	file = g_build_filename (CHECK_FILES, "tests", "data-models", "countriesA.xml", NULL);
+	if (! gda_data_model_export_to_file (m1, GDA_DATA_MODEL_IO_DATA_ARRAY_XML,
+					     file,
+					     NULL, 0, NULL, 0, NULL, &error)) {
+		g_print ("Error exporting to file '%s': %s\n",
+			 file,
+			 error && error->message ? error->message : "No detail");
+		exit (1);
+	}
+	g_print ("Generated '%s'\n", file);
+	g_free (file);
+
+	file = g_build_filename (CHECK_FILES, "tests", "data-models", "countriesB.xml", NULL);
+	if (! gda_data_model_export_to_file (m2, GDA_DATA_MODEL_IO_DATA_ARRAY_XML,
+					     file,
+					     NULL, 0, NULL, 0, NULL, &error)) {
+		g_print ("Error exporting to file '%s': %s\n",
+			 file,
+			 error && error->message ? error->message : "No detail");
+		exit (1);
+	}
+	g_print ("Generated '%s'\n", file);
+	g_free (file);
+#else
+	refA = load_from_file ("countriesA.xml");
+	assert_data_model_equel (m1, refA);
+
+	refB = load_from_file ("countriesB.xml");
+	assert_data_model_equel (m2, refB);
+#endif
+
+	iter1 = gda_data_model_create_iter (m1);
+	g_print ("*** simultaneous iter 1 %p\n", iter1);
+
+	iter2 = gda_data_model_create_iter (m2);
+	g_print ("*** simultaneous iter 2 %p\n", iter2);
+
+	move_iter_forward (iter1, "iter1", 10, refA, 0);
+	move_iter_forward (iter2, "iter2", 3, refB, 0);
+	move_iter_forward (iter1, "iter1", 3, refA, 10);
+	move_iter_forward (iter2, "iter2", 2, refB, 3);
+
+	g_object_unref (iter1);
+	g_object_unref (iter2);
+
+	g_object_unref (m1);
+	g_object_unref (m2);
+#ifndef EXPORT
+	g_object_unref (refA);
+	g_object_unref (refB);
+#endif
+}
+
+static void
+check_simultanous_select_forward (GdaConnection *virtual)
+{
+	GdaDataModel *m1, *m2;
+	GdaDataModel *refA, *refB;
+	GdaDataModelIter *iter1, *iter2;
+	GError *error = NULL;
+
+	g_print ("*** simultaneous SELECT FORWARD 1\n");
+	m1 = run_sql_select (virtual, "SELECT * FROM countries WHERE code LIKE 'A%' ORDER BY code",
+			     TRUE, &error);
+	if (!m1) {
+		g_print ("Could not execute SELECT with forward iter only (1): %s\n",
+                         error && error->message ? error->message : "No detail");
+                exit (1);
+	}
+
+	g_print ("*** simultaneous SELECT FORWARD 2\n");
+	m2 = run_sql_select (virtual, "SELECT * FROM countries WHERE code LIKE 'B%' ORDER BY code",
+			     TRUE, &error);
+	if (!m2) {
+		g_print ("Could not execute SELECT with forward iter only (2): %s\n",
+                         error && error->message ? error->message : "No detail");
+                exit (1);
+	}
+
+	refA = load_from_file ("countriesA.xml");
+	refB = load_from_file ("countriesB.xml");
+
+	iter1 = gda_data_model_create_iter (m1);
+	g_print ("*** simultaneous iter 1 %p\n", iter1);
+
+	iter2 = gda_data_model_create_iter (m2);
+	g_print ("*** simultaneous iter 2 %p\n", iter2);
+
+	move_iter_forward (iter1, "iter1", 10, refA, 0);
+	if (gda_data_model_iter_move_prev (iter1)) {
+		g_print ("Iter should not be allowed to move backward!\n");
+                exit (1);
+	}
+	move_iter_forward (iter2, "iter2", 3, refB, 0);
+	move_iter_forward (iter1, "iter1", 3, refA, 10);
+	move_iter_forward (iter2, "iter2", 2, refB, 3);
+
+	g_object_unref (iter1);
+	g_object_unref (iter2);
+
+	g_object_unref (refA);
+	g_object_unref (refB);
+
+	g_object_unref (m1);
+	g_object_unref (m2);
+}
+
+
+static void
+check_update_delete (GdaConnection *virtual)
+{
+	/* Check DELETE and UPDATE */
+	g_print ("*** Copying data into virtual 'cities' table...\n");
+	assert_run_sql_non_select (virtual, "INSERT INTO out.cities SELECT * FROM city WHERE population >= 500000");
+	g_print ("*** Showing list of cities WHERE population >= 1000000\n");
+	assert_run_sql_select (virtual, "SELECT * FROM out.cities WHERE population >= 1000000 "
+			       "ORDER BY name", ACTION_COMPARE, "cities1.xml");
+
+	g_print ("*** Deleting data where population < 2000000...\n");
+	assert_run_sql_non_select (virtual, "DELETE FROM out.cities WHERE population < 2000000");
+
+	g_print ("*** Showing (shorter) list of cities WHERE population >= 1000000\n");
+	assert_run_sql_select (virtual, "SELECT * FROM out.cities WHERE population >= 1000000 "
+			       "ORDER BY name", ACTION_COMPARE, "cities2.xml");
+
+	g_print ("*** Updating data where population > 3000000...\n");
+	assert_run_sql_non_select (virtual, "UPDATE out.cities SET population = 3000000 WHERE "
+				   "population >= 3000000");
+	
+	g_print ("*** Showing list of cities WHERE population >= 2100000\n");
+	assert_run_sql_select (virtual, "SELECT * FROM out.cities WHERE population >= 2100000 "
+			       "ORDER BY name", ACTION_COMPARE, "cities3.xml");
+}
diff --git a/tests/data-models/cities1.xml b/tests/data-models/cities1.xml
new file mode 100644
index 0000000..1a8abf0
--- /dev/null
+++ b/tests/data-models/cities1.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<gda_array id="EXPORT" name="Donn&#xE9;es export&#xE9;es">
+  <gda_array_field id="FI0" name="name" title="name" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_field id="FI1" name="countrycode" title="countrycode" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_field id="FI2" name="population" title="population" dbms_type="integer" gdatype="int" nullok="TRUE"/>
+  <gda_array_data>
+    <gda_array_row>
+      <gda_value>Baku</gda_value>
+      <gda_value>AZE</gda_value>
+      <gda_value>1787800</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Brisbane</gda_value>
+      <gda_value>AUS</gda_value>
+      <gda_value>1291117</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Buenos Aires</gda_value>
+      <gda_value>ARG</gda_value>
+      <gda_value>2982146</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Chittagong</gda_value>
+      <gda_value>BGD</gda_value>
+      <gda_value>1392860</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>C&#xF3;rdoba</gda_value>
+      <gda_value>ARG</gda_value>
+      <gda_value>1157507</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Dhaka</gda_value>
+      <gda_value>BGD</gda_value>
+      <gda_value>3612850</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Kabul</gda_value>
+      <gda_value>AFG</gda_value>
+      <gda_value>1780000</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>La Matanza</gda_value>
+      <gda_value>ARG</gda_value>
+      <gda_value>1266461</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Luanda</gda_value>
+      <gda_value>AGO</gda_value>
+      <gda_value>2022000</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Melbourne</gda_value>
+      <gda_value>AUS</gda_value>
+      <gda_value>2865329</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Perth</gda_value>
+      <gda_value>AUS</gda_value>
+      <gda_value>1096829</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Sofija</gda_value>
+      <gda_value>BGR</gda_value>
+      <gda_value>1122302</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Sydney</gda_value>
+      <gda_value>AUS</gda_value>
+      <gda_value>3276207</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Wien</gda_value>
+      <gda_value>AUT</gda_value>
+      <gda_value>1608144</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Yerevan</gda_value>
+      <gda_value>ARM</gda_value>
+      <gda_value>1248700</gda_value>
+    </gda_array_row>
+  </gda_array_data>
+</gda_array>
diff --git a/tests/data-models/cities2.xml b/tests/data-models/cities2.xml
new file mode 100644
index 0000000..afc1112
--- /dev/null
+++ b/tests/data-models/cities2.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<gda_array id="EXPORT" name="Donn&#xE9;es export&#xE9;es">
+  <gda_array_field id="FI0" name="name" title="name" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_field id="FI1" name="countrycode" title="countrycode" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_field id="FI2" name="population" title="population" dbms_type="integer" gdatype="int" nullok="TRUE"/>
+  <gda_array_data>
+    <gda_array_row>
+      <gda_value>Buenos Aires</gda_value>
+      <gda_value>ARG</gda_value>
+      <gda_value>2982146</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Dhaka</gda_value>
+      <gda_value>BGD</gda_value>
+      <gda_value>3612850</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Luanda</gda_value>
+      <gda_value>AGO</gda_value>
+      <gda_value>2022000</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Melbourne</gda_value>
+      <gda_value>AUS</gda_value>
+      <gda_value>2865329</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Sydney</gda_value>
+      <gda_value>AUS</gda_value>
+      <gda_value>3276207</gda_value>
+    </gda_array_row>
+  </gda_array_data>
+</gda_array>
diff --git a/tests/data-models/cities3.xml b/tests/data-models/cities3.xml
new file mode 100644
index 0000000..7c45c8e
--- /dev/null
+++ b/tests/data-models/cities3.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<gda_array id="EXPORT" name="Donn&#xE9;es export&#xE9;es">
+  <gda_array_field id="FI0" name="name" title="name" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_field id="FI1" name="countrycode" title="countrycode" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_field id="FI2" name="population" title="population" dbms_type="integer" gdatype="int" nullok="TRUE"/>
+  <gda_array_data>
+    <gda_array_row>
+      <gda_value>Buenos Aires</gda_value>
+      <gda_value>ARG</gda_value>
+      <gda_value>2982146</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Dhaka</gda_value>
+      <gda_value>BGD</gda_value>
+      <gda_value>3000000</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Melbourne</gda_value>
+      <gda_value>AUS</gda_value>
+      <gda_value>2865329</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>Sydney</gda_value>
+      <gda_value>AUS</gda_value>
+      <gda_value>3000000</gda_value>
+    </gda_array_row>
+  </gda_array_data>
+</gda_array>
diff --git a/tests/data-models/countriesA.xml b/tests/data-models/countriesA.xml
new file mode 100644
index 0000000..743fc93
--- /dev/null
+++ b/tests/data-models/countriesA.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<gda_array id="EXPORT" name="Exported Data">
+  <gda_array_field id="FI0" name="code" title="code" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_field id="FI1" name="name" title="name" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_data>
+    <gda_array_row>
+      <gda_value>ABW</gda_value>
+      <gda_value>Aruba</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>AFG</gda_value>
+      <gda_value>Afghanistan</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>AGO</gda_value>
+      <gda_value>Angola</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>AIA</gda_value>
+      <gda_value>Anguilla</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>ALB</gda_value>
+      <gda_value>Albania</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>AND</gda_value>
+      <gda_value>Andorra</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>ANT</gda_value>
+      <gda_value>Netherlands Antilles</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>ARE</gda_value>
+      <gda_value>United Arab Emirates</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>ARG</gda_value>
+      <gda_value>Argentina</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>ARM</gda_value>
+      <gda_value>Armenia</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>ASM</gda_value>
+      <gda_value>American Samoa</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>ATA</gda_value>
+      <gda_value>Antarctica</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>ATF</gda_value>
+      <gda_value>French Southern territories</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>ATG</gda_value>
+      <gda_value>Antigua and Barbuda</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>AUS</gda_value>
+      <gda_value>Australia</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>AUT</gda_value>
+      <gda_value>Austria</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>AZE</gda_value>
+      <gda_value>Azerbaijan</gda_value>
+    </gda_array_row>
+  </gda_array_data>
+</gda_array>
diff --git a/tests/data-models/countriesB.xml b/tests/data-models/countriesB.xml
new file mode 100644
index 0000000..834ea06
--- /dev/null
+++ b/tests/data-models/countriesB.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<gda_array id="EXPORT" name="Exported Data">
+  <gda_array_field id="FI0" name="code" title="code" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_field id="FI1" name="name" title="name" dbms_type="string" gdatype="string" nullok="TRUE"/>
+  <gda_array_data>
+    <gda_array_row>
+      <gda_value>BDI</gda_value>
+      <gda_value>Burundi</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>BEL</gda_value>
+      <gda_value>Belgium</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>BEN</gda_value>
+      <gda_value>Benin</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>BFA</gda_value>
+      <gda_value>Burkina Faso</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>BGD</gda_value>
+      <gda_value>Bangladesh</gda_value>
+    </gda_array_row>
+    <gda_array_row>
+      <gda_value>BGR</gda_value>
+      <gda_value>Bulgaria</gda_value>
+    </gda_array_row>
+  </gda_array_data>
+</gda_array>



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