基于列值的 WHERE 子句运算符
WHERE clause operator based on column value
我想要一个 table,其中包含要与要使用的 和 运算符进行比较的值(=
、!=
、 ~
、!~
等)。例如:
CREATE TABLE rule (
value1 varchar NOT NULL,
op1 varchar NOT NULL,
value2 varchar NOT NULL,
op2 varchar NOT NULL,
...
);
我想要的可以用这个伪代码来描述:
SELECT * FROM rule WHERE value1 op1 ?;
在示例中,我将运算符存储在单独的列中,但我对其他解决方案持开放态度。
这里是解决这个问题的明确逻辑:
where (op1 = '=' and value1 = ?) or
(op1 = '<' and value < ?) or
. . .
你可以尝试在condition中使用OR
和AND
SELECT *
FROM rule
WHERE
(op1 = '=' AND value1 = ?)
OR
(op1 = '!=' AND value1 != ?)
SQL 是静态语言,不允许参数化除 values 之外的任何内容。您需要某种形式的 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<>fiddle 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<>fiddle here
现在,SQL注入是不可能的。但是我们已经引入了更多的复杂性。而且我不确定 reoperator
在转储/恢复周期或主要版本升级期间是否保持有效。 (毕竟坚持 text
表示可能更好。)
或 如果您只允许一组预先确定的运算符 - 对安全查找具有 FK 约束 table 或 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<>fiddle here
来自 table 的输入是安全的,但函数本身现在是 SQL 注入的入口点。
我们甚至还没有涉及不同数据 类型 的并发症。
简而言之:可能,但你需要确切地知道你在做什么,以应对各种可能出现的并发症。 通常,有一种更简单的方法来实现您的要求。
我想要一个 table,其中包含要与要使用的 和 运算符进行比较的值(=
、!=
、 ~
、!~
等)。例如:
CREATE TABLE rule (
value1 varchar NOT NULL,
op1 varchar NOT NULL,
value2 varchar NOT NULL,
op2 varchar NOT NULL,
...
);
我想要的可以用这个伪代码来描述:
SELECT * FROM rule WHERE value1 op1 ?;
在示例中,我将运算符存储在单独的列中,但我对其他解决方案持开放态度。
这里是解决这个问题的明确逻辑:
where (op1 = '=' and value1 = ?) or
(op1 = '<' and value < ?) or
. . .
你可以尝试在condition中使用OR
和AND
SELECT *
FROM rule
WHERE
(op1 = '=' AND value1 = ?)
OR
(op1 = '!=' AND value1 != ?)
SQL 是静态语言,不允许参数化除 values 之外的任何内容。您需要某种形式的 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<>fiddle 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<>fiddle here
现在,SQL注入是不可能的。但是我们已经引入了更多的复杂性。而且我不确定 reoperator
在转储/恢复周期或主要版本升级期间是否保持有效。 (毕竟坚持 text
表示可能更好。)
或 如果您只允许一组预先确定的运算符 - 对安全查找具有 FK 约束 table 或 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<>fiddle here
来自 table 的输入是安全的,但函数本身现在是 SQL 注入的入口点。
我们甚至还没有涉及不同数据 类型 的并发症。
简而言之:可能,但你需要确切地知道你在做什么,以应对各种可能出现的并发症。 通常,有一种更简单的方法来实现您的要求。