packages feed

hpqtypes-extras 1.20.0.0 → 1.21.0.0

raw patch · 6 files changed

+74/−76 lines, 6 filesdep ~log-basedep ~tasty-hunitPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: log-base, tasty-hunit

API changes (from Hackage documentation)

- Database.PostgreSQL.PQTypes.SQL.Builder: checkAndRememberMaterializationSupport :: (MonadDB m, MonadIO m, MonadMask m) => m ()
+ Database.PostgreSQL.PQTypes.SQL.Builder: sqlWithNotMaterialized :: (MonadState v m, SqlWith v, Sqlable s) => SQL -> s -> m ()
- Database.PostgreSQL.PQTypes.SQL.Builder: SqlDelete :: SQL -> SQL -> [SqlCondition] -> [SQL] -> [(SQL, SQL, Materialized)] -> Recursive -> SqlDelete
+ Database.PostgreSQL.PQTypes.SQL.Builder: SqlDelete :: SQL -> SQL -> [SqlCondition] -> [SQL] -> [(SQL, SQL, Materialization)] -> Recursive -> SqlDelete
- Database.PostgreSQL.PQTypes.SQL.Builder: SqlInsert :: SQL -> Maybe (SQL, Maybe SQL) -> [(SQL, Multiplicity SQL)] -> [SQL] -> [(SQL, SQL, Materialized)] -> Recursive -> SqlInsert
+ Database.PostgreSQL.PQTypes.SQL.Builder: SqlInsert :: SQL -> Maybe (SQL, Maybe SQL) -> [(SQL, Multiplicity SQL)] -> [SQL] -> [(SQL, SQL, Materialization)] -> Recursive -> SqlInsert
- Database.PostgreSQL.PQTypes.SQL.Builder: SqlInsertSelect :: SQL -> Maybe (SQL, Maybe SQL) -> Bool -> [(SQL, SQL)] -> [SQL] -> SQL -> [SqlCondition] -> [SQL] -> [SQL] -> [SQL] -> Integer -> Integer -> [(SQL, SQL, Materialized)] -> Recursive -> SqlInsertSelect
+ Database.PostgreSQL.PQTypes.SQL.Builder: SqlInsertSelect :: SQL -> Maybe (SQL, Maybe SQL) -> Bool -> [(SQL, SQL)] -> [SQL] -> SQL -> [SqlCondition] -> [SQL] -> [SQL] -> [SQL] -> Integer -> Integer -> [(SQL, SQL, Materialization)] -> Recursive -> SqlInsertSelect
- Database.PostgreSQL.PQTypes.SQL.Builder: SqlSelect :: SQL -> [SQL] -> [SQL] -> Bool -> [SQL] -> [SqlCondition] -> [SQL] -> [SQL] -> [SQL] -> Integer -> Integer -> [(SQL, SQL, Materialized)] -> Recursive -> SqlSelect
+ Database.PostgreSQL.PQTypes.SQL.Builder: SqlSelect :: SQL -> [SQL] -> [SQL] -> Bool -> [SQL] -> [SqlCondition] -> [SQL] -> [SQL] -> [SQL] -> Integer -> Integer -> [(SQL, SQL, Materialization)] -> Recursive -> SqlSelect
- Database.PostgreSQL.PQTypes.SQL.Builder: SqlUpdate :: SQL -> SQL -> [SqlCondition] -> [(SQL, SQL)] -> [SQL] -> [(SQL, SQL, Materialized)] -> Recursive -> SqlUpdate
+ Database.PostgreSQL.PQTypes.SQL.Builder: SqlUpdate :: SQL -> SQL -> [SqlCondition] -> [(SQL, SQL)] -> [SQL] -> [(SQL, SQL, Materialization)] -> Recursive -> SqlUpdate
- Database.PostgreSQL.PQTypes.SQL.Builder: [sqlDeleteWith] :: SqlDelete -> [(SQL, SQL, Materialized)]
+ Database.PostgreSQL.PQTypes.SQL.Builder: [sqlDeleteWith] :: SqlDelete -> [(SQL, SQL, Materialization)]
- Database.PostgreSQL.PQTypes.SQL.Builder: [sqlInsertSelectWith] :: SqlInsertSelect -> [(SQL, SQL, Materialized)]
+ Database.PostgreSQL.PQTypes.SQL.Builder: [sqlInsertSelectWith] :: SqlInsertSelect -> [(SQL, SQL, Materialization)]
- Database.PostgreSQL.PQTypes.SQL.Builder: [sqlInsertWith] :: SqlInsert -> [(SQL, SQL, Materialized)]
+ Database.PostgreSQL.PQTypes.SQL.Builder: [sqlInsertWith] :: SqlInsert -> [(SQL, SQL, Materialization)]
- Database.PostgreSQL.PQTypes.SQL.Builder: [sqlSelectWith] :: SqlSelect -> [(SQL, SQL, Materialized)]
+ Database.PostgreSQL.PQTypes.SQL.Builder: [sqlSelectWith] :: SqlSelect -> [(SQL, SQL, Materialization)]
- Database.PostgreSQL.PQTypes.SQL.Builder: [sqlUpdateWith] :: SqlUpdate -> [(SQL, SQL, Materialized)]
+ Database.PostgreSQL.PQTypes.SQL.Builder: [sqlUpdateWith] :: SqlUpdate -> [(SQL, SQL, Materialization)]

Files

CHANGELOG.md view
@@ -1,3 +1,12 @@+# hpqtypes-extras-1.21.0.0 (2026-09-18)+* `migrateDatabase` and `checkDatabase` now require PostgreSQL 15 or later. On an+  older server they stop with an error.+* Remove `checkAndRememberMaterializationSupport`. All supported servers+  understand the `MATERIALIZED` keyword, so delete the call from your code.+* `sqlWith` now emits a plain `WITH` clause and lets PostgreSQL decide whether+  to materialize it. It used to emit `WITH ... AS NOT MATERIALIZED`. To keep the+  old behavior, use the new `sqlWithNotMaterialized`.+ # hpqtypes-extras-1.20.0.0 (2026-06-10) * Drop `crypton` dependency in favor of `ppad-ripemd160`. * Add support for customizing trigger functions.
hpqtypes-extras.cabal view
@@ -1,6 +1,6 @@ cabal-version:       3.0 name:                hpqtypes-extras-version:             1.20.0.0+version:             1.21.0.0 synopsis:            Extra utilities for hpqtypes library description:         The following extras for hpqtypes library:                      .@@ -89,8 +89,8 @@                , containers        >= 0.5                , exceptions        >= 0.10                , extra             >= 1.6.17-               , hpqtypes          >= 1.13.0.0-               , log-base          >= 0.11+               , hpqtypes          >= 1.13.0.0 && < 2+               , log-base          >= 0.12.0.1                , mtl               >= 2.2                , ppad-ripemd160    >= 0.1.4                , text              >= 1.2@@ -127,7 +127,7 @@                     , hpqtypes-extras                     , log-base                     , tasty-                    , tasty-hunit+                    , tasty-hunit >= 0.10                     , text                     , uuid-types 
src/Database/PostgreSQL/PQTypes/Checks.hs view
@@ -88,6 +88,7 @@     , dbTables = tables     }   migrations = do+    checkPostgresVersion     setDBTimeZoneToUTC     mapM_ checkExtension extensions     tablesWithVersions <- getTableVersions (tableVersions : tables)@@ -131,6 +132,7 @@     , dbDomains = domains     , dbTables = tables     } = execWriterT $ do+    lift checkPostgresVersion     (_, report) <- W.listen $ do       tablesWithVersions <- getTableVersions (tableVersions : tables)       tell $ checkVersions options tablesWithVersions@@ -600,12 +602,11 @@         sqlOrderBy "a.attnum"       desc <- fetchMany fetchTableColumn -      isAbove15 <- checkVersionIsAtLeast15       -- get info about constraints from pg_catalog       pk <- sqlGetPrimaryKey tblName       runQuery_ $ sqlGetChecks tblName       checks <- fetchMany fetchTableCheck-      runQuery_ $ sqlGetIndexes isAbove15 tblName Nothing+      runQuery_ $ sqlGetIndexes tblName Nothing       indexes <- fetchMany fetchTableIndex       runQuery_ $ sqlGetForeignKeys tblName       fkeys <- fetchMany fetchForeignKey@@ -1131,8 +1132,7 @@           indexSet <- case mLocalIndexName of             Nothing -> pure False             Just localIndexName -> do-              isAbove15 <- checkVersionIsAtLeast15-              runQuery_ $ sqlGetIndexes isAbove15 mgrTableName (Just localIndexName)+              runQuery_ $ sqlGetIndexes mgrTableName (Just localIndexName)               fetchMaybe fetchTableIndex >>= \case                 Nothing -> do                   logInfo_ "Local index not found"@@ -1378,11 +1378,21 @@ -- | Type synonym for a list of tables along with their database versions. type TablesWithVersions = [(Table, Int32)] --- The server_version_num has been there since 8.2-checkVersionIsAtLeast15 :: (MonadDB m, MonadThrow m) => m Bool-checkVersionIsAtLeast15 = do-  runSQL01_ "select current_setting('server_version_num',true)::int >= 150000;"-  fetchOne runIdentity+-- | Fail if the PostgreSQL server is older than the oldest supported version.+checkPostgresVersion :: (MonadDB m, MonadLog m, MonadThrow m) => m ()+checkPostgresVersion = do+  runSQL_ "SELECT current_setting('server_version_num')::int4, current_setting('server_version')"+  (versionNum, version) <- fetchOne $ id @(Int32, Text)+  when (versionNum < minimumVersion) . resultCheck . validationError $+    T.concat+      [ "PostgreSQL "+      , version+      , " is not supported, the minimum supported version is "+      , showt $ minimumVersion `div` 10_000+      ]+  where+    minimumVersion :: Int32+    minimumVersion = 150_000  -- | Associate each table in the list with its version as it exists in -- the DB, or 0 if it's missing from the DB.@@ -1516,17 +1526,14 @@     }  -- *** INDEXES ***-sqlGetIndexes :: Bool -> RawSQL () -> Maybe (RawSQL ()) -> SQL-sqlGetIndexes nullsNotDistinctSupported tableName mname = toSQLCommand . sqlSelect "pg_catalog.pg_class c" $ do+sqlGetIndexes :: RawSQL () -> Maybe (RawSQL ()) -> SQL+sqlGetIndexes tableName mname = toSQLCommand . sqlSelect "pg_catalog.pg_class c" $ do   sqlResult "c.relname::text" -- index name   sqlResult $ "ARRAY(" <> selectCoordinates "0" "i.indnkeyatts" <> ")" -- array of key columns in the index   sqlResult $ "ARRAY(" <> selectCoordinates "i.indnkeyatts" "i.indnatts" <> ")" -- array of included columns in the index   sqlResult "am.amname::text" -- the method used (btree, gin etc)   sqlResult "i.indisunique" -- is it unique?-  -- does it have NULLS NOT DISTINCT ?-  if nullsNotDistinctSupported-    then sqlResult "i.indnullsnotdistinct"-    else sqlResult "false"+  sqlResult "i.indnullsnotdistinct" -- does it have NULLS NOT DISTINCT?   -- if partial, get constraint def   sqlResult "pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)"   sqlJoinOn "pg_catalog.pg_index i" "c.oid = i.indexrelid"
src/Database/PostgreSQL/PQTypes/SQL/Builder.hs view
@@ -125,9 +125,9 @@   , sqlWith   , sqlWithRecursive   , sqlWithMaterialized+  , sqlWithNotMaterialized   , sqlUnion   , sqlUnionAll-  , checkAndRememberMaterializationSupport   , sqlSelect   , sqlSelect2   , SqlSelect (..)@@ -165,18 +165,12 @@   ) where -import Control.Monad.Catch import Control.Monad.State-import Data.Either-import Data.IORef-import Data.Int import Data.List import Data.Maybe import Data.Monoid.Utils import Data.String-import Data.Typeable import Database.PostgreSQL.PQTypes-import System.IO.Unsafe  class Sqlable a where   toSQLCommand :: a -> SQL@@ -209,7 +203,7 @@   deriving (Eq, Show)  data Multiplicity a = Single a | Many [a]-  deriving (Eq, Ord, Show, Typeable)+  deriving (Eq, Ord, Show)  -- | 'SqlCondition' are clauses that are part of the WHERE block in -- SQL statements. Each statement has a list of conditions, all of@@ -220,7 +214,7 @@ data SqlCondition   = SqlPlainCondition SQL   | SqlExistsCondition SqlSelect-  deriving (Typeable, Show)+  deriving (Show)  instance Sqlable SqlCondition where   toSQLCommand (SqlPlainCondition a) = a@@ -238,7 +232,7 @@   , sqlSelectHaving :: [SQL]   , sqlSelectOffset :: Integer   , sqlSelectLimit :: Integer-  , sqlSelectWith :: [(SQL, SQL, Materialized)]+  , sqlSelectWith :: [(SQL, SQL, Materialization)]   , sqlSelectRecursiveWith :: Recursive   } @@ -248,7 +242,7 @@   , sqlUpdateWhere :: [SqlCondition]   , sqlUpdateSet :: [(SQL, SQL)]   , sqlUpdateResult :: [SQL]-  , sqlUpdateWith :: [(SQL, SQL, Materialized)]+  , sqlUpdateWith :: [(SQL, SQL, Materialization)]   , sqlUpdateRecursiveWith :: Recursive   } @@ -257,7 +251,7 @@   , sqlInsertOnConflict :: Maybe (SQL, Maybe SQL)   , sqlInsertSet :: [(SQL, Multiplicity SQL)]   , sqlInsertResult :: [SQL]-  , sqlInsertWith :: [(SQL, SQL, Materialized)]+  , sqlInsertWith :: [(SQL, SQL, Materialization)]   , sqlInsertRecursiveWith :: Recursive   } @@ -274,7 +268,7 @@   , sqlInsertSelectHaving :: [SQL]   , sqlInsertSelectOffset :: Integer   , sqlInsertSelectLimit :: Integer-  , sqlInsertSelectWith :: [(SQL, SQL, Materialized)]+  , sqlInsertSelectWith :: [(SQL, SQL, Materialization)]   , sqlInsertSelectRecursiveWith :: Recursive   } @@ -283,7 +277,7 @@   , sqlDeleteUsing :: SQL   , sqlDeleteWhere :: [SqlCondition]   , sqlDeleteResult :: [SQL]-  , sqlDeleteWith :: [(SQL, SQL, Materialized)]+  , sqlDeleteWith :: [(SQL, SQL, Materialization)]   , sqlDeleteRecursiveWith :: Recursive   } @@ -355,7 +349,7 @@   toSQLCommand cmd =     smconcat       [ emitClausesSepComma (recursiveClause $ sqlSelectRecursiveWith cmd) $-          map (\(name, command, mat) -> name <+> "AS" <+> materializedClause mat <+> parenthesize command) (sqlSelectWith cmd)+          map withClause (sqlSelectWith cmd)       , if hasUnion || hasUnionAll           then emitClausesSep "" unionKeyword (mainSelectClause : unionCmd)           else mainSelectClause@@ -412,7 +406,7 @@   toSQLCommand cmd =     emitClausesSepComma       (recursiveClause $ sqlInsertRecursiveWith cmd)-      (map (\(name, command, mat) -> name <+> "AS" <+> materializedClause mat <+> parenthesize command) (sqlInsertWith cmd))+      (map withClause (sqlInsertWith cmd))       <+> "INSERT INTO"       <+> sqlInsertWhat cmd       <+> parenthesize (sqlConcatComma (map fst (sqlInsertSet cmd)))@@ -433,7 +427,7 @@       -- WITH clause needs to be at the top level, so we emit it here and not       -- include it in the SqlSelect below.       [ emitClausesSepComma (recursiveClause $ sqlInsertSelectRecursiveWith cmd) $-          map (\(name, command, mat) -> name <+> "AS" <+> materializedClause mat <+> parenthesize command) (sqlInsertSelectWith cmd)+          map withClause (sqlInsertSelectWith cmd)       , "INSERT INTO" <+> sqlInsertSelectWhat cmd       , parenthesize . sqlConcatComma . map fst $ sqlInsertSelectSet cmd       , parenthesize . toSQLCommand $@@ -456,27 +450,15 @@       , emitClausesSepComma "RETURNING" $ sqlInsertSelectResult cmd       ] --- This function has to be called as one of first things in your program--- for the library to make sure that it is aware if the "WITH MATERIALIZED"--- clause is supported by your PostgreSQL version.-checkAndRememberMaterializationSupport :: (MonadDB m, MonadIO m, MonadMask m) => m ()-checkAndRememberMaterializationSupport = do-  res :: Either DBException Int64 <- try . withNewConnection $ do-    runSQL01_ "WITH t(n) AS MATERIALIZED (SELECT (1 :: bigint)) SELECT n FROM t LIMIT 1"-    fetchOne runIdentity-  liftIO $ writeIORef withMaterializedSupported (isRight res)--withMaterializedSupported :: IORef Bool-{-# NOINLINE withMaterializedSupported #-}-withMaterializedSupported = unsafePerformIO $ newIORef False--isWithMaterializedSupported :: Bool-{-# NOINLINE isWithMaterializedSupported #-}-isWithMaterializedSupported = unsafePerformIO $ readIORef withMaterializedSupported--materializedClause :: Materialized -> SQL-materializedClause Materialized = if isWithMaterializedSupported then "MATERIALIZED" else ""-materializedClause NonMaterialized = if isWithMaterializedSupported then "NOT MATERIALIZED" else ""+withClause :: (SQL, SQL, Materialization) -> SQL+withClause (name, command, materialization) =+  name <+> "AS" <+> materializationClause <+> parenthesize command+  where+    materializationClause :: SQL+    materializationClause = case materialization of+      DefaultMaterialization -> ""+      Materialized -> "MATERIALIZED"+      NotMaterialized -> "NOT MATERIALIZED"  recursiveClause :: Recursive -> SQL recursiveClause Recursive = "WITH RECURSIVE"@@ -486,7 +468,7 @@   toSQLCommand cmd =     emitClausesSepComma       (recursiveClause $ sqlUpdateRecursiveWith cmd)-      (map (\(name, command, mat) -> name <+> "AS" <+> materializedClause mat <+> parenthesize command) (sqlUpdateWith cmd))+      (map withClause (sqlUpdateWith cmd))       <+> "UPDATE"       <+> sqlUpdateWhat cmd       <+> "SET"@@ -499,7 +481,7 @@   toSQLCommand cmd =     emitClausesSepComma       (recursiveClause $ sqlDeleteRecursiveWith cmd)-      (map (\(name, command, mat) -> name <+> "AS" <+> materializedClause mat <+> parenthesize command) (sqlDeleteWith cmd))+      (map withClause (sqlDeleteWith cmd))       <+> "DELETE FROM"       <+> sqlDeleteFrom cmd       <+> emitClause "USING" (sqlDeleteUsing cmd)@@ -570,7 +552,7 @@         }     ) -data Materialized = Materialized | NonMaterialized+data Materialization = DefaultMaterialization | Materialized | NotMaterialized data Recursive = Recursive | NonRecursive  -- This instance guarantees that once a single CTE has@@ -582,7 +564,7 @@   _ <> _ = NonRecursive  class SqlWith a where-  sqlWith1 :: a -> SQL -> SQL -> Materialized -> Recursive -> a+  sqlWith1 :: a -> SQL -> SQL -> Materialization -> Recursive -> a  instance SqlWith SqlSelect where   sqlWith1 cmd name sql mat recurse = cmd {sqlSelectWith = sqlSelectWith cmd ++ [(name, sql, mat)], sqlSelectRecursiveWith = recurse <> sqlSelectRecursiveWith cmd}@@ -596,15 +578,23 @@ instance SqlWith SqlDelete where   sqlWith1 cmd name sql mat recurse = cmd {sqlDeleteWith = sqlDeleteWith cmd ++ [(name, sql, mat)], sqlDeleteRecursiveWith = recurse <> sqlDeleteRecursiveWith cmd} +-- | Add a @WITH@ clause and let PostgreSQL decide whether to materialize it. sqlWith :: (MonadState v m, SqlWith v, Sqlable s) => SQL -> s -> m ()-sqlWith name sql = modify (\cmd -> sqlWith1 cmd name (toSQLCommand sql) NonMaterialized NonRecursive)+sqlWith name sql = modify (\cmd -> sqlWith1 cmd name (toSQLCommand sql) DefaultMaterialization NonRecursive) +-- | Add a @WITH ... AS MATERIALIZED@ clause. sqlWithMaterialized :: (MonadState v m, SqlWith v, Sqlable s) => SQL -> s -> m () sqlWithMaterialized name sql = modify (\cmd -> sqlWith1 cmd name (toSQLCommand sql) Materialized NonRecursive) +-- | Add a @WITH ... AS NOT MATERIALIZED@ clause.+--+-- @since 1.21.0.0+sqlWithNotMaterialized :: (MonadState v m, SqlWith v, Sqlable s) => SQL -> s -> m ()+sqlWithNotMaterialized name sql = modify (\cmd -> sqlWith1 cmd name (toSQLCommand sql) NotMaterialized NonRecursive)+ -- | Note: RECURSIVE only powers SELECTs (but the SELECT can feed an UPDATE outside of the recursive query). sqlWithRecursive :: (MonadState v m, SqlWith v, Sqlable s) => SQL -> s -> m ()-sqlWithRecursive name sql = modify (\cmd -> sqlWith1 cmd name (toSQLCommand sql) NonMaterialized Recursive)+sqlWithRecursive name sql = modify (\cmd -> sqlWith1 cmd name (toSQLCommand sql) DefaultMaterialization Recursive)  -- | Note: WHERE clause of the main SELECT is treated specially, i.e. it only -- applies to the main SELECT, not the whole union.
src/Database/PostgreSQL/PQTypes/Utils/NubList.hs view
@@ -5,8 +5,6 @@   , overNubList   ) where -import Data.Typeable- import Data.Semigroup qualified as SG import Data.Set qualified as Set import Text.Read qualified as R@@ -21,7 +19,7 @@ -- | NubList : A de-duplicated list that maintains the original order. newtype NubList a   = NubList {fromNubList :: [a]}-  deriving (Eq, Typeable)+  deriving (Eq)  -- NubList assumes that nub retains the list order while removing duplicate -- elements (keeping the first occurence). Documentation for "Data.List.nub"
test/Main.hs view
@@ -28,7 +28,6 @@ import Test.Tasty.Options  newtype ConnectionString = ConnectionString String-  deriving (Typeable)  instance IsOption ConnectionString where   defaultValue =@@ -1617,11 +1616,6 @@ testSqlWith step = do   step "Running sql WITH tests"   testPass-  runSQL_ "DELETE FROM bank"-  step "Checking for WITH MATERIALIZED support"-  checkAndRememberMaterializationSupport-  step "Running sql WITH tests again with WITH MATERIALIZED support flag set"-  testPass   where     migrate tables migrations = do       let definitions = tableDefsWithPgCrypto tables@@ -1644,9 +1638,9 @@         sqlFrom "bank_name"         sqlSetCmd "name" "bank_name"         sqlSet "location" ("Other side" :: T.Text)-      step "testing WITH .. UPDATE"+      step "testing WITH MATERIALIZED .. UPDATE"       runQuery_ . sqlUpdate "bank" $ do-        sqlWith "other_bank" $ do+        sqlWithMaterialized "other_bank" $ do           sqlSelect "bank" $ do             sqlWhereEq "name" ("other" :: T.Text)             sqlResult "id"@@ -1654,9 +1648,9 @@         sqlSet "location" ("abcd" :: T.Text)         sqlWhereInSql "bank.id" $ mkSQL "other_bank.id"         sqlResult "bank.id"-      step "testing WITH .. DELETE"+      step "testing WITH NOT MATERIALIZED .. DELETE"       runQuery_ . sqlDelete "bank" $ do-        sqlWith "other_bank" $ do+        sqlWithNotMaterialized "other_bank" $ do           sqlSelect "bank" $ do             sqlWhereEq "name" ("other" :: T.Text)             sqlResult "id"