在用戶表中,我有一個experience具有以下 json 結構的 jsob 列:
[
{
"field": "devops",
"years": 9
},
{
"field": "backend dev",
"years": 7
}
... // could be N number of objects with different values
]
業務需求
客戶可以要求在任何領域都有經驗的人,并且在每個領域都有各自的年經驗
這是一個示例查詢
SELECT * FROM users
WHERE
jsonb_path_exists(experience, '$[*] ? (@.field == "devops" && @.years > 5)') and
jsonb_path_exists(experience, '$[*] ? (@.field == "backend dev" && @.years > 5)')
LIMIT 3;
問題
讓我們說如果我收到請求
[
{ field: "devops", years: 5 },
{ field: "java", years: 6 },
{ field: "ui/ux", years: 2 }] // and so on
如何動態創建查詢而不用擔心 sql 注入?
技術堆疊
- 節點
- 打字稿
- 型別ORM
- Postgres
uj5u.com熱心網友回復:
這是一個引數化查詢,因此或多或少是安全的。qualifies標量子查詢計算是否experience滿足所有請求項。下面查詢中的引數是$1。您可能需要根據環境的風格更改其語法。
select t.* from
(
select u.*,
(
select count(*) = jsonb_array_length($1)
from jsonb_array_elements(u.experience) ej -- jsonb list of experiences
inner join jsonb_array_elements($1) rj -- jsonb list of request items
on ej ->> 'field' = rj ->> 'field'
and (ej ->> 'years')::numeric >= (rj ->> 'years')::numeric
) as qualifies
from users as u
) as t
where t.qualifies;
uj5u.com熱心網友回復:
指數
首先,您需要索引支持。我建議使用如下jsonb_path_ops索引:
CREATE INDEX users_experience_gin_idx ON users USING gin (experience jsonb_path_ops);
看:
- 用于在 JSON 陣列中查找元素的索引
詢問
以及可以利用該索引的查詢(100% 相當于您的原始索引):
SELECT *
FROM users
WHERE experience @? '$[*] ? (@.field == "devops" && @.years > 5 )'
AND experience @? '$[*] ? (@.field == "backend dev" && @.years > 5)'
LIMIT 3;
需要Postgres 12或更高版本,其中添加了 SQL/JSON 路徑語言。
索引支持系結到Postgres 中的運算子。該操作@?是等效jsonb_path_exists()。看:
- 在 JSONB 記錄陣列中查找包含鍵的行
動態生成查詢
SELECT 'SELECT * FROM users
WHERE experience @? '
|| string_agg(quote_nullable(format('$[*] ? (@.field == %s && @.years > %s)'
, f->'field'
, f->'years')) || '::jsonpath'
, E'\nAND experience @? ')
|| E'\nLIMIT 3'
FROM jsonb_array_elements('[{"field": "devops", "years": 5 },
{"field": "java", "years": 6 },
{"field": "ui/ux", "years": 2 }]') f;
生成上述形式的查詢:
SELECT * FROM users
WHERE experience @? '$[*] ? (@.field == "devops" && @.years > 5)'::jsonpath
AND experience @? '$[*] ? (@.field == "java" && @.years > 6)'::jsonpath
AND experience @? '$[*] ? (@.field == "ui/ux" && @.years > 2)'::jsonpath
LIMIT 3;
全自動化
如何動態創建查詢而不用擔心 sql 注入?
將上面的查詢生成放到一個 PL/pgSQL 函式中動態執行:
CREATE OR REPLACE FUNCTION f_users_with_experience(_filter_arr jsonb, _limit int = 3)
RETURNS SETOF users
LANGUAGE plpgsql PARALLEL SAFE STABLE STRICT AS
$func$
DECLARE
_sql text;
BEGIN
-- assert (you may want to be stricter?)
IF jsonb_path_exists (_filter_arr, '$[*] ? (!exists(@.field) || !exists(@.years))') THEN
RAISE EXCEPTION 'Parameter $2 (_filter_arr) must be a JSON array with keys "field" and "years" in every object. Invalid input was: >>%<<', _filter_arr;
END IF;
-- generate query string
SELECT INTO _sql
'SELECT * FROM users
WHERE experience @? '
|| string_agg(quote_nullable(format('$[*] ? (@.field == %s && @.years > %s)'
, f->'field'
, f->'years'))
, E'\nAND experience @? ')
|| E'\nLIMIT ' || _limit
FROM jsonb_array_elements(_filter_arr) f;
-- execute
IF _sql IS NULL THEN
RAISE EXCEPTION 'SQL statement is NULL. Should not occur!';
ELSE
-- RAISE NOTICE '%', _sql; -- debug first if in doubt
RETURN QUERY EXECUTE _sql;
END IF;
END
$func$;
稱呼:
SELECT * FROM f_users_with_experience('[{"field": "devops", "years": 5 },
, {"field": "backend dev", "years": 6}]');
或者使用不同的LIMIT:
SELECT * FROM f_users_with_experience('[{"field": "devops", "years": 5 }]', 123);
db<>在這里擺弄
您應該熟悉 PL/pgSQL 來使用它并理解它。
SQL注入是不可能的,因為......
- 強制執行有效的 JSON 輸入
- JSON 值與原始 JSON 雙引號連接。
- Most importantly, each generated
jsonpathvalue is single-quoted withquote_nullable().
While being at the topic of SQL/JSON path expressions I use one to assert valid input:
jsonb_path_exists (_filter_arr, '$[*] ? (!exists(@.field) || !exists(@.years))')
Checks every object in the JSON array and whether one of the two required keys (field, years) is missing.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/337785.html
標籤:sql 节点.js PostgreSQL sequelize.js 打字机
上一篇:為什么這是空的?
下一篇:在嵌套回圈中呼叫sql中的函式
