[libgda] Improved statement rewriting for NULL parameters



commit 036420a459b0bb241716cd9a14c3dd1eb2b21f63
Author: Vivien Malerba <malerba gnome-db org>
Date:   Fri Aug 19 15:56:01 2011 +0200

    Improved statement rewriting for NULL parameters

 doc/C/libgda-sections.txt             |    1 +
 libgda/gda-util.c                     |   87 ++++++++++++++++++++++++++++++--
 libgda/gda-util.h                     |    6 ++-
 libgda/libgda.symbols                 |    1 +
 libgda/providers-support/gda-pstmt.h  |    1 +
 libgda/sqlite/gda-sqlite-provider.c   |   57 ++++++++++++++++-----
 tests/parser/check_rewrite_for_null.c |    2 +-
 7 files changed, 132 insertions(+), 23 deletions(-)
---
diff --git a/doc/C/libgda-sections.txt b/doc/C/libgda-sections.txt
index b8fa1c9..96b67e1 100644
--- a/doc/C/libgda-sections.txt
+++ b/doc/C/libgda-sections.txt
@@ -1585,6 +1585,7 @@ gda_meta_store_set_reserved_keywords_func
 <SUBSECTION>
 gda_compute_dml_statements
 gda_compute_select_statement_from_update
+gda_rewrite_sql_statement_for_null_parameters
 gda_rewrite_statement_for_null_parameters
 gda_compute_unique_table_row_condition
 gda_compute_unique_table_row_condition_with_cnc
diff --git a/libgda/gda-util.c b/libgda/gda-util.c
index a3c04d1..75bd65a 100644
--- a/libgda/gda-util.c
+++ b/libgda/gda-util.c
@@ -1418,7 +1418,7 @@ get_prev_expr (GSList *expr_list, GdaSqlAnyPart *expr)
 }
 
 static gboolean
-null_param_unknown_foreach_func (GdaSqlAnyPart *part, NullData *data , GError **error)
+null_param_unknown_foreach_func (GdaSqlAnyPart *part, NullData *data, GError **error)
 {
 	GdaSqlExpr *expr;
 	if ((part->type != GDA_SQL_ANY_EXPR) || !((GdaSqlExpr*) part)->param_spec)
@@ -1484,28 +1484,36 @@ null_param_unknown_foreach_func (GdaSqlAnyPart *part, NullData *data , GError **
 }
 
 /**
- * gda_rewrite_statement_for_null_parameters:
- * @stmt: (transfer full): a #GdaSqlStatement
+ * gda_rewrite_sql_statement_for_null_parameters: (skip):
+ * @sqlst: (transfer full): a #GdaSqlStatement
  * @params: a #GdaSet to be used as parameters when executing @stmt
+ * @out_modified: (allow-none): a place to store the boolean which tells if @stmt has been modified or not, or %NULL
  * @error: a place to store errors, or %NULL
  *
- * Modifies @stmt to take into account any parameter which might be %NULL: if @stmt contains the
+ * Modifies @sqlst to take into account any parameter which might be %NULL: if @sqlst contains the
  * equivalent of "xxx = &lt;parameter definition&gt;" and if that parameter is in @params and
  * its value is of type GDA_TYPE_NUL, then that part is replaced with "xxx IS NULL". It also
  * handles the "xxx IS NOT NULL" transformation.
  *
+ * If @out_modified is not %NULL, then it will be set to %TRUE if @sqlst has been modified
+ * by this function, and to %FALSE otherwise.
+ *
  * This function is used by provider's implementations to make sure one can use parameters with
  * NULL values in statements without having to rewrite statements, as database usually don't
  * consider that "xxx = NULL" is the same as "xxx IS NULL" when using parameters.
  *
- * Returns: (transfer full): the modified @stmt statement, or %NULL if an error occurred
+ * Returns: (transfer full): the modified @sqlst statement, or %NULL if an error occurred
  *
  * Since: 4.2.9
  */
 GdaSqlStatement *
-gda_rewrite_statement_for_null_parameters (GdaSqlStatement *sqlst, GdaSet *params, GError **error)
+gda_rewrite_sql_statement_for_null_parameters (GdaSqlStatement *sqlst, GdaSet *params,
+					       gboolean *out_modified, GError **error)
 {
+	if (out_modified)
+		*out_modified = FALSE;
 	g_return_val_if_fail (sqlst, sqlst);
+
 	if (!params)
 		return sqlst;
 	GSList *list;
@@ -1530,6 +1538,8 @@ gda_rewrite_statement_for_null_parameters (GdaSqlStatement *sqlst, GdaSet *param
 			gda_sql_statement_free (sqlst);
 			return NULL;
 		}
+		if (out_modified)
+			*out_modified = data.expr_list ? TRUE : FALSE;
 		for (list = data.expr_list; list; list = list->next) {
 			((GdaSqlStatementUnknown*) data.contents)->expressions = 
 				g_slist_remove (((GdaSqlStatementUnknown*) data.contents)->expressions,
@@ -1544,6 +1554,8 @@ gda_rewrite_statement_for_null_parameters (GdaSqlStatement *sqlst, GdaSet *param
 			gda_sql_statement_free (sqlst);
 			return NULL;
 		}
+		if (out_modified)
+			*out_modified = data.expr_list ? TRUE : FALSE;
 		for (list = data.expr_list; list; list = list->next) {
 			GdaSqlOperation *op;
 			op = (GdaSqlOperation*) (((GdaSqlAnyPart*) list->data)->parent);
@@ -1559,6 +1571,69 @@ gda_rewrite_statement_for_null_parameters (GdaSqlStatement *sqlst, GdaSet *param
 	return sqlst;
 }
 
+/**
+ * gda_rewrite_statement_for_null_parameters:
+ * @stmt: (transfer none): a #GdaStatement
+ * @params: a #GdaSet to be used as parameters when executing @stmt
+ * @out_stmt: (transfer full) (allow-none): a place to store the new #GdaStatement, or %NULL
+ * @error: a place to store errors, or %NULL
+ *
+ * Modifies @stmt to take into account any parameter which might be %NULL: if @stmt contains the
+ * equivalent of "xxx = &lt;parameter definition&gt;" and if that parameter is in @params and
+ * its value is of type GDA_TYPE_NUL, then that part is replaced with "xxx IS NULL". It also
+ * handles the "xxx IS NOT NULL" transformation.
+ *
+ * For example the following SELECT:
+ * <programlisting>SELECT * FROM data WHERE id = ##id::int::null AND name = ##name::string</programlisting>
+ * in case the "id" parameter is set to NULL, is converted to:
+ * <programlisting>SELECT * FROM data WHERE id IS NULL AND name = ##name::string</programlisting>
+ *
+ * if @out_stmt is not %NULL, then it will contain:
+ * <itemizedlist>
+ *   <listitem><para>the modified statement if some modifications were required and no error occured (the function returns %TRUE)</para></listitem>
+ *   <listitem><para>%NULL if no modification to @stmt were required and no erro occurred (the function returns %FALSE)</para></listitem>
+ *   <listitem><para>%NULL if an error occured (the function returns %TRUE)</para></listitem>
+ * </itemizedlist>
+ *
+ * This function is used by provider's implementations to make sure one can use parameters with
+ * NULL values in statements without having to rewrite statements, as database usually don't
+ * consider that "xxx = NULL" is the same as "xxx IS NULL" when using parameters.
+ *
+ * Returns: %TRUE if @stmt needs to be transformed to handle NULL parameters, and %FALSE otherwise
+ *
+ * Since: 4.2.9
+ */
+gboolean
+gda_rewrite_statement_for_null_parameters (GdaStatement *stmt, GdaSet *params,
+					   GdaStatement **out_stmt, GError **error)
+{
+	GdaSqlStatement *sqlst;
+	gboolean mod;
+	g_return_val_if_fail (GDA_IS_STATEMENT (stmt), FALSE);
+	g_return_val_if_fail (!params || GDA_IS_SET (params), FALSE);
+
+	if (out_stmt)
+		*out_stmt = NULL;
+	if (!params)
+		return FALSE;
+
+	g_object_get ((GObject*) stmt, "structure", &sqlst, NULL);
+	if (gda_rewrite_sql_statement_for_null_parameters (sqlst, params, &mod, error)) {
+		if (out_stmt) {
+			if (mod) {
+				*out_stmt = g_object_new (GDA_TYPE_STATEMENT, "structure", sqlst, NULL);
+				gda_sql_statement_free (sqlst);
+			}
+		}
+		return mod;
+	}
+	else {
+		/* error => leave *out_stmt to %NULL */
+		return TRUE;
+	}
+}
+
+
 
 static gboolean stmt_rewrite_insert_remove (GdaSqlStatementInsert *ins, GdaSet *params, GError **error);
 static gboolean stmt_rewrite_insert_default_keyword (GdaSqlStatementInsert *ins, GdaSet *params, GError **error);
diff --git a/libgda/gda-util.h b/libgda/gda-util.h
index 51ab011..9cc51c8 100644
--- a/libgda/gda-util.h
+++ b/libgda/gda-util.h
@@ -98,8 +98,10 @@ gboolean         gda_compute_dml_statements (GdaConnection *cnc, GdaStatement *s
 					     GdaStatement **insert_stmt, GdaStatement **update_stmt, GdaStatement **delete_stmt, 
 					     GError **error);
 GdaSqlStatement *gda_compute_select_statement_from_update (GdaStatement *update_stmt, GError **error);
-GdaSqlStatement *gda_rewrite_statement_for_null_parameters (GdaSqlStatement *sqlst, GdaSet *params,
-							    GError **error);
+GdaSqlStatement *gda_rewrite_sql_statement_for_null_parameters (GdaSqlStatement *sqlst, GdaSet *params,
+								gboolean *out_modified, GError **error);
+gboolean         gda_rewrite_statement_for_null_parameters (GdaStatement *stmt, GdaSet *params,
+							    GdaStatement **out_stmt, GError **error);
 
 /*
  * DSN and connection string manipulations
diff --git a/libgda/libgda.symbols b/libgda/libgda.symbols
index d13e5b0..6875350 100644
--- a/libgda/libgda.symbols
+++ b/libgda/libgda.symbols
@@ -508,6 +508,7 @@
 	gda_repetitive_statement_get_type
 	gda_repetitive_statement_new
 	gda_rewrite_statement_for_null_parameters
+	gda_rewrite_sql_statement_for_null_parameters
 	gda_rfc1738_decode
 	gda_rfc1738_encode
 	gda_row_get_length
diff --git a/libgda/providers-support/gda-pstmt.h b/libgda/providers-support/gda-pstmt.h
index ee41052..e3953e2 100644
--- a/libgda/providers-support/gda-pstmt.h
+++ b/libgda/providers-support/gda-pstmt.h
@@ -48,6 +48,7 @@ struct _GdaPStmt {
 	GSList       *tmpl_columns; /* list of #GdaColumn objects which data models created from this prep. statement
 				     * can copy */
 
+	/*< private >*/
 	/* Padding for future expansion */
 	gpointer         _gda_reserved1;
 	gpointer         _gda_reserved2;
diff --git a/libgda/sqlite/gda-sqlite-provider.c b/libgda/sqlite/gda-sqlite-provider.c
index 57a6bb0..10872d0 100644
--- a/libgda/sqlite/gda-sqlite-provider.c
+++ b/libgda/sqlite/gda-sqlite-provider.c
@@ -2847,7 +2847,14 @@ gda_sqlite_provider_statement_execute (GdaServerProvider *provider, GdaConnectio
 			}
 		}
 		if (!h) {
-			if (! allow_noparam) {
+			if (allow_noparam) {
+				/* bind param to NULL */
+				SQLITE3_CALL (sqlite3_bind_null) (ps->sqlite_stmt, i);
+				empty_rs = TRUE;
+				continue;
+			}
+			else {
+
 				gchar *str;
 				str = g_strdup_printf (_("Missing parameter '%s' to execute query"), pname);
 				event = gda_connection_point_available_event (cnc, GDA_CONNECTION_EVENT_ERROR);
@@ -2857,16 +2864,16 @@ gda_sqlite_provider_statement_execute (GdaServerProvider *provider, GdaConnectio
 				g_free (str);
 				break;
 			}
-			else {
+		}
+
+		if (!gda_holder_is_valid (h)) {
+			if (allow_noparam) {
 				/* bind param to NULL */
 				SQLITE3_CALL (sqlite3_bind_null) (ps->sqlite_stmt, i);
 				empty_rs = TRUE;
 				continue;
 			}
-		}
-
-		if (!gda_holder_is_valid (h)) {
-			if (! allow_noparam) {
+			else {
 				gchar *str;
 				str = g_strdup_printf (_("Parameter '%s' is invalid"), pname);
 				event = gda_connection_point_available_event (cnc, GDA_CONNECTION_EVENT_ERROR);
@@ -2876,12 +2883,6 @@ gda_sqlite_provider_statement_execute (GdaServerProvider *provider, GdaConnectio
 				g_free (str);
 				break;
 			}
-			else {
-				/* bind param to NULL */
-				SQLITE3_CALL (sqlite3_bind_null) (ps->sqlite_stmt, i);
-				empty_rs = TRUE;
-				continue;
-			}
 		}
 		else if (gda_holder_value_is_default (h) && !gda_holder_get_value (h)) {
 			/* create a new GdaStatement to handle all default values and execute it instead */
@@ -2917,8 +2918,36 @@ gda_sqlite_provider_statement_execute (GdaServerProvider *provider, GdaConnectio
 		/*g_print ("BINDING param '%s' to %p\n", pname, h);*/
 
 		const GValue *value = gda_holder_get_value (h);
-		if (!value || gda_value_is_null (value))
-			SQLITE3_CALL (sqlite3_bind_null) (ps->sqlite_stmt, i);
+		if (!value || gda_value_is_null (value)) {
+			GdaStatement *rstmt;
+			if (! gda_rewrite_statement_for_null_parameters (stmt, params, &rstmt, error))
+				SQLITE3_CALL (sqlite3_bind_null) (ps->sqlite_stmt, i);
+			else if (!rstmt)
+				return NULL;
+			else {
+				GObject *obj;
+				obj = gda_sqlite_provider_statement_execute (provider, cnc,
+									     rstmt, params,
+									     model_usage,
+									     col_types,
+									     last_inserted_row,
+									     task_id, async_cb,
+									     cb_data, error);
+				g_object_unref (rstmt);
+				if (GDA_IS_DATA_SELECT (obj)) {
+					GdaPStmt *pstmt;
+					g_object_get (obj, "prepared-stmt", &pstmt, NULL);
+					if (pstmt) {
+						gda_pstmt_set_gda_statement (pstmt, stmt);
+						g_object_unref (pstmt);
+					}
+				}
+				if (new_ps)
+					g_object_unref (ps);
+				pending_blobs_free_list (blobs_list);
+				return obj;
+			}
+		}
 		else if (G_VALUE_TYPE (value) == G_TYPE_STRING)
 			SQLITE3_CALL (sqlite3_bind_text) (ps->sqlite_stmt, i,
 							  g_value_get_string (value), -1, SQLITE_TRANSIENT);
diff --git a/tests/parser/check_rewrite_for_null.c b/tests/parser/check_rewrite_for_null.c
index bdc78f6..23a23e2 100644
--- a/tests/parser/check_rewrite_for_null.c
+++ b/tests/parser/check_rewrite_for_null.c
@@ -83,7 +83,7 @@ do_test (ATest *test)
 	GdaSqlStatement *sqlst;
 	g_object_get (stmt, "structure", &sqlst, NULL);
 
-	sqlst = gda_rewrite_statement_for_null_parameters (sqlst, params, &error);
+	sqlst = gda_rewrite_sql_statement_for_null_parameters (sqlst, params, NULL, &error);
 	if (!sqlst) {
 		g_print ("Rewrite error: %s\n", error && error->message ? error->message : "No detail");
 		return FALSE;



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