我有以下功能
CREATE OR REPLACE FUNCTION match_custom_filter(filters text[], id text)
RETURNS boolean LANGUAGE plpgsql as $$
DECLARE
r boolean;
BEGIN
execute format(
'SELECT 1 FROM trackings t LEFT JOIN visitors v ON v.id = t.visitor_id
WHERE v.id = ''%s'' AND %s',
id,
array_to_string(filters, ') AND ('))
into r;
RETURN r;
END $$;
select v.*, array_agg(g.name) as groups from visitors v join groups g on match_custom_filter(g.formatted_custom_filters, v.id)
where v.id = 'cov4pisw00000sjctfyvwq126'
group by v.id
当过滤器不为空时,这可以正常工作。但是过滤器也可能是空的,在这种情况下,我将有一个没有右侧的悬空AND。
错误:
ERROR: syntax error at end of input
LINE 2: ... WHERE v.id = 'cov4pisw00000sjctfyvwq126' AND
^
QUERY: SELECT 1 FROM trackings t LEFT JOIN visitors v ON v.id = t.visitor_id
WHERE v.id = 'cov4pisw00000sjctfyvwq126' AND
CONTEXT: PL/pgSQL function match_custom_filter(text[],text) line 5 at EXECUTE statement
处理此问题的最佳方式是什么?
更新:
基于JSONB过滤器对象数组生成字符串过滤器数组的示例
def build_condition(%{"filter" => filter, "field" => field, "value" => value}) when field in @default_values do
case filter do
"greater_than" -> "#{field} > #{value}"
"less_than" -> "#{field} < #{value}"
"is" -> "#{field} = '#{value}'"
"is_not" -> "#{field} <> '#{value}'"
..
答案 0 :(得分:3)
首先是警告。你在这里做的是给你in-stored-proc sql注入。我强烈建议您重新考虑,以便正确参数化。
现在,说过这个,显而易见的选择是声明一个文本变量,然后对其进行预处理。
在DECLARE
块中添加:
filterstring text;
然后在你的身体中,你添加:
filterstring := array_to_string(filters, ') AND ('))
IF filterstring = '' or filterstring is null THEN
filterstring := 'TRUE';
END IF;
然后,您使用filterstring
代替array_to_string
来电中的format()
来电。
请注意,只要您通过字符串插值在任何地方组装查询,就可以进行SQL注入。
为了防止SQL注入,您需要重新考虑一下您的方法。您最好的选择是尽可能使用format()进行查询。所以:
execute 'SELECT 1 FROM trackings t
LEFT JOIN visitors v ON v.id = t.visitor_id
WHERE v.id = $1'
USING id;
这导致计划和填充价值发生在两个不同的点上。这在简单参数的情况下效果很好。但是,在动态过滤器的情况下,它不能很好地工作。
不是传入一维数组,而是传递一个二维(nx3数组),每行三个元素。这些是列名,运算符和值。您可以通过传递quote_ident
来清理列名称,并通过传递quote_literal
来清除值,但是对操作符进行清理可能是个问题所以我的建议是将这些列入白名单并抛出异常,如果找不到运营商。类似的东西:
DECLARE
...
op TEXT;
allowed_ops TEXT[] := ARRAY['=', '<=', '>='];
BEGIN
...
IF not(op = ANY(allowed_ops)) THEN
RAISE EXCEPTION 'Illegal operator in function, %', op;
END IF;
...
END;
这并不容易,但它是可行的。
答案 1 :(得分:2)
由于您开始使用filters in the form of a jsonb
array,因此应将其用作函数参数而不是text[]
。首先,它将允许您防止SQL注入。
CREATE OR REPLACE FUNCTION match_custom_filter(filters jsonb, id text)
RETURNS boolean LANGUAGE plpgsql AS $$
DECLARE
f text;
r boolean;
BEGIN
IF jsonb_array_length(filters) = 0 THEN
-- If no filters are specified then run a straight SQL query against trackings
PERFORM * FROM trackings WHERE visitor_id = quote_literal(id);
RETURN FOUND;
ELSE
-- Build the filters from the jsonb array
SELECT string_agg(
-- Concatenate the parts from a single json object into a filter
quote_ident(j->>'field') || -- avoid SQL injection on column name
CASE j->>'type'
WHEN 'greater_than' THEN ' > '
...
END ||
quote_literal(j->>'value'), -- avoid SQL injection on value
-- Aggregate individual filters with the AND operator
' AND ') INTO f
FROM jsonb_array_elements(filters) j;
-- Run a dynamic query with the filters
EXECUTE format('SELECT true FROM trackings t
LEFT JOIN visitors v ON v.id = t.visitor_id
WHERE v.id = %L AND %s LIMIT 1', id, f) INTO r;
RETURN r;
END IF;
END $$;
您应该调用此函数传递jsonb
数组,如下所示:
SELECT v.*, array_agg(g.name) AS groups
FROM visitors v JOIN groups g ON match_custom_filter(g.group->'filter', v.id)
WHERE v.id = 'cov4pisw00000sjctfyvwq126'
GROUP BY v.id;