| Copyright | (c) digitally induced GmbH 2026 |
|---|---|
| Safe Haskell | None |
| Language | GHC2021 |
IHP.FetchPipelined
Description
Uses PostgreSQL's pipeline mode (via hasql) to send multiple independent queries in a single network round trip. This is especially beneficial for cloud database deployments where round-trip latency is 1-5ms.
Compose queries using do notation (via ApplicativeDo):
(users, posts) <- pipeline do
users <- query @User |> fetchPipelined
posts <- query @Post |> orderByDesc #createdAt |> fetchPipelined
pure (users, posts)Pipeline is Applicative but NOT Monad, which enforces at the type level
that only independent queries can be pipelined. ApplicativeDo desugars the
do block into applicative operations, so each line runs as a separate query
in the same pipeline batch.
Synopsis
- fetchPipelined :: forall model (table :: Symbol). (Table model, model ~ GetModelByTableName table, KnownSymbol table, FromRowHasql model) => QueryBuilder table -> Pipeline [model]
- fetchVectorPipelined :: forall model (table :: Symbol). (Table model, model ~ GetModelByTableName table, KnownSymbol table, FromRowHasql model) => QueryBuilder table -> Pipeline (Vector model)
- fetchOneOrNothingPipelined :: forall model (table :: Symbol). (Table model, model ~ GetModelByTableName table, KnownSymbol table, FromRowHasql model) => QueryBuilder table -> Pipeline (Maybe model)
- fetchCountPipelined :: forall (table :: Symbol). KnownSymbol table => QueryBuilder table -> Pipeline Int
- fetchExistsPipelined :: forall (table :: Symbol). KnownSymbol table => QueryBuilder table -> Pipeline Bool
- pipeline :: (?modelContext :: ModelContext) => Pipeline a -> IO a
- data Pipeline a
Documentation
fetchPipelined :: forall model (table :: Symbol). (Table model, model ~ GetModelByTableName table, KnownSymbol table, FromRowHasql model) => QueryBuilder table -> Pipeline [model] Source #
Convert a query builder into a Pipeline step returning all matching rows.
Example: Fetching users and posts in a single round trip
(users, posts) <- pipeline do
users <- query @User |> filterWhere (#active, True) |> fetchPipelined
posts <- query @Post |> orderByDesc #createdAt |> fetchPipelined
pure (users, posts)fetchVectorPipelined :: forall model (table :: Symbol). (Table model, model ~ GetModelByTableName table, KnownSymbol table, FromRowHasql model) => QueryBuilder table -> Pipeline (Vector model) Source #
Like fetchPipelined, but returns a Vector instead of a list.
Example:
(users, posts) <- pipeline do
users <- query @User |> fetchVectorPipelined
posts <- query @Post |> fetchVectorPipelined
pure (users, posts)fetchOneOrNothingPipelined :: forall model (table :: Symbol). (Table model, model ~ GetModelByTableName table, KnownSymbol table, FromRowHasql model) => QueryBuilder table -> Pipeline (Maybe model) Source #
Convert a query builder into a Pipeline step returning at most one row.
Example:
(maybeUser, posts) <- pipeline do
maybeUser <- query @User |> filterWhere (#email, email) |> fetchOneOrNothingPipelined
posts <- query @Post |> fetchPipelined
pure (maybeUser, posts)fetchCountPipelined :: forall (table :: Symbol). KnownSymbol table => QueryBuilder table -> Pipeline Int Source #
Convert a query builder into a Pipeline step returning a count.
Example:
(users, userCount) <- pipeline do
users <- query @User |> fetchPipelined
userCount <- query @User |> filterWhere (#active, True) |> fetchCountPipelined
pure (users, userCount)fetchExistsPipelined :: forall (table :: Symbol). KnownSymbol table => QueryBuilder table -> Pipeline Bool Source #
Convert a query builder into a Pipeline step returning a boolean.
Example:
(users, hasUnread) <- pipeline do
users <- query @User |> fetchPipelined
hasUnread <- query @Message |> filterWhere (#isUnread, True) |> fetchExistsPipelined
pure (users, hasUnread)pipeline :: (?modelContext :: ModelContext) => Pipeline a -> IO a Source #
Execute a Pipeline in a single database round trip.
When row-level security (RLS) is enabled, the pipeline is automatically wrapped
with set_config / reset statements to preserve the request's RLS context.
These are included in the same pipeline batch, adding no extra round trips.
Example:
action DashboardAction = do
(users, posts, commentCount) <- pipeline do
users <- query @User |> fetchPipelined
posts <- query @Post |> orderByDesc #createdAt |> limit 10 |> fetchPipelined
commentCount <- query @Comment |> fetchCountPipelined
pure (users, posts, commentCount)
render DashboardView { .. }Composable abstraction over the execution of queries in the pipeline mode.
It allows you to issue multiple queries to the server in much fewer network transactions. If the amounts of sent and received data do not surpass the buffer sizes in the driver and on the server it will be just a single roundtrip. Typically the buffer size is 8KB.
This execution mode is much more efficient than running queries directly from Session, because in session every statement execution involves a dedicated network roundtrip.
An obvious question rises then: why not execute all queries like that?
In situations where the parameters depend on the result of another query it is impossible to execute them in parallel, because the client needs to receive the results of one query before sending the request to execute the next.
This reasoning is essentially the same as the one for the difference between Applicative and Monad.
That's why Pipeline does not have the Monad instance.
To execute Pipeline lift it into Session via pipeline.
Examples
Insert-Many or Batch-Insert
You can use pipeline to turn a single-row insert query into an efficient multi-row insertion session. In effect this should be comparable in performance to issuing a single multi-row insert statement.
Given the following definition in a Statements module:
insertOrder :: Statement OrderDetails OrderId
You can lift it into the following session
insertOrders :: [OrderDetails] ->Session[OrderId] insertOrders orders =pipeline$ for orders $ \order ->statementorder Statements.insertOrder
Combining Queries
Given the following definitions in a Statements module:
selectOrderDetails ::StatementOrderId (Maybe OrderDetails) selectOrderProducts ::StatementOrderId [OrderProduct] selectOrderFinancialTransactions ::StatementOrderId [FinancialTransaction]
You can combine them into a session using the ApplicativeDo extension as follows:
selectEverythingAboutOrder :: OrderId ->Session(Maybe OrderDetails, [OrderProduct], [FinancialTransaction]) selectEverythingAboutOrder orderId =pipeline$ do details <-statementorderId Statements.selectOrderDetails products <-statementorderId Statements.selectOrderProducts transactions <-statementorderId Statements.selectOrderFinancialTransactions pure (details, products, transactions)