on dev help sign in
refactor manipulations
42b84ffEitan Chatav
squeal-postgresql/exe/Example.hs+6−8
@@ -62,16 +62,14 @@ teardown = dropTable #emails >>> dropTable #users
62
62
63insertUser :: Manipulation Schema '[ 'NotNull 'PGtext, 'NotNull ('PGvararray ('Null 'PGint2))]
63insertUser :: Manipulation Schema '[ 'NotNull 'PGtext, 'NotNull ('PGvararray ('Null 'PGint2))]
64 '[ "fromOnly" ::: 'NotNull 'PGint4 ]
64 '[ "fromOnly" ::: 'NotNull 'PGint4 ]
65−insertUser = insertRows #users
65+insertUser = insertInto #users
66− (Default `as` #id :* Set (param @1) `as` #name :* Set (param @2) `as` #vec) []
66+ (Values_ (defaultAs #id :* param @1 `as` #name :* param @2 `as` #vec))
67− OnConflictDoNothing (Returning (#id `as` #fromOnly))
67+ (OnConflict (OnConstraint #pk_users) DoNothing) (Returning (#id `as` #fromOnly))
68
68
69insertEmail :: Manipulation Schema '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
69insertEmail :: Manipulation Schema '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
70−insertEmail = insertRows #emails
70+insertEmail = insertInto #emails
71− ( Default `as` #id :*
72− Set (param @1) `as` #user_id :*
71+ (Values_ (defaultAs #id :* param @1 `as` #user_id :* param @2 `as` #email))
73− Set (param @2) `as` #email ) []
72+ (OnConflict (OnConstraint #pk_emails) DoNothing) (Returning Nil)
74− OnConflictDoNothing (Returning Nil)
75
73
76getUsers :: Query Schema '[]
74getUsers :: Query Schema '[]
77 '[ "userName" ::: 'NotNull 'PGtext
75 '[ "userName" ::: 'NotNull 'PGtext
squeal-postgresql/src/Squeal/PostgreSQL/Manipulation.hs+183−201
@@ -17,6 +17,7 @@ Squeal data manipulation language.
17 , LambdaCase
17 , LambdaCase
18 , MultiParamTypeClasses
18 , MultiParamTypeClasses
19 , OverloadedStrings
19 , OverloadedStrings
20+ , PatternSynonyms
20 , RankNTypes
21 , RankNTypes
21 , TypeFamilies
22 , TypeFamilies
22 , TypeInType
23 , TypeInType
@@ -27,22 +28,24 @@ Squeal data manipulation language.
27
28
28module Squeal.PostgreSQL.Manipulation
29module Squeal.PostgreSQL.Manipulation
29 ( -- * Manipulation
30 ( -- * Manipulation
30− Manipulation (UnsafeManipulation, renderManipulation)
31+ Manipulation (..)
31 , queryStatement
32 , queryStatement
32− , ColumnValue (..)
33+ , QueryClause (..)
34+ , pattern Values_
35+ , DefaultAliasable (..)
36+ , ColumnExpression (..)
33− , ReturningClause (ReturningStar, Returning)
37+ , ReturningClause (..)
34− , ConflictClause (OnConflictDoRaise, OnConflictDoNothing, OnConflictDoUpdate)
38+ , ConflictClause (..)
39+ , ConflictTarget (..)
40+ , ConflictAction (..)
35 , UsingClause (..)
41 , UsingClause (..)
36− , ConflictTarget (OnConstraint, UniqueIndexOn)
37 -- * Insert
42 -- * Insert
38− , insertRows
43+ , insertInto
39− , insertRow
44+ , insertInto_
40− , insertRows_
41− , insertRow_
42− , insertQuery
43− , insertQuery_
44 , renderReturningClause
45 , renderReturningClause
45 , renderConflictClause
46 , renderConflictClause
47+ , renderConflictTarget
48+ , renderConflictAction
46 -- * Update
49 -- * Update
47 , update
50 , update
48 , update_
51 , update_
@@ -258,126 +261,125 @@ queryStatement q = UnsafeManipulation $ renderQuery q
258INSERT statements
261INSERT statements
259-----------------------------------------}
262-----------------------------------------}
260
263
261−-- | Insert multiple rows.
264+{- |
262−--
263−-- When a table is created, it contains no data. The first thing to do
265+When a table is created, it contains no data. The first thing to do
264−-- before a database can be of much use is to insert data. Data is
266+before a database can be of much use is to insert data. Data is
265−-- conceptually inserted one row at a time. Of course you can also insert
267+conceptually inserted one row at a time. Of course you can also insert
266−-- more than one row, but there is no way to insert less than one row.
268+more than one row, but there is no way to insert less than one row.
267−-- Even if you know only some column values, a complete row must be created.
269+Even if you know only some column values, a complete row must be created.
268−insertRows
270+-}
269− :: ( SOP.SListI columns
271+insertInto
270− , SOP.SListI results
271− , Has tab schema ('Table table)
272+ :: ( Has tab schema ('Table table)
273+ , columns ~ TableToColumns table
272 , row ~ TableToRow table
274 , row ~ TableToRow table
273− , columns ~ TableToColumns table )
275+ , SOP.SListI columns
276+ , SOP.SListI result )
274− => Alias tab -- ^ table to insert into
277+ => Alias tab
275− -> NP (Aliased (ColumnValue schema '[] params)) columns -- ^ row to insert
276− -> [NP (Aliased (ColumnValue schema '[] params)) columns] -- ^ more rows to insert
278+ -> QueryClause schema params columns
277− -> ConflictClause schema table params
279+ -> ConflictClause schema params table
278− -- ^ what to do in case of constraint conflict
279− -> ReturningClause schema params row results -- ^ results to return
280+ -> ReturningClause schema params row result
280− -> Manipulation schema params results
281+ -> Manipulation schema params result
281−insertRows tab rw rws conflict returning = UnsafeManipulation $
282+insertInto tab qry conflict ret = UnsafeManipulation $
282 "INSERT" <+> "INTO" <+> renderAlias tab
283 "INSERT" <+> "INTO" <+> renderAlias tab
283− <> renderConflictAlias conflict
284+ <+> renderQueryClause qry
284− <+> parenthesized (renderCommaSeparated renderAliasPart rw)
285− <+> "VALUES"
286− <+> commaSeparated
287− ( parenthesized
288− . renderCommaSeparated renderColumnValuePart <$> rw:rws )
289− <> renderConflictClause conflict
285+ <> renderConflictClause conflict
290− <> renderReturningClause returning
286+ <> renderReturningClause ret
291− where
292− renderAliasPart, renderColumnValuePart
293− :: Aliased (ColumnValue schema '[] params) ty -> ByteString
294− renderAliasPart (_ `As` name) = renderAlias name
295− renderColumnValuePart (value `As` _) = case value of
296− Default -> "DEFAULT"
297− Set expression -> renderExpression expression
298−
299−-- | Insert a single row.
300−insertRow
301− :: ( SOP.SListI columns
302− , SOP.SListI results
303− , Has tab schema ('Table table)
304− , row ~ TableToRow table
305− , columns ~ TableToColumns table )
306− => Alias tab -- ^ table to insert into
307− -> NP (Aliased (ColumnValue schema '[] params)) columns -- ^ row to insert
308− -> ConflictClause schema table params
309− -- ^ what to do in case of constraint conflict
310− -> ReturningClause schema params row results -- ^ results to return
311− -> Manipulation schema params results
312−insertRow tab rw = insertRows tab rw []
313
287
314−-- | Insert multiple rows returning `Nil` and raising an error on conflicts.
288+insertInto_
315−insertRows_
316− :: ( SOP.SListI columns
317− , Has tab schema ('Table table)
318− , columns ~ TableToColumns table )
319− => Alias tab -- ^ table to insert into
320− -> NP (Aliased (ColumnValue schema '[] params)) columns -- ^ row to insert
321− -> [NP (Aliased (ColumnValue schema '[] params)) columns] -- ^ more rows to insert
322− -> Manipulation schema params '[]
323−insertRows_ tab rw rws =
324− insertRows tab rw rws OnConflictDoRaise (Returning Nil)
325−
326−-- | Insert a single row returning `Nil` and raising an error on conflicts.
327−insertRow_
328− :: ( SOP.SListI columns
329− , Has tab schema ('Table table)
289+ :: ( Has tab schema ('Table table)
330− , columns ~ TableToColumns table )
290+ , columns ~ TableToColumns table
331− => Alias tab -- ^ table to insert into
332− -> NP (Aliased (ColumnValue schema '[] params)) columns -- ^ row to insert
333− -> Manipulation schema params '[]
334−insertRow_ tab rw = insertRow tab rw OnConflictDoRaise (Returning Nil)
335−
336−-- | Insert a `Query`.
337−insertQuery
338− :: ( SOP.SListI columns
339− , SOP.SListI results
340− , Has tab schema ('Table table)
341 , row ~ TableToRow table
291 , row ~ TableToRow table
342− , columns ~ TableToColumns table )
343− => Alias tab -- ^ table to insert into
344− -> Query schema params (TableToRow table)
345− -> ConflictClause schema table params
346− -- ^ what to do in case of constraint conflict
347− -> ReturningClause schema params row results -- ^ results to return
348− -> Manipulation schema params results
349−insertQuery tab query conflict returning = UnsafeManipulation $
350− "INSERT" <+> "INTO" <+> renderAlias tab
351− <> renderConflictAlias conflict
352− <+> renderQuery query
353− <> renderConflictClause conflict
354− <> renderReturningClause returning
355−
356−-- | Insert a `Query` returning `Nil` and raising an error on conflicts.
357−insertQuery_
358− :: ( SOP.SListI columns
292+ , SOP.SListI columns )
359− , Has tab schema ('Table table)
360− , columns ~ TableToColumns table )
361− => Alias tab -- ^ table to insert into
293+ => Alias tab
362− -> Query schema params (TableToRow table)
294+ -> QueryClause schema params columns
363 -> Manipulation schema params '[]
295 -> Manipulation schema params '[]
364−insertQuery_ tab query =
296+insertInto_ tab qry =
365− insertQuery tab query OnConflictDoRaise (Returning Nil)
297+ insertInto tab qry OnConflictDoRaise (Returning Nil)
366−
298+
367−-- | `ColumnValue`s are values to insert or update in a row.
299+data QueryClause schema params columns where
368−-- `Default` inserts or updates with the @DEFAULT@ value.
300+ Values
369−-- `Set` sets a value to be an `Expression`, which can refer to
301+ :: SOP.SListI columns
370−-- existing value in the row for an update.
302+ => NP (ColumnExpression schema '[] 'Ungrouped params) columns
371−data ColumnValue
303+ -> [NP (ColumnExpression schema '[] 'Ungrouped params) columns]
372− (schema :: SchemaType)
304+ -> QueryClause schema params columns
373− (tables :: FromType)
305+ Select
374− (params :: [NullityType])
306+ :: SOP.SListI columns
375− (ty :: ColumnType)
307+ => NP (ColumnExpression schema from grp params) columns
308+ -> TableExpression schema params from grp
309+ -> QueryClause schema params columns
310+ Subquery
311+ :: ColumnsToRow columns ~ row
312+ => Query schema params row
313+ -> QueryClause schema params columns
314+
315+renderQueryClause
316+ :: QueryClause schema params columns
317+ -> ByteString
318+renderQueryClause = \case
319+ Values row0 rows ->
320+ parenthesized (renderCommaSeparated renderAliasPart row0)
321+ <+> "VALUES"
322+ <+> commaSeparated
323+ ( parenthesized
324+ . renderCommaSeparated renderValuePart <$> row0 : rows )
325+ Select row0 tab ->
326+ parenthesized (renderCommaSeparated renderAliasPart row0)
327+ <+> "SELECT"
328+ <+> renderCommaSeparated renderValuePart row0
329+ <+> renderTableExpression tab
330+ Subquery qry -> renderQuery qry
376 where
331 where
377− Default :: ColumnValue schema tables params ('Def :=> ty)
332+ renderAliasPart, renderValuePart
378− Set
333+ :: ColumnExpression schema from grp params column -> ByteString
334+ renderAliasPart = \case
335+ DefaultAs name -> renderAlias name
336+ Specific (_ `As` name) -> renderAlias name
337+ renderValuePart = \case
338+ DefaultAs _ -> "DEFAULT"
339+ Specific (value `As` _) -> renderExpression value
340+
341+pattern Values_
342+ :: SOP.SListI columns
379− :: Expression schema tables 'Ungrouped params ty
343+ => NP (ColumnExpression schema '[] 'Ungrouped params) columns
380− -> ColumnValue schema tables params (constraint :=> ty)
344+ -> QueryClause schema params columns
345+pattern Values_ vals = Values vals []
346+
347+data ColumnExpression schema from grp params column where
348+ DefaultAs
349+ :: KnownSymbol col
350+ => Alias col
351+ -> ColumnExpression schema from grp params (col ::: 'Def :=> ty)
352+ Specific
353+ :: Aliased (Expression schema from grp params) (col ::: ty)
354+ -> ColumnExpression schema from grp params (col ::: defness :=> ty)
355+
356+instance (KnownSymbol alias, column ~ (alias ::: defness :=> ty))
357+ => Aliasable alias
358+ (Expression schema from grp params ty)
359+ (ColumnExpression schema from grp params column)
360+ where
361+ expression `as` alias = Specific (expression `As` alias)
362+instance (KnownSymbol alias, columns ~ '[alias ::: defness :=> ty])
363+ => Aliasable alias
364+ (Expression schema from grp params ty)
365+ (NP (ColumnExpression schema from grp params) columns)
366+ where
367+ expression `as` alias = expression `as` alias :* Nil
368+instance (KnownSymbol col, column ~ (col ::: 'Def :=> ty))
369+ => DefaultAliasable col (ColumnExpression schema from grp params column) where
370+ defaultAs = DefaultAs
371+instance (KnownSymbol col, columns ~ '[col ::: 'Def :=> ty])
372+ => DefaultAliasable col (NP (ColumnExpression schema from grp params) columns) where
373+ defaultAs col = defaultAs col :* Nil
374+
375+renderColumnExpression
376+ :: ColumnExpression schema from grp params column
377+ -> ByteString
378+renderColumnExpression = \case
379+ DefaultAs col ->
380+ renderAlias col <+> "=" <+> "DEFAULT"
381+ Specific (expression `As` col) ->
382+ renderAlias col <+> "=" <+> renderExpression expression
381
383
382-- | A `ReturningClause` computes and return value(s) based
384-- | A `ReturningClause` computes and return value(s) based
383-- on each row actually inserted, updated or deleted. This is primarily
385-- on each row actually inserted, updated or deleted. This is primarily
@@ -417,74 +419,61 @@ renderReturningClause = \case
417-- `OnConflictDoNothing` simply avoids inserting a row.
419-- `OnConflictDoNothing` simply avoids inserting a row.
418-- `OnConflictDoUpdate` updates the existing row that conflicts with the row
420-- `OnConflictDoUpdate` updates the existing row that conflicts with the row
419-- proposed for insertion.
421-- proposed for insertion.
420−data ConflictClause
422+data ConflictClause schema params table where
421− (schema :: SchemaType)
422− (table :: TableType)
423− (params :: [NullityType]) where
424− OnConflictDoRaise :: ConflictClause schema table params
423+ OnConflictDoRaise :: ConflictClause schema params table
425− OnConflictDoNothing :: ConflictClause schema table params
424+ OnConflict
426− OnConflictDoUpdate
427− :: ( row ~ TableToRow table, SOP.SListI columns, columns ~ (col0 ': cols)
428− , OrderedSubsetOf (TableToColumns table) columns )
429− => ConflictTarget table
425+ :: ConflictTarget constraints
430− -> NP (Aliased (ColumnValue schema '["existing" ::: row, "excluded" ::: row] params)) columns
426+ -> ConflictAction schema params columns
431− -> [Condition schema '["existing" ::: row, "excluded" ::: row] 'Ungrouped params]
432− -> ConflictClause schema table params
427+ -> ConflictClause schema params (constraints :=> columns)
433−
434−-- | A `ConflictTarget` specifies the constraint violation that triggers an
435−-- `OnConflictDoUpdate`.
436−data ConflictTarget (table :: TableType) where
437− -- | An explicitly named constraint.
438− OnConstraint
439− :: (Has constraintName constraints constraint)
440− => Alias constraintName
441− -> ConflictTarget '(constraints, columns)
442− -- | Match each unique index with precisely the following columns.
443− UniqueIndexOn
444− :: ( HasAll columnNames columns columnTypes
445− , SOP.All KnownSymbol columnNames )
446− => NP Alias columnNames
447− -> ConflictTarget '(constraints, columns)
448−
449−-- | Render any table alias required for a `ConflictClause`.
450−renderConflictAlias :: ConflictClause schema table params -> ByteString
451−renderConflictAlias = \case
452− OnConflictDoUpdate _ _ _ -> " AS existing"
453− _ -> ""
454
428
455-- | Render a `ConflictClause`.
429-- | Render a `ConflictClause`.
456renderConflictClause
430renderConflictClause
457 :: SOP.SListI (TableToColumns table)
431 :: SOP.SListI (TableToColumns table)
458− => ConflictClause schema table params
432+ => ConflictClause schema params table
459 -> ByteString
433 -> ByteString
460renderConflictClause = \case
434renderConflictClause = \case
461 OnConflictDoRaise -> ""
435 OnConflictDoRaise -> ""
462− OnConflictDoNothing -> " ON CONFLICT DO NOTHING"
436+ OnConflict target action -> " ON CONFLICT"
463− OnConflictDoUpdate target updates whs'
437+ <+> renderConflictTarget target <+> renderConflictAction action
464− -> " ON CONFLICT"
438+
465− <+> renderTarget target
439+data ConflictAction schema params columns where
440+ DoNothing :: ConflictAction schema params columns
441+ DoUpdate
442+ :: ( row ~ ColumnsToRow columns
443+ , SOP.SListI columns
444+ , columns ~ (col0 ': cols)
445+ , SOP.All (HasIn columns) subcolumns
446+ , AllUnique subcolumns )
447+ => NP (ColumnExpression schema '[t ::: row] 'Ungrouped params) subcolumns
448+ -> [Condition schema '[t ::: row] 'Ungrouped params]
449+ -> ConflictAction schema params columns
450+
451+renderConflictAction
452+ :: ConflictAction schema params columns
453+ -> ByteString
454+renderConflictAction = \case
455+ DoNothing -> "DO NOTHING"
456+ DoUpdate updates whs'
466− <+> "DO UPDATE SET"
457+ -> "DO UPDATE SET"
467− <+> renderCommaSeparated renderUpdate updates
458+ <+> renderCommaSeparated renderColumnExpression updates
468 <> case whs' of
459 <> case whs' of
469 [] -> ""
460 [] -> ""
470 wh:whs -> " WHERE" <+> renderExpression (foldr (.&&) wh whs)
461 wh:whs -> " WHERE" <+> renderExpression (foldr (.&&) wh whs)
471− where
462+
472− renderUpdate
463+-- | A `ConflictTarget` specifies the constraint violation that triggers a
473− :: Aliased (ColumnValue schema columns params) column
464+-- `ConflictAction`.
474− -> ByteString
465+data ConflictTarget constraints where
475− renderUpdate = \case
466+ OnConstraint
476− Default `As` column ->
467+ :: Has con constraints constraint
477− renderAlias column <+> "=" <+> "DEFAULT"
468+ => Alias con
478− Set expression `As` column ->
469+ -> ConflictTarget constraints
479− renderAlias column <+> "=" <+> renderExpression expression
470+
480− renderTarget
471+-- | Render a `ConflictTarget`
472+renderConflictTarget
481− :: ConflictTarget table
473+ :: ConflictTarget constraints
482− -> ByteString
474+ -> ByteString
483− renderTarget = \case
484− OnConstraint name ->
475+renderConflictTarget (OnConstraint con) =
485− "ON CONSTRAINT" <+> renderAlias name
476+ "ON" <+> "CONSTRAINT" <+> renderAlias con
486− UniqueIndexOn columns ->
487− parenthesized (commaSeparated (renderAliases columns))
488
477
489{-----------------------------------------
478{-----------------------------------------
490UPDATE statements
479UPDATE statements
@@ -517,12 +506,13 @@ update
517 , SOP.SListI results
506 , SOP.SListI results
518 , Has tab schema ('Table table)
507 , Has tab schema ('Table table)
519 , row ~ TableToRow table
508 , row ~ TableToRow table
520− , columns ~ (col0 ': cols)
521− , OrderedSubsetOf (TableToColumns table) columns )
509+ , columns ~ TableToColumns table
510+ , SOP.All (HasIn columns) subcolumns
511+ , AllUnique subcolumns )
522 => Alias tab -- ^ table to update
512 => Alias tab -- ^ table to update
523− -> (forall t. NP (Aliased (ColumnValue schema '[t ::: row] params)) columns)
513+ -> NP (ColumnExpression schema '[tab ::: row] 'Ungrouped params) subcolumns
524 -- ^ modified values to replace old values
514 -- ^ modified values to replace old values
525− -> (forall t. Condition schema '[t ::: row] 'Ungrouped params)
515+ -> Condition schema '[tab ::: row] 'Ungrouped params
526 -- ^ condition under which to perform update on a row
516 -- ^ condition under which to perform update on a row
527 -> ReturningClause schema params row results -- ^ results to return
517 -> ReturningClause schema params row results -- ^ results to return
528 -> Manipulation schema params results
518 -> Manipulation schema params results
@@ -530,30 +520,22 @@ update tab columns wh returning = UnsafeManipulation $
530 "UPDATE"
520 "UPDATE"
531 <+> renderAlias tab
521 <+> renderAlias tab
532 <+> "SET"
522 <+> "SET"
533− <+> renderCommaSeparated renderUpdate columns
523+ <+> renderCommaSeparated renderColumnExpression columns
534 <+> "WHERE" <+> renderExpression wh
524 <+> "WHERE" <+> renderExpression wh
535 <> renderReturningClause returning
525 <> renderReturningClause returning
536− where
537− renderUpdate
538− :: Aliased (ColumnValue schema columns params) column
539− -> ByteString
540− renderUpdate = \case
541− Default `As` column ->
542− renderAlias column <+> "=" <+> "DEFAULT"
543− Set expression `As` column ->
544− renderAlias column <+> "=" <+> renderExpression expression
545
526
546-- | Update a row returning `Nil`.
527-- | Update a row returning `Nil`.
547update_
528update_
548 :: ( SOP.SListI columns
529 :: ( SOP.SListI columns
549 , Has tab schema ('Table table)
530 , Has tab schema ('Table table)
550 , row ~ TableToRow table
531 , row ~ TableToRow table
551− , columns ~ (col0 ': cols)
552− , OrderedSubsetOf (TableToColumns table) columns )
532+ , columns ~ TableToColumns table
533+ , SOP.All (HasIn columns) subcolumns
534+ , AllUnique subcolumns )
553 => Alias tab -- ^ table to update
535 => Alias tab -- ^ table to update
554− -> (forall t. NP (Aliased (ColumnValue schema '[t ::: row] params)) columns)
536+ -> NP (ColumnExpression schema '[tab ::: row] 'Ungrouped params) subcolumns
555 -- ^ modified values to replace old values
537 -- ^ modified values to replace old values
556− -> (forall t. Condition schema '[t ::: row] 'Ungrouped params)
538+ -> Condition schema '[tab ::: row] 'Ungrouped params
557 -- ^ condition under which to perform update on a row
539 -- ^ condition under which to perform update on a row
558 -> Manipulation schema params '[]
540 -> Manipulation schema params '[]
559update_ tab columns wh = update tab columns wh (Returning Nil)
541update_ tab columns wh = update tab columns wh (Returning Nil)
squeal-postgresql/src/Squeal/PostgreSQL/Migration.hs+2−3
@@ -273,9 +273,8 @@ createMigrations =
273insertMigration
273insertMigration
274 :: Has "schema_migrations" schema ('Table MigrationsTable)
274 :: Has "schema_migrations" schema ('Table MigrationsTable)
275 => Manipulation schema '[ 'NotNull 'PGtext] '[]
275 => Manipulation schema '[ 'NotNull 'PGtext] '[]
276−insertMigration = insertRow_ #schema_migrations
276+insertMigration = insertInto_ #schema_migrations . Values_ $
277− ( Set (param @1) `as` #name :*
277+ (param @1) `as` #name :* defaultAs #executed_at
278− Default `as` #executed_at )
279
278
280-- | Deletes a `Migration` from the `MigrationsTable`
279-- | Deletes a `Migration` from the `MigrationsTable`
281deleteMigration
280deleteMigration
squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+24
@@ -74,6 +74,10 @@ module Squeal.PostgreSQL.Schema
74 , IsLabel (..)
74 , IsLabel (..)
75 , IsQualified (..)
75 , IsQualified (..)
76 , renderAliasString
76 , renderAliasString
77+ , HasIn
78+ , IsNotElem
79+ , AllUnique
80+ , DefaultAliasable (..)
77 -- * Enumerated Labels
81 -- * Enumerated Labels
78 , IsPGlabel (..)
82 , IsPGlabel (..)
79 , PGlabel (..)
83 , PGlabel (..)
@@ -117,6 +121,7 @@ module Squeal.PostgreSQL.Schema
117 , NullifyFrom
121 , NullifyFrom
118 -- * Table Conversions
122 -- * Table Conversions
119 , TableToColumns
123 , TableToColumns
124+ , ColumnsToRow
120 , TableToRow
125 , TableToRow
121 ) where
126 ) where
122
127
@@ -449,6 +454,25 @@ instance {-# OVERLAPPING #-} KnownSymbol alias
449instance {-# OVERLAPPABLE #-} (KnownSymbol alias, Has alias fields field)
454instance {-# OVERLAPPABLE #-} (KnownSymbol alias, Has alias fields field)
450 => Has alias (field' ': fields) field
455 => Has alias (field' ': fields) field
451
456
457+class HasIn fields (x :: (Symbol, a)) where
458+instance (Has alias fields field) => HasIn fields '(alias, field) where
459+
460+-- | Utility class for `AllUnique` to provide nicer error messages.
461+class IsNotElem x isElem where
462+instance IsNotElem x 'False where
463+instance (TypeError ( 'Text "Cannot assign to "
464+ ':<>: 'ShowType alias
465+ ':<>: 'Text " more than once"))
466+ => IsNotElem '(alias, a) 'True where
467+ -- | No elem of @xs@ appears more than once, in the context of assignment.
468+class AllUnique (xs :: [(Symbol, a)]) where
469+instance AllUnique '[] where
470+instance (IsNotElem x (Elem x xs), AllUnique xs) => AllUnique (x ': xs) where
471+
472+class KnownSymbol alias
473+ => DefaultAliasable alias aliased | aliased -> alias where
474+ defaultAs :: Alias alias -> aliased
475+
452-- | `HasAll` extends `Has` to take lists of @aliases@ and @fields@ and infer
476-- | `HasAll` extends `Has` to take lists of @aliases@ and @fields@ and infer
453-- a list of @subfields@.
477-- a list of @subfields@.
454class
478class