@@ -19,7 +19,7 @@ import Control.Monad.Except (runExcept)
1919import Data.Either (Either(..))
2020import Data.Map as Map
2121import Data.Symbol (class IsSymbol, reflectSymbol)
22−import Data.Tuple.Nested (type (/\))
22+import Type.Function (type (#))
2323import Effect.Aff (Aff)
2424import Foreign (Foreign, unsafeToForeign)
2525import Prim.Boolean (True, False)
@@ -29,7 +29,6 @@ import Prim.RowList (class RowToList)
2929import Prim.Symbol (class Cons, class Append) as Symbol
3030import Prim.TypeError (class Fail, Beside, Text, Quote)
3131import Record (get) as Record
32−import Type.Data.Boolean (class Or)
3332import Type.Proxy (Proxy(..))
3433import Type.RowList (class ListToRow)
3534import Yoga.JSON (class ReadForeign, readImpl)
@@ -42,26 +41,37 @@ import Yoga.Postgres as PG
4241data Table :: Symbol -> Row Type -> Type
4342data Table name columns = Table
4443
45−data Column :: Type -> Type -> Type
46−data Column typ constraints = Column
47−
4844-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
49−-- Constraint phantom types
45+-- Constraint wrappers (used with # operator from Type.Function)
46+-- Int # PrimaryKey # AutoIncrement = AutoIncrement (PrimaryKey Int)
5047-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
5148
52−data PrimaryKey
53−data AutoIncrement
54−data Unique
55−data None
49+data PrimaryKey :: Type -> Type
50+data PrimaryKey a
51+
52+data AutoIncrement :: Type -> Type
53+data AutoIncrement a
54+
55+data Unique :: Type -> Type
56+data Unique a
57+
58+data Default :: Symbol -> Type -> Type
59+data Default sym a
60+
61+data DefaultInt :: Int -> Type -> Type
62+data DefaultInt val a
5663
57−data Default :: Symbol -> Type
58−data Default a
64+data DefaultBool :: Boolean -> Type -> Type
65+data DefaultBool val a
5966
60−data DefaultInt :: Int -> Type
61−data DefaultInt a
67+data References
6268
63−data DefaultBool :: Boolean -> Type
64−data DefaultBool a
69+data ForeignKey :: Symbol -> Type -> Symbol -> Type -> Type
70+data ForeignKey table references col a
71+
72+-- Internal: used by LEFT JOIN to mark columns as nullable
73+data Nullable :: Type -> Type
74+data Nullable a
6575
6676-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6777-- JSONB type wrapper
@@ -86,6 +96,23 @@ instance IsNullable (Maybe a) where
8696else instance IsNullable a where
8797 isNullable _ = false
8898
99+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
100+-- ExtractType: recursively unwrap constraint wrappers to get base type
101+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
102+
103+class ExtractType :: Type -> Type -> Constraint
104+class ExtractType wrapped typ | wrapped -> typ
105+
106+instance ExtractType a typ => ExtractType (PrimaryKey a) typ
107+else instance ExtractType a typ => ExtractType (AutoIncrement a) typ
108+else instance ExtractType a typ => ExtractType (Unique a) typ
109+else instance ExtractType a typ => ExtractType (Default sym a) typ
110+else instance ExtractType a typ => ExtractType (DefaultInt val a) typ
111+else instance ExtractType a typ => ExtractType (DefaultBool val a) typ
112+else instance ExtractType a typ => ExtractType (ForeignKey t r c a) typ
113+else instance ExtractType a typ => ExtractType (Nullable a) (Maybe typ)
114+else instance ExtractType a a
115+
89116-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
90117-- PureScript type -> Postgres type name
91118-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -127,37 +154,42 @@ instance PGTypeName a => PGTypeName (Maybe a) where
127154class RenderConstraint a where
128155 renderConstraint :: Proxy a -> String
129156
130−instance RenderConstraint PrimaryKey where
131− renderConstraint _ = "PRIMARY KEY"
157+instance RenderConstraint a => RenderConstraint (PrimaryKey a) where
158+ renderConstraint _ = joinConstraints "PRIMARY KEY" (renderConstraint (Proxy :: Proxy a))
132159
133−instance RenderConstraint AutoIncrement where
134− renderConstraint _ = "GENERATED ALWAYS AS IDENTITY"
160+else instance RenderConstraint a => RenderConstraint (AutoIncrement a) where
161+ renderConstraint _ = joinConstraints "GENERATED ALWAYS AS IDENTITY" (renderConstraint (Proxy :: Proxy a))
135162
136−instance RenderConstraint Unique where
137− renderConstraint _ = "UNIQUE"
163+else instance RenderConstraint a => RenderConstraint (Unique a) where
164+ renderConstraint _ = joinConstraints "UNIQUE" (renderConstraint (Proxy :: Proxy a))
138165
139−instance RenderConstraint None where
140− renderConstraint _ = ""
166+else instance (Reflectable sym String, RenderConstraint a) => RenderConstraint (Default sym a) where
167+ renderConstraint _ = joinConstraints ("DEFAULT '" <> reflectType (Proxy :: Proxy sym) <> "'") (renderConstraint (Proxy :: Proxy a))
168+
169+else instance (Reflectable val Int, RenderConstraint a) => RenderConstraint (DefaultInt val a) where
170+ renderConstraint _ = joinConstraints ("DEFAULT " <> show (reflectType (Proxy :: Proxy val))) (renderConstraint (Proxy :: Proxy a))
141171
142−instance Reflectable sym String => RenderConstraint (Default sym) where
143− renderConstraint _ = "DEFAULT " <> reflectType (Proxy :: Proxy sym)
172+else instance (Reflectable val Boolean, RenderConstraint a) => RenderConstraint (DefaultBool val a) where
173+ renderConstraint _ = joinConstraints
174+ (if reflectType (Proxy :: Proxy val) then "DEFAULT true" else "DEFAULT false")
175+ (renderConstraint (Proxy :: Proxy a))
144176
145−instance Reflectable val Int => RenderConstraint (DefaultInt val) where
146− renderConstraint _ = "DEFAULT " <> show (reflectType (Proxy :: Proxy val))
177+else instance (IsSymbol table, IsSymbol col, RenderConstraint a) => RenderConstraint (ForeignKey table References col a) where
178+ renderConstraint _ = joinConstraints
179+ ("REFERENCES " <> reflectSymbol (Proxy :: Proxy table) <> "(" <> reflectSymbol (Proxy :: Proxy col) <> ")")
180+ (renderConstraint (Proxy :: Proxy a))
147181
148−instance Reflectable val Boolean => RenderConstraint (DefaultBool val) where
149− renderConstraint _ =
150− if reflectType (Proxy :: Proxy val) then "DEFAULT true"
151− else "DEFAULT false"
182+else instance RenderConstraint a => RenderConstraint (Nullable a) where
183+ renderConstraint _ = renderConstraint (Proxy :: Proxy a)
184+
185+else instance RenderConstraint a where
186+ renderConstraint _ = ""
152187
153−instance (RenderConstraint a, RenderConstraint b) => RenderConstraint (a /\ b) where
154− renderConstraint _ = do
155− let a = renderConstraint (Proxy :: Proxy a)
156− let b = renderConstraint (Proxy :: Proxy b)
157− case a, b of
158− "", s -> s
159− s, "" -> s
160− s1, s2 -> s1 <> " " <> s2
188+joinConstraints :: String -> String -> String
189+joinConstraints a b = case a, b of
190+ "", s -> s
191+ s, "" -> s
192+ s1, s2 -> s1 <> " " <> s2
161193
162194-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
163195-- DDL generation
@@ -175,18 +207,19 @@ instance RenderColumnsRL RL.Nil where
175207
176208instance
177209 ( IsSymbol name
210+ , ExtractType entry typ
178211 , PGTypeName typ
179212 , IsNullable typ
180− , RenderConstraint constraints
213+ , RenderConstraint entry
181214 , RenderColumnsRL tail
182215 ) =>
183− RenderColumnsRL (RL.Cons name (Column typ constraints) tail) where
216+ RenderColumnsRL (RL.Cons name entry tail) where
184217 renderColumnsRL _ = do
185218 let colName = reflectSymbol (Proxy :: Proxy name)
186219 let colType = pgTypeName (Proxy :: Proxy typ)
187220 let nullable = isNullable (Proxy :: Proxy typ)
188221 let notNull = if nullable then "" else " NOT NULL"
189− let constraints = renderConstraint (Proxy :: Proxy constraints)
222+ let constraints = renderConstraint (Proxy :: Proxy entry)
190223 let constraintsSuffix = if constraints == "" then "" else " " <> constraints
191224 [ colName <> " " <> colType <> notNull <> constraintsSuffix ] <> renderColumnsRL (Proxy :: Proxy tail)
192225
@@ -208,16 +241,22 @@ instance
208241class IsAutoGenerated a where
209242 isAutoGenerated :: Proxy a -> Boolean
210243
211−instance IsAutoGenerated AutoIncrement where
244+instance IsAutoGenerated (AutoIncrement a) where
212245 isAutoGenerated _ = true
213−else instance IsAutoGenerated (Default a) where
246+else instance IsAutoGenerated (Default s a) where
214247 isAutoGenerated _ = true
215−else instance IsAutoGenerated (DefaultInt a) where
248+else instance IsAutoGenerated (DefaultInt v a) where
216249 isAutoGenerated _ = true
217−else instance IsAutoGenerated (DefaultBool a) where
250+else instance IsAutoGenerated (DefaultBool v a) where
218251 isAutoGenerated _ = true
219−else instance (IsAutoGenerated a, IsAutoGenerated b) => IsAutoGenerated (a /\ b) where
220− isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a) || isAutoGenerated (Proxy :: Proxy b)
252+else instance IsAutoGenerated a => IsAutoGenerated (PrimaryKey a) where
253+ isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a)
254+else instance IsAutoGenerated a => IsAutoGenerated (Unique a) where
255+ isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a)
256+else instance IsAutoGenerated a => IsAutoGenerated (ForeignKey t r c a) where
257+ isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a)
258+else instance IsAutoGenerated a => IsAutoGenerated (Nullable a) where
259+ isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a)
221260else instance IsAutoGenerated a where
222261 isAutoGenerated _ = false
223262
@@ -230,15 +269,15 @@ instance InsertColumnsRL RL.Nil where
230269
231270instance
232271 ( IsSymbol name
233− , IsAutoGenerated constraints
272+ , IsAutoGenerated entry
234273 , InsertColumnsRL tail
235274 ) =>
236− InsertColumnsRL (RL.Cons name (Column typ constraints) tail) where
275+ InsertColumnsRL (RL.Cons name entry tail) where
237276 insertColumnsRL _ =
238277 let
239278 rest = insertColumnsRL (Proxy :: Proxy tail)
240279 in
241− if isAutoGenerated (Proxy :: Proxy constraints) then rest
280+ if isAutoGenerated (Proxy :: Proxy entry) then rest
242281 else [ reflectSymbol (Proxy :: Proxy name) ] <> rest
243282
244283class InsertSQLFor a where
@@ -412,12 +451,12 @@ else instance
412451 ) =>
413452 ExtractWordGo h tail acc word rest
414453
415−-- StripColumns: (name :: Column String None, ...) -> (name :: String, ...)
454+-- StripColumns: (name :: String # Unique, ...) -> (name :: String, ...)
416455class StripColumnsRL :: RL.RowList Type -> RL.RowList Type -> Constraint
417456class StripColumnsRL rl out | rl -> out
418457
419458instance StripColumnsRL RL.Nil RL.Nil
420−instance StripColumnsRL tail out' => StripColumnsRL (RL.Cons name (Column typ constraints) tail) (RL.Cons name typ out')
459+instance (ExtractType entry typ, StripColumnsRL tail out') => StripColumnsRL (RL.Cons name entry tail) (RL.Cons name typ out')
421460
422461class StripColumns :: Row Type -> Row Type -> Constraint
423462class StripColumns cols result | cols -> result
@@ -588,7 +627,8 @@ instance
588627
589628-- Comma: emit column, continue
590629else instance
591− ( ResolveColumn acc tables (Column typ constraints)
630+ ( ResolveColumn acc tables entry
631+ , ExtractType entry typ
592632 , SplitOnDot acc _hasDot _table colName
593633 , SkipSpaces tail rest
594634 , ParseSelectContinue rest tables (RL.Cons colName typ accRL) outRL
@@ -615,7 +655,8 @@ else instance
615655-- End of string: emit final column
616656else instance
617657 ( Symbol.Append acc h acc'
618− , ResolveColumn acc' tables (Column typ constraints)
658+ , ResolveColumn acc' tables entry
659+ , ExtractType entry typ
619660 , SplitOnDot acc' _hasDot _table colName
620661 ) =>
621662 ParseSelectGo h "" acc tables accRL (RL.Cons colName typ accRL)
@@ -634,7 +675,8 @@ class ParseSelectAfterCol colRef rest tables accRL outRL | colRef rest tables ac
634675
635676-- End: emit column with default label (column name after dot)
636677instance
637− ( ResolveColumn colRef tables (Column typ constraints)
678+ ( ResolveColumn colRef tables entry
679+ , ExtractType entry typ
638680 , SplitOnDot colRef _hasDot _table colName
639681 ) =>
640682 ParseSelectAfterCol colRef "" tables accRL (RL.Cons colName typ accRL)
@@ -651,7 +693,8 @@ class ParseSelectAfterColByHead head tail colRef tables accRL outRL | head tail
651693
652694-- Comma: emit column with default label, continue
653695instance
654− ( ResolveColumn colRef tables (Column typ constraints)
696+ ( ResolveColumn colRef tables entry
697+ , ExtractType entry typ
655698 , SplitOnDot colRef _hasDot _table colName
656699 , SkipSpaces tail rest
657700 , ParseSelectContinue rest tables (RL.Cons colName typ accRL) outRL
@@ -671,7 +714,8 @@ class ParseSelectHandleAS keyword afterKeyword colRef tables accRL outRL | keywo
671714
672715instance
673716 ( ExtractWord afterKeyword alias afterAlias
674− , ResolveColumn colRef tables (Column typ constraints)
717+ , ResolveColumn colRef tables entry
718+ , ExtractType entry typ
675719 , SkipSpaces afterAlias rest
676720 , ParseSelectExpectEnd rest tables (RL.Cons alias typ accRL) outRL
677721 ) =>
@@ -679,7 +723,8 @@ instance
679723
680724else instance
681725 ( ExtractWord afterKeyword alias afterAlias
682− , ResolveColumn colRef tables (Column typ constraints)
726+ , ResolveColumn colRef tables entry
727+ , ExtractType entry typ
683728 , SkipSpaces afterAlias rest
684729 , ParseSelectExpectEnd rest tables (RL.Cons alias typ accRL) outRL
685730 ) =>
@@ -789,7 +834,8 @@ else instance ResolveAggregateArgColByHead "8" col tables Star
789834else instance ResolveAggregateArgColByHead "9" col tables Star
790835-- Column reference
791836else instance
792− ( ResolveColumn col tables (Column typ constraints)
837+ ( ResolveColumn col tables entry
838+ , ExtractType entry typ
793839 , UnwrapMaybe typ unwrapped
794840 ) =>
795841 ResolveAggregateArgColByHead head col tables unwrapped
@@ -1072,7 +1118,8 @@ else instance FlushWhereWordByHead "9" word currentType tables paramsIn currentT
10721118
10731119-- Column reference: resolve and set as currentType
10741120else instance
1075− ( ResolveColumn word tables (Column typ constraints)
1121+ ( ResolveColumn word tables entry
1122+ , ExtractType entry typ
10761123 , UnwrapMaybe typ unwrapped
10771124 ) =>
10781125 FlushWhereWordByHead head word currentType tables paramsIn unwrapped paramsIn
@@ -1588,11 +1635,14 @@ else instance
15881635class IsAutoGeneratedTC :: Type -> Boolean -> Constraint
15891636class IsAutoGeneratedTC constraints result | constraints -> result
15901637
1591−instance IsAutoGeneratedTC AutoIncrement True
1592−else instance IsAutoGeneratedTC (Default a) True
1593−else instance IsAutoGeneratedTC (DefaultInt a) True
1594−else instance IsAutoGeneratedTC (DefaultBool a) True
1595−else instance (IsAutoGeneratedTC a ra, IsAutoGeneratedTC b rb, Or ra rb result) => IsAutoGeneratedTC (a /\ b) result
1638+instance IsAutoGeneratedTC (AutoIncrement a) True
1639+else instance IsAutoGeneratedTC (Default s a) True
1640+else instance IsAutoGeneratedTC (DefaultInt v a) True
1641+else instance IsAutoGeneratedTC (DefaultBool v a) True
1642+else instance IsAutoGeneratedTC a result => IsAutoGeneratedTC (PrimaryKey a) result
1643+else instance IsAutoGeneratedTC a result => IsAutoGeneratedTC (Unique a) result
1644+else instance IsAutoGeneratedTC a result => IsAutoGeneratedTC (ForeignKey t r c a) result
1645+else instance IsAutoGeneratedTC a result => IsAutoGeneratedTC (Nullable a) result
15961646else instance IsAutoGeneratedTC a False
15971647
15981648-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -1604,10 +1654,11 @@ class InsertableColumnsRL tableRL outRL | tableRL -> outRL
16041654
16051655instance InsertableColumnsRL RL.Nil RL.Nil
16061656instance
1607− ( IsAutoGeneratedTC constraints isAuto
1657+ ( IsAutoGeneratedTC entry isAuto
1658+ , ExtractType entry typ
16081659 , InsertableColumnDecide isAuto name typ tail outRL
16091660 ) =>
1610− InsertableColumnsRL (RL.Cons name (Column typ constraints) tail) outRL
1661+ InsertableColumnsRL (RL.Cons name entry tail) outRL
16111662
16121663class InsertableColumnDecide :: Boolean -> Symbol -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
16131664class InsertableColumnDecide isAuto name typ tail outRL | isAuto name typ tail -> outRL
@@ -1675,8 +1726,19 @@ class MakeNullableRL :: RL.RowList Type -> RL.RowList Type -> Constraint
16751726class MakeNullableRL rl out | rl -> out
16761727
16771728instance MakeNullableRL RL.Nil RL.Nil
1678−instance MakeNullableRL tail out' => MakeNullableRL (RL.Cons name (Column (Maybe a) constraints) tail) (RL.Cons name (Column (Maybe a) constraints) out')
1679−else instance MakeNullableRL tail out' => MakeNullableRL (RL.Cons name (Column typ constraints) tail) (RL.Cons name (Column (Maybe typ) constraints) out')
1729+instance
1730+ ( ExtractType entry typ
1731+ , MakeNullableDecide typ name entry tail out
1732+ ) =>
1733+ MakeNullableRL (RL.Cons name entry tail) out
1734+
1735+class MakeNullableDecide :: Type -> Symbol -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
1736+class MakeNullableDecide typ name entry tail out | typ name entry tail -> out
1737+
1738+-- Already nullable: keep as-is
1739+instance MakeNullableRL tail out' => MakeNullableDecide (Maybe a) name entry tail (RL.Cons name entry out')
1740+-- Not nullable: wrap in Nullable
1741+else instance MakeNullableRL tail out' => MakeNullableDecide typ name entry tail (RL.Cons name (Nullable entry) out')
16801742
16811743-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
16821744-- Query builder: unified Q type
@@ -1954,7 +2016,8 @@ class ValidateSetColumnsRL rl cols
19542016
19552017instance ValidateSetColumnsRL RL.Nil cols
19562018instance
1957− ( Row.Cons name (Column typ constraints) rest cols
2019+ ( Row.Cons name entry rest cols
2020+ , ExtractType entry typ
19582021 , ValidateSetColumnsRL tail cols
19592022 ) =>
19602023 ValidateSetColumnsRL (RL.Cons name typ tail) cols