-- configure options
ALTER SERVER testserver1 OPTIONS (
use_remote_estimate 'false',
+ updatable 'true',
fdw_startup_cost '123.456',
fdw_tuple_cost '0.123',
service 'value',
/*
* Validate option value, when we can do so without any context.
*/
- if (strcmp(def->defname, "use_remote_estimate") == 0)
+ if (strcmp(def->defname, "use_remote_estimate") == 0 ||
+ strcmp(def->defname, "updatable") == 0)
{
- /* use_remote_estimate accepts only boolean values */
+ /* these accept only boolean values */
(void) defGetBoolean(def);
}
else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
/* cost factors */
{"fdw_startup_cost", ForeignServerRelationId, false},
{"fdw_tuple_cost", ForeignServerRelationId, false},
+ /* updatable is available on both server and table */
+ {"updatable", ForeignServerRelationId, false},
+ {"updatable", ForeignTableRelationId, false},
{NULL, InvalidOid, false}
};
TupleTableSlot *planSlot);
static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo);
+static int postgresIsForeignRelUpdatable(Relation rel);
static void postgresExplainForeignScan(ForeignScanState *node,
ExplainState *es);
static void postgresExplainForeignModify(ModifyTableState *mtstate,
routine->ExecForeignUpdate = postgresExecForeignUpdate;
routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify;
+ routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
/* Support functions for EXPLAIN */
routine->ExplainForeignScan = postgresExplainForeignScan;
fmstate->conn = NULL;
}
+/*
+ * postgresIsForeignRelUpdatable
+ * Determine whether a foreign table supports INSERT, UPDATE and/or
+ * DELETE.
+ */
+static int
+postgresIsForeignRelUpdatable(Relation rel)
+{
+ bool updatable;
+ ForeignTable *table;
+ ForeignServer *server;
+ ListCell *lc;
+
+ /*
+ * By default, all postgres_fdw foreign tables are assumed updatable. This
+ * can be overridden by a per-server setting, which in turn can be
+ * overridden by a per-table setting.
+ */
+ updatable = true;
+
+ table = GetForeignTable(RelationGetRelid(rel));
+ server = GetForeignServer(table->serverid);
+
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "updatable") == 0)
+ updatable = defGetBoolean(def);
+ }
+ foreach(lc, table->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "updatable") == 0)
+ updatable = defGetBoolean(def);
+ }
+
+ /*
+ * Currently "updatable" means support for INSERT, UPDATE and DELETE.
+ */
+ return updatable ?
+ (1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE) : 0;
+}
+
/*
* postgresExplainForeignScan
* Produce extra output for EXPLAIN of a ForeignScan on a foreign table
-- configure options
ALTER SERVER testserver1 OPTIONS (
use_remote_estimate 'false',
+ updatable 'true',
fdw_startup_cost '123.456',
fdw_tuple_cost '0.123',
service 'value',
<literal>NULL</>, no action is taken during executor shutdown.
</para>
+ <para>
+<programlisting>
+int
+IsForeignRelUpdatable (Relation rel);
+</programlisting>
+
+ Report which update operations the specified foreign table supports.
+ The return value should be a bitmask of rule event numbers indicating
+ which operations are supported by the foreign table, using the
+ <literal>CmdType</> enumeration; that is,
+ <literal>(1 << CMD_UPDATE) = 4</> for <command>UPDATE</>,
+ <literal>(1 << CMD_INSERT) = 8</> for <command>INSERT</>, and
+ <literal>(1 << CMD_DELETE) = 16</> for <command>DELETE</>.
+ </para>
+
+ <para>
+ If the <function>IsForeignRelUpdatable</> pointer is set to
+ <literal>NULL</>, foreign tables are assumed to be insertable, updatable,
+ or deletable if the FDW provides <function>ExecForeignInsert</>,
+ <function>ExecForeignUpdate</>, or <function>ExecForeignDelete</>
+ respectively. This function is only needed if the FDW supports some
+ tables that are updatable and some that are not. (Even then, it's
+ permissible to throw an error in the execution routine instead of
+ checking in this function. However, this function is used to determine
+ updatability for display in the <literal>information_schema</> views.)
+ </para>
+
</sect2>
<sect2 id="fdw-callbacks-explain">
</para>
</sect3>
+
+ <sect3>
+ <title>Updatability Options</title>
+
+ <para>
+ By default all foreign tables using <filename>postgres_fdw</> are assumed
+ to be updatable. This may be overridden using the following option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>updatable</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</> allows foreign
+ tables to be modified using <command>INSERT</>, <command>UPDATE</> and
+ <command>DELETE</> commands. It can be specified for a foreign table
+ or a foreign server. A table-level option overrides a server-level
+ option.
+ The default is <literal>true</>.
+ </para>
+
+ <para>
+ Of course, if the remote table is not in fact updatable, an error
+ would occur anyway. Use of this option primarily allows the error to
+ be thrown locally without querying the remote server. Note however
+ that the <literal>information_schema</> views will report a
+ <filename>postgres_fdw</> foreign table to be updatable (or not)
+ according to the setting of this option, without any check of the
+ remote server.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
CAST(null AS character_data) AS generation_expression,
CAST(CASE WHEN c.relkind = 'r' OR
- (c.relkind = 'v' AND pg_view_is_updatable(c.oid))
+ (c.relkind IN ('v', 'f') AND
+ pg_column_is_updatable(c.oid, a.attnum, false))
THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_updatable
FROM (pg_attribute a LEFT JOIN pg_attrdef ad ON attrelid = adrelid AND attnum = adnum)
CAST(t.typname AS sql_identifier) AS user_defined_type_name,
CAST(CASE WHEN c.relkind = 'r' OR
- (c.relkind = 'v' AND pg_view_is_insertable(c.oid))
+ (c.relkind IN ('v', 'f') AND
+ -- 1 << CMD_INSERT
+ pg_relation_is_updatable(c.oid, false) & 8 = 8)
THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_insertable_into,
CAST(CASE WHEN t.typname IS NOT NULL THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_typed,
CAST('NONE' AS character_data) AS check_option,
CAST(
- CASE WHEN pg_view_is_updatable(c.oid) THEN 'YES' ELSE 'NO' END
+ -- (1 << CMD_UPDATE) + (1 << CMD_DELETE)
+ CASE WHEN pg_relation_is_updatable(c.oid, false) & 20 = 20
+ THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_updatable,
CAST(
- CASE WHEN pg_view_is_insertable(c.oid) THEN 'YES' ELSE 'NO' END
+ -- 1 << CMD_INSERT
+ CASE WHEN pg_relation_is_updatable(c.oid, false) & 8 = 8
+ THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_insertable_into,
CAST(
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot insert into foreign table \"%s\"",
RelationGetRelationName(resultRel))));
+ if (fdwroutine->IsForeignRelUpdatable != NULL &&
+ (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("foreign table \"%s\" does not allow inserts",
+ RelationGetRelationName(resultRel))));
break;
case CMD_UPDATE:
if (fdwroutine->ExecForeignUpdate == NULL)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot update foreign table \"%s\"",
RelationGetRelationName(resultRel))));
+ if (fdwroutine->IsForeignRelUpdatable != NULL &&
+ (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_UPDATE)) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("foreign table \"%s\" does not allow updates",
+ RelationGetRelationName(resultRel))));
break;
case CMD_DELETE:
if (fdwroutine->ExecForeignDelete == NULL)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot delete from foreign table \"%s\"",
RelationGetRelationName(resultRel))));
+ if (fdwroutine->IsForeignRelUpdatable != NULL &&
+ (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_DELETE)) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("foreign table \"%s\" does not allow deletes",
+ RelationGetRelationName(resultRel))));
break;
default:
elog(ERROR, "unrecognized CmdType: %d", (int) operation);
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 - test if the specified relation is updatable.
+ * relation_is_updatable - determine which update events the specified
+ * relation supports.
*
* This is used for the information_schema views, which have separate concepts
* of "updatable" and "trigger updatable". A relation is "updatable" if it
* can be updated without the need for triggers (either because it has a
* suitable RULE, or because it is simple enough to be automatically updated).
- *
* A relation is "trigger updatable" if it has a suitable INSTEAD OF trigger.
* The SQL standard regards this as not necessarily updatable, presumably
* because there is no way of knowing what the trigger will actually do.
- * That's currently handled directly in the information_schema views, so
- * need not be considered here.
- *
- * In the case of an automatically updatable view, the base relation must
- * also be updatable.
+ * The information_schema views therefore call this function with
+ * include_triggers = false. However, other callers might only care whether
+ * data-modifying SQL will work, so they can pass include_triggers = true
+ * to have trigger updatability included in the result.
*
- * reloid is the pg_class OID to examine. req_events is a bitmask of
- * rule event numbers; the relation is considered rule-updatable if it has
- * all the specified rules. (We do it this way so that we can test for
- * UPDATE plus DELETE rules in a single call.)
+ * The return value is a bitmask of rule event numbers indicating which of
+ * the INSERT, UPDATE and DELETE operations are supported. (We do it this way
+ * so that we can test for UPDATE plus DELETE support in a single call.)
*/
-bool
-relation_is_updatable(Oid reloid, int req_events)
+int
+relation_is_updatable(Oid reloid, bool include_triggers)
{
+ int events = 0;
Relation rel;
RuleLock *rulelocks;
+#define ALL_EVENTS ((1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE))
+
rel = try_relation_open(reloid, AccessShareLock);
/*
- * If the relation doesn't exist, say "false" rather than throwing an
+ * If the relation doesn't exist, return zero rather than throwing an
* error. This is helpful since scanning an information_schema view under
* MVCC rules can result in referencing rels that were just deleted
* according to a SnapshotNow probe.
*/
if (rel == NULL)
- return false;
+ return 0;
+
+ /* If the relation is a table, it is always updatable */
+ if (rel->rd_rel->relkind == RELKIND_RELATION)
+ {
+ relation_close(rel, AccessShareLock);
+ return ALL_EVENTS;
+ }
/* Look for unconditional DO INSTEAD rules, and note supported events */
rulelocks = rel->rd_rules;
if (rulelocks != NULL)
{
- int events = 0;
int i;
for (i = 0; i < rulelocks->numLocks; i++)
if (rulelocks->rules[i]->isInstead &&
rulelocks->rules[i]->qual == NULL)
{
- events |= 1 << rulelocks->rules[i]->event;
+ events |= ((1 << rulelocks->rules[i]->event) & ALL_EVENTS);
}
}
- /* If we have all rules needed, say "yes" */
- if ((events & req_events) == req_events)
+ /* If we have rules for all events, we're done */
+ if (events == ALL_EVENTS)
{
relation_close(rel, AccessShareLock);
- return true;
+ return events;
+ }
+ }
+
+ /* Similarly look for INSTEAD OF triggers, if they are to be included */
+ if (include_triggers)
+ {
+ TriggerDesc *trigDesc = rel->trigdesc;
+
+ if (trigDesc)
+ {
+ if (trigDesc->trig_insert_instead_row)
+ events |= (1 << CMD_INSERT);
+ if (trigDesc->trig_update_instead_row)
+ events |= (1 << CMD_UPDATE);
+ if (trigDesc->trig_delete_instead_row)
+ events |= (1 << CMD_DELETE);
+
+ /* If we have triggers for all events, we're done */
+ if (events == ALL_EVENTS)
+ {
+ relation_close(rel, AccessShareLock);
+ return events;
+ }
+ }
+ }
+
+ /* If this is a foreign table, check which update events it supports */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ FdwRoutine *fdwroutine = GetFdwRoutineForRelation(rel, false);
+
+ if (fdwroutine->IsForeignRelUpdatable != NULL)
+ events |= fdwroutine->IsForeignRelUpdatable(rel);
+ else
+ {
+ /* Assume presence of executor functions is sufficient */
+ 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;
}
/* Check if this is an automatically updatable view */
viewquery = get_view_query(rel);
rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+ Assert(base_rte->rtekind == RTE_RELATION);
if (base_rte->relkind == RELKIND_RELATION)
{
/* Tables are always updatable */
relation_close(rel, AccessShareLock);
- return true;
+ return ALL_EVENTS;
}
else
{
/* Do a recursive check for any other kind of base relation */
baseoid = base_rte->relid;
relation_close(rel, AccessShareLock);
- return relation_is_updatable(baseoid, req_events);
+ return relation_is_updatable(baseoid, include_triggers);
}
}
- /* If we reach here, the relation is not updatable */
+ /* If we reach here, the relation may support some update commands */
relation_close(rel, AccessShareLock);
- return false;
+ return events;
}
/*
- * information_schema support functions
+ * pg_relation_is_updatable - determine which update events the specified
+ * relation supports.
*
- * Test whether a view (identified by pg_class OID) is insertable-into or
- * updatable. The latter requires delete capability too. This is an
- * artifact of the way the SQL standard defines the information_schema views:
- * if we defined separate functions for update and delete, we'd double the
- * work required to compute the view columns.
- *
- * These rely on relation_is_updatable(), which is in rewriteHandler.c.
+ * This relies on relation_is_updatable() in rewriteHandler.c, which see
+ * for additional information.
*/
Datum
-pg_view_is_insertable(PG_FUNCTION_ARGS)
+pg_relation_is_updatable(PG_FUNCTION_ARGS)
{
- Oid viewoid = PG_GETARG_OID(0);
- int req_events = (1 << CMD_INSERT);
+ Oid reloid = PG_GETARG_OID(0);
+ bool include_triggers = PG_GETARG_BOOL(1);
- PG_RETURN_BOOL(relation_is_updatable(viewoid, req_events));
+ PG_RETURN_INT32(relation_is_updatable(reloid, include_triggers));
}
+/*
+ * pg_column_is_updatable - determine whether a column is updatable
+ *
+ * Currently we just check whether the column's relation is updatable.
+ * Eventually we might allow views to have some updatable and some
+ * non-updatable columns.
+ *
+ * Also, this function encapsulates the decision about just what
+ * information_schema.columns.is_updatable actually means. It's not clear
+ * whether deletability of the column's relation should be required, so
+ * we want that decision in C code where we could change it without initdb.
+ */
Datum
-pg_view_is_updatable(PG_FUNCTION_ARGS)
+pg_column_is_updatable(PG_FUNCTION_ARGS)
{
- Oid viewoid = PG_GETARG_OID(0);
- int req_events = (1 << CMD_UPDATE) | (1 << CMD_DELETE);
+ Oid reloid = PG_GETARG_OID(0);
+ AttrNumber attnum = PG_GETARG_INT16(1);
+ bool include_triggers = PG_GETARG_BOOL(2);
+ int events;
+
+ /* System columns are never updatable */
+ if (attnum <= 0)
+ PG_RETURN_BOOL(false);
+
+ events = relation_is_updatable(reloid, include_triggers);
+
+ /* We require both updatability and deletability of the relation */
+#define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE))
- PG_RETURN_BOOL(relation_is_updatable(viewoid, req_events));
+ PG_RETURN_BOOL((events & REQ_EVENTS) == REQ_EVENTS);
}
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201305061
+#define CATALOG_VERSION_NO 201306121
#endif
DATA(insert OID = 3162 ( pg_collation_for PGNSP PGUID 12 1 0 0 0 f f f f f f s 1 0 25 "2276" _null_ _null_ _null_ _null_ pg_collation_for _null_ _null_ _null_ ));
DESCR("collation of the argument; implementation of the COLLATION FOR expression");
-DATA(insert OID = 3842 ( pg_view_is_insertable PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_view_is_insertable _null_ _null_ _null_ ));
-DESCR("is a view insertable-into");
-DATA(insert OID = 3843 ( pg_view_is_updatable PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_view_is_updatable _null_ _null_ _null_ ));
-DESCR("is a view updatable");
+DATA(insert OID = 3842 ( pg_relation_is_updatable PGNSP PGUID 12 10 0 0 0 f f f f t f s 2 0 23 "2205 16" _null_ _null_ _null_ _null_ pg_relation_is_updatable _null_ _null_ _null_ ));
+DESCR("is a relation insertable/updatable/deletable");
+DATA(insert OID = 3843 ( pg_column_is_updatable PGNSP PGUID 12 10 0 0 0 f f f f t f s 3 0 16 "2205 21 16" _null_ _null_ _null_ _null_ pg_column_is_updatable _null_ _null_ _null_ ));
+DESCR("is a column updatable");
/* Deferrable unique constraint trigger */
DATA(insert OID = 1250 ( unique_key_recheck PGNSP PGUID 12 1 0 0 0 f f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_recheck _null_ _null_ _null_ ));
typedef void (*EndForeignModify_function) (EState *estate,
ResultRelInfo *rinfo);
+typedef int (*IsForeignRelUpdatable_function) (Relation rel);
+
typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
struct ExplainState *es);
ExecForeignUpdate_function ExecForeignUpdate;
ExecForeignDelete_function ExecForeignDelete;
EndForeignModify_function EndForeignModify;
+ IsForeignRelUpdatable_function IsForeignRelUpdatable;
/* Support functions for EXPLAIN */
ExplainForeignScan_function ExplainForeignScan;
extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
extern Node *build_column_default(Relation rel, int attrno);
-extern bool relation_is_updatable(Oid reloid, int req_events);
+extern int relation_is_updatable(Oid reloid, bool include_triggers);
#endif /* REWRITEHANDLER_H */
extern Datum pg_get_keywords(PG_FUNCTION_ARGS);
extern Datum pg_typeof(PG_FUNCTION_ARGS);
extern Datum pg_collation_for(PG_FUNCTION_ARGS);
-extern Datum pg_view_is_insertable(PG_FUNCTION_ARGS);
-extern Datum pg_view_is_updatable(PG_FUNCTION_ARGS);
+extern Datum pg_relation_is_updatable(PG_FUNCTION_ARGS);
+extern Datum pg_column_is_updatable(PG_FUNCTION_ARGS);
/* oid.c */
extern Datum oidin(PG_FUNCTION_ARGS);