more rehierarching

This commit is contained in:
Eitan Chatav 2019-09-24 10:34:08 -07:00
parent e3cc5561d7
commit c42b09d1f3
11 changed files with 458 additions and 357 deletions

View File

@ -28,6 +28,8 @@ library
Squeal.PostgreSQL.Definition.Function
Squeal.PostgreSQL.Definition.Index
Squeal.PostgreSQL.Definition.Table
Squeal.PostgreSQL.Definition.Table.Column
Squeal.PostgreSQL.Definition.Table.Constraint
Squeal.PostgreSQL.Definition.Type
Squeal.PostgreSQL.Definition.Schema
Squeal.PostgreSQL.Definition.View

View File

@ -199,6 +199,8 @@ import Squeal.PostgreSQL.Definition.Function as X
import Squeal.PostgreSQL.Definition.Index as X
import Squeal.PostgreSQL.Definition.Schema as X
import Squeal.PostgreSQL.Definition.Table as X
import Squeal.PostgreSQL.Definition.Table.Column as X
import Squeal.PostgreSQL.Definition.Table.Constraint as X
import Squeal.PostgreSQL.Definition.Type as X
import Squeal.PostgreSQL.Definition.View as X
import Squeal.PostgreSQL.Expression as X

View File

@ -54,7 +54,7 @@ import qualified Generics.SOP as SOP
import qualified GHC.Generics as GHC
import Squeal.PostgreSQL.Alias
import Squeal.PostgreSQL.Definition (Definition (..))
import Squeal.PostgreSQL.Definition
import Squeal.PostgreSQL.Expression
import Squeal.PostgreSQL.Expression.Type
import Squeal.PostgreSQL.List

View File

@ -49,7 +49,7 @@ import GHC.TypeLits
import qualified GHC.Generics as GHC
import Squeal.PostgreSQL.Alias
import Squeal.PostgreSQL.Definition (Definition (..))
import Squeal.PostgreSQL.Definition
import Squeal.PostgreSQL.Expression.Sort
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Schema

View File

@ -39,7 +39,7 @@ module Squeal.PostgreSQL.Definition.Schema
import GHC.TypeLits
import Squeal.PostgreSQL.Alias
import Squeal.PostgreSQL.Definition (Definition (..))
import Squeal.PostgreSQL.Definition
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Schema

View File

@ -34,14 +34,6 @@ module Squeal.PostgreSQL.Definition.Table
, createTableIfNotExists
, dropTable
, dropTableIfExists
, TableConstraintExpression (..)
, check
, unique
, primaryKey
, foreignKey
, ForeignKeyed
, OnDeleteClause (..)
, OnUpdateClause (..)
, alterTable
, alterTableRename
, AlterTable (..)
@ -57,17 +49,6 @@ module Squeal.PostgreSQL.Definition.Table
, setNotNull
, dropNotNull
, alterType
-- * Columns
, ColumnTypeExpression (..)
, nullable
, notNullable
, default_
, serial2
, smallserial
, serial4
, serial
, serial8
, bigserial
) where
import Control.DeepSeq
@ -78,10 +59,10 @@ import qualified Generics.SOP as SOP
import qualified GHC.Generics as GHC
import Squeal.PostgreSQL.Alias
import Squeal.PostgreSQL.Definition (Definition (..))
import Squeal.PostgreSQL.Definition
import Squeal.PostgreSQL.Definition.Table.Column
import Squeal.PostgreSQL.Definition.Table.Constraint
import Squeal.PostgreSQL.Expression
import Squeal.PostgreSQL.Expression.Logic
import Squeal.PostgreSQL.Expression.Type
import Squeal.PostgreSQL.List
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Schema
@ -195,288 +176,6 @@ renderCreation tab columns constraints = renderSQL tab
renderConstraint (constraint `As` alias) =
"CONSTRAINT" <+> renderSQL alias <+> renderSQL constraint
-- | Data types are a way to limit the kind of data that can be stored in a
-- table. For many applications, however, the constraint they provide is
-- too coarse. For example, a column containing a product price should
-- probably only accept positive values. But there is no standard data type
-- that accepts only positive numbers. Another issue is that you might want
-- to constrain column data with respect to other columns or rows.
-- For example, in a table containing product information,
-- there should be only one row for each product number.
-- `TableConstraint`s give you as much control over the data in your tables
-- as you wish. If a user attempts to store data in a column that would
-- violate a constraint, an error is raised. This applies
-- even if the value came from the default value definition.
newtype TableConstraintExpression
(sch :: Symbol)
(tab :: Symbol)
(schemas :: SchemasType)
(constraint :: TableConstraint)
= UnsafeTableConstraintExpression
{ renderTableConstraintExpression :: ByteString }
deriving (GHC.Generic,Show,Eq,Ord,NFData)
instance RenderSQL
(TableConstraintExpression sch tab schemas constraint) where
renderSQL = renderTableConstraintExpression
{-| A `check` constraint is the most generic `TableConstraint` type.
It allows you to specify that the value in a certain column must satisfy
a Boolean (truth-value) expression.
>>> :{
type Schema = '[
"tab" ::: 'Table ('[ "inequality" ::: 'Check '["a","b"]] :=> '[
"a" ::: 'NoDef :=> 'NotNull 'PGint4,
"b" ::: 'NoDef :=> 'NotNull 'PGint4
])]
:}
>>> :{
let
definition :: Definition (Public '[]) (Public Schema)
definition = createTable #tab
( (int & notNullable) `as` #a :*
(int & notNullable) `as` #b )
( check (#a :* #b) (#a .> #b) `as` #inequality )
:}
>>> printSQL definition
CREATE TABLE "tab" ("a" int NOT NULL, "b" int NOT NULL, CONSTRAINT "inequality" CHECK (("a" > "b")));
-}
check
:: ( Has sch schemas schema
, Has tab schema ('Table table)
, HasAll aliases (TableToRow table) subcolumns )
=> NP Alias aliases
-- ^ specify the subcolumns which are getting checked
-> (forall t. Condition '[] '[] 'Ungrouped schemas '[] '[t ::: subcolumns])
-- ^ a closed `Condition` on those subcolumns
-> TableConstraintExpression sch tab schemas ('Check aliases)
check _cols condition = UnsafeTableConstraintExpression $
"CHECK" <+> parenthesized (renderSQL condition)
{-| A `unique` constraint ensure that the data contained in a column,
or a group of columns, is unique among all the rows in the table.
>>> :{
type Schema = '[
"tab" ::: 'Table( '[ "uq_a_b" ::: 'Unique '["a","b"]] :=> '[
"a" ::: 'NoDef :=> 'Null 'PGint4,
"b" ::: 'NoDef :=> 'Null 'PGint4
])]
:}
>>> :{
let
definition :: Definition (Public '[]) (Public Schema)
definition = createTable #tab
( (int & nullable) `as` #a :*
(int & nullable) `as` #b )
( unique (#a :* #b) `as` #uq_a_b )
:}
>>> printSQL definition
CREATE TABLE "tab" ("a" int NULL, "b" int NULL, CONSTRAINT "uq_a_b" UNIQUE ("a", "b"));
-}
unique
:: ( Has sch schemas schema
, Has tab schema('Table table)
, HasAll aliases (TableToRow table) subcolumns )
=> NP Alias aliases
-- ^ specify subcolumns which together are unique for each row
-> TableConstraintExpression sch tab schemas ('Unique aliases)
unique columns = UnsafeTableConstraintExpression $
"UNIQUE" <+> parenthesized (renderSQL columns)
{-| A `primaryKey` constraint indicates that a column, or group of columns,
can be used as a unique identifier for rows in the table.
This requires that the values be both unique and not null.
>>> :{
type Schema = '[
"tab" ::: 'Table ('[ "pk_id" ::: 'PrimaryKey '["id"]] :=> '[
"id" ::: 'Def :=> 'NotNull 'PGint4,
"name" ::: 'NoDef :=> 'NotNull 'PGtext
])]
:}
>>> :{
let
definition :: Definition (Public '[]) (Public Schema)
definition = createTable #tab
( serial `as` #id :*
(text & notNullable) `as` #name )
( primaryKey #id `as` #pk_id )
:}
>>> printSQL definition
CREATE TABLE "tab" ("id" serial, "name" text NOT NULL, CONSTRAINT "pk_id" PRIMARY KEY ("id"));
-}
primaryKey
:: ( Has sch schemas schema
, Has tab schema ('Table table)
, HasAll aliases (TableToColumns table) subcolumns
, AllNotNull subcolumns )
=> NP Alias aliases
-- ^ specify the subcolumns which together form a primary key.
-> TableConstraintExpression sch tab schemas ('PrimaryKey aliases)
primaryKey columns = UnsafeTableConstraintExpression $
"PRIMARY KEY" <+> parenthesized (renderSQL columns)
{-| A `foreignKey` specifies that the values in a column
(or a group of columns) must match the values appearing in some row of
another table. We say this maintains the referential integrity
between two related tables.
>>> :{
type Schema =
'[ "users" ::: 'Table (
'[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "name" ::: 'NoDef :=> 'NotNull 'PGtext
])
, "emails" ::: 'Table (
'[ "pk_emails" ::: 'PrimaryKey '["id"]
, "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
, "email" ::: 'NoDef :=> 'Null 'PGtext
])
]
:}
>>> :{
let
setup :: Definition (Public '[]) (Public Schema)
setup =
createTable #users
( serial `as` #id :*
(text & notNullable) `as` #name )
( primaryKey #id `as` #pk_users ) >>>
createTable #emails
( serial `as` #id :*
(int & notNullable) `as` #user_id :*
(text & nullable) `as` #email )
( primaryKey #id `as` #pk_emails :*
foreignKey #user_id #users #id
OnDeleteCascade OnUpdateCascade `as` #fk_user_id )
in printSQL setup
:}
CREATE TABLE "users" ("id" serial, "name" text NOT NULL, CONSTRAINT "pk_users" PRIMARY KEY ("id"));
CREATE TABLE "emails" ("id" serial, "user_id" int NOT NULL, "email" text NULL, CONSTRAINT "pk_emails" PRIMARY KEY ("id"), CONSTRAINT "fk_user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE);
A `foreignKey` can even be a table self-reference.
>>> :{
type Schema =
'[ "employees" ::: 'Table (
'[ "employees_pk" ::: 'PrimaryKey '["id"]
, "employees_employer_fk" ::: 'ForeignKey '["employer_id"] "employees" '["id"]
] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "name" ::: 'NoDef :=> 'NotNull 'PGtext
, "employer_id" ::: 'NoDef :=> 'Null 'PGint4
])
]
:}
>>> :{
let
setup :: Definition (Public '[]) (Public Schema)
setup =
createTable #employees
( serial `as` #id :*
(text & notNullable) `as` #name :*
(integer & nullable) `as` #employer_id )
( primaryKey #id `as` #employees_pk :*
foreignKey #employer_id #employees #id
OnDeleteCascade OnUpdateCascade `as` #employees_employer_fk )
in printSQL setup
:}
CREATE TABLE "employees" ("id" serial, "name" text NOT NULL, "employer_id" integer NULL, CONSTRAINT "employees_pk" PRIMARY KEY ("id"), CONSTRAINT "employees_employer_fk" FOREIGN KEY ("employer_id") REFERENCES "employees" ("id") ON DELETE CASCADE ON UPDATE CASCADE);
-}
foreignKey
:: (ForeignKeyed schemas sch schema child parent
table reftable
columns refcolumns
constraints cols
reftys tys )
=> NP Alias columns
-- ^ column or columns in the table
-> Alias parent
-- ^ reference table
-> NP Alias refcolumns
-- ^ reference column or columns in the reference table
-> OnDeleteClause
-- ^ what to do when reference is deleted
-> OnUpdateClause
-- ^ what to do when reference is updated
-> TableConstraintExpression sch child schemas
('ForeignKey columns parent refcolumns)
foreignKey keys parent refs ondel onupd = UnsafeTableConstraintExpression $
"FOREIGN KEY" <+> parenthesized (renderSQL keys)
<+> "REFERENCES" <+> renderSQL parent
<+> parenthesized (renderSQL refs)
<+> renderSQL ondel
<+> renderSQL onupd
-- | A constraint synonym between types involved in a foreign key constraint.
type ForeignKeyed schemas
sch
schema
child parent
table reftable
columns refcolumns
constraints cols
reftys tys =
( Has sch schemas schema
, Has child schema ('Table table)
, Has parent schema ('Table reftable)
, HasAll columns (TableToColumns table) tys
, reftable ~ (constraints :=> cols)
, HasAll refcolumns cols reftys
, SOP.AllZip SamePGType tys reftys
, Uniquely refcolumns constraints )
-- | `OnDeleteClause` indicates what to do with rows that reference a deleted row.
data OnDeleteClause
= OnDeleteNoAction
-- ^ if any referencing rows still exist when the constraint is checked,
-- an error is raised
| OnDeleteRestrict -- ^ prevents deletion of a referenced row
| OnDeleteCascade
-- ^ specifies that when a referenced row is deleted,
-- row(s) referencing it should be automatically deleted as well
deriving (GHC.Generic,Show,Eq,Ord)
instance NFData OnDeleteClause
-- | Render `OnDeleteClause`.
instance RenderSQL OnDeleteClause where
renderSQL = \case
OnDeleteNoAction -> "ON DELETE NO ACTION"
OnDeleteRestrict -> "ON DELETE RESTRICT"
OnDeleteCascade -> "ON DELETE CASCADE"
-- | Analagous to `OnDeleteClause` there is also `OnUpdateClause` which is invoked
-- when a referenced column is changed (updated).
data OnUpdateClause
= OnUpdateNoAction
-- ^ if any referencing rows has not changed when the constraint is checked,
-- an error is raised
| OnUpdateRestrict -- ^ prevents update of a referenced row
| OnUpdateCascade
-- ^ the updated values of the referenced column(s) should be copied
-- into the referencing row(s)
deriving (GHC.Generic,Show,Eq,Ord)
instance NFData OnUpdateClause
-- | Render `OnUpdateClause`.
instance RenderSQL OnUpdateClause where
renderSQL = \case
OnUpdateNoAction -> "ON UPDATE NO ACTION"
OnUpdateRestrict -> "ON UPDATE RESTRICT"
OnUpdateCascade -> "ON UPDATE CASCADE"
-- | `dropTable` removes a table from the schema.
--
-- >>> :{
@ -776,51 +475,3 @@ dropNotNull = UnsafeAlterColumn $ "DROP NOT NULL"
-- ALTER TABLE "tab" ALTER COLUMN "col" TYPE numeric NOT NULL;
alterType :: ColumnTypeExpression schemas ty -> AlterColumn schemas ty0 ty
alterType ty = UnsafeAlterColumn $ "TYPE" <+> renderColumnTypeExpression ty
-- | `ColumnTypeExpression`s are used in `createTable` commands.
newtype ColumnTypeExpression (schemas :: SchemasType) (ty :: ColumnType)
= UnsafeColumnTypeExpression { renderColumnTypeExpression :: ByteString }
deriving (GHC.Generic,Show,Eq,Ord,NFData)
instance RenderSQL (ColumnTypeExpression schemas ty) where
renderSQL = renderColumnTypeExpression
-- | used in `createTable` commands as a column constraint to note that
-- @NULL@ may be present in a column
nullable
:: TypeExpression schemas (nullity ty)
-> ColumnTypeExpression schemas ('NoDef :=> 'Null ty)
nullable ty = UnsafeColumnTypeExpression $ renderSQL ty <+> "NULL"
-- | used in `createTable` commands as a column constraint to ensure
-- @NULL@ is not present in a column
notNullable
:: TypeExpression schemas (nullity ty)
-> ColumnTypeExpression schemas ('NoDef :=> 'NotNull ty)
notNullable ty = UnsafeColumnTypeExpression $ renderSQL ty <+> "NOT NULL"
-- | used in `createTable` commands as a column constraint to give a default
default_
:: Expression '[] '[] 'Ungrouped schemas '[] '[] ty
-> ColumnTypeExpression schemas ('NoDef :=> ty)
-> ColumnTypeExpression schemas ('Def :=> ty)
default_ x ty = UnsafeColumnTypeExpression $
renderSQL ty <+> "DEFAULT" <+> renderExpression x
-- | not a true type, but merely a notational convenience for creating
-- unique identifier columns with type `PGint2`
serial2, smallserial
:: ColumnTypeExpression schemas ('Def :=> 'NotNull 'PGint2)
serial2 = UnsafeColumnTypeExpression "serial2"
smallserial = UnsafeColumnTypeExpression "smallserial"
-- | not a true type, but merely a notational convenience for creating
-- unique identifier columns with type `PGint4`
serial4, serial
:: ColumnTypeExpression schemas ('Def :=> 'NotNull 'PGint4)
serial4 = UnsafeColumnTypeExpression "serial4"
serial = UnsafeColumnTypeExpression "serial"
-- | not a true type, but merely a notational convenience for creating
-- unique identifier columns with type `PGint8`
serial8, bigserial
:: ColumnTypeExpression schemas ('Def :=> 'NotNull 'PGint8)
serial8 = UnsafeColumnTypeExpression "serial8"
bigserial = UnsafeColumnTypeExpression "bigserial"

View File

@ -0,0 +1,105 @@
{-|
Module: Squeal.PostgreSQL.Definition.Table.Column
Description: Column type expressions
Copyright: (c) Eitan Chatav, 2017
Maintainer: eitan@morphism.tech
Stability: experimental
Column type expressions.
-}
{-# LANGUAGE
AllowAmbiguousTypes
, ConstraintKinds
, DeriveAnyClass
, DeriveGeneric
, DerivingStrategies
, FlexibleContexts
, FlexibleInstances
, GADTs
, LambdaCase
, MultiParamTypeClasses
, OverloadedLabels
, OverloadedStrings
, RankNTypes
, ScopedTypeVariables
, TypeApplications
, TypeInType
, TypeOperators
, UndecidableSuperClasses
#-}
module Squeal.PostgreSQL.Definition.Table.Column
( ColumnTypeExpression (..)
, nullable
, notNullable
, default_
, serial2
, smallserial
, serial4
, serial
, serial8
, bigserial
) where
import Control.DeepSeq
import Data.ByteString
import qualified GHC.Generics as GHC
import Squeal.PostgreSQL.Alias
import Squeal.PostgreSQL.Expression
import Squeal.PostgreSQL.Expression.Type
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Schema
-- $setup
-- >>> import Squeal.PostgreSQL
-- | `ColumnTypeExpression`s are used in `createTable` commands.
newtype ColumnTypeExpression (schemas :: SchemasType) (ty :: ColumnType)
= UnsafeColumnTypeExpression { renderColumnTypeExpression :: ByteString }
deriving (GHC.Generic,Show,Eq,Ord,NFData)
instance RenderSQL (ColumnTypeExpression schemas ty) where
renderSQL = renderColumnTypeExpression
-- | used in `createTable` commands as a column constraint to note that
-- @NULL@ may be present in a column
nullable
:: TypeExpression schemas (nullity ty)
-> ColumnTypeExpression schemas ('NoDef :=> 'Null ty)
nullable ty = UnsafeColumnTypeExpression $ renderSQL ty <+> "NULL"
-- | used in `createTable` commands as a column constraint to ensure
-- @NULL@ is not present in a column
notNullable
:: TypeExpression schemas (nullity ty)
-> ColumnTypeExpression schemas ('NoDef :=> 'NotNull ty)
notNullable ty = UnsafeColumnTypeExpression $ renderSQL ty <+> "NOT NULL"
-- | used in `createTable` commands as a column constraint to give a default
default_
:: Expression '[] '[] 'Ungrouped schemas '[] '[] ty
-> ColumnTypeExpression schemas ('NoDef :=> ty)
-> ColumnTypeExpression schemas ('Def :=> ty)
default_ x ty = UnsafeColumnTypeExpression $
renderSQL ty <+> "DEFAULT" <+> renderExpression x
-- | not a true type, but merely a notational convenience for creating
-- unique identifier columns with type `PGint2`
serial2, smallserial
:: ColumnTypeExpression schemas ('Def :=> 'NotNull 'PGint2)
serial2 = UnsafeColumnTypeExpression "serial2"
smallserial = UnsafeColumnTypeExpression "smallserial"
-- | not a true type, but merely a notational convenience for creating
-- unique identifier columns with type `PGint4`
serial4, serial
:: ColumnTypeExpression schemas ('Def :=> 'NotNull 'PGint4)
serial4 = UnsafeColumnTypeExpression "serial4"
serial = UnsafeColumnTypeExpression "serial"
-- | not a true type, but merely a notational convenience for creating
-- unique identifier columns with type `PGint8`
serial8, bigserial
:: ColumnTypeExpression schemas ('Def :=> 'NotNull 'PGint8)
serial8 = UnsafeColumnTypeExpression "serial8"
bigserial = UnsafeColumnTypeExpression "bigserial"

View File

@ -0,0 +1,339 @@
{-|
Module: Squeal.PostgreSQL.Definition.Table.Constraint
Description: Table constraint expressions
Copyright: (c) Eitan Chatav, 2017
Maintainer: eitan@morphism.tech
Stability: experimental
Table constraint expressions.
-}
{-# LANGUAGE
AllowAmbiguousTypes
, ConstraintKinds
, DeriveAnyClass
, DeriveGeneric
, DerivingStrategies
, FlexibleContexts
, FlexibleInstances
, GADTs
, LambdaCase
, MultiParamTypeClasses
, OverloadedLabels
, OverloadedStrings
, RankNTypes
, ScopedTypeVariables
, TypeApplications
, TypeInType
, TypeOperators
, UndecidableSuperClasses
#-}
module Squeal.PostgreSQL.Definition.Table.Constraint
( TableConstraintExpression (..)
, check
, unique
, primaryKey
, foreignKey
, ForeignKeyed
, OnDeleteClause (..)
, OnUpdateClause (..)
) where
import Control.DeepSeq
import Data.ByteString
import GHC.TypeLits
import qualified Generics.SOP as SOP
import qualified GHC.Generics as GHC
import Squeal.PostgreSQL.Alias
import Squeal.PostgreSQL.Expression.Logic
import Squeal.PostgreSQL.List
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Schema
-- $setup
-- >>> import Squeal.PostgreSQL
-- | Data types are a way to limit the kind of data that can be stored in a
-- table. For many applications, however, the constraint they provide is
-- too coarse. For example, a column containing a product price should
-- probably only accept positive values. But there is no standard data type
-- that accepts only positive numbers. Another issue is that you might want
-- to constrain column data with respect to other columns or rows.
-- For example, in a table containing product information,
-- there should be only one row for each product number.
-- `TableConstraint`s give you as much control over the data in your tables
-- as you wish. If a user attempts to store data in a column that would
-- violate a constraint, an error is raised. This applies
-- even if the value came from the default value definition.
newtype TableConstraintExpression
(sch :: Symbol)
(tab :: Symbol)
(schemas :: SchemasType)
(constraint :: TableConstraint)
= UnsafeTableConstraintExpression
{ renderTableConstraintExpression :: ByteString }
deriving (GHC.Generic,Show,Eq,Ord,NFData)
instance RenderSQL
(TableConstraintExpression sch tab schemas constraint) where
renderSQL = renderTableConstraintExpression
{-| A `check` constraint is the most generic `TableConstraint` type.
It allows you to specify that the value in a certain column must satisfy
a Boolean (truth-value) expression.
>>> :{
type Schema = '[
"tab" ::: 'Table ('[ "inequality" ::: 'Check '["a","b"]] :=> '[
"a" ::: 'NoDef :=> 'NotNull 'PGint4,
"b" ::: 'NoDef :=> 'NotNull 'PGint4
])]
:}
>>> :{
let
definition :: Definition (Public '[]) (Public Schema)
definition = createTable #tab
( (int & notNullable) `as` #a :*
(int & notNullable) `as` #b )
( check (#a :* #b) (#a .> #b) `as` #inequality )
:}
>>> printSQL definition
CREATE TABLE "tab" ("a" int NOT NULL, "b" int NOT NULL, CONSTRAINT "inequality" CHECK (("a" > "b")));
-}
check
:: ( Has sch schemas schema
, Has tab schema ('Table table)
, HasAll aliases (TableToRow table) subcolumns )
=> NP Alias aliases
-- ^ specify the subcolumns which are getting checked
-> (forall t. Condition '[] '[] 'Ungrouped schemas '[] '[t ::: subcolumns])
-- ^ a closed `Condition` on those subcolumns
-> TableConstraintExpression sch tab schemas ('Check aliases)
check _cols condition = UnsafeTableConstraintExpression $
"CHECK" <+> parenthesized (renderSQL condition)
{-| A `unique` constraint ensure that the data contained in a column,
or a group of columns, is unique among all the rows in the table.
>>> :{
type Schema = '[
"tab" ::: 'Table( '[ "uq_a_b" ::: 'Unique '["a","b"]] :=> '[
"a" ::: 'NoDef :=> 'Null 'PGint4,
"b" ::: 'NoDef :=> 'Null 'PGint4
])]
:}
>>> :{
let
definition :: Definition (Public '[]) (Public Schema)
definition = createTable #tab
( (int & nullable) `as` #a :*
(int & nullable) `as` #b )
( unique (#a :* #b) `as` #uq_a_b )
:}
>>> printSQL definition
CREATE TABLE "tab" ("a" int NULL, "b" int NULL, CONSTRAINT "uq_a_b" UNIQUE ("a", "b"));
-}
unique
:: ( Has sch schemas schema
, Has tab schema('Table table)
, HasAll aliases (TableToRow table) subcolumns )
=> NP Alias aliases
-- ^ specify subcolumns which together are unique for each row
-> TableConstraintExpression sch tab schemas ('Unique aliases)
unique columns = UnsafeTableConstraintExpression $
"UNIQUE" <+> parenthesized (renderSQL columns)
{-| A `primaryKey` constraint indicates that a column, or group of columns,
can be used as a unique identifier for rows in the table.
This requires that the values be both unique and not null.
>>> :{
type Schema = '[
"tab" ::: 'Table ('[ "pk_id" ::: 'PrimaryKey '["id"]] :=> '[
"id" ::: 'Def :=> 'NotNull 'PGint4,
"name" ::: 'NoDef :=> 'NotNull 'PGtext
])]
:}
>>> :{
let
definition :: Definition (Public '[]) (Public Schema)
definition = createTable #tab
( serial `as` #id :*
(text & notNullable) `as` #name )
( primaryKey #id `as` #pk_id )
:}
>>> printSQL definition
CREATE TABLE "tab" ("id" serial, "name" text NOT NULL, CONSTRAINT "pk_id" PRIMARY KEY ("id"));
-}
primaryKey
:: ( Has sch schemas schema
, Has tab schema ('Table table)
, HasAll aliases (TableToColumns table) subcolumns
, AllNotNull subcolumns )
=> NP Alias aliases
-- ^ specify the subcolumns which together form a primary key.
-> TableConstraintExpression sch tab schemas ('PrimaryKey aliases)
primaryKey columns = UnsafeTableConstraintExpression $
"PRIMARY KEY" <+> parenthesized (renderSQL columns)
{-| A `foreignKey` specifies that the values in a column
(or a group of columns) must match the values appearing in some row of
another table. We say this maintains the referential integrity
between two related tables.
>>> :{
type Schema =
'[ "users" ::: 'Table (
'[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "name" ::: 'NoDef :=> 'NotNull 'PGtext
])
, "emails" ::: 'Table (
'[ "pk_emails" ::: 'PrimaryKey '["id"]
, "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
, "email" ::: 'NoDef :=> 'Null 'PGtext
])
]
:}
>>> :{
let
setup :: Definition (Public '[]) (Public Schema)
setup =
createTable #users
( serial `as` #id :*
(text & notNullable) `as` #name )
( primaryKey #id `as` #pk_users ) >>>
createTable #emails
( serial `as` #id :*
(int & notNullable) `as` #user_id :*
(text & nullable) `as` #email )
( primaryKey #id `as` #pk_emails :*
foreignKey #user_id #users #id
OnDeleteCascade OnUpdateCascade `as` #fk_user_id )
in printSQL setup
:}
CREATE TABLE "users" ("id" serial, "name" text NOT NULL, CONSTRAINT "pk_users" PRIMARY KEY ("id"));
CREATE TABLE "emails" ("id" serial, "user_id" int NOT NULL, "email" text NULL, CONSTRAINT "pk_emails" PRIMARY KEY ("id"), CONSTRAINT "fk_user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE);
A `foreignKey` can even be a table self-reference.
>>> :{
type Schema =
'[ "employees" ::: 'Table (
'[ "employees_pk" ::: 'PrimaryKey '["id"]
, "employees_employer_fk" ::: 'ForeignKey '["employer_id"] "employees" '["id"]
] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "name" ::: 'NoDef :=> 'NotNull 'PGtext
, "employer_id" ::: 'NoDef :=> 'Null 'PGint4
])
]
:}
>>> :{
let
setup :: Definition (Public '[]) (Public Schema)
setup =
createTable #employees
( serial `as` #id :*
(text & notNullable) `as` #name :*
(integer & nullable) `as` #employer_id )
( primaryKey #id `as` #employees_pk :*
foreignKey #employer_id #employees #id
OnDeleteCascade OnUpdateCascade `as` #employees_employer_fk )
in printSQL setup
:}
CREATE TABLE "employees" ("id" serial, "name" text NOT NULL, "employer_id" integer NULL, CONSTRAINT "employees_pk" PRIMARY KEY ("id"), CONSTRAINT "employees_employer_fk" FOREIGN KEY ("employer_id") REFERENCES "employees" ("id") ON DELETE CASCADE ON UPDATE CASCADE);
-}
foreignKey
:: (ForeignKeyed schemas sch schema child parent
table reftable
columns refcolumns
constraints cols
reftys tys )
=> NP Alias columns
-- ^ column or columns in the table
-> Alias parent
-- ^ reference table
-> NP Alias refcolumns
-- ^ reference column or columns in the reference table
-> OnDeleteClause
-- ^ what to do when reference is deleted
-> OnUpdateClause
-- ^ what to do when reference is updated
-> TableConstraintExpression sch child schemas
('ForeignKey columns parent refcolumns)
foreignKey keys parent refs ondel onupd = UnsafeTableConstraintExpression $
"FOREIGN KEY" <+> parenthesized (renderSQL keys)
<+> "REFERENCES" <+> renderSQL parent
<+> parenthesized (renderSQL refs)
<+> renderSQL ondel
<+> renderSQL onupd
-- | A constraint synonym between types involved in a foreign key constraint.
type ForeignKeyed schemas
sch
schema
child parent
table reftable
columns refcolumns
constraints cols
reftys tys =
( Has sch schemas schema
, Has child schema ('Table table)
, Has parent schema ('Table reftable)
, HasAll columns (TableToColumns table) tys
, reftable ~ (constraints :=> cols)
, HasAll refcolumns cols reftys
, SOP.AllZip SamePGType tys reftys
, Uniquely refcolumns constraints )
-- | `OnDeleteClause` indicates what to do with rows that reference a deleted row.
data OnDeleteClause
= OnDeleteNoAction
-- ^ if any referencing rows still exist when the constraint is checked,
-- an error is raised
| OnDeleteRestrict -- ^ prevents deletion of a referenced row
| OnDeleteCascade
-- ^ specifies that when a referenced row is deleted,
-- row(s) referencing it should be automatically deleted as well
deriving (GHC.Generic,Show,Eq,Ord)
instance NFData OnDeleteClause
-- | Render `OnDeleteClause`.
instance RenderSQL OnDeleteClause where
renderSQL = \case
OnDeleteNoAction -> "ON DELETE NO ACTION"
OnDeleteRestrict -> "ON DELETE RESTRICT"
OnDeleteCascade -> "ON DELETE CASCADE"
-- | Analagous to `OnDeleteClause` there is also `OnUpdateClause` which is invoked
-- when a referenced column is changed (updated).
data OnUpdateClause
= OnUpdateNoAction
-- ^ if any referencing rows has not changed when the constraint is checked,
-- an error is raised
| OnUpdateRestrict -- ^ prevents update of a referenced row
| OnUpdateCascade
-- ^ the updated values of the referenced column(s) should be copied
-- into the referencing row(s)
deriving (GHC.Generic,Show,Eq,Ord)
instance NFData OnUpdateClause
-- | Render `OnUpdateClause`.
instance RenderSQL OnUpdateClause where
renderSQL = \case
OnUpdateNoAction -> "ON UPDATE NO ACTION"
OnUpdateRestrict -> "ON UPDATE RESTRICT"
OnUpdateCascade -> "ON UPDATE CASCADE"

View File

@ -50,7 +50,7 @@ import qualified Generics.SOP as SOP
import Squeal.PostgreSQL.Alias
import Squeal.PostgreSQL.Expression.Logic
import Squeal.PostgreSQL.Expression.Type
import Squeal.PostgreSQL.Definition (Definition (..))
import Squeal.PostgreSQL.Definition
import Squeal.PostgreSQL.List
import Squeal.PostgreSQL.PG
import Squeal.PostgreSQL.Render

View File

@ -39,7 +39,7 @@ module Squeal.PostgreSQL.Definition.View
import GHC.TypeLits
import Squeal.PostgreSQL.Alias
import Squeal.PostgreSQL.Definition (Definition (..))
import Squeal.PostgreSQL.Definition
import Squeal.PostgreSQL.Query
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Schema

View File

@ -168,6 +168,8 @@ import Squeal.PostgreSQL.Alias
import Squeal.PostgreSQL.Binary
import Squeal.PostgreSQL.Definition
import Squeal.PostgreSQL.Definition.Table
import Squeal.PostgreSQL.Definition.Table.Column
import Squeal.PostgreSQL.Definition.Table.Constraint
import Squeal.PostgreSQL.Expression.Comparison
import Squeal.PostgreSQL.Expression.Parameter
import Squeal.PostgreSQL.Expression.Time