Event-Condition-Action (ECA)
Structure
ON <event>
IF <condition>
DO <action>
| Part | Role | Examples |
|---|---|---|
| Event | Trigger — something that happened | INSERT on a table, message arrival, time elapsed, sensor reading |
| Condition | Guard — predicate evaluated at trigger time | price > 100, user.role == 'admin', stock < reorder_point |
| Action | Effect — what to do if condition holds | Send email, update a row, fire another event, call an API |
Origins: Active Databases
ECA was formalised in the active database research of the late 1980s–90s. The canonical expression is the database trigger, still the most widely deployed form:
CREATE TRIGGER reorder_stock
AFTER UPDATE ON inventory
FOR EACH ROW
WHEN (NEW.quantity < 10)
CALL place_reorder(NEW.product_id);Key research systems: HiPAC, Ariel, Starburst, POSTGRES (Stonebraker), and later ODE.
Event Types
Primitive events - Data manipulation: INSERT / UPDATE / DELETE on a relation - Temporal: absolute (AT '2026-01-01'), relative (AFTER 5 MINUTES), periodic (EVERY 1 HOUR) - External: message received, HTTP request, hardware signal
Composite events (event algebras) - E1 AND E2 — both occurred (within a window) - E1 OR E2 — either occurred - E1 SEQ E2 — E1 followed by E2 - NOT E — E did not occur within a window - E[n] — E occurred n times
Condition Evaluation
Conditions are predicates over: - the event parameters (old/new values in DB triggers) - database/system state at evaluation time - derived context (aggregates, joins, external lookups)
Timing matters — when is the condition checked relative to the event?
| Timing | Description |
|---|---|
| Immediate | Checked synchronously during the triggering transaction |
| Deferred | Checked at transaction commit |
| Decoupled | Checked in a separate async transaction |
Action Semantics
Actions can be: - DML operations (INSERT, UPDATE, DELETE) - Procedure/function calls - Message/notification dispatch - Further event emission (enabling rule chaining)
Coupling modes describe how the action relates to the triggering transaction:
| Mode | Meaning |
|---|---|
| Immediate | Runs inside the same transaction |
| Deferred | Runs at end of triggering transaction |
| Decoupled | Runs in a new, independent transaction |
Rule Chaining & Termination
Because an action can trigger another rule, chains and cycles are possible:
R1: ON insert(A) → insert(B)
R2: ON insert(B) → insert(C)
R3: ON insert(C) → insert(A) ← cycle
Termination analysis is a core ECA problem. Approaches: - Static analysis of trigger dependency graphs - Cycle detection (necessary but not sufficient — cycles don’t always diverge) - Depth limits / recursion guards at runtime - Triggering graph — directed graph where R→R’ if R’s action can trigger R’
Conflict Resolution & Priority
When multiple rules fire on the same event, execution order matters. Strategies: - Priority ordering — explicit numeric priority - Specificity — more specific condition wins (as in expert systems) - Recency — most recently added rule fires first - Arbitrary / non-deterministic — system chooses, rules must be independent
Granularity
| Level | Semantics |
|---|---|
Row-level (FOR EACH ROW) |
Rule fires once per affected tuple |
Statement-level (FOR EACH STATEMENT) |
Rule fires once per DML statement |
Row-level gives access to OLD and NEW tuple values; statement-level does not (in most systems).
ECA Beyond Databases
Complex Event Processing (CEP) Systems like Esper, Flink CEP, and AWS EventBridge detect patterns across event streams. The event component becomes a full pattern language over time windows.
Business Rule Engines Drools, CLIPS, Jess implement the Rete algorithm to efficiently match ECA-style production rules over a working memory. Conditions can span multiple facts simultaneously.
Workflow & Automation Zapier, n8n, GitHub Actions — all ECA at a higher abstraction: ON push to main IF tests pass DO deploy.
IoT / SCADA Sensor threshold rules are pure ECA: ON temperature_reading IF value > 80 DO trigger_alarm.
Frontend / Reactive UI Event listeners + guards: ON click IF form.valid DO submit(). React’s useEffect with dependency arrays is a constrained ECA form.
ECA in Practice: SQL Triggers (PostgreSQL)
-- Event: AFTER INSERT on orders
-- Condition: order total > 1000
-- Action: insert into high_value_orders
CREATE OR REPLACE FUNCTION flag_high_value()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.total > 1000 THEN
INSERT INTO high_value_orders(order_id, flagged_at)
VALUES (NEW.id, NOW());
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER check_order_value
AFTER INSERT ON orders
FOR EACH ROW
EXECUTE FUNCTION flag_high_value();Known Pitfalls
- Hidden control flow — triggers are invisible to application code, making behaviour hard to trace
- Performance — row-level triggers on bulk operations can be catastrophic
- Cascading failures — a bad action can corrupt data across many triggered rules before being caught
- Testing difficulty — ECA logic is tightly coupled to data state, hard to unit test in isolation
- Ordering non-determinism — when multiple rules fire, subtle bugs emerge from assumed ordering
Summary
ECA = reactive computation model
Event → what happened (primitive or composite)
Condition → is it relevant right now?
Action → what to do about it
Key concerns: coupling mode, granularity, chaining,
termination, priority, performance
It’s one of the oldest and most pervasive patterns in computing — showing up identically in DB triggers, CEP engines, rule systems, automation platforms, and reactive UI frameworks.