Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bluefin-opaleye/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Haskell Package Versioning Policy](https://pvp.hask
- Support `bluefin` versions `0.2.7 && < 0.6` in [#4](https://github.com/fpringle/bluefin-postgresql/pull/4).
- Extract common operation counting code to [postgresql-operation-counting](https://github.com/fpringle/postgresql-operation-counting) in [#6](https://github.com/fpringle/bluefin-postgresql/pull/6). Breaking change.
- Support `bluefin < 0.11` in [#8](https://github.com/fpringle/bluefin-postgresql/pull/8).
- Fix `Bluefin.Opaleye.Count` module docs in [#9](https://github.com/fpringle/bluefin-postgresql/pull/9).

## [0.1.0.0] - 27.02.2026

Expand Down
40 changes: 25 additions & 15 deletions bluefin-opaleye/src/Bluefin/Opaleye/Count.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ We would expect the number of SELECTs to remain basically constant (O(1)), while
A very naive implementation might be:

@
insertUsersNaive :: ('Opaleye' :> es) => [User] -> Eff es ()
insertUsersNaive users = for_ users $ \user -> do
insertUserFlat user
insertUsersNaive :: (e :> es) => 'Opaleye' e -> [User] -> Eff es ()
insertUsersNaive o users = for_ users $ \user -> do
insertUserFlat o user
for (transactions user) $ \transaction -> do
insertTransactionFlat transaction
insertTransactionFlat o transaction
for (subTransactions transaction) $ \subTransaction -> do
insertSubTransactionFlat subTransaction
insertSubTransactionFlat o subTransaction
@

However, if we ran a "benchmark" that looked something like this:
Expand All @@ -33,13 +33,23 @@ u1, u5, u10, u50 :: [User]
u1 = [User {transactions = [Transaction [SubTransaction]]}] -- one user, one transaction, one sub-transaction
u5 = ... -- five users, each with five transactions, each with 5 sub-transactions

benchmark :: ('Opaleye' :> es, State SQLOperationCounts :> es, IOE :> es) => Eff es ()
benchmark = for_ [(1, u1), (5, u5), (10, u10), (50, u50)] $ \(n, users) -> do
(counts, ()) <- withCounts $ insertUsersNaive users
liftIO . putStrLn $ "Counts at n=" <> show n <> ": " <> 'renderCountsBrief' counts
benchmark ::
(e :> es, e1 :> es, e2 :> es) =>
'Opaleye' e ->
State SQLOperationCounts e1 ->
IOE e2 ->
Eff es ()
benchmark o st ioe = for_ [(1, u1), (5, u5), (10, u10), (50, u50)] $ \(n, users) -> do
(counts, ()) <- withCounts st $ insertUsersNaive o users
effIO ioe . putStrLn $ "Counts at n=" <> show n <> ": " <> 'renderCountsBrief' counts

main :: IO ()
main = runEff . 'Conn.runWithConnectInfo' connInfo . evalState @SQLOperationCounts mempty . runOpaleyeWithConnectionCounting $ benchmark
main =
runEff $ \ioe ->
'Conn.runWithConnectInfo' ioe connInfo $ \withConn ->
evalState @SQLOperationCounts mempty $ \st ->
runOpaleyeWithConnectionCounting withConn ioe st $ \o ->
benchmark o st ioe
where
connInfo = ...
@
Expand All @@ -56,13 +66,13 @@ Counts at n=50: INSERT: 127550
This is obviously going to have a severe performance impact. Rearranging our implementation of @insertUsers@:

@
insertUsersBetter :: ('Opaleye' :> es) => [User] -> Eff es ()
insertUsersBetter users = do
insertUsersBetter :: (e :> es) => 'Opaleye' e -> [User] -> Eff es ()
insertUsersBetter o users = do
let transactions_ = concatMap transactions users
subTransactions_ = concatMap subTransactions transactions_
insertUsersFlat users
insertTransactionsFlat transactions_
insertSubTransactionsFlat subTransactions_
insertUsersFlat o users
insertTransactionsFlat o transactions_
insertSubTransactionsFlat o subTransactions_
@

As long as @insertTransactionsFlat@ etc are smart enough to only do one 'runInsert', then we should now get:
Expand Down
Loading