11-- |
22-- Module: Database.PostgreSQL.PQTypes.Model.Trigger
33--
4- -- Trigger name must be unique among triggers of same table. Only @CONSTRAINT@ triggers are
5- -- supported. They can only be run @AFTER@ an event. The associated functions are always
6- -- created with no arguments and always @RETURN TRIGGER@.
4+ -- Trigger name must be unique among triggers of the same table.
75--
8- -- For details, see <https://www.postgresql.org/docs/11 /sql-createtrigger.html>.
6+ -- For details, see <https://www.postgresql.org/docs/current /sql-createtrigger.html>.
97module Database.PostgreSQL.PQTypes.Model.Trigger
108 ( -- * Triggers
11- TriggerEvent (.. )
9+ TriggerKind (.. )
10+ , TriggerActionTime (.. )
11+ , TriggerEvent (.. )
1212 , Trigger (.. )
1313 , triggerMakeName
1414 , triggerBaseName
@@ -22,6 +22,9 @@ module Database.PostgreSQL.PQTypes.Model.Trigger
2222 , sqlCreateTriggerFunction
2323 , sqlDropTriggerFunction
2424 , triggerFunctionMakeName
25+
26+ -- * Constraints
27+ , ConstraintAttributes (.. )
2528 ) where
2629
2730import Data.Bits (testBit )
@@ -35,6 +38,40 @@ import Data.Text qualified as Text
3538import Database.PostgreSQL.PQTypes
3639import Database.PostgreSQL.PQTypes.SQL.Builder
3740
41+ -- | Timing for a regular trigger.
42+ --
43+ -- @since 1.17.0.0
44+ data TriggerActionTime
45+ = -- | An @AFTER@ trigger.
46+ After
47+ | -- | A @BEFORE@ trigger.
48+ Before
49+ deriving (Eq , Show )
50+
51+ -- | Possible combinations of constraint attributes.
52+ --
53+ -- @since 1.17.0.0
54+ data ConstraintAttributes
55+ = -- | The @NOT DEFERRABLE [INITIALLY IMMEDIATE]@ constraint.
56+ -- A @NOT DEFERRABLE@ constraint is @INITIALLY IMMEDIATE@ by default.
57+ NotDeferrable
58+ | -- | The @DEFERRABLE [INITIALLY IMMEDIATE]@ constraint.
59+ -- A @DEFERRABLE@ constraint is @INITIALLY IMMEDIATE@ by default.
60+ Deferrable
61+ | -- | The @DEFERRABLE INITIALLY DEFERRED@ constraint.
62+ DeferrableInitiallyDeferred
63+ deriving (Eq , Show )
64+
65+ -- | Trigger kind.
66+ --
67+ -- @since 1.17.0.0
68+ data TriggerKind
69+ = -- | Create a regular trigger: @CREATE TRIGGER@
70+ TriggerRegular TriggerActionTime
71+ | -- | Create a constraint trigger: @CREATE CONSTRAINT TRIGGER@
72+ TriggerConstraint ConstraintAttributes
73+ deriving (Eq , Show )
74+
3875-- | Trigger event name.
3976--
4077-- @since 1.15.0.0
@@ -58,29 +95,28 @@ data Trigger = Trigger
5895 , triggerName :: RawSQL ()
5996 -- ^ The internal name without any prefixes. Trigger name must be unique among
6097 -- triggers of same table. See 'triggerMakeName'.
98+ , triggerKind :: TriggerKind
99+ -- ^ The kind of trigger we want to create.
100+ -- @since 1.17.0.0
61101 , triggerEvents :: Set TriggerEvent
62102 -- ^ The set of events. Corresponds to the @{ __event__ [ OR ... ] }@ in the trigger
63103 -- definition. The order in which they are defined doesn't matter and there can
64104 -- only be one of each.
65- , triggerDeferrable :: Bool
66- -- ^ Is the trigger @DEFERRABLE@ or @NOT DEFERRABLE@ ?
67- , triggerInitiallyDeferred :: Bool
68- -- ^ Is the trigger @INITIALLY DEFERRED@ or @INITIALLY IMMEDIATE@ ?
69105 , triggerWhen :: Maybe (RawSQL () )
70106 -- ^ The condition that specifies whether the trigger should fire. Corresponds to the
71107 -- @WHEN ( __condition__ )@ in the trigger definition.
72108 , triggerFunction :: RawSQL ()
73109 -- ^ The function to execute when the trigger fires.
110+ -- The function is declared as taking no arguments and returning type @trigger@.
74111 }
75112 deriving (Show )
76113
77114instance Eq Trigger where
78115 t1 == t2 =
79116 triggerTable t1 == triggerTable t2
80117 && triggerName t1 == triggerName t2
118+ && triggerKind t1 == triggerKind t2
81119 && triggerEvents t1 == triggerEvents t2
82- && triggerDeferrable t1 == triggerDeferrable t2
83- && triggerInitiallyDeferred t1 == triggerInitiallyDeferred t2
84120 && triggerWhen t1 == triggerWhen t2
85121
86122-- Function source code is not guaranteed to be equal, so we ignore it.
@@ -117,37 +153,41 @@ triggerEventName = \case
117153
118154-- | Build an SQL statement that creates a trigger.
119155--
120- -- Only supports @CONSTRAINT@ triggers which can only run @AFTER@.
121- --
122156-- @since 1.15.0
123157sqlCreateTrigger :: Trigger -> RawSQL ()
124158sqlCreateTrigger Trigger {.. } =
125- " CREATE CONSTRAINT TRIGGER"
159+ " CREATE"
160+ <+> trgKind
126161 <+> trgName
127- <+> " AFTER "
162+ <+> tgrActionTime
128163 <+> trgEvents
129164 <+> " ON"
130165 <+> triggerTable
131- <+> trgTiming
166+ <+> trgConstraintAttributes
132167 <+> " FOR EACH ROW"
133168 <+> trgWhen
134169 <+> " EXECUTE FUNCTION"
135170 <+> trgFunction
136171 <+> " ()"
137172 where
173+ trgKind = case triggerKind of
174+ TriggerRegular _ -> " TRIGGER"
175+ TriggerConstraint _ -> " CONSTRAINT TRIGGER"
138176 trgName
139177 | triggerName == " " = error " Trigger must have a name."
140178 | otherwise = triggerMakeName triggerName triggerTable
179+ tgrActionTime = case triggerKind of
180+ TriggerRegular After -> " AFTER"
181+ TriggerRegular Before -> " BEFORE"
182+ TriggerConstraint _ -> " AFTER"
141183 trgEvents
142184 | triggerEvents == Set. empty = error " Trigger must have at least one event."
143185 | otherwise = mintercalate " OR " . map triggerEventName $ Set. toList triggerEvents
144- trgTiming =
145- let deferrable = (if triggerDeferrable then " " else " NOT" ) <+> " DEFERRABLE"
146- deferred =
147- if triggerInitiallyDeferred
148- then " INITIALLY DEFERRED"
149- else " INITIALLY IMMEDIATE"
150- in deferrable <+> deferred
186+ trgConstraintAttributes = case triggerKind of
187+ TriggerRegular _ -> " "
188+ TriggerConstraint NotDeferrable -> " NOT DEFERRABLE"
189+ TriggerConstraint Deferrable -> " DEFERRABLE"
190+ TriggerConstraint DeferrableInitiallyDeferred -> " DEFERRABLE INITIALLY DEFERRED"
151191 trgWhen = maybe " " (\ w -> " WHEN (" <+> w <+> " )" ) triggerWhen
152192 trgFunction = triggerFunctionMakeName triggerName
153193
@@ -205,6 +245,7 @@ getDBTriggers tableName = do
205245 runQuery_ . sqlSelect " pg_trigger t" $ do
206246 sqlResult " t.tgname::text" -- name
207247 sqlResult " t.tgtype" -- smallint == int2 => (2 bytes)
248+ sqlResult " t.tgconstraint > 0" -- we only check if CONSTRAINT has been specified
208249 sqlResult " t.tgdeferrable" -- boolean
209250 sqlResult " t.tginitdeferred" -- boolean
210251 -- This gets the entire query that created this trigger. Note that it's decompiled and
@@ -221,14 +262,13 @@ getDBTriggers tableName = do
221262 sqlWhereEq " c.relname" $ unRawSQL tableName
222263 fetchMany getTrigger
223264 where
224- getTrigger :: (String , Int16 , Bool , Bool , String , String , String , String ) -> (Trigger , RawSQL () )
225- getTrigger (tgname, tgtype, tgdeferrable, tginitdeferrable, triggerdef, proname, prosrc, tblName) =
265+ getTrigger :: (String , Int16 , Bool , Bool , Bool , String , String , String , String ) -> (Trigger , RawSQL () )
266+ getTrigger (tgname, tgtype, tgconstraint, tgdeferrable, tginitdeferrable, triggerdef, proname, prosrc, tblName) =
226267 ( Trigger
227268 { triggerTable = tableName'
228269 , triggerName = triggerBaseName (unsafeSQL tgname) tableName'
270+ , triggerKind = tgrKind
229271 , triggerEvents = trgEvents
230- , triggerDeferrable = tgdeferrable
231- , triggerInitiallyDeferred = tginitdeferrable
232272 , triggerWhen = tgrWhen
233273 , triggerFunction = unsafeSQL prosrc
234274 }
@@ -251,6 +291,30 @@ getDBTriggers tableName = do
251291 tgrWhen :: Maybe (RawSQL () )
252292 tgrWhen = parseBetween " WHEN (" " ) EXECUTE"
253293
294+ tgrKind :: TriggerKind
295+ tgrKind =
296+ if tgconstraint
297+ then TriggerConstraint trgConstraintAttrs
298+ else TriggerRegular tgrActionTime
299+
300+ trgConstraintAttrs :: ConstraintAttributes
301+ trgConstraintAttrs = case (tgdeferrable, tginitdeferrable) of
302+ (False , False ) -> NotDeferrable
303+ (True , False ) -> Deferrable
304+ (True , True ) -> DeferrableInitiallyDeferred
305+ (False , True ) -> error " A constraint declared INITIALLY DEFERRED must be DEFERRABLE."
306+
307+ tgrActionTime :: TriggerActionTime
308+ tgrActionTime = case (tgtypeInsteadBit, tgtypeBeforeBit) of
309+ (False , False ) -> After
310+ (False , True ) -> Before
311+ (True , False ) -> error " INSTEAD OF triggers are not available on tables."
312+ (True , True ) -> error " The tgtype can't match more than one timing."
313+ where
314+ -- Taken from PostgreSQL sources: src/include/catalog/pg_trigger.h:
315+ tgtypeBeforeBit = testBit tgtype 1 -- #define TRIGGER_TYPE_BEFORE (1 << 1)
316+ tgtypeInsteadBit = testBit tgtype 6 -- #define TRIGGER_TYPE_INSTEAD (1 << 6)
317+
254318 -- Similarly, in case of UPDATE OF, the columns can be simply parsed from the
255319 -- original query. Note that UPDATE and UPDATE OF are mutually exclusive and have
256320 -- the same bit set in the underlying tgtype bit field.
0 commit comments