perf: change Text queries to ByteString
Improves performance by not utf8 encoding the whole query with encodeUtf8. Only certain parts. It's also a gradual step needed to use the Snippet type from hasql-dynamic-statements.
This commit is contained in:
committed by
Steve Chavez
parent
7e3e19acbb
commit
d1d0c6772a
@@ -187,7 +187,7 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
, Just $ contentRangeH 1 0 $ if shouldCount then Just queryTotal else Nothing
|
, Just $ contentRangeH 1 0 $ if shouldCount then Just queryTotal else Nothing
|
||||||
, if null pkCols && isNothing (iOnConflict apiRequest)
|
, if null pkCols && isNothing (iOnConflict apiRequest)
|
||||||
then Nothing
|
then Nothing
|
||||||
else (\x -> ("Preference-Applied", encodeUtf8 (show x))) <$> iPreferResolution apiRequest
|
else (\x -> ("Preference-Applied", BS.pack (show x))) <$> iPreferResolution apiRequest
|
||||||
] ++ ctHeaders)) (unwrapGucHeader <$> ghdrs)
|
] ++ ctHeaders)) (unwrapGucHeader <$> ghdrs)
|
||||||
if contentType == CTSingularJSON && queryTotal /= 1
|
if contentType == CTSingularJSON && queryTotal /= 1
|
||||||
then do
|
then do
|
||||||
|
|||||||
@@ -56,13 +56,14 @@ readRequest schema rootTableName maxRows allRels apiRequest =
|
|||||||
rootWithRels :: Schema -> TableName -> [Relation] -> Action -> (QualifiedIdentifier, [Relation])
|
rootWithRels :: Schema -> TableName -> [Relation] -> Action -> (QualifiedIdentifier, [Relation])
|
||||||
rootWithRels schema rootTableName allRels action = case action of
|
rootWithRels schema rootTableName allRels action = case action of
|
||||||
ActionRead _ -> (QualifiedIdentifier schema rootTableName, allRels) -- normal read case
|
ActionRead _ -> (QualifiedIdentifier schema rootTableName, allRels) -- normal read case
|
||||||
_ -> (QualifiedIdentifier mempty sourceCTEName, mapMaybe toSourceRel allRels ++ allRels) -- mutation cases and calling proc
|
_ -> (QualifiedIdentifier mempty _sourceCTEName, mapMaybe toSourceRel allRels ++ allRels) -- mutation cases and calling proc
|
||||||
where
|
where
|
||||||
|
_sourceCTEName = decodeUtf8 sourceCTEName
|
||||||
-- To enable embedding in the sourceCTEName cases we need to replace the foreign key tableName in the Relation
|
-- To enable embedding in the sourceCTEName cases we need to replace the foreign key tableName in the Relation
|
||||||
-- with {sourceCTEName}. This way findRel can find relationships with sourceCTEName.
|
-- with {sourceCTEName}. This way findRel can find relationships with sourceCTEName.
|
||||||
toSourceRel :: Relation -> Maybe Relation
|
toSourceRel :: Relation -> Maybe Relation
|
||||||
toSourceRel r@Relation{relTable=t}
|
toSourceRel r@Relation{relTable=t}
|
||||||
| rootTableName == tableName t = Just $ r {relTable=t {tableName=sourceCTEName}}
|
| rootTableName == tableName t = Just $ r {relTable=t {tableName=_sourceCTEName}}
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
|
|
||||||
-- Build the initial tree with a Depth attribute so when a self join occurs we can differentiate the parent and child tables by having
|
-- Build the initial tree with a Depth attribute so when a self join occurs we can differentiate the parent and child tables by having
|
||||||
@@ -228,7 +229,7 @@ getJoinConditions previousAlias newAlias (Relation Table{tableSchema=tSchema, ta
|
|||||||
-- if this happens remove the schema `FROM "schema"."{sourceCTEName}"` and use only the
|
-- if this happens remove the schema `FROM "schema"."{sourceCTEName}"` and use only the
|
||||||
-- `FROM "{sourceCTEName}"`. If the schema remains the FROM would be invalid.
|
-- `FROM "{sourceCTEName}"`. If the schema remains the FROM would be invalid.
|
||||||
removeSourceCTESchema :: Schema -> TableName -> QualifiedIdentifier
|
removeSourceCTESchema :: Schema -> TableName -> QualifiedIdentifier
|
||||||
removeSourceCTESchema schema tbl = QualifiedIdentifier (if tbl == sourceCTEName then mempty else schema) tbl
|
removeSourceCTESchema schema tbl = QualifiedIdentifier (if tbl == decodeUtf8 sourceCTEName then mempty else schema) tbl
|
||||||
|
|
||||||
addFiltersOrdersRanges :: ApiRequest -> ReadRequest -> Either ApiRequestError ReadRequest
|
addFiltersOrdersRanges :: ApiRequest -> ReadRequest -> Either ApiRequestError ReadRequest
|
||||||
addFiltersOrdersRanges apiRequest rReq = do
|
addFiltersOrdersRanges apiRequest rReq = do
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ Any function that outputs a SqlFragment should be in this module.
|
|||||||
-}
|
-}
|
||||||
module PostgREST.Private.QueryFragment where
|
module PostgREST.Private.QueryFragment where
|
||||||
|
|
||||||
|
import qualified Data.ByteString.Char8 as BS (intercalate,
|
||||||
|
pack, unwords)
|
||||||
import qualified Data.HashMap.Strict as HM
|
import qualified Data.HashMap.Strict as HM
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.Text (intercalate,
|
import qualified Data.Text as T (intercalate,
|
||||||
isInfixOf, replace,
|
isInfixOf, map,
|
||||||
toLower)
|
null, replace,
|
||||||
import qualified Data.Text as T (map, null,
|
takeWhile,
|
||||||
takeWhile)
|
toLower)
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import Protolude hiding (cast,
|
import Protolude hiding (cast,
|
||||||
intercalate, replace,
|
intercalate, replace,
|
||||||
@@ -36,7 +38,7 @@ ignoredBody = "pgrst_ignored_body AS (SELECT $1::text) "
|
|||||||
-- We do this in SQL to avoid processing the JSON in application code
|
-- We do this in SQL to avoid processing the JSON in application code
|
||||||
normalizedBody :: SqlFragment
|
normalizedBody :: SqlFragment
|
||||||
normalizedBody =
|
normalizedBody =
|
||||||
unwords [
|
BS.unwords [
|
||||||
"pgrst_payload AS (SELECT $1::json AS json_data),",
|
"pgrst_payload AS (SELECT $1::json AS json_data),",
|
||||||
"pgrst_body AS (",
|
"pgrst_body AS (",
|
||||||
"SELECT",
|
"SELECT",
|
||||||
@@ -49,17 +51,20 @@ normalizedBody =
|
|||||||
selectBody :: SqlFragment
|
selectBody :: SqlFragment
|
||||||
selectBody = "(SELECT val FROM pgrst_body)"
|
selectBody = "(SELECT val FROM pgrst_body)"
|
||||||
|
|
||||||
pgFmtLit :: SqlFragment -> SqlFragment
|
pgFmtLit :: Text -> SqlFragment
|
||||||
pgFmtLit x =
|
pgFmtLit x =
|
||||||
let trimmed = trimNullChars x
|
let trimmed = trimNullChars x
|
||||||
escaped = "'" <> replace "'" "''" trimmed <> "'"
|
escaped = "'" <> T.replace "'" "''" trimmed <> "'"
|
||||||
slashed = replace "\\" "\\\\" escaped in
|
slashed = T.replace "\\" "\\\\" escaped in
|
||||||
if "\\" `isInfixOf` escaped
|
encodeUtf8 $ if "\\" `T.isInfixOf` escaped
|
||||||
then "E" <> slashed
|
then "E" <> slashed
|
||||||
else slashed
|
else slashed
|
||||||
|
|
||||||
pgFmtIdent :: SqlFragment -> SqlFragment
|
pgFmtIdent :: Text -> SqlFragment
|
||||||
pgFmtIdent x = "\"" <> replace "\"" "\"\"" (trimNullChars $ toS x) <> "\""
|
pgFmtIdent x = encodeUtf8 $ "\"" <> T.replace "\"" "\"\"" (trimNullChars x) <> "\""
|
||||||
|
|
||||||
|
trimNullChars :: Text -> Text
|
||||||
|
trimNullChars = T.takeWhile (/= '\x0')
|
||||||
|
|
||||||
asCsvF :: SqlFragment
|
asCsvF :: SqlFragment
|
||||||
asCsvF = asCsvHeaderF <> " || '\n' || " <> asCsvBodyF
|
asCsvF = asCsvHeaderF <> " || '\n' || " <> asCsvBodyF
|
||||||
@@ -92,16 +97,16 @@ locationF pKeys = [qc|(
|
|||||||
WHERE json_data.key IN ('{fmtPKeys}')
|
WHERE json_data.key IN ('{fmtPKeys}')
|
||||||
)|]
|
)|]
|
||||||
where
|
where
|
||||||
fmtPKeys = intercalate "','" pKeys
|
fmtPKeys = T.intercalate "','" pKeys
|
||||||
|
|
||||||
fromQi :: QualifiedIdentifier -> SqlFragment
|
fromQi :: QualifiedIdentifier -> SqlFragment
|
||||||
fromQi t = (if s == "" then "" else pgFmtIdent s <> ".") <> pgFmtIdent n
|
fromQi t = (if T.null s then mempty else pgFmtIdent s <> ".") <> pgFmtIdent n
|
||||||
where
|
where
|
||||||
n = qiName t
|
n = qiName t
|
||||||
s = qiSchema t
|
s = qiSchema t
|
||||||
|
|
||||||
emptyOnFalse :: Text -> Bool -> Text
|
emptyOnFalse :: SqlFragment -> Bool -> SqlFragment
|
||||||
emptyOnFalse val cond = if cond then "" else val
|
emptyOnFalse val cond = if cond then mempty else val
|
||||||
|
|
||||||
pgFmtColumn :: QualifiedIdentifier -> Text -> SqlFragment
|
pgFmtColumn :: QualifiedIdentifier -> Text -> SqlFragment
|
||||||
pgFmtColumn table "*" = fromQi table <> ".*"
|
pgFmtColumn table "*" = fromQi table <> ".*"
|
||||||
@@ -112,13 +117,13 @@ pgFmtField table (c, jp) = pgFmtColumn table c <> pgFmtJsonPath jp
|
|||||||
|
|
||||||
pgFmtSelectItem :: QualifiedIdentifier -> SelectItem -> SqlFragment
|
pgFmtSelectItem :: QualifiedIdentifier -> SelectItem -> SqlFragment
|
||||||
pgFmtSelectItem table (f@(fName, jp), Nothing, alias, _) = pgFmtField table f <> pgFmtAs fName jp alias
|
pgFmtSelectItem table (f@(fName, jp), Nothing, alias, _) = pgFmtField table f <> pgFmtAs fName jp alias
|
||||||
pgFmtSelectItem table (f@(fName, jp), Just cast, alias, _) = "CAST (" <> pgFmtField table f <> " AS " <> cast <> " )" <> pgFmtAs fName jp alias
|
pgFmtSelectItem table (f@(fName, jp), Just cast, alias, _) = "CAST (" <> pgFmtField table f <> " AS " <> encodeUtf8 cast <> " )" <> pgFmtAs fName jp alias
|
||||||
|
|
||||||
pgFmtOrderTerm :: QualifiedIdentifier -> OrderTerm -> SqlFragment
|
pgFmtOrderTerm :: QualifiedIdentifier -> OrderTerm -> SqlFragment
|
||||||
pgFmtOrderTerm qi ot = unwords [
|
pgFmtOrderTerm qi ot = BS.unwords [
|
||||||
toS . pgFmtField qi $ otTerm ot,
|
pgFmtField qi $ otTerm ot,
|
||||||
maybe "" show $ otDirection ot,
|
BS.pack $ maybe mempty show $ otDirection ot,
|
||||||
maybe "" show $ otNullOrder ot]
|
BS.pack $ maybe mempty show $ otNullOrder ot]
|
||||||
|
|
||||||
pgFmtFilter :: QualifiedIdentifier -> Filter -> SqlFragment
|
pgFmtFilter :: QualifiedIdentifier -> Filter -> SqlFragment
|
||||||
pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper of
|
pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper of
|
||||||
@@ -131,39 +136,39 @@ pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper
|
|||||||
In vals -> pgFmtField table fld <> " " <>
|
In vals -> pgFmtField table fld <> " " <>
|
||||||
let emptyValForIn = "= any('{}') " in -- Workaround because for postgresql "col IN ()" is invalid syntax, we instead do "col = any('{}')"
|
let emptyValForIn = "= any('{}') " in -- Workaround because for postgresql "col IN ()" is invalid syntax, we instead do "col = any('{}')"
|
||||||
case (&&) (length vals == 1) . T.null <$> headMay vals of
|
case (&&) (length vals == 1) . T.null <$> headMay vals of
|
||||||
Just False -> sqlOperator "in" <> "(" <> intercalate ", " (map unknownLiteral vals) <> ") "
|
Just False -> sqlOperator "in" <> "(" <> BS.intercalate ", " (unknownLiteral <$> vals) <> ") "
|
||||||
Just True -> emptyValForIn
|
Just True -> emptyValForIn
|
||||||
Nothing -> emptyValForIn
|
Nothing -> emptyValForIn
|
||||||
|
|
||||||
Fts op lang val ->
|
Fts op lang val ->
|
||||||
pgFmtFieldOp op
|
pgFmtFieldOp op
|
||||||
<> "("
|
<> "("
|
||||||
<> maybe "" ((<> ", ") . pgFmtLit) lang
|
<> maybe mempty ((<> ", ") . pgFmtLit) lang
|
||||||
<> unknownLiteral val
|
<> unknownLiteral val
|
||||||
<> ") "
|
<> ") "
|
||||||
where
|
where
|
||||||
pgFmtFieldOp op = pgFmtField table fld <> " " <> sqlOperator op
|
pgFmtFieldOp op = pgFmtField table fld <> " " <> sqlOperator op
|
||||||
sqlOperator o = HM.lookupDefault "=" o operators
|
sqlOperator o = HM.lookupDefault "=" o operators
|
||||||
notOp = if hasNot then "NOT" else ""
|
notOp = if hasNot then "NOT" else mempty
|
||||||
star c = if c == '*' then '%' else c
|
star c = if c == '*' then '%' else c
|
||||||
unknownLiteral = (<> "::unknown ") . pgFmtLit
|
unknownLiteral = (<> "::unknown ") . pgFmtLit
|
||||||
whiteList :: Text -> SqlFragment
|
whiteList :: Text -> SqlFragment
|
||||||
whiteList v = fromMaybe
|
whiteList v = maybe
|
||||||
(toS (pgFmtLit v) <> "::unknown ")
|
(pgFmtLit v <> "::unknown") encodeUtf8
|
||||||
(find ((==) . toLower $ v) ["null","true","false"])
|
(find ((==) . T.toLower $ v) ["null","true","false"])
|
||||||
|
|
||||||
pgFmtJoinCondition :: JoinCondition -> SqlFragment
|
pgFmtJoinCondition :: JoinCondition -> SqlFragment
|
||||||
pgFmtJoinCondition (JoinCondition (qi1, col1) (qi2, col2)) =
|
pgFmtJoinCondition (JoinCondition (qi1, col1) (qi2, col2)) =
|
||||||
pgFmtColumn qi1 col1 <> " = " <> pgFmtColumn qi2 col2
|
pgFmtColumn qi1 col1 <> " = " <> pgFmtColumn qi2 col2
|
||||||
|
|
||||||
pgFmtLogicTree :: QualifiedIdentifier -> LogicTree -> SqlFragment
|
pgFmtLogicTree :: QualifiedIdentifier -> LogicTree -> SqlFragment
|
||||||
pgFmtLogicTree qi (Expr hasNot op forest) = notOp <> " (" <> intercalate (" " <> show op <> " ") (pgFmtLogicTree qi <$> forest) <> ")"
|
pgFmtLogicTree qi (Expr hasNot op forest) = notOp <> " (" <> BS.intercalate (" " <> BS.pack (show op) <> " ") (pgFmtLogicTree qi <$> forest) <> ")"
|
||||||
where notOp = if hasNot then "NOT" else ""
|
where notOp = if hasNot then "NOT" else mempty
|
||||||
pgFmtLogicTree qi (Stmnt flt) = pgFmtFilter qi flt
|
pgFmtLogicTree qi (Stmnt flt) = pgFmtFilter qi flt
|
||||||
|
|
||||||
pgFmtJsonPath :: JsonPath -> SqlFragment
|
pgFmtJsonPath :: JsonPath -> SqlFragment
|
||||||
pgFmtJsonPath = \case
|
pgFmtJsonPath = \case
|
||||||
[] -> ""
|
[] -> mempty
|
||||||
(JArrow x:xs) -> "->" <> pgFmtJsonOperand x <> pgFmtJsonPath xs
|
(JArrow x:xs) -> "->" <> pgFmtJsonOperand x <> pgFmtJsonPath xs
|
||||||
(J2Arrow x:xs) -> "->>" <> pgFmtJsonOperand x <> pgFmtJsonPath xs
|
(J2Arrow x:xs) -> "->>" <> pgFmtJsonOperand x <> pgFmtJsonPath xs
|
||||||
where
|
where
|
||||||
@@ -171,7 +176,7 @@ pgFmtJsonPath = \case
|
|||||||
pgFmtJsonOperand (JIdx i) = pgFmtLit i <> "::int"
|
pgFmtJsonOperand (JIdx i) = pgFmtLit i <> "::int"
|
||||||
|
|
||||||
pgFmtAs :: FieldName -> JsonPath -> Maybe Alias -> SqlFragment
|
pgFmtAs :: FieldName -> JsonPath -> Maybe Alias -> SqlFragment
|
||||||
pgFmtAs _ [] Nothing = ""
|
pgFmtAs _ [] Nothing = mempty
|
||||||
pgFmtAs fName jp Nothing = case jOp <$> lastMay jp of
|
pgFmtAs fName jp Nothing = case jOp <$> lastMay jp of
|
||||||
Just (JKey key) -> " AS " <> pgFmtIdent key
|
Just (JKey key) -> " AS " <> pgFmtIdent key
|
||||||
Just (JIdx _) -> " AS " <> pgFmtIdent (fromMaybe fName lastKey)
|
Just (JIdx _) -> " AS " <> pgFmtIdent (fromMaybe fName lastKey)
|
||||||
@@ -179,12 +184,9 @@ pgFmtAs fName jp Nothing = case jOp <$> lastMay jp of
|
|||||||
-- `select=data->1->mycol->>2`, we need to show the result as [ {"mycol": ..}, {"mycol": ..} ]
|
-- `select=data->1->mycol->>2`, we need to show the result as [ {"mycol": ..}, {"mycol": ..} ]
|
||||||
-- `select=data->3`, we need to show the result as [ {"data": ..}, {"data": ..} ]
|
-- `select=data->3`, we need to show the result as [ {"data": ..}, {"data": ..} ]
|
||||||
where lastKey = jVal <$> find (\case JKey{} -> True; _ -> False) (jOp <$> reverse jp)
|
where lastKey = jVal <$> find (\case JKey{} -> True; _ -> False) (jOp <$> reverse jp)
|
||||||
Nothing -> ""
|
Nothing -> mempty
|
||||||
pgFmtAs _ _ (Just alias) = " AS " <> pgFmtIdent alias
|
pgFmtAs _ _ (Just alias) = " AS " <> pgFmtIdent alias
|
||||||
|
|
||||||
trimNullChars :: Text -> Text
|
|
||||||
trimNullChars = T.takeWhile (/= '\x0')
|
|
||||||
|
|
||||||
countF :: SqlQuery -> Bool -> (SqlFragment, SqlFragment)
|
countF :: SqlQuery -> Bool -> (SqlFragment, SqlFragment)
|
||||||
countF countQuery shouldCount =
|
countF countQuery shouldCount =
|
||||||
if shouldCount
|
if shouldCount
|
||||||
@@ -199,21 +201,21 @@ returningF :: QualifiedIdentifier -> [FieldName] -> SqlFragment
|
|||||||
returningF qi returnings =
|
returningF qi returnings =
|
||||||
if null returnings
|
if null returnings
|
||||||
then "RETURNING 1" -- For mutation cases where there's no ?select, we return 1 to know how many rows were modified
|
then "RETURNING 1" -- For mutation cases where there's no ?select, we return 1 to know how many rows were modified
|
||||||
else "RETURNING " <> intercalate ", " (pgFmtColumn qi <$> returnings)
|
else "RETURNING " <> BS.intercalate ", " (pgFmtColumn qi <$> returnings)
|
||||||
|
|
||||||
responseHeadersF :: PgVersion -> SqlFragment
|
responseHeadersF :: PgVersion -> SqlFragment
|
||||||
responseHeadersF pgVer =
|
responseHeadersF pgVer =
|
||||||
if pgVer >= pgVersion96
|
if pgVer >= pgVersion96
|
||||||
then currentSettingF "response.headers"
|
then currentSettingF "response.headers"
|
||||||
else "null" :: Text
|
else "null"
|
||||||
|
|
||||||
responseStatusF :: PgVersion -> SqlFragment
|
responseStatusF :: PgVersion -> SqlFragment
|
||||||
responseStatusF pgVer =
|
responseStatusF pgVer =
|
||||||
if pgVer >= pgVersion96
|
if pgVer >= pgVersion96
|
||||||
then currentSettingF "response.status"
|
then currentSettingF "response.status"
|
||||||
else "null" :: Text
|
else "null"
|
||||||
|
|
||||||
currentSettingF :: SqlFragment -> SqlFragment
|
currentSettingF :: Text -> SqlFragment
|
||||||
currentSettingF setting =
|
currentSettingF setting =
|
||||||
-- nullif is used because of https://gist.github.com/steve-chavez/8d7033ea5655096903f3b52f8ed09a15
|
-- nullif is used because of https://gist.github.com/steve-chavez/8d7033ea5655096903f3b52f8ed09a15
|
||||||
"nullif(current_setting(" <> pgFmtLit setting <> ", true), '')"
|
"nullif(current_setting(" <> pgFmtLit setting <> ", true), '')"
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ module PostgREST.QueryBuilder (
|
|||||||
, setLocalSearchPathQuery
|
, setLocalSearchPathQuery
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Set as S
|
import qualified Data.ByteString.Char8 as BS
|
||||||
|
import qualified Data.Set as S
|
||||||
|
|
||||||
import Data.Text (intercalate)
|
|
||||||
import Data.Tree (Tree (..))
|
import Data.Tree (Tree (..))
|
||||||
|
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
@@ -36,14 +36,14 @@ import Protolude hiding (cast, intercalate,
|
|||||||
|
|
||||||
readRequestToQuery :: ReadRequest -> SqlQuery
|
readRequestToQuery :: ReadRequest -> SqlQuery
|
||||||
readRequestToQuery (Node (Select colSelects mainQi tblAlias implJoins logicForest joinConditions_ ordts range, _) forest) =
|
readRequestToQuery (Node (Select colSelects mainQi tblAlias implJoins logicForest joinConditions_ ordts range, _) forest) =
|
||||||
unwords [
|
BS.unwords [
|
||||||
"SELECT " <> intercalate ", " (map (pgFmtSelectItem qi) colSelects ++ selects),
|
"SELECT " <> BS.intercalate ", " (map (pgFmtSelectItem qi) colSelects ++ selects),
|
||||||
"FROM " <> intercalate ", " (tabl : implJs),
|
"FROM " <> BS.intercalate ", " (tabl : implJs),
|
||||||
unwords joins,
|
BS.unwords joins,
|
||||||
("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) logicForest ++ map pgFmtJoinCondition joinConditions_))
|
("WHERE " <> BS.intercalate " AND " (map (pgFmtLogicTree qi) logicForest ++ map pgFmtJoinCondition joinConditions_))
|
||||||
`emptyOnFalse` (null logicForest && null joinConditions_),
|
`emptyOnFalse` (null logicForest && null joinConditions_),
|
||||||
("ORDER BY " <> intercalate ", " (map (pgFmtOrderTerm qi) ordts)) `emptyOnFalse` null ordts,
|
("ORDER BY " <> BS.intercalate ", " (map (pgFmtOrderTerm qi) ordts)) `emptyOnFalse` null ordts,
|
||||||
("LIMIT " <> maybe "ALL" show (rangeLimit range) <> " OFFSET " <> show (rangeOffset range)) `emptyOnFalse` (range == allRange)
|
("LIMIT " <> maybe "ALL" (BS.pack . show) (rangeLimit range) <> " OFFSET " <> (BS.pack . show) (rangeOffset range)) `emptyOnFalse` (range == allRange)
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
implJs = fromQi <$> implJoins
|
implJs = fromQi <$> implJoins
|
||||||
@@ -71,27 +71,27 @@ getJoinsSelects (Node (_, (_, Nothing, _, _, _)) _) _ = ([], [])
|
|||||||
|
|
||||||
mutateRequestToQuery :: MutateRequest -> SqlQuery
|
mutateRequestToQuery :: MutateRequest -> SqlQuery
|
||||||
mutateRequestToQuery (Insert mainQi iCols onConflct putConditions returnings) =
|
mutateRequestToQuery (Insert mainQi iCols onConflct putConditions returnings) =
|
||||||
unwords [
|
BS.unwords [
|
||||||
"WITH " <> normalizedBody,
|
"WITH " <> normalizedBody,
|
||||||
"INSERT INTO ", fromQi mainQi, if S.null iCols then " " else "(" <> cols <> ")",
|
"INSERT INTO ", fromQi mainQi, if S.null iCols then " " else "(" <> cols <> ")",
|
||||||
unwords [
|
BS.unwords [
|
||||||
"SELECT " <> cols <> " FROM",
|
"SELECT " <> cols <> " FROM",
|
||||||
"json_populate_recordset", "(null::", fromQi mainQi, ", " <> selectBody <> ") _",
|
"json_populate_recordset", "(null::", fromQi mainQi, ", " <> selectBody <> ") _",
|
||||||
-- Only used for PUT
|
-- Only used for PUT
|
||||||
("WHERE " <> intercalate " AND " (pgFmtLogicTree (QualifiedIdentifier mempty "_") <$> putConditions)) `emptyOnFalse` null putConditions],
|
("WHERE " <> BS.intercalate " AND " (pgFmtLogicTree (QualifiedIdentifier mempty "_") <$> putConditions)) `emptyOnFalse` null putConditions],
|
||||||
maybe "" (\(oncDo, oncCols) -> (
|
maybe "" (\(oncDo, oncCols) -> (
|
||||||
"ON CONFLICT(" <> intercalate ", " (pgFmtIdent <$> oncCols) <> ") " <> case oncDo of
|
"ON CONFLICT(" <> BS.intercalate ", " (pgFmtIdent <$> oncCols) <> ") " <> case oncDo of
|
||||||
IgnoreDuplicates ->
|
IgnoreDuplicates ->
|
||||||
"DO NOTHING"
|
"DO NOTHING"
|
||||||
MergeDuplicates ->
|
MergeDuplicates ->
|
||||||
if S.null iCols
|
if S.null iCols
|
||||||
then "DO NOTHING"
|
then "DO NOTHING"
|
||||||
else "DO UPDATE SET " <> intercalate ", " (pgFmtIdent <> const " = EXCLUDED." <> pgFmtIdent <$> S.toList iCols)
|
else "DO UPDATE SET " <> BS.intercalate ", " (pgFmtIdent <> const " = EXCLUDED." <> pgFmtIdent <$> S.toList iCols)
|
||||||
) `emptyOnFalse` null oncCols) onConflct,
|
) `emptyOnFalse` null oncCols) onConflct,
|
||||||
returningF mainQi returnings
|
returningF mainQi returnings
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
cols = intercalate ", " $ pgFmtIdent <$> S.toList iCols
|
cols = BS.intercalate ", " $ pgFmtIdent <$> S.toList iCols
|
||||||
mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
|
mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
|
||||||
if S.null uCols
|
if S.null uCols
|
||||||
-- if there are no columns we cannot do UPDATE table SET {empty}, it'd be invalid syntax
|
-- if there are no columns we cannot do UPDATE table SET {empty}, it'd be invalid syntax
|
||||||
@@ -99,30 +99,30 @@ mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
|
|||||||
-- the select has to be based on "returnings" to make computed overloaded functions not throw
|
-- the select has to be based on "returnings" to make computed overloaded functions not throw
|
||||||
then "WITH " <> ignoredBody <> "SELECT " <> empty_body_returned_columns <> " FROM " <> fromQi mainQi <> " WHERE false"
|
then "WITH " <> ignoredBody <> "SELECT " <> empty_body_returned_columns <> " FROM " <> fromQi mainQi <> " WHERE false"
|
||||||
else
|
else
|
||||||
unwords [
|
BS.unwords [
|
||||||
"WITH " <> normalizedBody,
|
"WITH " <> normalizedBody,
|
||||||
"UPDATE " <> fromQi mainQi <> " SET " <> cols,
|
"UPDATE " <> fromQi mainQi <> " SET " <> cols,
|
||||||
"FROM (SELECT * FROM json_populate_recordset", "(null::", fromQi mainQi, ", " <> selectBody <> ")) _ ",
|
"FROM (SELECT * FROM json_populate_recordset", "(null::", fromQi mainQi, ", " <> selectBody <> ")) _ ",
|
||||||
("WHERE " <> intercalate " AND " (pgFmtLogicTree mainQi <$> logicForest)) `emptyOnFalse` null logicForest,
|
("WHERE " <> BS.intercalate " AND " (pgFmtLogicTree mainQi <$> logicForest)) `emptyOnFalse` null logicForest,
|
||||||
returningF mainQi returnings
|
returningF mainQi returnings
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
cols = intercalate ", " (pgFmtIdent <> const " = _." <> pgFmtIdent <$> S.toList uCols)
|
cols = BS.intercalate ", " (pgFmtIdent <> const " = _." <> pgFmtIdent <$> S.toList uCols)
|
||||||
empty_body_returned_columns :: SqlFragment
|
empty_body_returned_columns :: SqlFragment
|
||||||
empty_body_returned_columns
|
empty_body_returned_columns
|
||||||
| null returnings = "NULL"
|
| null returnings = "NULL"
|
||||||
| otherwise = intercalate ", " (pgFmtColumn (QualifiedIdentifier mempty $ qiName mainQi) <$> returnings)
|
| otherwise = BS.intercalate ", " (pgFmtColumn (QualifiedIdentifier mempty $ qiName mainQi) <$> returnings)
|
||||||
mutateRequestToQuery (Delete mainQi logicForest returnings) =
|
mutateRequestToQuery (Delete mainQi logicForest returnings) =
|
||||||
unwords [
|
BS.unwords [
|
||||||
"WITH " <> ignoredBody,
|
"WITH " <> ignoredBody,
|
||||||
"DELETE FROM ", fromQi mainQi,
|
"DELETE FROM ", fromQi mainQi,
|
||||||
("WHERE " <> intercalate " AND " (map (pgFmtLogicTree mainQi) logicForest)) `emptyOnFalse` null logicForest,
|
("WHERE " <> BS.intercalate " AND " (map (pgFmtLogicTree mainQi) logicForest)) `emptyOnFalse` null logicForest,
|
||||||
returningF mainQi returnings
|
returningF mainQi returnings
|
||||||
]
|
]
|
||||||
|
|
||||||
requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> [FieldName] -> SqlQuery
|
requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> [FieldName] -> SqlQuery
|
||||||
requestToCallProcQuery qi pgArgs returnsScalar preferParams returnings =
|
requestToCallProcQuery qi pgArgs returnsScalar preferParams returnings =
|
||||||
unwords [
|
BS.unwords [
|
||||||
"WITH",
|
"WITH",
|
||||||
argsCTE,
|
argsCTE,
|
||||||
sourceBody ]
|
sourceBody ]
|
||||||
@@ -134,10 +134,10 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams returnings =
|
|||||||
| null pgArgs = (ignoredBody, "")
|
| null pgArgs = (ignoredBody, "")
|
||||||
| paramsAsSingleObject = ("pgrst_args AS (SELECT NULL)", "$1::json")
|
| paramsAsSingleObject = ("pgrst_args AS (SELECT NULL)", "$1::json")
|
||||||
| otherwise = (
|
| otherwise = (
|
||||||
unwords [
|
BS.unwords [
|
||||||
normalizedBody <> ",",
|
normalizedBody <> ",",
|
||||||
"pgrst_args AS (",
|
"pgrst_args AS (",
|
||||||
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtArgs (\a -> " " <> pgaType a) <> ")",
|
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtArgs (\a -> " " <> encodeUtf8 (pgaType a)) <> ")",
|
||||||
")"]
|
")"]
|
||||||
, if paramsAsMultipleObjects
|
, if paramsAsMultipleObjects
|
||||||
then fmtArgs (\a -> " := pgrst_args." <> pgFmtIdent (pgaName a))
|
then fmtArgs (\a -> " := pgrst_args." <> pgFmtIdent (pgaName a))
|
||||||
@@ -145,14 +145,14 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams returnings =
|
|||||||
)
|
)
|
||||||
|
|
||||||
fmtArgs :: (PgArg -> SqlFragment) -> SqlFragment
|
fmtArgs :: (PgArg -> SqlFragment) -> SqlFragment
|
||||||
fmtArgs argFrag = intercalate ", " ((\a -> pgFmtIdent (pgaName a) <> argFrag a) <$> pgArgs)
|
fmtArgs argFrag = BS.intercalate ", " ((\a -> pgFmtIdent (pgaName a) <> argFrag a) <$> pgArgs)
|
||||||
|
|
||||||
sourceBody :: SqlFragment
|
sourceBody :: SqlFragment
|
||||||
sourceBody
|
sourceBody
|
||||||
| paramsAsMultipleObjects =
|
| paramsAsMultipleObjects =
|
||||||
if returnsScalar
|
if returnsScalar
|
||||||
then "SELECT " <> callIt <> " AS pgrst_scalar FROM pgrst_args"
|
then "SELECT " <> callIt <> " AS pgrst_scalar FROM pgrst_args"
|
||||||
else unwords [ "SELECT pgrst_lat_args.*"
|
else BS.unwords [ "SELECT pgrst_lat_args.*"
|
||||||
, "FROM pgrst_args,"
|
, "FROM pgrst_args,"
|
||||||
, "LATERAL ( SELECT " <> returned_columns <> " FROM " <> callIt <> " ) pgrst_lat_args" ]
|
, "LATERAL ( SELECT " <> returned_columns <> " FROM " <> callIt <> " ) pgrst_lat_args" ]
|
||||||
| otherwise =
|
| otherwise =
|
||||||
@@ -166,7 +166,7 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams returnings =
|
|||||||
returned_columns :: SqlFragment
|
returned_columns :: SqlFragment
|
||||||
returned_columns
|
returned_columns
|
||||||
| null returnings = "*"
|
| null returnings = "*"
|
||||||
| otherwise = intercalate ", " (pgFmtColumn (QualifiedIdentifier mempty $ qiName qi) <$> returnings)
|
| otherwise = BS.intercalate ", " (pgFmtColumn (QualifiedIdentifier mempty $ qiName qi) <$> returnings)
|
||||||
|
|
||||||
|
|
||||||
-- | SQL query meant for COUNTing the root node of the Tree.
|
-- | SQL query meant for COUNTing the root node of the Tree.
|
||||||
@@ -175,14 +175,14 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams returnings =
|
|||||||
-- inside the FROM target.
|
-- inside the FROM target.
|
||||||
readRequestToCountQuery :: ReadRequest -> SqlQuery
|
readRequestToCountQuery :: ReadRequest -> SqlQuery
|
||||||
readRequestToCountQuery (Node (Select{from=qi, where_=logicForest}, _) _) =
|
readRequestToCountQuery (Node (Select{from=qi, where_=logicForest}, _) _) =
|
||||||
unwords [
|
BS.unwords [
|
||||||
"SELECT 1",
|
"SELECT 1",
|
||||||
"FROM " <> fromQi qi,
|
"FROM " <> fromQi qi,
|
||||||
("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) logicForest)) `emptyOnFalse` null logicForest
|
("WHERE " <> BS.intercalate " AND " (map (pgFmtLogicTree qi) logicForest)) `emptyOnFalse` null logicForest
|
||||||
]
|
]
|
||||||
|
|
||||||
limitedQuery :: SqlQuery -> Maybe Integer -> SqlQuery
|
limitedQuery :: SqlQuery -> Maybe Integer -> SqlQuery
|
||||||
limitedQuery query maxRows = query <> maybe mempty (\x -> " LIMIT " <> show x) maxRows
|
limitedQuery query maxRows = query <> maybe mempty (\x -> " LIMIT " <> BS.pack (show x)) maxRows
|
||||||
|
|
||||||
setLocalQuery :: Text -> (Text, Text) -> SqlQuery
|
setLocalQuery :: Text -> (Text, Text) -> SqlQuery
|
||||||
setLocalQuery prefix (k, v) =
|
setLocalQuery prefix (k, v) =
|
||||||
@@ -190,4 +190,4 @@ setLocalQuery prefix (k, v) =
|
|||||||
|
|
||||||
setLocalSearchPathQuery :: [Text] -> SqlQuery
|
setLocalSearchPathQuery :: [Text] -> SqlQuery
|
||||||
setLocalSearchPathQuery vals =
|
setLocalSearchPathQuery vals =
|
||||||
"SET LOCAL search_path = " <> intercalate ", " (pgFmtLit <$> vals) <> ";"
|
"SET LOCAL search_path = " <> BS.intercalate ", " (pgFmtLit <$> vals) <> ";"
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ createWriteStatement :: SqlQuery -> SqlQuery -> Bool -> Bool -> Bool ->
|
|||||||
PreferRepresentation -> [Text] -> PgVersion ->
|
PreferRepresentation -> [Text] -> PgVersion ->
|
||||||
H.Statement ByteString ResultsWithCount
|
H.Statement ByteString ResultsWithCount
|
||||||
createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys pgVer =
|
createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys pgVer =
|
||||||
unicodeStatement sql (param HE.unknown) decodeStandard True
|
H.Statement sql (param HE.unknown) decodeStandard True
|
||||||
where
|
where
|
||||||
sql = [qc|
|
sql = [qc|
|
||||||
WITH
|
WITH
|
||||||
@@ -62,7 +62,7 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys
|
|||||||
|
|
||||||
locF =
|
locF =
|
||||||
if isInsert && rep `elem` [Full, HeadersOnly]
|
if isInsert && rep `elem` [Full, HeadersOnly]
|
||||||
then unwords [
|
then BS.unwords [
|
||||||
"CASE WHEN pg_catalog.count(_postgrest_t) = 1",
|
"CASE WHEN pg_catalog.count(_postgrest_t) = 1",
|
||||||
"THEN coalesce(" <> locationF pKeys <> ", " <> noLocationF <> ")",
|
"THEN coalesce(" <> locationF pKeys <> ", " <> noLocationF <> ")",
|
||||||
"ELSE " <> noLocationF,
|
"ELSE " <> noLocationF,
|
||||||
@@ -87,7 +87,7 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys
|
|||||||
createReadStatement :: SqlQuery -> SqlQuery -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion ->
|
createReadStatement :: SqlQuery -> SqlQuery -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion ->
|
||||||
H.Statement () ResultsWithCount
|
H.Statement () ResultsWithCount
|
||||||
createReadStatement selectQuery countQuery isSingle countTotal asCsv binaryField pgVer =
|
createReadStatement selectQuery countQuery isSingle countTotal asCsv binaryField pgVer =
|
||||||
unicodeStatement sql HE.noParams decodeStandard False
|
H.Statement sql HE.noParams decodeStandard False
|
||||||
where
|
where
|
||||||
sql = [qc|
|
sql = [qc|
|
||||||
WITH
|
WITH
|
||||||
@@ -132,7 +132,7 @@ callProcStatement :: Bool -> SqlQuery -> SqlQuery -> SqlQuery -> Bool ->
|
|||||||
Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion ->
|
Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion ->
|
||||||
H.Statement ByteString ProcResults
|
H.Statement ByteString ProcResults
|
||||||
callProcStatement returnsScalar callProcQuery selectQuery countQuery countTotal isSingle asCsv asBinary multObjects binaryField pgVer =
|
callProcStatement returnsScalar callProcQuery selectQuery countQuery countTotal isSingle asCsv asBinary multObjects binaryField pgVer =
|
||||||
unicodeStatement sql (param HE.unknown) decodeProc True
|
H.Statement sql (param HE.unknown) decodeProc True
|
||||||
where
|
where
|
||||||
sql = [qc|
|
sql = [qc|
|
||||||
WITH {sourceCTEName} AS ({callProcQuery})
|
WITH {sourceCTEName} AS ({callProcQuery})
|
||||||
@@ -172,7 +172,7 @@ callProcStatement returnsScalar callProcQuery selectQuery countQuery countTotal
|
|||||||
|
|
||||||
createExplainStatement :: SqlQuery -> H.Statement () (Maybe Int64)
|
createExplainStatement :: SqlQuery -> H.Statement () (Maybe Int64)
|
||||||
createExplainStatement countQuery =
|
createExplainStatement countQuery =
|
||||||
unicodeStatement sql HE.noParams decodeExplain False
|
H.Statement sql HE.noParams decodeExplain False
|
||||||
where
|
where
|
||||||
sql = [qc| EXPLAIN (FORMAT JSON) {countQuery} |]
|
sql = [qc| EXPLAIN (FORMAT JSON) {countQuery} |]
|
||||||
-- |
|
-- |
|
||||||
@@ -188,9 +188,6 @@ createExplainStatement countQuery =
|
|||||||
let row = HD.singleRow $ column HD.bytea in
|
let row = HD.singleRow $ column HD.bytea in
|
||||||
(^? L.nth 0 . L.key "Plan" . L.key "Plan Rows" . L._Integral) <$> row
|
(^? L.nth 0 . L.key "Plan" . L.key "Plan Rows" . L._Integral) <$> row
|
||||||
|
|
||||||
unicodeStatement :: Text -> HE.Params a -> HD.Result b -> Bool -> H.Statement a b
|
|
||||||
unicodeStatement = H.Statement . encodeUtf8
|
|
||||||
|
|
||||||
decodeGucHeaders :: HD.Value (Either SimpleError [GucHeader])
|
decodeGucHeaders :: HD.Value (Either SimpleError [GucHeader])
|
||||||
decodeGucHeaders = first (const GucHeadersError) . JSON.eitherDecode . toS <$> HD.bytea
|
decodeGucHeaders = first (const GucHeadersError) . JSON.eitherDecode . toS <$> HD.bytea
|
||||||
|
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ decodeContentType ct = case BS.takeWhile (/= BS.c2w ';') ct of
|
|||||||
ct' -> CTOther ct'
|
ct' -> CTOther ct'
|
||||||
|
|
||||||
-- | A SQL query that can be executed independently
|
-- | A SQL query that can be executed independently
|
||||||
type SqlQuery = Text
|
type SqlQuery = ByteString
|
||||||
|
|
||||||
-- | A part of a SQL query that cannot be executed independently
|
-- | A part of a SQL query that cannot be executed independently
|
||||||
type SqlFragment = Text
|
type SqlFragment = ByteString
|
||||||
|
|
||||||
data PreferResolution = MergeDuplicates | IgnoreDuplicates deriving Eq
|
data PreferResolution = MergeDuplicates | IgnoreDuplicates deriving Eq
|
||||||
instance Show PreferResolution where
|
instance Show PreferResolution where
|
||||||
|
|||||||
+1
-1
@@ -68,7 +68,7 @@ exec pool input query =
|
|||||||
|
|
||||||
explainCost :: SqlQuery -> H.Statement ByteString (Maybe Int64)
|
explainCost :: SqlQuery -> H.Statement ByteString (Maybe Int64)
|
||||||
explainCost query =
|
explainCost query =
|
||||||
H.Statement (encodeUtf8 sql) (HE.param $ HE.nonNullable HE.unknown) decodeExplain False
|
H.Statement sql (HE.param $ HE.nonNullable HE.unknown) decodeExplain False
|
||||||
where
|
where
|
||||||
sql = "EXPLAIN (FORMAT JSON) " <> query
|
sql = "EXPLAIN (FORMAT JSON) " <> query
|
||||||
decodeExplain :: HD.Result (Maybe Int64)
|
decodeExplain :: HD.Result (Maybe Int64)
|
||||||
|
|||||||
Reference in New Issue
Block a user