Skip to content

Commit

Permalink
Move tutorial documentation into Read the Docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ocharles committed Mar 16, 2017
1 parent 457de97 commit f8d8c9d
Show file tree
Hide file tree
Showing 3 changed files with 292 additions and 285 deletions.
286 changes: 1 addition & 285 deletions Rel8.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
{-# LANGUAGE UndecidableInstances #-}

module Rel8
( -- $intro

-- * Defining Tables
( -- * Defining Tables
C
, Anon
, HasDefault(..)
Expand Down Expand Up @@ -45,7 +43,6 @@ module Rel8
, asc, desc, orderNulls, O.orderBy, OrderNulls(..)

-- * Aggregation
-- $aggregation
, aggregate
, AggregateTable
, count, groupBy, DBSum(..), countStar, DBMin(..), DBMax(..), DBAvg(..)
Expand Down Expand Up @@ -283,284 +280,3 @@ unionAll =
(\prim1 prim2 -> f (prim1, prim2))
(view expressions l)
(view expressions r)))))))



{- $intro
Welcome to @rel8@!
@rel8@ is a library that builds open the fantastic @opaleye@ library to
query databases, and provides a slightly alternative API. The main objectives
of @rel8@ are:
* /Conciseness/: Users using @rel8@ should not need to write boiler-plate
code. By using expressive types, we can provide sufficient information
for the compiler to infer code whenever possible.
* /Inferrable/: Despite using a lot of type level magic, it should never
be a requirement that the user must provide a type signature to allow a
program to compile.
* /Compatible/: @rel8@ tries to use the existing @opaleye@ API as much as
possible.
Now, let's dive in and see an example of a program using @rel8@.
=== Required language extensions and imports
@
{ -# LANGUAGE Arrows, DataKinds, DeriveGeneric, FlexibleInstances,
OverloadedStrings #- }
import Control.Applicative
import Control.Arrow
import Rel8
@
To use @rel8@, you will need a few language extensions:
* @Arrows@ is necessary to use @proc@ notation. As with @opaleye@, @rel8@
uses arrows to guarantee queries are valid.
* @DataKinds@ is used to promote values to the type level when defining
table/column metadata.
* @DeriveGeneric@ is used to automatically derive functions from schema
information.
The others are used to provide the type system extensions needed by @rel8@.
=== Defining base tables
In order to query a database of existing tables, we need to let @rel8@ know
about these tables, and the schema for each table. This is done by defining
a Haskell /record/ for each table in the database. These records should have
a type of the form @C f name hasDefault t@. Let's see how that looks with some
example tables:
@
data Part f = Part
{ partId :: 'C' f \"PID\" ''HasDefault' Int
, partName :: 'C' f \"PName\" ''NoDefault' String
, partColor :: 'C' f \"Color\" ''NoDefault' Int
, partWeight :: 'C' f \"Weight\" ''NoDefault' Double
, partCity :: 'C' f \"City\" ''NoDefault' String
} deriving (Generic)
instance 'BaseTable' Part where 'tableName' = \"part\"
instance 'Table' (Part 'Expr') (Part 'QueryResult')
@
The @Part@ table has 5 columns, each defined with the @C f ..@
pattern. For each column, we are specifying:
1. The column name
2. Whether or not this column has a default value when inserting new rows.
In this case @partId@ does, as this is an auto-incremented primary key
managed by the database.
3. The type of the column.
After defining the table, we finally need to make instances of 'BaseTable' and
'Table' so @rel8@ can query this table. By using @deriving (Generic)@, we
simply need to write @instance BaseTable Part where tableName = "part"@. The
'Table' instance demonstrates that a @Part Expr@ value can be selected from
the database as @Part QueryResult@.
=== Querying tables
With tables defined, we are now ready to write some queries. All 'BaseTable's
give rise to a query - the query of all rows in that table:
@
allParts :: 'O.Query' (Part 'Expr')
allParts = queryTable
@
Notice the type of @allParts@ specifies that we're working with @Part Expr@.
This means that the contents of the @Part@ record will contain expressions -
one for each column in the table. As 'O.Query' is a 'Functor', we can derive
a new query for all part cities in the database:
@
allPartCities :: 'O.Query' ('Expr' String)
allPartCities = partCity \<$\> allParts
@
Now we have a query containing just one column - expressions of type 'String'.
=== @WHERE@ clauses
Usually when we are querying database, we are querying for subsets of
information. In SQL, we apply predicates using @WHERE@ - and @rel8@ supports
this too, in two forms.
Firstly, we can use 'filterQuery', similar to how we would use 'filter':
@
londonParts :: 'O.Query' (Part 'Expr')
londonParts = 'filterQuery' (\\p -> partCity p '==.' \"London\") allParts
@
'filterQuery' takes a function from rows in a query to a predicate. In this
case we can use '==.' to compare to expressions for equality. On the left,
@partCity p :: Expr String@, and on the right @\"London\" :: Expr String@ (
the literal string @London@).
Alternatively, we can use 'where_' with arrow notation, which is similar to
using 'guard' with 'MonadPlus':
@
heavyParts :: 'O.Query' (Part 'Expr')
heavyParts = proc _ -> do
part <- 'queryTable' -< ()
'where_' -\< partWeight part '>.' 5
returnA -< part
@
== Joining Queries
@rel8@ supports joining multiple queries into one, in a few different ways.
=== Products and Inner Joins
We can take the product of two queries - each row of the first query
paired with each row of the second query - by sequencing queries inside a
'O.Query'. Let's introduce another table:
@
data Supplier f = Supplier
{ supplierId :: 'C' f \"SID\" ''HasDefault' Int
, supplierName :: 'C' f \"SName\" ''NoDefault' String
, supplierStatus :: 'C' f \"Status\" ''NoDefault' Int
, supplierCity :: 'C' f \"City\" ''NoDefault' String
} deriving (Generic)
instance 'BaseTable' Supplier where 'tableName' = "supplier"
instance 'Table' (Supplier 'Expr') (Supplier 'QueryResult')
@
We can take the product of all parts paired against all suppliers:
@
allPartsAndSuppliers :: 'O.Query' (Part 'Expr', Supplier 'Expr')
allPartsAndSuppliers = proc _ -> do
part <- 'queryTable' -< ()
supplier <- 'queryTable' -< ()
returnA -< (part, supplier)
@
We could write this a little more succinctly using using the @Applicative@
instance for 'O.Query', as '<*>' corresponds to products:
@
allPartsAndSuppliers2 :: 'O.Query' (Part 'Expr', Supplier 'Expr')
allPartsAndSuppliers2 = liftA2 (,) 'queryTable' 'queryTable'
@
In both queries, we've just used 'queryTable' to select the necessary rows.
'queryTable' is overloaded, but by knowing the type of rows to select, it
will change which table it queries from.
We can combine products with the techniques we've just seen in order to
produce the inner join of two tables. For example, here is a query to pair
up each part with all suppliers in the same city.
@
partsAndSuppliers :: Query (Part Expr, Supplier Expr)
partsAndSuppliers =
'filterQuery'
(\(part, supplier) -> partCity part '==.' supplierCity supplier)
allPartsAndSuppliers
@
=== Left Joins
The previous query gave us parts with /at least one/ supplier in the same
city. If a part has no suppliers in the same city, it will be omitted from
the results. But what if we needed this information? In SQL we can capture
this with a @LEFT JOIN@, and @rel8@ supports this.
Left joins can be introduced with the 'leftJoin', which takes two queries,
or using arrow notation with `leftJoinA`. Let's look at the latter, as it
is often more concise.
@
partsAndSuppliersLJ :: Query (Part Expr, MaybeTable (Supplier Expr))
partsAndSuppliersLJ = proc _ -> do
part <- queryTable -< ()
maybeSupplier
<- leftJoinA queryTable
-\< \\supplier -> partCity part ==. supplierCity supplier
returnA -< (part, maybeSupplier)
@
This is a little different to anything we've seen so far, so let's break it
down. 'leftJoinA' takes as its first argument the query to join in. In this
case we just use 'queryTable' to select all supplier rows. @LEFT JOIN@s
also require a predicate, and we supply this as /input/ to @leftJoinA@. The
input is itself a function, a function from rows in the to-be-joined table
to booleans. Notice that in this predicate, we are free to refer to tables and
columns already in the query (as @partCity part@ is not part of the supplier
table).
Left joins themselves can be filtered, as they are just another query.
However, the results of a left join are wrapped in 'MaybeTable', which
indicates that /all/ of the columns in this table might be null, if the
join failed to match any rows. We can use this information with our
@partsAndSuppliersLJ@ query to find parts where there are no suppliers in the
same city:
@
partsWithoutSuppliersInCity :: Query (Part Expr)
partsWithoutSuppliersInCity = proc _ -> do
(part, maybeSupplier) <- partsAndSuppliersLJ -< ()
where_ -< isNull (maybeSupplier $? supplierId)
returnA -< part
@
We are filtering our query for suppliers where the id is null. Ordinarily
this would be a type error - we declared that @supplierId@ contains @Int@,
rather than @Maybe Int@. However, because suppliers come from a left join,
when we project out from 'MaybeTable' /all/ columns become nullable. It may
help to think of @($?)@ as having the type:
@
($?) :: (a -> Expr b) -> MaybeTable a -> Expr (Maybe b)
@
though in @rel8@ we're a little bit more general.
-}


{- $aggregation
To aggregate a series of rows, use the 'aggregate' query transform.
@aggregate@ takes a 'Query' that returns any 'AggregateTable' as a result.
@AggregateTable@s are like @Tables@, except that all expressions describe
a way to aggregate data. While tuples are instances of @AggregateTable@,
it's recommended to introduce new data types to represent aggregations for
clarity:
=== Example
@
data UserInfo f = UserInfo
{ userCount :: Anon f Int64
, latestLogin :: Anon f UTCTime
, uType :: Anon f Type
} deriving (Generic)
instance AggregateTable (UserInfo Aggregate) (UserInfo Expr)
userInfo :: Query (UserInfo Expr)
userInfo = aggregate $ proc _ -> do
user <- queryTable -< ()
returnA -< UserInfo { userCount = count (userId user)
, latestLogin = max (userLastLoggedIn user)
, uType = groupBy (userType user)
}
@
-}
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Welcome to Rel8!
:maxdepth: 2
:caption: Contents:

tutorial
differences


Expand Down

0 comments on commit f8d8c9d

Please sign in to comment.