簡體   English   中英

Postgres jsonb 查詢動態值

[英]Postgres jsonb query for dynamic values

在用戶表中,我有一個具有以下 json 結構的 jsob 列experience

[
    {
        "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

此查詢適合參數化。 下面查詢中的參數是$1 您可能需要根據環境的風格更改其語法。

select * from 
(
  select u.*,
    (
      select count(*)
      from jsonb_array_elements(u.experience::jsonb) ej -- jsonb list of experiences 
      inner join jsonb_array_elements($1::jsonb) rj     -- jsonb list of request items
        on ej ->> 'field' =  rj ->> 'field'
        and (ej ->> 'years')::numeric >= (rj ->> 'years')::numeric
    ) cnt
 from users u
) t
where t.cnt = jsonb_array_length($1::jsonb);

指數

首先,您需要索引支持 我建議使用jsonb_path_ops索引,例如:

CREATE INDEX users_experience_gin_idx ON users USING gin (experience jsonb_path_ops);

看:

詢問

以及可以利用該索引的查詢(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() 看:

動態生成查詢

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注入是不可能的,因為......

  1. 強制執行有效的 JSON 輸入
  2. JSON 值與原始 JSON 雙引號連接。
  3. 最重要的是,每個生成的jsonpath值都用quote_nullable()單引號。

在討論 SQL/JSON 路徑表達式的主題時,我使用一個來斷言有效輸入:

jsonb_path_exists (_filter_arr, '$[*] ? (!exists(@.field) || !exists(@.years))')

檢查 JSON 數組中的每個對象,以及是否缺少兩個必需的鍵( fieldyears )之一。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM