diff --git a/contrib/file_fdw/input/file_fdw.source b/contrib/file_fdw/input/file_fdw.source new file mode 100644 index f7fd28d..063e23c *** a/contrib/file_fdw/input/file_fdw.source --- b/contrib/file_fdw/input/file_fdw.source *************** DELETE FROM agg_csv WHERE a = 100; *** 121,126 **** --- 121,148 ---- -- but this should be ignored SELECT * FROM agg_csv FOR UPDATE; + -- simple views on top of the foreign table aren't updatable + CREATE VIEW agg_csv_v AS SELECT * FROM agg_csv; + + SELECT table_name, is_insertable_into + FROM information_schema.tables + WHERE table_name LIKE 'agg_csv%' + ORDER BY table_name; + + SELECT table_name, is_updatable, is_insertable_into + FROM information_schema.views + WHERE table_name LIKE 'agg_csv%' + ORDER BY table_name; + + SELECT table_name, column_name, is_updatable + FROM information_schema.columns + WHERE table_name LIKE 'agg_csv%' + ORDER BY table_name, ordinal_position; + + INSERT INTO agg_csv_v VALUES(1,2.0); + UPDATE agg_csv_v SET a = 1; + DELETE FROM agg_csv_v WHERE a = 100; + -- privilege tests SET ROLE file_fdw_superuser; SELECT * FROM agg_text ORDER BY a; diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source new file mode 100644 index 4f90bae..2e015cc *** a/contrib/file_fdw/output/file_fdw.source --- b/contrib/file_fdw/output/file_fdw.source *************** SELECT * FROM agg_csv FOR UPDATE; *** 199,204 **** --- 199,243 ---- 42 | 324.78 (3 rows) + -- simple views on top of the foreign table aren't updatable + CREATE VIEW agg_csv_v AS SELECT * FROM agg_csv; + SELECT table_name, is_insertable_into + FROM information_schema.tables + WHERE table_name LIKE 'agg_csv%' + ORDER BY table_name; + table_name | is_insertable_into + ------------+-------------------- + agg_csv | NO + agg_csv_v | NO + (2 rows) + + SELECT table_name, is_updatable, is_insertable_into + FROM information_schema.views + WHERE table_name LIKE 'agg_csv%' + ORDER BY table_name; + table_name | is_updatable | is_insertable_into + ------------+--------------+-------------------- + agg_csv_v | NO | NO + (1 row) + + SELECT table_name, column_name, is_updatable + FROM information_schema.columns + WHERE table_name LIKE 'agg_csv%' + ORDER BY table_name, ordinal_position; + table_name | column_name | is_updatable + ------------+-------------+-------------- + agg_csv | a | NO + agg_csv | b | NO + agg_csv_v | a | NO + agg_csv_v | b | NO + (4 rows) + + INSERT INTO agg_csv_v VALUES(1,2.0); + ERROR: cannot insert into foreign table "agg_csv" + UPDATE agg_csv_v SET a = 1; + ERROR: cannot update foreign table "agg_csv" + DELETE FROM agg_csv_v WHERE a = 100; + ERROR: cannot delete from foreign table "agg_csv" -- privilege tests SET ROLE file_fdw_superuser; SELECT * FROM agg_text ORDER BY a; *************** SET ROLE file_fdw_superuser; *** 243,255 **** -- cleanup RESET ROLE; DROP EXTENSION file_fdw CASCADE; ! NOTICE: drop cascades to 8 other objects DETAIL: drop cascades to server file_server drop cascades to user mapping for file_fdw_user drop cascades to user mapping for file_fdw_superuser drop cascades to user mapping for no_priv_user drop cascades to foreign table agg_text drop cascades to foreign table agg_csv drop cascades to foreign table agg_bad drop cascades to foreign table text_csv DROP ROLE file_fdw_superuser, file_fdw_user, no_priv_user; --- 282,295 ---- -- cleanup RESET ROLE; DROP EXTENSION file_fdw CASCADE; ! NOTICE: drop cascades to 9 other objects DETAIL: drop cascades to server file_server drop cascades to user mapping for file_fdw_user drop cascades to user mapping for file_fdw_superuser drop cascades to user mapping for no_priv_user drop cascades to foreign table agg_text drop cascades to foreign table agg_csv + drop cascades to view agg_csv_v drop cascades to foreign table agg_bad drop cascades to foreign table text_csv DROP ROLE file_fdw_superuser, file_fdw_user, no_priv_user; diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out new file mode 100644 index cb007cd..4c2c801 *** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** select c2, count(*) from "S 1"."T 1" whe *** 2339,2341 **** --- 2339,2410 ---- 407 | 100 (13 rows) + -- Test updatable view on top of writable foreign table + CREATE VIEW ft2_v AS SELECT * FROM ft2; + SELECT table_name, is_insertable_into + FROM information_schema.tables + WHERE table_name LIKE 'ft2%' + ORDER BY table_name; + table_name | is_insertable_into + ------------+-------------------- + ft2 | NO + ft2_v | YES + (2 rows) + + SELECT table_name, is_updatable, is_insertable_into + FROM information_schema.views + WHERE table_name LIKE 'ft2%' + ORDER BY table_name; + table_name | is_updatable | is_insertable_into + ------------+--------------+-------------------- + ft2_v | YES | YES + (1 row) + + SELECT table_name, column_name, is_updatable + FROM information_schema.columns + WHERE table_name LIKE 'ft2%' + ORDER BY table_name, ordinal_position; + table_name | column_name | is_updatable + ------------+-------------+-------------- + ft2 | c1 | NO + ft2 | c2 | NO + ft2 | c3 | NO + ft2 | c4 | NO + ft2 | c5 | NO + ft2 | c6 | NO + ft2 | c7 | NO + ft2 | c8 | NO + ft2_v | c1 | YES + ft2_v | c2 | YES + ft2_v | c3 | YES + ft2_v | c4 | YES + ft2_v | c5 | YES + ft2_v | c6 | YES + ft2_v | c7 | YES + ft2_v | c8 | YES + (16 rows) + + INSERT INTO ft2_v (c1,c2,c3) VALUES (9999, 250, 'xxx') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 + ------+-----+-----------------+----+----+----+------------+---- + 9999 | 250 | xxx_trig_update | | | | ft2 | + (1 row) + + UPDATE ft2_v SET c2 = c2 + 500 WHERE c1 = 9999 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 + ------+-----+-----------------------------+----+----+----+------------+---- + 9999 | 750 | xxx_trig_update_trig_update | | | | ft2 | + (1 row) + + SELECT * FROM ft2_v WHERE c1 = 9999; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 + ------+-----+-----------------------------+----+----+----+------------+---- + 9999 | 750 | xxx_trig_update_trig_update | | | | ft2 | + (1 row) + + DELETE FROM ft2_v WHERE c1 = 9999 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 + ------+-----+-----------------------------+----+----+----+------------+---- + 9999 | 750 | xxx_trig_update_trig_update | | | | ft2 | + (1 row) + diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql new file mode 100644 index 670d769..416afc3 *** a/contrib/postgres_fdw/sql/postgres_fdw.sql --- b/contrib/postgres_fdw/sql/postgres_fdw.sql *************** select c2, count(*) from "S 1"."T 1" whe *** 369,371 **** --- 369,394 ---- commit; select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + + -- Test updatable view on top of writable foreign table + CREATE VIEW ft2_v AS SELECT * FROM ft2; + + SELECT table_name, is_insertable_into + FROM information_schema.tables + WHERE table_name LIKE 'ft2%' + ORDER BY table_name; + + SELECT table_name, is_updatable, is_insertable_into + FROM information_schema.views + WHERE table_name LIKE 'ft2%' + ORDER BY table_name; + + SELECT table_name, column_name, is_updatable + FROM information_schema.columns + WHERE table_name LIKE 'ft2%' + ORDER BY table_name, ordinal_position; + + INSERT INTO ft2_v (c1,c2,c3) VALUES (9999, 250, 'xxx') RETURNING *; + UPDATE ft2_v SET c2 = c2 + 500 WHERE c1 = 9999 RETURNING *; + SELECT * FROM ft2_v WHERE c1 = 9999; + DELETE FROM ft2_v WHERE c1 = 9999 RETURNING *; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c new file mode 100644 index 83f26e3..296d6a1 *** a/src/backend/rewrite/rewriteHandler.c --- b/src/backend/rewrite/rewriteHandler.c *************** view_is_auto_updatable(Relation view) *** 2014,2019 **** --- 2014,2020 ---- base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); if (base_rte->rtekind != RTE_RELATION || (base_rte->relkind != RELKIND_RELATION && + base_rte->relkind != RELKIND_FOREIGN_TABLE && base_rte->relkind != RELKIND_VIEW)) return gettext_noop("Views that do not select from a single table or view are not automatically updatable."); *************** relation_is_updatable(Oid reloid, int re *** 2120,2125 **** --- 2121,2143 ---- } } + /* If this is a foreign table, check if it is updatable */ + if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + int events = 0; + FdwRoutine *fdwroutine = GetFdwRoutineForRelation(rel, false); + + if (fdwroutine->ExecForeignInsert != NULL) + events |= (1 << CMD_INSERT); + if (fdwroutine->ExecForeignUpdate != NULL) + events |= (1 << CMD_UPDATE); + if (fdwroutine->ExecForeignDelete != NULL) + events |= (1 << CMD_DELETE); + + relation_close(rel, AccessShareLock); + return (events & req_events) == req_events; + } + /* Check if this is an automatically updatable view */ if (rel->rd_rel->relkind == RELKIND_VIEW && view_is_auto_updatable(rel) == NULL)