On 02/02/2010 06:40 PM, Tom Lane wrote:
> The difference with PQconnectdbParams is that the dbname might not be
> the first thing in the parameter array.  I think that a straightforward
> implementation would have the effect of the expanded dbname overriding
> parameters given before it, but not those given after it.  Consider
> 
>       keyword[0] = "port";
>       values[0] = "5678";
>       keyword[1] = "dbname";
>       values[1] = "dbname = db user = foo port = 9999";
>       keyword[2] = "user";
>       values[2] = "uu";
> 
> What I'm imagining is that this would end up equivalent to
> dbname = db user = uu port = 9999.  That's probably reasonable,
> and maybe even useful, as long as it's documented.

No doc changes yet, and I still have not corrected the earlier mentioned
issue,

> While I'm looking at this, there's another bogosity in the original
> patch: it neglected the PQsetdbLogin call in psql/command.c, meaning
> that doing \c would result in losing the application_name setting.

but I wanted to get feedback before going further.

This patch implements Tom's idea above. Note that I also rearranged the
parameters for the call from psql so that dbname is last, therefore
allowing a conninfo to override all other settings. The result looks like:

        keywords[0]     = "host";
        values[0]       = options.host;
        keywords[1]     = "port";
        values[1]       = options.port;
        keywords[2]     = "user";
        values[2]       = options.username;
        keywords[3]     = "password";
        values[3]       = password;
        keywords[4]     = "application_name";
        values[4]       = pset.progname;
        keywords[5]     = "dbname";
        values[5]       = (options.action == ACT_LIST_DB &&
                        options.dbname == NULL) ?
                        "postgres" : options.dbname;
        keywords[6]     = NULL;
        values[6]       = NULL;

Which produces:

# psql -U postgres "dbname=regression user=fred application_name='joe\'s
app'"
psql (8.5devel)
Type "help" for help.

regression=> select backend_start, application_name from pg_stat_activity ;
         backend_start         | application_name
-------------------------------+------------------
 2010-02-02 21:44:55.969202-08 | joe's app
(1 row)

regression=> select current_user;
 current_user
--------------
 fred
(1 row)

Are there any of the psql parameters that we do not want to allow to be
overridden by the conninfo string?


Joe
Index: src/bin/psql/startup.c
===================================================================
RCS file: /opt/src/cvs/pgsql/src/bin/psql/startup.c,v
retrieving revision 1.159
diff -c -r1.159 startup.c
*** src/bin/psql/startup.c	28 Jan 2010 06:28:26 -0000	1.159
--- src/bin/psql/startup.c	3 Feb 2010 05:41:54 -0000
***************
*** 90,97 ****
  	char	   *password = NULL;
  	char	   *password_prompt = NULL;
  	bool		new_pass;
- 	const char *keywords[] = {"host","port","dbname","user",
- 							  "password","application_name",NULL};
  
  	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("psql"));
  
--- 90,95 ----
***************
*** 173,192 ****
  	/* loop until we have a password if requested by backend */
  	do
  	{
!         const char *values[] = {
!                   options.host,
!                   options.port,
!                   (options.action == ACT_LIST_DB && 
!                                options.dbname == NULL) ? "postgres" : options.dbname,
!                   options.username,
!                   password,
!                   pset.progname,
!                   NULL
!               };
!         
!         new_pass = false;
! 
!         pset.db = PQconnectdbParams(keywords, values);
  
  		if (PQstatus(pset.db) == CONNECTION_BAD &&
  			PQconnectionNeedsPassword(pset.db) &&
--- 171,201 ----
  	/* loop until we have a password if requested by backend */
  	do
  	{
! #define PARAMS_ARRAY_SIZE	7
! 		const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords));
! 		const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values));
! 
! 		keywords[0]	= "host";
! 		values[0]	= options.host;
! 		keywords[1]	= "port";
! 		values[1]	= options.port;
! 		keywords[2]	= "user";
! 		values[2]	= options.username;
! 		keywords[3]	= "password";
! 		values[3]	= password;
! 		keywords[4]	= "application_name";
! 		values[4]	= pset.progname;
! 		keywords[5]	= "dbname";
! 		values[5]	= (options.action == ACT_LIST_DB &&
! 						options.dbname == NULL) ?
! 						"postgres" : options.dbname;
! 		keywords[6]	= NULL;
! 		values[6]	= NULL;
! 
! 		new_pass = false;
! 		pset.db = PQconnectdbParams(keywords, values, true /* expand conninfo string in dbname if found */);
! 		free(keywords);
! 		free(values);
  
  		if (PQstatus(pset.db) == CONNECTION_BAD &&
  			PQconnectionNeedsPassword(pset.db) &&
Index: src/interfaces/libpq/fe-connect.c
===================================================================
RCS file: /opt/src/cvs/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.385
diff -c -r1.385 fe-connect.c
*** src/interfaces/libpq/fe-connect.c	28 Jan 2010 06:28:26 -0000	1.385
--- src/interfaces/libpq/fe-connect.c	3 Feb 2010 05:29:54 -0000
***************
*** 269,275 ****
  			   PQExpBuffer errorMessage, bool use_defaults);
  static PQconninfoOption *conninfo_array_parse(const char **keywords,
  				const char **values, PQExpBuffer errorMessage,
! 				bool use_defaults);
  static char *conninfo_getval(PQconninfoOption *connOptions,
  				const char *keyword);
  static void defaultNoticeReceiver(void *arg, const PGresult *res);
--- 269,275 ----
  			   PQExpBuffer errorMessage, bool use_defaults);
  static PQconninfoOption *conninfo_array_parse(const char **keywords,
  				const char **values, PQExpBuffer errorMessage,
! 				bool use_defaults, bool expand_dbname);
  static char *conninfo_getval(PQconninfoOption *connOptions,
  				const char *keyword);
  static void defaultNoticeReceiver(void *arg, const PGresult *res);
***************
*** 336,344 ****
   * call succeeded.
   */
  PGconn *
! PQconnectdbParams(const char **keywords, const char **values)
  {
! 	PGconn	   *conn = PQconnectStartParams(keywords, values);
  
  	if (conn && conn->status != CONNECTION_BAD)
  		(void) connectDBComplete(conn);
--- 336,346 ----
   * call succeeded.
   */
  PGconn *
! PQconnectdbParams(const char **keywords,
! 				  const char **values,
! 				  bool expand_dbname)
  {
! 	PGconn	   *conn = PQconnectStartParams(keywords, values, expand_dbname);
  
  	if (conn && conn->status != CONNECTION_BAD)
  		(void) connectDBComplete(conn);
***************
*** 400,406 ****
   * See PQconnectPoll for more info.
   */
  PGconn *
! PQconnectStartParams(const char **keywords, const char **values)
  {
  	PGconn			   *conn;
  	PQconninfoOption   *connOptions;
--- 402,410 ----
   * See PQconnectPoll for more info.
   */
  PGconn *
! PQconnectStartParams(const char **keywords,
! 					 const char **values,
! 					 bool expand_dbname)
  {
  	PGconn			   *conn;
  	PQconninfoOption   *connOptions;
***************
*** 416,422 ****
  	 * Parse the conninfo arrays
  	 */
  	connOptions = conninfo_array_parse(keywords, values,
! 									   &conn->errorMessage, true);
  	if (connOptions == NULL)
  	{
  		conn->status = CONNECTION_BAD;
--- 420,427 ----
  	 * Parse the conninfo arrays
  	 */
  	connOptions = conninfo_array_parse(keywords, values,
! 									   &conn->errorMessage,
! 									   true, expand_dbname);
  	if (connOptions == NULL)
  	{
  		conn->status = CONNECTION_BAD;
***************
*** 3729,3744 ****
   * left in errorMessage.
   * Defaults are supplied (from a service file, environment variables, etc)
   * for unspecified options, but only if use_defaults is TRUE.
   */
  static PQconninfoOption *
  conninfo_array_parse(const char **keywords, const char **values,
! 					 PQExpBuffer errorMessage, bool use_defaults)
  {
  	char			   *tmp;
  	PQconninfoOption   *options;
  	PQconninfoOption   *option;
  	int					i = 0;
  
  	/* Make a working copy of PQconninfoOptions */
  	options = malloc(sizeof(PQconninfoOptions));
  	if (options == NULL)
--- 3734,3786 ----
   * left in errorMessage.
   * Defaults are supplied (from a service file, environment variables, etc)
   * for unspecified options, but only if use_defaults is TRUE.
+  *
+  * If expand_dbname is TRUE, and the value passed for keyword "dbname"
+  * contains an "=", assume it is a conninfo string and process it,
+  * overriding any previously processed conflicting keywords. Subsequent
+  * keywords will take precedence, however.
   */
  static PQconninfoOption *
  conninfo_array_parse(const char **keywords, const char **values,
! 					 PQExpBuffer errorMessage, bool use_defaults,
! 					 bool expand_dbname)
  {
  	char			   *tmp;
  	PQconninfoOption   *options;
+ 	PQconninfoOption   *str_options = NULL;
  	PQconninfoOption   *option;
  	int					i = 0;
  
+ 	/*
+ 	 * If expand_dbname is true, check keyword "dbname"
+ 	 * to see if val is actually a conninfo string
+ 	 */
+ 	while(expand_dbname && keywords[i])
+ 	{
+ 		const char *pname = keywords[i];
+ 		const char *pvalue  = values[i];
+ 
+ 		/* first find "dbname" if any */
+ 		if (strcmp(pname, "dbname") == 0)
+ 		{
+ 			/* next look for "=" in the value */
+ 			if (pvalue && strchr(pvalue, '='))
+ 			{
+ 				/*
+ 				 * Must be a conninfo string, so parse it, but do not
+ 				 * use defaults here -- those get picked up later.
+ 				 * We only want to override for those parameters actually
+ 				 * passed.
+ 				 */
+ 				str_options = conninfo_parse(pvalue, errorMessage, false);
+ 				if (str_options == NULL)
+ 					return NULL;
+ 			}
+ 			break;
+ 		}
+ 		++i;
+ 	}
+ 
  	/* Make a working copy of PQconninfoOptions */
  	options = malloc(sizeof(PQconninfoOptions));
  	if (options == NULL)
***************
*** 3749,3754 ****
--- 3791,3797 ----
  	}
  	memcpy(options, PQconninfoOptions, sizeof(PQconninfoOptions));
  
+ 	i = 0;
  	/* Parse the keywords/values arrays */
  	while(keywords[i])
  	{
***************
*** 3774,3792 ****
  				return NULL;
  			}
  
! 		    /*
! 		     * Store the value
! 		     */
! 		    if (option->val)
! 		    	free(option->val);
! 		    option->val = strdup(pvalue);
! 		    if (!option->val)
! 		    {
! 		    	printfPQExpBuffer(errorMessage,
! 		    					  libpq_gettext("out of memory\n"));
! 		    	PQconninfoFree(options);
! 		    	return NULL;
! 		    }
  		}
  		++i;
  	}
--- 3817,3867 ----
  				return NULL;
  			}
  
! 			/*
! 			 * If we are on the dbname parameter, and we have a parsed
! 			 * conninfo string, copy those parameters across, overriding
! 			 * any existing previous settings
! 			 */
! 			if (strcmp(pname, "dbname") == 0 && str_options)
! 			{
! 				PQconninfoOption *str_option;
! 
! 				for (str_option = str_options; str_option->keyword != NULL; str_option++)
! 				{
! 					if (str_option->val != NULL)
! 					{
! 						int			k;
! 
! 						for (k = 0; options[k].keyword; k++)
! 						{
! 							if (strcmp(options[k].keyword, str_option->keyword) == 0)
! 							{
! 								if (options[k].val)
! 									free(options[k].val);
! 								options[k].val = strdup(str_option->val);
! 								break;
! 							}
! 						}
! 					}
! 				}
! 				PQconninfoFree(str_options);
! 			}
! 			else
! 			{
! 				/*
! 				 * Store the value, overriding previous settings
! 				 */
! 				if (option->val)
! 					free(option->val);
! 				option->val = strdup(pvalue);
! 				if (!option->val)
! 				{
! 					printfPQExpBuffer(errorMessage,
! 									  libpq_gettext("out of memory\n"));
! 					PQconninfoFree(options);
! 					return NULL;
! 				}
! 			}
  		}
  		++i;
  	}
Index: src/interfaces/libpq/libpq-fe.h
===================================================================
RCS file: /opt/src/cvs/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.150
diff -c -r1.150 libpq-fe.h
*** src/interfaces/libpq/libpq-fe.h	28 Jan 2010 06:28:26 -0000	1.150
--- src/interfaces/libpq/libpq-fe.h	3 Feb 2010 02:37:50 -0000
***************
*** 226,237 ****
  /* make a new client connection to the backend */
  /* Asynchronous (non-blocking) */
  extern PGconn *PQconnectStart(const char *conninfo);
! extern PGconn *PQconnectStartParams(const char **keywords, const char **values);
  extern PostgresPollingStatusType PQconnectPoll(PGconn *conn);
  
  /* Synchronous (blocking) */
  extern PGconn *PQconnectdb(const char *conninfo);
! extern PGconn *PQconnectdbParams(const char **keywords, const char **values);
  extern PGconn *PQsetdbLogin(const char *pghost, const char *pgport,
  			 const char *pgoptions, const char *pgtty,
  			 const char *dbName,
--- 226,239 ----
  /* make a new client connection to the backend */
  /* Asynchronous (non-blocking) */
  extern PGconn *PQconnectStart(const char *conninfo);
! extern PGconn *PQconnectStartParams(const char **keywords,
! 			 const char **values, bool expand_dbname);
  extern PostgresPollingStatusType PQconnectPoll(PGconn *conn);
  
  /* Synchronous (blocking) */
  extern PGconn *PQconnectdb(const char *conninfo);
! extern PGconn *PQconnectdbParams(const char **keywords,
! 			 const char **values, bool expand_dbname);
  extern PGconn *PQsetdbLogin(const char *pghost, const char *pgport,
  			 const char *pgoptions, const char *pgtty,
  			 const char *dbName,

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to