diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a98400bdd..5a5b001eb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -67,7 +67,7 @@ jobs: strategy: fail-fast: false matrix: - pgVersion: [12, 13, 14, 15, 16, 17] + pgVersion: [13, 14, 15, 16, 17] name: PG ${{ matrix.pgVersion }} runs-on: ubuntu-24.04 defaults: diff --git a/default.nix b/default.nix index 54363ef16..c60524c96 100644 --- a/default.nix +++ b/default.nix @@ -59,7 +59,6 @@ let { name = "postgresql-15"; postgresql = pkgs.postgresql_15.withPackages (p: [ p.postgis p.pg_safeupdate ]); } { name = "postgresql-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); } { name = "postgresql-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis p.pg_safeupdate ]); } - { name = "postgresql-12"; postgresql = pkgs.postgresql_12.withPackages (p: [ p.postgis p.pg_safeupdate ]); } ]; haskellPackages = pkgs.haskell.packages."${compiler}"; diff --git a/nix/README.md b/nix/README.md index facd79317..aedc8bc80 100644 --- a/nix/README.md +++ b/nix/README.md @@ -90,7 +90,7 @@ postgrest-dump-schema postgrest-watch postgrest-gen-ctags postgrest-with-all postgrest-gen-jwt postgrest-with-git postgrest-gen-secret postgrest-with-pgrst -postgrest-git-hooks postgrest-with-postgresql-12 +postgrest-git-hooks postgrest-hsie-graph-modules postgrest-with-postgresql-13 postgrest-hsie-graph-symbols postgrest-with-postgresql-14 postgrest-hsie-minimal-imports postgrest-with-postgresql-15 diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index bc696929d..095cdc3d3 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -45,7 +45,6 @@ import PostgREST.AppState (AppState) import PostgREST.Auth.Types (AuthResult (..)) import PostgREST.Config (AppConfig (..), LogLevel (..), LogQuery (..)) -import PostgREST.Config.PgVersion (PgVersion (..)) import PostgREST.Error (Error) import PostgREST.Network (resolveHost) import PostgREST.Observation (Observation (..)) @@ -108,12 +107,11 @@ postgrest logLevel appState connWorker = Right authResult -> do appConf <- AppState.getConfig appState -- the config must be read again because it can reload maybeSchemaCache <- AppState.getSchemaCache appState - pgVer <- AppState.getPgVersion appState let eitherResponse :: IO (Either Error Wai.Response) eitherResponse = - runExceptT $ postgrestResponse appState appConf maybeSchemaCache pgVer authResult req + runExceptT $ postgrestResponse appState appConf maybeSchemaCache authResult req response <- either Error.errorResponseFor identity <$> eitherResponse -- Launch the connWorker when the connection is down. The postgrest @@ -129,11 +127,10 @@ postgrestResponse :: AppState.AppState -> AppConfig -> Maybe SchemaCache - -> PgVersion -> AuthResult -> Wai.Request -> Handler IO Wai.Response -postgrestResponse appState conf@AppConfig{..} maybeSchemaCache pgVer authResult@AuthResult{..} req = do +postgrestResponse appState conf@AppConfig{..} maybeSchemaCache authResult@AuthResult{..} req = do sCache <- case maybeSchemaCache of Just sCache -> @@ -150,7 +147,7 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache pgVer authResult@ (parseTime, apiReq@ApiRequest{..}) <- withTiming $ liftEither . mapLeft Error.ApiRequestError $ ApiRequest.userApiRequest conf prefs req body (planTime, plan) <- withTiming $ liftEither $ Plan.actionPlan iAction conf apiReq sCache - let query = Query.query conf authResult apiReq plan sCache pgVer + let query = Query.query conf authResult apiReq plan sCache logSQL = lift . AppState.getObserver appState . DBQuery (Query.getSQLQuery query) (queryTime, queryResult) <- withTiming $ do diff --git a/src/PostgREST/Config/PgVersion.hs b/src/PostgREST/Config/PgVersion.hs index 273d62941..6db42e90b 100644 --- a/src/PostgREST/Config/PgVersion.hs +++ b/src/PostgREST/Config/PgVersion.hs @@ -3,7 +3,6 @@ module PostgREST.Config.PgVersion ( PgVersion(..) , minimumPgVersion - , pgVersion130 , pgVersion140 , pgVersion150 , pgVersion170 @@ -26,10 +25,7 @@ instance Ord PgVersion where -- | Tells the minimum PostgreSQL version required by this version of PostgREST minimumPgVersion :: PgVersion -minimumPgVersion = pgVersion121 - -pgVersion121 :: PgVersion -pgVersion121 = PgVersion 120001 "12.1" "12.1" +minimumPgVersion = pgVersion130 pgVersion130 :: PgVersion pgVersion130 = PgVersion 130000 "13.0" "13.0" diff --git a/src/PostgREST/Plan.hs b/src/PostgREST/Plan.hs index f808476d5..d7141170b 100644 --- a/src/PostgREST/Plan.hs +++ b/src/PostgREST/Plan.hs @@ -70,7 +70,6 @@ import PostgREST.SchemaCache.Routine (MediaHandler (..), Routine (..), RoutineMap, RoutineParam (..), - funcReturnsCompositeAlias, funcReturnsScalar, funcReturnsSetOfScalar, funcReturnsSingle) @@ -1024,7 +1023,6 @@ callPlan proc ApiRequest{} paramKeys args readReq = FunctionCall { , funCArgs = args , funCScalar = funcReturnsScalar proc , funCSetOfScalar = funcReturnsSetOfScalar proc -, funCRetCompositeAlias = funcReturnsCompositeAlias proc , funCFilterFields = getFilterFieldNames readReq , funCReturning = inferColsEmbedNeeds readReq [] } diff --git a/src/PostgREST/Plan/CallPlan.hs b/src/PostgREST/Plan/CallPlan.hs index 2174e82d3..1b89257ff 100644 --- a/src/PostgREST/Plan/CallPlan.hs +++ b/src/PostgREST/Plan/CallPlan.hs @@ -19,14 +19,13 @@ import PostgREST.SchemaCache.Routine (Routine (..), import Protolude data CallPlan = FunctionCall - { funCQi :: QualifiedIdentifier - , funCParams :: CallParams - , funCArgs :: CallArgs - , funCScalar :: Bool - , funCSetOfScalar :: Bool - , funCRetCompositeAlias :: Bool - , funCFilterFields :: Set FieldName - , funCReturning :: Set FieldName + { funCQi :: QualifiedIdentifier + , funCParams :: CallParams + , funCArgs :: CallArgs + , funCScalar :: Bool + , funCSetOfScalar :: Bool + , funCFilterFields :: Set FieldName + , funCReturning :: Set FieldName } data CallParams diff --git a/src/PostgREST/Query.hs b/src/PostgREST/Query.hs index f504f64c5..2749bcbf2 100644 --- a/src/PostgREST/Query.hs +++ b/src/PostgREST/Query.hs @@ -39,7 +39,6 @@ import PostgREST.ApiRequest.Preferences (PreferCount (..), import PostgREST.Auth.Types (AuthResult (..)) import PostgREST.Config (AppConfig (..), OpenAPIMode (..)) -import PostgREST.Config.PgVersion (PgVersion (..)) import PostgREST.Error (Error) import PostgREST.MediaType (MediaType (..)) import PostgREST.Plan (ActionPlan (..), @@ -80,16 +79,16 @@ data QueryResult | MaybeDbResult InspectPlan (Maybe (TablesMap, RoutineMap, Maybe Text)) | NoDbResult InfoPlan -query :: AppConfig -> AuthResult -> ApiRequest -> ActionPlan -> SchemaCache -> PgVersion -> Query -query _ _ _ (NoDb x) _ _ = NoDbQuery $ NoDbResult x -query config AuthResult{..} apiReq (Db plan) sCache pgVer = +query :: AppConfig -> AuthResult -> ApiRequest -> ActionPlan -> SchemaCache -> Query +query _ _ _ (NoDb x) _ = NoDbQuery $ NoDbResult x +query config AuthResult{..} apiReq (Db plan) sCache = DbQuery isoLvl txMode dbHandler transaction mainSQLQuery where transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction prepared = configDbPreparedStatements config isoLvl = planIsoLvl config authRole plan txMode = planTxMode plan - (mainActionQuery, mainSQLQuery) = actionQuery plan config apiReq pgVer sCache + (mainActionQuery, mainSQLQuery) = actionQuery plan config apiReq sCache dbHandler = do setPgLocals plan config authClaims authRole apiReq runPreReq config @@ -108,8 +107,8 @@ planIsoLvl AppConfig{configRoleIsoLvl} role actPlan = case actPlan of roleIsoLvl = HM.findWithDefault SQL.ReadCommitted role configRoleIsoLvl -- TODO: Generate the Hasql Statement in a diferent module after the OpenAPI functionality is removed -actionQuery :: DbActionPlan -> AppConfig -> ApiRequest -> PgVersion -> SchemaCache -> (DbHandler QueryResult, ByteString) -actionQuery (DbCrud plan@WrappedReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ _ = +actionQuery :: DbActionPlan -> AppConfig -> ApiRequest -> SchemaCache -> (DbHandler QueryResult, ByteString) +actionQuery (DbCrud plan@WrappedReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ = (mainActionQuery, mainSQLQuery) where countQuery = QueryBuilder.readPlanToCountQuery wrReadPlan @@ -131,7 +130,7 @@ actionQuery (DbCrud plan@WrappedReadPlan{..}) conf@AppConfig{..} apiReq@ApiReque optionalRollback conf apiReq DbCrudResult plan <$> resultSetWTotal conf apiReq resultSet countQuery -actionQuery (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ _ = +actionQuery (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ = (mainActionQuery, mainSQLQuery) where (isPut, isInsert, pkCols) = case mrMutatePlan of {Insert{where_,insPkCols} -> ((not . null) where_, True, insPkCols); _ -> (False,False, mempty);} @@ -163,12 +162,12 @@ actionQuery (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiReques optionalRollback conf apiReq pure $ DbCrudResult plan resultSet -actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} pgVer _ = +actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ = (mainActionQuery, mainSQLQuery) where (result, mainSQLQuery) = Statements.prepareCall crProc - (QueryBuilder.callPlanToQuery crCallPlan pgVer) + (QueryBuilder.callPlanToQuery crCallPlan) (QueryBuilder.readPlanToQuery crReadPlan) (QueryBuilder.readPlanToCountQuery crReadPlan) (shouldCount preferCount) @@ -182,7 +181,7 @@ actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{ failExceedsMaxAffectedPref (preferMaxAffected,preferHandling) resultSet pure $ DbCallResult plan resultSet -actionQuery (MaybeDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ _ sCache = +actionQuery (MaybeDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ sCache = (mainActionQuery, mempty) where mainActionQuery = lift $ diff --git a/src/PostgREST/Query/QueryBuilder.hs b/src/PostgREST/Query/QueryBuilder.hs index 5a65f56a5..7a2fedfbd 100644 --- a/src/PostgREST/Query/QueryBuilder.hs +++ b/src/PostgREST/Query/QueryBuilder.hs @@ -28,7 +28,6 @@ import Data.Maybe (fromJust) import Data.Tree (Tree (..)) import PostgREST.ApiRequest.Preferences (PreferResolution (..)) -import PostgREST.Config.PgVersion (PgVersion, pgVersion130) import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..)) import PostgREST.SchemaCache.Relationship (Cardinality (..), Junction (..), @@ -169,8 +168,8 @@ mutatePlanToQuery (Delete mainQi logicForest returnings) = where whereLogic = if null logicForest then mempty else " WHERE " <> intercalateSnippet " AND " (pgFmtLogicTree mainQi <$> logicForest) -callPlanToQuery :: CallPlan -> PgVersion -> SQL.Snippet -callPlanToQuery (FunctionCall qi params arguments returnsScalar returnsSetOfScalar returnsCompositeAlias filterFields returnings) pgVer = +callPlanToQuery :: CallPlan -> SQL.Snippet +callPlanToQuery (FunctionCall qi params arguments returnsScalar returnsSetOfScalar filterFields returnings) = "SELECT " <> (if returnsScalar || returnsSetOfScalar then "pgrst_call.pgrst_scalar" else returnedColumns) <> " " <> fromCall where @@ -186,9 +185,8 @@ callPlanToQuery (FunctionCall qi params arguments returnsScalar returnsSetOfScal "LATERAL " <> callIt (fmtParams prms) callIt :: SQL.Snippet -> SQL.Snippet - callIt argument | pgVer < pgVersion130 && returnsCompositeAlias = "(SELECT (" <> fromQi qi <> "(" <> argument <> ")).*) pgrst_call" - | returnsScalar || returnsSetOfScalar = "(SELECT " <> fromQi qi <> "(" <> argument <> ") pgrst_scalar) pgrst_call" - | otherwise = fromQi qi <> "(" <> argument <> ") pgrst_call" + callIt argument | returnsScalar || returnsSetOfScalar = "(SELECT " <> fromQi qi <> "(" <> argument <> ") pgrst_scalar) pgrst_call" + | otherwise = fromQi qi <> "(" <> argument <> ") pgrst_call" fmtParams :: [RoutineParam] -> SQL.Snippet fmtParams prms = intercalateSnippet ", " diff --git a/src/PostgREST/SchemaCache/Routine.hs b/src/PostgREST/SchemaCache/Routine.hs index 248fb5682..c0517d380 100644 --- a/src/PostgREST/SchemaCache/Routine.hs +++ b/src/PostgREST/SchemaCache/Routine.hs @@ -14,7 +14,6 @@ module PostgREST.SchemaCache.Routine , funcReturnsSingleComposite , funcReturnsVoid , funcTableName - , funcReturnsCompositeAlias , funcReturnsSingle , MediaHandlerMap , ResolvedHandler @@ -127,12 +126,6 @@ funcReturnsSetOfScalar proc = case proc of Function{pdReturnType = SetOf (Scalar{})} -> True _ -> False -funcReturnsCompositeAlias :: Routine -> Bool -funcReturnsCompositeAlias proc = case proc of - Function{pdReturnType = Single (Composite _ True)} -> True - Function{pdReturnType = SetOf (Composite _ True)} -> True - _ -> False - funcReturnsSingleComposite :: Routine -> Bool funcReturnsSingleComposite proc = case proc of Function{pdReturnType = Single (Composite _ _)} -> True diff --git a/test/spec/Feature/Query/InsertSpec.hs b/test/spec/Feature/Query/InsertSpec.hs index b016333a4..7aef6ce2b 100644 --- a/test/spec/Feature/Query/InsertSpec.hs +++ b/test/spec/Feature/Query/InsertSpec.hs @@ -11,8 +11,7 @@ import Test.Hspec.Wai import Test.Hspec.Wai.JSON import Text.Heredoc -import PostgREST.Config.PgVersion (PgVersion, pgVersion130, - pgVersion140) +import PostgREST.Config.PgVersion (PgVersion, pgVersion140) import Protolude hiding (get) import SpecHelper @@ -208,11 +207,7 @@ spec actualPgVersion = do it "fails with 400 and error" $ post "/simple_pk" [json| { "extra":"foo"} |] `shouldRespondWith` - (if actualPgVersion >= pgVersion130 then - [json|{"hint":null,"details":"Failing row contains (null, foo).","code":"23502","message":"null value in column \"k\" of relation \"simple_pk\" violates not-null constraint"}|] - else - [json|{"hint":null,"details":"Failing row contains (null, foo).","code":"23502","message":"null value in column \"k\" violates not-null constraint"}|] - ) + [json|{"hint":null,"details":"Failing row contains (null, foo).","code":"23502","message":"null value in column \"k\" of relation \"simple_pk\" violates not-null constraint"}|] { matchStatus = 400 , matchHeaders = [ matchContentTypeJson] } diff --git a/test/spec/Feature/Query/PlanSpec.hs b/test/spec/Feature/Query/PlanSpec.hs index 9e207eb9c..f045c8aa9 100644 --- a/test/spec/Feature/Query/PlanSpec.hs +++ b/test/spec/Feature/Query/PlanSpec.hs @@ -15,8 +15,7 @@ import Test.Hspec hiding (pendingWith) import Test.Hspec.Wai import Test.Hspec.Wai.JSON -import PostgREST.Config.PgVersion (PgVersion, pgVersion130, - pgVersion170) +import PostgREST.Config.PgVersion (PgVersion, pgVersion170) import Protolude hiding (get) import SpecHelper @@ -50,27 +49,15 @@ spec actualPgVersion = do resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" } totalCost `shouldBe` 24.28 - it "outputs blocks info when using the buffers option" $ - if actualPgVersion >= pgVersion130 - then do - r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=buffers") "" + it "outputs blocks info when using the buffers option" $ do + r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=buffers") "" - let resBody = simpleBody r - resHeaders = simpleHeaders r + let resBody = simpleBody r + resHeaders = simpleHeaders r - liftIO $ do - resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=buffers; charset=utf-8") - resBody `shouldSatisfy` (\t -> T.isInfixOf "Shared Hit Blocks" (decodeUtf8 $ LBS.toStrict t)) - else do - -- analyze is required for buffers on pg < 13 - r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=analyze|buffers") "" - - let blocks = simpleBody r ^? nth 0 . key "Plan" . key "Shared Hit Blocks" - resHeaders = simpleHeaders r - - liftIO $ do - resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=analyze|buffers; charset=utf-8") - blocks `shouldBe` Just [aesonQQ| 1.0 |] + liftIO $ do + resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=buffers; charset=utf-8") + resBody `shouldSatisfy` (\t -> T.isInfixOf "Shared Hit Blocks" (decodeUtf8 $ LBS.toStrict t)) it "outputs the search path when using the settings option" $ do r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=settings") "" @@ -87,16 +74,15 @@ spec actualPgVersion = do } |] - when (actualPgVersion >= pgVersion130) $ - it "outputs WAL info when using the wal option" $ do - r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=analyze|wal") "" + it "outputs WAL info when using the wal option" $ do + r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=analyze|wal") "" - let walRecords = simpleBody r ^? nth 0 . key "Plan" . key "WAL Records" - resHeaders = simpleHeaders r + let walRecords = simpleBody r ^? nth 0 . key "Plan" . key "WAL Records" + resHeaders = simpleHeaders r - liftIO $ do - resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=analyze|wal; charset=utf-8") - walRecords `shouldBe` Just [aesonQQ|0|] + liftIO $ do + resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=analyze|wal; charset=utf-8") + walRecords `shouldBe` Just [aesonQQ|0|] it "outputs columns info when using the verbose option" $ do r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=verbose") ""