libgda r3117 - in trunk: . doc/C doc/C/tmpl libgda libgda/sql-parser providers/postgres providers/skel-implementation/capi tests/parser



Author: vivien
Date: Sun Apr  6 11:47:57 2008
New Revision: 3117
URL: http://svn.gnome.org/viewvc/libgda?rev=3117&view=rev

Log:
2008-04-06  Vivien Malerba <malerba gnome-db org>

	* libgda/gda-server-operation.c:
	* libgda/gda-easy.[ch]: modifications for bug #525877
	* tests/parser/Makefile.am:
	* tests/parser/check_validation.c:
	* tests/parser/check_normalization.c:
	* tests/parser/check_dml_comp.c: separated the validation check into a strict validation
	check, a DML command computation from a SELECT command and a "normalization" check
	* tests/parser/testvalid.xml: more test cases
	* libgda/sql-parser/: improved GsaSqlStatement structure check, validation, and added
	gda_sql_statement_normalize() which for example will expand all the '*' fields into the actual
	list of fields in the table.
	* libgda/gda-util.[ch]: added gda_identifier_equal() to compare SQL identifiers in
	a safe way (case insensitive comparison unless double quoted) and gda_identifier_hash()
	which computes a hash for an identifier (both can be used as base functions for a GHashTable)
	* providers/skel-implementation/capi/parser.y:
	* providers/postgres/parser.y:
	* libgda/sql-parser/parser.y: modified parser to allow the "customers"."id" style syntax
	* doc/C: misc doc updates


Added:
   trunk/tests/parser/check_dml_comp.c
   trunk/tests/parser/check_normalization.c
Modified:
   trunk/ChangeLog
   trunk/doc/C/libgda-4.0-sections.txt
   trunk/doc/C/tmpl/gda-sql-statement.sgml
   trunk/doc/C/tmpl/gda-statement.sgml
   trunk/libgda/gda-data-model-query.c
   trunk/libgda/gda-easy.c
   trunk/libgda/gda-easy.h
   trunk/libgda/gda-meta-struct.c
   trunk/libgda/gda-server-operation.c
   trunk/libgda/gda-statement.c
   trunk/libgda/gda-statement.h
   trunk/libgda/gda-util.c
   trunk/libgda/gda-util.h
   trunk/libgda/sql-parser/gda-statement-struct-decl.h
   trunk/libgda/sql-parser/gda-statement-struct-parts.c
   trunk/libgda/sql-parser/gda-statement-struct-parts.h
   trunk/libgda/sql-parser/gda-statement-struct-util.c
   trunk/libgda/sql-parser/gda-statement-struct-util.h
   trunk/libgda/sql-parser/gda-statement-struct.c
   trunk/libgda/sql-parser/gda-statement-struct.h
   trunk/libgda/sql-parser/parser.y
   trunk/providers/postgres/parser.y
   trunk/providers/skel-implementation/capi/parser.y
   trunk/tests/parser/   (props changed)
   trunk/tests/parser/Makefile.am
   trunk/tests/parser/check_validation.c
   trunk/tests/parser/testvalid.xml

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	Sun Apr  6 11:47:57 2008
@@ -1083,6 +1083,7 @@
 gda_statement_is_useless
 gda_statement_check_structure
 gda_statement_check_validity
+gda_statement_normalize
 <SUBSECTION Standard>
 GDA_IS_STATEMENT
 GDA_STATEMENT
@@ -1144,6 +1145,7 @@
 gda_sql_statement_check_structure
 gda_sql_statement_check_validity
 gda_sql_statement_check_clean
+gda_sql_statement_normalize
 <SUBSECTION>
 GdaSqlAnyPart
 GdaSqlAnyPartType

Modified: trunk/doc/C/tmpl/gda-sql-statement.sgml
==============================================================================
--- trunk/doc/C/tmpl/gda-sql-statement.sgml	(original)
+++ trunk/doc/C/tmpl/gda-sql-statement.sgml	Sun Apr  6 11:47:57 2008
@@ -146,6 +146,17 @@
 @stmt: 
 
 
+<!-- ##### FUNCTION gda_sql_statement_normalize ##### -->
+<para>
+
+</para>
+
+ stmt: 
+ cnc: 
+ error: 
+ Returns: 
+
+
 <!-- ##### STRUCT GdaSqlAnyPart ##### -->
 <para>
 Base structure of which all structures (except #GdaSqlStatement) "inherit". It identifies, for each structure, its type and
@@ -307,12 +318,12 @@
 @any: inheritance structure
 @distinct: TRUE if a DISTINCT clause applies
 @distinct_expr: expression on which the distinct applies, or %NULL
- expr_list: list of expressions, one for each column in the data set returned by the execution of the statement
+ expr_list: list of expressions (as #GdaSqlSelectField pointers), one for each column in the data set returned by the execution of the statement
 @from: target(s) of the SELECT statement
 @where_cond: condition expression filtering the resulting data set (WHERE condition)
- group_by: grouping expressions
+ group_by: grouping expressions (as #GdaSqlExpr pointers)
 @having_cond: condition expression filtering the groupped expressions (HAVING condition)
- order_by: ordering expressions
+ order_by: ordering expressions (as #GdaSqlSelectOrder pointers)
 @limit_count: size limiting expression (LIMIT clause)
 @limit_offset: when @limit_count is defined, the start offset for the limit
 
@@ -1078,7 +1089,9 @@
 <!-- ##### STRUCT GdaSqlSelectField ##### -->
 <para>
   This structure represents a selected item in a SELECT statement (when executed, the returned data set
-  will have one column per selected item).
+  will have one column per selected item). Note that the @table_name and 
+  @table field parts <emphasis>will be</emphasis> overwritten by &LIBGDA;,
+  set the value of @expr->value instead.
 </para>
 
 @any: inheritance structure
@@ -1154,7 +1167,9 @@
 <!-- ##### STRUCT GdaSqlSelectTarget ##### -->
 <para>
   This structure represents a target used to fetch data from in a SELECT statement; it can represent a table or
-  a sub select.
+  a sub select. Note that the @table_name
+  part <emphasis>will be</emphasis> overwritten by &LIBGDA;,
+  set the value of @expr->value instead.
 </para>
 
 @any: inheritance structure

Modified: trunk/doc/C/tmpl/gda-statement.sgml
==============================================================================
--- trunk/doc/C/tmpl/gda-statement.sgml	(original)
+++ trunk/doc/C/tmpl/gda-statement.sgml	Sun Apr  6 11:47:57 2008
@@ -206,3 +206,14 @@
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_statement_normalize ##### -->
+<para>
+
+</para>
+
+ stmt: 
+ cnc: 
+ error: 
+ Returns: 
+
+

Modified: trunk/libgda/gda-data-model-query.c
==============================================================================
--- trunk/libgda/gda-data-model-query.c	(original)
+++ trunk/libgda/gda-data-model-query.c	Sun Apr  6 11:47:57 2008
@@ -29,8 +29,10 @@
 #include <libgda/gda-data-model-extra.h>
 #include <libgda/gda-data-model-iter.h>
 #include <libgda/gda-statement.h>
+#include <libgda/gda-meta-struct.h>
 #include <libgda/gda-set.h>
 #include <libgda/gda-holder.h>
+#include <libgda/gda-util.h>
 #include <sql-parser/gda-sql-statement.h>
 
 struct _GdaDataModelQueryPrivate {
@@ -427,10 +429,6 @@
 				}
 				if (qindex == SEL_QUERY) {
 					/* SELECT statement */
-
-					/* expand any target.* field => add a gda_sql_statement_select_expand_all () method */
-					TO_IMPLEMENT;
-
 					if (model->priv->params[qindex])
 						g_signal_connect (model->priv->params[qindex], "holder_changed",
 								  G_CALLBACK (holder_changed_cb), model);
@@ -777,8 +775,6 @@
 static void
 create_columns (GdaDataModelQuery *model)
 {
-	GSList *fields = NULL;
-
 	if (model->priv->columns)
 		return;
 	if (! model->priv->statements[SEL_QUERY])
@@ -788,30 +784,30 @@
 	 * statement's GdaSqlSelectFields and if a name and GType can be determined, then use them, otherwise
 	 * use "column%d" and G_TYPE_STRING, and let the user modify that directly using gda_data_model_describe () 
 	 */
-	TO_IMPLEMENT;
-	if (0) {
+	GdaSqlStatement *sqlst;
+	g_object_get (G_OBJECT (model->priv->statements[SEL_QUERY]), "structure", &sqlst, NULL);
+
+	if (gda_sql_statement_normalize (sqlst, model->priv->cnc, NULL)) {
+		GdaSqlStatementSelect *selst = (GdaSqlStatementSelect*) sqlst->contents;
 		GSList *list;
-		gboolean allok = TRUE;
 		
-		for (list = fields; list; list = list->next) {
-		}
-		if (! allok) 
-			return;
-
-		list = fields;
-		while (list) {
+		for (list = selst->expr_list; list; list = list->next) {
+			GdaSqlSelectField *field = (GdaSqlSelectField*) list->data;
 			GdaColumn *col;
 
 			col = gda_column_new ();
-			gda_column_set_name (col, "col");
-			gda_column_set_title (col, "col");
-			gda_column_set_g_type (col, G_TYPE_STRING);
+			if (field->validity_meta_table_column) {
+				gda_column_set_name (col, field->validity_meta_table_column->column_name);
+				gda_column_set_title (col, field->validity_meta_table_column->column_name);
+				gda_column_set_g_type (col, field->validity_meta_table_column->gtype);
+			}
+			else {
+				gda_column_set_name (col, "col");
+				gda_column_set_title (col, "col");
+				gda_column_set_g_type (col, G_TYPE_STRING);
+			}
 			model->priv->columns = g_slist_append (model->priv->columns, col);
-			
-			list = g_slist_next (list);
 		}
-
-		g_slist_free (fields);
 	}
 	else {
 		if (model->priv->data) {
@@ -835,6 +831,7 @@
 			model->priv->emit_reset = TRUE;
 		}
 	}
+	gda_sql_statement_free (sqlst);
 
 	if (model->priv->columns && model->priv->emit_reset) {
 		model->priv->emit_reset = FALSE;
@@ -1474,12 +1471,15 @@
  * If @target is %NULL, then an error will be returned if @model's SELECT query has more than
  * one target.
  *
- * Returns: TRUE if the INSERT, DELETE and UPDATE statements have been computed.
+ * Returns: TRUE at least one of the INSERT, DELETE and UPDATE statements has been computed
  */
 gboolean
 gda_data_model_query_compute_modification_queries (GdaDataModelQuery *model, const gchar *target, 
 						   GdaDataModelQueryOptions options, GError **error)
 {
+	gint i;
+	gboolean require_pk = TRUE;
+	GdaStatement *ins, *upd, *del;
 	g_return_val_if_fail (GDA_IS_DATA_MODEL_QUERY (model), FALSE);
 	g_return_val_if_fail (model->priv, FALSE);
 
@@ -1490,6 +1490,23 @@
 		return FALSE;
 	}
 
-	TO_IMPLEMENT;
+	for (i = SEL_QUERY; i <= DEL_QUERY; i++) {
+		if (model->priv->statements[i])
+			forget_statement (model, model->priv->statements[i]);
+	}
+	
+	if (options & GDA_DATA_MODEL_QUERY_OPTION_USE_ALL_FIELDS_IF_NO_PK)
+		require_pk = FALSE;
+	if (gda_compute_dml_statements (model->priv->cnc, model->priv->statements[SEL_QUERY], require_pk, 
+					&ins, &upd, &del, error)) {
+		
+		g_object_set (G_OBJECT (model), "insert_query", ins, 
+			      "update-query", upd, "delete-query", del, NULL);
+		if (ins) g_object_unref (ins);
+		if (upd) g_object_unref (upd);
+		if (del) g_object_unref (del);
+		return TRUE;
+	}
+
 	return FALSE;
 }

Modified: trunk/libgda/gda-easy.c
==============================================================================
--- trunk/libgda/gda-easy.c	(original)
+++ trunk/libgda/gda-easy.c	Sun Apr  6 11:47:57 2008
@@ -64,7 +64,7 @@
 		op = gda_server_provider_create_operation (prov, NULL, GDA_SERVER_OPERATION_CREATE_DB, 
 							   NULL, error);
 		if (op) {
-			g_object_set_data (G_OBJECT (op), "_gda_provider_obj", prov);
+			g_object_set_data_full (G_OBJECT (op), "_gda_provider_obj", g_object_ref (prov), g_object_unref);
 			if (db_name)
 				gda_server_operation_set_value_at (op, db_name, NULL, "/DB_DEF_P/DB_NAME");
 		}
@@ -95,9 +95,8 @@
 	if (provider) 
 		return gda_server_provider_perform_operation (provider, NULL, op, error);
 	else {
-		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_PROVIDER_NOT_FOUND_ERROR, 
-			     _("Could not find operation's associated provider, "
-			       "did you use gda_prepare_create_database() ?")); 
+		g_warning ("Could not find operation's associated provider, "
+			   "did you use gda_prepare_create_database() ?");
 		return FALSE;
 	}
 }
@@ -132,7 +131,7 @@
 		op = gda_server_provider_create_operation (prov, NULL, GDA_SERVER_OPERATION_DROP_DB, 
 							   NULL, error);
 		if (op) {
-			g_object_set_data (G_OBJECT (op), "_gda_provider_obj", prov);
+			g_object_set_data_full (G_OBJECT (op), "_gda_provider_obj", g_object_ref (prov), g_object_unref);
 			if (db_name)
 				gda_server_operation_set_value_at (op, db_name, NULL, "/DB_DESC_P/DB_NAME");
 		}
@@ -163,9 +162,8 @@
 	if (provider) 
 		return gda_server_provider_perform_operation (provider, NULL, op, error);
 	else {
-		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_PROVIDER_NOT_FOUND_ERROR, 
-			     _("Could not find operation's associated provider, "
-			       "did you use gda_prepare_drop_database() ?")); 
+		g_warning ("Could not find operation's associated provider, "
+			   "did you use gda_prepare_drop_database() ?"); 
 		return FALSE;
 	}
 }
@@ -264,7 +262,7 @@
 		
 	g_return_val_if_fail (gda_connection_is_opened (cnc), FALSE);	
 	
-	server = gda_connection_get_provider_obj(cnc);
+	server = gda_connection_get_provider_obj (cnc);
 	
 	if (!table_name) {
 		g_set_error (error, GDA_EASY_ERROR, GDA_EASY_OBJECT_NAME_ERROR, 
@@ -318,8 +316,7 @@
 	
 		va_end (args);
 		
-		g_object_set_data (G_OBJECT (op), "_gda_connection", cnc);
-		g_object_set_data (G_OBJECT (op), "_gda_provider_obj", server);
+		g_object_set_data_full (G_OBJECT (op), "_gda_connection", g_object_ref (cnc), g_object_unref);
 		
 		return op;		
 	}
@@ -345,16 +342,18 @@
 gda_perform_create_table (GdaServerOperation *op, GError **error)
 {
 	GdaConnection *cnc;
-	GdaServerProvider *server;
 	
 	g_return_val_if_fail (GDA_IS_SERVER_OPERATION (op), FALSE);	
-	g_return_val_if_fail (gda_server_operation_get_op_type (op) ==
-						  GDA_SERVER_OPERATION_CREATE_TABLE, FALSE);
+	g_return_val_if_fail (gda_server_operation_get_op_type (op) == GDA_SERVER_OPERATION_CREATE_TABLE, FALSE);
 	
-	server = GDA_SERVER_PROVIDER (g_object_get_data (G_OBJECT (op), "_gda_provider_obj"));
-	cnc = GDA_CONNECTION (g_object_get_data (G_OBJECT (op), "_gda_connection"));
-	
-	return gda_server_provider_perform_operation (server, cnc, op, error);
+	cnc = g_object_get_data (G_OBJECT (op), "_gda_connection");
+	if (cnc) 
+		return gda_server_provider_perform_operation (gda_connection_get_provider_obj (cnc), cnc, op, error);
+	else {
+		g_warning ("Could not find operation's associated connection, "
+			   "did you use gda_prepare_create_table() ?"); 
+		return FALSE;
+	}
 }
 
 /**
@@ -386,8 +385,7 @@
 		
 		if (gda_server_operation_set_value_at (op, table_name, 
 						       error, "/TABLE_DESC_P/TABLE_NAME")) {
-			g_object_set_data (G_OBJECT (op), "_gda_connection", cnc);
-			g_object_set_data (G_OBJECT (op), "_gda_provider_obj", server);
+			g_object_set_data_full (G_OBJECT (op), "_gda_connection", g_object_ref (cnc), g_object_unref);
 			return op;
 		}
 		else 
@@ -410,16 +408,18 @@
 gda_perform_drop_table (GdaServerOperation *op, GError **error)
 {
 	GdaConnection *cnc;
-	GdaServerProvider *server;
-
-	g_return_val_if_fail (GDA_IS_SERVER_OPERATION (op), FALSE);
-	g_return_val_if_fail (gda_server_operation_get_op_type (op) ==
-			      GDA_SERVER_OPERATION_DROP_TABLE, FALSE);
 	
-	server = GDA_SERVER_PROVIDER (g_object_get_data (G_OBJECT (op), "_gda_provider_obj"));
-	cnc = GDA_CONNECTION (g_object_get_data (G_OBJECT (op), "_gda_connection"));
+	g_return_val_if_fail (GDA_IS_SERVER_OPERATION (op), FALSE);	
+	g_return_val_if_fail (gda_server_operation_get_op_type (op) == GDA_SERVER_OPERATION_DROP_TABLE, FALSE);
 	
-	return gda_server_provider_perform_operation (server, cnc, op, error);
+	cnc = g_object_get_data (G_OBJECT (op), "_gda_connection");
+	if (cnc) 
+		return gda_server_provider_perform_operation (gda_connection_get_provider_obj (cnc), cnc, op, error);
+	else {
+		g_warning ("Could not find operation's associated connection, "
+			   "did you use gda_prepare_drop_table() ?"); 
+		return FALSE;
+	}
 }
 
 static guint

Modified: trunk/libgda/gda-easy.h
==============================================================================
--- trunk/libgda/gda-easy.h	(original)
+++ trunk/libgda/gda-easy.h	Sun Apr  6 11:47:57 2008
@@ -77,7 +77,7 @@
 /*
  * Tables creation and destruction
  */
-GdaServerOperation *gda_prepare_create_table	       (GdaConnection *cnc, const gchar *table_name, GError **error, ...);
+GdaServerOperation *gda_prepare_create_table	      (GdaConnection *cnc, const gchar *table_name, GError **error, ...);
 gboolean            gda_perform_create_table          (GdaServerOperation *op, GError **error);
 GdaServerOperation *gda_prepare_drop_table            (GdaConnection *cnc, const gchar *table_name, GError **error);
 gboolean            gda_perform_drop_table            (GdaServerOperation *op, GError **error);

Modified: trunk/libgda/gda-meta-struct.c
==============================================================================
--- trunk/libgda/gda-meta-struct.c	(original)
+++ trunk/libgda/gda-meta-struct.c	Sun Apr  6 11:47:57 2008
@@ -793,9 +793,9 @@
 		for (list = mstruct->db_objects; list; list = list->next) {
 			GdaMetaDbObject *dbo;
 			dbo = GDA_META_DB_OBJECT (list->data);
-			if (!strcmp (dbo->obj_name, obj_name) &&
-			    (!obj_schema || !strcmp (dbo->obj_schema, obj_schema)) &&
-			    (!obj_catalog || !strcmp (dbo->obj_catalog, obj_catalog)))
+			if (gda_identifier_equal (dbo->obj_name, obj_name) &&
+			    (!obj_schema || gda_identifier_equal (dbo->obj_schema, obj_schema)) &&
+			    (!obj_catalog || gda_identifier_equal (dbo->obj_catalog, obj_catalog)))
 				matching = g_slist_prepend (matching, dbo);
 		}
 
@@ -828,6 +828,7 @@
 {
 	GSList *list;
 	const gchar *cname;
+
 	g_return_val_if_fail (GDA_IS_META_STRUCT (mstruct), NULL);
 	g_return_val_if_fail (table, NULL);
 	g_return_val_if_fail (col_name && (G_VALUE_TYPE (col_name) == G_TYPE_STRING), NULL);
@@ -835,7 +836,7 @@
 
 	for (list = table->columns; list; list = list->next) {
 		GdaMetaTableColumn *tcol = GDA_META_TABLE_COLUMN (list->data);
-		if (!strcmp (tcol->column_name, cname))
+		if (gda_identifier_equal (tcol->column_name, cname))
 			return tcol;
 	}
 	return NULL;

Modified: trunk/libgda/gda-server-operation.c
==============================================================================
--- trunk/libgda/gda-server-operation.c	(original)
+++ trunk/libgda/gda-server-operation.c	Sun Apr  6 11:47:57 2008
@@ -166,11 +166,11 @@
 	g_object_class_install_property (object_class, PROP_CNC,
 					 g_param_spec_object ("connection", NULL, NULL, 
 							      GDA_TYPE_CONNECTION,
-							      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+							      G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 	g_object_class_install_property (object_class, PROP_PROV,
 					 g_param_spec_object ("provider_obj", NULL, NULL, 
-                                                               GDA_TYPE_SERVER_PROVIDER,
-							       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+							      GDA_TYPE_SERVER_PROVIDER,
+							      G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 	g_object_class_install_property (object_class, PROP_SPEC_FILE,
 					 g_param_spec_string ("spec_file", NULL, NULL, 
 							      NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
@@ -511,7 +511,7 @@
 			if (op->priv->cnc)
 				g_object_unref (op->priv->cnc);
 
-			op->priv->cnc = GDA_CONNECTION( g_value_get_object (value));
+			op->priv->cnc = GDA_CONNECTION (g_value_get_object (value));
 			op->priv->cnc_set = TRUE;
 
 			if (op->priv->cnc) {
@@ -641,6 +641,12 @@
 	op = GDA_SERVER_OPERATION (object);
 	if (op->priv) {
 		switch (param_id) {
+		case PROP_CNC:
+			g_value_set_object (value, op->priv->cnc);
+			break;
+		case PROP_PROV:
+			g_value_set_object (value, op->priv->prov);
+			break;
 		case PROP_OP_TYPE:
 			g_value_set_int (value, op->priv->op_type);
 			break;

Modified: trunk/libgda/gda-statement.c
==============================================================================
--- trunk/libgda/gda-statement.c	(original)
+++ trunk/libgda/gda-statement.c	Sun Apr  6 11:47:57 2008
@@ -393,6 +393,27 @@
 }
 
 /**
+ * gda_statement_normalize
+ * @stmt: a #GdaStatement object
+ * @cnc: a #GdaConnection object
+ * @error: a place to store errors, or %NULL
+ *
+ * "Normalizes" some parts of @stmt, see gda_sql_statement_normalize() for more
+ * information.
+ *
+ * Returns: TRUE if no error occurred
+ */
+gboolean
+gda_statement_normalize (GdaStatement *stmt, GdaConnection *cnc, GError **error)
+{
+	g_return_val_if_fail (GDA_IS_STATEMENT (stmt), FALSE);
+	g_return_val_if_fail (stmt->priv, FALSE);
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+
+	return gda_sql_statement_normalize (stmt->priv->internal_struct, cnc, error);
+}
+
+/**
  * gda_statement_serialize
  * @stmt: a #GdaStatement object
  *

Modified: trunk/libgda/gda-statement.h
==============================================================================
--- trunk/libgda/gda-statement.h	(original)
+++ trunk/libgda/gda-statement.h	Sun Apr  6 11:47:57 2008
@@ -97,6 +97,7 @@
 gboolean            gda_statement_is_useless             (GdaStatement *stmt);
 gboolean            gda_statement_check_structure        (GdaStatement *stmt, GError **error);
 gboolean            gda_statement_check_validity         (GdaStatement *stmt, GdaConnection *cnc, GError **error);
+gboolean            gda_statement_normalize              (GdaStatement *stmt, GdaConnection *cnc, GError **error);
 
 G_END_DECLS
 

Modified: trunk/libgda/gda-util.c
==============================================================================
--- trunk/libgda/gda-util.c	(original)
+++ trunk/libgda/gda-util.c	Sun Apr  6 11:47:57 2008
@@ -607,6 +607,128 @@
 }
 
 /*
+ * Checks related to the structure of the SELECT statement before computing the INSERT, UPDATE and DELETE
+ * corresponding statements.
+ */
+static gboolean
+dml_statements_check_select_structure (GdaConnection *cnc, GdaSqlStatement *sel_struct, GError **error)
+{
+	GdaSqlStatementSelect *stsel;
+	stsel = (GdaSqlStatementSelect*) sel_struct->contents;
+	if (!stsel->from) {
+		g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
+			     _("SELECT statement has no FROM part"));
+		return FALSE;
+	}
+	if (stsel->from->targets && stsel->from->targets->next) {
+		g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
+			     _("SELECT statement involves more than one table or expression"));
+		return FALSE;
+	}
+	GdaSqlSelectTarget *target;
+	target = (GdaSqlSelectTarget*) stsel->from->targets->data;
+	if (!target->table_name) {
+		g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
+			     _("SELECT statement involves more than one table or expression"));
+		return FALSE;
+	}
+	if (!gda_sql_statement_check_validity (sel_struct, cnc, error)) 
+		return FALSE;
+
+	/* check that we want to modify a table */
+	g_assert (target->validity_meta_object); /* because gda_sql_statement_check_validity() returned TRUE */
+	if (target->validity_meta_object->obj_type != GDA_META_DB_TABLE) {
+		g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
+			     _("Can only build modification statement for tables"));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Builds the WHERE condition of the computed DML statement
+ */
+static GdaSqlExpr*
+dml_statements_build_condition (GdaSqlStatementSelect *stsel, GdaMetaTable *mtable, gboolean require_pk, GError **error)
+{
+	gint i;
+	GdaSqlExpr *expr;
+	GdaSqlOperation *and_cond = NULL;
+
+	if (mtable->pk_cols_nb == 0) {
+		g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
+			     _("Table does not have any primary key"));
+		return NULL;
+	}
+	
+	expr = gda_sql_expr_new (NULL); /* no parent */
+	if (require_pk) {
+		if (mtable->pk_cols_nb == 0) {
+			g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
+					     _("Table does not have any primary key"));
+			goto onerror;
+		}
+		else if (mtable->pk_cols_nb > 1) {
+			and_cond = gda_sql_operation_new (GDA_SQL_ANY_PART (expr));
+			and_cond->operator = GDA_SQL_OPERATOR_AND;
+			expr->cond = and_cond;
+		}
+		for (i = 0; i < mtable->pk_cols_nb; i++) {
+			GdaSqlSelectField *sfield = NULL;
+			GdaMetaTableColumn *tcol;
+			GSList *list;
+			
+			tcol = (GdaMetaTableColumn *) g_slist_nth_data (mtable->columns, mtable->pk_cols_array[i]);
+			for (list = stsel->expr_list; list; list = list->next) {
+				sfield = (GdaSqlSelectField *) list->data;
+				if (sfield->validity_meta_table_column == tcol)
+					break;
+				else
+					sfield = NULL;
+			}
+			if (!sfield) {
+				g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
+					     _("Table's primary key is not selected"));
+				goto onerror;
+			}
+			else {
+				GdaSqlOperation *op;
+				GdaSqlExpr *opexpr;
+				GdaSqlParamSpec *pspec;
+
+				/* equal condition */
+				op = gda_sql_operation_new (GDA_SQL_ANY_PART (and_cond ? (gpointer)and_cond : (gpointer)expr));
+				op->operator = GDA_SQL_OPERATOR_EQ;
+				if (and_cond) 
+					and_cond->operands = g_slist_append (and_cond->operands, op);
+				else
+					expr->cond = op;
+				/* left operand */
+				opexpr = gda_sql_expr_new (GDA_SQL_ANY_PART (op));
+				g_value_set_string (opexpr->value = gda_value_new (G_TYPE_STRING), tcol->column_name);
+				op->operands = g_slist_append (op->operands, opexpr);
+
+				/* right operand */
+				opexpr = gda_sql_expr_new (GDA_SQL_ANY_PART (op));
+				pspec = g_new0 (GdaSqlParamSpec, 1);
+				pspec->name = g_strdup_printf ("-%d", mtable->pk_cols_array[i]);
+				pspec->g_type = tcol->gtype != G_TYPE_INVALID ? tcol->gtype: G_TYPE_STRING;
+				pspec->type = g_strdup (gda_g_type_to_string (pspec->g_type));
+				pspec->nullok = tcol->nullok;
+				opexpr->param_spec = pspec;
+				op->operands = g_slist_append (op->operands, opexpr);
+			}
+		}
+	}
+	return expr;
+	
+ onerror:
+	gda_sql_expr_free (expr);
+	return NULL;
+}
+
+/*
  * Statement computation from meta store 
  * @select_stmt: must be a SELECT statement (compound statements not handled)
  */
@@ -621,7 +743,11 @@
 	GdaStatement *ret_delete = NULL;
 	GdaMetaStruct *mstruct;
 	gboolean retval = TRUE;
-	GdaMetaTable *mtable;
+	GdaSqlSelectTarget *target;
+
+	GdaSqlStatementInsert *ist = NULL;
+	GdaSqlStatementUpdate *ust = NULL;
+	GdaSqlStatementDelete *dst = NULL;	
 
 	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
 	g_return_val_if_fail (GDA_IS_STATEMENT (select_stmt), FALSE);
@@ -630,28 +756,13 @@
 	/*g_print ("*** %s\n", gda_statement_serialize (select_stmt));*/
 
 	g_object_get (G_OBJECT (select_stmt), "structure", &sel_struct, NULL);
-	stsel = (GdaSqlStatementSelect*) sel_struct->contents;
-	if (!stsel->from) {
-		g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
-			     _("SELECT statement has no FROM part"));
-		retval = FALSE;
-		goto cleanup;
-	}
-	if (stsel->from->targets && stsel->from->targets->next) {
-		g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
-			     _("SELECT statement involves more than one table or expression"));
-		retval = FALSE;
-		goto cleanup;
-	}
-	GdaSqlSelectTarget *target;
-	target = (GdaSqlSelectTarget*) stsel->from->targets->data;
-	if (!target->table_name) {
-		g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
-			     _("SELECT statement involves more than one table or expression"));
+	if (!dml_statements_check_select_structure (cnc, sel_struct, error)) {
 		retval = FALSE;
 		goto cleanup;
 	}
-	if (!gda_sql_statement_check_validity (sel_struct, cnc, error)) {
+
+	/* normalize the statement */
+	if (!gda_sql_statement_normalize (sel_struct, cnc, error)) {
 		retval = FALSE;
 		goto cleanup;
 	}
@@ -659,34 +770,11 @@
 	mstruct = sel_struct->validity_meta_struct;
 	g_assert (mstruct); /* because gda_sql_statement_check_validity() returned TRUE */
 
-	/* check that we want to modify a table */
-	if (target->validity_meta_object->obj_type != GDA_META_DB_TABLE) {
-		g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
-			     _("Can only build modification statement for tables"));
-		retval = FALSE;
-		goto cleanup;
-	}
-
 	/* check that the condition will be buildable */
-	mtable = GDA_META_DB_OBJECT_GET_TABLE (target->validity_meta_object);
-	if ((update || delete) && require_pk) {
-		gint i;
-		if (mtable->pk_cols_nb == 0) {
-			g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
-			     _("Table does not have any primary key"));
-			retval = FALSE;
-			goto cleanup;
-		}
-		for (i = 0; i < mtable->pk_cols_nb; i++) {
-			TO_IMPLEMENT;
-		}
-	}
+	stsel = (GdaSqlStatementSelect*) sel_struct->contents;
+	target = (GdaSqlSelectTarget*) stsel->from->targets->data;
 	
-	/* actual statement structure's computation */
-	GdaSqlStatementInsert *ist;
-	GdaSqlStatementUpdate *ust;
-	GdaSqlStatementDelete *dst;	
-        
+	/* actual statement structure's computation */        
 	if (insert) {
 		ist = g_new0 (GdaSqlStatementInsert, 1);
 		GDA_SQL_ANY_PART (ist)->type = GDA_SQL_ANY_STMT_INSERT;
@@ -699,6 +787,14 @@
 		GDA_SQL_ANY_PART (ust)->type = GDA_SQL_ANY_STMT_UPDATE;
 		ust->table = gda_sql_table_new (GDA_SQL_ANY_PART (ust));
 		ust->table->table_name = g_strdup ((gchar *) target->table_name);
+		ust->cond = dml_statements_build_condition (stsel, 
+							    GDA_META_DB_OBJECT_GET_TABLE (target->validity_meta_object),
+							    require_pk, error);
+		if (!ust->cond) {
+			retval = FALSE;
+			goto cleanup;
+		}
+		GDA_SQL_ANY_PART (ust->cond)->parent = GDA_SQL_ANY_PART (ust);
 	}
         
 	if (delete) {
@@ -706,27 +802,28 @@
 		GDA_SQL_ANY_PART (dst)->type = GDA_SQL_ANY_STMT_DELETE;
 		dst->table = gda_sql_table_new (GDA_SQL_ANY_PART (dst));
 		dst->table->table_name = g_strdup ((gchar *) target->table_name);
+		dst->cond = dml_statements_build_condition (stsel, 
+							    GDA_META_DB_OBJECT_GET_TABLE (target->validity_meta_object),
+							    require_pk, error);
+		if (!dst->cond) {
+			retval = FALSE;
+			goto cleanup;
+		}
+		GDA_SQL_ANY_PART (dst->cond)->parent = GDA_SQL_ANY_PART (dst);
 	}
 
 	GSList *expr_list;
 	gint colindex;
 	GSList *insert_values_list = NULL;
 	GHashTable *fields_hash; /* key = a table's field's name, value = we don't care */
-	fields_hash = g_hash_table_new (g_str_hash, g_str_equal);
+	fields_hash = g_hash_table_new ((GHashFunc) gda_identifier_hash, (GEqualFunc) gda_identifier_equal);
 	for (expr_list = stsel->expr_list, colindex = 0; 
 	     expr_list;
 	     expr_list = expr_list->next, colindex++) {
 		GdaSqlSelectField *selfield = (GdaSqlSelectField *) expr_list->data;
-		if (selfield->expr && selfield->expr->value && 
-		    !strcmp (g_value_get_string (selfield->expr->value), "*")) {
-			TO_IMPLEMENT;
+		if ((selfield->validity_meta_object != target->validity_meta_object) ||
+		    !selfield->validity_meta_table_column)
 			continue;
-		}
-		else {
-			if ((selfield->validity_meta_object != target->validity_meta_object) ||
-			    !selfield->validity_meta_table_column)
-				continue;
-		}
 
 		/* field to insert into */
 		if (g_hash_table_lookup (fields_hash, selfield->field_name))
@@ -775,9 +872,18 @@
 	/* finish the statements */
 	if (insert) {
 		GdaSqlStatement *st;
+
+		if (!ist->fields_list) {
+			/* nothing to insert => don't create statement */
+			g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
+				     _("Could not compute any field to insert into"));
+			retval = FALSE;
+			goto cleanup;
+		}
 		ist->values_list = g_slist_append (NULL, insert_values_list);
 		st = gda_sql_statement_new (GDA_SQL_STATEMENT_INSERT);
 		st->contents = ist;
+		ist = NULL;
 		ret_insert = g_object_new (GDA_TYPE_STATEMENT, "structure", st, NULL);
 		gda_sql_statement_free (st);
 	}
@@ -785,6 +891,7 @@
 		GdaSqlStatement *st;
 		st = gda_sql_statement_new (GDA_SQL_STATEMENT_UPDATE);
 		st->contents = ust;
+		ust = NULL;
 		ret_update = g_object_new (GDA_TYPE_STATEMENT, "structure", st, NULL);
 		gda_sql_statement_free (st);
 	}
@@ -792,12 +899,29 @@
 		GdaSqlStatement *st;
 		st = gda_sql_statement_new (GDA_SQL_STATEMENT_DELETE);
 		st->contents = dst;
+		dst = NULL;
 		ret_delete = g_object_new (GDA_TYPE_STATEMENT, "structure", st, NULL);
 		gda_sql_statement_free (st);
 	}
 	
  cleanup:
 	gda_sql_statement_free (sel_struct);
+	if (ist) {
+		GdaSqlStatementContentsInfo *cinfo;
+		cinfo = gda_sql_statement_get_contents_infos (GDA_SQL_STATEMENT_INSERT);
+		cinfo->free (ist);
+	}
+	if (ust) {
+		GdaSqlStatementContentsInfo *cinfo;
+		cinfo = gda_sql_statement_get_contents_infos (GDA_SQL_STATEMENT_UPDATE);
+		cinfo->free (ust);
+	}
+	if (dst) {
+		GdaSqlStatementContentsInfo *cinfo;
+		cinfo = gda_sql_statement_get_contents_infos (GDA_SQL_STATEMENT_DELETE);
+		cinfo->free (dst);
+	}
+		
 	if (insert)
 		*insert = ret_insert;
 	if (update)
@@ -806,3 +930,71 @@
 		*delete = ret_delete;
 	return retval;
 }
+
+/*
+ * computes a hash string from @is, to be used in hash tables as a GHashFunc
+ */
+guint
+gda_identifier_hash (const gchar *id)
+{
+	const signed char *p = (signed char *) id;
+	guint32 h = 0;
+	gboolean lower = FALSE;
+
+	if (*p != '"') {
+		lower = TRUE;
+		h = g_ascii_tolower (*p);
+	}
+	
+	for (p += 1; *p && *p != '"'; p++) {
+		if (lower)
+			h = (h << 5) - h + g_ascii_tolower (*p);
+		else
+			h = (h << 5) - h + *p;
+	}
+	if (*p == '"' && *(p+1)) 
+		g_warning ("Argument passed to %s() is not an SQL identifier", __FUNCTION__);
+
+	return h;
+}
+
+/*
+ * Does the same as strcmp(id1, id2), but handles the case where id1 and/or id2 are enclosed in double quotes,
+ * can also be used in hash tables as a GEqualFunc
+ */
+gboolean
+gda_identifier_equal (const gchar *id1, const gchar *id2)
+{
+	const gchar *ptr1, *ptr2;
+	gboolean dq1 = FALSE, dq2 = FALSE;
+
+	ptr1 = id1;
+	if (*ptr1 == '"') {
+		ptr1++;
+		dq1 = TRUE;
+	}
+	ptr2 = id2;
+	if (*ptr2 == '"') {
+		ptr2++;
+		dq2 = TRUE;
+	}
+	for (; *ptr1 && *ptr2; ptr1++, ptr2++) {
+		gchar c1, c2;
+		c1 = *ptr1;
+		c2 = *ptr2;
+		if (!dq1)
+			c1 = g_ascii_tolower (c1);
+		if (!dq2)
+			c2 = g_ascii_tolower (c2);
+		if (c1 != c2)
+			return FALSE;
+	}
+	if (*ptr1 || *ptr2) {
+		if (*ptr1 && (*ptr1 == '"'))
+			return TRUE;
+		if (*ptr2 && (*ptr2 == '"'))
+			return TRUE;
+		return FALSE;
+	}
+	return TRUE;
+}

Modified: trunk/libgda/gda-util.h
==============================================================================
--- trunk/libgda/gda-util.h	(original)
+++ trunk/libgda/gda-util.h	Sun Apr  6 11:47:57 2008
@@ -43,6 +43,8 @@
  */
 gchar       *gda_default_escape_string (const gchar *string);
 gchar       *gda_default_unescape_string (const gchar *string);
+guint        gda_identifier_hash (const gchar *id);
+gboolean     gda_identifier_equal (const gchar *id1, const gchar *id2);
 
 /*
  * Param & model utilities

Modified: trunk/libgda/sql-parser/gda-statement-struct-decl.h
==============================================================================
--- trunk/libgda/sql-parser/gda-statement-struct-decl.h	(original)
+++ trunk/libgda/sql-parser/gda-statement-struct-decl.h	Sun Apr  6 11:47:57 2008
@@ -33,6 +33,7 @@
 typedef enum {
 	GDA_SQL_STRUCTURE_CONTENTS_ERROR,
 	GDA_SQL_MALFORMED_IDENTIFIER_ERROR,
+	GDA_SQL_MISSING_IDENTIFIER_ERROR,
 	GDA_SQL_VALIDATION_ERROR
 } GdaSqlErrorType;
 

Modified: trunk/libgda/sql-parser/gda-statement-struct-parts.c
==============================================================================
--- trunk/libgda/sql-parser/gda-statement-struct-parts.c	(original)
+++ trunk/libgda/sql-parser/gda-statement-struct-parts.c	Sun Apr  6 11:47:57 2008
@@ -840,24 +840,8 @@
 	field->expr = expr;
 	gda_sql_any_part_set_parent (field->expr, field);
 
-	if (expr && expr->value) {
-		const gchar *str;
-		gchar *ptr;
-
-		str = g_value_get_string (expr->value);
-		if (_string_is_identifier (str)) {
-			for (ptr = (gchar *) str; *ptr; ptr++);
-			for (; (ptr > str) && (*ptr != '.'); ptr --);
-			if (ptr != str) {
-				field->field_name = g_strdup (ptr + 1);
-				*ptr = 0;
-				field->table_name = g_strdup (str);
-				*ptr = '.';
-			}
-			else 
-				field->field_name = g_strdup (ptr);
-		}
-	}
+	if (expr && expr->value) 
+		_split_identifier_string (g_value_dup_string (expr->value), &(field->table_name), &(field->field_name));
 }
 
 void

Modified: trunk/libgda/sql-parser/gda-statement-struct-parts.h
==============================================================================
--- trunk/libgda/sql-parser/gda-statement-struct-parts.h	(original)
+++ trunk/libgda/sql-parser/gda-statement-struct-parts.h	Sun Apr  6 11:47:57 2008
@@ -189,7 +189,7 @@
 gchar             *gda_sql_case_serialize      (GdaSqlCase *scase);
 
 /*
- * Any expression in a SELECT ... before the FROM
+ * Any expression in a SELECT ... before the FROM clause
  */
 struct _GdaSqlSelectField
 {

Modified: trunk/libgda/sql-parser/gda-statement-struct-util.c
==============================================================================
--- trunk/libgda/sql-parser/gda-statement-struct-util.c	(original)
+++ trunk/libgda/sql-parser/gda-statement-struct-util.c	Sun Apr  6 11:47:57 2008
@@ -249,9 +249,14 @@
 
 	if (!str || !(*str)) 
 		return FALSE;
-	for (ptr = (gchar *) str; IdChar(*ptr) || (*ptr == '*') || (*ptr == '.'); ptr++);
+	for (ptr = *str == '"' ? str + 1 : str; 
+	     IdChar(*ptr) || (*ptr == '*') || (*ptr == '.') || ((*ptr == '"') && ptr[1] == 0); 
+	     ptr++);
 	if (*ptr) 
 		return FALSE;
+	if ((*str == '"') && (ptr[-1] == '"'))
+		return TRUE;
+
 	/* @str is composed only of character that can be used in an identifier */
 	d = g_ascii_strtod (str, &endptr);
 	if (!*endptr)
@@ -259,3 +264,78 @@
 		return FALSE;
 	return TRUE;
 }
+
+/*
+ * Reuses @str and fills in @remain and @last
+ *
+ * @str "swallowed" by the function and must be allocated memory, and @remain and @last are
+ * also allocated memory.
+ *
+ * Accepted notations for each individual part:
+ *   - aaa
+ *   - "aaa"
+ *
+ * So "aaa".bbb, aaa.bbb, "aaa"."bbb", aaa."bbb" are Ok.
+ *
+ * Returns TRUE:
+ * if @str has the <part1>.<part2> form, then @last will contain <part2> and @remain will contain <part1>
+ * if @str has the <part1> form, then @last will contain <part1> and @remain will contain NULL
+ * 
+ * Returns FALSE:
+ * if @str is NULL:
+ * if @str is "":
+ * if @str is malformed:
+ *              @last and @remain will contain NULL
+ */
+gboolean
+_split_identifier_string (gchar *str, gchar **remain, gchar **last)
+{
+	gchar *ptr;
+	gboolean inq = FALSE;
+	gint len;
+
+	*remain = NULL;
+	*last = NULL;
+	if (!str)
+		return FALSE;
+	g_strchomp (str);
+	if (!*str) {
+		g_free (str);
+		return FALSE;
+	}
+
+	len = strlen (str) - 1;
+	if (((str[len] == '"') && (str[len-1] == '.')) ||
+	    (str[len] == '.')) {
+		g_free (str);
+		return FALSE;
+	}
+
+	if (((str[0] == '"') && (str[1] == '.')) ||
+	    (str[0] == '.')) {
+		g_free (str);
+		return FALSE;
+	}
+
+	for (ptr = str + strlen (str) - 1; ptr >= str; ptr--) {
+		if (*ptr == '"') 
+			inq = !inq;
+		else if ((*ptr == '.') && !inq) {
+			*ptr = 0;
+			*remain = str;
+			*last = g_strdup (ptr+1);
+			break;
+		}
+	}
+	if (!(*last) && !(*remain))
+		*last = str;
+
+	if (*last && !_string_is_identifier (*last)) {
+		g_free (*last);
+		*last = NULL;
+		g_free (*remain);
+		*remain = NULL;
+		return FALSE;
+	}
+	return TRUE;
+}

Modified: trunk/libgda/sql-parser/gda-statement-struct-util.h
==============================================================================
--- trunk/libgda/sql-parser/gda-statement-struct-util.h	(original)
+++ trunk/libgda/sql-parser/gda-statement-struct-util.h	Sun Apr  6 11:47:57 2008
@@ -27,8 +27,9 @@
 gchar *_remove_quotes (gchar *str);
 gchar *_add_quotes (const gchar *str);
 
-gchar *_json_quote_string (const gchar *str);
-gboolean _string_is_identifier (const gchar *str);
+gchar    *_json_quote_string (const gchar *str);
+gboolean  _string_is_identifier (const gchar *str);
+gboolean  _split_identifier_string (gchar *str, gchar **remain, gchar **last);
 
 /* to be removed, only here for debug */
 gchar *gda_sql_value_stringify (const GValue *value);

Modified: trunk/libgda/sql-parser/gda-statement-struct.c
==============================================================================
--- trunk/libgda/sql-parser/gda-statement-struct.c	(original)
+++ trunk/libgda/sql-parser/gda-statement-struct.c	Sun Apr  6 11:47:57 2008
@@ -23,6 +23,7 @@
 #include <libgda/gda-debug-macros.h>
 #include <libgda/gda-connection.h>
 #include <libgda/gda-meta-struct.h>
+#include <libgda/gda-util.h>
 #include <libgda/sql-parser/gda-statement-struct-util.h>
 #include <libgda/sql-parser/gda-statement-struct-unknown.h>
 #include <libgda/sql-parser/gda-statement-struct-trans.h>
@@ -254,6 +255,9 @@
  * If @cnc is %NULL, then remove any information from a previous call to this method stored in @stmt. In this case,
  * the @stmt-&gt;validity_meta_struct attribute is cleared.
  *
+ * Also note that some parts of @stmt may be modified: for example leading ad trailing spaces in aliases or
+ * objects names will be removed.
+ *
  * Returns: TRUE if no error occurred
  */
 gboolean
@@ -312,18 +316,104 @@
 			return cinfo->check_validity_func (node, data, error);
 		break;
 	}
-	case GDA_SQL_ANY_EXPR:
-		return gda_sql_expr_check_validity ((GdaSqlExpr*) node, data, error);
-	case GDA_SQL_ANY_SQL_FIELD:
-		return gda_sql_field_check_validity ((GdaSqlField*) node, data, error);
-	case GDA_SQL_ANY_SQL_TABLE:
-		return gda_sql_table_check_validity ((GdaSqlTable*) node, data, error);
-	case GDA_SQL_ANY_SQL_FUNCTION:
-		return gda_sql_function_check_validity ((GdaSqlFunction*) node, data, error);
-	case GDA_SQL_ANY_SQL_SELECT_FIELD:
-		return gda_sql_select_field_check_validity ((GdaSqlSelectField*) node, data, error);
-	case GDA_SQL_ANY_SQL_SELECT_TARGET:
-		return gda_sql_select_target_check_validity ((GdaSqlSelectTarget*) node, data, error);
+	case GDA_SQL_ANY_EXPR: {
+		GdaSqlExpr *expr = (GdaSqlExpr *) node;
+		if (expr->cast_as) {
+			g_strchomp (expr->cast_as);
+			if (! *(expr->cast_as)) {
+				g_free (expr->cast_as);
+				expr->cast_as = NULL;
+			}
+		}
+		return gda_sql_expr_check_validity (expr, data, error);
+	}
+	case GDA_SQL_ANY_SQL_FIELD: {
+		GdaSqlField *field = (GdaSqlField*) node;
+		if (field->field_name) {
+			g_strchomp (field->field_name);
+			if (! *(field->field_name)) {
+				g_free (field->field_name);
+				field->field_name = NULL;
+			}
+		}
+		return gda_sql_field_check_validity (field, data, error);
+	}
+	case GDA_SQL_ANY_SQL_TABLE: {
+		GdaSqlTable *table = (GdaSqlTable*) node;
+		if (table->table_name) {
+			g_strchomp (table->table_name);
+			if (! *(table->table_name)) {
+				g_free (table->table_name);
+				table->table_name = NULL;
+			}
+		}
+		return gda_sql_table_check_validity (table, data, error);
+	}
+	case GDA_SQL_ANY_SQL_FUNCTION: {
+		GdaSqlFunction *function = (GdaSqlFunction*) node;
+		if (function->function_name) {
+			g_strchomp (function->function_name);
+			if (! *(function->function_name)) {
+				g_free (function->function_name);
+				function->function_name = NULL;
+			}
+		}
+		return gda_sql_function_check_validity (function, data, error);
+	}
+	case GDA_SQL_ANY_SQL_SELECT_FIELD: {
+		GdaSqlSelectField *field = (GdaSqlSelectField*) node;
+		if (field->as) {
+			g_strchomp (field->as);
+			if (! *(field->as)) {
+				g_free (field->as);
+				field->as = NULL;
+			}
+		}
+
+		if (field->expr && field->expr->value && (G_VALUE_TYPE (field->expr->value) == G_TYPE_STRING)) {
+			g_free (field->field_name);
+			g_free (field->table_name);
+			_split_identifier_string (g_value_dup_string (field->expr->value), &(field->table_name),
+						  &(field->field_name));
+		}
+		if (field->table_name) {
+			g_strchomp (field->table_name);
+			if (! *(field->table_name)) {
+				g_free (field->table_name);
+				field->table_name = NULL;
+			}
+		}
+		if (field->field_name) {
+			g_strchomp (field->field_name);
+			if (! *(field->field_name)) {
+				g_free (field->field_name);
+				field->field_name = NULL;
+			}
+		}
+		return gda_sql_select_field_check_validity (field, data, error);
+	}
+	case GDA_SQL_ANY_SQL_SELECT_TARGET: {
+		GdaSqlSelectTarget *target = (GdaSqlSelectTarget*) node;
+		if (target->as) {
+			g_strchomp (target->as);
+			if (! *(target->as)) {
+				g_free (target->as);
+				target->as = NULL;
+			}
+		}
+		if (target->expr && target->expr->value && (G_VALUE_TYPE (target->expr->value) == G_TYPE_STRING)) {
+			g_free (target->table_name);
+			target->table_name = g_value_dup_string (target->expr->value);
+		}
+		if (target->table_name) {
+			g_strchomp (target->table_name);
+			if (! *(target->table_name)) {
+				g_free (target->table_name);
+				target->table_name = NULL;
+			}
+		}
+		return gda_sql_select_target_check_validity (target, data, error);
+	}
 	default:
 		break;
 	}
@@ -551,7 +641,7 @@
 		return TRUE;
 
 	memset (&value, 0, sizeof (GValue));
-	if (!strcmp (field->field_name, "*"))
+	if (gda_identifier_equal (field->field_name, "*"))
 		starred_field = TRUE;
 
 	if (!field->table_name) {
@@ -591,9 +681,15 @@
 			}
 		}
 		if (!dbo) {
-			g_set_error (error, GDA_SQL_ERROR, GDA_SQL_VALIDATION_ERROR,
-				     _("Could not identify table for field '%s'"), field->field_name);
-			return FALSE;
+			targets = ((GdaSqlStatementSelect *)any)->from->targets;
+			if (starred_field && targets && !targets->next) 
+				/* only one target => it's the one */
+				dbo = ((GdaSqlSelectTarget*) targets->data)->validity_meta_object;
+			else {
+				g_set_error (error, GDA_SQL_ERROR, GDA_SQL_VALIDATION_ERROR,
+					     _("Could not identify table for field '%s'"), field->field_name);
+				return FALSE;
+			}
 		}
 		field->validity_meta_object = dbo;
 		field->validity_meta_table_column = tcol;
@@ -612,7 +708,7 @@
 			return FALSE;
 		
 		/* field part */
-		if (strcmp (field->field_name, "*")) {
+		if (!gda_identifier_equal (field->field_name, "*")) {
 			GdaMetaTableColumn *tcol;
 			g_value_set_string (g_value_init (&value, G_TYPE_STRING), field->field_name);
 			tcol = gda_meta_struct_get_table_column (data->mstruct, 
@@ -1160,3 +1256,98 @@
 	/* finally call @func for this node */
 	return func (node, data, error);
 }
+
+
+static gboolean foreach_normalize (GdaSqlAnyPart *node, GdaConnection *cnc, GError **error);
+
+/**
+ * gda_sql_statement_normalize
+ * @stmt: a pointer to a #GdaSqlStatement structure
+ * @cnc: a #GdaConnection object, or %NULL
+ *
+ * "Normalizes" (in place) some parts of @stmt, which means @stmt may be modified.
+ * At the moment any "*" field in a SELECT statement will be replaced by one
+ * #GdaSqlSelectField structure for each field in the referenced table.
+ *
+ * Returns: TRUE if no error occurred
+ */
+gboolean
+gda_sql_statement_normalize (GdaSqlStatement *stmt, GdaConnection *cnc, GError **error)
+{
+	gboolean retval;
+	g_return_val_if_fail (stmt, FALSE);
+
+	if (!stmt->validity_meta_struct && !gda_sql_statement_check_validity (stmt, cnc, error))
+		return FALSE;
+
+	retval = gda_sql_any_part_foreach (GDA_SQL_ANY_PART (stmt->contents), 
+					   (GdaSqlForeachFunc) foreach_normalize, cnc, error);
+#ifdef GDA_DEBUG
+	GError *lerror = NULL;
+	if (retval && !gda_sql_statement_check_validity (stmt, cnc, &lerror)) {
+		g_warning ("Internal error in %s(): statement is not valid anymore after: %s", __FUNCTION__,
+			   lerror && lerror->message ? lerror->message :  "No detail");
+		if (lerror)
+			g_error_free (lerror);
+	}
+#endif
+	return retval;
+}
+
+static gboolean
+foreach_normalize (GdaSqlAnyPart *node, GdaConnection *cnc, GError **error)
+{
+	if (!node) return TRUE;
+
+	if (node->type == GDA_SQL_ANY_SQL_SELECT_FIELD) {
+		GdaSqlSelectField *field = (GdaSqlSelectField*) node;
+		if (((field->field_name && gda_identifier_equal (field->field_name, "*")) ||
+		    (field->expr && field->expr->value && (G_VALUE_TYPE (field->expr->value) == G_TYPE_STRING) &&
+		     gda_identifier_equal (g_value_get_string (field->expr->value), "*"))) &&
+		    field->validity_meta_object) {
+			/* expand * to all the fields */
+			GdaMetaTable *mtable = GDA_META_DB_OBJECT_GET_TABLE (field->validity_meta_object);
+			GSList *list;
+			GdaSqlAnyPart *parent_node = ((GdaSqlAnyPart*) field)->parent;
+			gint nodepos = g_slist_index (((GdaSqlStatementSelect*) parent_node)->expr_list, node);
+			if (parent_node->type != GDA_SQL_ANY_STMT_SELECT) {
+				g_set_error (error, GDA_SQL_ERROR, GDA_SQL_STRUCTURE_CONTENTS_ERROR,
+					     _("Select field is not in a SELECT statement"));
+				return FALSE;
+			}
+			for (list = mtable->columns; list; list = list->next) {
+				GdaSqlSelectField *nfield;
+				GdaMetaTableColumn *tcol = (GdaMetaTableColumn *) list->data;
+
+				nfield = gda_sql_select_field_new (parent_node);
+				nfield->field_name = g_strdup (tcol->column_name);
+				if (field->table_name)
+					nfield->table_name = g_strdup (field->table_name);
+				nfield->validity_meta_object = field->validity_meta_object;
+				nfield->validity_meta_table_column = tcol;
+				nfield->expr = gda_sql_expr_new ((GdaSqlAnyPart*) nfield);
+				nfield->expr->value = gda_value_new (G_TYPE_STRING);
+				if (field->table_name)
+					g_value_take_string (nfield->expr->value, g_strdup_printf ("%s.%s", 
+												   nfield->table_name,
+												   nfield->field_name));
+				else
+					g_value_set_string (nfield->expr->value, nfield->field_name);
+
+				/* insert nfield into expr_list */
+				GSList *expr_list = ((GdaSqlStatementSelect*) parent_node)->expr_list;
+				if (list == mtable->columns) {
+					GSList *lnode = g_slist_nth (expr_list, nodepos);
+					lnode->data = nfield;
+				}
+				else 
+					((GdaSqlStatementSelect*) parent_node)->expr_list = 
+						g_slist_insert (expr_list, nfield, ++nodepos);
+			}
+			/* get rid of @field */
+			gda_sql_select_field_free (field);
+		}
+	}
+
+	return TRUE;
+}

Modified: trunk/libgda/sql-parser/gda-statement-struct.h
==============================================================================
--- trunk/libgda/sql-parser/gda-statement-struct.h	(original)
+++ trunk/libgda/sql-parser/gda-statement-struct.h	Sun Apr  6 11:47:57 2008
@@ -43,6 +43,7 @@
 gboolean                     gda_sql_statement_check_structure (GdaSqlStatement *stmt, GError **error);
 gboolean                     gda_sql_statement_check_validity  (GdaSqlStatement *stmt, GdaConnection *cnc, GError **error);
 void                         gda_sql_statement_check_clean     (GdaSqlStatement *stmt);
+gboolean                     gda_sql_statement_normalize       (GdaSqlStatement *stmt, GdaConnection *cnc, GError **error);
 
 GdaSqlStatementContentsInfo *gda_sql_statement_get_contents_infos (GdaSqlStatementType type) ;
 

Modified: trunk/libgda/sql-parser/parser.y
==============================================================================
--- trunk/libgda/sql-parser/parser.y	(original)
+++ trunk/libgda/sql-parser/parser.y	Sun Apr  6 11:47:57 2008
@@ -986,9 +986,9 @@
 //
 nm(A) ::= JOIN(X).       {A = X;}
 nm(A) ::= ID(X).       {A = X;}
+nm(A) ::= TEXTUAL(X). {A = X;}
 
 // Fully qualified name
-fullname(A) ::= TEXTUAL(X). {A = X;}
 fullname(A) ::= nm(X). {A = X;}
 fullname(A) ::= nm(S) DOT nm(X). {gchar *str;
 				  str = g_strdup_printf ("%s.%s", g_value_get_string (S), g_value_get_string (X));

Modified: trunk/providers/postgres/parser.y
==============================================================================
--- trunk/providers/postgres/parser.y	(original)
+++ trunk/providers/postgres/parser.y	Sun Apr  6 11:47:57 2008
@@ -987,9 +987,9 @@
 //
 nm(A) ::= JOIN(X).       {A = X;}
 nm(A) ::= ID(X).       {A = X;}
+nm(A) ::= TEXTUAL(X). {A = X;}
 
 // Fully qualified name
-fullname(A) ::= TEXTUAL(X). {A = X;}
 fullname(A) ::= nm(X). {A = X;}
 fullname(A) ::= nm(S) DOT nm(X). {gchar *str;
 				  str = g_strdup_printf ("%s.%s", g_value_get_string (S), g_value_get_string (X));

Modified: trunk/providers/skel-implementation/capi/parser.y
==============================================================================
--- trunk/providers/skel-implementation/capi/parser.y	(original)
+++ trunk/providers/skel-implementation/capi/parser.y	Sun Apr  6 11:47:57 2008
@@ -1001,9 +1001,9 @@
 //
 nm(A) ::= JOIN(X).       {A = X;}
 nm(A) ::= ID(X).       {A = X;}
+nm(A) ::= TEXTUAL(X). {A = X;}
 
 // Fully qualified name
-fullname(A) ::= nm(X). {A = X;}
 fullname(A) ::= nm(S) DOT nm(X). {gchar *str;
 				  str = g_strdup_printf ("%s.%s", g_value_get_string (S), g_value_get_string (X));
 				  A = g_new0 (GValue, 1);

Modified: trunk/tests/parser/Makefile.am
==============================================================================
--- trunk/tests/parser/Makefile.am	(original)
+++ trunk/tests/parser/Makefile.am	Sun Apr  6 11:47:57 2008
@@ -5,8 +5,8 @@
 	$(LIBGDA_CFLAGS) \
 	-DROOT_DIR=\""$(top_srcdir)"\" 
 
-TESTS = check_parser check_validation
-check_PROGRAMS = check_parser check_validation
+TESTS = check_parser check_validation check_normalization check_dml_comp
+check_PROGRAMS = check_parser check_validation check_normalization check_dml_comp
 
 check_parser_SOURCES = check_parser.c
 check_parser_LDADD = \
@@ -18,4 +18,14 @@
 	$(top_builddir)/libgda/libgda-4.0.la \
 	$(LIBGDA_LIBS)
 
+check_normalization_SOURCES = check_normalization.c
+check_normalization_LDADD = \
+	$(top_builddir)/libgda/libgda-4.0.la \
+	$(LIBGDA_LIBS)
+
+check_dml_comp_SOURCES = check_dml_comp.c
+check_dml_comp_LDADD = \
+	$(top_builddir)/libgda/libgda-4.0.la \
+	$(LIBGDA_LIBS)
+
 EXTRA_DIST = testdata.xml testvalid.xml

Added: trunk/tests/parser/check_dml_comp.c
==============================================================================
--- (empty file)
+++ trunk/tests/parser/check_dml_comp.c	Sun Apr  6 11:47:57 2008
@@ -0,0 +1,192 @@
+#include <stdio.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <gmodule.h>
+#include <libgda/libgda.h>
+#include <libgda/gda-util.h>
+#include <sql-parser/gda-sql-parser.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+GdaConnection *cnc;
+static gint do_test (const xmlChar *id, const xmlChar *sql, 
+		     const gchar *computed_type, const xmlChar *computed_exp, gboolean require_pk);
+
+int 
+main (int argc, char** argv)
+{
+	xmlDocPtr doc;
+        xmlNodePtr root, node;
+	gint failures = 0;
+	gint ntests = 0;
+	gchar *fname;
+
+	gda_init ("Parser validation", ".1", argc, argv);
+
+	/* open connection */
+	gchar *cnc_string;
+	fname = g_build_filename (ROOT_DIR, "data", NULL);
+	cnc_string = g_strdup_printf ("DB_DIR=%s;DB_NAME=sales_test", fname);
+	g_free (fname);
+	cnc = gda_connection_open_from_string ("SQLite", cnc_string, NULL,
+					       GDA_CONNECTION_OPTIONS_READ_ONLY, NULL);
+	if (!cnc) {
+		g_print ("Failed to open connection, cnc_string = %s\n", cnc_string);
+		exit (1);
+	}
+	if (!gda_connection_update_meta_store (cnc, NULL, NULL)) {
+		g_print ("Failed to update meta store, cnc_string = %s\n", cnc_string);
+		exit (1);
+	}
+	g_free (cnc_string);
+
+	/* load file */
+	fname = g_build_filename (ROOT_DIR, "tests", "parser", "testvalid.xml", NULL);
+	if (! g_file_test (fname, G_FILE_TEST_EXISTS)) {
+                g_print ("File '%s' does not exist\n", fname);
+                exit (1);
+        }
+
+	/* use test data */
+	doc = xmlParseFile (fname);
+	g_free (fname);
+	g_assert (doc);
+	root = xmlDocGetRootElement (doc);
+	g_assert (!strcmp ((gchar*) root->name, "testdata"));
+	for (node = root->children; node; node = node->next) {
+		if (strcmp ((gchar*) node->name, "test"))
+			continue;
+		xmlNodePtr snode;
+		xmlChar *sql = NULL;
+		xmlChar *id;
+
+		id = xmlGetProp (node, BAD_CAST "id");
+		for (snode = node->children; snode; snode = snode->next) {
+			if (!strcmp ((gchar*) snode->name, "sql")) 
+				sql = xmlNodeGetContent (snode);
+			else if (!strcmp ((gchar*) snode->name, "insert") ||
+				 !strcmp ((gchar*) snode->name, "update") ||
+				 !strcmp ((gchar*) snode->name, "delete")) {
+				xmlChar *comp_exp;
+				xmlChar *prop;
+				gboolean require_pk = TRUE;
+				comp_exp = xmlNodeGetContent (snode);
+				prop = xmlGetProp (snode, "need_pk");
+				if (prop) {
+					if ((*prop == 'f') || (*prop == 'F') || (*prop == '0'))
+						require_pk = FALSE;
+					xmlFree (prop);
+				}
+				if (sql) {
+					if (!do_test (id, sql, snode->name, comp_exp, require_pk))
+						failures++;
+					ntests++;
+				}
+			}
+		}
+
+		/* mem free */
+		if (sql) xmlFree (sql);
+		if (id)	xmlFree (id);
+	}
+	xmlFreeDoc (doc);
+
+	g_print ("TESTS COUNT: %d\n", ntests);
+	g_print ("FAILURES: %d\n", failures);
+  
+	return failures != 0 ? 1 : 0;
+}
+
+/*
+ * Returns: the number of failures
+ */
+static gint
+do_test (const xmlChar *id, const xmlChar *sql,
+	 const gchar *computed_type, const xmlChar *computed_exp, gboolean require_pk) 
+{
+	static GdaSqlParser *parser = NULL;
+	GdaStatement *stmt;
+	GError *error = NULL;
+
+	if (!parser) {
+		parser = gda_connection_create_parser (cnc);
+		if (!parser)
+			parser = gda_sql_parser_new ();
+	}
+
+#ifdef GDA_DEBUG
+	g_print ("===== TEST %s COMPUTING %s (%s), SQL: @%s \n", id, computed_type, 
+		 require_pk ? "PK fields" : "All fields", sql);
+#endif
+
+	stmt = gda_sql_parser_parse_string (parser, sql, NULL, NULL);
+	if (!stmt) {
+		g_print ("ERROR for test '%s': could not parse statement\n", id);
+		return FALSE;
+	}
+	/*g_print ("EXP %d, got %d\n", valid_expected, is_valid);*/
+	/*g_print ("PARSED: %s\n", gda_statement_serialize (stmt));*/
+
+	if (computed_exp) {
+		GdaStatement *cstmt = NULL;
+		switch (*computed_type) {
+		case 'i':
+		case 'I':
+			gda_compute_dml_statements (cnc, stmt, require_pk, &cstmt, NULL, NULL, &error);
+			break;
+		case 'u':
+		case 'U':
+			gda_compute_dml_statements (cnc, stmt, require_pk, NULL, &cstmt, NULL, &error);
+			break;
+		case 'd':
+		case 'D':
+			gda_compute_dml_statements (cnc, stmt, require_pk, NULL, NULL, &cstmt, &error);
+			break;
+		default:
+			TO_IMPLEMENT;
+		}
+
+		if (!*computed_exp && cstmt) {
+			g_object_unref (stmt);
+			gchar *serial, *rend;
+			serial = gda_statement_serialize (cstmt);
+			rend = gda_statement_to_sql (cstmt, NULL, NULL);
+			g_print ("ERROR for test '%s': %s statement created but none expected\n"
+				 "\tgot: %s\n\tSQL: %s\n", id, computed_type, serial, rend);
+			g_free (serial);
+			g_free (rend);
+			g_object_unref (cstmt);
+			return FALSE;
+		}
+		if (*computed_exp && !cstmt) {
+			g_print ("ERROR for test '%s': %s statement not created but expected: %s\n", id,
+				 computed_type,
+				 error && error->message ? error->message : "No detail");
+			g_object_unref (stmt);
+			return FALSE;
+		}
+		if (*computed_exp && cstmt) {
+			gchar *serial;
+			serial = gda_statement_serialize (cstmt);
+			if (strcmp (serial, computed_exp)) {
+				gchar *rend;
+				rend = gda_statement_to_sql (cstmt, NULL, NULL);
+				g_print ("ERROR for test '%s': computed %s statement is incorrect:\n"
+					 "\texp: %s\n\tgot: %s\n\tSQL: %s\n", id, computed_type, computed_exp, serial,
+					 rend);
+				g_free (rend);
+				g_object_unref (stmt);
+				g_object_unref (cstmt);
+				g_free (serial);
+				return FALSE;
+			}
+			g_free (serial);
+		}
+	}
+	g_object_unref (stmt);
+	return TRUE;
+}

Added: trunk/tests/parser/check_normalization.c
==============================================================================
--- (empty file)
+++ trunk/tests/parser/check_normalization.c	Sun Apr  6 11:47:57 2008
@@ -0,0 +1,139 @@
+#include <stdio.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <gmodule.h>
+#include <libgda/libgda.h>
+#include <libgda/gda-util.h>
+#include <sql-parser/gda-sql-parser.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+GdaConnection *cnc;
+static gint do_test (const xmlChar *id, const xmlChar *sql, const xmlChar *norm);
+
+int 
+main (int argc, char** argv)
+{
+	xmlDocPtr doc;
+        xmlNodePtr root, node;
+	gint failures = 0;
+	gint ntests = 0;
+	gchar *fname;
+
+	gda_init ("Parser validation", ".1", argc, argv);
+
+	/* open connection */
+	gchar *cnc_string;
+	fname = g_build_filename (ROOT_DIR, "data", NULL);
+	cnc_string = g_strdup_printf ("DB_DIR=%s;DB_NAME=sales_test", fname);
+	g_free (fname);
+	cnc = gda_connection_open_from_string ("SQLite", cnc_string, NULL,
+					       GDA_CONNECTION_OPTIONS_READ_ONLY, NULL);
+	if (!cnc) {
+		g_print ("Failed to open connection, cnc_string = %s\n", cnc_string);
+		exit (1);
+	}
+	if (!gda_connection_update_meta_store (cnc, NULL, NULL)) {
+		g_print ("Failed to update meta store, cnc_string = %s\n", cnc_string);
+		exit (1);
+	}
+	g_free (cnc_string);
+
+	/* load file */
+	fname = g_build_filename (ROOT_DIR, "tests", "parser", "testvalid.xml", NULL);
+	if (! g_file_test (fname, G_FILE_TEST_EXISTS)) {
+                g_print ("File '%s' does not exist\n", fname);
+                exit (1);
+        }
+
+	/* use test data */
+	doc = xmlParseFile (fname);
+	g_free (fname);
+	g_assert (doc);
+	root = xmlDocGetRootElement (doc);
+	g_assert (!strcmp ((gchar*) root->name, "testdata"));
+	for (node = root->children; node; node = node->next) {
+		if (strcmp ((gchar*) node->name, "test"))
+			continue;
+		xmlNodePtr snode;
+		xmlChar *sql = NULL;
+		xmlChar *id;
+		xmlChar *norm = NULL;
+
+		id = xmlGetProp (node, BAD_CAST "id");
+		for (snode = node->children; snode; snode = snode->next) {
+			if (!strcmp ((gchar*) snode->name, "sql")) 
+				sql = xmlNodeGetContent (snode);
+			if (!strcmp ((gchar*) snode->name, "normalized")) 
+				norm = xmlNodeGetContent (snode);
+		}
+		if (sql && norm) {
+			if (!do_test (id, sql, norm))
+				failures++;
+			ntests++;
+		}
+
+		/* mem free */
+		if (sql) xmlFree (sql);
+		if (norm) xmlFree (norm);
+		if (id)	xmlFree (id);
+	}
+	xmlFreeDoc (doc);
+
+	g_print ("TESTS COUNT: %d\n", ntests);
+	g_print ("FAILURES: %d\n", failures);
+  
+	return failures != 0 ? 1 : 0;
+}
+
+/*
+ * Returns: the number of failures
+ */
+static gint
+do_test (const xmlChar *id, const xmlChar *sql, const xmlChar *norm) 
+{
+	static GdaSqlParser *parser = NULL;
+	GdaStatement *stmt;
+	GError *error = NULL;
+	gchar *str;
+
+	if (!parser) {
+		parser = gda_connection_create_parser (cnc);
+		if (!parser)
+			parser = gda_sql_parser_new ();
+	}
+
+#ifdef GDA_DEBUG
+	g_print ("===== TEST %s SQL: @%s \n", id, sql);
+#endif
+
+	stmt = gda_sql_parser_parse_string (parser, sql, NULL, NULL);
+	if (!stmt) {
+		g_print ("ERROR for test '%s': could not parse statement\n", id);
+		return FALSE;
+	}
+	if (!gda_statement_normalize (stmt, cnc, &error)) {
+		g_print ("ERROR for test '%s': statement can't be normalized: %s\n", id,
+			 error && error->message ? error->message : "No detail");
+		g_object_unref (stmt);
+		return FALSE;
+	}
+
+	str = gda_statement_serialize (stmt);
+	if (strcmp (str, norm)) {
+		gchar *sql;
+		sql = gda_statement_to_sql (stmt, NULL, NULL);
+		g_print ("ERROR for test '%s': \n\tEXP: %s\n\tGOT: %s\n\tSQL: %s\n", id, norm, str, sql);
+		g_free (sql);
+		g_free (str);
+		return FALSE;
+	}
+	
+	g_free (str);
+	g_object_unref (stmt);
+	return TRUE;
+}

Modified: trunk/tests/parser/check_validation.c
==============================================================================
--- trunk/tests/parser/check_validation.c	(original)
+++ trunk/tests/parser/check_validation.c	Sun Apr  6 11:47:57 2008
@@ -13,8 +13,7 @@
 #include <libxml/tree.h>
 
 GdaConnection *cnc;
-static gint do_test (const xmlChar *id, const xmlChar *sql, gboolean valid_expected, 
-		     const gchar *computed_type, const xmlChar *computed_exp, gboolean require_pk);
+static gint do_test (const xmlChar *id, const xmlChar *sql, gboolean valid_expected);
 
 int 
 main (int argc, char** argv)
@@ -77,28 +76,9 @@
 					xmlFree (prop);
 				}
 			}
-			else if (!strcmp ((gchar*) snode->name, "insert") ||
-				 !strcmp ((gchar*) snode->name, "update") ||
-				 !strcmp ((gchar*) snode->name, "delete")) {
-				xmlChar *comp_exp;
-				xmlChar *prop;
-				gboolean require_pk = TRUE;
-				comp_exp = xmlNodeGetContent (snode);
-				prop = xmlGetProp (snode, "need_pk");
-				if (prop) {
-					if ((*prop == 'f') || (*prop == 'F') || (*prop == '0'))
-						require_pk = FALSE;
-					xmlFree (prop);
-				}
-				if (sql) {
-					if (!do_test (id, sql, valid, snode->name, comp_exp, require_pk))
-						failures++;
-					ntests++;
-				}
-			}
 		}
 		if (sql) {
-			if (!do_test (id, sql, valid, NULL, NULL, FALSE))
+			if (!do_test (id, sql, valid))
 				failures++;
 			ntests++;
 		}
@@ -119,8 +99,7 @@
  * Returns: the number of failures
  */
 static gint
-do_test (const xmlChar *id, const xmlChar *sql, gboolean valid_expected, 
-	 const gchar *computed_type, const xmlChar *computed_exp, gboolean require_pk) 
+do_test (const xmlChar *id, const xmlChar *sql, gboolean valid_expected) 
 {
 	static GdaSqlParser *parser = NULL;
 	GdaStatement *stmt;
@@ -134,11 +113,7 @@
 	}
 
 #ifdef GDA_DEBUG
-	if (computed_type)
-		g_print ("===== TEST %s COMPUTING %s (%s), SQL: @%s \n", id, computed_type, 
-			 require_pk ? "PK fields" : "All fields", sql);
-	else
-		g_print ("===== TEST %s SQL: @%s \n", id, sql);
+	g_print ("===== TEST %s SQL: @%s \n", id, sql);
 #endif
 
 	stmt = gda_sql_parser_parse_string (parser, sql, NULL, NULL);
@@ -161,62 +136,6 @@
 	/*g_print ("EXP %d, got %d\n", valid_expected, is_valid);*/
 	/*g_print ("PARSED: %s\n", gda_statement_serialize (stmt));*/
 
-	if (computed_exp) {
-		GdaStatement *cstmt = NULL;
-		switch (*computed_type) {
-		case 'i':
-		case 'I':
-			gda_compute_dml_statements (cnc, stmt, require_pk, &cstmt, NULL, NULL, &error);
-			break;
-		case 'u':
-		case 'U':
-			gda_compute_dml_statements (cnc, stmt, require_pk, NULL, &cstmt, NULL, &error);
-			break;
-		case 'd':
-		case 'D':
-			gda_compute_dml_statements (cnc, stmt, require_pk, NULL, NULL, &cstmt, &error);
-			break;
-		default:
-			TO_IMPLEMENT;
-		}
-
-		if (!*computed_exp && cstmt) {
-			g_object_unref (stmt);
-			gchar *serial, *rend;
-			serial = gda_statement_serialize (cstmt);
-			rend = gda_statement_to_sql (cstmt, NULL, NULL);
-			g_print ("ERROR for test '%s': %s statement created but none expected\n"
-				 "\tgot: %s\n\tSQL: %s\n", id, computed_type, serial, rend);
-			g_free (serial);
-			g_free (rend);
-			g_object_unref (cstmt);
-			return FALSE;
-		}
-		if (*computed_exp && !cstmt) {
-			g_print ("ERROR for test '%s': %s statement not created but expected: %s\n", id,
-				 computed_type,
-				 error && error->message ? error->message : "No detail");
-			g_object_unref (stmt);
-			return FALSE;
-		}
-		if (*computed_exp && cstmt) {
-			gchar *serial;
-			serial = gda_statement_serialize (cstmt);
-			if (strcmp (serial, computed_exp)) {
-				gchar *rend;
-				rend = gda_statement_to_sql (cstmt, NULL, NULL);
-				g_print ("ERROR for test '%s': computed %s statement is incorrect:\n"
-					 "\texp: %s\n\tgot: %s\n\tSQL: %s\n", id, computed_type, computed_exp, serial,
-					 rend);
-				g_free (rend);
-				g_object_unref (stmt);
-				g_object_unref (cstmt);
-				g_free (serial);
-				return FALSE;
-			}
-			g_free (serial);
-		}
-	}
 	g_object_unref (stmt);
 	return TRUE;
 }

Modified: trunk/tests/parser/testvalid.xml
==============================================================================
--- trunk/tests/parser/testvalid.xml	(original)
+++ trunk/tests/parser/testvalid.xml	Sun Apr  6 11:47:57 2008
@@ -57,7 +57,8 @@
   <!-- target has no primary key -->
   <test id="pre3">
     <sql valid="t">SELECT * FROM sales_orga</sql>
-    <insert>unknown</insert>
+    <normalized>{"statement":{"sql":"SELECT * FROM sales_orga","stmt_type":"SELECT","contents":{"distinct":"false","fields":[{"expr":{"value":"id_salesrep"},"field_name":"id_salesrep"},{"expr":{"value":"id_role"},"field_name":"id_role"},{"expr":{"value":"note"},"field_name":"note"}],"from":{"targets":[{"expr":{"value":"sales_orga"},"table_name":"sales_orga"}]}}}}</normalized>
+    <insert>{"statement":{"sql":null,"stmt_type":"INSERT","contents":{"table":"sales_orga","fields":["id_salesrep","id_role","note"],"values":[[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+2","descr":null,"type":"gchararray","is_param":false,"nullok":true}}]]}}}</insert>
     <update/>
     <delete/>
   </test>
@@ -65,8 +66,8 @@
   <test id="0">
     <sql valid="t">SELECT id, name FROM customers</sql>
     <insert>{"statement":{"sql":null,"stmt_type":"INSERT","contents":{"table":"customers","fields":["id","name"],"values":[[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gchararray","is_param":false,"nullok":false}}]]}}}</insert>
-    <update>unknown</update>
-    <delete>unknown</delete>
+    <update>{"statement":{"sql":null,"stmt_type":"UPDATE","contents":{"table":"customers","fields":["id","name"],"expressions":[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gchararray","is_param":false,"nullok":false}}],"condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</update>
+    <delete>{"statement":{"sql":null,"stmt_type":"DELETE","contents":{"table":"customers","condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</delete>
   </test>
 
   <test id="0.1">
@@ -79,4 +80,62 @@
     <insert>{"statement":{"sql":null,"stmt_type":"INSERT","contents":{"table":"customers","fields":["id","name"],"values":[[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+2","descr":null,"type":"gchararray","is_param":false,"nullok":false}}]]}}}</insert>
   </test>
 
+  <test id="0.3">
+    <sql valid="t">SELECT id || name FROM customers</sql>
+    <insert/>
+    <update/>
+    <delete/>
+  </test>
+
+  <!-- normalization test -->
+  <test id="N0">
+    <sql valid="t">SELECT count (*) FROM sales_orga</sql>
+    <normalized>{"statement":{"sql":"SELECT count (*) FROM sales_orga","stmt_type":"SELECT","contents":{"distinct":"false","fields":[{"expr":{"func":{"function_name":"count","function_args":[{"value":"*"}]}}}],"from":{"targets":[{"expr":{"value":"sales_orga"},"table_name":"sales_orga"}]}}}}</normalized>
+    <insert/>
+    <update/>
+    <delete/>
+  </test>
+
+  <test id="N1">
+    <sql valid="t">SELECT name, *, city FROM customers</sql>
+    <normalized>{"statement":{"sql":"SELECT name, *, city FROM customers","stmt_type":"SELECT","contents":{"distinct":"false","fields":[{"expr":{"value":"name"},"field_name":"name"},{"expr":{"value":"id"},"field_name":"id"},{"expr":{"value":"name"},"field_name":"name"},{"expr":{"value":"default_served_by"},"field_name":"default_served_by"},{"expr":{"value":"country"},"field_name":"country"},{"expr":{"value":"city"},"field_name":"city"},{"expr":{"value":"city"},"field_name":"city"}],"from":{"targets":[{"expr":{"value":"customers"},"table_name":"customers"}]}}}}</normalized>
+    <insert>{"statement":{"sql":null,"stmt_type":"INSERT","contents":{"table":"customers","fields":["name","id","default_served_by","country","city"],"values":[[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gchararray","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+3","descr":null,"type":"gint","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+4","descr":null,"type":"gchararray","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+5","descr":null,"type":"gchararray","is_param":false,"nullok":true}}]]}}}</insert>
+    <update>{"statement":{"sql":null,"stmt_type":"UPDATE","contents":{"table":"customers","fields":["name","id","default_served_by","country","city"],"expressions":[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gchararray","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+3","descr":null,"type":"gint","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+4","descr":null,"type":"gchararray","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+5","descr":null,"type":"gchararray","is_param":false,"nullok":true}}],"condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</update>
+    <delete>{"statement":{"sql":null,"stmt_type":"DELETE","contents":{"table":"customers","condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</delete>
+  </test>
+
+  <test id="N2">
+    <sql valid="t">SELECT s.* FROM sales_orga as s</sql>
+    <normalized>{"statement":{"sql":"SELECT s.* FROM sales_orga as s","stmt_type":"SELECT","contents":{"distinct":"false","fields":[{"expr":{"value":"s.id_salesrep"},"field_name":"id_salesrep","table_name":"s"},{"expr":{"value":"s.id_role"},"field_name":"id_role","table_name":"s"},{"expr":{"value":"s.note"},"field_name":"note","table_name":"s"}],"from":{"targets":[{"expr":{"value":"sales_orga"},"table_name":"sales_orga","as":"s"}]}}}}</normalized>
+    <insert>{"statement":{"sql":null,"stmt_type":"INSERT","contents":{"table":"sales_orga","fields":["id_salesrep","id_role","note"],"values":[[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+2","descr":null,"type":"gchararray","is_param":false,"nullok":true}}]]}}}</insert>
+    <update></update>
+    <delete></delete>
+    <update needpk="f"></update>
+    <delete needpk="f"></delete>
+  </test>
+
+  <test id="N3">
+    <sql valid="t">SELECT "name", c.*, "id" FROM customers as c</sql>
+    <normalized>{"statement":{"sql":"SELECT \"name\", c.*, \"id\" FROM customers as c","stmt_type":"SELECT","contents":{"distinct":"false","fields":[{"expr":{"value":"\"name\""},"field_name":"\"name\""},{"expr":{"value":"c.id"},"field_name":"id","table_name":"c"},{"expr":{"value":"c.name"},"field_name":"name","table_name":"c"},{"expr":{"value":"c.default_served_by"},"field_name":"default_served_by","table_name":"c"},{"expr":{"value":"c.country"},"field_name":"country","table_name":"c"},{"expr":{"value":"c.city"},"field_name":"city","table_name":"c"},{"expr":{"value":"\"id\""},"field_name":"\"id\""}],"from":{"targets":[{"expr":{"value":"customers"},"table_name":"customers","as":"c"}]}}}}</normalized>
+    <insert>{"statement":{"sql":null,"stmt_type":"INSERT","contents":{"table":"customers","fields":["\"name\"","id","default_served_by","country","city"],"values":[[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gchararray","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+3","descr":null,"type":"gint","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+4","descr":null,"type":"gchararray","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+5","descr":null,"type":"gchararray","is_param":false,"nullok":true}}]]}}}</insert>
+    <update>{"statement":{"sql":null,"stmt_type":"UPDATE","contents":{"table":"customers","fields":["\"name\"","id","default_served_by","country","city"],"expressions":[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gchararray","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+3","descr":null,"type":"gint","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+4","descr":null,"type":"gchararray","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+5","descr":null,"type":"gchararray","is_param":false,"nullok":true}}],"condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</update>
+    <delete>{"statement":{"sql":null,"stmt_type":"DELETE","contents":{"table":"customers","condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</delete>
+  </test>
+
+  <test id="N4">
+    <sql valid="t">SELECT "name", "customers".*, "id" FROM customers as c</sql>
+    <normalized>{"statement":{"sql":"SELECT \"name\", \"customers\".*, \"id\" FROM customers as c","stmt_type":"SELECT","contents":{"distinct":"false","fields":[{"expr":{"value":"\"name\""},"field_name":"\"name\""},{"expr":{"value":"\"customers\".id"},"field_name":"id","table_name":"\"customers\""},{"expr":{"value":"\"customers\".name"},"field_name":"name","table_name":"\"customers\""},{"expr":{"value":"\"customers\".default_served_by"},"field_name":"default_served_by","table_name":"\"customers\""},{"expr":{"value":"\"customers\".country"},"field_name":"country","table_name":"\"customers\""},{"expr":{"value":"\"customers\".city"},"field_name":"city","table_name":"\"customers\""},{"expr":{"value":"\"id\""},"field_name":"\"id\""}],"from":{"targets":[{"expr":{"value":"customers"},"table_name":"customers","as":"c"}]}}}}</normalized>
+    <insert>{"statement":{"sql":null,"stmt_type":"INSERT","contents":{"table":"customers","fields":["\"name\"","id","default_served_by","country","city"],"values":[[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gchararray","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+3","descr":null,"type":"gint","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+4","descr":null,"type":"gchararray","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+5","descr":null,"type":"gchararray","is_param":false,"nullok":true}}]]}}}</insert>
+    <update>{"statement":{"sql":null,"stmt_type":"UPDATE","contents":{"table":"customers","fields":["\"name\"","id","default_served_by","country","city"],"expressions":[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gchararray","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+3","descr":null,"type":"gint","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+4","descr":null,"type":"gchararray","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+5","descr":null,"type":"gchararray","is_param":false,"nullok":true}}],"condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</update>
+    <delete>{"statement":{"sql":null,"stmt_type":"DELETE","contents":{"table":"customers","condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</delete>
+  </test>
+
+  <test id="N5">
+    <sql valid="t">SELECT NaMe, c."*", ID FROM customers as c</sql>
+    <normalized>{"statement":{"sql":"SELECT NaMe, c.\"*\", ID FROM customers as c","stmt_type":"SELECT","contents":{"distinct":"false","fields":[{"expr":{"value":"NaMe"},"field_name":"NaMe"},{"expr":{"value":"c.id"},"field_name":"id","table_name":"c"},{"expr":{"value":"c.name"},"field_name":"name","table_name":"c"},{"expr":{"value":"c.default_served_by"},"field_name":"default_served_by","table_name":"c"},{"expr":{"value":"c.country"},"field_name":"country","table_name":"c"},{"expr":{"value":"c.city"},"field_name":"city","table_name":"c"},{"expr":{"value":"ID"},"field_name":"ID"}],"from":{"targets":[{"expr":{"value":"customers"},"table_name":"customers","as":"c"}]}}}}</normalized>
+    <insert>{"statement":{"sql":null,"stmt_type":"INSERT","contents":{"table":"customers","fields":["NaMe","id","default_served_by","country","city"],"values":[[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gchararray","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+3","descr":null,"type":"gint","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+4","descr":null,"type":"gchararray","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+5","descr":null,"type":"gchararray","is_param":false,"nullok":true}}]]}}}</insert>
+    <update>{"statement":{"sql":null,"stmt_type":"UPDATE","contents":{"table":"customers","fields":["NaMe","id","default_served_by","country","city"],"expressions":[{"value":null,"param_spec":{"name":"+0","descr":null,"type":"gchararray","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+1","descr":null,"type":"gint","is_param":false,"nullok":false}},{"value":null,"param_spec":{"name":"+3","descr":null,"type":"gint","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+4","descr":null,"type":"gchararray","is_param":false,"nullok":true}},{"value":null,"param_spec":{"name":"+5","descr":null,"type":"gchararray","is_param":false,"nullok":true}}],"condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</update>
+    <delete>{"statement":{"sql":null,"stmt_type":"DELETE","contents":{"table":"customers","condition":{"operation":{"operator":"=","operand0":{"value":"id"},"operand1":{"value":null,"param_spec":{"name":"-0","descr":null,"type":"gint","is_param":false,"nullok":false}}}}}}}</delete>
+  </test>
+
 </testdata>



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