feat: allow spreading one-to-many and many-to-many embedded resources

* Note: Aggregates are not implemented
This commit is contained in:
Laurence Isla
2025-03-24 14:45:56 +00:00
committed by GitHub
parent 09ba7c0d28
commit 0b618d0bef
14 changed files with 1001 additions and 253 deletions
+16 -6
View File
@@ -22,6 +22,7 @@ module PostgREST.Query.SqlFragment
, pgFmtOrderTerm
, pgFmtSelectItem
, pgFmtSpreadSelectItem
, pgFmtSpreadJoinSelectItem
, fromJsonBodyF
, responseHeadersF
, responseStatusF
@@ -257,7 +258,7 @@ pgFmtField table cf = case cfToTsVector cf of
_ -> fmtFld
where
fmtFld = case cf of
CoercibleField{cfFullRow=True} -> fromQi table
CoercibleField{cfFullRow=True} -> pgFmtIdent (qiName table)
CoercibleField{cfName=fn, cfJsonPath=[]} -> pgFmtColumn table fn
CoercibleField{cfName=fn, cfToJson=doToJson, cfJsonPath=jp} | doToJson -> "to_jsonb(" <> pgFmtColumn table fn <> ")" <> pgFmtJsonPath jp
| otherwise -> pgFmtColumn table fn <> pgFmtJsonPath jp
@@ -278,11 +279,7 @@ pgFmtSelectItem table CoercibleSelectField{csField=fld, csAggFunction=agg, csAgg
pgFmtSpreadSelectItem :: Alias -> SpreadSelectField -> SQL.Snippet
pgFmtSpreadSelectItem aggAlias SpreadSelectField{ssSelName, ssSelAggFunction, ssSelAggCast, ssSelAlias} =
pgFmtApplyAggregate ssSelAggFunction ssSelAggCast fullSelName <> pgFmtAs ssSelAlias
where
fullSelName = case ssSelName of
"*" -> pgFmtIdent aggAlias <> ".*"
_ -> pgFmtIdent aggAlias <> "." <> pgFmtIdent ssSelName
pgFmtApplyAggregate ssSelAggFunction ssSelAggCast (pgFmtFullSelName aggAlias ssSelName) <> pgFmtAs ssSelAlias
pgFmtApplyAggregate :: Maybe AggregateFunction -> Maybe Cast -> SQL.Snippet -> SQL.Snippet
pgFmtApplyAggregate Nothing _ snippet = snippet
@@ -294,6 +291,14 @@ pgFmtApplyAggregate (Just agg) aggCast snippet =
convertAggFunction = SQL.sql . BS.map toUpper . BS.pack . show
aggregatedSnippet = convertAggFunction agg <> "(" <> snippet <> ")"
pgFmtSpreadJoinSelectItem :: Alias -> [CoercibleOrderTerm] -> SpreadSelectField -> SQL.Snippet
pgFmtSpreadJoinSelectItem aggAlias order SpreadSelectField{ssSelName, ssSelAlias} =
"COALESCE(json_agg(" <> fmtField <> " " <> fmtOrder <> "),'[]')::jsonb" <> " AS " <> fmtAlias
where
fmtField = pgFmtFullSelName aggAlias ssSelName
fmtOrder = orderF (QualifiedIdentifier "" aggAlias) order
fmtAlias = pgFmtIdent (fromMaybe ssSelName ssSelAlias)
pgFmtApplyCast :: Maybe Cast -> SQL.Snippet -> SQL.Snippet
pgFmtApplyCast Nothing snippet = snippet
-- Ideally we'd quote the cast with "pgFmtIdent cast". However, that would invalidate common casts such as "int", "bigint", etc.
@@ -301,6 +306,11 @@ pgFmtApplyCast Nothing snippet = snippet
-- Not quoting should be fine, we validate the input on Parsers.
pgFmtApplyCast (Just cast) snippet = "CAST( " <> snippet <> " AS " <> SQL.sql (encodeUtf8 cast) <> " )"
pgFmtFullSelName :: Alias -> FieldName -> SQL.Snippet
pgFmtFullSelName aggAlias fieldName = case fieldName of
"*" -> pgFmtIdent aggAlias <> ".*"
_ -> pgFmtIdent aggAlias <> "." <> pgFmtIdent fieldName
-- TODO: At this stage there shouldn't be a Maybe since ApiRequest should ensure that an INSERT/UPDATE has a body
fromJsonBodyF :: Maybe LBS.ByteString -> [CoercibleField] -> Bool -> Bool -> Bool -> SQL.Snippet
fromJsonBodyF body fields includeSelect includeLimitOne includeDefaults =