1

我想使用 Persistent/Esqueleto 来实现计数估计。

本文推荐的一种方法是定义这样的函数

CREATE FUNCTION count_estimate(query text) RETURNS integer AS $$
DECLARE
  rec   record;
  rows  integer;
BEGIN
  FOR rec IN EXECUTE 'EXPLAIN ' || query LOOP
    rows := substring(rec."QUERY PLAN" FROM ' rows=([[:digit:]]+)');
    EXIT WHEN rows IS NOT NULL;
  END LOOP;
  RETURN rows;
END;
$$ LANGUAGE plpgsql VOLATILE STRICT;

然后像这样使用它

SELECT count_estimate('SELECT * FROM companies WHERE status = ''Active''');

为了使用该count_estimate功能,我需要(我认为?)呈现 Peristent/Equeleto 生成的查询,但是当我尝试使用 呈现查询时renderQuerySelect,我得到类似这样的结果

SELECT "companies"."id", "companies"."name", "companies"."status"
FROM "companies"
WHERE "companies"."status" IN (?)
; [PersistText "Active"]

这当然不能塞进count_estimate,因为它会在?占位符上出现语法错误。我也不能天真地替换?with "Active",因为它会在第一个双引号上出现语法错误。

如何以我的count_estimate函数可以接受的方式呈现查询?

我试过这样的东西,但它在运行时失败

getEstimate :: (Text, [PersistValue]) -> DB [Single Int]
getEstimate (query, params) = rawSql [st|
  SELECT count_estimate('#{query}');
  |] params
4

1 回答 1

1

我设法弄清楚(大部分)。

PersistValue这是在查询和参数中转义单引号的问题。我现在正在这样做,但是需要重新添加转义,否则我认为它会产生 SQL 注入漏洞。我可能还需要以PersistValue某种特定方式处理其他构造函数,但我还没有遇到问题。

import qualified Data.Text as T
import qualified Database.Persist as P

getEstimate :: (Text, [PersistValue]) -> DB (Maybe Int)
getEstimate (query, params) = fmap unSingle . listToMaybe <$> rawSql [st|
  SELECT count_estimate('#{T.replace "'" "''" query}');
  |] (map replace' params)
  where literal a = PersistLiteral_ P.Unescaped ("''" <> a <> "''")
        replace' = \case
          PersistText t -> literal $ encodeUtf8 t
          PersistDay  d -> literal $ encodeUtf8 $ pack $ showGregorian d
          a             -> a
于 2021-12-22T21:01:04.130 回答