groundhog-th-0.8: Type-safe datatype-database mapping library.

Safe HaskellNone




This module provides functions to generate the auxiliary structures for the user data type


Settings format

Groundhog needs to analyze the datatypes and create the auxiliary definitions before it can work with them. We use YAML-based settings to list the datatypes and adjust the result of their introspection.

A datatype can be treated as entity or embedded. An entity is stored in its own table, can be referenced in fields of other data, etc. It is a first-class value. An embedded type can only be a field of an entity or another embedded type. For example, the tuples are embedded. You can create your own embedded types and adjust the fields names of an existing embedded type individually for any place where it is used.

Unless the property is marked as mandatory, it can be omitted. In this case value created by the NamingStyle will be used.

data Settable = First {foo :: String, bar :: Int, next :: Maybe (Key Settable BackendSpecific)} deriving (Eq, Show)

    -- The declaration with defaulted names

mkPersist defaultCodegenConfig [groundhog|
entity: Settable                       # If we did not have a constraint, this line would be enough
  - name: someconstraint
  - name: First
      - name: someconstraint
        fields: [foo, bar]

Which is equivalent to the example below that has all properties set explicitly.

mkPersist defaultCodegenConfig [groundhog|
definitions:                           # First level key whose value is a list of definitions. It can be considered an optional header.
                                       # The list elements start with hyphen+space. Keys are separated from values by a colon+space. See full definition at
  - entity: Settable                   # Mandatory. Entity datatype name
    dbName: Settable                   # Name of the main table
    # schema: public                   # Name of the schema to which the table belongs
    autoKey:                           # Description of the autoincremented key for data family Key instance
      constrName: SettableKey          # Name of constructor
      default: true                    # The default key is used when entity is referenced without key wrapper. E.g., "field :: SomeData" instead of "field :: Key SomeData keytype"
    keys:                              # List of the unique keys. An entity may have unique keys only if it has one constructor
      - name: someconstraint           # This name references names from uniques field of constructor
        keyPhantom: Someconstraint     # Name of phantom datatype that corresponds for each unique key
        constrName: SomeconstraintKey  # Name of data family Key instance constructor for this unique key
        dbName: Key#Someconstraint     # It is used for function "persistName" of "PersistField (Key Settable (Unique Someconstraint))"
        fields: []                     # Set fields that comprise this unique constraint. It works like setting fields in constructors
        mkEmbedded: false              # Defines if instance of "Embedded (Key Settable (Unique Someconstraint))" will be created. The "Selector" constructor names are defined by properties of key fields.
        default: false                 # Defines if this unique key is used as default
    constructors:                      # List of constructors. The constructors you don't change can be omitted
      - name: First                    # Mandatory. Constructor name
        phantomName: FooBarConstructor # Constructor phantom type name used to guarantee type safety
        dbName: First                  # Name of constructor table which is created only for datatypes with multiple constructors
        keyDbName: id                  # Name for the primary key column
        fields:                        # List of constructor fields. If you don't change a field, you can omit it
          - name: foo                  # The name as in constructor record. If constructor is not a record, the name is created by mkNormalFieldName. For example, the fields in constructor SomeConstr would have names someConstr0 and someConstr1 by default.
            dbName: foo                # Column name
            exprName: FooField         # Name of a field used in expressions
          # type: varchar              # This would result in having field type DbOther "varchar" instead of DbString. Value of this attribute will be used by DB backend for migration
          # default: foo_value         # The default value for column in the clause
          # reference:                 # This is explicit reference to a parent table not mapped by Groundhog
          #   schema: myschema         # Optional schema
          #   table: mytable           # Name of the parent table
          #   columns: [mytable_id]    # Parent columns. If the current field is embedded, e.g., a tuple, it will be a composite key
          #   onDelete: cascade        # Defines ON DELETE clause of references. It can have values: no action, restrict, cascade, set null, set default
          #   onUpdate: restrict       # Defines ON UPDATE
          # onDelete: cascade          # Clauses onDelete and onUpdate can be set outside of reference too. This is deprecated and kept for compatibility
          # If onDelete or onUpdate are omitted, the database will choose the action automatically. Note that it may differ across databases.
          # For example, MySQL has "restrict" by default, but in PostgreSQL it is "no action".
          - name: bar
            dbName: bar
            exprName: BarField
                                       # For some databases "type: integer" would be appropriate
          - name: next
            dbName: next
            exprName: NextField
          - name: someconstraint
            type: constraint           # The type can be be "constraint", "index", or "primary"
            fields: [foo, bar]         # List of constructor parameter names. Not column names.
   # This is example for databases which support expression indexes.
   # Note that for checking index during migration expression should be written in exactly the same form as database returns.
   #  - name: myuniqueindex
   #    type: index
   #    fields: [foo, {expr: "(bar + 1)" }]

This is an example of embedded datatype usage.

data Company = Company {name :: String, headquarter :: Address, dataCentre :: Address, salesOffice :: Address} deriving (Eq, Show)
data Address = Address {city :: String, zipCode :: String, street :: String} deriving (Eq, Show)

mkPersist defaultCodegenConfig [groundhog|
  - entity: Company
      - name: Company
                                        # Property embeddedType of headquarter field is not mentioned, so the corresponding table columns will have names prefixed with headquarter (headquarter$city, headquarter$zip_code, headquarter$street)
          - name: dataCentre
            embeddedType:               # If a field has an embedded type you can access its subfields. If you do it, the database columns will match with the embedded dbNames (no prefixing).
              - name: city              # Just a regular list of fields. However, note that you should use default dbNames of embedded
                dbName: dc_city
              - name: zip_code          # Here we use embedded dbName (zip_code) which differs from the name used in Address definition (zipCode) for accessing the field.
                dbName: dc_zipcode
              - name: street
                dbName: dc_street
          - name: salesOffice
            embeddedType:               # Similar declaration, but using another syntax for YAML objects
              - {name: city, dbName: sales_city}
              - {name: zip_code, dbName: sales_zipcode}
              - {name: street, dbName: sales_street}
  - embedded: Address                        
    fields:                             # The syntax is the same as for constructor fields. Nested embedded types are allowed.
      - name: city                      # This line does nothing and can be omitted. Default settings for city are not changed.
      - name: zipCode
        dbName: zip_code                # Change column name.
                                        # Street is not mentioned so it will have default settings.

We can also make our types instances of PrimitivePersistField to store them in one column.

data WeekDay = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
  deriving (Eq, Show, Enum)
data Point = Point Int Int
  deriving (Eq, Show, Read)

mkPersist defaultCodegenConfig [groundhog|
  - primitive: WeekDay
    converter: enumConverter            # Its column will have integer type. The conversion will use Enum instance.
  - primitive: Point
    converter: showReadConverter        # Its column will have string type. The conversion will use Show/Read instances. If representation is omitted, showread will be used by default.

mkPersist :: CodegenConfig -> PersistDefinitions -> Q [Dec] Source #

Creates the auxiliary structures. Particularly, it creates GADT Field data instance for referring to the fields in expressions and phantom types for data constructors. The default names of auxiliary datatypes and names used in database are generated using the naming style and can be changed via configuration. The datatypes and their generation options are defined via YAML configuration parsed by quasiquoter groundhog.

groundhog :: QuasiQuoter Source #

Converts quasiquoted settings into the datatype used by mkPersist.

groundhogFile :: QuasiQuoter Source #

Parses configuration stored in the file

mkPersist defaultCodegenConfig [groundhogFile|../groundhog.yaml|]

Settings for code generation

data CodegenConfig Source #




When describing a datatype you can omit the most of the declarations. In this case the omitted parts of description will be automatically generated using the default names created by naming style. Any default name can be overridden by setting its value explicitly.

data NamingStyle Source #

Defines how the names are created. The mk* functions correspond to the set* functions. Functions mkNormal* define names of non-record constructor Field




suffixNamingStyle :: NamingStyle Source #

Default style. Adds "Field" to each record field name.


data SomeData a = Normal Int | Record { bar :: Maybe String, asc :: a}
-- Generated code
data NormalConstructor
data RecordConstructor
instance PersistEntity where
  data Field (SomeData a) where
    Normal0Field :: Field NormalConstructor Int
    BarField :: Field RecordConstructor (Maybe String)
    AscField :: Field RecordConstructor a

persistentNamingStyle :: NamingStyle Source #

Creates field names in Persistent fashion prepending constructor names to the fields.


data SomeData a = Normal Int | Record { bar :: Maybe String, asc :: a}
-- Generated code
data NormalConstructor
data RecordConstructor
instance PersistEntity where
  data Field (SomeData a) where
    Normal0 :: Field NormalConstructor Int
    RecordBar :: Field RecordConstructor (Maybe String)
    RecordAsc :: Field RecordConstructor a

conciseNamingStyle :: NamingStyle Source #

Creates the shortest field names. It is more likely to lead in name conflicts than other naming styles.


data SomeData a = Normal Int | Record { bar :: Maybe String, asc :: a}
-- Generated code
data NormalConstructor
data RecordConstructor
instance PersistEntity where
  data Field (SomeData a) where
    Normal0 :: Field NormalConstructor Int
    Bar :: Field RecordConstructor (Maybe String)
    Asc :: Field RecordConstructor a

lowerCaseSuffixNamingStyle :: NamingStyle Source #

The generated Haskell names of phantom types (constructors, fields, etc.) are the same as with suffixNamingStyle. But the table names and columns are converted from camelCase to underscore_lower_case with toUnderscore.

toUnderscore :: String -> String Source #

Transforms string from camelCase to lower_case_underscore naming convention. ColumnName -> column_name, parseURL -> parse_url, FieldIEEE754Floating -> field_ieee754_floating

Utility functions


showReadConverter :: (Show a, Read a) => (a -> String, String -> a) Source #

enumConverter :: Enum a => (a -> Int, Int -> a) Source #