orville-postgresql-1.0.0.0: A Haskell library for PostgreSQL
CopyrightFlipstone Technology Partners 2023
LicenseMIT
StabilityStable
Safe HaskellSafe-Inferred
LanguageHaskell2010

Orville.PostgreSQL.Plan

Description

Since: 1.0.0.0

Synopsis

Documentation

data Plan scope param result Source #

A Plan is an executable set of queries that can be executed to load data from the database, using the results of prior queries as input parameters to following queries in controlled ways. In particular, the "controlled" aspect of this allows plans that take a single input to be adapted to take multiple input parameters in a list without the resulting plan executing N+1 queries. This restriction means that while query results can be used as input parameters to later queries, they cannot be used to decide to run completely different queries based on other query results. Allowing this would prevent the Plan structure from eliminating N+1 query loops.

Note that during execution, queries are never combined across tables to form joins or subqueries. Queries are still executed in the same sequence as specified in the plan, just on all the inputs at once rather than in a loop. If you need to do a join with a plan, you can always construct your own custom Operation and use planOperation to incorporate it into a plan.

The param type variable indicates what type of value is expected as input when the plan is executed.

The result type for a plan indicates what Haskell type is produced when the plan is executed.

The scope type is used internally by Orville to track how the plan is currently executed against a single input or multiple inputs. This type parameter should never be specified as a concrete type in user code, but must be exposed as a variable to ensure that execute scope is tracked correctly through usages of bind.

Since: 1.0.0.0

Instances

Instances details
Applicative (Plan scope param) Source # 
Instance details

Defined in Orville.PostgreSQL.Plan

Methods

pure :: a -> Plan scope param a #

(<*>) :: Plan scope param (a -> b) -> Plan scope param a -> Plan scope param b #

liftA2 :: (a -> b -> c) -> Plan scope param a -> Plan scope param b -> Plan scope param c #

(*>) :: Plan scope param a -> Plan scope param b -> Plan scope param b #

(<*) :: Plan scope param a -> Plan scope param b -> Plan scope param a #

Functor (Plan scope param) Source # 
Instance details

Defined in Orville.PostgreSQL.Plan

Methods

fmap :: (a -> b) -> Plan scope param a -> Plan scope param b #

(<$) :: a -> Plan scope param b -> Plan scope param a #

data Planned scope param a Source #

A Planned value is a wrapper around the results of previously-run queries when using the bind function. At the time that you are writing a plan, you do not know whether the Plan will be run with a single input or multiple inputs. A Planned value may end up being either an individual item or a list of items. Due to this, your ability to interact with the value is limited to the use of fmap to extract (or build) other values from the results. Planned values can be used together with the use function to make a Plan that produces the extracted value.

Note that while Planned could provide an Applicative instance as well, it does not to avoid confusion with the Applicative instance for Plan itself. If you need to build a value from several Planned values using Applicative, you should call use on each of the values and use the Applicative instance for Plan.

Since: 1.0.0.0

Instances

Instances details
Functor (Planned scope param) Source # 
Instance details

Defined in Orville.PostgreSQL.Plan

Methods

fmap :: (a -> b) -> Planned scope param a -> Planned scope param b #

(<$) :: a -> Planned scope param b -> Planned scope param a #

data Execute Source #

Execute is a tag type used as the scope variable for Plan values when executing them via the execute function.

Since: 1.0.0.0

data Explain Source #

Explain is a tag type used as the scope variable when explaining a Plan via the explain function.

Since: 1.0.0.0

askParam :: Plan scope param param Source #

askParam allows the input parameter for the plan to be retrieved as the result of the plan. Together with bind you can use this to get access to the input parameter as a Planned value.

Since: 1.0.0.0

Using a Plan after it is constructed

execute :: MonadOrville m => Plan Execute param result -> param -> m result Source #

execute accepts the input parameter (or parameters) expected by a Plan and runs the plan to completion, either throwing an AssertionFailed exception in the monad m or producing the expected result.

If you have a plan that takes one input and want to provide a list of input, use planMany to adapt it to a multple-input plan before calling execute.

Since: 1.0.0.0

explain :: Plan Explain param result -> [String] Source #

explain produces a textual description of the steps outlined by a Plan -- in most cases example SQL queries. If you want to see the explanation of how the plan will run with multiple input parameters, you can use planMany to adapt it before calling explain.

Since: 1.0.0.0

Making a Plan to find rows in the database

findMaybeOne :: Ord fieldValue => TableDefinition key writeEntity readEntity -> FieldDefinition nullability fieldValue -> Plan scope fieldValue (Maybe readEntity) Source #

findMaybeOne constructs a Plan that will find at most one row from the given table where the plan's input value matches the given database field.

Since: 1.0.0.0

findMaybeOneWhere :: Ord fieldValue => TableDefinition key writeEntity readEntity -> FieldDefinition nullability fieldValue -> BooleanExpr -> Plan scope fieldValue (Maybe readEntity) Source #

findMaybeOneWhere is similar to findMaybeOne, but allows a BooleanExpr to be specified to restrict which rows are matched by the database query.

Since: 1.0.0.0

findOne :: (Show fieldValue, Ord fieldValue) => TableDefinition key writeEntity readEntity -> FieldDefinition nullability fieldValue -> Plan scope fieldValue readEntity Source #

findOne is an alias to findOneShowVia that uses the Show instance of fieldValue when producing a failure message in the event that the entity cannot be found.

Since: 1.0.0.0

findOneShowVia :: Ord fieldValue => (fieldValue -> String) -> TableDefinition key writeEntity readEntity -> FieldDefinition nullability fieldValue -> Plan scope fieldValue readEntity Source #

findOneShowVia is similar to findMaybeOne, but it expects that there will always be a row found matching the plan's input value. If no row is found, an AssertionFailed exception will be thrown. This is a useful convenience when looking up foreign-key associations that are expected to be enforced by the database itself.

Since: 1.0.0.0

findOneWhere :: (Show fieldValue, Ord fieldValue) => TableDefinition key writeEntity readEntity -> FieldDefinition nullability fieldValue -> BooleanExpr -> Plan scope fieldValue readEntity Source #

findOneWhere is an alias to findOneWhereShowVia that uses the Show instance of fieldValue when producing a failure message in the event that the entity cannot be found.

Since: 1.0.0.0

findOneWhereShowVia :: Ord fieldValue => (fieldValue -> String) -> TableDefinition key writeEntity readEntity -> FieldDefinition nullability fieldValue -> BooleanExpr -> Plan scope fieldValue readEntity Source #

findOneWhereShowVia is similar to findOneShowVia, but allows a BooleanExpr to be specified to restrict which rows are matched by the database query.

Since: 1.0.0.0

findAll :: Ord fieldValue => TableDefinition key writeEntity readEntity -> FieldDefinition nullability fieldValue -> Plan scope fieldValue [readEntity] Source #

findAll constructs a Plan that will find all the rows from the given table where the plan's input value matches the given database field.

Since: 1.0.0.0

findAllWhere :: Ord fieldValue => TableDefinition key writeEntity readEntity -> FieldDefinition nullability fieldValue -> BooleanExpr -> Plan scope fieldValue [readEntity] Source #

findAllWhere is similar to findAll, but allows a BooleanExpr to be specified to restrict which rows are matched by the database query.

Since: 1.0.0.0

Creating a multi-step Plan from other Plan values

bind :: Plan scope param a -> (Planned scope param a -> Plan scope param result) -> Plan scope param result Source #

bind gives access to the results of a plan to use as input values to future plans. The plan result is given the input parameter to the provided function, which must produce the remaining Plan to be executed. The value will be wrapped in the Planned type, which may represent either a result or multiple results, depending on whether one plan is currently being executed with one and multiple input parameters. This ensures that the caller produces only a single remaining Plan to be used for all inputs when there are multiple to eliminate the need to possibly run different queries for different inputs (which would an introduce N+1 query execution).

The Planned value (or values) provided by bind have actually been retrieved from the database, so the value can be used multiple times when constructing the remaining Plan without fear of causing the query to run multiple times.

Also see use for how to lift a Planned value back into a Plan.

Since: 1.0.0.0

use :: Planned scope param a -> Plan scope param a Source #

use constructs a Plan that always produces the Planned value as its result, regardless of the parameter given as input to the plan.

Since: 1.0.0.0

using :: Planned scope param a -> Plan scope a b -> Plan scope param b Source #

using uses a Planned value in the input to another Plan. The resulting plan will ignore its input and use the Planned value as the input to produce its result instead.

Since: 1.0.0.0

chain :: Plan scope a b -> Plan scope b c -> Plan scope a c Source #

chain connects the output of one plan to the input of another to form a larger plan that will execute the first followed by the second.

Since: 1.0.0.0

chainMaybe :: Plan scope a (Maybe b) -> Plan scope b (Maybe c) -> Plan scope a (Maybe c) Source #

chainMaybe connects two plans that both yield Maybes. If the first plan yields no result, the second is skipped. See also chain.

Since: 1.0.0.0

apply :: Plan scope param (a -> b) -> Plan scope param a -> Plan scope param b Source #

apply applies a function produced by a plan to the value produced by another plan. This is usually used via the <*> operator through the Applicative instance for Plan.

Since: 1.0.0.0

planMany :: (forall manyScope. Plan manyScope param result) -> Plan scope [param] (Many param result) Source #

planMany adapts a plan that takes a single input parameter to work on multiple input parameters. When the new plan is executed, each query will execute in the same basic order, but with adjusted conditions to find all the rows for all inputs at once rather than running the planned queries once for each input.

Since: 1.0.0.0

planList :: (forall scope. Plan scope param result) -> Plan listScope [param] [result] Source #

planList lifts a plan so both its param and result become lists. This saves you from having to fmap in elems when all you want back from a Many is the list of results inside it.

There will always be the same number of elements in the [result] list as there are in the [param] list, even if there are duplicate values in the input parameters. This may be counter-intuitive in the trivial case where a plan that queries a single table is passed to planList but cannot be avoided due to more complicated situations where the original plan executes queries against multiple tables. When a plan that queries multiple tables is passed, the query results must be correlated based on the input parameters to build each result value.

Since: 1.0.0.0

focusParam :: (a -> b) -> Plan scope b result -> Plan scope a result Source #

focusParam builds a plan from a function and an existing plan, taking the result of that function as input. This is especially useful when there is some structure, and a plan that only needs a part of that structure as input. The function argument can access part of the structure for the plan argument to use, so the final returned plan can take the entire structure as input.

Since: 1.0.0.0

planEither :: Plan scope leftParam leftResult -> Plan scope rightParam rightResult -> Plan scope (Either leftParam rightParam) (Either leftResult rightResult) Source #

planEither lets you construct a plan that branches by executing a different plan for the Left and Right sides of an Either value. When used with a single input parameter, only one of the two plans will be used, based on the input parameter. When used on multiple input parameters, each of the two plans will be executed only once with all the Left and Right values provided as input parameters respectively.

Since: 1.0.0.0

planMaybe :: Plan scope a b -> Plan scope (Maybe a) (Maybe b) Source #

planMaybe lifts a plan so both its param and result become Maybes. This is useful when modifying an existing plan to deal with optionality. Writing just one plan can then easily produce both the required and optional versions.

Since: 1.0.0.0

Bridges from other types into Plan

data AssertionFailed Source #

AssertionFailed may be returned from the execute functions of an Operation to indicate that some expected invariant has failed. For example, following a foreign key that is enforced by the database only to find that no record exists. When an Operation returns an AssertionFailed value during plan execution, the error is thrown as an exception using the MonadThrow instance for whatever monad the plan is executing in.

Since: 1.0.0.0

assert :: (param -> a -> Either String b) -> Plan scope param a -> Plan scope param b Source #

assert allows you to make an assertion about a plan's result that will throw an AssertionFailed exception during execution if it proves to be false. The first parameter is the assertion function, which should return either an error message to be given in the exception or the value to be used as the plan's result.

Since: 1.0.0.0

planSelect :: Select row -> Plan scope () [row] Source #

planSelect allows any Orville Select query to be incorporated into a plan. Note that the Select cannot depend on the plan's input parameters in this case. If the plan is executed with multiple inputs, the same set of all the results will be used as the results for each of the input parameters.

Since: 1.0.0.0

planOperation :: Operation param result -> Plan scope param result Source #

planOperation allows any primitive Operation to be used as an atomic step in a plan. When the plan is executed, the appropriate Operation functions will be used depending on the execution context.

Since: 1.0.0.0