1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-16 06:01:02 +03:00

Add support for LIKE in CREATE FOREIGN TABLE

LIKE enables the creation of foreign tables based on the column
definitions, constraints and objects of the defined source relation(s).

This feature mirrors the behavior of CREATE TABLE LIKE, but ignores
the INCLUDING sub-options that do not make sense for foreign tables:
INDEXES, COMPRESSION, IDENTITY and STORAGE.  The supported sub-options
are COMMENTS, CONSTRAINTS, DEFAULTS, GENERATED and STATISTICS, mapping
with the clauses already supported by the command.

Note that the restriction with LIKE in CREATE FOREIGN TABLE was added in
a0c6dfeecf.

Author: Zhang Mingli
Reviewed-by: Álvaro Herrera, Sami Imseih, Michael Paquier
Discussion: https://postgr.es/m/42d3f855-2275-4361-a42a-826172ca2dc4@Spark
This commit is contained in:
Michael Paquier
2025-02-19 15:50:37 +09:00
parent e7563e3c75
commit 302cf15759
4 changed files with 271 additions and 12 deletions

View File

@ -23,7 +23,8 @@ PostgreSQL documentation
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable> ( [
{ <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> [ ... ] ]
| <replaceable>table_constraint</replaceable> }
| <replaceable>table_constraint</replaceable>
| LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ] }
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
@ -57,6 +58,10 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] }
[ ENFORCED | NOT ENFORCED ]
<phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase>
{ INCLUDING | EXCLUDING } { COMMENTS | CONSTRAINTS | DEFAULTS | GENERATED | STATISTICS | ALL }
<phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase>
IN ( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] ) |
@ -191,6 +196,111 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
<varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
The <literal>LIKE</literal> clause specifies a table from which
the new table automatically copies all column names, their data types,
and their not-null constraints.
</para>
<para>
Unlike <literal>INHERITS</literal>, the new table and original table
are completely decoupled after creation is complete. Changes to the
original table will not be applied to the new table, and it is not
possible to include data of the new table in scans of the original
table.
</para>
<para>
Also unlike <literal>INHERITS</literal>, columns and
constraints copied by <literal>LIKE</literal> are not merged with similarly
named columns and constraints.
If the same name is specified explicitly or in another
<literal>LIKE</literal> clause, an error is signaled.
</para>
<para>
The optional <replaceable>like_option</replaceable> clauses specify
which additional properties of the original table to copy. Specifying
<literal>INCLUDING</literal> copies the property, specifying
<literal>EXCLUDING</literal> omits the property.
<literal>EXCLUDING</literal> is the default. If multiple specifications
are made for the same kind of object, the last one is used. The
available options are:
<variablelist>
<varlistentry>
<term><literal>INCLUDING COMMENTS</literal></term>
<listitem>
<para>
Comments for the copied columns, constraints, and indexes will be
copied. The default behavior is to exclude comments, resulting in
the copied columns and constraints in the new table having no
comments.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>INCLUDING CONSTRAINTS</literal></term>
<listitem>
<para>
<literal>CHECK</literal> constraints will be copied. No distinction
is made between column constraints and table constraints. Not-null
constraints are always copied to the new table.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>INCLUDING DEFAULTS</literal></term>
<listitem>
<para>
Default expressions for the copied column definitions will be
copied. Otherwise, default expressions are not copied, resulting in
the copied columns in the new table having null defaults. Note that
copying defaults that call database-modification functions, such as
<function>nextval</function>, may create a functional linkage
between the original and new tables.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>INCLUDING GENERATED</literal></term>
<listitem>
<para>
Any generation expressions of copied column definitions will be
copied. By default, new columns will be regular base columns.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>INCLUDING STATISTICS</literal></term>
<listitem>
<para>
Extended statistics are copied to the new table.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>INCLUDING ALL</literal></term>
<listitem>
<para>
<literal>INCLUDING ALL</literal> is an abbreviated form selecting
all the available individual options. (It could be useful to write
individual <literal>EXCLUDING</literal> clauses after
<literal>INCLUDING ALL</literal> to select all but some specific
options.)
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>CONSTRAINT <replaceable class="parameter">constraint_name</replaceable></literal></term>
<listitem>
@ -448,6 +558,8 @@ CREATE FOREIGN TABLE measurement_y2016m07
The ability to specify column default values is also
a <productname>PostgreSQL</productname> extension. Table inheritance, in the form
defined by <productname>PostgreSQL</productname>, is nonstandard.
The <literal>LIKE</literal> clause, as supported in this command, is
nonstandard.
</para>
</refsect1>

View File

@ -1131,6 +1131,10 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
* process at this point, add the TableLikeClause to cxt->likeclauses, which
* will cause utility.c to call expandTableLikeClause() after the new
* table has been created.
*
* Some options are ignored. For example, as foreign tables have no storage,
* these INCLUDING options have no effect: STORAGE, COMPRESSION, IDENTITY
* and INDEXES. Similarly, INCLUDING INDEXES is ignored from a view.
*/
static void
transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_clause)
@ -1145,12 +1149,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
setup_parser_errposition_callback(&pcbstate, cxt->pstate,
table_like_clause->relation->location);
/* we could support LIKE in many cases, but worry about it another day */
if (cxt->isforeign)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("LIKE is not supported for creating foreign tables")));
/* Open the relation referenced by the LIKE clause */
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
@ -1231,7 +1229,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
* Copy identity if requested
*/
if (attribute->attidentity &&
(table_like_clause->options & CREATE_TABLE_LIKE_IDENTITY))
(table_like_clause->options & CREATE_TABLE_LIKE_IDENTITY) &&
!cxt->isforeign)
{
Oid seq_relid;
List *seq_options;
@ -1250,14 +1249,16 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
}
/* Likewise, copy storage if requested */
if (table_like_clause->options & CREATE_TABLE_LIKE_STORAGE)
if ((table_like_clause->options & CREATE_TABLE_LIKE_STORAGE) &&
!cxt->isforeign)
def->storage = attribute->attstorage;
else
def->storage = 0;
/* Likewise, copy compression if requested */
if ((table_like_clause->options & CREATE_TABLE_LIKE_COMPRESSION) != 0
&& CompressionMethodIsValid(attribute->attcompression))
if ((table_like_clause->options & CREATE_TABLE_LIKE_COMPRESSION) != 0 &&
CompressionMethodIsValid(attribute->attcompression) &&
!cxt->isforeign)
def->compression =
pstrdup(GetCompressionMethodName(attribute->attcompression));
else
@ -1536,7 +1537,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
* Process indexes if required.
*/
if ((table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) &&
relation->rd_rel->relhasindex)
relation->rd_rel->relhasindex &&
childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
{
List *parent_indexes;
ListCell *l;

View File

@ -566,3 +566,106 @@ DROP TYPE ctlty1;
DROP VIEW ctlv1;
DROP TABLE IF EXISTS ctlt4, ctlt10, ctlt11, ctlt11a, ctlt12;
NOTICE: table "ctlt10" does not exist, skipping
--
-- CREATE FOREIGN TABLE LIKE
--
CREATE FOREIGN DATA WRAPPER ctl_dummy;
CREATE SERVER ctl_s0 FOREIGN DATA WRAPPER ctl_dummy;
CREATE TABLE ctl_table(a int PRIMARY KEY,
b varchar COMPRESSION pglz,
c int GENERATED ALWAYS AS (a * 2) STORED,
d bigint GENERATED ALWAYS AS IDENTITY,
e int DEFAULT 1);
CREATE INDEX ctl_table_a_key ON ctl_table(a);
COMMENT ON COLUMN ctl_table.b IS 'Column b';
CREATE STATISTICS ctl_table_stat ON a,b FROM ctl_table;
ALTER TABLE ctl_table ADD CONSTRAINT foo CHECK (b = 'text');
ALTER TABLE ctl_table ALTER COLUMN b SET STORAGE MAIN;
\d+ ctl_table
Table "public.ctl_table"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+-------------------+-----------+----------+------------------------------------+---------+--------------+-------------
a | integer | | not null | | plain | |
b | character varying | | | | main | | Column b
c | integer | | | generated always as (a * 2) stored | plain | |
d | bigint | | not null | generated always as identity | plain | |
e | integer | | | 1 | plain | |
Indexes:
"ctl_table_pkey" PRIMARY KEY, btree (a)
"ctl_table_a_key" btree (a)
Check constraints:
"foo" CHECK (b::text = 'text'::text)
Statistics objects:
"public.ctl_table_stat" ON a, b FROM ctl_table
Not-null constraints:
"ctl_table_a_not_null" NOT NULL "a"
"ctl_table_d_not_null" NOT NULL "d"
-- Test EXCLUDING ALL
CREATE FOREIGN TABLE ctl_foreign_table1(LIKE ctl_table EXCLUDING ALL) SERVER ctl_s0;
\d+ ctl_foreign_table1
Foreign table "public.ctl_foreign_table1"
Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
--------+-------------------+-----------+----------+---------+-------------+----------+--------------+-------------
a | integer | | not null | | | plain | |
b | character varying | | | | | extended | |
c | integer | | | | | plain | |
d | bigint | | not null | | | plain | |
e | integer | | | | | plain | |
Not-null constraints:
"ctl_table_a_not_null" NOT NULL "a"
"ctl_table_d_not_null" NOT NULL "d"
Server: ctl_s0
-- \d+ does not report the value of attcompression for a foreign table, so
-- check separately.
SELECT attname, attcompression FROM pg_attribute
WHERE attrelid = 'ctl_foreign_table1'::regclass and attnum > 0 ORDER BY attnum;
attname | attcompression
---------+----------------
a |
b |
c |
d |
e |
(5 rows)
-- Test INCLUDING ALL
-- INDEXES, IDENTITY, COMPRESSION, STORAGE are not copied.
CREATE FOREIGN TABLE ctl_foreign_table2(LIKE ctl_table INCLUDING ALL) SERVER ctl_s0;
\d+ ctl_foreign_table2
Foreign table "public.ctl_foreign_table2"
Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description
--------+-------------------+-----------+----------+------------------------------------+-------------+----------+--------------+-------------
a | integer | | not null | | | plain | |
b | character varying | | | | | extended | | Column b
c | integer | | | generated always as (a * 2) stored | | plain | |
d | bigint | | not null | | | plain | |
e | integer | | | 1 | | plain | |
Check constraints:
"foo" CHECK (b::text = 'text'::text)
Statistics objects:
"public.ctl_foreign_table2_a_b_stat" ON a, b FROM ctl_foreign_table2
Not-null constraints:
"ctl_table_a_not_null" NOT NULL "a"
"ctl_table_d_not_null" NOT NULL "d"
Server: ctl_s0
-- \d+ does not report the value of attcompression for a foreign table, so
-- check separately.
SELECT attname, attcompression FROM pg_attribute
WHERE attrelid = 'ctl_foreign_table2'::regclass and attnum > 0 ORDER BY attnum;
attname | attcompression
---------+----------------
a |
b |
c |
d |
e |
(5 rows)
DROP TABLE ctl_table;
DROP FOREIGN TABLE ctl_foreign_table1;
DROP FOREIGN TABLE ctl_foreign_table2;
DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
NOTICE: drop cascades to server ctl_s0

View File

@ -225,3 +225,45 @@ DROP SEQUENCE ctlseq1;
DROP TYPE ctlty1;
DROP VIEW ctlv1;
DROP TABLE IF EXISTS ctlt4, ctlt10, ctlt11, ctlt11a, ctlt12;
--
-- CREATE FOREIGN TABLE LIKE
--
CREATE FOREIGN DATA WRAPPER ctl_dummy;
CREATE SERVER ctl_s0 FOREIGN DATA WRAPPER ctl_dummy;
CREATE TABLE ctl_table(a int PRIMARY KEY,
b varchar COMPRESSION pglz,
c int GENERATED ALWAYS AS (a * 2) STORED,
d bigint GENERATED ALWAYS AS IDENTITY,
e int DEFAULT 1);
CREATE INDEX ctl_table_a_key ON ctl_table(a);
COMMENT ON COLUMN ctl_table.b IS 'Column b';
CREATE STATISTICS ctl_table_stat ON a,b FROM ctl_table;
ALTER TABLE ctl_table ADD CONSTRAINT foo CHECK (b = 'text');
ALTER TABLE ctl_table ALTER COLUMN b SET STORAGE MAIN;
\d+ ctl_table
-- Test EXCLUDING ALL
CREATE FOREIGN TABLE ctl_foreign_table1(LIKE ctl_table EXCLUDING ALL) SERVER ctl_s0;
\d+ ctl_foreign_table1
-- \d+ does not report the value of attcompression for a foreign table, so
-- check separately.
SELECT attname, attcompression FROM pg_attribute
WHERE attrelid = 'ctl_foreign_table1'::regclass and attnum > 0 ORDER BY attnum;
-- Test INCLUDING ALL
-- INDEXES, IDENTITY, COMPRESSION, STORAGE are not copied.
CREATE FOREIGN TABLE ctl_foreign_table2(LIKE ctl_table INCLUDING ALL) SERVER ctl_s0;
\d+ ctl_foreign_table2
-- \d+ does not report the value of attcompression for a foreign table, so
-- check separately.
SELECT attname, attcompression FROM pg_attribute
WHERE attrelid = 'ctl_foreign_table2'::regclass and attnum > 0 ORDER BY attnum;
DROP TABLE ctl_table;
DROP FOREIGN TABLE ctl_foreign_table1;
DROP FOREIGN TABLE ctl_foreign_table2;
DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;