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:
Steve Chavez
2023-04-05 15:34:41 -05:00
committed by GitHub
parent 16f2849724
commit acf62320ef
8 changed files with 39 additions and 25 deletions
+1 -1
View File
@@ -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
+2
View File
@@ -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
+7 -6
View File
@@ -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
+3 -3
View File
@@ -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)
+6 -3
View File
@@ -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 ", "
+5 -3
View File
@@ -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
View File
@@ -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
+2 -3
View File
@@ -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} |]