Skip to content

Databases

In addition to defining types for each of your tables, beam also requires you to declare your database as a type with fields for holding all entities in your database. This includes more than just tables. For example, user-defined types that you would like to work with must also be included in your database type.

A simple database type

Like tables, a database type takes a functor and applies it to each entity in the database. For example, a database type for the two tables defined above has the form.

data ExampleDb f
    = ExampleDb
    { persons :: f (TableEntity PersonT)
    , posts   :: f (TableEntity PersonT)
    } deriving Generic
instance Database be ExampleDb

exampleDb :: DatabaseSettings be ExampleDb
exampleDb = defaultDbSettings

Other database entities

Views

Some databases also offer the concept of 'views' -- pseudo-tables that are built from a pre-defined query. Suppose we wanted to create a view that returned the latest comments and their respective posters.

data PostAndPosterView f
    = PostAndPosterView
    { post   :: PostT f
    , poster :: PersonT f
    } deriving Generic
instance Beamable PostAndPosterView

We can include this in our database:

data ExampleDb f
    = ExampleDb
    { persons        :: f (TableEntity PersonT)
    , posts          :: f (TableEntity PersonT)
    , postAndPosters :: f (ViewEntity PostAndPosterView)
    } deriving Generic

Now we can use postAndPosters wherever we'd use a table. Note that you do not need to specify the definition of the view. The definition is not important to access the view, so beam does not need to know about it at the type-level. If you want to manipulate view definitions, use the migrations package.

Note that the all_ query primitive requires a TableEntity. Thus, all_ (postAndPosters exampleDb) will fail to type-check. Use the allFromView_ combinator instead.

Note

You could also declare a view as a TableEntity. The main advantage of declaring an entity as ViewEntity is that you will be prevented by the Haskell type system from constructing INSERTs, UPDATEs, and DELETEs using your view. Also, beam-migrate will not recognize database schema equivalence if a view is declared as a table or vice versa.

Domain types

Domain types are a way of creating new database types with additional constraints. Beam supports declaring these types as part of your database, so they can be used anywhere a data type can. In order to use your domain type, you need to supply beam a Haskell newtype that is used to represent values of this type in Haskell.

Character sets

Beam does not yet support character sets. Support is planned in future releases.

Collations

Beam does not yet support collations. Support is planned in future releases.

Translations

Beam does not yet support translations. Support is planned in future releases.

Other database entities

Other standard SQL database entities (like triggers) are defined by beam-migrate as they have no effect on query semantics.

Database descriptors

In order to interact with the database, beam needs to know more about the data structure, it also needs to know how to refer to each entity in your database. For the most part, beam can figure out the names for you using its Generics-based defaulting mechanims. Once you have a database type defined, you can create a database descriptor using the defaultDbSettings function.

For example, to create a backend-agnostic database descriptor for the ExampleDb type:

exampleDb :: DatabaseSettings be ExampleDb
exampleDb = defaultDbSettings

The defaultDbSettings function produces a settings value where each entity is given a default name as explained in the previous section.

Now, we can use the entities in exampleDb to write queries. The rules for name defaulting for database entities are the same as those for table fields

Modifying the defaults

The withDbModification function can be used to modify the output of the defaultDbSettings. It combines a database settings value with a database modifications value. The easiest way to construct a database modification value is with the dbModification function, which produces a modification that makes no changes.

You can then use Haskell record syntax to specify table or other entity modifications. For example, the modifyTable function can be used to produce a table modification given a modifier function for the table name and a table modification. Like database modifications, an identity table modification can be constructed with the tableModification function. Modifications to field names con be accomplished using Haskell record syntax on the result of tableModification. The fieldNamed field modification will give a field an explicit new name.

For example, to rename the persons table as people in the database above,

exampleDb :: DatabaseSettings be ExampleDb
exampleDb = defaultDbSettings `withDbModification`
            dbModification {
              persons = modifyTable (\_ -> "people") tableModification
            }

Or, to keep the persons table named as it is, but change the name of the personEmail field from "email" to "email_address"

exampleDb :: DatabaseSettings be ExampleDb
exampleDb = defaultDbSettings `withDbModification`
            dbModification {
              persons = modifyTable id $
                        tableModification {
                          personEmail = fieldNamed "email_address"
                        }
            }

An appropriate IsString instance is also given so you can avoid the use of fieldNamed. For example, the above is equivalent to

exampleDb :: DatabaseSettings be ExampleDb
exampleDb = defaultDbSettings `withDbModification`
            dbModification {
              persons = modifyTable id $
                        tableModification {
                          personEmail = "email_address"
                        }
            }