PostgreSQL Error 42P17: invalid object definition
PostgreSQL error code 42P17 (invalid_object_definition) occurs when a database object such as a view, rule, or trigger is defined in a way that violates PostgreSQL's internal structural rules — even if the syntax itself is technically correct. This is distinct from a simple syntax error; instead, it signals a logical problem in how the object is defined, such as a circular dependency or an incompatible return type.
Top 3 Causes and Fixes
1. Circular View References
The most common cause of 42P17 is a view that directly or indirectly references itself. PostgreSQL detects this at definition time and immediately raises the error to prevent infinite loops at query execution.
-- BAD: View referencing itself (triggers 42P17)
CREATE VIEW my_view AS
SELECT * FROM my_view;
-- ERROR: 42P17: infinite recursion detected in rules for relation "my_view"
-- BAD: Indirect circular reference
CREATE VIEW view_a AS SELECT * FROM view_b;
CREATE VIEW view_b AS SELECT * FROM view_a; -- 42P17
-- GOOD: Base your view on actual tables
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT,
amount NUMERIC,
status TEXT
);
CREATE VIEW active_orders AS
SELECT id, customer_id, amount
FROM orders
WHERE status = 'active';
-- Check for circular view dependencies
SELECT
dep.relname AS dependent_view,
src.relname AS source_view
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
JOIN pg_class dep ON pg_rewrite.ev_class = dep.oid
JOIN pg_class src ON pg_depend.refobjid = src.oid
WHERE src.relkind = 'v'
AND dep.relname != src.relname;
2. Invalid or Circular Rule Definitions
PostgreSQL Rules that modify the same table they are defined on can create circular structures, causing 42P17. In practice, rules are complex and error-prone — replacing them with triggers is almost always the safer choice.
-- BAD: A rule that updates the same table it watches
CREATE RULE bad_rule AS
ON UPDATE TO orders
DO ALSO UPDATE orders
SET amount = NEW.amount * 1.1
WHERE id = NEW.id;
-- Risk: 42P17 or infinite loop
-- GOOD: Drop the rule and replace with a trigger
DROP RULE IF EXISTS bad_rule ON orders;
CREATE OR REPLACE FUNCTION adjust_amount()
RETURNS trigger AS $$
BEGIN
IF NEW.amount IS DISTINCT FROM OLD.amount THEN
NEW.amount := NEW.amount * 1.1;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_adjust_amount
BEFORE UPDATE ON orders
FOR EACH ROW
EXECUTE FUNCTION adjust_amount();
3. Trigger Function with Wrong Return Type
Every trigger function must declare RETURNS trigger. Using any other return type, or applying an INSTEAD OF trigger to a plain table instead of a view, will raise 42P17.
-- BAD: Wrong return type for a trigger function
CREATE OR REPLACE FUNCTION bad_trigger()
RETURNS INTEGER AS $$ -- should be RETURNS trigger
BEGIN
RETURN 1;
END;
$$ LANGUAGE plpgsql;
-- BAD: INSTEAD OF on a plain table (not a view)
CREATE TRIGGER bad_trigger
INSTEAD OF UPDATE ON orders -- 42P17: INSTEAD OF only works on views
FOR EACH ROW EXECUTE FUNCTION adjust_amount();
-- GOOD: Correct trigger function
CREATE OR REPLACE FUNCTION correct_trigger()
RETURNS trigger AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
RETURN OLD;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- GOOD: INSTEAD OF applied to a view
CREATE VIEW orders_view AS
SELECT id, customer_id, amount FROM orders;
CREATE TRIGGER trg_orders_view
INSTEAD OF UPDATE ON orders_view -- correct: view target
FOR EACH ROW EXECUTE FUNCTION correct_trigger();
Quick Fix Checklist
| Symptom | Action |
|---|---|
| View references itself | Rewrite view to reference base tables only |
| Circular view chain | Identify dependency with pg_depend, break the chain |
| Rule causes loop | Drop rule, implement equivalent logic as a trigger |
| Trigger returns wrong type | Change RETURNS <type> to RETURNS trigger
|
| INSTEAD OF on table | Move trigger to a view, or switch to BEFORE/AFTER |
Prevention Tips
1. Always test DDL inside a transaction block.
Wrap all object creation scripts in BEGIN/ROLLBACK during development so errors never leave your schema in a broken state.
BEGIN;
CREATE OR REPLACE VIEW test_view AS
SELECT id, amount FROM orders WHERE status = 'active';
-- Inspect, then decide:
ROLLBACK; -- safe rollback if anything looks wrong
2. Use migration tools with error-stop enabled.
Tools like Flyway or Liquibase paired with psql --set ON_ERROR_STOP=1 ensure that a 42P17 error immediately halts your deployment pipeline, preventing partial migrations from reaching production.
Related Error Codes
-
42P16 (
invalid_table_definition): Similar error for malformed table definitions. -
42809 (
wrong_object_type): Raised when a command targets the wrong object type (e.g., INSTEAD OF on a table). -
0A000 (
feature_not_supported): Can appear in older PostgreSQL versions when an unsupported trigger or view feature is used.
📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.
Top comments (0)