使用用户输入的动态列名打开游标的安全方法
Safe way to open cursor with dynamic column name from user input
我正在尝试编写打开带有动态列名称的游标的函数。
我担心这里明显的 SQL 注入可能性。
我很高兴在 fine manual 中看到这可以轻松完成,但是当我在我的示例中尝试它时,
出错了
error: column does not exist.
我目前的尝试可以浓缩成this SQL Fiddle。下面,我展示了这个 fiddle.
的格式化代码
tst()
函数的目标是能够计算常量查询的任何给定列中值的不同出现次数。
我要求提示我做错了什么,或者可能是一些替代方法以安全的方式实现相同的目标。
CREATE TABLE t1 (
f1 character varying not null,
f2 character varying not null
);
CREATE TABLE t2 (
f1 character varying not null,
f2 character varying not null
);
INSERT INTO t1 (f1,f2) VALUES ('a1','b1'), ('a2','b2');
INSERT INTO t2 (f1,f2) VALUES ('a1','c1'), ('a2','c2');
CREATE OR REPLACE FUNCTION tst(p_field character varying)
RETURNS INTEGER AS
$BODY$
DECLARE
v_r record;
v_cur refcursor;
v_sql character varying := 'SELECT count(DISTINCT(%I)) as qty
FROM t1 LEFT JOIN t2 ON (t1.f1=t2.f1)';
BEGIN
OPEN v_cur FOR EXECUTE format(v_sql,lower(p_field));
FETCH v_cur INTO v_r;
CLOSE v_cur;
return v_r.qty;
END;
$BODY$
LANGUAGE plpgsql;
测试执行:
SELECT tst('t1.f1')
提供错误信息:
ERROR: column "t1.f1" does not exist
Hint: PL/pgSQL function tst(character varying) line 1 at OPEN
这可行:
SELECT tst('f1');
您面临的问题:format()
将与 %I
连接的参数解释为 one 标识符。您正在尝试传递一个 table 限定的列名称,该名称由 两个 标识符组成,被解释为 "t1.f1"
(一个名称,双引号以保留否则名称中的非法点。
如果要传递table和列名,使用两个参数:
CREATE OR REPLACE FUNCTION tst2(_col text, _tbl text = NULL)
RETURNS int AS
$func$
DECLARE
v_r record;
v_cur refcursor;
v_sql text := 'SELECT count(DISTINCT %s) AS qty
FROM t1 LEFT JOIN t2 USING (f1)';
BEGIN
OPEN v_cur FOR EXECUTE
format(v_sql, CASE WHEN _tbl <> '' -- rule out NULL and ''
THEN quote_ident(lower(_tbl)) || '.' ||
quote_ident(lower(_col))
ELSE quote_ident(lower(_col)) END);
FETCH v_cur INTO v_r;
CLOSE v_cur;
RETURN v_r.qty;
END
$func$ LANGUAGE plpgsql;
旁白:DISTINCT f1
- 列名称周围没有括号,除非您想将其设为行类型。
实际上,您根本不需要光标。更快、更简单:
CREATE OR REPLACE FUNCTION tst3(_col text, _tbl text = NULL, OUT ct bigint) AS
$func$
BEGIN
EXECUTE format('SELECT count(DISTINCT %s) AS qty
FROM t1 LEFT JOIN t2 USING (f1)'
, CASE WHEN _tbl <> '' -- rule out NULL and ''
THEN quote_ident(lower(_tbl)) || '.' ||
quote_ident(lower(_col))
ELSE quote_ident(lower(_col)) END)
INTO ct;
RETURN;
END
$func$ LANGUAGE plpgsql;
为了方便起见,我提供了 NULL
作为 参数默认值 。这样您就可以仅使用列名称或使用列和 table 名称来调用该函数。但并非没有列名。
致电:
SELECT tst3('f1', 't1');
SELECT tst3('f1');
SELECT tst3(_col := 'f1');
与 test2()
相同。
相关回答:
- Table name as a PostgreSQL function parameter
我正在尝试编写打开带有动态列名称的游标的函数。 我担心这里明显的 SQL 注入可能性。 我很高兴在 fine manual 中看到这可以轻松完成,但是当我在我的示例中尝试它时,
出错了error: column does not exist.
我目前的尝试可以浓缩成this SQL Fiddle。下面,我展示了这个 fiddle.
的格式化代码tst()
函数的目标是能够计算常量查询的任何给定列中值的不同出现次数。
我要求提示我做错了什么,或者可能是一些替代方法以安全的方式实现相同的目标。
CREATE TABLE t1 (
f1 character varying not null,
f2 character varying not null
);
CREATE TABLE t2 (
f1 character varying not null,
f2 character varying not null
);
INSERT INTO t1 (f1,f2) VALUES ('a1','b1'), ('a2','b2');
INSERT INTO t2 (f1,f2) VALUES ('a1','c1'), ('a2','c2');
CREATE OR REPLACE FUNCTION tst(p_field character varying)
RETURNS INTEGER AS
$BODY$
DECLARE
v_r record;
v_cur refcursor;
v_sql character varying := 'SELECT count(DISTINCT(%I)) as qty
FROM t1 LEFT JOIN t2 ON (t1.f1=t2.f1)';
BEGIN
OPEN v_cur FOR EXECUTE format(v_sql,lower(p_field));
FETCH v_cur INTO v_r;
CLOSE v_cur;
return v_r.qty;
END;
$BODY$
LANGUAGE plpgsql;
测试执行:
SELECT tst('t1.f1')
提供错误信息:
ERROR: column "t1.f1" does not exist Hint: PL/pgSQL function tst(character varying) line 1 at OPEN
这可行:
SELECT tst('f1');
您面临的问题:format()
将与 %I
连接的参数解释为 one 标识符。您正在尝试传递一个 table 限定的列名称,该名称由 两个 标识符组成,被解释为 "t1.f1"
(一个名称,双引号以保留否则名称中的非法点。
如果要传递table和列名,使用两个参数:
CREATE OR REPLACE FUNCTION tst2(_col text, _tbl text = NULL)
RETURNS int AS
$func$
DECLARE
v_r record;
v_cur refcursor;
v_sql text := 'SELECT count(DISTINCT %s) AS qty
FROM t1 LEFT JOIN t2 USING (f1)';
BEGIN
OPEN v_cur FOR EXECUTE
format(v_sql, CASE WHEN _tbl <> '' -- rule out NULL and ''
THEN quote_ident(lower(_tbl)) || '.' ||
quote_ident(lower(_col))
ELSE quote_ident(lower(_col)) END);
FETCH v_cur INTO v_r;
CLOSE v_cur;
RETURN v_r.qty;
END
$func$ LANGUAGE plpgsql;
旁白:DISTINCT f1
- 列名称周围没有括号,除非您想将其设为行类型。
实际上,您根本不需要光标。更快、更简单:
CREATE OR REPLACE FUNCTION tst3(_col text, _tbl text = NULL, OUT ct bigint) AS
$func$
BEGIN
EXECUTE format('SELECT count(DISTINCT %s) AS qty
FROM t1 LEFT JOIN t2 USING (f1)'
, CASE WHEN _tbl <> '' -- rule out NULL and ''
THEN quote_ident(lower(_tbl)) || '.' ||
quote_ident(lower(_col))
ELSE quote_ident(lower(_col)) END)
INTO ct;
RETURN;
END
$func$ LANGUAGE plpgsql;
为了方便起见,我提供了 NULL
作为 参数默认值 。这样您就可以仅使用列名称或使用列和 table 名称来调用该函数。但并非没有列名。
致电:
SELECT tst3('f1', 't1');
SELECT tst3('f1');
SELECT tst3(_col := 'f1');
与 test2()
相同。
相关回答:
- Table name as a PostgreSQL function parameter