1 /*
2  * Copyright (C) 2008 - 2011 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 
21 #include <libgda/libgda.h>
22 #include <sql-parser/gda-sql-parser.h>
23 #include "html.h"
24 #include <libgda/gda-enum-types.h>
25 #include <libgda/sqlite/virtual/gda-virtual-provider.h>
26 
27 /* options */
28 gboolean ask_pass = FALSE;
29 gchar *outfile = NULL;
30 
31 static GOptionEntry entries[] = {
32         { "no-password-ask", 'p', 0, G_OPTION_ARG_NONE, &ask_pass, "Don't ast for a password when it is empty", NULL },
33         { "output-file", 'o', 0, G_OPTION_ARG_STRING, &outfile, "Output file", "output file"},
34         { NULL, 0, 0, 0, NULL, NULL, NULL }
35 };
36 
37 HtmlConfig *config;
38 
39 static GdaConnection *open_connection (const gchar *cnc_string, GError **error);
40 static gboolean report_provider_status (GdaServerProvider *prov, GdaConnection *cnc);
41 
42 int
main(int argc,char * argv[])43 main (int argc, char *argv[])
44 {
45 	GOptionContext *context;
46 	GError *error = NULL;
47 	int exit_status = EXIT_SUCCESS;
48 	GSList *list, *cnc_list = NULL;
49 
50 	context = g_option_context_new ("[DSN|connection string]...");
51 	g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
52         if (!g_option_context_parse (context, &argc, &argv, &error)) {
53                 g_print ("Can't parse arguments: %s\n", error->message);
54 		exit_status = EXIT_FAILURE;
55 		goto cleanup;
56         }
57         g_option_context_free (context);
58         gda_init ();
59 	ask_pass = !ask_pass;
60 
61 	config = g_new0 (HtmlConfig, 1);
62 	html_init_config (HTML_CONFIG (config));
63 	config->index = html_file_new (HTML_CONFIG (config),
64 				       "index.html", "Providers status");
65 	config->dir = g_strdup (".");
66 
67 	/* parse command line arguments for connections */
68 	if (argc > 1) {
69 		gint i;
70 		for (i = 1; i < argc; i++) {
71 			/* open connection */
72 			GdaConnection *cnc;
73 			cnc = open_connection (argv[i], &error);
74 			if (!cnc) {
75 				g_print ("Can't open connection to '%s': %s\n", argv[i],
76 					 error && error->message ? error->message : "No detail");
77 				exit_status = EXIT_FAILURE;
78 				goto cleanup;
79 			}
80 			cnc_list = g_slist_append (cnc_list, cnc);
81 		}
82 	}
83 	else {
84 		if (getenv ("GDA_SQL_CNC")) {
85 			GdaConnection *cnc;
86 			cnc = open_connection (getenv ("GDA_SQL_CNC"), &error);
87 			if (!cnc) {
88 				g_print ("Can't open connection defined by GDA_SQL_CNC: %s\n",
89 					 error && error->message ? error->message : "No detail");
90 				exit_status = EXIT_FAILURE;
91 				goto cleanup;
92 			}
93 			cnc_list = g_slist_append (cnc_list, cnc);
94 		}
95 		else {
96 			/* report status for all providers */
97 			GdaDataModel *providers;
98 			gint i, nb;
99 
100 			providers = gda_config_list_providers ();
101 			nb = gda_data_model_get_n_rows (providers);
102 			for (i = 0; i < nb; i++) {
103 				GdaServerProvider *prov = NULL;
104 				const gchar *pname;
105 				const GValue *cvalue;
106 
107 				cvalue = gda_data_model_get_value_at (providers, 0, i, &error);
108 				if (!cvalue)
109 					g_error ("Can't load next provider: %s\n",
110 						 error && error->message ? error->message : "No detail");
111 				pname = g_value_get_string (cvalue);
112 				prov = gda_config_get_provider (pname, &error);
113 				if (!prov)
114 					g_error ("Can't load the '%s' provider: %s\n", pname,
115 						 error && error->message ? error->message : "No detail");
116 				if (!report_provider_status (prov, NULL)) {
117 					exit_status = EXIT_FAILURE;
118 					goto cleanup;
119 				}
120 			}
121 			g_object_unref (providers);
122 		}
123 	}
124 
125 	/* report provider's status for all the connections */
126 	for (list = cnc_list; list; list = list->next) {
127 		if (!report_provider_status (NULL, GDA_CONNECTION (list->data))) {
128 			exit_status = EXIT_FAILURE;
129 			goto cleanup;
130 
131 		}
132 	}
133 
134 	g_slist_foreach (HTML_CONFIG (config)->all_files, (GFunc) html_file_write, config);
135 
136 	/* cleanups */
137  cleanup:
138 	g_slist_foreach (cnc_list, (GFunc) g_object_unref, NULL);
139 	g_slist_free (cnc_list);
140 
141 	return exit_status;
142 }
143 
144 
145 /*
146  * Open a connection
147  */
148 static GdaConnection*
open_connection(const gchar * cnc_string,GError ** error)149 open_connection (const gchar *cnc_string, GError **error)
150 {
151 	GdaConnection *cnc = NULL;
152 
153 	GdaDsnInfo *info;
154 	gchar *user, *pass, *real_cnc, *real_provider, *real_auth_string = NULL;
155 	gda_connection_string_split (cnc_string, &real_cnc, &real_provider, &user, &pass);
156 	if (!real_cnc) {
157 		g_free (user);
158 		g_free (pass);
159 		g_free (real_provider);
160 		g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_DSN_NOT_FOUND_ERROR,
161 			     "Malformed connection string '%s'", cnc_string);
162 		return NULL;
163 	}
164 
165 	if (ask_pass) {
166 		if (user && !*user) {
167 			gchar buf[80];
168 			g_print ("\tUsername for '%s': ", cnc_string);
169 			if (scanf ("%80s", buf) == -1) {
170 				g_free (real_cnc);
171 				g_free (user);
172 				g_free (pass);
173 				g_free (real_provider);
174 				g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_DSN_NOT_FOUND_ERROR,
175 					     "No username for '%s'", cnc_string);
176 				return NULL;
177 			}
178 			g_free (user);
179 			user = g_strdup (buf);
180 		}
181 		if (pass && !*pass) {
182 			gchar buf[80];
183 			g_print ("\tPassword for '%s': ", cnc_string);
184 			if (scanf ("%80s", buf) == -1) {
185 				g_free (real_cnc);
186 				g_free (user);
187 				g_free (pass);
188 				g_free (real_provider);
189 				g_set_error (error, GDA_CONNECTION_ERROR, GDA_CONNECTION_DSN_NOT_FOUND_ERROR,
190 					     "No password for '%s'", cnc_string);
191 				return NULL;
192 			}
193 			g_free (pass);
194 			pass = g_strdup (buf);
195 		}
196 		if (user || pass) {
197 			gchar *s1;
198 			s1 = gda_rfc1738_encode (user);
199 			if (pass) {
200 				gchar *s2;
201 				s2 = gda_rfc1738_encode (pass);
202 				real_auth_string = g_strdup_printf ("USERNAME=%s;PASSWORD=%s", s1, s2);
203 				g_free (s2);
204 			}
205 			else
206 				real_auth_string = g_strdup_printf ("USERNAME=%s", s1);
207 			g_free (s1);
208 		}
209 	}
210 
211 	info = gda_config_get_dsn_info (real_cnc);
212 	if (info && !real_provider)
213 		cnc = gda_connection_open_from_dsn (cnc_string, real_auth_string, 0, error);
214 	else
215 		cnc = gda_connection_open_from_string (NULL, cnc_string, real_auth_string, 0, error);
216 
217 	g_free (real_cnc);
218 	g_free (user);
219 	g_free (pass);
220 	g_free (real_provider);
221 	g_free (real_auth_string);
222 
223 	return cnc;
224 }
225 
226 static gboolean
report_provider_status(GdaServerProvider * prov,GdaConnection * cnc)227 report_provider_status (GdaServerProvider *prov, GdaConnection *cnc)
228 {
229 	gchar *header_str;
230 	HtmlFile *file = config->index;
231 	gboolean is_virt;
232 
233 	typedef void (*AFunc) (void);
234 	typedef struct {
235 		const gchar *name;
236 		gboolean     should_be;
237 		void       (*func) (void);
238 	} ProvFunc;
239 	GdaServerProviderClass *pclass;
240 
241 	if (prov && cnc && (prov != gda_connection_get_provider (cnc)))
242 		/* ignoring connection as it has a different provider */
243 		return TRUE;
244 	g_assert (prov || cnc);
245 
246 	/* section */
247 	if (cnc)
248 		header_str = g_strdup_printf ("Report for connection '%s'", gda_connection_get_cnc_string (cnc));
249 	else
250 		header_str = g_strdup_printf ("Report for '%s' provider", gda_server_provider_get_name (prov));
251 
252 	/* provider info */
253 	if (!prov)
254 		prov = gda_connection_get_provider (cnc);
255 	is_virt = GDA_IS_VIRTUAL_PROVIDER (prov);
256 	pclass = (GdaServerProviderClass*) G_OBJECT_GET_CLASS (prov);
257 	ProvFunc fa[] = {
258 		{"get_name", TRUE, (AFunc) pclass->get_name},
259 		{"get_version", TRUE, (AFunc) pclass->get_version},
260 		{"get_server_version", TRUE, (AFunc) pclass->get_server_version},
261 		{"supports_feature", TRUE, (AFunc) pclass->supports_feature},
262 		{"get_data_handler", TRUE, (AFunc) pclass->get_data_handler},
263 		{"get_def_dbms_type", TRUE, (AFunc) pclass->get_def_dbms_type},
264 		{"escape_string", TRUE, (AFunc) pclass->escape_string},
265 		{"unescape_string", TRUE, (AFunc) pclass->unescape_string},
266 		{"open_connection", TRUE, (AFunc) pclass->open_connection},
267 		{"close_connection", TRUE, (AFunc) pclass->close_connection},
268 		{"get_database", TRUE, (AFunc) pclass->get_database},
269 		{"supports_operation", is_virt ? FALSE : TRUE, (AFunc) pclass->supports_operation},
270 		{"create_operation", FALSE, (AFunc) pclass->create_operation},
271 		{"render_operation", FALSE, (AFunc) pclass->render_operation},
272 		{"perform_operation", FALSE, (AFunc) pclass->perform_operation},
273 		{"begin_transaction", FALSE, (AFunc) pclass->begin_transaction},
274 		{"commit_transaction", FALSE, (AFunc) pclass->commit_transaction},
275 		{"rollback_transaction", FALSE, (AFunc) pclass->rollback_transaction},
276 		{"add_savepoint", FALSE, (AFunc) pclass->add_savepoint},
277 		{"rollback_savepoint", FALSE, (AFunc) pclass->rollback_savepoint},
278 		{"delete_savepoint", FALSE, (AFunc) pclass->delete_savepoint},
279 		{"create_parser", FALSE, (AFunc) pclass->create_parser},
280 		{"statement_to_sql", TRUE, (AFunc) pclass->statement_to_sql},
281 		{"statement_prepare", TRUE, (AFunc) pclass->statement_prepare},
282 		{"statement_execute", TRUE, (AFunc) pclass->statement_execute},
283 		{"identifier_quote", TRUE, (AFunc) pclass->identifier_quote}
284 	};
285 
286 	ProvFunc md[] = {
287 		{"_info", TRUE, (AFunc) pclass->meta_funcs._info},
288 		{"_btypes", TRUE, (AFunc) pclass->meta_funcs._btypes},
289 		{"_udt", TRUE, (AFunc) pclass->meta_funcs._udt},
290 		{"udt", TRUE, (AFunc) pclass->meta_funcs.udt},
291 		{"_udt_cols", TRUE, (AFunc) pclass->meta_funcs._udt_cols},
292 		{"udt_cols", TRUE, (AFunc) pclass->meta_funcs.udt_cols},
293 		{"_enums", TRUE, (AFunc) pclass->meta_funcs._enums},
294 		{"enums", TRUE, (AFunc) pclass->meta_funcs.enums},
295 		{"_domains", TRUE, (AFunc) pclass->meta_funcs._domains},
296 		{"domains", TRUE, (AFunc) pclass->meta_funcs.domains},
297 		{"_constraints_dom", TRUE, (AFunc) pclass->meta_funcs._constraints_dom},
298 		{"constraints_dom", TRUE, (AFunc) pclass->meta_funcs.constraints_dom},
299 		{"_el_types", TRUE, (AFunc) pclass->meta_funcs._el_types},
300 		{"el_types", TRUE, (AFunc) pclass->meta_funcs.el_types},
301 		{"_collations", TRUE, (AFunc) pclass->meta_funcs._collations},
302 		{"collations", TRUE, (AFunc) pclass->meta_funcs.collations},
303 		{"_character_sets", TRUE, (AFunc) pclass->meta_funcs._character_sets},
304 		{"character_sets", TRUE, (AFunc) pclass->meta_funcs.character_sets},
305 		{"_schemata", TRUE, (AFunc) pclass->meta_funcs._schemata},
306 		{"schemata", TRUE, (AFunc) pclass->meta_funcs.schemata},
307 		{"_tables_views", TRUE, (AFunc) pclass->meta_funcs._tables_views},
308 		{"tables_views", TRUE, (AFunc) pclass->meta_funcs.tables_views},
309 		{"_columns", TRUE, (AFunc) pclass->meta_funcs._columns},
310 		{"columns", TRUE, (AFunc) pclass->meta_funcs.columns},
311 		{"_view_cols", TRUE, (AFunc) pclass->meta_funcs._view_cols},
312 		{"view_cols", TRUE, (AFunc) pclass->meta_funcs.view_cols},
313 		{"_constraints_tab", TRUE, (AFunc) pclass->meta_funcs._constraints_tab},
314 		{"constraints_tab", TRUE, (AFunc) pclass->meta_funcs.constraints_tab},
315 		{"_constraints_ref", TRUE, (AFunc) pclass->meta_funcs._constraints_ref},
316 		{"constraints_ref", TRUE, (AFunc) pclass->meta_funcs.constraints_ref},
317 		{"_key_columns", TRUE, (AFunc) pclass->meta_funcs._key_columns},
318 		{"key_columns", TRUE, (AFunc) pclass->meta_funcs.key_columns},
319 		{"_check_columns", TRUE, (AFunc) pclass->meta_funcs._check_columns},
320 		{"check_columns", TRUE, (AFunc) pclass->meta_funcs.check_columns},
321 		{"_triggers", TRUE, (AFunc) pclass->meta_funcs._triggers},
322 		{"triggers", TRUE, (AFunc) pclass->meta_funcs.triggers},
323 		{"_routines", TRUE, (AFunc) pclass->meta_funcs._routines},
324 		{"routines", TRUE, (AFunc) pclass->meta_funcs.routines},
325 		{"_routine_col", TRUE, (AFunc) pclass->meta_funcs._routine_col},
326 		{"routine_col", TRUE, (AFunc) pclass->meta_funcs.routine_col},
327 		{"_routine_par", TRUE, (AFunc) pclass->meta_funcs._routine_par},
328 		{"routine_par", TRUE, (AFunc) pclass->meta_funcs.routine_par},
329 	};
330 	gboolean has_xa = gda_server_provider_supports_feature (prov, cnc,
331 								GDA_CONNECTION_FEATURE_XA_TRANSACTIONS);
332 
333 
334 	xmlNodePtr table, tr, td, span;
335 	GdaSqlParser *parser;
336 	GString *string;
337 	gsize i;
338 	GdaProviderInfo *pinfo;
339 
340 	pinfo = gda_config_get_provider_info (gda_server_provider_get_name (prov));
341 	g_assert (pinfo);
342 
343 	table = xmlNewChild (file->body, NULL, BAD_CAST "table", NULL);
344 	xmlSetProp (table, BAD_CAST "width", BAD_CAST "100%");
345 	tr = xmlNewChild (table, NULL, BAD_CAST "tr", NULL);
346 	td = xmlNewTextChild (tr, NULL, BAD_CAST "th", BAD_CAST header_str);
347 	xmlSetProp (td, BAD_CAST "colspan", BAD_CAST  "4");
348 
349 	/* line 1 */
350 	tr = xmlNewChild (table, NULL, BAD_CAST "tr", NULL);
351 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Provider's name:");
352 	td = xmlNewTextChild (tr, NULL, BAD_CAST "td", BAD_CAST gda_server_provider_get_name (prov));
353 	xmlSetProp (td, BAD_CAST "width", (xmlChar*) "35%");
354 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Provider is virtual:");
355 	td = xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST (is_virt ? "Yes (uses the SQLite engine)" : "No"));
356 	xmlSetProp (td, BAD_CAST "width", (xmlChar*) "35%");
357 
358 	/* line 2 */
359 	tr = xmlNewChild (table, NULL, BAD_CAST "tr", NULL);
360 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Provider's version:");
361 	xmlNewTextChild (tr, NULL, BAD_CAST "td", BAD_CAST gda_server_provider_get_version (prov));
362 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Provider's server version:");
363 	xmlNewTextChild (tr, NULL, BAD_CAST "td",
364 			 BAD_CAST (cnc ? gda_server_provider_get_server_version (prov, cnc) : "(non connected)"));
365 
366 	/* line 3 */
367 	tr = xmlNewChild (table, NULL, BAD_CAST "tr", NULL);
368 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Provider's description:");
369 	xmlNewTextChild (tr, NULL, BAD_CAST "td", BAD_CAST pinfo->description);
370 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Filename:");
371 	xmlNewTextChild (tr, NULL, BAD_CAST "td", BAD_CAST pinfo->location);
372 
373 	/* line 4 */
374 	parser = gda_server_provider_create_parser (prov, cnc);
375 	tr = xmlNewChild (table, NULL, BAD_CAST "tr", NULL);
376 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Creates its own SQL parser:");
377 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST (parser ? "Yes" : "No"));
378 	if (parser)
379 		g_object_unref (parser);
380 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Non implemented base methods:");
381 	span = NULL;
382 	td = xmlNewChild (tr, NULL, BAD_CAST "td", NULL);
383 	for (i = 0; i < sizeof (fa) / sizeof (ProvFunc); i++) {
384 		gchar *str;
385 		ProvFunc *pf = &(fa[i]);
386 
387 		if (pf->func)
388 			continue;
389 
390 		if (span)
391 			str = g_strdup_printf (", %s()", pf->name);
392 		else
393 			str = g_strdup_printf ("%s()", pf->name);
394 		span = xmlNewTextChild (td, NULL, BAD_CAST "span", BAD_CAST str);
395 		g_free (str);
396 		if (pf->should_be)
397 			xmlSetProp (span, BAD_CAST "class", BAD_CAST "error");
398 	}
399 	if (!span)
400 		xmlNodeSetContent (td, BAD_CAST "---");
401 
402 	/* line 5 */
403 	tr = xmlNewChild (table, NULL, BAD_CAST "tr", NULL);
404 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Non implemented meta data methods:");
405 	span = NULL;
406 	td = xmlNewChild (tr, NULL, BAD_CAST "td", NULL);
407 	for (i = 0; i < sizeof (md) / sizeof (ProvFunc); i++) {
408 		gchar *str;
409 		ProvFunc *pf = &(md[i]);
410 
411 		if (pf->func)
412 			continue;
413 
414 		if (span)
415 			str = g_strdup_printf (", %s()", pf->name);
416 		else
417 			str = g_strdup_printf ("%s()", pf->name);
418 		span = xmlNewTextChild (td, NULL, BAD_CAST "span", BAD_CAST str);
419 		g_free (str);
420 		if (pf->should_be)
421 			xmlSetProp (span, BAD_CAST "class", BAD_CAST "error");
422 	}
423 	if (!span)
424 		xmlNodeSetContent (td, BAD_CAST "---");
425 
426 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Non implemented XA transactions:");
427 	if (pclass->xa_funcs) {
428 		if (!has_xa) {
429 			td = xmlNewChild (tr, NULL, BAD_CAST "td",
430 					  BAD_CAST "The provider has the 'xa_funcs' part but "
431 					  "reports that distributed transactions are "
432 					  "not supported.");
433 			xmlSetProp (td, BAD_CAST "class", BAD_CAST "warning");
434 		}
435 		else {
436 			ProvFunc dt[] = {
437 				{"xa_start", TRUE, (AFunc) pclass->xa_funcs->xa_start},
438 				{"xa_end", FALSE, (AFunc) pclass->xa_funcs->xa_end},
439 				{"xa_prepare", TRUE, (AFunc) pclass->xa_funcs->xa_prepare},
440 				{"xa_commit", TRUE, (AFunc) pclass->xa_funcs->xa_commit},
441 				{"xa_rollback", TRUE, (AFunc) pclass->xa_funcs->xa_rollback},
442 				{"xa_recover", TRUE, (AFunc) pclass->xa_funcs->xa_recover},
443 			};
444 			span = NULL;
445 			td = xmlNewChild (tr, NULL, BAD_CAST "td", NULL);
446 			for (i = 0; i < sizeof (dt) / sizeof (ProvFunc); i++) {
447 				gchar *str;
448 				ProvFunc *pf = &(dt[i]);
449 
450 				if (pf->func)
451 					continue;
452 
453 				if (span)
454 					str = g_strdup_printf (", %s()", pf->name);
455 				else
456 					str = g_strdup_printf ("%s()", pf->name);
457 				span = xmlNewTextChild (td, NULL, BAD_CAST "span", BAD_CAST str);
458 				g_free (str);
459 				if (pf->should_be)
460 					xmlSetProp (span, BAD_CAST "class", BAD_CAST "error");
461 			}
462 			if (!span)
463 				xmlNodeSetContent (td, BAD_CAST "---");
464 		}
465 	}
466 	else {
467 		if (has_xa) {
468 			td = xmlNewTextChild (tr, NULL, BAD_CAST "td",
469 					   BAD_CAST "The provider does not have the 'xa_funcs' part but "
470 					  "reports that distributed transactions are "
471 					  "supported.");
472 			xmlSetProp (td, BAD_CAST "class", BAD_CAST "warning");
473 		}
474 		else
475 			xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "---");
476 	}
477 
478 	/* line 6 */
479 	tr = xmlNewChild (table, NULL, BAD_CAST "tr", NULL);
480 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Connection's parameters:");
481 	if (pinfo->dsn_params && pinfo->dsn_params->holders) {
482 		GSList *list;
483 		td = xmlNewChild (tr, NULL, BAD_CAST "td", NULL);
484 		for (list = pinfo->dsn_params->holders; list; list = list->next) {
485 			gchar *str, *descr;
486 			GdaHolder *holder = GDA_HOLDER (list->data);
487 			g_object_get (G_OBJECT (holder), "description", &descr, NULL);
488 			if (descr)
489 				str = g_strdup_printf ("%s: %s", gda_holder_get_id (holder), descr);
490 			else
491 				str = g_strdup (gda_holder_get_id (holder));
492 			g_free (descr);
493 			xmlNewTextChild (td, NULL, BAD_CAST "div", BAD_CAST str);
494 			g_free (str);
495 		}
496 	}
497 	else {
498 		td = xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "None provided");
499 		xmlSetProp (td, BAD_CAST "class", BAD_CAST "error");
500 	}
501 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Authentication's parameters:");
502 	if (pinfo->auth_params) {
503 		GSList *list;
504 		if (pinfo->auth_params->holders) {
505 			td = xmlNewChild (tr, NULL, BAD_CAST "td", NULL);
506 			for (list = pinfo->auth_params->holders; list; list = list->next) {
507 				gchar *str, *descr;
508 				GdaHolder *holder = GDA_HOLDER (list->data);
509 				g_object_get (G_OBJECT (holder), "description", &descr, NULL);
510 				if (descr)
511 					str = g_strdup_printf ("%s: %s", gda_holder_get_id (holder), descr);
512 				else
513 					str = g_strdup (gda_holder_get_id (holder));
514 				g_free (descr);
515 				xmlNewTextChild (td, NULL, BAD_CAST "div", BAD_CAST str);
516 				g_free (str);
517 			}
518 		}
519 		else
520 			td = xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "None required");
521 	}
522 	else {
523 		td = xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "None provided");
524 		xmlSetProp (td, BAD_CAST "class", BAD_CAST "error");
525 	}
526 
527 	/* line 7 */
528 	GdaConnectionFeature f;
529 	string = NULL;
530 	tr = xmlNewChild (table, NULL, BAD_CAST "tr", NULL);
531 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Supported features:");
532 	for (f = 0; f < GDA_CONNECTION_FEATURE_LAST; f++) {
533 		if (gda_server_provider_supports_feature (prov, cnc, f)) {
534 			GEnumValue *ev;
535 
536 			ev = g_enum_get_value ((GEnumClass *) g_type_class_ref (GDA_TYPE_CONNECTION_FEATURE), f);
537 			if (!string)
538 				string = g_string_new (ev->value_name);
539 			else
540 				g_string_append_printf (string, ", %s", ev->value_name);
541 		}
542 	}
543 	if (string) {
544 		xmlNewTextChild (tr, NULL, BAD_CAST "td", BAD_CAST string->str);
545 		g_string_free (string, TRUE);
546 	}
547 	else
548 		xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "---");
549 
550 	string = NULL;
551 	xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "Unsupported features:");
552 	for (f = 0; f < GDA_CONNECTION_FEATURE_LAST; f++) {
553 		if (!gda_server_provider_supports_feature (prov, cnc, f)) {
554 			GEnumValue *ev;
555 
556 			ev = g_enum_get_value ((GEnumClass *) g_type_class_ref (GDA_TYPE_CONNECTION_FEATURE), f);
557 			if (!string)
558 				string = g_string_new (ev->value_name);
559 			else
560 				g_string_append_printf (string, ", %s", ev->value_name);
561 		}
562 	}
563 	if (string) {
564 		xmlNewTextChild (tr, NULL, BAD_CAST "td", BAD_CAST string->str);
565 		g_string_free (string, TRUE);
566 	}
567 	else
568 		xmlNewChild (tr, NULL, BAD_CAST "td", BAD_CAST "---");
569 
570 	g_free (header_str);
571 
572 	return TRUE;
573 }
574