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
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
+2
View File
@@ -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
+7 -6
View File
@@ -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
+3 -3
View File
@@ -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)
+6 -3
View File
@@ -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 ", "
+5 -3
View File
@@ -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
View File
@@ -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
+2 -3
View File
@@ -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} |]