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
|
||||
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
|
||||
|
||||
(ActionInspect headersOnly, TargetDefaultSpec tSchema) -> do
|
||||
|
||||
@@ -56,6 +56,7 @@ import PostgREST.SchemaCache.Identifiers (FieldName,
|
||||
Schema)
|
||||
import PostgREST.SchemaCache.Proc (ProcDescription (..),
|
||||
ProcParam (..), ProcsMap,
|
||||
procReturnsCompositeAlias,
|
||||
procReturnsScalar,
|
||||
procReturnsSetOfScalar)
|
||||
import PostgREST.SchemaCache.Relationship (Cardinality (..),
|
||||
@@ -550,6 +551,7 @@ callPlan proc ApiRequest{iPreferences=Preferences{..}} paramKeys args readReq =
|
||||
, funCArgs = Just args
|
||||
, funCScalar = procReturnsScalar proc
|
||||
, funCSetOfScalar = procReturnsSetOfScalar proc
|
||||
, funCRetCompositeAlias = procReturnsCompositeAlias proc
|
||||
, funCReturning = inferColsEmbedNeeds readReq []
|
||||
}
|
||||
where
|
||||
|
||||
@@ -17,12 +17,13 @@ import PostgREST.SchemaCache.Proc (ProcDescription (..),
|
||||
import Protolude
|
||||
|
||||
data CallPlan = FunctionCall
|
||||
{ funCQi :: QualifiedIdentifier
|
||||
, funCParams :: CallParams
|
||||
, funCArgs :: Maybe LBS.ByteString
|
||||
, funCScalar :: Bool
|
||||
, funCSetOfScalar :: Bool
|
||||
, funCReturning :: [FieldName]
|
||||
{ funCQi :: QualifiedIdentifier
|
||||
, funCParams :: CallParams
|
||||
, funCArgs :: Maybe LBS.ByteString
|
||||
, funCScalar :: Bool
|
||||
, funCSetOfScalar :: Bool
|
||||
, funCRetCompositeAlias :: Bool
|
||||
, funCReturning :: [FieldName]
|
||||
}
|
||||
|
||||
data CallParams
|
||||
|
||||
@@ -152,15 +152,15 @@ deleteQuery mrPlan apiReq@ApiRequest{..} conf = do
|
||||
optionalRollback conf apiReq
|
||||
pure resultSet
|
||||
|
||||
invokeQuery :: ProcDescription -> CallReadPlan -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
||||
invokeQuery proc CallReadPlan{crReadPlan, crCallPlan, crBinField} apiReq@ApiRequest{iPreferences=Preferences{..}, ..} conf@AppConfig{..} = do
|
||||
invokeQuery :: ProcDescription -> CallReadPlan -> ApiRequest -> AppConfig -> PgVersion -> DbHandler ResultSet
|
||||
invokeQuery proc CallReadPlan{crReadPlan, crCallPlan, crBinField} apiReq@ApiRequest{iPreferences=Preferences{..}, ..} conf@AppConfig{..} pgVer = do
|
||||
resultSet <-
|
||||
lift . SQL.statement mempty $
|
||||
Statements.prepareCall
|
||||
(Proc.procReturnsScalar proc)
|
||||
(Proc.procReturnsSingleComposite proc)
|
||||
(Proc.procReturnsSetOfScalar proc)
|
||||
(QueryBuilder.callPlanToQuery crCallPlan)
|
||||
(QueryBuilder.callPlanToQuery crCallPlan pgVer)
|
||||
(QueryBuilder.readPlanToQuery crReadPlan)
|
||||
(QueryBuilder.readPlanToCountQuery crReadPlan)
|
||||
(shouldCount preferCount)
|
||||
|
||||
@@ -22,6 +22,8 @@ import qualified Hasql.DynamicStatements.Snippet as SQL
|
||||
import Data.Tree (Tree (..))
|
||||
|
||||
import PostgREST.ApiRequest.Preferences (PreferResolution (..))
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion110,
|
||||
pgVersion130)
|
||||
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
|
||||
import PostgREST.SchemaCache.Proc (ProcParam (..))
|
||||
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)
|
||||
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts)
|
||||
|
||||
callPlanToQuery :: CallPlan -> SQL.Snippet
|
||||
callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar returnings) =
|
||||
callPlanToQuery :: CallPlan -> PgVersion -> SQL.Snippet
|
||||
callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar returnsCompositeAlias returnings) pgVer =
|
||||
"SELECT " <> (if returnsScalar || returnsSetOfScalar then "pgrst_call AS pgrst_scalar " else returnedColumns) <> " " <>
|
||||
fromCall
|
||||
where
|
||||
@@ -175,7 +177,8 @@ callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar re
|
||||
"LATERAL " <> callIt (fmtParams prms)
|
||||
|
||||
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 prms = SQL.sql $ BS.intercalate ", "
|
||||
|
||||
@@ -248,6 +248,7 @@ decodeProcs =
|
||||
<*> column HD.text
|
||||
<*> column HD.bool
|
||||
<*> column HD.bool
|
||||
<*> column HD.bool
|
||||
<*> column HD.bool)
|
||||
<*> (parseVolatility <$> column HD.char)
|
||||
<*> column HD.bool
|
||||
@@ -255,15 +256,15 @@ decodeProcs =
|
||||
addKey :: ProcDescription -> (QualifiedIdentifier, ProcDescription)
|
||||
addKey pd = (QualifiedIdentifier (pdSchema pd) (pdName pd), pd)
|
||||
|
||||
parseRetType :: Text -> Text -> Bool -> Bool -> Bool -> RetType
|
||||
parseRetType schema name isSetOf isComposite isVoid
|
||||
parseRetType :: Text -> Text -> Bool -> Bool -> Bool -> Bool -> RetType
|
||||
parseRetType schema name isSetOf isComposite isVoid isCompositeAlias
|
||||
| isVoid = Single $ Scalar True
|
||||
| isSetOf = SetOf pgType
|
||||
| otherwise = Single pgType
|
||||
where
|
||||
qi = QualifiedIdentifier schema name
|
||||
pgType
|
||||
| isComposite = Composite qi
|
||||
| isComposite = Composite qi isCompositeAlias
|
||||
| otherwise = Scalar False
|
||||
|
||||
parseVolatility :: Char -> ProcVolatility
|
||||
@@ -340,6 +341,7 @@ procsSqlQuery pgVer = [q|
|
||||
or COALESCE(proargmodes::text[] && '{t,b,o}', false)
|
||||
) AS rettype_is_composite,
|
||||
('void'::regtype = t.oid) AS rettype_is_void,
|
||||
bt.oid <> bt.base as rettype_is_composite_alias,
|
||||
p.provolatile,
|
||||
p.provariadic > 0 as hasvariadic
|
||||
FROM pg_proc p
|
||||
|
||||
@@ -13,6 +13,7 @@ module PostgREST.SchemaCache.Proc
|
||||
, procReturnsSingleComposite
|
||||
, procReturnsVoid
|
||||
, procTableName
|
||||
, procReturnsCompositeAlias
|
||||
) where
|
||||
|
||||
import qualified Data.Aeson as JSON
|
||||
@@ -25,7 +26,7 @@ import Protolude
|
||||
|
||||
data PgType
|
||||
= 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)
|
||||
|
||||
data RetType
|
||||
@@ -79,10 +80,16 @@ procReturnsSetOfScalar proc = case proc of
|
||||
ProcDescription{pdReturnType = SetOf (Scalar _)} -> True
|
||||
_ -> 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 proc = case proc of
|
||||
ProcDescription{pdReturnType = Single (Composite _)} -> True
|
||||
_ -> False
|
||||
ProcDescription{pdReturnType = Single (Composite _ _)} -> True
|
||||
_ -> False
|
||||
|
||||
procReturnsVoid :: ProcDescription -> Bool
|
||||
procReturnsVoid proc = case proc of
|
||||
@@ -91,6 +98,6 @@ procReturnsVoid proc = case proc of
|
||||
|
||||
procTableName :: ProcDescription -> Maybe TableName
|
||||
procTableName proc = case pdReturnType proc of
|
||||
SetOf (Composite qi) -> Just $ qiName qi
|
||||
Single (Composite qi) -> Just $ qiName qi
|
||||
_ -> Nothing
|
||||
SetOf (Composite qi _) -> Just $ qiName qi
|
||||
Single (Composite qi _) -> Just $ qiName qi
|
||||
_ -> Nothing
|
||||
|
||||
@@ -15,7 +15,7 @@ import Text.Heredoc
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
||||
pgVersion109, pgVersion110,
|
||||
pgVersion112, pgVersion114,
|
||||
pgVersion130, pgVersion140)
|
||||
pgVersion140)
|
||||
|
||||
import Protolude hiding (get)
|
||||
import SpecHelper
|
||||
@@ -376,8 +376,7 @@ spec actualPgVersion =
|
||||
]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
-- https://github.com/PostgREST/postgrest/pull/2677#issuecomment-1444976849
|
||||
when (actualPgVersion >= pgVersion130) $
|
||||
when (actualPgVersion >= pgVersion110) $
|
||||
it "can embed if rpc returns domain of table type" $ do
|
||||
post "/rpc/getproject_domain?select=id,name,client:clients(id),tasks(id)"
|
||||
[json| { "id": 1} |]
|
||||
|
||||
Reference in New Issue
Block a user