[Review] inherit support for foreign tables
Shigeru Hanada wrote:
Attached patch allows a foreign table to be a child of a table. It
also allows foreign tables to have CHECK constraints.
Sorry for the delay. I started to look at this patch.
Though this would be debatable, in current implementation, constraints
defined on a foreign table (now only NOT NULL and CHECK are supported)
are not enforced during INSERT or UPDATE executed against foreign
tables. This means that retrieved data might violates the constraints
defined on local side. This is debatable issue because integrity of
data is important for DBMS, but in the first cut, it is just
documented as a note.
I agree with you, but we should introduce a new keyword such as
ASSERTIVE or something in 9.4, in preparation for the enforcement of
constraints on the local side in a future release? What I'm concerned
about is, otherwise, users will have to rewrite those DDL queries at
that point. No?
Because I don't see practical case to have a foreign table as a
parent, and it avoid a problem about recursive ALTER TABLE operation,
foreign tables can't be a parent.
I agree with you on that point.
For other commands recursively processed such as ANALYZE, foreign
tables in the leaf of inheritance tree are ignored.
I'm not sure that in the processing of the ANALYZE command, we should
ignore child foreign tables. It seems to me that the recursive
processing is not that hard. Rather what I'm concerned about is that if
the recursive processing is allowed, then autovacuum will probably have
to access to forign tables on the far side in order to acquire those
sample rows. It might be undesirable from the viewpoint of security or
from the viewpoint of efficiency.
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -22,6 +22,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable
class="PARAMETER">table_name
<replaceable class="PARAMETER">column_name</replaceable> <replaceable
class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable
class="PA\
RAMETER">option</replaceable> '<replaceable
class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE
<replaceable>collation</replaceable> ] [ <rep\
laceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable>
'<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
@@ -159,6 +160,17 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable
class="PARAMETER">table_name
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing table or foreign table from which the new foreign
+ table automatically inherits all columns, see
+ <xref linkend="ddl-inherit"> for details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
Correct? I think we should not allow a foreign table to be a parent.
I'll look at this patch more closely.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Fujita-san,
Thanks for the review.
2014/1/23 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
Shigeru Hanada wrote:
Though this would be debatable, in current implementation, constraints
defined on a foreign table (now only NOT NULL and CHECK are supported)
are not enforced during INSERT or UPDATE executed against foreign
tables. This means that retrieved data might violates the constraints
defined on local side. This is debatable issue because integrity of
data is important for DBMS, but in the first cut, it is just
documented as a note.I agree with you, but we should introduce a new keyword such as
ASSERTIVE or something in 9.4, in preparation for the enforcement of
constraints on the local side in a future release? What I'm concerned
about is, otherwise, users will have to rewrite those DDL queries at
that point. No?
In the original thread, whether the new syntax is necessary (maybe
ASSERTIVE will not be used though) is under discussion. Anyway, your
point, decrease user's DDL rewrite less as possible is important.
Could you post review result and/or your opinion as the reply to the
original thread, then we include other people interested in this
feature can share discussion.
Because I don't see practical case to have a foreign table as a
parent, and it avoid a problem about recursive ALTER TABLE operation,
foreign tables can't be a parent.I agree with you on that point.
For other commands recursively processed such as ANALYZE, foreign
tables in the leaf of inheritance tree are ignored.I'm not sure that in the processing of the ANALYZE command, we should
ignore child foreign tables. It seems to me that the recursive
processing is not that hard. Rather what I'm concerned about is that if
the recursive processing is allowed, then autovacuum will probably have
to access to forign tables on the far side in order to acquire those
sample rows. It might be undesirable from the viewpoint of security or
from the viewpoint of efficiency.
As you say, it's not difficult to do recursive ANALYZE including
foreign tables. The reason why ANALYZE ignores descendant foreign
tables is consistent behavior. At the moment, ANALYZE ignores foreign
tables in top-level (non-child) when no table name was given, and if
we want to ANALYZE foreign tables we need to specify explicitly.
I think it's not so bad to show WARNING or INFO message about foreign
table ignorance, including which is not a child.
--- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -22,6 +22,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PA\ RAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <rep\ laceable class="PARAMETER">column_constraint</replaceable> [ ... ] ] [, ... ] ] ) +[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ] SERVER <replaceable class="parameter">server_name</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]@@ -159,6 +160,17 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable
class="PARAMETER">table_name
</varlistentry><varlistentry> + <term><replaceable class="PARAMETER">parent_table</replaceable></term> + <listitem> + <para> + The name of an existing table or foreign table from which the new foreign + table automatically inherits all columns, see + <xref linkend="ddl-inherit"> for details of table inheritance. + </para> + </listitem> + </varlistentry>Correct? I think we should not allow a foreign table to be a parent.
Oops, good catch.
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
(2014/01/25 11:27), Shigeru Hanada wrote:
2014/1/23 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
Shigeru Hanada wrote:
Though this would be debatable, in current implementation, constraints
defined on a foreign table (now only NOT NULL and CHECK are supported)
are not enforced during INSERT or UPDATE executed against foreign
tables. This means that retrieved data might violates the constraints
defined on local side. This is debatable issue because integrity of
data is important for DBMS, but in the first cut, it is just
documented as a note.I agree with you, but we should introduce a new keyword such as
ASSERTIVE or something in 9.4, in preparation for the enforcement of
constraints on the local side in a future release? What I'm concerned
about is, otherwise, users will have to rewrite those DDL queries at
that point. No?In the original thread, whether the new syntax is necessary (maybe
ASSERTIVE will not be used though) is under discussion. Anyway, your
point, decrease user's DDL rewrite less as possible is important.
Could you post review result and/or your opinion as the reply to the
original thread, then we include other people interested in this
feature can share discussion.
OK I'll give my opinion in that thread.
For other commands recursively processed such as ANALYZE, foreign
tables in the leaf of inheritance tree are ignored.I'm not sure that in the processing of the ANALYZE command, we should
ignore child foreign tables. It seems to me that the recursive
processing is not that hard. Rather what I'm concerned about is that if
the recursive processing is allowed, then autovacuum will probably have
to access to forign tables on the far side in order to acquire those
sample rows. It might be undesirable from the viewpoint of security or
from the viewpoint of efficiency.As you say, it's not difficult to do recursive ANALYZE including
foreign tables. The reason why ANALYZE ignores descendant foreign
tables is consistent behavior. At the moment, ANALYZE ignores foreign
tables in top-level (non-child) when no table name was given, and if
we want to ANALYZE foreign tables we need to specify explicitly.I think it's not so bad to show WARNING or INFO message about foreign
table ignorance, including which is not a child.
Yeah, the consistency is essential for its ease of use. But I'm not
sure that inherited stats ignoring foreign tables is actually useful for
query optimization. What I think about the consistency is a) the
ANALYZE command with no table names skips ANALYZEing inheritance trees
that include at least one foreign table as a child, but b) the ANALYZE
command with a table name indicating an inheritance tree that includes
any foreign tables does compute the inherited stats in the same way as
an inheritance tree consiting of only ordinary tables by acquiring the
sample rows from each foreign table on the far side.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2014-01-27 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
(2014/01/25 11:27), Shigeru Hanada wrote:
Yeah, the consistency is essential for its ease of use. But I'm not sure
that inherited stats ignoring foreign tables is actually useful for query
optimization. What I think about the consistency is a) the ANALYZE command
with no table names skips ANALYZEing inheritance trees that include at least
one foreign table as a child, but b) the ANALYZE command with a table name
indicating an inheritance tree that includes any foreign tables does compute
the inherited stats in the same way as an inheritance tree consiting of only
ordinary tables by acquiring the sample rows from each foreign table on the
far side.
b) sounds little complex to understand or explain.
Is it too big change that making ANALYZE command to handle foreign
tables too even if no table name was specified? IIRC, performance
issue was the reason to exclude foreign tables from auto-analyze
processing. ANALYZEing large database contains local huge data also
takes long time. One idea to avoid unexpected long processing is to
add option to foreign tables to mark it as "not-auto-analyzable".
Thoughts?
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
(2014/01/27 21:49), Shigeru Hanada wrote:
2014-01-27 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
(2014/01/25 11:27), Shigeru Hanada wrote:
Yeah, the consistency is essential for its ease of use. But I'm not sure
that inherited stats ignoring foreign tables is actually useful for query
optimization. What I think about the consistency is a) the ANALYZE command
with no table names skips ANALYZEing inheritance trees that include at least
one foreign table as a child, but b) the ANALYZE command with a table name
indicating an inheritance tree that includes any foreign tables does compute
the inherited stats in the same way as an inheritance tree consiting of only
ordinary tables by acquiring the sample rows from each foreign table on the
far side.b) sounds little complex to understand or explain.
Is it too big change that making ANALYZE command to handle foreign
tables too even if no table name was specified? IIRC, performance
issue was the reason to exclude foreign tables from auto-analyze
processing. ANALYZEing large database contains local huge data also
takes long time. One idea to avoid unexpected long processing is to
add option to foreign tables to mark it as "not-auto-analyzable".
Maybe I didn't express my idea clearly. Sorry for that.
I don't think that we now allow the ANALYZE command to handle foreign
tables when no table name is specified with the command. I think that
we allow the ANALYZE command to handle an inheritance tree that includes
foreign tables when the name of the parent table is specified, without
ignoring such foreign tables in the caluculation. ISTM it would be
possible to do so if we introduce a new parameter, say, vac_mode, which
indicates wether vacuum() is called with a specific table or not.
I'll try to modify the ANALYZE command to do so on top of you patch.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
(2014/01/28 22:01), Etsuro Fujita wrote:
(2014/01/27 21:49), Shigeru Hanada wrote:
Is it too big change that making ANALYZE command to handle foreign
tables too even if no table name was specified? IIRC, performance
issue was the reason to exclude foreign tables from auto-analyze
processing. ANALYZEing large database contains local huge data also
takes long time. One idea to avoid unexpected long processing is to
add option to foreign tables to mark it as "not-auto-analyzable".Maybe I didn't express my idea clearly. Sorry for that.
I don't think that we now allow the ANALYZE command to handle foreign
tables when no table name is specified with the command. I think that
we allow the ANALYZE command to handle an inheritance tree that includes
foreign tables when the name of the parent table is specified, without
ignoring such foreign tables in the caluculation. ISTM it would be
possible to do so if we introduce a new parameter, say, vac_mode, which
indicates wether vacuum() is called with a specific table or not.I'll try to modify the ANALYZE command to do so on top of you patch.
Done on top of your patch, foreign_inherit.patch, not the latest
version, foreign_inherit-v2.patch. As I proposed, an inheritance tree
that includes foreign tables is now ANALYZEd, without ignoring such
foreign tables in the inherited-stats computation, if the name of the
parent table is specified with the ANALYZE command. (That has been done
by a small modification of analyze.c, thanks to [1]/messages/by-id/E1SGFOO-0006ZF-8n@gemulon.postgresql.org.) The ANALYZE
command with no tablename or the autovacuum worker skips the
inherited-stats computation itself for inheritance trees that includes
foreign tables, which is different from the original patch. To
distinguish the ANALYZE-with-a-tablename command from the others (ie,
the ANALYZE-with-no-tablename command or the autovacuum worker), I've
introduced a new parameter, vacmode, in vacuum(), and thus called
analyze_rel() with that parameter. Attached is the modified patch.
Could you review the patch?
Thanks,
[1]: /messages/by-id/E1SGFOO-0006ZF-8n@gemulon.postgresql.org
/messages/by-id/E1SGFOO-0006ZF-8n@gemulon.postgresql.org
Best regards,
Etsuro Fujita
Attachments:
foreign_inherit-v1.1.patchtext/plain; charset=Shift_JIS; name=foreign_inherit-v1.1.patchDownload
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 258,263 **** CREATE TABLE products (
--- 258,274 ----
even if the value came from the default value definition.
</para>
+ <note>
+ <para>
+ Note that constraints can be defined on foreign tables too, but such
+ constraints are not enforced on insert or update. Those constraints are
+ "assertive", and work only to tell planner that some kind of optimization
+ such as constraint exclusion can be considerd. This seems useless, but
+ allows us to use foriegn table as child table (see
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2021,2028 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, a table can inherit from
! zero or more other tables, and a query can reference either all
rows of a table or all rows of a table plus all of its descendant tables.
The latter behavior is the default.
For example, the following query finds the names of all cities,
--- 2032,2039 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, a table or foreign table can
! inherit from zero or more other tables, and a query can reference either all
rows of a table or all rows of a table plus all of its descendant tables.
The latter behavior is the default.
For example, the following query finds the names of all cities,
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,342 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,176 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing table or foreign table from which the new foreign
+ table automatically inherits all columns, see
+ <xref linkend="ddl-inherit"> for details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 91,96 **** static void BlockSampler_Init(BlockSampler bs, BlockNumber nblocks,
--- 91,98 ----
int samplesize);
static bool BlockSampler_HasMore(BlockSampler bs);
static BlockNumber BlockSampler_Next(BlockSampler bs);
+ static bool inheritance_tree_include_foreign(Relation onerel,
+ bool *isAnalyzable);
static void compute_index_stats(Relation onerel, double totalrows,
AnlIndexData *indexdata, int nindexes,
HeapTuple *rows, int numrows,
***************
*** 114,120 **** static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
* analyze_rel() -- analyze one relation
*/
void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
{
Relation onerel;
int elevel;
--- 116,123 ----
* analyze_rel() -- analyze one relation
*/
void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
! BufferAccessStrategy bstrategy)
{
Relation onerel;
int elevel;
***************
*** 270,276 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
* If there are child tables, do recursive ANALYZE.
*/
if (onerel->rd_rel->relhassubclass)
! do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
* Close source relation now, but keep lock so that no one deletes it
--- 273,288 ----
* If there are child tables, do recursive ANALYZE.
*/
if (onerel->rd_rel->relhassubclass)
! {
! bool include_foreign;
! bool isAnalyzable;
!
! include_foreign = inheritance_tree_include_foreign(onerel,
! &isAnalyzable);
!
! if (!include_foreign || (isAnalyzable && vacmode == VAC_MODE_SINGLE))
! do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
! }
/*
* Close source relation now, but keep lock so that no one deletes it
***************
*** 668,673 **** do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 680,772 ----
}
/*
+ * Check wether the inheritance tree includes foreign tables. And if so,
+ * check wether such foreign tables are all analyzable.
+ */
+ static bool
+ inheritance_tree_include_foreign(Relation onerel, bool *isAnalyzable)
+ {
+ bool result = false;
+ List *tableOIDs;
+ ListCell *lc;
+
+ *isAnalyzable = true;
+
+ /*
+ * Find all members of inheritance set. We only need AccessShareLock on
+ * the children.
+ */
+ tableOIDs =
+ find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL);
+
+ /*
+ * Check that there's at least one descendant, else fail. This could
+ * happen despite analyze_rel's relhassubclass check, if table once had a
+ * child but no longer does. In that case, we can clear the
+ * relhassubclass field so as not to make the same mistake again later.
+ * (This is safe because we hold ShareUpdateExclusiveLock.)
+ */
+ if (list_length(tableOIDs) < 2)
+ {
+ /* CCI because we already updated the pg_class row in this command */
+ CommandCounterIncrement();
+ SetRelationHasSubclass(RelationGetRelid(onerel), false);
+ return result;
+ }
+
+ /*
+ * Check wether the inheritance tree includes some foreign tables.
+ */
+ foreach(lc, tableOIDs)
+ {
+ Oid childOID = lfirst_oid(lc);
+ Relation childrel;
+
+ /* Parent table should not be foreign */
+ if (childOID == RelationGetRelid(onerel))
+ continue;
+
+ /* We already got the needed lock */
+ childrel = heap_open(childOID, NoLock);
+
+ /* Ignore if temp table of another backend */
+ if (RELATION_IS_OTHER_TEMP(childrel))
+ {
+ /* ... but release the lock on it */
+ Assert(childrel != onerel);
+ heap_close(childrel, AccessShareLock);
+ continue;
+ }
+
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ FdwRoutine *fdwroutine;
+ AcquireSampleRowsFunc func = NULL;
+ BlockNumber relpages = 0;
+ bool ok = false;
+
+ /* Found it */
+ result = true;
+
+ /* Check whether the FDW supports analysis */
+ fdwroutine = GetFdwRoutineForRelation(childrel, false);
+
+ if (fdwroutine->AnalyzeForeignTable != NULL)
+ ok = fdwroutine->AnalyzeForeignTable(childrel,
+ &func,
+ &relpages);
+
+ if (!ok)
+ *isAnalyzable = false;
+ }
+
+ heap_close(childrel, AccessShareLock);
+ }
+
+ return result;
+ }
+
+ /*
* Compute statistics about indexes of a relation
*/
static void
***************
*** 1451,1456 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1550,1556 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1485,1490 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1585,1592 ----
* BlockNumber, so we use double arithmetic.
*/
rels = (Relation *) palloc(list_length(tableOIDs) * sizeof(Relation));
+ acquirefunc = (AcquireSampleRowsFunc *) palloc(list_length(tableOIDs)
+ * sizeof(AcquireSampleRowsFunc));
relblocks = (double *) palloc(list_length(tableOIDs) * sizeof(double));
totalblocks = 0;
nrels = 0;
***************
*** 1506,1512 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
--- 1608,1638 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
! {
! acquirefunc[nrels] = acquire_sample_rows;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
! }
! else
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! acquirefunc[nrels] = NULL;
!
! fdwroutine = GetFdwRoutineForRelation(childrel, false);
! Assert(fdwroutine->AnalyzeForeignTable != NULL)
!
! ok = fdwroutine->AnalyzeForeignTable(childrel,
! &acquirefunc[nrels],
! &relpages);
! Assert(ok);
!
! relblocks[nrels] = (double) relpages;
! }
!
totalblocks += relblocks[nrels];
nrels++;
}
***************
*** 1524,1529 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1650,1656 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1539,1550 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
tdrows;
/* Fetch a random sample of the child's rows */
! childrows = acquire_sample_rows(childrel,
! elevel,
! rows + numrows,
! childtargrows,
! &trows,
! &tdrows);
/* We may need to convert from child's rowtype to parent's */
if (childrows > 0 &&
--- 1666,1677 ----
tdrows;
/* Fetch a random sample of the child's rows */
! childrows = childacquirefunc(childrel,
! elevel,
! rows + numrows,
! childtargrows,
! &trows,
! &tdrows);
/* We may need to convert from child's rowtype to parent's */
if (childrows > 0 &&
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 465,474 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
! if (stmt->constraints != NIL && relkind == RELKIND_FOREIGN_TABLE)
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("constraints are not supported on foreign tables")));
/*
* Look up the namespace in which we are supposed to create the relation,
--- 465,489 ----
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
! /*
! * Shouldn't this have been checked in parser?
! */
! if (relkind == RELKIND_FOREIGN_TABLE)
! {
! ListCell *lc;
! foreach(lc, stmt->constraints)
! {
! NewConstraint *nc = lfirst(lc);
!
! if (nc->contype != CONSTR_CHECK &&
! nc->contype != CONSTR_DEFAULT &&
! nc->contype != CONSTR_NULL &&
! nc->contype != CONSTR_NOTNULL)
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("only check constraints are supported on foreign tables")));
! }
! }
/*
* Look up the namespace in which we are supposed to create the relation,
***************
*** 3044,3050 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_ADD_INDEX;
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
! ATSimplePermissions(rel, ATT_TABLE);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
--- 3059,3065 ----
pass = AT_PASS_ADD_INDEX;
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
***************
*** 3058,3064 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_ADD_CONSTR;
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
! ATSimplePermissions(rel, ATT_TABLE);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
--- 3073,3079 ----
pass = AT_PASS_ADD_CONSTR;
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
***************
*** 3126,3138 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_MISC;
break;
case AT_AddInherit: /* INHERIT */
! ATSimplePermissions(rel, ATT_TABLE);
/* This command never recurses */
ATPrepAddInherit(rel);
pass = AT_PASS_MISC;
break;
case AT_AlterConstraint: /* ALTER CONSTRAINT */
! ATSimplePermissions(rel, ATT_TABLE);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
--- 3141,3153 ----
pass = AT_PASS_MISC;
break;
case AT_AddInherit: /* INHERIT */
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* This command never recurses */
ATPrepAddInherit(rel);
pass = AT_PASS_MISC;
break;
case AT_AlterConstraint: /* ALTER CONSTRAINT */
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
***************
*** 3161,3167 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_EnableAlwaysRule:
case AT_EnableReplicaRule:
case AT_DisableRule:
- case AT_DropInherit: /* NO INHERIT */
case AT_AddOf: /* OF */
case AT_DropOf: /* NOT OF */
ATSimplePermissions(rel, ATT_TABLE);
--- 3176,3181 ----
***************
*** 3169,3174 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
--- 3183,3194 ----
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_DropInherit: /* NO INHERIT */
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* These commands never recurse */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
case AT_GenericOptions:
ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
/* No command-specific prep needed */
***************
*** 4421,4427 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE);
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
--- 4441,4447 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
***************
*** 5317,5323 **** ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE);
/*
* get the number of the attribute
--- 5337,5343 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* get the number of the attribute
***************
*** 5708,5714 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
--- 5728,5734 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 7197,7203 **** ATExecDropConstraint(Relation rel, const char *constrName,
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE);
conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
--- 7217,7223 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 96,101 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 96,102 ----
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
{
const char *stmttype;
+ VacuumMode vacmode;
volatile bool in_outer_xact,
use_own_xacts;
List *relations;
***************
*** 144,149 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 145,164 ----
ALLOCSET_DEFAULT_MAXSIZE);
/*
+ * Identify vacuum mode. If relid is not InvalidOid, the caller should be
+ * an autovacuum worker. See the above comments.
+ */
+ if (relid != InvalidOid)
+ vacmode = VAC_MODE_AUTOVACUUM;
+ else
+ {
+ if (!vacstmt->relation)
+ vacmode = VAC_MODE_ALL;
+ else
+ vacmode = VAC_MODE_SINGLE;
+ }
+
+ /*
* If caller didn't give us a buffer strategy object, make one in the
* cross-transaction memory context.
*/
***************
*** 246,252 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_strategy);
if (use_own_xacts)
{
--- 261,267 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vacmode, vac_strategy);
if (use_own_xacts)
{
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1337,1347 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
/*
* Build an RTE for the child, and attach to query's rangetable list.
* We copy most fields of the parent's RTE, but replace relation OID,
! * and set inh = false. Also, set requiredPerms to zero since all
! * required permissions checks are done on the original RTE.
*/
childrte = copyObject(rte);
childrte->relid = childOID;
childrte->inh = false;
childrte->requiredPerms = 0;
parse->rtable = lappend(parse->rtable, childrte);
--- 1337,1348 ----
/*
* Build an RTE for the child, and attach to query's rangetable list.
* We copy most fields of the parent's RTE, but replace relation OID,
! * relkind and set inh = false. Also, set requiredPerms to zero since
! * all required permissions checks are done on the original RTE.
*/
childrte = copyObject(rte);
childrte->relid = childOID;
+ childrte->relkind = newrelation->rd_rel->relkind;
childrte->inh = false;
childrte->requiredPerms = 0;
parse->rtable = lappend(parse->rtable, childrte);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4206,4237 **** AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
! SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
n->base.relation = $4;
n->base.tableElts = $6;
! n->base.inhRelations = NIL;
n->base.if_not_exists = false;
/* FDW-specific data */
! n->servername = $9;
! n->options = $10;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
! SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
n->base.relation = $7;
n->base.tableElts = $9;
! n->base.inhRelations = NIL;
n->base.if_not_exists = true;
/* FDW-specific data */
! n->servername = $12;
! n->options = $13;
$$ = (Node *) n;
}
;
--- 4206,4237 ----
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
! OptInherit SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
n->base.relation = $4;
n->base.tableElts = $6;
! n->base.inhRelations = $8;
n->base.if_not_exists = false;
/* FDW-specific data */
! n->servername = $10;
! n->options = $11;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
! OptInherit SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
n->base.relation = $7;
n->base.tableElts = $9;
! n->base.inhRelations = $11;
n->base.if_not_exists = true;
/* FDW-specific data */
! n->servername = $13;
! n->options = $14;
$$ = (Node *) n;
}
;
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 515,526 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
break;
case CONSTR_CHECK:
- if (cxt->isforeign)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("constraints are not supported on foreign tables"),
- parser_errposition(cxt->pstate,
- constraint->location)));
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
--- 515,520 ----
***************
*** 605,614 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
! if (cxt->isforeign)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
--- 599,608 ----
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
! if (cxt->isforeign && constraint->contype != CONSTR_CHECK)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("only check constraints are supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
*** a/src/include/commands/vacuum.h
--- b/src/include/commands/vacuum.h
***************
*** 138,143 **** extern int vacuum_freeze_min_age;
--- 138,152 ----
extern int vacuum_freeze_table_age;
+ /* Possible modes for vacuum() */
+ typedef enum
+ {
+ VAC_MODE_ALL, /* Vacuum/analyze all relations */
+ VAC_MODE_SINGLE, /* Vacuum/analyze a specific relation */
+ VAC_MODE_AUTOVACUUM /* Autovacuum worker */
+ } VacuumMode;
+
+
/* in commands/vacuum.c */
extern void vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel);
***************
*** 170,176 **** extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
BufferAccessStrategy bstrategy);
/* in commands/analyze.c */
! extern void analyze_rel(Oid relid, VacuumStmt *vacstmt,
BufferAccessStrategy bstrategy);
extern bool std_typanalyze(VacAttrStats *stats);
extern double anl_random_fract(void);
--- 179,185 ----
BufferAccessStrategy bstrategy);
/* in commands/analyze.c */
! extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
BufferAccessStrategy bstrategy);
extern bool std_typanalyze(VacAttrStats *stats);
extern double anl_random_fract(void);
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 750,765 **** CREATE TABLE use_ft1_column_type (x ft1);
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR
ERROR: cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type
DROP TABLE use_ft1_column_type;
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); -- ERROR
! ERROR: constraints are not supported on foreign tables
! LINE 1: ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c...
! ^
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR
! ERROR: "ft1" is not a table
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! ERROR: "ft1" is not a table
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
! ERROR: "ft1" is not a table
ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR
ERROR: "ft1" is not a table
ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
--- 750,761 ----
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR
ERROR: cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type
DROP TABLE use_ft1_column_type;
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR
! ERROR: constraint "no_const" of relation "ft1" does not exist
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! NOTICE: constraint "no_const" of relation "ft1" does not exist, skipping
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR
ERROR: "ft1" is not a table
ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 314,323 **** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
CREATE TABLE use_ft1_column_type (x ft1);
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR
DROP TABLE use_ft1_column_type;
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); -- ERROR
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR
ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');
--- 314,323 ----
CREATE TABLE use_ft1_column_type (x ft1);
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR
DROP TABLE use_ft1_column_type;
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR
ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');