refactor: move decoding to Query.hs
Towards solving https://github.com/PostgREST/postgrest/issues/3934. This makes the Statements internal module pure now it only returns Hasql.Snippet.
This commit is contained in:
committed by
Steve Chavez
parent
00d92aa474
commit
3e776213b7
+94
-13
@@ -1,22 +1,35 @@
|
|||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
-- TODO: This module shouldn't depend on SchemaCache
|
{-|
|
||||||
|
Module : PostgREST.Query
|
||||||
|
Description : PostgREST query executor
|
||||||
|
|
||||||
|
This module parametrizes, prepares, executes SQL queries and decodes their results.
|
||||||
|
|
||||||
|
TODO: This module shouldn't depend on SchemaCache
|
||||||
|
-}
|
||||||
module PostgREST.Query
|
module PostgREST.Query
|
||||||
( Query (..)
|
( Query (..)
|
||||||
, QueryResult (..)
|
, QueryResult (..)
|
||||||
|
, ResultSet (..)
|
||||||
, query
|
, query
|
||||||
, getSQLQuery
|
, getSQLQuery
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import Control.Lens ((^?))
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
import qualified Data.Aeson.KeyMap as KM
|
import qualified Data.Aeson.KeyMap as KM
|
||||||
import qualified Data.ByteString as BS
|
import qualified Data.Aeson.Lens as L
|
||||||
|
import qualified Data.ByteString as BS hiding
|
||||||
|
(break)
|
||||||
|
import qualified Data.ByteString.Char8 as BS
|
||||||
import qualified Data.HashMap.Strict as HM
|
import qualified Data.HashMap.Strict as HM
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Hasql.Decoders as HD
|
import qualified Hasql.Decoders as HD
|
||||||
import qualified Hasql.DynamicStatements.Snippet as SQL (Snippet)
|
import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql)
|
||||||
import qualified Hasql.DynamicStatements.Statement as SQL
|
import qualified Hasql.DynamicStatements.Statement as SQL
|
||||||
import qualified Hasql.Session as SQL (Session)
|
import qualified Hasql.Session as SQL (Session)
|
||||||
|
import qualified Hasql.Statement as SQL
|
||||||
import qualified Hasql.Transaction as SQL
|
import qualified Hasql.Transaction as SQL
|
||||||
import qualified Hasql.Transaction.Sessions as SQL
|
import qualified Hasql.Transaction.Sessions as SQL
|
||||||
|
|
||||||
@@ -47,7 +60,6 @@ import PostgREST.Plan (ActionPlan (..),
|
|||||||
InfoPlan (..),
|
InfoPlan (..),
|
||||||
InspectPlan (..))
|
InspectPlan (..))
|
||||||
import PostgREST.Plan.MutatePlan (MutatePlan (..))
|
import PostgREST.Plan.MutatePlan (MutatePlan (..))
|
||||||
import PostgREST.Query.Statements (ResultSet (..))
|
|
||||||
import PostgREST.SchemaCache (SchemaCache (..))
|
import PostgREST.SchemaCache (SchemaCache (..))
|
||||||
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
|
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
|
||||||
import PostgREST.SchemaCache.Routine (Routine (..), RoutineMap)
|
import PostgREST.SchemaCache.Routine (Routine (..), RoutineMap)
|
||||||
@@ -73,6 +85,27 @@ data QueryResult
|
|||||||
| MaybeDbResult InspectPlan (Maybe (TablesMap, RoutineMap, Maybe Text))
|
| MaybeDbResult InspectPlan (Maybe (TablesMap, RoutineMap, Maybe Text))
|
||||||
| NoDbResult InfoPlan
|
| NoDbResult InfoPlan
|
||||||
|
|
||||||
|
-- | Standard result set format used for all queries
|
||||||
|
data ResultSet
|
||||||
|
= RSStandard
|
||||||
|
{ rsTableTotal :: Maybe Int64
|
||||||
|
-- ^ count of all the table rows
|
||||||
|
, rsQueryTotal :: Int64
|
||||||
|
-- ^ count of the query rows
|
||||||
|
, rsLocation :: [(BS.ByteString, BS.ByteString)]
|
||||||
|
-- ^ The Location header(only used for inserts) is represented as a list of strings containing
|
||||||
|
-- variable bindings like @"k1=eq.42"@, or the empty list if there is no location header.
|
||||||
|
, rsBody :: BS.ByteString
|
||||||
|
-- ^ the aggregated body of the query
|
||||||
|
, rsGucHeaders :: Maybe BS.ByteString
|
||||||
|
-- ^ the HTTP headers to be added to the response
|
||||||
|
, rsGucStatus :: Maybe Text
|
||||||
|
-- ^ the HTTP status to be added to the response
|
||||||
|
, rsInserted :: Maybe Int64
|
||||||
|
-- ^ the number of rows inserted (Only used for upserts)
|
||||||
|
}
|
||||||
|
| RSPlan BS.ByteString -- ^ the plan of the query
|
||||||
|
|
||||||
query :: AppConfig -> AuthResult -> ApiRequest -> ActionPlan -> SchemaCache -> Query
|
query :: AppConfig -> AuthResult -> ApiRequest -> ActionPlan -> SchemaCache -> Query
|
||||||
query _ _ _ (NoDb x) _ = NoDbQuery $ NoDbResult x
|
query _ _ _ (NoDb x) _ = NoDbQuery $ NoDbResult x
|
||||||
query config AuthResult{..} apiReq (Db plan) sCache =
|
query config AuthResult{..} apiReq (Db plan) sCache =
|
||||||
@@ -106,7 +139,7 @@ actionQuery (DbCrud plan@WrappedReadPlan{..}) conf@AppConfig{..} apiReq@ApiReque
|
|||||||
(mainActionQuery, mainSQLQuery)
|
(mainActionQuery, mainSQLQuery)
|
||||||
where
|
where
|
||||||
countQuery = QueryBuilder.readPlanToCountQuery wrReadPlan
|
countQuery = QueryBuilder.readPlanToCountQuery wrReadPlan
|
||||||
(result, mainSQLQuery) = Statements.prepareRead
|
result@(SQL.Statement mainSQLQuery _ _ _) = SQL.dynamicallyParameterized (Statements.prepareRead
|
||||||
(QueryBuilder.readPlanToQuery wrReadPlan)
|
(QueryBuilder.readPlanToQuery wrReadPlan)
|
||||||
(if preferCount == Just EstimatedCount then
|
(if preferCount == Just EstimatedCount then
|
||||||
-- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed
|
-- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed
|
||||||
@@ -117,18 +150,23 @@ actionQuery (DbCrud plan@WrappedReadPlan{..}) conf@AppConfig{..} apiReq@ApiReque
|
|||||||
(shouldCount preferCount)
|
(shouldCount preferCount)
|
||||||
wrMedia
|
wrMedia
|
||||||
wrHandler
|
wrHandler
|
||||||
configDbPreparedStatements
|
) decodeIt configDbPreparedStatements
|
||||||
mainActionQuery = do
|
mainActionQuery = do
|
||||||
resultSet <- lift $ SQL.statement mempty result
|
resultSet <- lift $ SQL.statement mempty result
|
||||||
failNotSingular wrMedia resultSet
|
failNotSingular wrMedia resultSet
|
||||||
optionalRollback conf apiReq
|
optionalRollback conf apiReq
|
||||||
DbCrudResult plan <$> resultSetWTotal conf apiReq resultSet countQuery
|
DbCrudResult plan <$> resultSetWTotal conf apiReq resultSet countQuery
|
||||||
|
|
||||||
|
decodeIt :: HD.Result ResultSet
|
||||||
|
decodeIt = case wrMedia of
|
||||||
|
MTVndPlan{} -> planRow
|
||||||
|
_ -> HD.singleRow $ standardRow True
|
||||||
|
|
||||||
actionQuery (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ =
|
actionQuery (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ =
|
||||||
(mainActionQuery, mainSQLQuery)
|
(mainActionQuery, mainSQLQuery)
|
||||||
where
|
where
|
||||||
(isPut, isInsert, pkCols) = case mrMutatePlan of {Insert{where_,insPkCols} -> ((not . null) where_, True, insPkCols); _ -> (False,False, mempty);}
|
(isPut, isInsert, pkCols) = case mrMutatePlan of {Insert{where_,insPkCols} -> ((not . null) where_, True, insPkCols); _ -> (False,False, mempty);}
|
||||||
(result, mainSQLQuery) = Statements.prepareWrite
|
result@(SQL.Statement mainSQLQuery _ _ _) = SQL.dynamicallyParameterized (Statements.prepareWrite
|
||||||
(QueryBuilder.readPlanToQuery mrReadPlan)
|
(QueryBuilder.readPlanToQuery mrReadPlan)
|
||||||
(QueryBuilder.mutatePlanToQuery mrMutatePlan)
|
(QueryBuilder.mutatePlanToQuery mrMutatePlan)
|
||||||
isInsert
|
isInsert
|
||||||
@@ -137,8 +175,7 @@ actionQuery (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiReques
|
|||||||
mrHandler
|
mrHandler
|
||||||
preferRepresentation
|
preferRepresentation
|
||||||
preferResolution
|
preferResolution
|
||||||
pkCols
|
pkCols) decodeIt configDbPreparedStatements
|
||||||
configDbPreparedStatements
|
|
||||||
failMutation resultSet = case mrMutation of
|
failMutation resultSet = case mrMutation of
|
||||||
MutationCreate -> do
|
MutationCreate -> do
|
||||||
failNotSingular mrMedia resultSet
|
failNotSingular mrMedia resultSet
|
||||||
@@ -156,18 +193,23 @@ actionQuery (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiReques
|
|||||||
optionalRollback conf apiReq
|
optionalRollback conf apiReq
|
||||||
pure $ DbCrudResult plan resultSet
|
pure $ DbCrudResult plan resultSet
|
||||||
|
|
||||||
|
decodeIt :: HD.Result ResultSet
|
||||||
|
decodeIt = case mrMedia of
|
||||||
|
MTVndPlan{} -> planRow
|
||||||
|
_ -> fromMaybe (RSStandard Nothing 0 mempty mempty Nothing Nothing Nothing) <$> HD.rowMaybe (standardRow False)
|
||||||
|
|
||||||
actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ =
|
actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ =
|
||||||
(mainActionQuery, mainSQLQuery)
|
(mainActionQuery, mainSQLQuery)
|
||||||
where
|
where
|
||||||
(result, mainSQLQuery) = Statements.prepareCall
|
result@(SQL.Statement mainSQLQuery _ _ _) = SQL.dynamicallyParameterized (Statements.prepareCall
|
||||||
crProc
|
crProc
|
||||||
(QueryBuilder.callPlanToQuery crCallPlan)
|
(QueryBuilder.callPlanToQuery crCallPlan)
|
||||||
(QueryBuilder.readPlanToQuery crReadPlan)
|
(QueryBuilder.readPlanToQuery crReadPlan)
|
||||||
(QueryBuilder.readPlanToCountQuery crReadPlan)
|
(QueryBuilder.readPlanToCountQuery crReadPlan)
|
||||||
(shouldCount preferCount)
|
(shouldCount preferCount)
|
||||||
crMedia
|
crMedia
|
||||||
crHandler
|
crHandler) decodeIt configDbPreparedStatements
|
||||||
configDbPreparedStatements
|
|
||||||
mainActionQuery = do
|
mainActionQuery = do
|
||||||
resultSet <- lift $ SQL.statement mempty result
|
resultSet <- lift $ SQL.statement mempty result
|
||||||
optionalRollback conf apiReq
|
optionalRollback conf apiReq
|
||||||
@@ -175,6 +217,11 @@ actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{
|
|||||||
failExceedsMaxAffectedPref (preferMaxAffected,preferHandling) resultSet
|
failExceedsMaxAffectedPref (preferMaxAffected,preferHandling) resultSet
|
||||||
pure $ DbCallResult plan resultSet
|
pure $ DbCallResult plan resultSet
|
||||||
|
|
||||||
|
decodeIt :: HD.Result ResultSet
|
||||||
|
decodeIt = case crMedia of
|
||||||
|
MTVndPlan{} -> planRow
|
||||||
|
_ -> fromMaybe (RSStandard (Just 0) 0 mempty mempty Nothing Nothing Nothing) <$> HD.rowMaybe (standardRow True)
|
||||||
|
|
||||||
actionQuery (MaybeDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ sCache =
|
actionQuery (MaybeDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ sCache =
|
||||||
(mainActionQuery, mempty)
|
(mainActionQuery, mempty)
|
||||||
where
|
where
|
||||||
@@ -225,9 +272,16 @@ resultSetWTotal AppConfig{..} ApiRequest{iPreferences=Preferences{..}} rs@RSStan
|
|||||||
return rs
|
return rs
|
||||||
where
|
where
|
||||||
explain =
|
explain =
|
||||||
lift . SQL.statement mempty . Statements.preparePlanRows countQuery $
|
lift . SQL.statement mempty $
|
||||||
|
SQL.dynamicallyParameterized (Statements.preparePlanRows countQuery)
|
||||||
|
decodeIt
|
||||||
configDbPreparedStatements
|
configDbPreparedStatements
|
||||||
|
|
||||||
|
decodeIt :: HD.Result (Maybe Int64)
|
||||||
|
decodeIt =
|
||||||
|
let row = HD.singleRow $ column HD.bytea in
|
||||||
|
(^? L.nth 0 . L.key "Plan" . L.key "Plan Rows" . L._Integral) <$> row
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- Fail a response if a single JSON object was requested and not exactly one
|
-- Fail a response if a single JSON object was requested and not exactly one
|
||||||
-- was found.
|
-- was found.
|
||||||
@@ -274,3 +328,30 @@ runPreReqQuery conf = lift $ traverse_ (SQL.statement mempty . stmt) (configDbPr
|
|||||||
getSQLQuery :: Query -> ByteString
|
getSQLQuery :: Query -> ByteString
|
||||||
getSQLQuery DbQuery{dqSQL} = dqSQL
|
getSQLQuery DbQuery{dqSQL} = dqSQL
|
||||||
getSQLQuery _ = mempty
|
getSQLQuery _ = mempty
|
||||||
|
|
||||||
|
-- | We use rowList because when doing EXPLAIN (FORMAT TEXT), the result comes as many rows. FORMAT JSON comes as one.
|
||||||
|
planRow :: HD.Result ResultSet
|
||||||
|
planRow = RSPlan . BS.unlines <$> HD.rowList (column HD.bytea)
|
||||||
|
|
||||||
|
column :: HD.Value a -> HD.Row a
|
||||||
|
column = HD.column . HD.nonNullable
|
||||||
|
|
||||||
|
nullableColumn :: HD.Value a -> HD.Row (Maybe a)
|
||||||
|
nullableColumn = HD.column . HD.nullable
|
||||||
|
|
||||||
|
arrayColumn :: HD.Value a -> HD.Row [a]
|
||||||
|
arrayColumn = column . HD.listArray . HD.nonNullable
|
||||||
|
|
||||||
|
standardRow :: Bool -> HD.Row ResultSet
|
||||||
|
standardRow noLocation =
|
||||||
|
RSStandard <$> nullableColumn HD.int8 <*> column HD.int8
|
||||||
|
<*> (if noLocation then pure mempty else fmap splitKeyValue <$> arrayColumn HD.bytea)
|
||||||
|
<*> (fromMaybe mempty <$> nullableColumn HD.bytea)
|
||||||
|
<*> nullableColumn HD.bytea
|
||||||
|
<*> nullableColumn HD.text
|
||||||
|
<*> nullableColumn HD.int8
|
||||||
|
where
|
||||||
|
splitKeyValue :: ByteString -> (ByteString, ByteString)
|
||||||
|
splitKeyValue kv =
|
||||||
|
let (k, v) = BS.break (== '=') kv in
|
||||||
|
(k, BS.tail v)
|
||||||
|
|||||||
@@ -1,28 +1,15 @@
|
|||||||
{-|
|
{-|
|
||||||
Module : PostgREST.Query.Statements
|
Module : PostgREST.Query.Statements
|
||||||
Description : PostgREST single SQL statements.
|
Description : PostgREST SQL statements.
|
||||||
|
|
||||||
This module constructs single SQL statements that can be parametrized and prepared.
|
|
||||||
|
|
||||||
- It consumes the SqlQuery types generated by the QueryBuilder module.
|
|
||||||
- It generates the body format and some headers of the final HTTP response.
|
|
||||||
-}
|
-}
|
||||||
module PostgREST.Query.Statements
|
module PostgREST.Query.Statements
|
||||||
( prepareWrite
|
( prepareWrite
|
||||||
, prepareRead
|
, prepareRead
|
||||||
, prepareCall
|
, prepareCall
|
||||||
, preparePlanRows
|
, preparePlanRows
|
||||||
, ResultSet (..)
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson.Lens as L
|
import qualified Hasql.DynamicStatements.Snippet as SQL
|
||||||
import qualified Data.ByteString.Char8 as BS
|
|
||||||
import qualified Hasql.Decoders as HD
|
|
||||||
import qualified Hasql.DynamicStatements.Snippet as SQL
|
|
||||||
import qualified Hasql.DynamicStatements.Statement as SQL
|
|
||||||
import qualified Hasql.Statement as SQL
|
|
||||||
|
|
||||||
import Control.Lens ((^?))
|
|
||||||
|
|
||||||
import PostgREST.ApiRequest.Preferences
|
import PostgREST.ApiRequest.Preferences
|
||||||
import PostgREST.MediaType (MTVndPlanFormat (..),
|
import PostgREST.MediaType (MTVndPlanFormat (..),
|
||||||
@@ -33,34 +20,10 @@ import PostgREST.SchemaCache.Routine (MediaHandler (..), Routine,
|
|||||||
|
|
||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
-- | Standard result set format used for all queries
|
|
||||||
data ResultSet
|
|
||||||
= RSStandard
|
|
||||||
{ rsTableTotal :: Maybe Int64
|
|
||||||
-- ^ count of all the table rows
|
|
||||||
, rsQueryTotal :: Int64
|
|
||||||
-- ^ count of the query rows
|
|
||||||
, rsLocation :: [(BS.ByteString, BS.ByteString)]
|
|
||||||
-- ^ The Location header(only used for inserts) is represented as a list of strings containing
|
|
||||||
-- variable bindings like @"k1=eq.42"@, or the empty list if there is no location header.
|
|
||||||
, rsBody :: BS.ByteString
|
|
||||||
-- ^ the aggregated body of the query
|
|
||||||
, rsGucHeaders :: Maybe BS.ByteString
|
|
||||||
-- ^ the HTTP headers to be added to the response
|
|
||||||
, rsGucStatus :: Maybe Text
|
|
||||||
-- ^ the HTTP status to be added to the response
|
|
||||||
, rsInserted :: Maybe Int64
|
|
||||||
-- ^ the number of rows inserted (Only used for upserts)
|
|
||||||
}
|
|
||||||
| RSPlan BS.ByteString -- ^ the plan of the query
|
|
||||||
|
|
||||||
|
|
||||||
prepareWrite :: SQL.Snippet -> SQL.Snippet -> Bool -> Bool -> MediaType -> MediaHandler ->
|
prepareWrite :: SQL.Snippet -> SQL.Snippet -> Bool -> Bool -> MediaType -> MediaHandler ->
|
||||||
Maybe PreferRepresentation -> Maybe PreferResolution -> [Text] -> Bool -> (SQL.Statement () ResultSet, ByteString)
|
Maybe PreferRepresentation -> Maybe PreferResolution -> [Text] -> SQL.Snippet
|
||||||
prepareWrite selectQuery mutateQuery isInsert isPut mt handler rep resolution pKeys prepared =
|
prepareWrite selectQuery mutateQuery isInsert isPut mt handler rep resolution pKeys = mtSnippet mt snippet
|
||||||
(result, sql)
|
|
||||||
where
|
where
|
||||||
result@(SQL.Statement sql _ _ _) = SQL.dynamicallyParameterized (mtSnippet mt snippet) decodeIt prepared
|
|
||||||
checkUpsert snip = if isInsert && (isPut || resolution == Just MergeDuplicates) then snip else "''"
|
checkUpsert snip = if isInsert && (isPut || resolution == Just MergeDuplicates) then snip else "''"
|
||||||
pgrstInsertedF = checkUpsert "nullif(current_setting('pgrst.inserted', true),'')::int"
|
pgrstInsertedF = checkUpsert "nullif(current_setting('pgrst.inserted', true),'')::int"
|
||||||
snippet =
|
snippet =
|
||||||
@@ -89,16 +52,9 @@ prepareWrite selectQuery mutateQuery isInsert isPut mt handler rep resolution pK
|
|||||||
| handler == NoAgg = "SELECT * FROM " <> sourceCTE
|
| handler == NoAgg = "SELECT * FROM " <> sourceCTE
|
||||||
| otherwise = selectQuery
|
| otherwise = selectQuery
|
||||||
|
|
||||||
decodeIt :: HD.Result ResultSet
|
prepareRead :: SQL.Snippet -> SQL.Snippet -> Bool -> MediaType -> MediaHandler -> SQL.Snippet
|
||||||
decodeIt = case mt of
|
prepareRead selectQuery countQuery countTotal mt handler = mtSnippet mt snippet
|
||||||
MTVndPlan{} -> planRow
|
|
||||||
_ -> fromMaybe (RSStandard Nothing 0 mempty mempty Nothing Nothing Nothing) <$> HD.rowMaybe (standardRow False)
|
|
||||||
|
|
||||||
prepareRead :: SQL.Snippet -> SQL.Snippet -> Bool -> MediaType -> MediaHandler -> Bool -> (SQL.Statement () ResultSet, ByteString)
|
|
||||||
prepareRead selectQuery countQuery countTotal mt handler prepared =
|
|
||||||
(result, sql)
|
|
||||||
where
|
where
|
||||||
result@(SQL.Statement sql _ _ _) = SQL.dynamicallyParameterized (mtSnippet mt snippet) decodeIt prepared
|
|
||||||
snippet =
|
snippet =
|
||||||
"WITH " <> sourceCTE <> " AS ( " <> selectQuery <> " ) " <>
|
"WITH " <> sourceCTE <> " AS ( " <> selectQuery <> " ) " <>
|
||||||
countCTEF <> " " <>
|
countCTEF <> " " <>
|
||||||
@@ -113,18 +69,11 @@ prepareRead selectQuery countQuery countTotal mt handler prepared =
|
|||||||
|
|
||||||
(countCTEF, countResultF) = countF countQuery countTotal
|
(countCTEF, countResultF) = countF countQuery countTotal
|
||||||
|
|
||||||
decodeIt :: HD.Result ResultSet
|
|
||||||
decodeIt = case mt of
|
|
||||||
MTVndPlan{} -> planRow
|
|
||||||
_ -> HD.singleRow $ standardRow True
|
|
||||||
|
|
||||||
prepareCall :: Routine -> SQL.Snippet -> SQL.Snippet -> SQL.Snippet -> Bool ->
|
prepareCall :: Routine -> SQL.Snippet -> SQL.Snippet -> SQL.Snippet -> Bool ->
|
||||||
MediaType -> MediaHandler -> Bool ->
|
MediaType -> MediaHandler -> SQL.Snippet
|
||||||
(SQL.Statement () ResultSet, ByteString)
|
prepareCall rout callProcQuery selectQuery countQuery countTotal mt handler = mtSnippet mt snippet
|
||||||
prepareCall rout callProcQuery selectQuery countQuery countTotal mt handler prepared =
|
|
||||||
(result, sql)
|
|
||||||
where
|
where
|
||||||
result@(SQL.Statement sql _ _ _) = SQL.dynamicallyParameterized (mtSnippet mt snippet) decodeIt prepared
|
|
||||||
snippet =
|
snippet =
|
||||||
"WITH " <> sourceCTE <> " AS (" <> callProcQuery <> ") " <>
|
"WITH " <> sourceCTE <> " AS (" <> callProcQuery <> ") " <>
|
||||||
countCTEF <>
|
countCTEF <>
|
||||||
@@ -141,49 +90,11 @@ prepareCall rout callProcQuery selectQuery countQuery countTotal mt handler prep
|
|||||||
|
|
||||||
(countCTEF, countResultF) = countF countQuery countTotal
|
(countCTEF, countResultF) = countF countQuery countTotal
|
||||||
|
|
||||||
decodeIt :: HD.Result ResultSet
|
|
||||||
decodeIt = case mt of
|
|
||||||
MTVndPlan{} -> planRow
|
|
||||||
_ -> fromMaybe (RSStandard (Just 0) 0 mempty mempty Nothing Nothing Nothing) <$> HD.rowMaybe (standardRow True)
|
|
||||||
|
|
||||||
preparePlanRows :: SQL.Snippet -> Bool -> SQL.Statement () (Maybe Int64)
|
preparePlanRows :: SQL.Snippet -> SQL.Snippet
|
||||||
preparePlanRows countQuery =
|
preparePlanRows = explainF PlanJSON mempty
|
||||||
SQL.dynamicallyParameterized snippet decodeIt
|
|
||||||
where
|
|
||||||
snippet = explainF PlanJSON mempty countQuery
|
|
||||||
decodeIt :: HD.Result (Maybe Int64)
|
|
||||||
decodeIt =
|
|
||||||
let row = HD.singleRow $ column HD.bytea in
|
|
||||||
(^? L.nth 0 . L.key "Plan" . L.key "Plan Rows" . L._Integral) <$> row
|
|
||||||
|
|
||||||
standardRow :: Bool -> HD.Row ResultSet
|
|
||||||
standardRow noLocation =
|
|
||||||
RSStandard <$> nullableColumn HD.int8 <*> column HD.int8
|
|
||||||
<*> (if noLocation then pure mempty else fmap splitKeyValue <$> arrayColumn HD.bytea)
|
|
||||||
<*> (fromMaybe mempty <$> nullableColumn HD.bytea)
|
|
||||||
<*> nullableColumn HD.bytea
|
|
||||||
<*> nullableColumn HD.text
|
|
||||||
<*> nullableColumn HD.int8
|
|
||||||
where
|
|
||||||
splitKeyValue :: ByteString -> (ByteString, ByteString)
|
|
||||||
splitKeyValue kv =
|
|
||||||
let (k, v) = BS.break (== '=') kv in
|
|
||||||
(k, BS.tail v)
|
|
||||||
|
|
||||||
mtSnippet :: MediaType -> SQL.Snippet -> SQL.Snippet
|
mtSnippet :: MediaType -> SQL.Snippet -> SQL.Snippet
|
||||||
mtSnippet mediaType snippet = case mediaType of
|
mtSnippet mediaType snippet = case mediaType of
|
||||||
MTVndPlan _ fmt opts -> explainF fmt opts snippet
|
MTVndPlan _ fmt opts -> explainF fmt opts snippet
|
||||||
_ -> snippet
|
_ -> snippet
|
||||||
|
|
||||||
-- | We use rowList because when doing EXPLAIN (FORMAT TEXT), the result comes as many rows. FORMAT JSON comes as one.
|
|
||||||
planRow :: HD.Result ResultSet
|
|
||||||
planRow = RSPlan . BS.unlines <$> HD.rowList (column HD.bytea)
|
|
||||||
|
|
||||||
column :: HD.Value a -> HD.Row a
|
|
||||||
column = HD.column . HD.nonNullable
|
|
||||||
|
|
||||||
nullableColumn :: HD.Value a -> HD.Row (Maybe a)
|
|
||||||
nullableColumn = HD.column . HD.nullable
|
|
||||||
|
|
||||||
arrayColumn :: HD.Value a -> HD.Row [a]
|
|
||||||
arrayColumn = column . HD.listArray . HD.nonNullable
|
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ import PostgREST.Plan (CallReadPlan (..),
|
|||||||
InfoPlan (..),
|
InfoPlan (..),
|
||||||
InspectPlan (..))
|
InspectPlan (..))
|
||||||
import PostgREST.Plan.MutatePlan (MutatePlan (..))
|
import PostgREST.Plan.MutatePlan (MutatePlan (..))
|
||||||
import PostgREST.Query (QueryResult (..))
|
import PostgREST.Query (QueryResult (..),
|
||||||
import PostgREST.Query.Statements (ResultSet (..))
|
ResultSet (..))
|
||||||
import PostgREST.Response.GucHeader (GucHeader, unwrapGucHeader)
|
import PostgREST.Response.GucHeader (GucHeader, unwrapGucHeader)
|
||||||
import PostgREST.SchemaCache (SchemaCache (..))
|
import PostgREST.SchemaCache (SchemaCache (..))
|
||||||
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..),
|
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..),
|
||||||
|
|||||||
Reference in New Issue
Block a user