Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit d0cfc01

Browse files
committed
Allow psql variables to be interpolated with literal or identifier escaping.
Loosely based on a patch by Pavel Stehule.
1 parent 76be0c8 commit d0cfc01

File tree

2 files changed

+123
-37
lines changed

2 files changed

+123
-37
lines changed

doc/src/sgml/ref/psql-ref.sgml

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.236 2009/12/24 23:36:39 tgl Exp $
2+
$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.237 2010/01/29 17:44:12 rhaas Exp $
33
PostgreSQL documentation
44
-->
55

@@ -658,7 +658,12 @@ testdb=&gt;
658658
<para>
659659
If an unquoted argument begins with a colon (<literal>:</literal>),
660660
it is taken as a <application>psql</> variable and the value of the
661-
variable is used as the argument instead.
661+
variable is used as the argument instead. If the variable name is
662+
surrounded by single quotes (e.g. <literal>:'var'</literal>), it
663+
will be escaped as an SQL literal and the result will be used as
664+
the argument. If the variable name is surrounded by double quotes,
665+
it will be escaped as an SQL identifier and the result will be used
666+
as the argument.
662667
</para>
663668

664669
<para>
@@ -2711,59 +2716,62 @@ bar
27112716
<para>
27122717
An additional useful feature of <application>psql</application>
27132718
variables is that you can substitute (<quote>interpolate</quote>)
2714-
them into regular <acronym>SQL</acronym> statements. The syntax for
2715-
this is again to prepend the variable name with a colon
2719+
them into regular <acronym>SQL</acronym> statements.
2720+
<application>psql</application> provides special facilities for
2721+
ensuring that values used as SQL literals and identifiers are
2722+
properly escaped. The syntax for interpolating a value without
2723+
any special escaping is again to prepend the variable name with a colon
27162724
(<literal>:</literal>):
27172725
<programlisting>
27182726
testdb=&gt; <userinput>\set foo 'my_table'</userinput>
27192727
testdb=&gt; <userinput>SELECT * FROM :foo;</userinput>
27202728
</programlisting>
2721-
would then query the table <literal>my_table</literal>. The value of
2722-
the variable is copied literally, so it can even contain unbalanced
2723-
quotes or backslash commands. You must make sure that it makes sense
2724-
where you put it. Variable interpolation will not be performed into
2725-
quoted <acronym>SQL</acronym> entities.
2729+
would then query the table <literal>my_table</literal>. Note that this
2730+
may be unsafe: the value of the variable is copied literally, so it can
2731+
even contain unbalanced quotes or backslash commands. You must make sure
2732+
that it makes sense where you put it.
2733+
</para>
2734+
2735+
<para>
2736+
When a value is to be used as an SQL literal or identifier, it is
2737+
safest to arrange for it to be escaped. To escape the value of
2738+
a variable as an SQL literal, write a colon followed by the variable
2739+
name in single quotes. To escape the value an SQL identifier, write
2740+
a colon followed by the variable name in double quotes. The previous
2741+
example would be more safely written this way:
2742+
<programlisting>
2743+
testdb=&gt; <userinput>\set foo 'my_table'</userinput>
2744+
testdb=&gt; <userinput>SELECT * FROM :"foo";</userinput>
2745+
</programlisting>
2746+
Variable interpolation will not be performed into quoted
2747+
<acronym>SQL</acronym> entities.
27262748
</para>
27272749

27282750
<para>
27292751
One possible use of this mechanism is to
27302752
copy the contents of a file into a table column. First load the file into a
27312753
variable and then proceed as above:
27322754
<programlisting>
2733-
testdb=&gt; <userinput>\set content '''' `cat my_file.txt` ''''</userinput>
2734-
testdb=&gt; <userinput>INSERT INTO my_table VALUES (:content);</userinput>
2735-
</programlisting>
2736-
One problem with this approach is that <filename>my_file.txt</filename>
2737-
might contain single quotes. These need to be escaped so that
2738-
they don't cause a syntax error when the second line is processed. This
2739-
could be done with the program <command>sed</command>:
2740-
<programlisting>
2741-
testdb=&gt; <userinput>\set content '''' `sed -e "s/'/''/g" &lt; my_file.txt` ''''</userinput>
2742-
</programlisting>
2743-
If you are using non-standard-conforming strings then you'll also need
2744-
to double backslashes. This is a bit tricky:
2745-
<programlisting>
2746-
testdb=&gt; <userinput>\set content '''' `sed -e "s/'/''/g" -e 's/\\/\\\\/g' &lt; my_file.txt` ''''</userinput>
2755+
testdb=&gt; <userinput>\set content `cat my_file.txt`</userinput>
2756+
testdb=&gt; <userinput>INSERT INTO my_table VALUES (:'content');</userinput>
27472757
</programlisting>
2748-
Note the use of different shell quoting conventions so that neither
2749-
the single quote marks nor the backslashes are special to the shell.
2750-
Backslashes are still special to <command>sed</command>, however, so
2751-
we need to double them. (Perhaps
2752-
at one point you thought it was great that all Unix commands use the
2753-
same escape character.)
2758+
(Note that this still won't work if my_file.txt contains NUL bytes.
2759+
psql does not support embedded NUL bytes in variable values.)
27542760
</para>
27552761

27562762
<para>
2757-
Since colons can legally appear in SQL commands, the following rule
2758-
applies: the character sequence
2759-
<quote>:name</quote> is not changed unless <quote>name</> is the name
2760-
of a variable that is currently set. In any case you can escape
2761-
a colon with a backslash to protect it from substitution. (The
2762-
colon syntax for variables is standard <acronym>SQL</acronym> for
2763+
Since colons can legally appear in SQL commands, an apparent attempt
2764+
at interpolation (such as <literal>:name</literal>,
2765+
<literal>:'name'</literal>, or <literal>:"name"</literal>) is not
2766+
changed unless the named variable is currently set. In any case you
2767+
can escape a colon with a backslash to protect it from substitution.
2768+
(The colon syntax for variables is standard <acronym>SQL</acronym> for
27632769
embedded query languages, such as <application>ECPG</application>.
27642770
The colon syntax for array slices and type casts are
27652771
<productname>PostgreSQL</productname> extensions, hence the
2766-
conflict.)
2772+
conflict. The colon syntax for escaping a variable's value as an
2773+
SQL literal or identifier is a <application>psql</application>
2774+
extension.)
27672775
</para>
27682776

27692777
</refsect3>

src/bin/psql/psqlscan.l

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* Portions Copyright (c) 1994, Regents of the University of California
3434
*
3535
* IDENTIFICATION
36-
* $PostgreSQL: pgsql/src/bin/psql/psqlscan.l,v 1.31 2010/01/02 16:57:59 momjian Exp $
36+
* $PostgreSQL: pgsql/src/bin/psql/psqlscan.l,v 1.32 2010/01/29 17:44:12 rhaas Exp $
3737
*
3838
*-------------------------------------------------------------------------
3939
*/
@@ -118,6 +118,7 @@ static YY_BUFFER_STATE prepare_buffer(const char *txt, int len,
118118
char **txtcopy);
119119
static void emit(const char *txt, int len);
120120
static bool is_utf16_surrogate_first(uint32 c);
121+
static void escape_variable(bool as_ident);
121122

122123
#define ECHO emit(yytext, yyleng)
123124

@@ -707,6 +708,14 @@ other .
707708
}
708709
}
709710

711+
:'[A-Za-z0-9_]+' {
712+
escape_variable(false);
713+
}
714+
715+
:\"[A-Za-z0-9_]+\" {
716+
escape_variable(true);
717+
}
718+
710719
/*
711720
* Back to backend-compatible rules.
712721
*/
@@ -927,6 +936,27 @@ other .
927936
return LEXRES_OK;
928937
}
929938

939+
:'[A-Za-z0-9_]+' {
940+
if (option_type == OT_VERBATIM)
941+
ECHO;
942+
else
943+
{
944+
escape_variable(false);
945+
return LEXRES_OK;
946+
}
947+
}
948+
949+
950+
:\"[A-Za-z0-9_]+\" {
951+
if (option_type == OT_VERBATIM)
952+
ECHO;
953+
else
954+
{
955+
escape_variable(true);
956+
return LEXRES_OK;
957+
}
958+
}
959+
930960
"|" {
931961
ECHO;
932962
if (option_type == OT_FILEPIPE)
@@ -1740,3 +1770,51 @@ is_utf16_surrogate_first(uint32 c)
17401770
{
17411771
return (c >= 0xD800 && c <= 0xDBFF);
17421772
}
1773+
1774+
static void
1775+
escape_variable(bool as_ident)
1776+
{
1777+
char saved_char;
1778+
const char *value;
1779+
1780+
/* Variable lookup. */
1781+
saved_char = yytext[yyleng - 1];
1782+
yytext[yyleng - 1] = '\0';
1783+
value = GetVariable(pset.vars, yytext + 2);
1784+
1785+
/* Escaping. */
1786+
if (value)
1787+
{
1788+
if (!pset.db)
1789+
psql_error("can't escape without active connection\n");
1790+
else
1791+
{
1792+
char *escaped_value;
1793+
1794+
if (as_ident)
1795+
escaped_value =
1796+
PQescapeIdentifier(pset.db, value, strlen(value));
1797+
else
1798+
escaped_value =
1799+
PQescapeLiteral(pset.db, value, strlen(value));
1800+
if (escaped_value == NULL)
1801+
{
1802+
const char *error = PQerrorMessage(pset.db);
1803+
psql_error("%s", error);
1804+
}
1805+
else
1806+
{
1807+
appendPQExpBufferStr(output_buf, escaped_value);
1808+
PQfreemem(escaped_value);
1809+
return;
1810+
}
1811+
}
1812+
}
1813+
1814+
/*
1815+
* If we reach this point, some kind of error has occurred. Emit the
1816+
* original text into the output buffer.
1817+
*/
1818+
yytext[yyleng - 1] = saved_char;
1819+
emit(yytext, yyleng);
1820+
}

0 commit comments

Comments
 (0)