diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 37e1fffb5fe85f3ccdb7418b8ea02e88279c4e4b..3bbad0c45999cfa2dc74ad9940001343a6eda9c2 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v 1.138 2003/10/03 18:26:14 tgl Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v 1.139 2003/10/04 21:05:20 tgl Exp $ --> <chapter id="libpq"> @@ -1553,8 +1553,7 @@ NULL is returned if the column number is out of range. <term><function>PQfnumber</function><indexterm><primary>PQfnumber</></></term> <listitem> <para> - Returns the column number - associated with the given column name. + Returns the column number associated with the given column name. <synopsis> int PQfnumber(const PGresult *res, const char *column_name); @@ -1564,6 +1563,24 @@ int PQfnumber(const PGresult *res, <para> -1 is returned if the given name does not match any column. </para> + +<para> + The given name is treated like an identifier in an SQL command, + that is, it is downcased unless double-quoted. For example, + given a query result generated from the SQL command +<programlisting> +select 1 as FOO, 2 as "BAR"; +</programlisting> + we would have the results: +<programlisting> +PQfname(res, 0) <lineannotation>foo</lineannotation> +PQfname(res, 1) <lineannotation>BAR</lineannotation> +PQfnumber(res, "FOO") <lineannotation>0</lineannotation> +PQfnumber(res, "foo") <lineannotation>0</lineannotation> +PQfnumber(res, "BAR") <lineannotation>-1</lineannotation> +PQfnumber(res, "\"BAR\"") <lineannotation>1</lineannotation> +</programlisting> +</para> </listitem> </varlistentry> diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 6d159a9a35a5569a770525edddf4f312b59d7a86..810a753ed3c7565e1dd7194daf7b60a5922708be 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.150 2003/10/03 18:26:14 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.151 2003/10/04 21:05:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1803,32 +1803,80 @@ PQfname(const PGresult *res, int field_num) } /* - * returns -1 on a bad field name + * PQfnumber: find column number given column name + * + * The column name is parsed as if it were in a SQL statement, including + * case-folding and double-quote processing. But note a possible gotcha: + * downcasing in the frontend might follow different locale rules than + * downcasing in the backend... + * + * Returns -1 if no match. In the present backend it is also possible + * to have multiple matches, in which case the first one is found. */ int PQfnumber(const PGresult *res, const char *field_name) { - int i; char *field_case; + bool in_quotes; + char *iptr; + char *optr; + int i; if (!res) return -1; + /* + * Note: it is correct to reject a zero-length input string; the proper + * input to match a zero-length field name would be "". + */ if (field_name == NULL || field_name[0] == '\0' || res->attDescs == NULL) return -1; + /* + * Note: this code will not reject partially quoted strings, eg + * foo"BAR"foo will become fooBARfoo when it probably ought to be + * an error condition. + */ field_case = strdup(field_name); - if (*field_case == '"') + if (field_case == NULL) + return -1; /* grotty */ + + in_quotes = false; + optr = field_case; + for (iptr = field_case; *iptr; iptr++) { - strcpy(field_case, field_case + 1); - *(field_case + strlen(field_case) - 1) = '\0'; + char c = *iptr; + + if (in_quotes) + { + if (c == '"') + { + if (iptr[1] == '"') + { + /* doubled quotes become a single quote */ + *optr++ = '"'; + iptr++; + } + else + in_quotes = false; + } + else + *optr++ = c; + } + else if (c == '"') + { + in_quotes = true; + } + else + { + if (isupper((unsigned char) c)) + c = tolower((unsigned char) c); + *optr++ = c; + } } - else - for (i = 0; field_case[i]; i++) - if (isupper((unsigned char) field_case[i])) - field_case[i] = tolower((unsigned char) field_case[i]); + *optr = '\0'; for (i = 0; i < res->numAttributes; i++) {