PostgreSQL quote ident and literal
背景
在PostgreSQL中,支持特殊字符的表名,列名等,但是他们在使用时需要使用双引号,例如包含大小写的表名。
在字符串输入时,可以使用的方式引用字符串,也可以使用单引号,但是如果在字符串中出现了应用字符,需要对引用字符进行转义。
这是数据库操作的常见问题。
PostgreSQL 提供了几个函数用来输出转义后的字符串.
https://www.postgresql.org/docs/9.6/static/functions-string.html
Function | Return Type | Description | Example | Result |
---|---|---|---|---|
quote_ident(string text) | text | Return the given string suitably quoted to be used as an identifier in an SQL statement string. Quotes are added only if necessary (i.e., if the string contains non-identifier characters or would be case-folded). Embedded quotes are properly doubled. See also Example 41-1. | quote_ident(‘Foo bar’) | “Foo bar” |
quote_literal(string text) | text | Return the given string suitably quoted to be used as a string literal in an SQL statement string. Embedded single-quotes and backslashes are properly doubled. Note that quote_literal returns null on null input; if the argument might be null, quote_nullable is often more suitable. See also Example 41-1. | quote_literal(E’O'Reilly’) | ‘O’‘Reilly’ |
quote_literal(value anyelement) | text | Coerce the given value to text and then quote it as a literal. Embedded single-quotes and backslashes are properly doubled. | quote_literal(42.5) | ‘42.5’ |
quote_nullable(string text) | text | Return the given string suitably quoted to be used as a string literal in an SQL statement string; or, if the argument is null, return NULL. Embedded single-quotes and backslashes are properly doubled. See also Example 41-1. | quote_nullable(NULL) | NULL |
quote_nullable(value anyelement) | text | Coerce the given value to text and then quote it as a literal; or, if the argument is null, return NULL. Embedded single-quotes and backslashes are properly doubled. | quote_nullable(42.5) | ‘42.5’ |
一般可用于构造SQL语句, 例如dblink中的动态SQL语句的构造 :
http://blog.163.com/digoal@126/blog/static/16387704020128772037884/
http://blog.163.com/digoal@126/blog/static/163877040201321125220134/
http://blog.163.com/digoal@126/blog/static/1638770402012731944439/
防止因为输入问题造成的不正确SQL.
正文
quote_ident用于构造数据库对象名, 例如表名, 列名.
quote_literal用于构造字符串.
quote_nullable和quote_literal类似, 只是空参数的处理不一样.
quote_literal是strict的, 输入空, 返回空.
quote_nullable是not strict的, 内部处理空值, 返回NULL字符串.
digoal=# select proisstrict,provolatile,proname from pg_proc where proname ~ 'quote';
proisstrict | provolatile | proname
-------------+-------------+----------------
t | i | quote_ident
t | i | quote_literal
t | s | quote_literal
f | i | quote_nullable
f | s | quote_nullable
(5 rows)
src/backend/utils/adt/quote.c
/*
* quote_nullable -
* Returns a properly quoted literal, with null values returned
* as the text string 'NULL'.
*/
Datum
quote_nullable(PG_FUNCTION_ARGS)
{
if (PG_ARGISNULL(0))
PG_RETURN_TEXT_P(cstring_to_text("NULL"));
else
PG_RETURN_DATUM(DirectFunctionCall1(quote_literal,
PG_GETARG_DATUM(0)));
}
quote_nullable和quote_literal的差异 :
digoal=# select 1 where quote_nullable(null) is null;
?column?
----------
(0 rows)
digoal=# select 1 where quote_literal(null) is null;
?column?
----------
1
(1 row)
digoal=# select 1 where quote_literal(null)='NULL';
?column?
----------
(0 rows)
digoal=# select 1 where quote_nullable(null)='NULL'; -- 当输入为空时,返回'NULL'字符串
?column?
----------
1
(1 row)
构造动态SQL举例.
不使用quote :
digoal=# create or replace function f_test(i_tablename text, i_cname text, i_cval text) returns void as $$
declare
v_sql text;
begin
v_sql := 'create table '||i_tablename||'('||i_cname||' text)';
raise notice '%', v_sql;
execute v_sql;
v_sql := 'insert into '||i_tablename||'('||i_cname||') values ('||i_cval||')';
raise notice '%', v_sql;
execute v_sql;
end;
$$ language plpgsql;
CREATE FUNCTION
当表名, 列名中有空格或其他特殊字符时, 必须要用双引号, 因此以下调用将报错.
digoal=# select f_test('a b','d e','''');
NOTICE: 00000: create table a b(d e text)
LOCATION: exec_stmt_raise, pl_exec.c:2985
ERROR: 42601: syntax error at or near "b"
LINE 1: create table a b(d e text)
^
QUERY: create table a b(d e text)
CONTEXT: PL/pgSQL function f_test(text,text,text) line 7 at EXECUTE statement
LOCATION: scanner_yyerror, scan.l:1044
当字符串中使用单引号时, 也必须逃逸, 使用双单引号. 这里也会报错.
digoal=# select f_test('ab','de','''');
NOTICE: 00000: create table ab(de text)
LOCATION: exec_stmt_raise, pl_exec.c:2985
NOTICE: 00000: insert into ab(de) values (')
LOCATION: exec_stmt_raise, pl_exec.c:2985
ERROR: 42601: unterminated quoted string at or near "')"
LINE 1: insert into ab(de) values (')
^
QUERY: insert into ab(de) values (')
CONTEXT: PL/pgSQL function f_test(text,text,text) line 10 at EXECUTE statement
LOCATION: scanner_yyerror, scan.l:1044
使用quote :
使用quote函数后, 数据库会自动根据需要帮你加上双引号和其他逃逸.
digoal=# create or replace function f_quote(i_tablename text, i_cname text, i_cval text) returns void as $$
declare
v_sql text;
begin
v_sql := 'create table '||quote_ident(i_tablename)||'('||quote_ident(i_cname)||' text)';
raise notice '%', v_sql;
execute v_sql;
v_sql := 'insert into '||quote_ident(i_tablename)||'('||quote_ident(i_cname)||') values ('||quote_literal(i_cval)||')';
raise notice '%', v_sql;
execute v_sql;
end;
$$ language plpgsql;
digoal=# select f_quote('a b','d e','''');
NOTICE: 00000: create table "a b"("d e" text)
LOCATION: exec_stmt_raise, pl_exec.c:2985
NOTICE: 00000: insert into "a b"("d e") values ('''')
LOCATION: exec_stmt_raise, pl_exec.c:2985
f_quote
---------
(1 row)
digoal=# select * from "a b";
d e
-----
'
(1 row)
参考
1. http://www.postgresql.org/docs/devel/static/functions-string.html
2. http://blog.163.com/digoal@126/blog/static/16387704020128772037884/
3. http://blog.163.com/digoal@126/blog/static/163877040201321125220134/
4. http://blog.163.com/digoal@126/blog/static/1638770402012731944439/