@@ -34,10 +34,16 @@ module Squeal.PostgreSQL.Definition.Table
34 , createTableIfNotExists
34 , createTableIfNotExists
35 , dropTable
35 , dropTable
36 , dropTableIfExists
36 , dropTableIfExists
37+ , TableConstraintExpression (..)
43+ , OnDeleteClause (..)
44+ , OnUpdateClause (..)
37 , alterTable
45 , alterTable
39 , alterTableRename
46 , alterTableRename
40− , alterTableIfExistsRename
41 , AlterTable (..)
47 , AlterTable (..)
42 , addConstraint
48 , addConstraint
43 , dropConstraint
49 , dropConstraint
@@ -51,6 +57,17 @@ module Squeal.PostgreSQL.Definition.Table
51 , setNotNull
57 , setNotNull
52 , dropNotNull
58 , dropNotNull
53 , alterType
59 , alterType
61+ , ColumnTypeExpression (..)
56import Control.DeepSeq
73import Control.DeepSeq
@@ -61,9 +78,9 @@ import qualified Generics.SOP as SOP
61import qualified GHC.Generics as GHC
78import qualified GHC.Generics as GHC
63import Squeal.PostgreSQL.Alias
80import Squeal.PostgreSQL.Alias
64−import Squeal.PostgreSQL.Definition
81+import Squeal.PostgreSQL.Definition (Definition (..))
65−import Squeal.PostgreSQL.Definition.Table.Constraint
66import Squeal.PostgreSQL.Expression
82import Squeal.PostgreSQL.Expression
83+import Squeal.PostgreSQL.Expression.Logic
67import Squeal.PostgreSQL.Expression.Type
84import Squeal.PostgreSQL.Expression.Type
68import Squeal.PostgreSQL.List
85import Squeal.PostgreSQL.List
69import Squeal.PostgreSQL.Render
86import Squeal.PostgreSQL.Render
@@ -178,6 +195,288 @@ renderCreation tab columns constraints = renderSQL tab
178 renderConstraint (constraint `As` alias) =
195 renderConstraint (constraint `As` alias) =
179 "CONSTRAINT" <+> renderSQL alias <+> renderSQL constraint
196 "CONSTRAINT" <+> renderSQL alias <+> renderSQL constraint
198+-- | Data types are a way to limit the kind of data that can be stored in a
199+-- table. For many applications, however, the constraint they provide is
200+-- too coarse. For example, a column containing a product price should
201+-- probably only accept positive values. But there is no standard data type
202+-- that accepts only positive numbers. Another issue is that you might want
203+-- to constrain column data with respect to other columns or rows.
204+-- For example, in a table containing product information,
205+-- there should be only one row for each product number.
206+-- `TableConstraint`s give you as much control over the data in your tables
207+-- as you wish. If a user attempts to store data in a column that would
208+-- violate a constraint, an error is raised. This applies
209+-- even if the value came from the default value definition.
210+newtype TableConstraintExpression
213+ (schemas :: SchemasType)
214+ (constraint :: TableConstraint)
215+ = UnsafeTableConstraintExpression
216+ { renderTableConstraintExpression :: ByteString }
217+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
219+ (TableConstraintExpression sch tab schemas constraint) where
220+ renderSQL = renderTableConstraintExpression
222+{-| A `check` constraint is the most generic `TableConstraint` type.
223+It allows you to specify that the value in a certain column must satisfy
224+a Boolean (truth-value) expression.
228+ "tab" ::: 'Table ('[ "inequality" ::: 'Check '["a","b"]] :=> '[
229+ "a" ::: 'NoDef :=> 'NotNull 'PGint4,
230+ "b" ::: 'NoDef :=> 'NotNull 'PGint4
236+ definition :: Definition (Public '[]) (Public Schema)
237+ definition = createTable #tab
238+ ( (int & notNullable) `as` #a :*
239+ (int & notNullable) `as` #b )
240+ ( check (#a :* #b) (#a .> #b) `as` #inequality )
243+>>> printSQL definition
244+CREATE TABLE "tab" ("a" int NOT NULL, "b" int NOT NULL, CONSTRAINT "inequality" CHECK (("a" > "b")));
247+ :: ( Has sch schemas schema
248+ , Has tab schema ('Table table)
249+ , HasAll aliases (TableToRow table) subcolumns )
251+ -- ^ specify the subcolumns which are getting checked
252+ -> (forall t. Condition '[] '[] 'Ungrouped schemas '[] '[t ::: subcolumns])
253+ -- ^ a closed `Condition` on those subcolumns
254+ -> TableConstraintExpression sch tab schemas ('Check aliases)
255+check _cols condition = UnsafeTableConstraintExpression $
256+ "CHECK" <+> parenthesized (renderSQL condition)
258+{-| A `unique` constraint ensure that the data contained in a column,
259+or a group of columns, is unique among all the rows in the table.
263+ "tab" ::: 'Table( '[ "uq_a_b" ::: 'Unique '["a","b"]] :=> '[
264+ "a" ::: 'NoDef :=> 'Null 'PGint4,
265+ "b" ::: 'NoDef :=> 'Null 'PGint4
271+ definition :: Definition (Public '[]) (Public Schema)
272+ definition = createTable #tab
273+ ( (int & nullable) `as` #a :*
274+ (int & nullable) `as` #b )
275+ ( unique (#a :* #b) `as` #uq_a_b )
278+>>> printSQL definition
279+CREATE TABLE "tab" ("a" int NULL, "b" int NULL, CONSTRAINT "uq_a_b" UNIQUE ("a", "b"));
282+ :: ( Has sch schemas schema
283+ , Has tab schema('Table table)
284+ , HasAll aliases (TableToRow table) subcolumns )
286+ -- ^ specify subcolumns which together are unique for each row
287+ -> TableConstraintExpression sch tab schemas ('Unique aliases)
288+unique columns = UnsafeTableConstraintExpression $
289+ "UNIQUE" <+> parenthesized (renderSQL columns)
291+{-| A `primaryKey` constraint indicates that a column, or group of columns,
292+can be used as a unique identifier for rows in the table.
293+This requires that the values be both unique and not null.
297+ "tab" ::: 'Table ('[ "pk_id" ::: 'PrimaryKey '["id"]] :=> '[
298+ "id" ::: 'Def :=> 'NotNull 'PGint4,
299+ "name" ::: 'NoDef :=> 'NotNull 'PGtext
305+ definition :: Definition (Public '[]) (Public Schema)
306+ definition = createTable #tab
307+ ( serial `as` #id :*
308+ (text & notNullable) `as` #name )
309+ ( primaryKey #id `as` #pk_id )
312+>>> printSQL definition
313+CREATE TABLE "tab" ("id" serial, "name" text NOT NULL, CONSTRAINT "pk_id" PRIMARY KEY ("id"));
316+ :: ( Has sch schemas schema
317+ , Has tab schema ('Table table)
318+ , HasAll aliases (TableToColumns table) subcolumns
319+ , AllNotNull subcolumns )
321+ -- ^ specify the subcolumns which together form a primary key.
322+ -> TableConstraintExpression sch tab schemas ('PrimaryKey aliases)
323+primaryKey columns = UnsafeTableConstraintExpression $
324+ "PRIMARY KEY" <+> parenthesized (renderSQL columns)
326+{-| A `foreignKey` specifies that the values in a column
327+(or a group of columns) must match the values appearing in some row of
328+another table. We say this maintains the referential integrity
329+between two related tables.
333+ '[ "users" ::: 'Table (
334+ '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
335+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
336+ , "name" ::: 'NoDef :=> 'NotNull 'PGtext
338+ , "emails" ::: 'Table (
339+ '[ "pk_emails" ::: 'PrimaryKey '["id"]
340+ , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
342+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
343+ , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
344+ , "email" ::: 'NoDef :=> 'Null 'PGtext
351+ setup :: Definition (Public '[]) (Public Schema)
354+ ( serial `as` #id :*
355+ (text & notNullable) `as` #name )
356+ ( primaryKey #id `as` #pk_users ) >>>
358+ ( serial `as` #id :*
359+ (int & notNullable) `as` #user_id :*
360+ (text & nullable) `as` #email )
361+ ( primaryKey #id `as` #pk_emails :*
362+ foreignKey #user_id #users #id
363+ OnDeleteCascade OnUpdateCascade `as` #fk_user_id )
366+CREATE TABLE "users" ("id" serial, "name" text NOT NULL, CONSTRAINT "pk_users" PRIMARY KEY ("id"));
367+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);
369+A `foreignKey` can even be a table self-reference.
373+ '[ "employees" ::: 'Table (
374+ '[ "employees_pk" ::: 'PrimaryKey '["id"]
375+ , "employees_employer_fk" ::: 'ForeignKey '["employer_id"] "employees" '["id"]
377+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
378+ , "name" ::: 'NoDef :=> 'NotNull 'PGtext
379+ , "employer_id" ::: 'NoDef :=> 'Null 'PGint4
386+ setup :: Definition (Public '[]) (Public Schema)
388+ createTable #employees
389+ ( serial `as` #id :*
390+ (text & notNullable) `as` #name :*
391+ (integer & nullable) `as` #employer_id )
392+ ( primaryKey #id `as` #employees_pk :*
393+ foreignKey #employer_id #employees #id
394+ OnDeleteCascade OnUpdateCascade `as` #employees_employer_fk )
397+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);
400+ :: (ForeignKeyed schemas sch schema child parent
406+ -- ^ column or columns in the table
408+ -- ^ reference table
409+ -> NP Alias refcolumns
410+ -- ^ reference column or columns in the reference table
412+ -- ^ what to do when reference is deleted
414+ -- ^ what to do when reference is updated
415+ -> TableConstraintExpression sch child schemas
416+ ('ForeignKey columns parent refcolumns)
417+foreignKey keys parent refs ondel onupd = UnsafeTableConstraintExpression $
418+ "FOREIGN KEY" <+> parenthesized (renderSQL keys)
419+ <+> "REFERENCES" <+> renderSQL parent
420+ <+> parenthesized (renderSQL refs)
424+-- | A constraint synonym between types involved in a foreign key constraint.
425+type ForeignKeyed schemas
433+ ( Has sch schemas schema
434+ , Has child schema ('Table table)
435+ , Has parent schema ('Table reftable)
436+ , HasAll columns (TableToColumns table) tys
437+ , reftable ~ (constraints :=> cols)
438+ , HasAll refcolumns cols reftys
439+ , SOP.AllZip SamePGType tys reftys
440+ , Uniquely refcolumns constraints )
442+-- | `OnDeleteClause` indicates what to do with rows that reference a deleted row.
445+ -- ^ if any referencing rows still exist when the constraint is checked,
446+ -- an error is raised
447+ | OnDeleteRestrict -- ^ prevents deletion of a referenced row
449+ -- ^ specifies that when a referenced row is deleted,
450+ -- row(s) referencing it should be automatically deleted as well
451+ deriving (GHC.Generic,Show,Eq,Ord)
452+instance NFData OnDeleteClause
453+-- | Render `OnDeleteClause`.
454+instance RenderSQL OnDeleteClause where
456+ OnDeleteNoAction -> "ON DELETE NO ACTION"
457+ OnDeleteRestrict -> "ON DELETE RESTRICT"
458+ OnDeleteCascade -> "ON DELETE CASCADE"
460+-- | Analagous to `OnDeleteClause` there is also `OnUpdateClause` which is invoked
461+-- when a referenced column is changed (updated).
464+ -- ^ if any referencing rows has not changed when the constraint is checked,
465+ -- an error is raised
466+ | OnUpdateRestrict -- ^ prevents update of a referenced row
468+ -- ^ the updated values of the referenced column(s) should be copied
469+ -- into the referencing row(s)
470+ deriving (GHC.Generic,Show,Eq,Ord)
471+instance NFData OnUpdateClause
473+-- | Render `OnUpdateClause`.
474+instance RenderSQL OnUpdateClause where
476+ OnUpdateNoAction -> "ON UPDATE NO ACTION"
477+ OnUpdateRestrict -> "ON UPDATE RESTRICT"
478+ OnUpdateCascade -> "ON UPDATE CASCADE"
181-- | `dropTable` removes a table from the schema.
480-- | `dropTable` removes a table from the schema.
@@ -197,56 +496,35 @@ dropTable tab = UnsafeDefinition $ "DROP TABLE" <+> renderSQL tab <> ";"
198dropTableIfExists
497dropTableIfExists
199 :: ( Has sch schemas schema
498 :: ( Has sch schemas schema
200− , KnownSymbol tab)
499+ , Has tab schema ('Table table))
201 => QualifiedAlias sch tab -- ^ table to remove
500 => QualifiedAlias sch tab -- ^ table to remove
202− -> Definition schemas (Alter sch (DropSchemumIfExists tab 'Table schema) schemas)
501+ -> Definition schemas (Alter sch (DropIfExists tab schema) schemas)
203dropTableIfExists tab = UnsafeDefinition $ "DROP TABLE IF EXISTS" <+> renderSQL tab <> ";"
502dropTableIfExists tab = UnsafeDefinition $ "DROP TABLE IF EXISTS" <+> renderSQL tab <> ";"
205-- | `alterTable` changes the definition of a table from the schema.
504-- | `alterTable` changes the definition of a table from the schema.
206alterTable
505alterTable
207− :: (Has sch schemas schema, KnownSymbol tab)
506+ :: (Has sch schemas schema, Has tab schema ('Table table0))
208 => QualifiedAlias sch tab -- ^ table to alter
507 => QualifiedAlias sch tab -- ^ table to alter
209− -> AlterTable sch tab schemas table -- ^ alteration to perform
508+ -> AlterTable sch tab schemas table1 -- ^ alteration to perform
210− -> Definition schemas (Alter sch (Alter tab ('Table table) schema) schemas)
509+ -> Definition schemas (Alter sch (Alter tab ('Table table1) schema) schemas)
211alterTable tab alteration = UnsafeDefinition $
510alterTable tab alteration = UnsafeDefinition $
212 "ALTER TABLE"
511 "ALTER TABLE"
213 <+> renderSQL tab
512 <+> renderSQL tab
214 <+> renderAlterTable alteration
513 <+> renderAlterTable alteration
217−-- | `alterTable` changes the definition of a table from the schema.
219− :: (Has sch schemas schema, KnownSymbol tab)
220− => QualifiedAlias sch tab -- ^ table to alter
221− -> AlterTable sch tab schemas table -- ^ alteration to perform
222− -> Definition schemas (Alter sch (AlterIfExists tab ('Table table) schema) schemas)
223−alterTableIfExists tab alteration = UnsafeDefinition $
224− "ALTER TABLE IF EXISTS"
226− <+> renderAlterTable alteration
229-- | `alterTableRename` changes the name of a table from the schema.
516-- | `alterTableRename` changes the name of a table from the schema.
231-- >>> printSQL $ alterTableRename #foo #bar
518-- >>> printSQL $ alterTableRename #foo #bar
232-- ALTER TABLE "foo" RENAME TO "bar";
519-- ALTER TABLE "foo" RENAME TO "bar";
233alterTableRename
520alterTableRename
234− :: (KnownSymbol tab0, KnownSymbol tab1)
521+ :: (KnownSymbol table0, KnownSymbol table1)
235− => Alias tab0 -- ^ table to rename
522+ => Alias table0 -- ^ table to rename
236− -> Alias tab1 -- ^ what to rename it
523+ -> Alias table1 -- ^ what to rename it
237− -> Definition schema (Rename tab0 tab1 schema)
524+ -> Definition schema (Rename table0 table1 schema)
238−alterTableRename tab0 tab1 = UnsafeDefinition $
525+alterTableRename table0 table1 = UnsafeDefinition $
239− "ALTER TABLE" <+> renderSQL tab0
526+ "ALTER TABLE" <+> renderSQL table0
240− <+> "RENAME TO" <+> renderSQL tab1 <> ";"
527+ <+> "RENAME TO" <+> renderSQL table1 <> ";"
242−alterTableIfExistsRename
243− :: (KnownSymbol tab0, KnownSymbol tab1)
244− => Alias tab0 -- ^ table to rename
245− -> Alias tab1 -- ^ what to rename it
246− -> Definition schema (RenameIfExists tab0 tab1 schema)
247−alterTableIfExistsRename tab0 tab1 = UnsafeDefinition $
248− "ALTER TABLE IF EXISTS" <+> renderSQL tab0
249− <+> "RENAME TO" <+> renderSQL tab1 <> ";"
251-- | An `AlterTable` describes the alteration to perform on the columns
529-- | An `AlterTable` describes the alteration to perform on the columns
252-- of a table.
530-- of a table.
@@ -498,3 +776,51 @@ dropNotNull = UnsafeAlterColumn $ "DROP NOT NULL"
498-- ALTER TABLE "tab" ALTER COLUMN "col" TYPE numeric NOT NULL;
776-- ALTER TABLE "tab" ALTER COLUMN "col" TYPE numeric NOT NULL;
499alterType :: ColumnTypeExpression schemas ty -> AlterColumn schemas ty0 ty
777alterType :: ColumnTypeExpression schemas ty -> AlterColumn schemas ty0 ty
500alterType ty = UnsafeAlterColumn $ "TYPE" <+> renderColumnTypeExpression ty
778alterType ty = UnsafeAlterColumn $ "TYPE" <+> renderColumnTypeExpression ty
780+-- | `ColumnTypeExpression`s are used in `createTable` commands.
781+newtype ColumnTypeExpression (schemas :: SchemasType) (ty :: ColumnType)
782+ = UnsafeColumnTypeExpression { renderColumnTypeExpression :: ByteString }
783+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
784+instance RenderSQL (ColumnTypeExpression schemas ty) where
785+ renderSQL = renderColumnTypeExpression
787+-- | used in `createTable` commands as a column constraint to note that
788+-- @NULL@ may be present in a column
790+ :: TypeExpression schemas (nullity ty)
791+ -> ColumnTypeExpression schemas ('NoDef :=> 'Null ty)
792+nullable ty = UnsafeColumnTypeExpression $ renderSQL ty <+> "NULL"
794+-- | used in `createTable` commands as a column constraint to ensure
795+-- @NULL@ is not present in a column
797+ :: TypeExpression schemas (nullity ty)
798+ -> ColumnTypeExpression schemas ('NoDef :=> 'NotNull ty)
799+notNullable ty = UnsafeColumnTypeExpression $ renderSQL ty <+> "NOT NULL"
801+-- | used in `createTable` commands as a column constraint to give a default
803+ :: Expression '[] '[] 'Ungrouped schemas '[] '[] ty
804+ -> ColumnTypeExpression schemas ('NoDef :=> ty)
805+ -> ColumnTypeExpression schemas ('Def :=> ty)
806+default_ x ty = UnsafeColumnTypeExpression $
807+ renderSQL ty <+> "DEFAULT" <+> renderExpression x
809+-- | not a true type, but merely a notational convenience for creating
810+-- unique identifier columns with type `PGint2`
812+ :: ColumnTypeExpression schemas ('Def :=> 'NotNull 'PGint2)
813+serial2 = UnsafeColumnTypeExpression "serial2"
814+smallserial = UnsafeColumnTypeExpression "smallserial"
815+-- | not a true type, but merely a notational convenience for creating
816+-- unique identifier columns with type `PGint4`
818+ :: ColumnTypeExpression schemas ('Def :=> 'NotNull 'PGint4)
819+serial4 = UnsafeColumnTypeExpression "serial4"
820+serial = UnsafeColumnTypeExpression "serial"
821+-- | not a true type, but merely a notational convenience for creating
822+-- unique identifier columns with type `PGint8`
824+ :: ColumnTypeExpression schemas ('Def :=> 'NotNull 'PGint8)
825+serial8 = UnsafeColumnTypeExpression "serial8"
826+bigserial = UnsafeColumnTypeExpression "bigserial"