Skip to content

Commit

Permalink
Make recursive associative in an or-like fashion
Browse files Browse the repository at this point in the history
  • Loading branch information
Raveline committed Aug 27, 2024
1 parent 2090101 commit 2f7cf86
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 27 deletions.
38 changes: 13 additions & 25 deletions src/Database/PostgreSQL/PQTypes/SQL/Builder.hs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ module Database.PostgreSQL.PQTypes.SQL.Builder
, sqlUnion
, sqlUnionAll
, checkAndRememberMaterializationSupport
, checkAndRememberRecursiveSupport

, sqlSelect
, sqlSelect2
Expand Down Expand Up @@ -452,16 +451,6 @@ checkAndRememberMaterializationSupport = do
fetchOne runIdentity
liftIO $ writeIORef withMaterializedSupported (isRight res)

-- 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 RECURSIVE"
-- clause is supported by your PostgreSQL version.
checkAndRememberRecursiveSupport :: (MonadDB m, MonadIO m, MonadMask m) => m ()
checkAndRememberRecursiveSupport = do
res :: Either DBException Int64 <- try . withNewConnection $ do
runSQL01_ "WITH RECURSIVE t(n) AS (SELECT (1 :: bigint)) SELECT n FROM t LIMIT 1"
fetchOne runIdentity
liftIO $ writeIORef withRecursiveSupported (isRight res)

withMaterializedSupported :: IORef Bool
{-# NOINLINE withMaterializedSupported #-}
withMaterializedSupported = unsafePerformIO $ newIORef False
Expand All @@ -470,20 +459,12 @@ isWithMaterializedSupported :: Bool
{-# NOINLINE isWithMaterializedSupported #-}
isWithMaterializedSupported = unsafePerformIO $ readIORef withMaterializedSupported

withRecursiveSupported :: IORef Bool
{-# NOINLINE withRecursiveSupported #-}
withRecursiveSupported = unsafePerformIO $ newIORef False

isWithRecursiveSupported :: Bool
{-# NOINLINE isWithRecursiveSupported #-}
isWithRecursiveSupported = unsafePerformIO $ readIORef withRecursiveSupported

materializedClause :: Materialized -> SQL
materializedClause Materialized = if isWithMaterializedSupported then "MATERIALIZED" else ""
materializedClause NonMaterialized = if isWithMaterializedSupported then "NOT MATERIALIZED" else ""

recursiveClause :: Recursive -> SQL
recursiveClause Recursive = if isWithRecursiveSupported then "WITH RECURSIVE" else "WITH"
recursiveClause Recursive = "WITH RECURSIVE"
recursiveClause NonRecursive = "WITH"

instance Sqlable SqlUpdate where
Expand Down Expand Up @@ -559,21 +540,28 @@ sqlDelete table refine =
data Materialized = Materialized | NonMaterialized
data Recursive = Recursive | NonRecursive

-- This instance guarantees that once a single CTE has
-- been marked as recursive, the whole "WITH" block will
-- get the RECURSIVE keyword associated to it.
instance Semigroup Recursive where
_ <> Recursive = Recursive
Recursive <> _ = Recursive
_ <> _ = NonRecursive

class SqlWith a where
sqlWith1 :: a -> SQL -> SQL -> Materialized -> Recursive -> a


instance SqlWith SqlSelect where
sqlWith1 cmd name sql mat recurse = cmd { sqlSelectWith = sqlSelectWith cmd ++ [(name,sql,mat)], sqlSelectRecursiveWith = recurse }
sqlWith1 cmd name sql mat recurse = cmd { sqlSelectWith = sqlSelectWith cmd ++ [(name,sql,mat)], sqlSelectRecursiveWith = recurse <> sqlSelectRecursiveWith cmd }

instance SqlWith SqlInsertSelect where
sqlWith1 cmd name sql mat recurse = cmd { sqlInsertSelectWith = sqlInsertSelectWith cmd ++ [(name,sql,mat)], sqlInsertSelectRecursiveWith = recurse }
sqlWith1 cmd name sql mat recurse = cmd { sqlInsertSelectWith = sqlInsertSelectWith cmd ++ [(name,sql,mat)], sqlInsertSelectRecursiveWith = recurse <> sqlInsertSelectRecursiveWith cmd }

instance SqlWith SqlUpdate where
sqlWith1 cmd name sql mat recurse = cmd { sqlUpdateWith = sqlUpdateWith cmd ++ [(name,sql,mat)], sqlUpdateRecursiveWith = recurse }
sqlWith1 cmd name sql mat recurse = cmd { sqlUpdateWith = sqlUpdateWith cmd ++ [(name,sql,mat)], sqlUpdateRecursiveWith = recurse <> sqlUpdateRecursiveWith cmd }

instance SqlWith SqlDelete where
sqlWith1 cmd name sql mat recurse = cmd { sqlDeleteWith = sqlDeleteWith cmd ++ [(name,sql,mat)], sqlDeleteRecursiveWith = recurse }
sqlWith1 cmd name sql mat recurse = cmd { sqlDeleteWith = sqlDeleteWith cmd ++ [(name,sql,mat)], sqlDeleteRecursiveWith = recurse <> sqlDeleteRecursiveWith cmd }

sqlWith :: (MonadState v m, SqlWith v, Sqlable s) => SQL -> s -> m ()
sqlWith name sql = modify (\cmd -> sqlWith1 cmd name (toSQLCommand sql) NonMaterialized NonRecursive)
Expand Down
9 changes: 7 additions & 2 deletions test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1282,8 +1282,6 @@ testSqlWith step = do

testSqlWithRecursive :: HasCallStack => (String -> TestM ()) -> TestM ()
testSqlWithRecursive step = do
step "Checking recursive support"
checkAndRememberRecursiveSupport
step "Running WITH RECURSIVE tests"
testPass
where
Expand Down Expand Up @@ -1318,6 +1316,13 @@ testSqlWithRecursive step = do
sqlResult "child.cartel_boss_id"
sqlJoinOn "rcartel rcartel1" "child.cartel_boss_id = rcartel1.cartel_member_id"
]
-- This dummy with is not actually used
-- It's just here to test that further "sqlWith" do not remove the RECURSIVE
-- keyword when actually producing the SQL
sqlWith "lcartel" $ do
sqlSelect "rcartel c" $ do
sqlResult "c.cartel_member_id"
sqlResult "c.cartel_boss_id"
sqlResult "member.firstname"
sqlResult "member.lastname"
sqlResult "boss.firstname"
Expand Down

0 comments on commit 2f7cf86

Please sign in to comment.