[libgda] GdaBrowser: handle multiple foreign keys between 2 tables in data manager perspective



commit 3a19be53ce58145c01f36271ffbe5201d36d23f5
Author: Vivien Malerba <malerba gnome-db org>
Date:   Tue Sep 7 20:38:23 2010 +0200

    GdaBrowser: handle multiple foreign keys between 2 tables in data manager perspective

 tools/browser/data-manager/data-console.c          |   14 +-
 tools/browser/data-manager/data-source.c           |  136 ++++++++++++++++++--
 tools/browser/data-manager/data-source.h           |    6 +-
 tools/browser/data-manager/data-widget.c           |    5 +
 tools/browser/help/C/data-manager-perspective.page |   10 +-
 tools/browser/help/C/data-manager-xml-syntax.page  |   32 ++++-
 6 files changed, 174 insertions(+), 29 deletions(-)
---
diff --git a/tools/browser/data-manager/data-console.c b/tools/browser/data-manager/data-console.c
index f45a6d4..4347fe2 100644
--- a/tools/browser/data-manager/data-console.c
+++ b/tools/browser/data-manager/data-console.c
@@ -42,8 +42,8 @@
 #define MAIN_PAGE_EDITORS 0
 #define MAIN_PAGE_DATA 1
 
-#define EDITOR_PAGE_XML 0
-#define EDITOR_PAGE_UI 1
+#define EDITOR_PAGE_XML 1
+#define EDITOR_PAGE_UI 0
 
 typedef enum {
 	LAYOUT_HORIZ,
@@ -365,16 +365,16 @@ data_console_new (BrowserConnection *bcnc)
 	gtk_notebook_set_show_border (GTK_NOTEBOOK (nb), FALSE);
 	dconsole->priv->editors_notebook = nb;
 
-	dconsole->priv->xml_sped = xml_spec_editor_new (dconsole->priv->mgr);
-	gtk_widget_show (dconsole->priv->xml_sped);
-	gtk_notebook_append_page (GTK_NOTEBOOK (dconsole->priv->editors_notebook),
-				  dconsole->priv->xml_sped, NULL);
-
 	dconsole->priv->ui_sped = ui_spec_editor_new (dconsole->priv->mgr);
 	gtk_widget_show (dconsole->priv->ui_sped);
 	gtk_notebook_append_page (GTK_NOTEBOOK (dconsole->priv->editors_notebook),
 				  dconsole->priv->ui_sped, NULL);
 
+	dconsole->priv->xml_sped = xml_spec_editor_new (dconsole->priv->mgr);
+	gtk_widget_show (dconsole->priv->xml_sped);
+	gtk_notebook_append_page (GTK_NOTEBOOK (dconsole->priv->editors_notebook),
+				  dconsole->priv->xml_sped, NULL);
+
 	/* buttons */
 	GtkWidget *bbox, *button;
 	bbox = gtk_vbutton_box_new ();
diff --git a/tools/browser/data-manager/data-source.c b/tools/browser/data-manager/data-source.c
index 1723777..d291bc9 100644
--- a/tools/browser/data-manager/data-source.c
+++ b/tools/browser/data-manager/data-source.c
@@ -30,6 +30,7 @@
 
 #include "data-source.h"
 #define DEFAULT_DATA_SOURCE_NAME "DataSource"
+#define DEPENDENCY_SEPARATOR "<|>"
 
 /* signals */
 enum {
@@ -60,6 +61,7 @@ static GObjectClass  *parent_class = NULL;
 typedef struct {
 	gchar *dep_id;
 	gchar *dep_table;
+	gchar *dep_columns; /* column names, separated by a |, sorted */
 } Dependency;
 
 static void
@@ -67,18 +69,61 @@ dependency_free (Dependency *dep)
 {
 	g_free (dep->dep_id);
 	g_free (dep->dep_table);
+	g_free (dep->dep_columns);
 	g_free (dep);
 }
+
+/*
+ * converts an array of column names into a single string in the format:
+ * <colname>[SEP<colname>...] where SEP is DEPENDENCY_SEPARATOR
+ * 
+ * Returns: a new string, never %NULL
+ */
+static gchar *
+column_names_to_string (gint size, const gchar **colnames)
+{
+	if (!colnames)
+		return g_strdup ("");
+
+	GString *string = NULL;
+	GArray *colsarray;
+	gint i;
+	colsarray = g_array_new (FALSE, FALSE, sizeof (gchar*));
+	for (i = 0; i < size; i++)
+		g_array_append_val (colsarray, colnames[i]);
+	g_array_sort (colsarray, (GCompareFunc) g_strcmp0);
+	for (i = 0; i < size; i++) {
+		gchar *tmp;
+		tmp = g_array_index (colsarray, gchar *, i);
+		if (!string)
+			string = g_string_new (tmp);
+		else {
+			g_string_append (string, DEPENDENCY_SEPARATOR);
+			g_string_append (string, tmp);
+		}
+	}
+	g_array_free (colsarray, TRUE);
+	return g_string_free (string, FALSE);
+}
+
 static Dependency *
-dependency_find (GSList *dep_list, const gchar *id, const gchar *table)
+dependency_find (GSList *dep_list, const gchar *id, const gchar *table, gint size, const gchar **colnames)
 {
 	GSList *list;
+	gchar *colsstring = NULL;
 	for (list = dep_list; list; list = list->next) {
 		Dependency *dep = (Dependency*) list->data;
 		if (strcmp (dep->dep_id, id) || strcmp (dep->dep_table, table))
 			continue;
-		return dep;
+
+		if (!colsstring)
+			colsstring = column_names_to_string (size, colnames);
+		if (!strcmp (colsstring, dep->dep_columns)) {
+			g_free (colsstring);
+			return dep;
+		}
 	}
+	g_free (colsstring);
 	return NULL;
 }
 
@@ -446,17 +491,42 @@ init_from_table_node (DataSource *source, xmlNodePtr node, GError **error)
 	for (subnode = node->children; subnode; subnode = subnode->next) {
 		if (!strcmp ((gchar*)subnode->name, "depend")) {
 			xmlChar *fk_table, *id;
+			GArray *cols_array = NULL;
+			xmlNodePtr chnode;
 			
 			fk_table = xmlGetProp (subnode, BAD_CAST "foreign_key_table");
 			id = xmlGetProp (subnode, BAD_CAST "id");
+			for (chnode = subnode->children; chnode; chnode = chnode->next) {
+				xmlChar *colname;
+				if (strcmp ((gchar*)chnode->name, "column"))
+					continue;
+				colname = xmlNodeGetContent (chnode);
+				if (colname) {
+					if (! cols_array)
+						cols_array = g_array_new (FALSE, FALSE, sizeof (gchar*));
+					g_array_append_val (cols_array, colname);
+				}
+			}
 
 			if (fk_table &&
-			    ! data_source_add_dependendency (source, (gchar *) fk_table, (gchar*) id, error))
+			    ! data_source_add_dependency (source, (gchar *) fk_table, (gchar*) id,
+							  cols_array ? cols_array->len : 0,
+							  (const gchar **) (cols_array ? cols_array->data : NULL),
+							  error))
 				retval = FALSE;
 			if (fk_table)
 				xmlFree (fk_table);
 			if (id)
 				xmlFree (id);
+			if (cols_array) {
+				gint i;
+				for (i = 0; i < cols_array->len; i++) {
+					xmlChar *colname;
+					colname = g_array_index (cols_array, xmlChar*, i);
+					xmlFree (colname);
+				}
+				g_array_free (cols_array, TRUE);
+			}
 			break;
 		}
 	}
@@ -465,23 +535,27 @@ init_from_table_node (DataSource *source, xmlNodePtr node, GError **error)
 }
 
 /**
- * data_source_add_dependendency
+ * data_source_add_dependency
  * @source: a #DataSource
  * @table: the name of the referenced table
  * @id: the ID of the referenced data source, or %NULL if its ID is the same as the table name
+ * @col_name_size: the size of @col_names
+ * @col_names: names of the FK columns involved in the foreign key, or %NULL
+ * @error: a place to store errors, or %NULL
  *
  * Adds a dependency on the @table table, only for DATA_SOURCE_TABLE sources
  */
 gboolean
-data_source_add_dependendency (DataSource *source, const gchar *table,
-			       const char *id, GError **error)
+data_source_add_dependency (DataSource *source, const gchar *table,
+			    const char *id, gint col_name_size, const gchar **col_names,
+			    GError **error)
 {
 	g_return_val_if_fail (IS_DATA_SOURCE (source), FALSE);
 	g_return_val_if_fail (table && *table, FALSE);
 	g_return_val_if_fail (source->priv->source_type == DATA_SOURCE_TABLE, FALSE);
 	g_return_val_if_fail (source->priv->builder, FALSE);
 
-	if (dependency_find (source->priv->dependencies, id ? id : table, table))
+	if (dependency_find (source->priv->dependencies, id ? id : table, table, col_name_size, col_names))
 		return TRUE;
 
 	GdaMetaTable *mtable, *mlinked;
@@ -500,7 +574,22 @@ data_source_add_dependendency (DataSource *source, const gchar *table,
 	for (list = mtable->fk_list; list; list = list->next) {
 		if (GDA_META_TABLE_FOREIGN_KEY (list->data)->depend_on == GDA_META_DB_OBJECT (mlinked)) {
 			fk = GDA_META_TABLE_FOREIGN_KEY (list->data);
-			break;
+			if (col_names && (col_name_size == fk->cols_nb)) {
+				gint i;
+				for (i = 0; i < col_name_size; i++) {
+					gint j;
+					for (j = 0; j < col_name_size; j++) {
+						if (!strcmp (col_names [i], fk->fk_names_array [j]))
+							break;
+					}
+					if (j == col_name_size) {
+						fk = NULL; /* not this FK */
+						break;
+					}
+				}
+			}
+			if (fk)
+				break;
 		}
 	}
 	if (!fk) {
@@ -508,7 +597,23 @@ data_source_add_dependendency (DataSource *source, const gchar *table,
 			if (GDA_META_TABLE_FOREIGN_KEY (list->data)->depend_on == GDA_META_DB_OBJECT (mtable)) {
 				fk = GDA_META_TABLE_FOREIGN_KEY (list->data);
 				reverse = TRUE;
-				break;
+				if (col_names && (col_name_size == fk->cols_nb)) {
+					gint i;
+					for (i = 0; i < col_name_size; i++) {
+						gint j;
+						for (j = 0; j < col_name_size; j++) {
+							if (!strcmp (col_names [i],
+								     fk->fk_names_array [j]))
+								break;
+						}
+						if (j == col_name_size) {
+							fk = NULL; /* not this FK */
+							break;
+						}
+					}
+				}
+				if (fk)
+					break;
 			}
 		}
 	}
@@ -598,6 +703,7 @@ data_source_add_dependendency (DataSource *source, const gchar *table,
 	Dependency *dep = g_new0 (Dependency, 1);
 	dep->dep_id = g_strdup (id ? id : table);
 	dep->dep_table = g_strdup (table);
+	dep->dep_columns = column_names_to_string (col_name_size, col_names);
 	source->priv->dependencies = g_slist_append (source->priv->dependencies, dep);
 
 	compute_stmt_and_params (source);
@@ -631,9 +737,15 @@ data_source_to_xml_node (DataSource *source)
 				depnode = xmlNewChild (node, NULL, BAD_CAST "depend", NULL);
 				xmlSetProp (depnode, BAD_CAST "foreign_key_table",
 					    BAD_CAST (dep->dep_table));
-				if (strcmp (dep->dep_id, dep->dep_table))
-					xmlSetProp (depnode, BAD_CAST "id",
-						    BAD_CAST (dep->dep_id));
+				xmlSetProp (depnode, BAD_CAST "id",
+					    BAD_CAST (dep->dep_id));
+
+				gchar **array;
+				gint i;
+				array = g_strsplit (dep->dep_columns, DEPENDENCY_SEPARATOR, 0);
+				for (i = 0; array[i]; i++)
+					xmlNewChild (depnode, NULL, BAD_CAST "column", BAD_CAST (array[i]));
+				g_strfreev (array);
 			}
 		}
 		break;
diff --git a/tools/browser/data-manager/data-source.h b/tools/browser/data-manager/data-source.h
index 5968931..e596b99 100644
--- a/tools/browser/data-manager/data-source.h
+++ b/tools/browser/data-manager/data-source.h
@@ -71,8 +71,10 @@ const gchar        *data_source_get_title           (DataSource *source);
 /* Data source as table API */
 gboolean            data_source_set_table           (DataSource *source, const gchar *table, GError **error);
 const gchar        *data_source_get_table           (DataSource *source);
-gboolean            data_source_add_dependendency   (DataSource *source, const gchar *table,
-						     const char *id, GError **error);
+gboolean            data_source_add_dependency      (DataSource *source, const gchar *table,
+						     const char *id,
+						     gint col_name_size, const gchar **col_names,
+						     GError **error);
 
 /* Data source as SQL query API */
 void                data_source_set_query           (DataSource *source, const gchar *sql, GError **warning);
diff --git a/tools/browser/data-manager/data-widget.c b/tools/browser/data-manager/data-widget.c
index e1f237c..6185c02 100644
--- a/tools/browser/data-manager/data-widget.c
+++ b/tools/browser/data-manager/data-widget.c
@@ -547,6 +547,11 @@ compute_fk_dependency (GdaMetaTableForeignKey *fkey, GSList *selfields, gboolean
 			    BAD_CAST GDA_META_DB_OBJECT (table)->obj_short_name);
 
 		xmlSetProp (snode, BAD_CAST "id", BAD_CAST data_source_get_id (part->source));
+
+		gint i;
+		for (i = 0; i < fkey->cols_nb; i++)
+			xmlNewChild (snode, NULL, BAD_CAST "column", BAD_CAST (fkey->fk_names_array[i]));
+
 		*out_sourcespec = node;
 		return g_string_free (string, FALSE);
 	}
diff --git a/tools/browser/help/C/data-manager-perspective.page b/tools/browser/help/C/data-manager-perspective.page
index c489f0b..e1e86cd 100644
--- a/tools/browser/help/C/data-manager-perspective.page
+++ b/tools/browser/help/C/data-manager-perspective.page
@@ -64,10 +64,14 @@
     The commands available through the command buttons are:
   </p>
   <list>
-    <item><p><gui>Clear</gui>: clears the XML editor</p></item>
+    <item><p><gui>Reset</gui>: resets the XML editor to a default XML template, only available when the XML
+    view is currently displayed</p></item>
+    <item><p><gui>Add data source</gui>: displays a popup menu with an entry per table to quickly add
+    the whole contents of a table as a data source, only available when the UI
+    editor is currently displayed</p></item>
     <item><p><gui>Variables</gui>: shows/hide the <link xref="variables-syntax">variables</link> panel where you can give values to the variables
-	present in the SQL code of any data source's definition. The panel is automatically shown when a variable is detected in 
-	the SQL code.</p></item>
+    present in the SQL code of any data source's definition. The panel is automatically shown when a variable is detected in 
+    the SQL code.</p></item>
     <item><p><gui>Execute</gui>: executes the defined data sources</p></item>
     <item><p><gui>View XML</gui>: toggles between the XML editor and the UI editor</p></item>
     <item><p><gui>Help</gui>: shows some help</p></item>
diff --git a/tools/browser/help/C/data-manager-xml-syntax.page b/tools/browser/help/C/data-manager-xml-syntax.page
index ea160c7..931c815 100644
--- a/tools/browser/help/C/data-manager-xml-syntax.page
+++ b/tools/browser/help/C/data-manager-xml-syntax.page
@@ -92,18 +92,38 @@
   </p>
   <list>
     <item><p>requires the "name" attribute which represents the table name.</p></item>
-    <item><p>can contain a <code>&lt;depend&gt;</code> node which defines a dependency on another
+    <item><p>can have a "id" attribute corresponding to the data source's ID. If not present,
+    and ID will be assigned automatically.</p></item>
+    <item><p>can contain a <code>&lt;depend&gt;</code> tag which defines a dependency on another
 	data source with
 	the "foreign_key_table" attribute defining the name of the table to which there are foreign keys
 	used to determine the dependency, and the "id" attribute can specify a data source ID if different than
 	the aforementioned table</p></item>
   </list>
+  <p>
+    The <code>&lt;depend&gt;</code> tag, which, for a data source from a table, defines a dependency to
+    another data source from a table:
+  </p>
+    <list>
+    <item><p>requires the "foreign_key_table" attribute defining the name of the table to which there are
+    foreign keys used to determine the dependency</p></item>
+    <item><p>can have a "id" attribute corresponding to the ID of the referenced data source. If not
+    provided, then the dependency may fail if there is no data source which ID is the"foreign_key_table"
+    attribute.</p></item>
+    <item><p>can contain one or more <code>&lt;column&gt;</code> tag which contents define the columns
+    to identify the foreign key to use; this is necessary if there are multiple foreign keys,
+    and can be omitted if there is only one possible koreign key. The listed columns are the one
+    from the table where the foreign key exists.</p></item>
+  </list>
+
 
   <code><![CDATA[
 <data>
-  <table name="customers"/>
+  <table id="the_cust" name="customers"/>
   <table name="orders">
-    <depend foreign_key_table="customers"/>
+    <depend id="the_cust" foreign_key_table="customers">
+      <column>customer_id</column>
+    </depend>
   </table>
 </data>]]>
   </code>
@@ -114,8 +134,10 @@
     <item><p>the <code>customers</code> data source which selects all the contents of the
 	<em>customers</em> table.</p></item>
     <item><p>the <code>orders</code> data source which selects among contents of the <code>orders</code>
-	table, the rows which correspond to a row in the <code>customers</code> table (assuming here
-	that the <code>orders</code> references the <code>customers</code> table in some way).
+	table, the rows which correspond to a row in the <code>customers</code> table using the foreign key
+	on table orders which involves the "orders.customer_id" column and the primary key of the
+	customers table. The "id" attribute of the <code>&lt;depend&gt;</code> tag is necessary here to
+	identify referenced the data source.
     </p></item>
   </list>
   <p>



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