基于列值的WHERE子句运算符

时间:2018-10-02 21:21:28

标签: sql postgresql where-clause dynamic-sql

我想创建一个表,其中包含要与要使用的运算符进行比较的值(=!=~,{{1 }}等)。例如:

!~

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

CREATE TABLE rule (
    value1 varchar NOT NULL,
    op1 varchar NOT NULL,
    value2 varchar NOT NULL,
    op2 varchar NOT NULL,
    ...
);

在示例中,我将运算符存储在单独的列中,但是我对其他解决方案持开放态度。

3 个答案:

答案 0 :(得分:5)

SQL是静态语言,除外,不允许对其他任何参数进行参数化。您需要某种形式的 dynamic SQL-简化为连接依次执行的查询字符串。

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

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

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');

db <>提琴here

但是,这种混淆很大程度上禁止了性能优化的执行计划。此类功能对Postgres查询计划器来说是黑盒,例如,不能使用索引。

您可以打开 SQL注入。在上面的示例中正确引用了_val_arg,从而使SQL注入成为不可能。但是 operator 不能被引用。您 可以 使用对象标识符类型regoperator来保证有效的运算符-并强制转换为regoper并与OPERATOR()构造结合使用获取有效的语法。喜欢:

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

db <>提琴here

现在,SQL注入是不可能的。但是,我们引入了更多的复杂性。而且我不确定reoperator在转储/还原周期或主要版本升级期间是否仍然有效。 (毕竟,坚持使用text表示可能更好。)

或者,如果您只允许一组预定的运算符-对安全查找表具有FK约束,或者对enum类型具有约束,或者仅对一个普通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');

db <>提琴here

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

而且,我们甚至还没有涉及到不同数据类型 的复杂性。

简而言之:可能,但是您需要确切地知道您在做什么以应对各种可能的并发症。 通常,有一种更简单的方式来实现您的要求。

答案 1 :(得分:1)

以下是解决此问题的明确逻辑:

where (op1 = '=' and value1 = ?) or
      (op1 = '<' and value < ?) or
      . . .

答案 2 :(得分:1)

您可以尝试在有条件的情况下使用ORAND

SELECT * 
FROM rule 
WHERE 
    (op1 = '=' AND value1 = ?) 
OR
    (op1 = '!=' AND value1 != ?)