WHERE子句基于列值的运算符

Nik*_*dun 1 sql postgresql dynamic-sql where-clause

我想有一个包含值来比较表操作员使用(=,!=,~,!~等).例如:

CREATE TABLE rule (
    value1 varchar NOT NULL,
    op1 varchar NOT NULL,
    value2 varchar NOT NULL,
    op2 varchar NOT NULL,
    ...
);
Run Code Online (Sandbox Code Playgroud)

我想要的可以使用这个伪代码来描述:

SELECT * FROM rule WHERE value1 op1 ?;
Run Code Online (Sandbox Code Playgroud)

在示例中,我将操作符存储在单独的列中,但我对其他解决方案持开放态度.

Erw*_*ter 5

SQL是静态语言,不允许参数化除值之外的任何内容.您需要某种形式的动态 SQL - 它会烧掉以连接依次执行的查询字符串.

您可以SELECT使用服务器端PL/pgSQL函数使整个语句动态化.或者使用任何客户端逻辑,这需要额外的往返服务器.

或者通过将该部分封装在函数中来使评估动态化:

CREATE TABLE the_rule (
   value1 text NOT NULL
 , op1    text NOT NULL
 , value2 text NOT NULL
 , op2    text NOT NULL
);

INSERT INTO the_rule VALUES
   ('foo','=','bar','<')
 , ('baz','<','bam','>');


CREATE FUNCTION rule_eval(_val text, _opr text, _arg text
                        , OUT _pass bool) AS
$func$
BEGIN
   EXECUTE format('SELECT %L %s %L', _val, _opr, _arg)
   INTO _pass;
END
$func$  LANGUAGE plpgsql;

SELECT * FROM the_rule
WHERE  rule_eval(value1, op1, 'foo')
AND    rule_eval(value2, op2, 'aaa');
Run Code Online (Sandbox Code Playgroud)

db <> 在这里小提琴

但是,这种混淆在很大程度上阻止了性能优化的执行计划.例如,这些函数是Postgres查询计划程序的黑盒子,索引不能使用.

而且您对SQL注入持开放态度._val_arg在上面的示例中正确引用,从而使SQL注入无法进行.但是运营商不能被引用.您可以使用对象标识符类型regoperator来保证有效的运算符 - 并转换regoperOPERATOR()构造并与其结合以获得有效的语法.喜欢:

CREATE TABLE the_rule (
   value1 text NOT NULL
 , op1    regoperator NOT NULL
 , value2 text NOT NULL
 , op2    regoperator NOT NULL
);

INSERT INTO the_rule VALUES
   ('foo', '=(text,text)', 'bar', '<(text,text)')
 , ('baz', '<(text,text)', 'bam', '>(text,text)');

CREATE FUNCTION rule_eval(_val text, _opr regoperator, _arg text
                        , OUT _pass bool) AS
$func$
BEGIN
   EXECUTE format('SELECT %L OPERATOR(%s) %L', _val, _opr::regoper, _arg)
   INTO _pass;
END
$func$  LANGUAGE plpgsql;

-- Same query as above
Run Code Online (Sandbox Code Playgroud)

db <> 在这里小提琴

现在,SQL注入是不可能的.但是我们已经引入了更多的复杂性.而且我不确定reoperator在转储/恢复周期或主要版本升级期间保持有效.(text毕竟可能更好地坚持代表.)

或者,如果您只允许一组预定的运算符 - 对于安全查找表或enum类型具有FK约束,或者CHECK对于充满允许运算符的手只有一个普通约束.喜欢:

CREATE TABLE the_rule (
   value1 text NOT NULL
 , op1    text NOT NULL CHECK (op1 = ANY ('{>,>=,=,<=,<}'))
 , value2 text NOT NULL
 , op2    text NOT NULL CHECK (op2 = ANY ('{>,>=,=,<=,<}'))
);

INSERT INTO the_rule VALUES
   ('foo', '=', 'bar', '<')
 , ('baz', '<', 'bam', '>');

CREATE FUNCTION rule_eval(_val text, _opr text, _arg text
                        , OUT _pass bool) AS
$func$
BEGIN
   EXECUTE format('SELECT %L %s %L', _val, _opr, _arg)
   INTO _pass;
END
$func$  LANGUAGE plpgsql;

SELECT * FROM the_rule
WHERE  rule_eval(value1, op1, 'foo')
AND    rule_eval(value2, op2, 'aaa');
Run Code Online (Sandbox Code Playgroud)

db <> 在这里小提琴

表中的输入是安全的,但函数本身现在是SQL注入的入口点.

我们甚至没有触及不同数据类型的并发症.

简而言之:可能,但你需要确切知道你正在做什么来处理各种可能的并发症.通常,有一种更简单的方法来实现您的需求.