{-# LANGUAGE NamedFieldPuns #-} {-| Module : PostgREST.Query.Statements Description : PostgREST main queries -} module PostgREST.Query.Statements ( mainWrite , mainRead , mainCall , postExplain ) where import qualified Hasql.DynamicStatements.Snippet as SQL import PostgREST.ApiRequest.Preferences import PostgREST.MediaType (MTVndPlanFormat (..), MediaType (..)) import PostgREST.Plan.CallPlan import PostgREST.Plan.MutatePlan as MTPlan import PostgREST.Plan.ReadPlan import PostgREST.Query.QueryBuilder import PostgREST.Query.SqlFragment import PostgREST.SchemaCache.Routine (MediaHandler (..), Routine, funcReturnsSingle) import Protolude mainWrite :: ReadPlanTree -> MutatePlan -> MediaType -> MediaHandler -> Maybe PreferRepresentation -> Maybe PreferResolution -> SQL.Snippet mainWrite rPlan mtplan mt handler rep resolution = mtSnippet mt snippet where checkUpsert snip = if isInsert && (isPut || resolution == Just MergeDuplicates) then snip else "''" pgrstInsertedF = checkUpsert "nullif(current_setting('pgrst.inserted', true),'')::int" snippet = "WITH " <> sourceCTE <> " AS (" <> mutateQuery <> ") " <> "SELECT " <> "'' AS total_result_set, " <> "pg_catalog.count(_postgrest_t) AS page_total, " <> locF <> " AS header, " <> handlerF Nothing handler <> " AS body, " <> responseHeadersF <> " AS response_headers, " <> responseStatusF <> " AS response_status, " <> pgrstInsertedF <> " AS response_inserted " <> "FROM (" <> selectF <> ") _postgrest_t" locF = if isInsert && rep == Just HeadersOnly then "CASE WHEN pg_catalog.count(_postgrest_t) = 1 " <> "THEN coalesce(" <> locationF pkCols <> ", " <> noLocationF <> ") " <> "ELSE " <> noLocationF <> " " <> "END" else noLocationF selectF -- prevent using any of the column names in ?select= when no response is returned from the CTE | handler == NoAgg = "SELECT * FROM " <> sourceCTE | otherwise = selectQuery selectQuery = readPlanToQuery rPlan mutateQuery = mutatePlanToQuery mtplan (isPut, isInsert, pkCols) = case mtplan of MTPlan.Insert{MTPlan.where_,insPkCols} -> ((not . null) where_, True, insPkCols) _ -> (False,False, mempty); mainRead :: ReadPlanTree -> SQL.Snippet -> Maybe PreferCount -> Maybe Integer -> MediaType -> MediaHandler -> SQL.Snippet mainRead rPlan countQuery pCount maxRows mt handler = mtSnippet mt snippet where snippet = "WITH " <> sourceCTE <> " AS ( " <> selectQuery <> " ) " <> countCTEF <> " " <> "SELECT " <> countResultF <> " AS total_result_set, " <> "pg_catalog.count(_postgrest_t) AS page_total, " <> handlerF Nothing handler <> " AS body, " <> responseHeadersF <> " AS response_headers, " <> responseStatusF <> " AS response_status, " <> "''" <> " AS response_inserted " <> "FROM ( SELECT * FROM " <> sourceCTE <> " ) _postgrest_t" (countCTEF, countResultF) = countF countQ $ shouldCount pCount selectQuery = readPlanToQuery rPlan countQ = if pCount == Just EstimatedCount then -- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed limitedQuery countQuery ((+ 1) <$> maxRows) else countQuery mainCall :: Routine -> CallPlan -> ReadPlanTree -> Maybe PreferCount -> MediaType -> MediaHandler -> SQL.Snippet mainCall rout cPlan rPlan pCount mt handler = mtSnippet mt snippet where snippet = "WITH " <> sourceCTE <> " AS (" <> callProcQuery <> ") " <> countCTEF <> "SELECT " <> countResultF <> " AS total_result_set, " <> (if funcReturnsSingle rout then "1" else "pg_catalog.count(_postgrest_t)") <> " AS page_total, " <> handlerF (Just rout) handler <> " AS body, " <> responseHeadersF <> " AS response_headers, " <> responseStatusF <> " AS response_status, " <> "''" <> " AS response_inserted " <> "FROM (" <> selectQuery <> ") _postgrest_t" (countCTEF, countResultF) = countF countQuery $ shouldCount pCount selectQuery = readPlanToQuery rPlan callProcQuery = callPlanToQuery cPlan countQuery = readPlanToCountQuery rPlan -- This occurs after the main query runs, that's why it's prefixed with "post" postExplain :: SQL.Snippet -> SQL.Snippet postExplain = explainF PlanJSON mempty mtSnippet :: MediaType -> SQL.Snippet -> SQL.Snippet mtSnippet mediaType snippet = case mediaType of MTVndPlan _ fmt opts -> explainF fmt opts snippet _ -> snippet