RPC returning table alias now works for pg 11/12 (#2737)
The new LATERAL query used for calling the function, introduced on https://github.com/PostgREST/postgrest/pull/2677, failed on functions that returned a domain like `CREATE DOMAIN projects_domain AS projects`. Work around that by changing the query conditionally, by obtaining a bool that represents the composite alias on the SchemaCache and only do this on pg 11 and 12.
This commit is contained in:
@@ -198,7 +198,7 @@ handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@A
|
|||||||
|
|
||||||
(ActionInvoke invMethod, TargetProc identifier _) -> do
|
(ActionInvoke invMethod, TargetProc identifier _) -> do
|
||||||
cPlan <- liftEither $ Plan.callReadPlan identifier conf sCache apiReq invMethod
|
cPlan <- liftEither $ Plan.callReadPlan identifier conf sCache apiReq invMethod
|
||||||
resultSet <- runQuery (Plan.crTxMode cPlan) $ Query.invokeQuery (Plan.crProc cPlan) cPlan apiReq conf
|
resultSet <- runQuery (Plan.crTxMode cPlan) $ Query.invokeQuery (Plan.crProc cPlan) cPlan apiReq conf pgVer
|
||||||
return $ Response.invokeResponse invMethod (Plan.crProc cPlan) apiReq resultSet
|
return $ Response.invokeResponse invMethod (Plan.crProc cPlan) apiReq resultSet
|
||||||
|
|
||||||
(ActionInspect headersOnly, TargetDefaultSpec tSchema) -> do
|
(ActionInspect headersOnly, TargetDefaultSpec tSchema) -> do
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import PostgREST.SchemaCache.Identifiers (FieldName,
|
|||||||
Schema)
|
Schema)
|
||||||
import PostgREST.SchemaCache.Proc (ProcDescription (..),
|
import PostgREST.SchemaCache.Proc (ProcDescription (..),
|
||||||
ProcParam (..), ProcsMap,
|
ProcParam (..), ProcsMap,
|
||||||
|
procReturnsCompositeAlias,
|
||||||
procReturnsScalar,
|
procReturnsScalar,
|
||||||
procReturnsSetOfScalar)
|
procReturnsSetOfScalar)
|
||||||
import PostgREST.SchemaCache.Relationship (Cardinality (..),
|
import PostgREST.SchemaCache.Relationship (Cardinality (..),
|
||||||
@@ -550,6 +551,7 @@ callPlan proc ApiRequest{iPreferences=Preferences{..}} paramKeys args readReq =
|
|||||||
, funCArgs = Just args
|
, funCArgs = Just args
|
||||||
, funCScalar = procReturnsScalar proc
|
, funCScalar = procReturnsScalar proc
|
||||||
, funCSetOfScalar = procReturnsSetOfScalar proc
|
, funCSetOfScalar = procReturnsSetOfScalar proc
|
||||||
|
, funCRetCompositeAlias = procReturnsCompositeAlias proc
|
||||||
, funCReturning = inferColsEmbedNeeds readReq []
|
, funCReturning = inferColsEmbedNeeds readReq []
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -17,12 +17,13 @@ import PostgREST.SchemaCache.Proc (ProcDescription (..),
|
|||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
data CallPlan = FunctionCall
|
data CallPlan = FunctionCall
|
||||||
{ funCQi :: QualifiedIdentifier
|
{ funCQi :: QualifiedIdentifier
|
||||||
, funCParams :: CallParams
|
, funCParams :: CallParams
|
||||||
, funCArgs :: Maybe LBS.ByteString
|
, funCArgs :: Maybe LBS.ByteString
|
||||||
, funCScalar :: Bool
|
, funCScalar :: Bool
|
||||||
, funCSetOfScalar :: Bool
|
, funCSetOfScalar :: Bool
|
||||||
, funCReturning :: [FieldName]
|
, funCRetCompositeAlias :: Bool
|
||||||
|
, funCReturning :: [FieldName]
|
||||||
}
|
}
|
||||||
|
|
||||||
data CallParams
|
data CallParams
|
||||||
|
|||||||
@@ -152,15 +152,15 @@ deleteQuery mrPlan apiReq@ApiRequest{..} conf = do
|
|||||||
optionalRollback conf apiReq
|
optionalRollback conf apiReq
|
||||||
pure resultSet
|
pure resultSet
|
||||||
|
|
||||||
invokeQuery :: ProcDescription -> CallReadPlan -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
invokeQuery :: ProcDescription -> CallReadPlan -> ApiRequest -> AppConfig -> PgVersion -> DbHandler ResultSet
|
||||||
invokeQuery proc CallReadPlan{crReadPlan, crCallPlan, crBinField} apiReq@ApiRequest{iPreferences=Preferences{..}, ..} conf@AppConfig{..} = do
|
invokeQuery proc CallReadPlan{crReadPlan, crCallPlan, crBinField} apiReq@ApiRequest{iPreferences=Preferences{..}, ..} conf@AppConfig{..} pgVer = do
|
||||||
resultSet <-
|
resultSet <-
|
||||||
lift . SQL.statement mempty $
|
lift . SQL.statement mempty $
|
||||||
Statements.prepareCall
|
Statements.prepareCall
|
||||||
(Proc.procReturnsScalar proc)
|
(Proc.procReturnsScalar proc)
|
||||||
(Proc.procReturnsSingleComposite proc)
|
(Proc.procReturnsSingleComposite proc)
|
||||||
(Proc.procReturnsSetOfScalar proc)
|
(Proc.procReturnsSetOfScalar proc)
|
||||||
(QueryBuilder.callPlanToQuery crCallPlan)
|
(QueryBuilder.callPlanToQuery crCallPlan pgVer)
|
||||||
(QueryBuilder.readPlanToQuery crReadPlan)
|
(QueryBuilder.readPlanToQuery crReadPlan)
|
||||||
(QueryBuilder.readPlanToCountQuery crReadPlan)
|
(QueryBuilder.readPlanToCountQuery crReadPlan)
|
||||||
(shouldCount preferCount)
|
(shouldCount preferCount)
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import qualified Hasql.DynamicStatements.Snippet as SQL
|
|||||||
import Data.Tree (Tree (..))
|
import Data.Tree (Tree (..))
|
||||||
|
|
||||||
import PostgREST.ApiRequest.Preferences (PreferResolution (..))
|
import PostgREST.ApiRequest.Preferences (PreferResolution (..))
|
||||||
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion110,
|
||||||
|
pgVersion130)
|
||||||
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
|
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
|
||||||
import PostgREST.SchemaCache.Proc (ProcParam (..))
|
import PostgREST.SchemaCache.Proc (ProcParam (..))
|
||||||
import PostgREST.SchemaCache.Relationship (Cardinality (..),
|
import PostgREST.SchemaCache.Relationship (Cardinality (..),
|
||||||
@@ -163,8 +165,8 @@ mutatePlanToQuery (Delete mainQi logicForest range ordts returnings)
|
|||||||
whereLogic = if null logicForest then mempty else " WHERE " <> intercalateSnippet " AND " (pgFmtLogicTree mainQi <$> logicForest)
|
whereLogic = if null logicForest then mempty else " WHERE " <> intercalateSnippet " AND " (pgFmtLogicTree mainQi <$> logicForest)
|
||||||
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts)
|
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts)
|
||||||
|
|
||||||
callPlanToQuery :: CallPlan -> SQL.Snippet
|
callPlanToQuery :: CallPlan -> PgVersion -> SQL.Snippet
|
||||||
callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar returnings) =
|
callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar returnsCompositeAlias returnings) pgVer =
|
||||||
"SELECT " <> (if returnsScalar || returnsSetOfScalar then "pgrst_call AS pgrst_scalar " else returnedColumns) <> " " <>
|
"SELECT " <> (if returnsScalar || returnsSetOfScalar then "pgrst_call AS pgrst_scalar " else returnedColumns) <> " " <>
|
||||||
fromCall
|
fromCall
|
||||||
where
|
where
|
||||||
@@ -175,7 +177,8 @@ callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar re
|
|||||||
"LATERAL " <> callIt (fmtParams prms)
|
"LATERAL " <> callIt (fmtParams prms)
|
||||||
|
|
||||||
callIt :: SQL.Snippet -> SQL.Snippet
|
callIt :: SQL.Snippet -> SQL.Snippet
|
||||||
callIt argument = SQL.sql (fromQi qi) <> "(" <> argument <> ") pgrst_call"
|
callIt argument | pgVer < pgVersion130 && pgVer >= pgVersion110 && returnsCompositeAlias = "(SELECT (" <> SQL.sql (fromQi qi) <> "(" <> argument <> ")).*) pgrst_call"
|
||||||
|
| otherwise = SQL.sql (fromQi qi) <> "(" <> argument <> ") pgrst_call"
|
||||||
|
|
||||||
fmtParams :: [ProcParam] -> SQL.Snippet
|
fmtParams :: [ProcParam] -> SQL.Snippet
|
||||||
fmtParams prms = SQL.sql $ BS.intercalate ", "
|
fmtParams prms = SQL.sql $ BS.intercalate ", "
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ decodeProcs =
|
|||||||
<*> column HD.text
|
<*> column HD.text
|
||||||
<*> column HD.bool
|
<*> column HD.bool
|
||||||
<*> column HD.bool
|
<*> column HD.bool
|
||||||
|
<*> column HD.bool
|
||||||
<*> column HD.bool)
|
<*> column HD.bool)
|
||||||
<*> (parseVolatility <$> column HD.char)
|
<*> (parseVolatility <$> column HD.char)
|
||||||
<*> column HD.bool
|
<*> column HD.bool
|
||||||
@@ -255,15 +256,15 @@ decodeProcs =
|
|||||||
addKey :: ProcDescription -> (QualifiedIdentifier, ProcDescription)
|
addKey :: ProcDescription -> (QualifiedIdentifier, ProcDescription)
|
||||||
addKey pd = (QualifiedIdentifier (pdSchema pd) (pdName pd), pd)
|
addKey pd = (QualifiedIdentifier (pdSchema pd) (pdName pd), pd)
|
||||||
|
|
||||||
parseRetType :: Text -> Text -> Bool -> Bool -> Bool -> RetType
|
parseRetType :: Text -> Text -> Bool -> Bool -> Bool -> Bool -> RetType
|
||||||
parseRetType schema name isSetOf isComposite isVoid
|
parseRetType schema name isSetOf isComposite isVoid isCompositeAlias
|
||||||
| isVoid = Single $ Scalar True
|
| isVoid = Single $ Scalar True
|
||||||
| isSetOf = SetOf pgType
|
| isSetOf = SetOf pgType
|
||||||
| otherwise = Single pgType
|
| otherwise = Single pgType
|
||||||
where
|
where
|
||||||
qi = QualifiedIdentifier schema name
|
qi = QualifiedIdentifier schema name
|
||||||
pgType
|
pgType
|
||||||
| isComposite = Composite qi
|
| isComposite = Composite qi isCompositeAlias
|
||||||
| otherwise = Scalar False
|
| otherwise = Scalar False
|
||||||
|
|
||||||
parseVolatility :: Char -> ProcVolatility
|
parseVolatility :: Char -> ProcVolatility
|
||||||
@@ -340,6 +341,7 @@ procsSqlQuery pgVer = [q|
|
|||||||
or COALESCE(proargmodes::text[] && '{t,b,o}', false)
|
or COALESCE(proargmodes::text[] && '{t,b,o}', false)
|
||||||
) AS rettype_is_composite,
|
) AS rettype_is_composite,
|
||||||
('void'::regtype = t.oid) AS rettype_is_void,
|
('void'::regtype = t.oid) AS rettype_is_void,
|
||||||
|
bt.oid <> bt.base as rettype_is_composite_alias,
|
||||||
p.provolatile,
|
p.provolatile,
|
||||||
p.provariadic > 0 as hasvariadic
|
p.provariadic > 0 as hasvariadic
|
||||||
FROM pg_proc p
|
FROM pg_proc p
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ module PostgREST.SchemaCache.Proc
|
|||||||
, procReturnsSingleComposite
|
, procReturnsSingleComposite
|
||||||
, procReturnsVoid
|
, procReturnsVoid
|
||||||
, procTableName
|
, procTableName
|
||||||
|
, procReturnsCompositeAlias
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
@@ -25,7 +26,7 @@ import Protolude
|
|||||||
|
|
||||||
data PgType
|
data PgType
|
||||||
= Scalar Bool -- True if the type is void
|
= Scalar Bool -- True if the type is void
|
||||||
| Composite QualifiedIdentifier
|
| Composite QualifiedIdentifier Bool -- True if the composite is a domain alias(used to work around a bug in pg 11 and 12, see QueryBuilder.hs)
|
||||||
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
||||||
|
|
||||||
data RetType
|
data RetType
|
||||||
@@ -79,10 +80,16 @@ procReturnsSetOfScalar proc = case proc of
|
|||||||
ProcDescription{pdReturnType = SetOf (Scalar _)} -> True
|
ProcDescription{pdReturnType = SetOf (Scalar _)} -> True
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
|
procReturnsCompositeAlias :: ProcDescription -> Bool
|
||||||
|
procReturnsCompositeAlias proc = case proc of
|
||||||
|
ProcDescription{pdReturnType = Single (Composite _ True)} -> True
|
||||||
|
ProcDescription{pdReturnType = SetOf (Composite _ True)} -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
procReturnsSingleComposite :: ProcDescription -> Bool
|
procReturnsSingleComposite :: ProcDescription -> Bool
|
||||||
procReturnsSingleComposite proc = case proc of
|
procReturnsSingleComposite proc = case proc of
|
||||||
ProcDescription{pdReturnType = Single (Composite _)} -> True
|
ProcDescription{pdReturnType = Single (Composite _ _)} -> True
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
procReturnsVoid :: ProcDescription -> Bool
|
procReturnsVoid :: ProcDescription -> Bool
|
||||||
procReturnsVoid proc = case proc of
|
procReturnsVoid proc = case proc of
|
||||||
@@ -91,6 +98,6 @@ procReturnsVoid proc = case proc of
|
|||||||
|
|
||||||
procTableName :: ProcDescription -> Maybe TableName
|
procTableName :: ProcDescription -> Maybe TableName
|
||||||
procTableName proc = case pdReturnType proc of
|
procTableName proc = case pdReturnType proc of
|
||||||
SetOf (Composite qi) -> Just $ qiName qi
|
SetOf (Composite qi _) -> Just $ qiName qi
|
||||||
Single (Composite qi) -> Just $ qiName qi
|
Single (Composite qi _) -> Just $ qiName qi
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import Text.Heredoc
|
|||||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
||||||
pgVersion109, pgVersion110,
|
pgVersion109, pgVersion110,
|
||||||
pgVersion112, pgVersion114,
|
pgVersion112, pgVersion114,
|
||||||
pgVersion130, pgVersion140)
|
pgVersion140)
|
||||||
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
@@ -376,8 +376,7 @@ spec actualPgVersion =
|
|||||||
]|]
|
]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
-- https://github.com/PostgREST/postgrest/pull/2677#issuecomment-1444976849
|
when (actualPgVersion >= pgVersion110) $
|
||||||
when (actualPgVersion >= pgVersion130) $
|
|
||||||
it "can embed if rpc returns domain of table type" $ do
|
it "can embed if rpc returns domain of table type" $ do
|
||||||
post "/rpc/getproject_domain?select=id,name,client:clients(id),tasks(id)"
|
post "/rpc/getproject_domain?select=id,name,client:clients(id),tasks(id)"
|
||||||
[json| { "id": 1} |]
|
[json| { "id": 1} |]
|
||||||
|
|||||||
Reference in New Issue
Block a user