diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out index 5392a7d1f..497955793 100644 --- a/contrib/citext/expected/citext.out +++ b/contrib/citext/expected/citext.out @@ -2276,3 +2276,44 @@ SELECT like_escape( name::text, ''::citext ) = like_escape( name::text, '' ) AS t (5 rows) +-- Ensure correct behavior for citext with materialized views. +CREATE TABLE citext_table ( + id serial primary key, + name citext +); +INSERT INTO citext_table (name) + VALUES ('one'), ('two'), ('three'), (NULL), (NULL); +CREATE MATERIALIZED VIEW citext_matview AS + SELECT * FROM citext_table; +CREATE UNIQUE INDEX citext_matview_id + ON citext_matview (id); +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; + id | name | id | name +----+------+----+------ +(0 rows) + +UPDATE citext_table SET name = 'Two' WHERE name = 'TWO'; +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; + id | name | id | name +----+------+----+------ + | | 2 | Two + 2 | two | | +(2 rows) + +REFRESH MATERIALIZED VIEW CONCURRENTLY citext_matview; +SELECT * FROM citext_matview ORDER BY id; + id | name +----+------- + 1 | one + 2 | Two + 3 | three + 4 | + 5 | +(5 rows) + diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out index 5316ad0cd..33274894d 100644 --- a/contrib/citext/expected/citext_1.out +++ b/contrib/citext/expected/citext_1.out @@ -2276,3 +2276,43 @@ SELECT like_escape( name::text, ''::citext ) = like_escape( name::text, '' ) AS t (5 rows) +-- Ensure correct behavior for citext with materialized views. +CREATE TABLE citext_table ( + id serial primary key, + name citext +); +INSERT INTO citext_table (name) + VALUES ('one'), ('two'), ('three'), (NULL), (NULL); +CREATE MATERIALIZED VIEW citext_matview AS + SELECT * FROM citext_table; +CREATE UNIQUE INDEX citext_matview_id + ON citext_matview (id); +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; + id | name | id | name +----+------+----+------ +(0 rows) + +UPDATE citext_table SET name = 'Two' WHERE name = 'TWO'; +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; + id | name | id | name +----+------+----+------ + | | 2 | Two + 2 | two | | +(2 rows) + +REFRESH MATERIALIZED VIEW CONCURRENTLY citext_matview; +SELECT * FROM citext_matview ORDER BY id; + id | name +----+------- + 1 | one + 2 | Two + 3 | three + 4 | + 5 | +(5 rows) diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql index c345cbd50..a0e520dc4 100644 --- a/contrib/citext/sql/citext.sql +++ b/contrib/citext/sql/citext.sql @@ -681,3 +681,26 @@ SELECT COUNT(*) = 19::bigint AS t FROM try; SELECT like_escape( name, '' ) = like_escape( name::text, '' ) AS t FROM srt; SELECT like_escape( name::text, ''::citext ) = like_escape( name::text, '' ) AS t FROM srt; + +-- Ensure correct behavior for citext with materialized views. +CREATE TABLE citext_table ( + id serial primary key, + name citext +); +INSERT INTO citext_table (name) + VALUES ('one'), ('two'), ('three'), (NULL), (NULL); +CREATE MATERIALIZED VIEW citext_matview AS + SELECT * FROM citext_table; +CREATE UNIQUE INDEX citext_matview_id + ON citext_matview (id); +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; +UPDATE citext_table SET name = 'Two' WHERE name = 'TWO'; +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; +REFRESH MATERIALIZED VIEW CONCURRENTLY citext_matview; +SELECT * FROM citext_matview ORDER BY id; diff --git a/contrib/oid2name/oid2name.cpp b/contrib/oid2name/oid2name.cpp index 683a130b7..02d7fb04d 100644 --- a/contrib/oid2name/oid2name.cpp +++ b/contrib/oid2name/oid2name.cpp @@ -446,7 +446,7 @@ void sql_exec_dumpalltables(PGconn* conn, struct options* opts) " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " " LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database()," " pg_catalog.pg_tablespace t " - "WHERE relkind IN ('r'%s%s) AND " + "WHERE relkind IN ('r', 'm'%s%s) AND " " %s" " t.oid = CASE" " WHEN reltablespace <> 0 THEN reltablespace" @@ -545,7 +545,7 @@ void sql_exec_searchtables(PGconn* conn, struct options* opts) " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace \n" " LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),\n" " pg_catalog.pg_tablespace t \n" - "WHERE relkind IN ('r', 'i', 'S', 't') AND \n" + "WHERE relkind IN ('r', 'm', 'i', 'S', 't') AND \n" " t.oid = CASE\n" " WHEN reltablespace <> 0 THEN reltablespace\n" " ELSE dattablespace\n" diff --git a/contrib/pg_upgrade/info.cpp b/contrib/pg_upgrade/info.cpp index cf362f3dc..3d5088430 100644 --- a/contrib/pg_upgrade/info.cpp +++ b/contrib/pg_upgrade/info.cpp @@ -339,7 +339,7 @@ static void get_rel_infos(ClusterInfo* cluster, DbInfo* dbinfo) " FROM pg_catalog.pg_class p INNER JOIN pg_catalog.pg_namespace n ON (p.relnamespace = n.oid)" " LEFT OUTER JOIN pg_catalog.pg_tablespace t ON (p.reltablespace = t.oid)" " WHERE p.oid < 16384 AND" - " p.relkind IN ('r', 'i', 't') AND" + " p.relkind IN ('r', 'm', 'i', 't') AND" " p.relisshared= false " " ORDER BY 1", is_exists ? ", t.relative " : ""); diff --git a/contrib/pg_upgrade/pg_upgrade.cpp b/contrib/pg_upgrade/pg_upgrade.cpp index cd9a3f02d..fd14c8ed2 100755 --- a/contrib/pg_upgrade/pg_upgrade.cpp +++ b/contrib/pg_upgrade/pg_upgrade.cpp @@ -788,7 +788,7 @@ static void set_frozenxids(void) "UPDATE pg_catalog.pg_class " "SET relfrozenxid64 = '%lu' " /* only heap and TOAST are vacuumed */ - "WHERE relkind IN ('r', 't')", + "WHERE relkind IN ('r', 'm','t')", old_cluster.controldata.chkpnt_nxtxid)); PQfinish(conn); diff --git a/contrib/pg_upgrade/version_old_8_3.cpp b/contrib/pg_upgrade/version_old_8_3.cpp index efa28e273..ee8884ed6 100644 --- a/contrib/pg_upgrade/version_old_8_3.cpp +++ b/contrib/pg_upgrade/version_old_8_3.cpp @@ -138,6 +138,7 @@ void old_8_3_check_for_tsquery_usage(ClusterInfo* cluster) "FROM pg_catalog.pg_class c, " " pg_catalog.pg_namespace n, " " pg_catalog.pg_attribute a " + /* materialized views didn't exist in 8.3, so no need to check 'm' */ "WHERE c.relkind = 'r' AND " " c.oid = a.attrelid AND " " NOT a.attisdropped AND " @@ -302,6 +303,7 @@ void old_8_3_rebuild_tsvector_tables(ClusterInfo* cluster, bool check_mode) "FROM pg_catalog.pg_class c, " " pg_catalog.pg_namespace n, " " pg_catalog.pg_attribute a " + /* materialized views didn't exist in 8.3, so no need to check 'm' */ "WHERE c.relkind = 'r' AND " " c.oid = a.attrelid AND " " NOT a.attisdropped AND " diff --git a/contrib/pgstattuple/pgstattuple.cpp b/contrib/pgstattuple/pgstattuple.cpp index d673dd9ca..33e91224d 100644 --- a/contrib/pgstattuple/pgstattuple.cpp +++ b/contrib/pgstattuple/pgstattuple.cpp @@ -194,6 +194,7 @@ static Datum pgstat_relation(Relation rel, FunctionCallInfo fcinfo) switch (rel->rd_rel->relkind) { case RELKIND_RELATION: + case RELKIND_MATVIEW: case RELKIND_TOASTVALUE: case RELKIND_UNCATALOGED: case RELKIND_SEQUENCE: diff --git a/contrib/vacuumlo/vacuumlo.cpp b/contrib/vacuumlo/vacuumlo.cpp index 2bfc16313..e8fa0a73a 100644 --- a/contrib/vacuumlo/vacuumlo.cpp +++ b/contrib/vacuumlo/vacuumlo.cpp @@ -166,7 +166,7 @@ static int vacuumlo(const char* database, const struct _param* param) strcat(buf, " AND a.atttypid = t.oid "); strcat(buf, " AND c.relnamespace = s.oid "); strcat(buf, " AND t.typname in ('oid', 'lo') "); - strcat(buf, " AND c.relkind = 'r'"); + strcat(buf, " AND c.relkind = in ('r', 'm')"); strcat(buf, " AND s.nspname !~ '^pg_'"); res = PQexec(conn, buf); if (PQresultStatus(res) != PGRES_TUPLES_OK) { diff --git a/src/bin/initdb/initdb.cpp b/src/bin/initdb/initdb.cpp index 0396de9c8..aa59059cd 100644 --- a/src/bin/initdb/initdb.cpp +++ b/src/bin/initdb/initdb.cpp @@ -2139,7 +2139,7 @@ static void setup_privileges(void) privileges_setup[0] = xstrdup("UPDATE pg_class " " SET relacl = E'{\"=r/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' " - " WHERE relkind IN ('r', 'v', 'S') AND relacl IS NULL;\n"); + " WHERE relkind IN ('r', 'v', 'm', 'S') AND relacl IS NULL;\n"); privileges_setup[1] = xstrdup("GRANT USAGE ON SCHEMA pg_catalog TO PUBLIC;\n"); privileges_setup[2] = xstrdup("GRANT CREATE, USAGE ON SCHEMA public TO PUBLIC;\n"); privileges_setup[3] = xstrdup("REVOKE ALL ON pg_largeobject FROM PUBLIC;\n"); diff --git a/src/bin/pg_dump/common.cpp b/src/bin/pg_dump/common.cpp index 125d16419..d344de000 100644 --- a/src/bin/pg_dump/common.cpp +++ b/src/bin/pg_dump/common.cpp @@ -268,8 +268,10 @@ static void flagInhTables(TableInfo* ptblinfo, int inumTables, InhInfo* inhinfo, TableInfo** parents; for (i = 0; i < inumTables; i++) { - /* Sequences and views never have parents */ - if (ptblinfo[i].relkind == RELKIND_SEQUENCE || ptblinfo[i].relkind == RELKIND_VIEW) + /* Some kinds never have parents */ + if (ptblinfo[i].relkind == RELKIND_SEQUENCE || + ptblinfo[i].relkind == RELKIND_VIEW || + tblinfo[i].relkind == RELKIND_MATVIEW) continue; /* Don't bother computing anything for non-target tables, either */ @@ -308,8 +310,10 @@ static void flagInhAttrs(TableInfo* ptblinfo, int inumTables) int numParents; TableInfo** parents; - /* Sequences and views never have parents */ - if (tbinfo->relkind == RELKIND_SEQUENCE || tbinfo->relkind == RELKIND_VIEW) + /* Some kinds never have parents */ + if (tbinfo->relkind == RELKIND_SEQUENCE || + tbinfo->relkind == RELKIND_VIEW || + tbinfo->relkind == RELKIND_MATVIEW) continue; /* Don't bother computing anything for non-target tables, either */ diff --git a/src/bin/pg_dump/pg_dump.cpp b/src/bin/pg_dump/pg_dump.cpp index 121d546e7..f9d745f73 100644 --- a/src/bin/pg_dump/pg_dump.cpp +++ b/src/bin/pg_dump/pg_dump.cpp @@ -293,6 +293,7 @@ static void exclude_error_tables(Archive* fout, SimpleOidList* oids); static NamespaceInfo* findNamespace(Archive* fout, Oid nsoid, Oid objoid); static void dumpTableData(Archive* fout, TableDataInfo* tdinfo); +static void refreshMatViewData(Archive* fout, TableDataInfo* tdinfo); static void guessConstraintInheritance(TableInfo* tblinfo, int numTables); static void dumpComment(Archive* fout, const char* target, const char* nmspace, const char* owner, CatalogId catalogId, int subid, DumpId dumpId); @@ -359,6 +360,7 @@ static void addBoundaryDependencies(DumpableObject** dobjs, int numObjs, Dumpabl static void getDomainConstraints(Archive* fout, TypeInfo* tyinfo); static void getTableData(TableInfo* tblinfo, int numTables); static void makeTableDataInfo(TableInfo* tbinfo, bool oids); +static void buildMatViewRefreshDependencies(Archive* fout); static void getTableDataFKConstraints(void); static char* format_function_arguments(FuncInfo* finfo, char* funcargs); static char* format_function_arguments_old(Archive* fout, FuncInfo* finfo, int nallargs, const char** allargtypes, @@ -842,6 +844,7 @@ int main(int argc, char** argv) if (!schemaOnly) { getTableData(tblinfo, numTables); + buildMatViewRefreshDependencies(fout); if (dataOnly) getTableDataFKConstraints(); } @@ -1846,10 +1849,11 @@ static void expand_table_name_patterns( "SELECT c.oid" "\nFROM pg_catalog.pg_class c" "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace" - "\nWHERE c.relkind in ('%c', '%c', '%c', '%c')\n", + "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n", RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW, + RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE); processSQLNamePattern(GetConnection(fout), @@ -2483,6 +2487,42 @@ static void dumpTableData(Archive* fout, TableDataInfo* tdinfo) destroyPQExpBuffer(copyBuf); } +/* + * refreshMatViewData - + * load or refresh the contents of a single materialized view + * + * Actually, this just makes an ArchiveEntry for the REFRESH MATERIALIZED VIEW + * statement. + */ +static void refreshMatViewData(Archive *fout, TableDataInfo *tdinfo) +{ + TableInfo *tbinfo = tdinfo->tdtable; + PQExpBuffer q; + + q = createPQExpBuffer(); + + appendPQExpBuffer(q, "REFRESH MATERIALIZED VIEW %s;\n", fmtId(tbinfo->dobj.name)); + + ArchiveEntry(fout, tdinfo->dobj.catId, /* catalog ID */ + tdinfo->dobj.dumpId, /* dump ID */ + tbinfo->dobj.name, /* Name */ + tbinfo->dobj.nmspace->dobj.name, /* Namespace */ + NULL, /* Tablespace */ + tbinfo->rolname, /* Owner */ + false, /* with oids */ + "MATERIALIZED VIEW DATA", /* Desc */ + SECTION_POST_DATA, /* Section */ + q->data, /* Create */ + "", /* Del */ + NULL, /* Copy */ + tdinfo->dobj.dependencies, /* Deps */ + tdinfo->dobj.nDeps, /* # Deps */ + NULL, /* Dumper */ + NULL); /* Dumper Arg */ + + destroyPQExpBuffer(q); +} + /* * getTableData - * set up dumpable objects representing the contents of tables @@ -2537,7 +2577,10 @@ static void makeTableDataInfo(TableInfo* tbinfo, bool boids) /* OK, let's dump it */ tdinfo = (TableDataInfo*)pg_malloc(sizeof(TableDataInfo)); - tdinfo->dobj.objType = DO_TABLE_DATA; + if (tbinfo->relkind == RELKIND_MATVIEW) + tdinfo->dobj.objType = DO_REFRESH_MATVIEW; + else + tdinfo->dobj.objType = DO_TABLE_DATA; /* * Note: use tableoid 0 so that this object won't be mistaken for @@ -2556,6 +2599,108 @@ static void makeTableDataInfo(TableInfo* tbinfo, bool boids) tbinfo->dataObj = tdinfo; } +/* + * The refresh for a materialized view must be dependent on the refresh for + * any materialized view that this one is dependent on. + * + * This must be called after all the objects are created, but before they are + * sorted. + */ +static void buildMatViewRefreshDependencies(Archive *fout) +{ + PQExpBuffer query = createPQExpBuffer(); + PGresult *res = NULL; + int ntups, i; + int i_classid, i_objid, i_refobjid; + + /* Make sure we are in proper schema */ + selectSourceSchema(fout, "pg_catalog"); + + if (fout->remoteVersion >= 90204) { + appendPQExpBuffer(query, "with recursive w as " + "( " + "select d1.objid, d2.refobjid, c2.relkind as refrelkind " + "from pg_depend d1 " + "join pg_class c1 on c1.oid = d1.objid " + "and c1.relkind = 'm' " + "join pg_rewrite r1 on r1.ev_class = d1.objid " + "join pg_depend d2 on d2.classid = 'pg_rewrite'::regclass " + "and d2.objid = r1.oid " + "and d2.refobjid <> d1.objid " + "join pg_class c2 on c2.oid = d2.refobjid " + "and c2.relkind in ('m','v') " + "where d1.classid = 'pg_class'::regclass " + "union " + "select w.objid, d3.refobjid, c3.relkind " + "from w " + "join pg_rewrite r3 on r3.ev_class = w.refobjid " + "join pg_depend d3 on d3.classid = 'pg_rewrite'::regclass " + "and d3.objid = r3.oid " + "and d3.refobjid <> w.refobjid " + "join pg_class c3 on c3.oid = d3.refobjid " + "and c3.relkind in ('m','v') " + ") " + "select 'pg_class'::regclass::oid as classid, objid, refobjid " + "from w " + "where refrelkind = 'm'"); + } + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + i_classid = PQfnumber(res, "classid"); + i_objid = PQfnumber(res, "objid"); + i_refobjid = PQfnumber(res, "refobjid"); + + for (i = 0; i < ntups; i++) { + CatalogId objId; + CatalogId refobjId; + DumpableObject *dobj; + DumpableObject *refdobj; + TableInfo *tbinfo; + TableInfo *reftbinfo; + + objId.tableoid = atooid(PQgetvalue(res, i, i_classid)); + objId.oid = atooid(PQgetvalue(res, i, i_objid)); + refobjId.tableoid = objId.tableoid; + refobjId.oid = atooid(PQgetvalue(res, i, i_refobjid)); + + dobj = findObjectByCatalogId(objId); + if (dobj == NULL) + continue; + + Assert(dobj->objType == DO_TABLE); + tbinfo = (TableInfo*)dobj; + Assert(tbinfo->relkind == RELKIND_MATVIEW); + dobj = (DumpableObject*)tbinfo->dataObj; + if (dobj == NULL) { + continue; + } + Assert(dobj->objType == DO_REFRESH_MATVIEW); + + refdobj = findObjectByCatalogId(refobjId); + if (refdobj == NULL) { + continue; + } + + Assert(refdobj->objType == DO_TABLE); + reftbinfo = (TableInfo*)refdobj; + Assert(reftbinfo->relkind == RELKIND_MATVIEW); + refdobj = (DumpableObject*)reftbinfo->dataObj; + if (refdobj == NULL) { + continue; + } + Assert(refdobj->objType == DO_REFRESH_MATVIEW); + + addObjectDependency(dobj, refdobj->dumpId); + } + + PQclear(res); + + destroyPQExpBuffer(query); +} + /* * getTableDataFKConstraints - * add dump-order dependencies reflecting foreign key constraints @@ -5306,6 +5451,7 @@ TableInfo* getTables(Archive* fout, int* numTables) int i_toastoid = 0; int i_toastfrozenxid = 0, i_toastfrozenxid64 = 0; int i_relpersistence = 0; + int i_relispopulated = 0; int i_relbucket = 0; int i_owning_tab = 0; int i_owning_col = 0; @@ -5363,7 +5509,7 @@ TableInfo* getTables(Archive* fout, int* numTables) * defined to inherit from a system catalog (pretty weird, but...) * * We ignore relations that are not ordinary tables, sequences, views, - * composite types, or foreign tables. + * materialized views, composite types, or foreign tables. * * Composite-type table entries won't be dumped as such, but we have to * make a DumpableObject for them so that we can track dependencies of the @@ -5397,7 +5543,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "c.relfrozenxid, %s, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "%s, " - "c.relpersistence, " + "c.relpersistence, 't' as relispopulated," "%s, ", username_subquery, isHasRelfrozenxid64 ? "c.relfrozenxid64" : "0 AS relfrozenxid64", @@ -5445,7 +5591,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "c.relfrozenxid, %s, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "%s, " - "c.relpersistence, " + "c.relpersistence, 't' as relispopulated, " "%s, ", username_subquery, isHasRelfrozenxid64 ? "c.relfrozenxid64" : "0 AS c.relfrozenxid64", @@ -5478,13 +5624,14 @@ TableInfo* getTables(Archive* fout, int* numTables) "d.objsubid = 0 AND " "d.refclassid = c.tableoid AND d.deptype = 'a') " "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) " - "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') AND c.relnamespace != %d " + "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') AND c.relnamespace != %d " "ORDER BY c.oid", RELKIND_SEQUENCE, RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW, RELKIND_COMPOSITE_TYPE, + RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE, CSTORE_NAMESPACE); } @@ -5501,7 +5648,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "c.relhasindex, c.relhasrules, c.relhasoids, " "c.relfrozenxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " - "'p' AS relpersistence, " + "'p' AS relpersistence, 't' as relispopulated, " "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL::Oid END AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " @@ -5538,7 +5685,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "c.relhasindex, c.relhasrules, c.relhasoids, " "c.relfrozenxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " - "'p' AS relpersistence, " + "'p' AS relpersistence, 't' as relispopulated, " "NULL AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " @@ -5573,7 +5720,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "c.relhasindex, c.relhasrules, c.relhasoids, " "c.relfrozenxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " - "'p' AS relpersistence, " + "'p' AS relpersistence, 't' as relispopulated, " "NULL AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " @@ -5609,7 +5756,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "0 AS relfrozenxid, " "0 AS toid, " "0 AS tfrozenxid, " - "'p' AS relpersistence, " + "'p' AS relpersistence, 't' as relispopulated, " "NULL AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " @@ -5644,7 +5791,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "0 AS relfrozenxid, " "0 AS toid, " "0 AS tfrozenxid, " - "'p' AS relpersistence, " + "'p' AS relpersistence, 't' as relispopulated, " "NULL AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " @@ -5675,7 +5822,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "0 AS relfrozenxid, " "0 AS toid, " "0 AS tfrozenxid, " - "'p' AS relpersistence, " + "'p' AS relpersistence, 't' as relispopulated, " "NULL AS reloftype, " "NULL::oid AS owning_tab, " "NULL::int4 AS owning_col, " @@ -5701,7 +5848,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "0 AS relfrozenxid, " "0 AS toid, " "0 AS tfrozenxid, " - "'p' AS relpersistence, " + "'p' AS relpersistence, 't' as relispopulated, " "NULL AS reloftype, " "NULL::oid AS owning_tab, " "NULL::int4 AS owning_col, " @@ -5737,7 +5884,7 @@ TableInfo* getTables(Archive* fout, int* numTables) "0 as relfrozenxid, " "0 AS toid, " "0 AS tfrozenxid, " - "'p' AS relpersistence, " + "'p' AS relpersistence, 't' as relispopulated, " "NULL AS reloftype, " "NULL::oid AS owning_tab, " "NULL::int4 AS owning_col, " @@ -5780,6 +5927,7 @@ TableInfo* getTables(Archive* fout, int* numTables) i_relhasindex = PQfnumber(res, "relhasindex"); i_relhasrules = PQfnumber(res, "relhasrules"); i_relhasoids = PQfnumber(res, "relhasoids"); + i_relispopulated = PQfnumber(res, "relispopulated"); i_relfrozenxid = PQfnumber(res, "relfrozenxid"); i_relfrozenxid64 = PQfnumber(res, "relfrozenxid64"); i_toastoid = PQfnumber(res, "toid"); @@ -5838,6 +5986,7 @@ TableInfo* getTables(Archive* fout, int* numTables) tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0); tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0); tblinfo[i].isMOT = false; + tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0); tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident)); tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid)); tblinfo[i].frozenxid64 = atoxid(PQgetvalue(res, i, i_relfrozenxid64)); @@ -5955,6 +6104,7 @@ TableInfo* getTables(Archive* fout, int* numTables) else selectDumpableTable(fout, &tblinfo[i]); tblinfo[i].interesting = tblinfo[i].dobj.dump; + tblinfo[i].postponed_def = false; /* might get set during sort */ /* * in Upgrade scenario no need to take a lock on table as @@ -6135,8 +6285,9 @@ void getIndexes(Archive* fout, TableInfo tblinfo[], int numTables) TableInfo* tbinfo = &tblinfo[i]; int32 contrants_processed = 0; - /* Only plain tables and mot have indexes */ - if ((tbinfo->relkind != RELKIND_RELATION && !tbinfo->isMOT) || !tbinfo->hasindex) + /* Only plain tables, materialized views and mot have indexes */ + if ((tbinfo->relkind != RELKIND_RELATION && !tbinfo->isMOT && tbinfo->relkind != RELKIND_MATVIEW) || + !tbinfo->hasindex) continue; /* Ignore indexes of tables not to be dumped */ @@ -6931,13 +7082,15 @@ RuleInfo* getRules(Archive* fout, int* numRules) ruleinfo[i].ev_enabled = *(PQgetvalue(res, i, i_ev_enabled)); if (ruleinfo[i].ruletable != NULL) { /* - * If the table is a view, force its ON SELECT rule to be sorted - * before the view itself --- this ensures that any dependencies - * for the rule affect the table's positioning. Other rules are - * forced to appear after their table. + * If the table is a view or materialized view, force its ON + * SELECT rule to be sorted before the view itself --- this + * ensures that any dependencies for the rule affect the table's + * positioning. Other rules are forced to appear after their + * table. */ - if (ruleinfo[i].ruletable->relkind == RELKIND_VIEW && ruleinfo[i].ev_type == '1' && - ruleinfo[i].is_instead) { + if ((ruleinfo[i].ruletable->relkind == RELKIND_VIEW || ruleinfo[i].ruletable->relkind == RELKIND_MATVIEW) && + ruleinfo[i].ev_type == '1' && ruleinfo[i].is_instead) { + addObjectDependency(&ruleinfo[i].ruletable->dobj, ruleinfo[i].dobj.dumpId); /* We'll merge the rule into CREATE VIEW, if possible */ ruleinfo[i].separate = false; @@ -9069,6 +9222,9 @@ static void dumpDumpableObject(Archive* fout, DumpableObject* dobj) case DO_INDEX: dumpIndex(fout, (IndxInfo*)dobj); break; + case DO_REFRESH_MATVIEW: + refreshMatViewData(fout, (TableDataInfo*) dobj); + break; case DO_RULE: dumpRule(fout, (RuleInfo*)dobj); break; @@ -15704,37 +15860,67 @@ static char* changeTableName(const char* tableName) oldTableName = NULL; return newTableName; } + +/* + * Create the AS clause for a view or materialized view. The semicolon is + * stripped because a materialized view must add a WITH NO DATA clause. + * + * This returns a new buffer which must be freed by the caller. + */ +static PQExpBuffer createViewAsClause(Archive* fout, TableInfo* tbinfo) +{ + PQExpBuffer query = createPQExpBuffer(); + PQExpBuffer result = createPQExpBuffer(); + PGresult *res; + int len; + + /* Fetch the view definition */ + if (fout->remoteVersion >= 70300) { + /* Beginning in 7.3, viewname is not unique; rely on OID */ + appendPQExpBuffer(query, "SELECT pg_catalog.pg_get_viewdef('%u'::pg_catalog.oid) AS viewdef", + tbinfo->dobj.catId.oid); + } else { + appendPQExpBuffer(query, "SELECT definition AS viewdef " + "FROM pg_views WHERE viewname = "); + appendStringLiteralAH(query, tbinfo->dobj.name, fout); + appendPQExpBuffer(query, ";"); + } + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + if (PQntuples(res) != 1) { + if (PQntuples(res) < 1) + exit_horribly(NULL, "query to obtain definition of view \"%s\" returned no data\n", tbinfo->dobj.name); + else + exit_horribly(NULL, "query to obtain definition of view \"%s\" returned more than one definition\n", + tbinfo->dobj.name); + } + + len = PQgetlength(res, 0, 0); + + if (len == 0) + exit_horribly(NULL, "definition of view \"%s\" appears to be empty (length zero)\n", tbinfo->dobj.name); + + /* Strip off the trailing semicolon so that other things may follow. */ + Assert(PQgetvalue(res, 0, 0)[len - 1] == ';'); + appendBinaryPQExpBuffer(result, PQgetvalue(res, 0, 0), len - 1); + + PQclear(res); + destroyPQExpBuffer(query); + + return result; +} + /* * dumpViewSchema - * write the declaration (not data) of one user-defined view + * write the declaration (not data) of one user-defined view */ -static void dumpViewSchema( - Archive* fout, TableInfo* tbinfo, PQExpBuffer query, PQExpBuffer q, PQExpBuffer delq, PQExpBuffer labelq) +static void dumpViewSchema(Archive* fout, TableInfo* tbinfo, PQExpBuffer query, PQExpBuffer q, PQExpBuffer delq, + PQExpBuffer labelq) { - char* viewdef = NULL; - char* schemainfo = NULL; - PGresult* defres = NULL; - PGresult* schemares = NULL; - - /* Beginning in 7.3, viewname is not unique; rely on OID */ - appendPQExpBuffer( - query, "SELECT pg_catalog.pg_get_viewdef('%u'::pg_catalog.oid) AS viewdef", tbinfo->dobj.catId.oid); - defres = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); - - if (PQntuples(defres) != 1) { - if (PQntuples(defres) < 1) - exit_horribly(NULL, "query to obtain definition of view %s returned no data\n", fmtId(tbinfo->dobj.name)); - else - exit_horribly(NULL, - "query to obtain definition of view %s returned more than one definition\n", - fmtId(tbinfo->dobj.name)); - } - - viewdef = PQgetvalue(defres, 0, 0); - - if (strlen(viewdef) == 0) { - exit_horribly(NULL, "definition of view %s appears to be empty (length zero)\n", fmtId(tbinfo->dobj.name)); - } + char *schemainfo = NULL; + PGresult *schemares = NULL; + PQExpBuffer result; /* Fetch views'schema info */ resetPQExpBuffer(query); @@ -15774,13 +15960,15 @@ static void dumpViewSchema( appendPQExpBuffer(q, "CREATE VIEW %s(%s)", fmtId(tbinfo->dobj.name), schemainfo); if ((tbinfo->reloptions != NULL) && strlen(tbinfo->reloptions) > 0) appendPQExpBuffer(q, " WITH (%s)", tbinfo->reloptions); - appendPQExpBuffer(q, " AS\n %s\n", viewdef); + result = createViewAsClause(fout, tbinfo); + appendPQExpBuffer(q, " AS\n %s;\n", result->data); + destroyPQExpBuffer(result); appendPQExpBuffer(labelq, "VIEW %s", fmtId(tbinfo->dobj.name)); - PQclear(defres); PQclear(schemares); } + /* * dumpTableSchema * write the declaration (not data) of one user-defined table or view @@ -15799,6 +15987,7 @@ static void dumpTableSchema(Archive* fout, TableInfo* tbinfo) char* storage = NULL; char* srvname = NULL; char* ftoptions = NULL; + char* ft_write_only_str = NULL; char** ft_frmt_clmn = NULL; /* formatter columns */ char* formatter = NULL; int cnt_ft_frmt_clmns = 0; /* no of formatter columns */ @@ -15830,90 +16019,96 @@ static void dumpTableSchema(Archive* fout, TableInfo* tbinfo) reltypename = "VIEW"; dumpViewSchema(fout, tbinfo, query, q, delq, labelq); } else { + switch (tbinfo->relkind) { + case RELKIND_FOREIGN_TABLE: + int i_srvname; + int i_ftoptions; + int i_ftwriteonly; - if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) { - int i_srvname; - int i_ftoptions; - int i_ftwriteonly; - char* ft_write_only_str = NULL; + reltypename = "FOREIGN TABLE"; - reltypename = "FOREIGN TABLE"; - - /* retrieve name of foreign server and generic options */ - appendPQExpBuffer(query, - "SELECT fs.srvname, " - "pg_catalog.array_to_string(ARRAY(" - "SELECT pg_catalog.quote_ident(option_name) || " - "' ' || pg_catalog.quote_literal(option_value) " - "FROM pg_catalog.pg_options_to_table(ftoptions) " - "ORDER BY option_name" - "), E',\n ') AS ftoptions, ft.ftwriteonly ftwriteonly " - "FROM pg_catalog.pg_foreign_table ft " - "JOIN pg_catalog.pg_foreign_server fs " - "ON (fs.oid = ft.ftserver) " - "WHERE ft.ftrelid = '%u'", - tbinfo->dobj.catId.oid); - res = ExecuteSqlQueryForSingleRow(fout, query->data); - i_srvname = PQfnumber(res, "srvname"); - i_ftoptions = PQfnumber(res, "ftoptions"); - i_ftwriteonly = PQfnumber(res, "ftwriteonly"); - srvname = gs_strdup(PQgetvalue(res, 0, i_srvname)); - ftoptions = gs_strdup(PQgetvalue(res, 0, i_ftoptions)); - ft_write_only_str = PQgetvalue(res, 0, i_ftwriteonly); - if (('t' == ft_write_only_str[0]) || ('1' == ft_write_only_str[0]) || - (('o' == ft_write_only_str[0]) && ('n' == ft_write_only_str[1]))) { - ft_write_only = true; - } - - PQclear(res); - - if (binary_upgrade && ((ftoptions != NULL) && ftoptions[0]) && (NULL != strstr(ftoptions, "error_table"))) { - binary_upgrade_set_error_table_oids(fout, q, tbinfo); - } - - if (((ftoptions != NULL) && ftoptions[0]) && (NULL != strstr(ftoptions, "formatter"))) { - int i_formatter = 0; - char* temp_iter = NULL; - int iterf = 0; - resetPQExpBuffer(query); + /* retrieve name of foreign server and generic options */ appendPQExpBuffer(query, - "WITH ft_options AS " - "(SELECT (pg_catalog.pg_options_to_table(ft.ftoptions)).option_name, " - "(pg_catalog.pg_options_to_table(ft.ftoptions)).option_value " + "SELECT fs.srvname, " + "pg_catalog.array_to_string(ARRAY(" + "SELECT pg_catalog.quote_ident(option_name) || " + "' ' || pg_catalog.quote_literal(option_value) " + "FROM pg_catalog.pg_options_to_table(ftoptions) " + "ORDER BY option_name" + "), E',\n ') AS ftoptions, ft.ftwriteonly ftwriteonly " "FROM pg_catalog.pg_foreign_table ft " - "WHERE ft.ftrelid = %u) " - "SELECT option_value FROM ft_options WHERE option_name = 'formatter'", + "JOIN pg_catalog.pg_foreign_server fs " + "ON (fs.oid = ft.ftserver) " + "WHERE ft.ftrelid = '%u'", tbinfo->dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); - i_formatter = PQfnumber(res, "option_value"); - formatter = gs_strdup(PQgetvalue(res, 0, i_formatter)); - PQclear(res); - temp_iter = formatter; - cnt_ft_frmt_clmns = 1; - while (*temp_iter != '\0') { - if ('.' == *temp_iter) - cnt_ft_frmt_clmns++; - temp_iter++; + i_srvname = PQfnumber(res, "srvname"); + i_ftoptions = PQfnumber(res, "ftoptions"); + i_ftwriteonly = PQfnumber(res, "ftwriteonly"); + srvname = gs_strdup(PQgetvalue(res, 0, i_srvname)); + ftoptions = gs_strdup(PQgetvalue(res, 0, i_ftoptions)); + ft_write_only_str = PQgetvalue(res, 0, i_ftwriteonly); + if (('t' == ft_write_only_str[0]) || ('1' == ft_write_only_str[0]) || + (('o' == ft_write_only_str[0]) && ('n' == ft_write_only_str[1]))) { + ft_write_only = true; } - ft_frmt_clmn = (char**)pg_malloc(cnt_ft_frmt_clmns * sizeof(char*)); - iterf = 0; - temp_iter = formatter; - while (*temp_iter != '\0') { - ft_frmt_clmn[iterf] = temp_iter; - while (*temp_iter && '.' != *temp_iter) - temp_iter++; - if ('.' == *temp_iter) { - iterf++; - *temp_iter = '\0'; + PQclear(res); + + if (binary_upgrade && ((ftoptions != NULL) && ftoptions[0]) && + (NULL != strstr(ftoptions, "error_table"))) { + binary_upgrade_set_error_table_oids(fout, q, tbinfo); + } + + if (((ftoptions != NULL) && ftoptions[0]) && (NULL != strstr(ftoptions, "formatter"))) { + int i_formatter = 0; + char *temp_iter = NULL; + int iterf = 0; + resetPQExpBuffer(query); + appendPQExpBuffer(query, + "WITH ft_options AS " + "(SELECT (pg_catalog.pg_options_to_table(ft.ftoptions)).option_name, " + "(pg_catalog.pg_options_to_table(ft.ftoptions)).option_value " + "FROM pg_catalog.pg_foreign_table ft " + "WHERE ft.ftrelid = %u) " + "SELECT option_value FROM ft_options WHERE option_name = 'formatter'", + tbinfo->dobj.catId.oid); + res = ExecuteSqlQueryForSingleRow(fout, query->data); + i_formatter = PQfnumber(res, "option_value"); + formatter = gs_strdup(PQgetvalue(res, 0, i_formatter)); + PQclear(res); + temp_iter = formatter; + cnt_ft_frmt_clmns = 1; + while (*temp_iter != '\0') { + if ('.' == *temp_iter) + cnt_ft_frmt_clmns++; temp_iter++; } + + ft_frmt_clmn = (char**)pg_malloc(cnt_ft_frmt_clmns * sizeof(char*)); + iterf = 0; + temp_iter = formatter; + while (*temp_iter != '\0') { + ft_frmt_clmn[iterf] = temp_iter; + while (*temp_iter && '.' != *temp_iter) + temp_iter++; + if ('.' == *temp_iter) { + iterf++; + *temp_iter = '\0'; + temp_iter++; + } + } } - } - } else { - reltypename = "TABLE"; - srvname = NULL; - ftoptions = NULL; + break; + case RELKIND_MATVIEW: + reltypename = "MATERIALIZED VIEW"; + srvname = NULL; + ftoptions = NULL; + break; + default: + reltypename = "TABLE"; + srvname = NULL; + ftoptions = NULL; } if ((NULL != tbinfo->reloptions) && ((NULL != strstr(tbinfo->reloptions, "orientation=column")) || @@ -16006,8 +16201,7 @@ static void dumpTableSchema(Archive* fout, TableInfo* tbinfo) * No applications will dump data from DN which is doing expansion. * So, if tbinfo->relbucket is 1, just skip dumpping pg_hashbucket. */ - if (enableHashbucket == true && fout->getHashbucketInfo == false && tbinfo->relbucket != (Oid)1) - { + if (enableHashbucket == true && fout->getHashbucketInfo == false && tbinfo->relbucket != (Oid)1) { PQExpBuffer getHashbucketQuery = createPQExpBuffer(); if (tbinfo->relbucket == (Oid)-1) @@ -16074,146 +16268,148 @@ static void dumpTableSchema(Archive* fout, TableInfo* tbinfo) } /* Dump the attributes */ - actual_atts = 0; - for (j = 0; j < attrNums; j++) { - /* - * Normally, dump if it's locally defined in this table, and not - * dropped. But for binary upgrade, we'll dump all the columns, - * and then fix up the dropped and nonlocal cases below. - */ - if (shouldPrintColumn(tbinfo, j)) { + if (tbinfo->relkind != RELKIND_MATVIEW) { + actual_atts = 0; + for (j = 0; j < attrNums; j++) { /* - * Default value --- suppress if to be printed separately. + * Normally, dump if it's locally defined in this table, and not + * dropped. But for binary upgrade, we'll dump all the columns, + * and then fix up the dropped and nonlocal cases below. */ - bool has_default = (tbinfo->attrdefs[j] != NULL && !tbinfo->attrdefs[j]->separate); - - /* - * Not Null constraint --- suppress if inherited, except in - * binary-upgrade case where that won't work. - */ - bool has_notnull = (tbinfo->notnull[j] && (!tbinfo->inhNotNull[j] || binary_upgrade)); - - /* Skip column if fully defined by reloftype */ - if ((tbinfo->reloftype != NULL) && !has_default && !has_notnull && !binary_upgrade) - continue; - - /* Format properly if not first attr */ - if (actual_atts == 0) - appendPQExpBuffer(q, " ("); - else - appendPQExpBuffer(q, ","); - appendPQExpBuffer(q, "\n "); - actual_atts++; - - /* Attribute name */ - appendPQExpBuffer(q, "%s ", fmtId(tbinfo->attnames[j])); - - if (tbinfo->attisdropped[j]) { + if (shouldPrintColumn(tbinfo, j)) { /* - * ALTER TABLE DROP COLUMN clears pg_attribute.atttypid, - * so we will not have gotten a valid type name; insert - * INTEGER as a stopgap. We'll clean things up later. + * Default value --- suppress if to be printed separately. */ - appendPQExpBuffer(q, "INTEGER /* dummy */"); - /* Skip all the rest, too */ - continue; - } + bool has_default = (tbinfo->attrdefs[j] != NULL && !tbinfo->attrdefs[j]->separate); - /* Attribute type */ - if ((tbinfo->reloftype != NULL) && !binary_upgrade) { - appendPQExpBuffer(q, "WITH OPTIONS"); - } else if (fout->remoteVersion >= 70100) { - appendPQExpBuffer(q, "%s", tbinfo->atttypnames[j]); - } else { - /* If no format_type, fake it */ - name = myFormatType(tbinfo->atttypnames[j], tbinfo->atttypmod[j]); - appendPQExpBuffer(q, "%s", name); - GS_FREE(name); - } + /* + * Not Null constraint --- suppress if inherited, except in + * binary-upgrade case where that won't work. + */ + bool has_notnull = (tbinfo->notnull[j] && (!tbinfo->inhNotNull[j] || binary_upgrade)); - /* Add collation if not default for the type */ - if (OidIsValid(tbinfo->attcollation[j])) { - CollInfo* coll = NULL; + /* Skip column if fully defined by reloftype */ + if ((tbinfo->reloftype != NULL) && !has_default && !has_notnull && !binary_upgrade) + continue; - coll = findCollationByOid(tbinfo->attcollation[j]); - if (NULL != coll) { - /* always schema-qualify, don't try to be smart */ - appendPQExpBuffer(q, " COLLATE %s.", fmtId(coll->dobj.nmspace->dobj.name)); - appendPQExpBuffer(q, "%s", fmtId(coll->dobj.name)); + /* Format properly if not first attr */ + if (actual_atts == 0) + appendPQExpBuffer(q, " ("); + else + appendPQExpBuffer(q, ","); + appendPQExpBuffer(q, "\n "); + actual_atts++; + + /* Attribute name */ + appendPQExpBuffer(q, "%s ", fmtId(tbinfo->attnames[j])); + + if (tbinfo->attisdropped[j]) { + /* + * ALTER TABLE DROP COLUMN clears pg_attribute.atttypid, + * so we will not have gotten a valid type name; insert + * INTEGER as a stopgap. We'll clean things up later. + */ + appendPQExpBuffer(q, "INTEGER /* dummy */"); + /* Skip all the rest, too */ + continue; } - } - if (NULL != formatter) { - int iter = 0; - for (iter = 0; iter < cnt_ft_frmt_clmns; iter++) { - if ((0 == strncmp(tbinfo->attnames[j], ft_frmt_clmn[iter], strlen(tbinfo->attnames[j]))) && - ('(' == ft_frmt_clmn[iter][strlen(tbinfo->attnames[j])])) { - appendPQExpBuffer(q, " position%s", &ft_frmt_clmn[iter][strlen(tbinfo->attnames[j])]); + /* Attribute type */ + if ((tbinfo->reloftype != NULL) && !binary_upgrade) { + appendPQExpBuffer(q, "WITH OPTIONS"); + } else if (fout->remoteVersion >= 70100) { + appendPQExpBuffer(q, "%s", tbinfo->atttypnames[j]); + } else { + /* If no format_type, fake it */ + name = myFormatType(tbinfo->atttypnames[j], tbinfo->atttypmod[j]); + appendPQExpBuffer(q, "%s", name); + GS_FREE(name); + } + + /* Add collation if not default for the type */ + if (OidIsValid(tbinfo->attcollation[j])) { + CollInfo *coll = NULL; + + coll = findCollationByOid(tbinfo->attcollation[j]); + if (NULL != coll) { + /* always schema-qualify, don't try to be smart */ + appendPQExpBuffer(q, " COLLATE %s.", fmtId(coll->dobj.nmspace->dobj.name)); + appendPQExpBuffer(q, "%s", fmtId(coll->dobj.name)); } } + + if (NULL != formatter) { + int iter = 0; + for (iter = 0; iter < cnt_ft_frmt_clmns; iter++) { + if ((0 == strncmp(tbinfo->attnames[j], ft_frmt_clmn[iter], strlen(tbinfo->attnames[j]))) && + ('(' == ft_frmt_clmn[iter][strlen(tbinfo->attnames[j])])) { + appendPQExpBuffer(q, " position%s", &ft_frmt_clmn[iter][strlen(tbinfo->attnames[j])]); + } + } + } + + if (has_default) + appendPQExpBuffer(q, " DEFAULT %s", tbinfo->attrdefs[j]->adef_expr); + + if (has_notnull) + appendPQExpBuffer(q, " NOT NULL"); } - - if (has_default) - appendPQExpBuffer(q, " DEFAULT %s", tbinfo->attrdefs[j]->adef_expr); - - if (has_notnull) - appendPQExpBuffer(q, " NOT NULL"); } - } - /* - * 1) do not use --include-alter-table - * 2) the SQL command not need to be modified - */ - if (!include_alter_table || !isChangeCreateSQL) { /* - * Add non-inherited CHECK constraints, if any. + * 1) do not use --include-alter-table + * 2) the SQL command not need to be modified */ - for (j = 0; j < tbinfo->ncheck; j++) { - ConstraintInfo* constr = &(tbinfo->checkexprs[j]); + if (!include_alter_table || !isChangeCreateSQL) { + /* + * Add non-inherited CHECK constraints, if any. + */ + for (j = 0; j < tbinfo->ncheck; j++) { + ConstraintInfo *constr = &(tbinfo->checkexprs[j]); - if (constr->separate || !constr->conislocal) - continue; + if (constr->separate || !constr->conislocal) { + continue; + } - if (actual_atts == 0) - appendPQExpBuffer(q, " (\n "); - else - appendPQExpBuffer(q, ",\n "); + if (actual_atts == 0) + appendPQExpBuffer(q, " (\n "); + else + appendPQExpBuffer(q, ",\n "); - appendPQExpBuffer(q, "CONSTRAINT %s ", fmtId(constr->dobj.name)); - appendPQExpBuffer(q, "%s", constr->condef); + appendPQExpBuffer(q, "CONSTRAINT %s ", fmtId(constr->dobj.name)); + appendPQExpBuffer(q, "%s", constr->condef); - actual_atts++; + actual_atts++; + } } - } - if (actual_atts) { - appendPQExpBuffer(q, "\n)"); - } else if (!((tbinfo->reloftype != NULL) && !binary_upgrade)) { - /* - * We must have a parenthesized attribute list, even though empty, - * when not using the OF TYPE syntax. - */ - appendPQExpBuffer(q, " (\n)"); - } - - if (numParents > 0 && !binary_upgrade) { - appendPQExpBuffer(q, "\nINHERITS ("); - for (k = 0; k < numParents; k++) { - TableInfo* parentRel = parents[k]; - - if (k > 0) - appendPQExpBuffer(q, ", "); - if (parentRel->dobj.nmspace != tbinfo->dobj.nmspace) - appendPQExpBuffer(q, "%s.", fmtId(parentRel->dobj.nmspace->dobj.name)); - appendPQExpBuffer(q, "%s", fmtId(parentRel->dobj.name)); + if (actual_atts) { + appendPQExpBuffer(q, "\n)"); + } else if (!((tbinfo->reloftype != NULL) && !binary_upgrade)) { + /* + * We must have a parenthesized attribute list, even though empty, + * when not using the OF TYPE syntax. + */ + appendPQExpBuffer(q, " (\n)"); } - appendPQExpBuffer(q, ")"); + + if (numParents > 0 && !binary_upgrade) { + appendPQExpBuffer(q, "\nINHERITS ("); + for (k = 0; k < numParents; k++) { + TableInfo *parentRel = parents[k]; + + if (k > 0) + appendPQExpBuffer(q, ", "); + if (parentRel->dobj.nmspace != tbinfo->dobj.nmspace) + appendPQExpBuffer(q, "%s.", fmtId(parentRel->dobj.nmspace->dobj.name)); + appendPQExpBuffer(q, "%s", fmtId(parentRel->dobj.name)); + } + appendPQExpBuffer(q, ")"); + } + + if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) + appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname)); } - - if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) - appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname)); - if (((tbinfo->reloptions != NULL) && strlen(tbinfo->reloptions) > 0) || ((tbinfo->toast_reloptions != NULL) && strlen(tbinfo->toast_reloptions) > 0)) { bool addcomma = false; @@ -16545,6 +16741,18 @@ static void dumpTableSchema(Archive* fout, TableInfo* tbinfo) } #endif + /* + * For materialized views, create the AS clause just like a view. + * At this point, we always mark the view as not populated. + */ + if (tbinfo->relkind == RELKIND_MATVIEW) + { + PQExpBuffer result; + result = createViewAsClause(fout, tbinfo); + appendPQExpBuffer(q, " AS\n%s\n WITH NO DATA;\n", result->data); + destroyPQExpBuffer(result); + } + appendPQExpBuffer(q, ";\n"); if (include_alter_table && isChangeCreateSQL) { @@ -16981,7 +17189,7 @@ static void dumpTableSchema(Archive* fout, TableInfo* tbinfo) tbinfo->rolname, (strcmp(reltypename, "TABLE") == 0) ? tbinfo->hasoids : false, reltypename, - SECTION_PRE_DATA, + tbinfo->postponed_def ? SECTION_POST_DATA : SECTION_PRE_DATA, q->data, delq->data, NULL, @@ -18970,6 +19178,7 @@ static void addBoundaryDependencies(DumpableObject** dobjs, int numObjs, Dumpabl addObjectDependency(postDataBound, dobj->dumpId); break; case DO_INDEX: + case DO_REFRESH_MATVIEW: case DO_TRIGGER: case DO_DEFAULT_ACL: case DO_RLSPOLICY: diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 1bc1c8ac1..0599d06f7 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -88,7 +88,8 @@ typedef enum { DO_PRE_DATA_BOUNDARY, DO_POST_DATA_BOUNDARY, DO_FTBL_CONSTRAINT, /* dump informational constraint info of the HDFS foreign table */ - DO_RLSPOLICY /* dump row level security policy of table */ + DO_RLSPOLICY, /* dump row level security policy of table */ + DO_REFRESH_MATVIEW } DumpableObjectType; typedef struct _dumpableObject { @@ -201,11 +202,12 @@ typedef struct _tableInfo { char relkind; int1 relcmprs; /* row compression attribution */ char relpersistence; /* relation persistence */ + bool relispopulated; /* relation is populated */ char relreplident; /* replica identifier */ char* reltablespace; /* relation tablespace */ char* reloptions; /* options specified by WITH (...) */ Oid relbucket; /* relation bucket OID */ - char* toast_reloptions; /* ditto, for the TOAST table */ + char* toast_reloptions; /* WITH options for the TOAST table */ bool hasindex; /* does it have any indexes? */ bool hasrules; /* does it have any rules? */ bool hastriggers; /* does it have any triggers? */ @@ -225,6 +227,7 @@ typedef struct _tableInfo { bool relrowmovement; bool interesting; /* true if need to collect more data */ + bool postponed_def; /* matview must be postponed into post-data */ #ifdef PGXC /* PGXC table locator Data */ diff --git a/src/bin/pg_dump/pg_dump_sort.cpp b/src/bin/pg_dump/pg_dump_sort.cpp index 5af5c7579..8825073eb 100644 --- a/src/bin/pg_dump/pg_dump_sort.cpp +++ b/src/bin/pg_dump/pg_dump_sort.cpp @@ -15,6 +15,7 @@ */ #include "pg_backup_archiver.h" #include "dumpmem.h" +#include "catalog/pg_class.h" #ifdef GAUSS_SFT_TEST #include "gauss_sft.h" @@ -29,8 +30,9 @@ static const char* modulename = gettext_noop("sorter"); * Objects are sorted by priority levels, and within an equal priority level * by OID. (This is a relatively crude hack to provide semi-reasonable * behavior for old databases without full dependency info.) Note: collations, - * extensions, text search, foreign-data, and default ACL objects can't really - * happen here, so the rather bogus priorities for them don't matter. + * extensions, text search, foreign-data, materialized view, event trigger, + * and default ACL objects can't really happen here, so the rather bogus + * priorities for them don't matter. * * NOTE: object-type priorities must match the section assignments made in * pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY, @@ -70,7 +72,8 @@ static const int oldObjectTypePriority[] = { 9, /* DO_BLOB */ 12, /* DO_BLOB_DATA */ 10, /* DO_PRE_DATA_BOUNDARY */ - 13 /* DO_POST_DATA_BOUNDARY */ + 13, /* DO_POST_DATA_BOUNDARY */ + 15 /* DO_REFRESH_MATVIEW */ }; /* @@ -116,7 +119,8 @@ static const int newObjectTypePriority[] = { 24, /* DO_BLOB_DATA */ 22, /* DO_PRE_DATA_BOUNDARY */ 25, /* DO_POST_DATA_BOUNDARY */ - 31 /* DO_FTBL_CONSTRAINT */ + 31, /* DO_FTBL_CONSTRAINT */ + 33 /* DO_REFRESH_MATVIEW */ }; static DumpId postDataBoundId; @@ -654,6 +658,7 @@ static void repairTypeFuncLoop(DumpableObject* typeobj, DumpableObject* funcobj) * will be an implicit dependency in the other direction, we need to break * the loop. If there are no other objects in the loop then we can remove * the implicit dependency and leave the ON SELECT rule non-separate. + * This applies to matviews, as well. */ static void repairViewRuleLoop(DumpableObject* viewobj, DumpableObject* ruleobj) { @@ -668,7 +673,9 @@ static void repairViewRuleLoop(DumpableObject* viewobj, DumpableObject* ruleobj) * Because findLoop() finds shorter cycles before longer ones, it's likely * that we will have previously fired repairViewRuleLoop() and removed the * rule's dependency on the view. Put it back to ensure the rule won't be - * emitted before the view... + * emitted before the view. + * + * Note: this approach does *not* work for matviews, at the moment. */ static void repairViewRuleMultiLoop(DumpableObject* viewobj, DumpableObject* ruleobj) { @@ -692,6 +699,34 @@ static void repairViewRuleMultiLoop(DumpableObject* viewobj, DumpableObject* rul addObjectDependency(ruleobj, postDataBoundId); } +/* + * If a matview is involved in a multi-object loop, we can't currently fix + * that by splitting off the rule. As a stopgap, we try to fix it by + * dropping the constraint that the matview be dumped in the pre-data section. + * This is sufficient to handle cases where a matview depends on some unique + * index, as can happen if it has a GROUP BY for example. + * + * Note that the "next object" is not necessarily the matview itself; + * it could be the matview's rowtype, for example. We may come through here + * several times while removing all the pre-data linkages. In particular, + * if there are other matviews that depend on the one with the circularity + * problem, we'll come through here for each such matview and mark them all + * as postponed. (This works because all MVs have pre-data dependencies + * to begin with, so each of them will get visited.) + */ +static void repairMatViewBoundaryMultiLoop(DumpableObject* boundaryobj, DumpableObject* nextobj) +{ + /* remove boundary's dependency on object after it in loop */ + removeObjectDependency(boundaryobj, nextobj->dumpId); + /* if that object is a matview, mark it as postponed into post-data */ + if (nextobj->objType == DO_TABLE) { + TableInfo* nextinfo = (TableInfo*)nextobj; + + if (nextinfo->relkind == RELKIND_MATVIEW) + nextinfo->postponed_def = true; + } +} + /* * Because we make tables depend on their CHECK constraints, while there * will be an automatic dependency in the other direction, we need to break @@ -791,24 +826,29 @@ static bool repairDependencyViewLoops(DumpableObject** loop, int nLoop) int i = 0; int j = 0; - /* View and its ON SELECT rule */ + /* View (including matview) and its ON SELECT rule */ if (nLoop == 2 && loop[0]->objType == DO_TABLE && loop[1]->objType == DO_RULE && + (((TableInfo*)loop[0])->relkind == 'v' || /* RELKIND_VIEW */ + ((TableInfo*)loop[0])->relkind == 'm') && /* RELKIND_MATVIEW */ ((RuleInfo*)loop[1])->ev_type == '1' && ((RuleInfo*)loop[1])->is_instead && ((RuleInfo*)loop[1])->ruletable == (TableInfo*)loop[0]) { repairViewRuleLoop(loop[0], loop[1]); return true; } if (nLoop == 2 && loop[1]->objType == DO_TABLE && loop[0]->objType == DO_RULE && + (((TableInfo*)loop[1])->relkind == 'v' || /* RELKIND_VIEW */ + ((TableInfo*)loop[1])->relkind == 'm') && /* RELKIND_MATVIEW */ ((RuleInfo*)loop[0])->ev_type == '1' && ((RuleInfo*)loop[0])->is_instead && ((RuleInfo*)loop[0])->ruletable == (TableInfo*)loop[1]) { repairViewRuleLoop(loop[1], loop[0]); return true; } - /* Indirect loop involving view and ON SELECT rule */ + /* Indirect loop involving view (but not matview) and ON SELECT rule */ if (nLoop > 2) { for (i = 0; i < nLoop; i++) { - if (loop[i]->objType == DO_TABLE) { + /* RELKIND_VIEW */ + if (loop[i]->objType == DO_TABLE && ((TableInfo*)loop[i])->relkind == 'v') { for (j = 0; j < nLoop; j++) { if (loop[j]->objType == DO_RULE && ((RuleInfo*)loop[j])->ev_type == '1' && ((RuleInfo*)loop[j])->is_instead && ((RuleInfo*)loop[j])->ruletable == (TableInfo*)loop[i]) { @@ -825,7 +865,21 @@ static bool repairDependencyTblChkConstraintLoops(DumpableObject** loop, int nLo { int i = 0; int j = 0; - + /* Indirect loop involving matview and data boundary */ + if (nLoop > 2) { + for (i = 0; i < nLoop; i++) { + if (loop[i]->objType == DO_TABLE && ((TableInfo*)loop[i])->relkind == 'm') { /* RELKIND_MATVIEW */ + for (j = 0; j < nLoop; j++) { + if (loop[j]->objType == DO_PRE_DATA_BOUNDARY) { + DumpableObject* nextobj; + nextobj = (j < nLoop - 1) ? loop[j + 1] : loop[0]; + repairMatViewBoundaryMultiLoop(loop[j], nextobj); + return true; + } + } + } + } + } /* Table and CHECK constraint */ if (nLoop == 2 && loop[0]->objType == DO_TABLE && loop[1]->objType == DO_CONSTRAINT && ((ConstraintInfo*)loop[1])->contype == 'c' && ((ConstraintInfo*)loop[1])->contable == (TableInfo*)loop[0]) { @@ -1072,6 +1126,11 @@ static void describeDumpableObject(DumpableObject* obj, char* buf, int bufsize) buf, bufsize, bufsize - 1, "INDEX %s (ID %d OID %u)", obj->name, obj->dumpId, obj->catId.oid); securec_check_ss_c(nRet, "\0", "\0"); return; + case DO_REFRESH_MATVIEW: + nRet = snprintf( + buf, bufsize, "REFRESH MATERIALIZED VIEW %s (ID %d OID %u)", obj->name, obj->dumpId, obj->catId.oid); + securec_check_ss_c(nRet, "\0", "\0"); + return; case DO_RULE: nRet = snprintf_s( buf, bufsize, bufsize - 1, "RULE %s (ID %d OID %u)", obj->name, obj->dumpId, obj->catId.oid); diff --git a/src/bin/psql/command.cpp b/src/bin/psql/command.cpp index 602eb5d19..3dde5adfb 100644 --- a/src/bin/psql/command.cpp +++ b/src/bin/psql/command.cpp @@ -383,7 +383,7 @@ static backslashResult exec_command(const char* cmd, PsqlScanState scan_state, P success = describeTableDetails(pattern, show_verbose, show_system); else /* standard listing of interesting things */ - success = listTables("tvsE", NULL, show_verbose, show_system); + success = listTables("tvmsE", NULL, show_verbose, show_system); break; case 'a': success = describeAggregates(pattern, show_verbose, show_system); @@ -449,6 +449,7 @@ static backslashResult exec_command(const char* cmd, PsqlScanState scan_state, P break; case 't': case 'v': + case 'm': case 'i': case 's': case 'E': diff --git a/src/bin/psql/describe.cpp b/src/bin/psql/describe.cpp index 5bcc49fe4..0548ac446 100755 --- a/src/bin/psql/describe.cpp +++ b/src/bin/psql/describe.cpp @@ -739,12 +739,15 @@ bool permissionsList(const char* pattern) printfPQExpBuffer(&buf, "SELECT n.nspname as \"%s\",\n" " c.relname as \"%s\",\n" - " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'S' THEN '%s' WHEN 'f' THEN '%s' END as \"%s\",\n" + " CASE c.relkind" + " WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'm' THEN '%s' WHEN 'S' THEN '%s' WHEN 'f' THEN '%s'" + " END as \"%s\",\n" " ", gettext_noop("Schema"), gettext_noop("Name"), gettext_noop("table"), gettext_noop("view"), + gettext_noop("materialized view"), gettext_noop("sequence"), gettext_noop("foreign table"), gettext_noop("Type")); @@ -763,7 +766,7 @@ bool permissionsList(const char* pattern) appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_class c\n" " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" - "WHERE c.relkind IN ('r', 'v', 'S', 'f')\n"); + "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n"); /* * Unless a schema pattern is specified, we suppress system and temp @@ -1429,7 +1432,7 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation * types, and foreign tables (c.f. CommentObject() in comment.c). */ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || tableinfo.relkind == 'f' || - tableinfo.relkind == 'c') + tableinfo.relkind == 'c' || tableinfo.relkind == 'm') appendPQExpBuffer(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)"); } @@ -1454,6 +1457,12 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation case 'v': printfPQExpBuffer(&title, _("View \"%s.%s\""), schemaname, relationname); break; + case 'm': + if (tableinfo.relpersistence == 'u') + printfPQExpBuffer(&title, _("Unlogged materialized view \"%s.%s\""), schemaname, relationname); + else + printfPQExpBuffer(&title, _("Materialized view \"%s.%s\""), schemaname, relationname); + break; case 'S': printfPQExpBuffer(&title, _("Sequence \"%s.%s\""), schemaname, relationname); break; @@ -1487,7 +1496,8 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation headers[1] = gettext_noop("Type"); cols = 2; - if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || tableinfo.relkind == 'f' || tableinfo.relkind == 'c') { + if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || tableinfo.relkind == 'f' || + tableinfo.relkind == 'c' || tableinfo.relkind == 'm') { show_modifiers = true; headers[cols++] = gettext_noop("Modifiers"); modifiers = (char**)pg_malloc_zero((unsigned long)(numrows + 1) * sizeof(*modifiers)); @@ -1504,11 +1514,11 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation if (verbose) { headers[cols++] = gettext_noop("Storage"); - if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f') + if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f' || tableinfo.relkind == 'm') headers[cols++] = gettext_noop("Stats target"); /* Column comments, if the relkind supports this feature. */ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || tableinfo.relkind == 'c' || - tableinfo.relkind == 'f') + tableinfo.relkind == 'f' || tableinfo.relkind == 'm') headers[cols++] = gettext_noop("Description"); } @@ -1518,8 +1528,8 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation for (i = 0; i < cols; i++) printTableAddHeader(&cont, headers[i], true, 'l'); - /* Check if table is a view */ - if (tableinfo.relkind == 'v' && verbose) { + /* Check if table is a view or materialized view */ + if ((tableinfo.relkind == 'v' || tableinfo.relkind == 'm') && verbose) { PGresult* result = NULL; printfPQExpBuffer(&buf, "SELECT pg_catalog.pg_get_viewdef('%s'::pg_catalog.oid, true);", oid); @@ -1598,13 +1608,13 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation false); /* Statistics target, if the relkind supports this feature */ - if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f') { + if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f' || tableinfo.relkind == 'm') { printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1), false, false); } /* Column comments, if the relkind supports this feature. */ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || tableinfo.relkind == 'c' || - tableinfo.relkind == 'f') + tableinfo.relkind == 'f' || tableinfo.relkind == 'm') printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2), false, false); } } @@ -1698,39 +1708,7 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation } PQclear(result); - } else if (view_def != NULL) { - PGresult* result = NULL; - /* Footer information about a view */ - printTableAddFooter(&cont, _("View definition:")); - printTableAddFooter(&cont, view_def); - - /* print rules */ - if (tableinfo.hasrules) { - printfPQExpBuffer(&buf, - "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true))\n" - "FROM pg_catalog.pg_rewrite r\n" - "WHERE r.ev_class = '%s' AND r.rulename != '_RETURN' ORDER BY 1;", - oid); - result = PSQLexec(buf.data, false); - if (result == NULL) - goto error_return; - - if (PQntuples(result) > 0) { - printTableAddFooter(&cont, _("Rules:")); - for (i = 0; i < PQntuples(result); i++) { - const char* ruledef = NULL; - - /* Everything after "CREATE RULE" is echoed verbatim */ - ruledef = PQgetvalue(result, i, 1); - ruledef += 12; - - printfPQExpBuffer(&buf, " %s", ruledef); - printTableAddFooter(&cont, buf.data); - } - } - PQclear(result); - } } else if (tableinfo.relkind == 'S') { /* Footer information about a sequence */ PGresult* result = NULL; @@ -1766,7 +1744,7 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation * don't print anything. */ PQclear(result); - } else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f') { + } else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f' || tableinfo.relkind == 'm') { /* Footer information about a table */ PGresult* result = NULL; int tuples = 0; @@ -2038,7 +2016,7 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation } /* print rules */ - if (tableinfo.hasrules) { + if (tableinfo.hasrules && tableinfo.relkind != 'm') { if (pset.sversion >= 80300) { printfPQExpBuffer(&buf, "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true)), " @@ -2128,6 +2106,39 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation } } + if (view_def != NULL) { + PGresult *result = NULL; + + /* Footer information about a view */ + printTableAddFooter(&cont, _("View definition:")); + printTableAddFooter(&cont, view_def); + + /* print rules */ + if (tableinfo.hasrules) { + printfPQExpBuffer(&buf, + "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true))\n" + "FROM pg_catalog.pg_rewrite r\n" + "WHERE r.ev_class = '%s' AND r.rulename != '_RETURN' ORDER BY 1;", + oid); + result = PSQLexec(buf.data, false); + if (result == NULL) + goto error_return; + if (PQntuples(result) > 0) { + printTableAddFooter(&cont, _("Rules:")); + for (i = 0; i < PQntuples(result); i++) { + const char *ruledef = NULL; + + /* Everything after "CREATE RULE" is echoed verbatim */ + ruledef = PQgetvalue(result, i, 1); + ruledef += 12; + printfPQExpBuffer(&buf, " %s", ruledef); + printTableAddFooter(&cont, buf.data); + } + } + PQclear(result); + } + } + /* * Print triggers next, if any (but only user-defined triggers). This * could apply to either a table or a view. @@ -2252,7 +2263,7 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation /* * Finish printing the footer information about a table. */ - if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f') { + if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f' || tableinfo.relkind == 'm') { PGresult* result = NULL; int tuples; @@ -2485,8 +2496,8 @@ static bool describeOneTableDetails(const char* schemaname, const char* relation printTableAddFooter(&cont, buf.data); } - /* OIDs, if verbose */ - if (verbose) { + /* OIDs, if verbose and not a materialized view */ + if (verbose && tableinfo.relkind != 'm') { const char* s = _("Has OIDs"); printfPQExpBuffer(&buf, "%s: %s", s, (tableinfo.hasoids ? _("yes") : _("no"))); @@ -2680,7 +2691,7 @@ error_return: static void add_tablespace_footer(printTableContent* const cont, char relkind, Oid tablespace, const bool newline) { /* relkinds for which we support tablespaces */ - if (relkind == 'r' || relkind == 'i') { + if (relkind == 'r' || relkind == 'm' || relkind == 'i') { /* * We ignore the database default tablespace so that users not using * tablespaces don't need to know about them. This case also covers @@ -2978,6 +2989,7 @@ bool listDbRoleSettings(const char* pattern, const char* pattern2) * t - tables * i - indexes * v - views + * m - materialized views * s - sequences * E - foreign table (Note: different from 'f', the relkind value) * (any order of the above is fine) @@ -2988,6 +3000,7 @@ bool listTables(const char* tabtypes, const char* pattern, bool verbose, bool sh bool showTables = strchr(tabtypes, 't') != NULL; bool showIndexes = strchr(tabtypes, 'i') != NULL; bool showViews = strchr(tabtypes, 'v') != NULL; + bool showMatViews = strchr(tabtypes, 'm') != NULL; bool showSeq = strchr(tabtypes, 's') != NULL; bool showForeign = strchr(tabtypes, 'E') != NULL; @@ -2997,8 +3010,8 @@ bool listTables(const char* tabtypes, const char* pattern, bool verbose, bool sh bool* translate_columns = NULL; int trues[] = {2}; - if (!(showTables || showIndexes || showViews || showSeq || showForeign)) { - showTables = showViews = showSeq = showForeign = true; + if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign)) { + showTables = showViews = showMatViews = showSeq = showForeign = true; } initPQExpBuffer(&buf); @@ -3010,13 +3023,21 @@ bool listTables(const char* tabtypes, const char* pattern, bool verbose, bool sh printfPQExpBuffer(&buf, "SELECT n.nspname as \"%s\",\n" " c.relname as \"%s\",\n" - " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' WHEN 's' THEN " - "'%s' WHEN 'f' THEN '%s' END as \"%s\",\n" + " CASE c.relkind" + " WHEN 'r' THEN '%s'" + " WHEN 'v' THEN '%s'" + " WHEN 'm' THEN '%s'" + " WHEN 'i' THEN '%s'" + " WHEN 'S' THEN '%s'" + " WHEN 's' THEN '%s'" + " WHEN 'f' THEN '%s'" + " END as \"%s\",\n" " pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"", gettext_noop("Schema"), gettext_noop("Name"), gettext_noop("table"), gettext_noop("view"), + gettext_noop("materialized view"), gettext_noop("index"), gettext_noop("sequence"), gettext_noop("special"), @@ -3062,6 +3083,8 @@ bool listTables(const char* tabtypes, const char* pattern, bool verbose, bool sh appendPQExpBuffer(&buf, "'r',"); if (showViews) appendPQExpBuffer(&buf, "'v',"); + if (showMatViews) + appendPQExpBuffer(&buf, "'m',"); if (showIndexes) appendPQExpBuffer(&buf, "'i',"); if (showSeq) diff --git a/src/bin/psql/help.cpp b/src/bin/psql/help.cpp index 50a5d6548..98c473ebd 100755 --- a/src/bin/psql/help.cpp +++ b/src/bin/psql/help.cpp @@ -257,6 +257,7 @@ void slashUsage(unsigned short int pager) fprintf(output, _(" \\di[S+] [PATTERN] list indexes\n")); fprintf(output, _(" \\dl list large objects, same as \\lo_list\n")); fprintf(output, _(" \\dL[S+] [PATTERN] list procedural languages\n")); + fprintf(output, _(" \\dm[S+] [PATTERN] list materialized views\n")); fprintf(output, _(" \\dn[S+] [PATTERN] list schemas\n")); fprintf(output, _(" \\do[S] [PATTERN] list operators\n")); fprintf(output, _(" \\dO[S+] [PATTERN] list collations\n")); diff --git a/src/bin/psql/tab-complete.cpp b/src/bin/psql/tab-complete.cpp index 1eb98ff3b..6b555f4f1 100644 --- a/src/bin/psql/tab-complete.cpp +++ b/src/bin/psql/tab-complete.cpp @@ -370,11 +370,11 @@ static const SchemaQuery Query_for_list_of_relations = { /* qualresult */ NULL}; -static const SchemaQuery Query_for_list_of_tsvf = { +static const SchemaQuery Query_for_list_of_tsvmf = { /* catname */ "pg_catalog.pg_class c", /* selcondition */ - "c.relkind IN ('r', 'S', 'v', 'f')", + "c.relkind IN ('r', 'S', 'v', 'm', 'f')", /* viscondition */ "pg_catalog.pg_table_is_visible(c.oid)", /* namespace */ @@ -384,11 +384,25 @@ static const SchemaQuery Query_for_list_of_tsvf = { /* qualresult */ NULL}; -static const SchemaQuery Query_for_list_of_tf = { +static const SchemaQuery Query_for_list_of_tmf = { /* catname */ "pg_catalog.pg_class c", /* selcondition */ - "c.relkind IN ('r', 'f')", + "c.relkind IN ('r', 'm', 'f')", + /* viscondition */ + "pg_catalog.pg_table_is_visible(c.oid)", + /* namespace */ + "c.relnamespace", + /* result */ + "pg_catalog.quote_ident(c.relname)", + /* qualresult */ + NULL}; + +static const SchemaQuery Query_for_list_of_tm = { + /* catname */ + "pg_catalog.pg_class c", + /* selcondition */ + "c.relkind IN ('r', 'm')", /* viscondition */ "pg_catalog.pg_table_is_visible(c.oid)", /* namespace */ @@ -412,6 +426,21 @@ static const SchemaQuery Query_for_list_of_views = { /* qualresult */ NULL}; +static const SchemaQuery Query_for_list_of_matviews = { + /* catname */ + "pg_catalog.pg_class c", + /* selcondition */ + "c.relkind IN ('m')", + /* viscondition */ + "pg_catalog.pg_table_is_visible(c.oid)", + /* namespace */ + "c.relnamespace", + /* result */ + "pg_catalog.quote_ident(c.relname)", + /* qualresult */ + NULL +}; + /* * Queries to get lists of names of various kinds of things, possibly * restricted to names matching a partially entered name. In these queries, @@ -657,6 +686,7 @@ static const pgsql_thing_t words_after_create[] = { {"GROUP", Query_for_list_of_roles, NULL, 0}, {"LANGUAGE", Query_for_list_of_languages, NULL, 0}, {"INDEX", NULL, &Query_for_list_of_indexes, 0}, + {"MATERIALIZED VIEW", NULL, NULL}, #ifdef PGXC {"NODE", Query_for_list_of_available_nodenames, NULL, 0}, {"NODE GROUP", Query_for_list_of_available_nodegroup_names, NULL, 0}, diff --git a/src/common/backend/catalog/aclchk.cpp b/src/common/backend/catalog/aclchk.cpp index 2f259eb5e..9a8fe1014 100755 --- a/src/common/backend/catalog/aclchk.cpp +++ b/src/common/backend/catalog/aclchk.cpp @@ -815,11 +815,13 @@ static List* objectsInSchemaToOids(GrantObjectType objtype, List* nspnames) switch (objtype) { case ACL_OBJECT_RELATION: - /* Process regular tables, views and foreign tables */ + /* Process regular tables, views, materialized views and foreign tables */ objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION); objects = list_concat(objects, objs); objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW); objects = list_concat(objects, objs); + objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW); + objects = list_concat(objects, objs); objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE); objects = list_concat(objects, objs); break; diff --git a/src/common/backend/catalog/builtin_funcs.ini b/src/common/backend/catalog/builtin_funcs.ini index 26f59aefd..4073bb3af 100644 --- a/src/common/backend/catalog/builtin_funcs.ini +++ b/src/common/backend/catalog/builtin_funcs.ini @@ -973,6 +973,10 @@ "btrecordcmp", 1, AddBuiltinFunc(_0(2987), _1("btrecordcmp"), _2(2), _3(true), _4(false), _5(btrecordcmp), _6(23), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 2249, 2249), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("btrecordcmp"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) ), + AddFuncGroup( + "btrecordimagecmp", 1, + AddBuiltinFunc(_0(3997), _1("btrecordimagecmp"), _2(2), _3(true), _4(false), _5(btrecordimagecmp), _6(23), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 2249, 2249), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("btrecordimagecmp"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) + ), AddFuncGroup( "btreltimecmp", 1, AddBuiltinFunc(_0(380), _1("btreltimecmp"), _2(2), _3(true), _4(false), _5(btreltimecmp), _6(23), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 703, 703), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("btreltimecmp"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) @@ -8004,6 +8008,30 @@ "record_gt", 1, AddBuiltinFunc(_0(2984), _1("record_gt"), _2(2), _3(true), _4(false), _5(record_gt), _6(16), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 2249, 2249), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("record_gt"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) ), + AddFuncGroup( + "record_image_eq", 1, + AddBuiltinFunc(_0(3991), _1("record_image_eq"), _2(2), _3(true), _4(false), _5(record_image_eq), _6(16), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 2249, 2249), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("record_image_eq"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) + ), + AddFuncGroup( + "record_image_ge", 1, + AddBuiltinFunc(_0(3996), _1("record_image_ge"), _2(2), _3(true), _4(false), _5(record_image_ge), _6(16), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 2249, 2249), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("record_image_ge"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) + ), + AddFuncGroup( + "record_image_gt", 1, + AddBuiltinFunc(_0(3994), _1("record_image_gt"), _2(2), _3(true), _4(false), _5(record_image_gt), _6(16), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 2249, 2249), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("record_image_gt"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) + ), + AddFuncGroup( + "record_image_le", 1, + AddBuiltinFunc(_0(3995), _1("record_image_le"), _2(2), _3(true), _4(false), _5(record_image_le), _6(16), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 2249, 2249), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("record_image_le"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) + ), + AddFuncGroup( + "record_image_lt", 1, + AddBuiltinFunc(_0(3993), _1("record_image_lt"), _2(2), _3(true), _4(false), _5(record_image_lt), _6(16), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 2249, 2249), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("record_image_lt"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) + ), + AddFuncGroup( + "record_image_ne", 1, + AddBuiltinFunc(_0(3992), _1("record_image_ne"), _2(2), _3(true), _4(false), _5(record_image_ne), _6(16), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('i'), _18(0), _19(2, 2249, 2249), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("record_image_ne"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) + ), AddFuncGroup( "record_in", 1, AddBuiltinFunc(_0(2290), _1("record_in"), _2(3), _3(true), _4(false), _5(record_in), _6(2249), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14('f'), _15(false), _16(false), _17('s'), _18(0), _19(3, 2275, 26, 23), _20(NULL), _21(NULL), _22(NULL), _23(NULL), _24("record_in"), _25(NULL), _26(NULL), _27(NULL), _28(0), _29(false), _30(NULL), _31(false)) diff --git a/src/common/backend/catalog/dependency.cpp b/src/common/backend/catalog/dependency.cpp index 15ccedd6e..b17d24b89 100755 --- a/src/common/backend/catalog/dependency.cpp +++ b/src/common/backend/catalog/dependency.cpp @@ -3034,6 +3034,9 @@ static void getRelationDescription(StringInfo buffer, Oid relid) case RELKIND_VIEW: appendStringInfo(buffer, _("view %s"), relname); break; + case RELKIND_MATVIEW: + appendStringInfo(buffer, _("materialized view %s"), relname); + break; case RELKIND_COMPOSITE_TYPE: appendStringInfo(buffer, _("composite type %s"), relname); break; diff --git a/src/common/backend/catalog/heap.cpp b/src/common/backend/catalog/heap.cpp index affa45047..eda2406ef 100755 --- a/src/common/backend/catalog/heap.cpp +++ b/src/common/backend/catalog/heap.cpp @@ -970,7 +970,7 @@ void InsertPgClassTuple( else nulls[Anum_pg_class_reloptions - 1] = true; - if (relkind == RELKIND_RELATION || relkind == RELKIND_TOASTVALUE) + if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || relkind == RELKIND_TOASTVALUE) values[Anum_pg_class_relfrozenxid64 - 1] = u_sess->utils_cxt.RecentXmin; else values[Anum_pg_class_relfrozenxid64 - 1] = InvalidTransactionId; @@ -1027,6 +1027,7 @@ static void AddNewRelationTuple(Relation pg_class_desc, Relation new_rel_desc, O switch (relkind) { case RELKIND_RELATION: + case RELKIND_MATVIEW: case RELKIND_INDEX: case RELKIND_TOASTVALUE: /* The relation is real, but as yet empty */ @@ -1049,7 +1050,7 @@ static void AddNewRelationTuple(Relation pg_class_desc, Relation new_rel_desc, O } /* Initialize relfrozenxid */ - if (relkind == RELKIND_RELATION || relkind == RELKIND_TOASTVALUE) { + if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || relkind == RELKIND_TOASTVALUE) { /* * Initialize to the minimum XID that could put tuples in the table. * We know that no xacts older than RecentXmin are still running, so @@ -1828,8 +1829,8 @@ Oid heap_create_with_catalog(const char* relname, Oid relnamespace, Oid reltable * supplied. */ if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_oid) && - (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW || - relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE)) { + (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW || + relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE)) { relid = u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_oid; u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_oid = InvalidOid; @@ -1879,6 +1880,7 @@ Oid heap_create_with_catalog(const char* relname, Oid relnamespace, Oid reltable switch (relkind) { case RELKIND_RELATION: case RELKIND_VIEW: + case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid, relnamespace); break; @@ -1957,13 +1959,12 @@ Oid heap_create_with_catalog(const char* relname, Oid relnamespace, Oid reltable /* * Decide whether to create an array type over the relation's rowtype. We * do not create any array types for system catalogs (ie, those made - * during initdb). We create array types for regular relations, views, - * composite types and foreign tables ... but not, eg, for toast tables or - * sequences. + * during initdb). We do not create them where the use of a relation as + * such is an implementation detail: toast tables, sequences and indexes. */ if (IsUnderPostmaster && !u_sess->attr.attr_common.IsInplaceUpgrade && - (relkind == RELKIND_RELATION || relkind == RELKIND_VIEW || relkind == RELKIND_FOREIGN_TABLE || - relkind == RELKIND_COMPOSITE_TYPE)) + (relkind == RELKIND_RELATION || relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW || + relkind == RELKIND_FOREIGN_TABLE || relkind == RELKIND_COMPOSITE_TYPE)) new_array_oid = AssignTypeArrayOid(); /* @@ -2203,7 +2204,7 @@ Oid heap_create_with_catalog(const char* relname, Oid relnamespace, Oid reltable register_on_commit_action(relid, oncommit); if (relpersistence == RELPERSISTENCE_UNLOGGED) { - Assert(relkind == RELKIND_RELATION || relkind == RELKIND_TOASTVALUE); + Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || relkind == RELKIND_TOASTVALUE); heap_create_init_fork(new_rel_desc); } diff --git a/src/common/backend/catalog/objectaddress.cpp b/src/common/backend/catalog/objectaddress.cpp index cc08d7423..96a41a5b7 100755 --- a/src/common/backend/catalog/objectaddress.cpp +++ b/src/common/backend/catalog/objectaddress.cpp @@ -186,6 +186,7 @@ ObjectAddress get_object_address( case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: + case OBJECT_MATVIEW: case OBJECT_FOREIGN_TABLE: address = get_relation_by_qualified_name(objtype, objname, &relation, lockmode, missing_ok); break; @@ -530,6 +531,12 @@ static ObjectAddress get_relation_by_qualified_name( (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a view", RelationGetRelationName(relation)))); break; + case OBJECT_MATVIEW: + if (relation->rd_rel->relkind != RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a materialized view", RelationGetRelationName(relation)))); + break; case OBJECT_FOREIGN_TABLE: if (relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, @@ -763,6 +770,7 @@ void check_object_ownership( case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: + case OBJECT_MATVIEW: case OBJECT_FOREIGN_TABLE: case OBJECT_COLUMN: case OBJECT_RULE: diff --git a/src/common/backend/catalog/system_views.sql b/src/common/backend/catalog/system_views.sql index e34f8f79e..a248f5ee5 100644 --- a/src/common/backend/catalog/system_views.sql +++ b/src/common/backend/catalog/system_views.sql @@ -169,6 +169,18 @@ CREATE VIEW pg_tables AS LEFT JOIN pg_object po ON (po.object_oid = C.oid and po.object_type = 'r') WHERE C.relkind = 'r'; +CREATE VIEW pg_matviews AS + SELECT + N.nspname AS schemaname, + C.relname AS matviewname, + pg_get_userbyid(C.relowner) AS matviewowner, + T.spcname AS tablespace, + C.relhasindex AS hasindexes, + pg_get_viewdef(C.oid) AS definition + FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) + LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace) + WHERE C.relkind = 'm'; + CREATE VIEW pg_indexes AS SELECT N.nspname AS schemaname, @@ -180,7 +192,7 @@ CREATE VIEW pg_indexes AS JOIN pg_class I ON (I.oid = X.indexrelid) LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) LEFT JOIN pg_tablespace T ON (T.oid = I.reltablespace) - WHERE C.relkind = 'r' AND I.relkind = 'i'; + WHERE C.relkind IN ('r', 'm') AND I.relkind = 'i'; -- For global temporary table CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS @@ -442,6 +454,7 @@ SELECT l.objoid, l.classoid, l.objsubid, CASE WHEN rel.relkind = 'r' THEN 'table'::text WHEN rel.relkind = 'v' THEN 'view'::text + WHEN rel.relkind = 'm' THEN 'materialized view'::text WHEN rel.relkind = 'S' THEN 'sequence'::text WHEN rel.relkind = 'f' THEN 'foreign table'::text END AS objtype, rel.relnamespace AS objnamespace, @@ -629,7 +642,7 @@ CREATE VIEW pg_stat_all_tables AS FROM pg_class C LEFT JOIN pg_index I ON C.oid = I.indrelid LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) - WHERE C.relkind IN ('r', 't') + WHERE C.relkind IN ('r', 't', 'm') GROUP BY C.oid, N.nspname, C.relname; CREATE VIEW pg_stat_xact_all_tables AS @@ -649,7 +662,7 @@ CREATE VIEW pg_stat_xact_all_tables AS FROM pg_class C LEFT JOIN pg_index I ON C.oid = I.indrelid LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) - WHERE C.relkind IN ('r', 't') + WHERE C.relkind IN ('r', 't', 'm') GROUP BY C.oid, N.nspname, C.relname; CREATE VIEW pg_stat_sys_tables AS @@ -694,7 +707,7 @@ CREATE VIEW pg_statio_all_tables AS pg_class T ON C.reltoastrelid = T.oid LEFT JOIN pg_class X ON T.reltoastidxid = X.oid LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) - WHERE C.relkind IN ('r', 't') + WHERE C.relkind IN ('r', 't', 'm') GROUP BY C.oid, N.nspname, C.relname, T.oid, X.oid; CREATE VIEW pg_statio_sys_tables AS @@ -721,7 +734,7 @@ CREATE VIEW pg_stat_all_indexes AS pg_index X ON C.oid = X.indrelid JOIN pg_class I ON I.oid = X.indexrelid LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) - WHERE C.relkind IN ('r', 't'); + WHERE C.relkind IN ('r', 't', 'm'); CREATE VIEW pg_stat_sys_indexes AS SELECT * FROM pg_stat_all_indexes @@ -747,7 +760,7 @@ CREATE VIEW pg_statio_all_indexes AS pg_index X ON C.oid = X.indrelid JOIN pg_class I ON I.oid = X.indexrelid LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) - WHERE C.relkind IN ('r', 't'); + WHERE C.relkind IN ('r', 't', 'm'); CREATE VIEW pg_statio_sys_indexes AS SELECT * FROM pg_statio_all_indexes diff --git a/src/common/backend/catalog/toasting.cpp b/src/common/backend/catalog/toasting.cpp index 4f5fb1c48..bb312e45f 100755 --- a/src/common/backend/catalog/toasting.cpp +++ b/src/common/backend/catalog/toasting.cpp @@ -96,8 +96,9 @@ void BootstrapToastTable(char* relName, Oid toastOid, Oid toastIndexOid) Relation rel; rel = heap_openrv(makeRangeVar(NULL, relName, -1), AccessExclusiveLock); - if (rel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", relName))); + if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table or materialized view", relName))); /* create_toast_table does all the work */ if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum)0, false, NULL)) @@ -106,6 +107,13 @@ void BootstrapToastTable(char* relName, Oid toastOid, Oid toastIndexOid) heap_close(rel, NoLock); } +void NewRelationCreateToastTable(Oid relOid, Datum reloptions) +{ + Relation rel; + rel = heap_open(relOid, AccessExclusiveLock); + (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, false, NULL); + heap_close(rel, NoLock); +} /* * create_toast_table --- internal workhorse diff --git a/src/common/backend/nodes/copyfuncs.cpp b/src/common/backend/nodes/copyfuncs.cpp index a15d12d3c..22ab1ce20 100644 --- a/src/common/backend/nodes/copyfuncs.cpp +++ b/src/common/backend/nodes/copyfuncs.cpp @@ -2181,6 +2181,7 @@ static IntoClause* _copyIntoClause(const IntoClause* from) COPY_SCALAR_FIELD(onCommit); COPY_SCALAR_FIELD(row_compress); COPY_STRING_FIELD(tableSpaceName); + COPY_NODE_FIELD(viewQuery); COPY_SCALAR_FIELD(skipData); #ifdef PGXC COPY_NODE_FIELD(distributeby); @@ -4800,11 +4801,22 @@ static CreateTableAsStmt* _copyCreateTableAsStmt(const CreateTableAsStmt* from) COPY_NODE_FIELD(query); COPY_NODE_FIELD(into); + COPY_SCALAR_FIELD(relkind); COPY_SCALAR_FIELD(is_select_into); return newnode; } +static RefreshMatViewStmt* _copyRefreshMatViewStmt(const RefreshMatViewStmt* from) +{ + RefreshMatViewStmt* newnode = makeNode(RefreshMatViewStmt); + + COPY_SCALAR_FIELD(skipData); + COPY_NODE_FIELD(relation); + + return newnode; +} + static CreateSeqStmt* _copyCreateSeqStmt(const CreateSeqStmt* from) { CreateSeqStmt* newnode = makeNode(CreateSeqStmt); @@ -6399,6 +6411,9 @@ void* copyObject(const void* from) case T_CreateTableAsStmt: retval = _copyCreateTableAsStmt((CreateTableAsStmt*)from); break; + case T_RefreshMatViewStmt: + retval = _copyRefreshMatViewStmt((RefreshMatViewStmt*)from); + break; case T_ReplicaIdentityStmt: retval = _copyReplicaIdentityStmt((ReplicaIdentityStmt*)from); break; diff --git a/src/common/backend/nodes/equalfuncs.cpp b/src/common/backend/nodes/equalfuncs.cpp index bbbf0f57d..41c52a839 100755 --- a/src/common/backend/nodes/equalfuncs.cpp +++ b/src/common/backend/nodes/equalfuncs.cpp @@ -120,6 +120,7 @@ static bool _equalIntoClause(const IntoClause* a, const IntoClause* b) COMPARE_SCALAR_FIELD(onCommit); COMPARE_SCALAR_FIELD(row_compress); COMPARE_STRING_FIELD(tableSpaceName); + COMPARE_NODE_FIELD(viewQuery); COMPARE_SCALAR_FIELD(skipData); return true; @@ -1554,11 +1555,21 @@ static bool _equalCreateTableAsStmt(const CreateTableAsStmt* a, const CreateTabl { COMPARE_NODE_FIELD(query); COMPARE_NODE_FIELD(into); + COMPARE_SCALAR_FIELD(relkind); COMPARE_SCALAR_FIELD(is_select_into); return true; } +static bool _equalRefreshMatViewStmt(const RefreshMatViewStmt* a, const RefreshMatViewStmt* b) +{ + COMPARE_SCALAR_FIELD(skipData); + COMPARE_NODE_FIELD(relation); + + return true; +} + + static bool _equalReplicaIdentityStmt(const ReplicaIdentityStmt* a, const ReplicaIdentityStmt* b) { COMPARE_SCALAR_FIELD(identity_type); @@ -3130,6 +3141,9 @@ bool equal(const void* a, const void* b) case T_CreateTableAsStmt: retval = _equalCreateTableAsStmt((CreateTableAsStmt*)a, (CreateTableAsStmt*)b); break; + case T_RefreshMatViewStmt: + retval = _equalRefreshMatViewStmt((RefreshMatViewStmt*)a, (RefreshMatViewStmt*)b); + break; case T_CreateSeqStmt: retval = _equalCreateSeqStmt((CreateSeqStmt*)a, (CreateSeqStmt*)b); break; diff --git a/src/common/backend/nodes/makefuncs.cpp b/src/common/backend/nodes/makefuncs.cpp index a14815dbb..452506302 100755 --- a/src/common/backend/nodes/makefuncs.cpp +++ b/src/common/backend/nodes/makefuncs.cpp @@ -617,7 +617,34 @@ Param* makeParam(ParamKind paramkind, int paramid, Oid paramtype, int32 paramtyp } /* - * makeIndexInfo + * makeColumnDef - + * build a ColumnDef node to represent a simple column definition. + * + * Type and collation are specified by OID. + * Other properties are all basic to start with. + */ +ColumnDef* makeColumnDef(const char* colname, Oid typeOid, int32 typmod, Oid collOid) +{ + ColumnDef* n = makeNode(ColumnDef); + + n->colname = pstrdup(colname); + n->typname = makeTypeNameFromOid(typeOid, typmod); + n->inhcount = 0; + n->is_local = true; + n->is_not_null = false; + n->is_from_type = false; + n->storage = 0; + n->raw_default = NULL; + n->cooked_default = NULL; + n->collClause = NULL; + n->collOid = collOid; + n->constraints = NIL; + n->fdwoptions = NIL; + + return n; +} + +/* makeIndexInfo * create an IndexInfo node */ IndexInfo* makeIndexInfo(int numattrs, List* expressions, List* predicates, bool unique, bool isready, bool concurrent) diff --git a/src/common/backend/nodes/nodeFuncs.cpp b/src/common/backend/nodes/nodeFuncs.cpp index 39eff7a08..513774d1a 100755 --- a/src/common/backend/nodes/nodeFuncs.cpp +++ b/src/common/backend/nodes/nodeFuncs.cpp @@ -2824,6 +2824,9 @@ bool raw_expression_tree_walker(Node* node, bool (*walker)(), void* context) return true; } /* colNames, options are deemed uninteresting */ + /* viewQuery should be null in raw parsetree, but check it */ + if (p2walker(into->viewQuery, context)) + return true; } break; case T_List: foreach (temp, (List*)node) { diff --git a/src/common/backend/nodes/outfuncs.cpp b/src/common/backend/nodes/outfuncs.cpp index bfe7eed9b..af0ea3ccb 100755 --- a/src/common/backend/nodes/outfuncs.cpp +++ b/src/common/backend/nodes/outfuncs.cpp @@ -1854,6 +1854,7 @@ static void _outIntoClause(StringInfo str, IntoClause* node) WRITE_ENUM_FIELD(onCommit, OnCommitAction); WRITE_ENUM_FIELD(row_compress, RelCompressType); WRITE_STRING_FIELD(tableSpaceName); + WRITE_NODE_FIELD(viewQuery); WRITE_BOOL_FIELD(skipData); } diff --git a/src/common/backend/nodes/readfuncs.cpp b/src/common/backend/nodes/readfuncs.cpp index 025cd2f4b..7b913aa54 100644 --- a/src/common/backend/nodes/readfuncs.cpp +++ b/src/common/backend/nodes/readfuncs.cpp @@ -1511,6 +1511,7 @@ static IntoClause* _readIntoClause(void) READ_ENUM_FIELD(onCommit, OnCommitAction); READ_ENUM_FIELD(row_compress, RelCompressType); READ_STRING_FIELD(tableSpaceName); + READ_NODE_FIELD(viewQuery); READ_BOOL_FIELD(skipData); READ_DONE(); diff --git a/src/common/backend/parser/analyze.cpp b/src/common/backend/parser/analyze.cpp index af19c2b82..338693cfe 100644 --- a/src/common/backend/parser/analyze.cpp +++ b/src/common/backend/parser/analyze.cpp @@ -227,6 +227,7 @@ Query* transformTopLevelStmt(ParseState* pstate, Node* parseTree, bool isFirstNo ctas->query = parseTree; ctas->into = stmt->intoClause; + ctas->relkind = OBJECT_TABLE; ctas->is_select_into = true; /* @@ -2993,7 +2994,7 @@ static Query* transformExplainStmt(ParseState* pstate, ExplainStmt* stmt) { Query* result = NULL; - /* transform contained query, allowing SELECT INTO */ + /* transform a CREATE TABLE AS, SELECT ... INTO, or CREATE MATERIALIZED VIEW Statement */ stmt->query = (Node*)transformTopLevelStmt(pstate, stmt->query); /* represent the command as a utility Query */ @@ -3014,9 +3015,56 @@ static Query* transformCreateTableAsStmt(ParseState* pstate, CreateTableAsStmt* { Query* result = NULL; ListCell* lc = NULL; + Query* query; /* transform contained query */ - stmt->query = (Node*)transformStmt(pstate, stmt->query); + query = transformStmt(pstate, stmt->query); + stmt->query = (Node*)query; + /* additional work needed for CREATE MATERIALIZED VIEW */ + if (stmt->relkind == OBJECT_MATVIEW) { + /* + * Prohibit a data-modifying CTE in the query used to create a + * materialized view. It's not sufficiently clear what the user would + * want to happen if the MV is refreshed or incrementally maintained. + */ + if (query->hasModifyingCTE) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg( + "materialized views must not use data-modifying statements in WITH"))); + + /* + * Check whether any temporary database objects are used in the creation query. + * It would be hard to refresh data or incrementally maintain it if a source + * disappeared. + */ + if (isQueryUsingTempRelation(query)) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views must not use temporary tables or views"))); + /* + * A materialized view would either need to save parameters for use in + * maintaining/loading the data or prohibit them entirely. The latter + * seems safer and more sane. + */ + if (query_contains_extern_params(query)) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views may not be defined using bound parameters"))); + /* + * For now, we disallow unlogged materialized views, because it + * seems like a bad idea for them to just go to empty after a crash. + * (If we could mark them as unpopulated, that would be better, but + * that requires catalog changes which crash recovery can't presently + * handle.) + */ + if (stmt->into->rel->relpersistence == RELPERSISTENCE_UNLOGGED) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialized views cannot be UNLOGGED"))); + + /* + * At runtime, we'll need a copy of the parsed-but-not-rewritten Query + * for purposes of creating the view's ON SELECT rule. We stash that + * in the IntoClause because that's where intorel_startup() can + * conveniently get it from. + */ + stmt->into->viewQuery = (Node*)copyObject(query); + } /* if result type of new relation is unknown-type, then resolve as type TEXT */ foreach (lc, ((Query*)stmt->query)->targetList) { diff --git a/src/common/backend/parser/gram.y b/src/common/backend/parser/gram.y index 0d7e16b5e..c6b6c1695 100755 --- a/src/common/backend/parser/gram.y +++ b/src/common/backend/parser/gram.y @@ -277,7 +277,7 @@ static void ParseUpdateMultiSet(List *set_target_list, SelectStmt *stmt, core_yy DropOwnedStmt ReassignOwnedStmt AlterTSConfigurationStmt AlterTSDictionaryStmt AnonyBlockStmt BarrierStmt AlterNodeStmt CreateNodeStmt DropNodeStmt AlterCoordinatorStmt - CreateNodeGroupStmt AlterNodeGroupStmt DropNodeGroupStmt + CreateMatViewStmt RefreshMatViewStmt CreateNodeGroupStmt AlterNodeGroupStmt DropNodeGroupStmt CreateResourcePoolStmt AlterResourcePoolStmt DropResourcePoolStmt CreateWorkloadGroupStmt AlterWorkloadGroupStmt DropWorkloadGroupStmt CreateAppWorkloadGroupMappingStmt AlterAppWorkloadGroupMappingStmt DropAppWorkloadGroupMappingStmt @@ -398,7 +398,7 @@ static void ParseUpdateMultiSet(List *set_target_list, SelectStmt *stmt, core_yy %type fdw_option %type OptTempTableName -%type into_clause create_as_target +%type into_clause create_as_target create_mv_target %type createfunc_opt_item createproc_opt_item common_func_opt_item dostmt_opt_item %type func_arg func_arg_with_default table_func_column @@ -407,6 +407,7 @@ static void ParseUpdateMultiSet(List *set_target_list, SelectStmt *stmt, core_yy %type opt_trusted opt_restart_seqs %type OptTemp +%type OptNoLog %type OnCommitOption %type for_locking_item @@ -661,7 +662,7 @@ static void ParseUpdateMultiSet(List *set_target_list, SelectStmt *stmt, core_yy LABEL LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING LEAKPROOF LEAST LESS LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOG_P LOGGING LOOP - MAPPING MATCH MATCHED MAXEXTENTS MAXSIZE MAXTRANS MAXVALUE MERGE MINUS_P MINUTE_P MINVALUE MINEXTENTS MODE MODIFY_P MONTH_P MOVE MOVEMENT + MAPPING MATCH MATERIALIZED MATCHED MAXEXTENTS MAXSIZE MAXTRANS MAXVALUE MERGE MINUS_P MINUTE_P MINVALUE MINEXTENTS MODE MODIFY_P MONTH_P MOVE MOVEMENT NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NLSSORT NO NOCOMPRESS NOCYCLE NODE NOLOGGING NOMAXVALUE NOMINVALUE NONE NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMBER_P NUMERIC NUMSTR NVARCHAR2 NVL @@ -676,7 +677,7 @@ static void ParseUpdateMultiSet(List *set_target_list, SelectStmt *stmt, core_yy QUERY QUOTE - RANGE RAW READ REAL REASSIGN REBUILD RECHECK RECURSIVE REF REFERENCES REINDEX REJECT_P + RANGE RAW READ REAL REASSIGN REBUILD RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX REJECT_P RELATIVE_P RELEASE RELOPTIONS REMOTE_P RENAME REPEATABLE REPLACE REPLICA RESET RESIZE RESOURCE RESTART RESTRICT RETURN RETURNING RETURNS REUSE REVOKE RIGHT ROLE ROLLBACK ROLLUP ROW ROWNUM ROWS RULE @@ -897,6 +898,7 @@ stmt : | CreateDataSourceStmt | CreateFunctionStmt | CreateGroupStmt + | CreateMatViewStmt | CreateNodeGroupStmt | CreateNodeStmt | CreateOpClassStmt @@ -958,6 +960,7 @@ stmt : | IndexStmt | InsertStmt | ListenStmt + | RefreshMatViewStmt | LoadStmt | LockStmt | MergeStmt @@ -2165,9 +2168,9 @@ DiscardStmt: /***************************************************************************** * - * ALTER [ TABLE | INDEX | SEQUENCE | VIEW ] variations + * ALTER [ TABLE | INDEX | SEQUENCE | VIEW | MATERIALIZED VIEW ] variations * - * Note: we accept all subcommands for each of the four variants, and sort + * Note: we accept all subcommands for each of the five variants, and sort * out what's really legal at execution time. *****************************************************************************/ @@ -2351,6 +2354,24 @@ AlterTableStmt: n->missing_ok = true; $$ = (Node *)n; } + | ALTER MATERIALIZED VIEW qualified_name alter_table_cmds + { + AlterTableStmt *n = makeNode(AlterTableStmt); + n->relation = $4; + n->cmds = $5; + n->relkind = OBJECT_MATVIEW; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name alter_table_cmds + { + AlterTableStmt *n = makeNode(AlterTableStmt); + n->relation = $6; + n->cmds = $7; + n->relkind = OBJECT_MATVIEW; + n->missing_ok = true; + $$ = (Node *)n; + } ; modify_column_cmds: modify_column_cmd { $$ = list_make1($1); } @@ -5146,6 +5167,7 @@ CreateAsStmt: CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); ctas->query = $6; ctas->into = $4; + ctas->relkind = OBJECT_TABLE; ctas->is_select_into = false; /* cram additional flags into the IntoClause */ $4->rel->relpersistence = $2; @@ -5166,6 +5188,7 @@ create_as_target: $$->options = $3; $$->onCommit = $4; $$->row_compress = $5; + $$->viewQuery = NULL; $$->tableSpaceName = $6; $$->skipData = false; /* might get changed later */ /* PGXC_BEGIN */ @@ -5181,6 +5204,66 @@ opt_with_data: | /*EMPTY*/ { $$ = TRUE; } ; +/***************************************************************************** + * + * QUERY : + * CREATE MATERIALIZED VIEW relname AS SelectStmt + * + *****************************************************************************/ + +CreateMatViewStmt: + CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data + { + CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); + ctas->query = $7; + ctas->into = $5; + ctas->relkind = OBJECT_MATVIEW; + ctas->is_select_into = false; + /* cram additional flags into the IntoClause */ + $5->rel->relpersistence = $2; + $5->skipData = !($8); + $$ = (Node *) ctas; + } + ; + +create_mv_target: + qualified_name opt_column_list opt_reloptions OptTableSpace + { + $$ = makeNode(IntoClause); + $$->rel = $1; + $$->colNames = $2; + $$->options = $3; + $$->onCommit = ONCOMMIT_NOOP; + $$->tableSpaceName = $4; + $$->viewQuery = NULL; + $$->skipData = false; /* might get changed later */ + } + ; + +OptNoLog: + UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; } + | /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; } + ; + + +/***************************************************************************** + * + * QUERY : + * REFRESH MATERIALIZED VIEW qualified_name + * + *****************************************************************************/ + +RefreshMatViewStmt: + REFRESH MATERIALIZED VIEW qualified_name opt_with_data + { + RefreshMatViewStmt *n = makeNode(RefreshMatViewStmt); + n->relation = $4; + n->skipData = !($5); + $$ = (Node *) n; + } + ; + + /***************************************************************************** * @@ -5853,6 +5936,15 @@ AlterExtensionContentsStmt: n->objname = $6; $$ = (Node *)n; } + | ALTER EXTENSION name add_drop MATERIALIZED VIEW any_name + { + AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt); + n->extname = $3; + n->action = $4; + n->objtype = OBJECT_MATVIEW; + n->objname = $7; + $$ = (Node *)n; + } | ALTER EXTENSION name add_drop FOREIGN TABLE any_name { AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt); @@ -7731,6 +7823,7 @@ DropStmt: DROP drop_type IF_P EXISTS any_name_list opt_drop_behavior drop_type: TABLE { $$ = OBJECT_TABLE; } | SEQUENCE { $$ = OBJECT_SEQUENCE; } | VIEW { $$ = OBJECT_VIEW; } + | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; } | INDEX { $$ = OBJECT_INDEX; } | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; } | TYPE_P { $$ = OBJECT_TYPE; } @@ -7796,7 +7889,8 @@ opt_restart_seqs: * EXTENSION | ROLE | TEXT SEARCH PARSER | * TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE | * TEXT SEARCH CONFIGURATION | FOREIGN TABLE | - * FOREIGN DATA WRAPPER | SERVER ] | + * FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER | + * MATERIALIZED VIEW] | * AGGREGATE (arg1, ...) | * FUNCTION (arg1, arg2, ...) | * OPERATOR (leftoperand_typ, rightoperand_typ) | @@ -7970,6 +8064,7 @@ comment_type: | DOMAIN_P { $$ = OBJECT_DOMAIN; } | TYPE_P { $$ = OBJECT_TYPE; } | VIEW { $$ = OBJECT_VIEW; } + | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; } | COLLATION { $$ = OBJECT_COLLATION; } | CONVERSION_P { $$ = OBJECT_CONVERSION; } | TABLESPACE { $$ = OBJECT_TABLESPACE; } @@ -8071,6 +8166,7 @@ security_label_type: | TABLESPACE { $$ = OBJECT_TABLESPACE; } | TYPE_P { $$ = OBJECT_TYPE; } | VIEW { $$ = OBJECT_VIEW; } + | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; } ; security_label: Sconst { $$ = $1; } @@ -10143,6 +10239,26 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name n->missing_ok = true; $$ = (Node *)n; } + | ALTER MATERIALIZED VIEW qualified_name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_MATVIEW; + n->relation = $4; + n->subname = NULL; + n->newname = $7; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_MATVIEW; + n->relation = $6; + n->subname = NULL; + n->newname = $9; + n->missing_ok = true; + $$ = (Node *)n; + } | ALTER INDEX qualified_name RENAME TO name { RenameStmt *n = makeNode(RenameStmt); @@ -10227,6 +10343,28 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name n->missing_ok = true; $$ = (Node *)n; } + | ALTER MATERIALIZED VIEW qualified_name RENAME opt_column name TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_COLUMN; + n->relationType = OBJECT_MATVIEW; + n->relation = $4; + n->subname = $7; + n->newname = $9; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name RENAME opt_column name TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_COLUMN; + n->relationType = OBJECT_MATVIEW; + n->relation = $6; + n->subname = $9; + n->newname = $11; + n->missing_ok = true; + $$ = (Node *)n; + } | ALTER TABLE relation_expr RENAME CONSTRAINT name TO name { RenameStmt *n = makeNode(RenameStmt); @@ -10640,6 +10778,24 @@ AlterObjectSchemaStmt: n->missing_ok = true; $$ = (Node *)n; } + | ALTER MATERIALIZED VIEW qualified_name SET SCHEMA name + { + AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); + n->objectType = OBJECT_MATVIEW; + n->relation = $4; + n->newschema = $7; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name SET SCHEMA name + { + AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); + n->objectType = OBJECT_MATVIEW; + n->relation = $6; + n->newschema = $9; + n->missing_ok = true; + $$ = (Node *)n; + } | ALTER FOREIGN TABLE relation_expr SET SCHEMA name { AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); @@ -12660,6 +12816,8 @@ ExplainableStmt: | MergeStmt | DeclareCursorStmt | CreateAsStmt + | CreateMatViewStmt + | RefreshMatViewStmt | ExecuteStmt /* by default all are $$=$1 */ ; @@ -12845,6 +13003,7 @@ ExecuteStmt: EXECUTE name execute_param_clause n->params = $8; ctas->query = (Node *) n; ctas->into = $4; + ctas->relkind = OBJECT_TABLE; ctas->is_select_into = false; /* cram additional flags into the IntoClause */ $4->rel->relpersistence = $2; @@ -13646,6 +13805,7 @@ into_clause: /* Here $$ is a temp table, so row_compress can be any value. To be safe, REL_CMPRS_PAGE_PLAIN is used. */ $$->row_compress = REL_CMPRS_PAGE_PLAIN; $$->tableSpaceName = NULL; + $$->viewQuery = NULL; $$->skipData = false; } | /*EMPTY*/ @@ -17785,6 +17945,7 @@ unreserved_keyword: | MAPPING | MATCH | MATCHED + | MATERIALIZED | MAXEXTENTS | MAXSIZE | MAXTRANS @@ -17862,6 +18023,7 @@ unreserved_keyword: | RECHECK | RECURSIVE | REF + | REFRESH | REINDEX | RELATIVE_P | RELEASE diff --git a/src/common/backend/parser/parse_clause.cpp b/src/common/backend/parser/parse_clause.cpp index 09102e98b..5006e47ca 100755 --- a/src/common/backend/parser/parse_clause.cpp +++ b/src/common/backend/parser/parse_clause.cpp @@ -275,8 +275,12 @@ bool interpretInhOption(InhOption inhOpt) * table/result set should be created with OIDs. This needs to be done after * parsing the query string because the return value can depend upon the * default_with_oids GUC var. + * + * In some situations, we want to reject an OIDS option even if it's present. + * That's (rather messily) handled here rather than reloptions.c, because that + * code explicitly punts checking for oids to here. */ -bool interpretOidsOption(List* defList) +bool interpretOidsOption(List* defList, bool allowOids) { ListCell* cell = NULL; @@ -285,10 +289,16 @@ bool interpretOidsOption(List* defList) DefElem* def = (DefElem*)lfirst(cell); if (def->defnamespace == NULL && pg_strcasecmp(def->defname, "oids") == 0) { + if (!allowOids) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s\"", def->defname))); + return defGetBoolean(def); } } - + /* Force no-OIDS result if caller disallows OIDS. */ + if (!allowOids) + return false; /* OIDS option was not specified, so use default. */ return false; } diff --git a/src/common/backend/parser/parse_param.cpp b/src/common/backend/parser/parse_param.cpp index 4bbabf3f4..3658ab2cc 100755 --- a/src/common/backend/parser/parse_param.cpp +++ b/src/common/backend/parser/parse_param.cpp @@ -54,6 +54,7 @@ static Node* variable_paramref_hook(ParseState* pstate, ParamRef* pref); static Node* variable_coerce_param_hook( ParseState* pstate, Param* param, Oid targetTypeId, int32 targetTypeMod, int location); static bool check_parameter_resolution_walker(Node* node, ParseState* pstate); +static bool query_contains_extern_params_walker(Node* node, void* context); /* * Set up to process a query containing references to fixed parameters. @@ -288,3 +289,30 @@ static bool check_parameter_resolution_walker(Node* node, ParseState* pstate) } return expression_tree_walker(node, (bool (*)())check_parameter_resolution_walker, (void*)pstate); } + + +/* + * Check to see if a fully-parsed query tree contains any PARAM_EXTERN Params. + */ +bool query_contains_extern_params(Query* query) +{ + return query_tree_walker(query, (bool (*)())query_contains_extern_params_walker, NULL, 0); +} + +static bool query_contains_extern_params_walker(Node* node, void* context) +{ + if (node == NULL) + return false; + if (IsA(node, Param)) { + Param* param = (Param*)node; + + if (param->paramkind == PARAM_EXTERN) + return true; + return false; + } + if (IsA(node, Query)) { + /* Recurse into RTE subquery or not-yet-planned sublink subquery */ + return query_tree_walker((Query*)node, (bool(*)())query_contains_extern_params_walker, context, 0); + } + return expression_tree_walker(node, (bool(*)())query_contains_extern_params_walker, context); +} diff --git a/src/common/backend/parser/parse_relation.cpp b/src/common/backend/parser/parse_relation.cpp index 48d32f76a..cc2c863b4 100755 --- a/src/common/backend/parser/parse_relation.cpp +++ b/src/common/backend/parser/parse_relation.cpp @@ -61,6 +61,7 @@ static int32* getValuesTypmods(RangeTblEntry* rte); #ifndef PGXC static int specialAttNum(const char* attname); #endif +static bool isQueryUsingTempRelation_walker(Node* node, void* context); static Oid getPartitionOidForRTE(RangeTblEntry* rte, RangeVar* relation, ParseState* pstate, Relation rel); @@ -549,12 +550,49 @@ Node* scanRTEForColumn(ParseState* pstate, RangeTblEntry* rte, char* colname, in return result; } +/* + * Examine a fully-parsed query, and return TRUE iff any relation underlying + * the query is a temporary relation (table, view, or materialized view). + */ +bool isQueryUsingTempRelation(Query* query) +{ + return isQueryUsingTempRelation_walker((Node*)query, NULL); +} + +static bool isQueryUsingTempRelation_walker(Node* node, void* context) +{ + if (node == NULL) + return false; + + if (IsA(node, Query)) { + Query* query = (Query*)node; + ListCell* rtable; + + foreach (rtable, query->rtable) { + RangeTblEntry* rte = (RangeTblEntry*)lfirst(rtable); + + if (rte->rtekind == RTE_RELATION) { + Relation rel = heap_open(rte->relid, AccessShareLock); + char relpersistence = rel->rd_rel->relpersistence; + + heap_close(rel, AccessShareLock); + if (relpersistence == RELPERSISTENCE_TEMP || relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + return true; + } + } + + return query_tree_walker(query, (bool (*)())isQueryUsingTempRelation_walker, context, QTW_IGNORE_JOINALIASES); + } + + return expression_tree_walker(node, (bool (*)())isQueryUsingTempRelation_walker, context); +} + /* * colNameToVar - * Search for an unqualified column name. - * If found, return the appropriate Var node (or expression). - * If not found, return NULL. If the name proves ambiguous, raise error. - * If localonly is true, only names in the innermost query are considered. + * Search for an unqualified column name. + * If found, return the appropriate Var node (or expression). + * If not found, return NULL. If the name proves ambiguous, raise error. + * If localonly is true, only names in the innermost query are considered. */ Node* colNameToVar(ParseState* pstate, char* colname, bool localonly, int location, RangeTblEntry** final_rte) { diff --git a/src/common/backend/parser/parse_utilcmd.cpp b/src/common/backend/parser/parse_utilcmd.cpp index eb19244b8..083da321b 100644 --- a/src/common/backend/parser/parse_utilcmd.cpp +++ b/src/common/backend/parser/parse_utilcmd.cpp @@ -152,7 +152,7 @@ typedef struct { */ #define TRANSFORM_RELATION_LIKE_CLAUSE(rel_relkind) \ (((rel_relkind) != RELKIND_RELATION && (rel_relkind) != RELKIND_VIEW && (rel_relkind) != RELKIND_COMPOSITE_TYPE && \ - (rel_relkind) != RELKIND_FOREIGN_TABLE) \ + relation->rd_rel->relkind != RELKIND_MATVIEW && (rel_relkind) != RELKIND_FOREIGN_TABLE) \ ? false \ : true) @@ -525,7 +525,7 @@ List* transformCreateStmt(CreateStmt* stmt, const char* queryString, const List* if (cxt.hasoids) { stmt->options = lappend(stmt->options, makeDefElem("oids", (Node*)makeInteger(cxt.hasoids))); } - cxt.hasoids = interpretOidsOption(stmt->options); + cxt.hasoids = interpretOidsOption(stmt->options, true); #ifdef PGXC if (cxt.distributeby != NULL) { @@ -1159,7 +1159,7 @@ static void transformTableLikeClause( if (!TRANSFORM_RELATION_LIKE_CLAUSE(relation->rd_rel->relkind)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, view, composite type, or foreign table", + errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table", RelationGetRelationName(relation)))); cancel_parser_errposition_callback(&pcbstate); @@ -3203,7 +3203,10 @@ void transformRuleStmt(RuleStmt* stmt, const char* queryString, List** actions, * beforehand. */ rel = heap_openrv(stmt->relation, AccessExclusiveLock); - + if (rel->rd_rel->relkind == RELKIND_MATVIEW) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rules on materialized views are not supported"))); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; diff --git a/src/common/backend/utils/adt/dbsize.cpp b/src/common/backend/utils/adt/dbsize.cpp index f68b807ea..025e687a2 100755 --- a/src/common/backend/utils/adt/dbsize.cpp +++ b/src/common/backend/utils/adt/dbsize.cpp @@ -1665,6 +1665,7 @@ Datum pg_relation_filenode(PG_FUNCTION_ARGS) switch (relform->relkind) { case RELKIND_RELATION: + case RELKIND_MATVIEW: case RELKIND_INDEX: case RELKIND_SEQUENCE: case RELKIND_TOASTVALUE: @@ -1783,6 +1784,7 @@ Datum pg_relation_filepath(PG_FUNCTION_ARGS) switch (relform->relkind) { case RELKIND_RELATION: + case RELKIND_MATVIEW: case RELKIND_INDEX: case RELKIND_SEQUENCE: case RELKIND_TOASTVALUE: diff --git a/src/common/backend/utils/adt/ri_triggers.cpp b/src/common/backend/utils/adt/ri_triggers.cpp index 51013c926..cd798dfd8 100755 --- a/src/common/backend/utils/adt/ri_triggers.cpp +++ b/src/common/backend/utils/adt/ri_triggers.cpp @@ -174,7 +174,6 @@ static void quoteOneName(char* buffer, const char* name); static void quoteRelationName(char* buffer, Relation rel); static void ri_GenerateQual(StringInfo buf, const char* sep, const char* leftop, Oid leftoptype, Oid opoid, const char* rightop, Oid rightoptype); -static void ri_add_cast_to(StringInfo buf, Oid typid); static void ri_GenerateQualCollation(StringInfo buf, Oid collation); static int ri_NullCheck(Relation rel, HeapTuple tup, RI_QueryKey* key, int pairidx); static void ri_BuildQueryKeyFull(RI_QueryKey* key, const RI_ConstraintInfo* riinfo, int32 constr_queryno); @@ -2319,61 +2318,8 @@ static void quoteRelationName(char* buffer, Relation rel) static void ri_GenerateQual(StringInfo buf, const char* sep, const char* leftop, Oid leftoptype, Oid opoid, const char* rightop, Oid rightoptype) { - HeapTuple opertup; - Form_pg_operator operform; - char* oprname = NULL; - char* nspname = NULL; - - opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid)); - if (!HeapTupleIsValid(opertup)) { - ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for operator %u", opoid))); - } - operform = (Form_pg_operator)GETSTRUCT(opertup); - Assert(operform->oprkind == 'b'); - oprname = NameStr(operform->oprname); - - nspname = get_namespace_name(operform->oprnamespace, true); - - appendStringInfo(buf, " %s %s", sep, leftop); - if (leftoptype != operform->oprleft) { - ri_add_cast_to(buf, operform->oprleft); - } - appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname)); - appendStringInfoString(buf, oprname); - appendStringInfo(buf, ") %s", rightop); - if (rightoptype != operform->oprright) { - ri_add_cast_to(buf, operform->oprright); - } - ReleaseSysCache(opertup); -} - -/* - * Add a cast specification to buf. We spell out the type name the hard way, - * intentionally not using format_type_be(). This is to avoid corner cases - * for CHARACTER, BIT, and perhaps other types, where specifying the type - * using SQL-standard syntax results in undesirable data truncation. By - * doing it this way we can be certain that the cast will have default (-1) - * target typmod. - */ -static void ri_add_cast_to(StringInfo buf, Oid typid) -{ - HeapTuple typetup; - Form_pg_type typform; - char* typname = NULL; - char* nspname = NULL; - - typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); - if (!HeapTupleIsValid(typetup)) { - ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for type %u", typid))); - } - typform = (Form_pg_type)GETSTRUCT(typetup); - - typname = NameStr(typform->typname); - nspname = get_namespace_name(typform->typnamespace, true); - - appendStringInfo(buf, "::%s.%s", quote_identifier(nspname), quote_identifier(typname)); - - ReleaseSysCache(typetup); + appendStringInfo(buf, " %s ", sep); + generate_operator_clause(buf, leftop, leftoptype, opoid, rightop, rightoptype); } /* diff --git a/src/common/backend/utils/adt/rowtypes.cpp b/src/common/backend/utils/adt/rowtypes.cpp index cb52f2995..9c1cc8881 100755 --- a/src/common/backend/utils/adt/rowtypes.cpp +++ b/src/common/backend/utils/adt/rowtypes.cpp @@ -17,6 +17,7 @@ #include +#include "access/tuptoaster.h" #include "catalog/pg_type.h" #include "libpq/pqformat.h" #include "utils/builtins.h" @@ -1151,21 +1152,446 @@ Datum btrecordcmp(PG_FUNCTION_ARGS) PG_RETURN_INT32(record_cmp(fcinfo)); } -void record_set_extra(RecordIOData** my_extra, int ncolumns, Oid tupType, int32 tupTypmod, PG_FUNCTION_ARGS) + +/* + * record_image_cmp : + * Internal byte-oriented comparison function for records. + * + * Returns -1, 0 or 1 + * + * Note: The normal concepts of "equality" do not apply here; different + * representation of values considered to be equal are not considered to be + * identical. As an example, for the citext type 'A' and 'a' are equal, but + * they are not identical. + */ +static int record_image_cmp(PG_FUNCTION_ARGS) +{ + HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); + HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + int32 result = 0; + Oid tupType1; + Oid tupType2; + int32 tupTypmod1; + int32 tupTypmod2; + TupleDesc tupdesc1; + TupleDesc tupdesc2; + HeapTupleData tuple1; + HeapTupleData tuple2; + int ncolumns1; + int ncolumns2; + RecordCompareData* my_extra; + int ncols; + Datum* values1; + Datum* values2; + bool* nulls1; + bool* nulls2; + int i1; + int i2; + int j; + + /* Extract type info from the tuples */ + tupType1 = HeapTupleHeaderGetTypeId(record1); + tupTypmod1 = HeapTupleHeaderGetTypMod(record1); + tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1); + ncolumns1 = tupdesc1->natts; + tupType2 = HeapTupleHeaderGetTypeId(record2); + tupTypmod2 = HeapTupleHeaderGetTypMod(record2); + tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2); + ncolumns2 = tupdesc2->natts; + + /* Build temporary HeapTuple control structures */ + tuple1.t_len = HeapTupleHeaderGetDatumLength(record1); + ItemPointerSetInvalid(&(tuple1.t_self)); + tuple1.t_tableOid = InvalidOid; + tuple1.t_data = record1; + tuple2.t_len = HeapTupleHeaderGetDatumLength(record2); + ItemPointerSetInvalid(&(tuple2.t_self)); + tuple2.t_tableOid = InvalidOid; + tuple2.t_data = record2; + + /* + * We arrange to look up the needed comparison info just once per series + * of calls, assuming the record types don't change underneath us. + */ + ncols = Max(ncolumns1, ncolumns2); + my_extra = (RecordCompareData*)fcinfo->flinfo->fn_extra; + if (my_extra == NULL || my_extra->ncolumns < ncols) { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(RecordCompareData) - sizeof(ColumnCompareData) + ncols * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData*)fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncols; + my_extra->record1_type = InvalidOid; + my_extra->record2_typmod = 0; + } + + if (my_extra->record1_type != tupType1 || my_extra->record1_typmod != tupTypmod1 || + my_extra->record2_type != tupType2 || my_extra->record2_typmod != tupTypmod2) { + MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType1; + my_extra->record1_typmod = tupTypmod1; + my_extra->record2_type = tupType2; + my_extra->record2_typmod = tupTypmod2; + } + + /* Break down the tuples into fields */ + values1 = (Datum*)palloc(ncolumns1 * sizeof(Datum)); + nulls1 = (bool*)palloc(ncolumns1 * sizeof(bool)); + heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1); + values2 = (Datum*)palloc(ncolumns2 * sizeof(Datum)); + nulls2 = (bool*)palloc(ncolumns2 * sizeof(bool)); + heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + + /* + * Scan corresponding columns, allowing for dropped columns in different + * places in the two rows. i1 and i2 are physical column indexes, j is + * the logical column index. + */ + i1 = i2 = j = 0; + while (i1 < ncolumns1 || i2 < ncolumns2) { + /* + * Skip dropped columns + */ + if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped) { + i1++; + continue; + } + if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped) { + i2++; + continue; + } + if (i1 >= ncolumns1 || i2 >= ncolumns2) { + break; /* we'll deal with mismatch below loop */ + } + + /* + * Have two matching columns, they must be same type + */ + if (tupdesc1->attrs[i1]->atttypid != tupdesc2->attrs[i2]->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare dissimilar column types %s and %s at record column %d", + format_type_be(tupdesc1->attrs[i1]->atttypid), + format_type_be(tupdesc2->attrs[i2]->atttypid), + j + 1))); + /* + * We consider two NULLs equal; NULL > not-NULL. + */ + if (!nulls1[i1] || !nulls2[i2]) { + int cmpresult; + + if (nulls1[i1]) { + /* arg1 is greater than arg2 */ + result = 1; + break; + } + if (nulls2[i2]) { + /* arg1 is less than arg2 */ + result = -1; + break; + } + + /* Compare the pair of elements */ + if (tupdesc1->attrs[i1]->attlen == -1) { + Size len1, len2; + struct varlena *arg1val; + struct varlena *arg2val; + + len1 = toast_raw_datum_size(values1[i1]); + len2 = toast_raw_datum_size(values2[i2]); + arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]); + arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]); + + cmpresult = memcmp(VARDATA_ANY(arg1val), VARDATA_ANY(arg2val), len1 - VARHDRSZ); + if ((cmpresult == 0) && (len1 != len2)) + cmpresult = (len1 < len2) ? -1 : 1; + + if ((Pointer)arg1val != (Pointer)values1[i1]) + pfree(arg1val); + if ((Pointer)arg2val != (Pointer)values2[i2]) + pfree(arg2val); + } else if (tupdesc1->attrs[i1]->attbyval) { + cmpresult = memcmp(&(values1[i1]), &(values2[i2]), tupdesc1->attrs[i1]->attlen); + } else { + cmpresult = + memcmp(DatumGetPointer(values1[i1]), DatumGetPointer(values2[i2]), tupdesc1->attrs[i1]->attlen); + } + + if (cmpresult < 0) { + /* arg1 is less than arg2 */ + result = -1; + break; + } else if (cmpresult > 0) { + /* arg1 is greater than arg2 */ + result = 1; + break; + } + } + + /* equal, so continue to next column */ + i1++, i2++, j++; + } + + /* + * If we didn't break out of the loop early, check for column count + * mismatch. (We do not report such mismatch if we found unequal column + * values; is that a feature or a bug?) + */ + if (result == 0) { + if (i1 != ncolumns1 || i2 != ncolumns2) + ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare record types with different numbers of columns"))); + } + + pfree(values1); + pfree(nulls1); + pfree(values2); + pfree(nulls2); + ReleaseTupleDesc(tupdesc1); + ReleaseTupleDesc(tupdesc2); + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record1, 0); + PG_FREE_IF_COPY(record2, 1); + + return result; +} + +/* + * record_image_eq : + * compares two records for identical contents, based on byte images + * result : + * returns true if the records are identical, false otherwise. + * + * Note: we do not use record_image_cmp here, since we can avoid + * de-toasting for unequal lengths this way. + */ +Datum record_image_eq(PG_FUNCTION_ARGS) +{ + HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); + HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + bool result = true; + Oid tupType1; + Oid tupType2; + int32 tupTypmod1; + int32 tupTypmod2; + TupleDesc tupdesc1; + TupleDesc tupdesc2; + HeapTupleData tuple1; + HeapTupleData tuple2; + int ncolumns1; + int ncolumns2; + RecordCompareData* my_extra; + int ncols; + Datum* values1; + Datum* values2; + bool* nulls1; + bool* nulls2; + int i1; + int i2; + int j; + + /* Extract type info from the tuples */ + tupType1 = HeapTupleHeaderGetTypeId(record1); + tupTypmod1 = HeapTupleHeaderGetTypMod(record1); + tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1); + ncolumns1 = tupdesc1->natts; + tupType2 = HeapTupleHeaderGetTypeId(record2); + tupTypmod2 = HeapTupleHeaderGetTypMod(record2); + tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2); + ncolumns2 = tupdesc2->natts; + + /* Build temporary HeapTuple control structures */ + tuple1.t_len = HeapTupleHeaderGetDatumLength(record1); + ItemPointerSetInvalid(&(tuple1.t_self)); + tuple1.t_tableOid = InvalidOid; + tuple1.t_data = record1; + tuple2.t_len = HeapTupleHeaderGetDatumLength(record2); + ItemPointerSetInvalid(&(tuple2.t_self)); + tuple2.t_tableOid = InvalidOid; + tuple2.t_data = record2; + + /* + * We arrange to look up the needed comparison info just once per series + * of calls, assuming the record types don't change underneath us. + */ + ncols = Max(ncolumns1, ncolumns2); + my_extra = (RecordCompareData*)fcinfo->flinfo->fn_extra; + if (my_extra == NULL || my_extra->ncolumns < ncols) { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(RecordCompareData) - sizeof(ColumnCompareData) + ncols * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData*)fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncols; + my_extra->record1_type = InvalidOid; + my_extra->record1_typmod = 0; + my_extra->record2_type = InvalidOid; + my_extra->record2_typmod = 0; + } + + if (my_extra->record1_type != tupType1 || my_extra->record1_typmod != tupTypmod1 || + my_extra->record2_type != tupType2 || my_extra->record2_typmod != tupTypmod2) { + MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType1; + my_extra->record1_typmod = tupTypmod1; + my_extra->record2_type = tupType2; + my_extra->record2_typmod = tupTypmod2; + } + + /* Break down the tuples into fields */ + values1 = (Datum*)palloc(ncolumns1 * sizeof(Datum)); + nulls1 = (bool*)palloc(ncolumns1 * sizeof(bool)); + heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1); + values2 = (Datum*)palloc(ncolumns2 * sizeof(Datum)); + nulls2 = (bool*)palloc(ncolumns2 * sizeof(bool)); + heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + + /* + * Scan corresponding columns, allowing for dropped columns in different + * places in the two rows. i1 and i2 are physical column indexes, j is + * the logical column index. + */ + i1 = i2 = j = 0; + while (i1 < ncolumns1 || i2 < ncolumns2) { + /* + * Skip dropped columns + */ + if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped) { + i1++; + continue; + } + if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped) { + i2++; + continue; + } + if (i1 >= ncolumns1 || i2 >= ncolumns2) { + break; /* we'll deal with mismatch below loop */ + } + + /* + * Have two matching columns, they must be same type + */ + if (tupdesc1->attrs[i1]->atttypid != tupdesc2->attrs[i2]->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare dissimilar column types %s and %s at record column %d", + format_type_be(tupdesc1->attrs[i1]->atttypid), + format_type_be(tupdesc2->attrs[i2]->atttypid), + j + 1))); + + /* + * We consider two NULLs equal; NULL > not-NULL. + */ + if (!nulls1[i1] || !nulls2[i2]) { + if (nulls1[i1] || nulls2[i2]) { + result = false; + break; + } + + /* Compare the pair of elements */ + if (tupdesc1->attrs[i1]->attlen == -1) { + Size len1, len2; + + len1 = toast_raw_datum_size(values1[i1]); + len2 = toast_raw_datum_size(values2[i2]); + /* No need to de-toast if lengths don't match. */ + if (len1 != len2) + result = false; + else { + struct varlena *arg1val; + struct varlena *arg2val; + + arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]); + arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]); + + result = (memcmp(VARDATA_ANY(arg1val), VARDATA_ANY(arg2val), len1 - VARHDRSZ) == 0); + + /* Only free memory if it's a copy made here. */ + if ((Pointer)arg1val != (Pointer)values1[i1]) + pfree(arg1val); + if ((Pointer)arg2val != (Pointer)values2[i2]) + pfree(arg2val); + } + } else if (tupdesc1->attrs[i1]->attbyval) { + result = (memcmp(&(values1[i1]), &(values2[i2]), tupdesc1->attrs[i1]->attlen) == 0); + } else { + result = (memcmp(DatumGetPointer(values1[i1]), + DatumGetPointer(values2[i2]), tupdesc1->attrs[i1]->attlen) == 0); + } + if (!result) { + break; + } + } + + /* equal, so continue to next column */ + i1++, i2++, j++; + } + + /* + * If we didn't break out of the loop early, check for column count + * mismatch. (We do not report such mismatch if we found unequal column + * values; is that a feature or a bug?) + */ + if (result) { + if (i1 != ncolumns1 || i2 != ncolumns2) + ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare record types with different numbers of columns"))); + } + + pfree(values1); + pfree(nulls1); + pfree(values2); + pfree(nulls2); + ReleaseTupleDesc(tupdesc1); + ReleaseTupleDesc(tupdesc2); + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record1, 0); + PG_FREE_IF_COPY(record2, 1); + + return result; +} + +Datum record_image_ne(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo))); +} + +Datum record_image_lt(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0); +} + +Datum record_image_gt(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0); +} + +Datum record_image_le(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0); +} + +Datum record_image_ge(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0); +} + +Datum btrecordimagecmp(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(record_image_cmp(fcinfo)); +} +void record_set_extra(RecordIOData **my_extra, int ncolumns, Oid tupType, int32 tupTypmod, PG_FUNCTION_ARGS) { *my_extra = (RecordIOData*)fcinfo->flinfo->fn_extra; if ((*my_extra) == NULL || (*my_extra)->ncolumns != ncolumns) { - fcinfo->flinfo->fn_extra = MemoryContextAlloc( - fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); (*my_extra) = (RecordIOData*)fcinfo->flinfo->fn_extra; (*my_extra)->record_type = InvalidOid; (*my_extra)->record_typmod = 0; } if ((*my_extra)->record_type != tupType || (*my_extra)->record_typmod != tupTypmod) { - errno_t rc = memset_s((*my_extra), - sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData), - 0, + errno_t rc = memset_s((*my_extra), + sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData), 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); securec_check(rc, "\0", "\0"); (*my_extra)->record_type = tupType; diff --git a/src/common/backend/utils/adt/ruleutils.cpp b/src/common/backend/utils/adt/ruleutils.cpp index 0dedcbc6f..f382978ca 100755 --- a/src/common/backend/utils/adt/ruleutils.cpp +++ b/src/common/backend/utils/adt/ruleutils.cpp @@ -273,6 +273,7 @@ static char* generate_relation_name(Oid relid, List* namespaces); static char* generate_function_name( Oid funcid, int nargs, List* argnames, Oid* argtypes, bool was_variadic, bool* use_variadic_p); static char* generate_operator_name(Oid operid, Oid arg1, Oid arg2); +static void add_cast_to(StringInfo buf, Oid typid); static text* string_to_text(char* str); static char* flatten_reloptions(Oid relid); @@ -10139,6 +10140,83 @@ static char* generate_operator_name(Oid operid, Oid arg1, Oid arg2) return buf.data; } +/* + * generate_operator_clause --- generate a binary-operator WHERE clause + * + * This is used for internally-generated-and-executed SQL queries, where + * precision is essential and readability is secondary. The basic + * requirement is to append "leftop op rightop" to buf, where leftop and + * rightop are given as strings and are assumed to yield types leftoptype + * and rightoptype; the operator is identified by OID. The complexity + * comes from needing to be sure that the parser will select the desired + * operator when the query is parsed. We always name the operator using + * OPERATOR(schema.op) syntax, so as to avoid search-path uncertainties. + * We have to emit casts too, if either input isn't already the input type + * of the operator; else we are at the mercy of the parser's heuristics for + * ambiguous-operator resolution. The caller must ensure that leftop and + * rightop are suitable arguments for a cast operation; it's best to insert + * parentheses if they aren't just variables or parameters. + */ +void generate_operator_clause(StringInfo buf, const char* leftop, Oid leftoptype, Oid opoid, const char* rightop, + Oid rightoptype) +{ + HeapTuple opertup; + Form_pg_operator operform; + char *oprname = NULL; + char *nspname = NULL; + + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid)); + if (!HeapTupleIsValid(opertup)) { + ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for operator %u", opoid))); + } + operform = (Form_pg_operator)GETSTRUCT(opertup); + Assert(operform->oprkind == 'b'); + oprname = NameStr(operform->oprname); + + nspname = get_namespace_name(operform->oprnamespace, true); + + appendStringInfoString(buf, leftop); + if (leftoptype != operform->oprleft) { + add_cast_to(buf, operform->oprleft); + } + appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname)); + appendStringInfoString(buf, oprname); + appendStringInfo(buf, ") %s", rightop); + if (rightoptype != operform->oprright) { + add_cast_to(buf, operform->oprright); + } + ReleaseSysCache(opertup); +} + +/* + * Add a cast specification to buf. We spell out the type name the hard way, + * intentionally not using format_type_be(). This is to avoid corner cases + * for CHARACTER, BIT, and perhaps other types, where specifying the type + * using SQL-standard syntax results in undesirable data truncation. By + * doing it this way we can be certain that the cast will have default (-1) + * target typmod. + */ +static void add_cast_to(StringInfo buf, Oid typid) +{ + HeapTuple typetup; + Form_pg_type typform; + char *typname = NULL; + char *nspname = NULL; + + typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(typetup)) { + ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for type %u", typid))); + } + typform = (Form_pg_type)GETSTRUCT(typetup); + + typname = NameStr(typform->typname); + nspname = get_namespace_name(typform->typnamespace, true); + + appendStringInfo(buf, "::%s.%s", quote_identifier(nspname), quote_identifier(typname)); + + ReleaseSysCache(typetup); +} + /* * generate_collation_name * Compute the name to display for a collation specified by OID diff --git a/src/common/backend/utils/adt/xml.cpp b/src/common/backend/utils/adt/xml.cpp index ed5dfcd13..72c88fab5 100755 --- a/src/common/backend/utils/adt/xml.cpp +++ b/src/common/backend/utils/adt/xml.cpp @@ -2085,7 +2085,7 @@ static List* schema_get_xml_visible_tables(Oid nspid) initStringInfo(&query); appendStringInfo(&query, - "SELECT oid FROM pg_catalog.pg_class WHERE relnamespace = %u AND relkind IN ('r', 'v') AND " + "SELECT oid FROM pg_catalog.pg_class WHERE relnamespace = %u AND relkind IN ('r', 'm', 'v') AND " "pg_catalog.has_table_privilege (oid, 'SELECT') ORDER BY relname;", nspid); @@ -2111,7 +2111,7 @@ static List* database_get_xml_visible_tables(void) { /* At the moment there is no order required here. */ return query_to_oid_list( - "SELECT oid FROM pg_catalog.pg_class WHERE relkind IN ('r', 'v') AND pg_catalog.has_table_privilege " + "SELECT oid FROM pg_catalog.pg_class WHERE relkind IN ('r', 'm', 'v') AND pg_catalog.has_table_privilege " "(pg_class.oid, 'SELECT') AND relnamespace IN (" XML_VISIBLE_SCHEMAS ");"); } diff --git a/src/common/backend/utils/cache/relcache.cpp b/src/common/backend/utils/cache/relcache.cpp index c43a66c9c..1baabb1a3 100644 --- a/src/common/backend/utils/cache/relcache.cpp +++ b/src/common/backend/utils/cache/relcache.cpp @@ -1055,6 +1055,7 @@ static void relation_parse_rel_options(Relation relation, HeapTuple tuple) case RELKIND_TOASTVALUE: case RELKIND_INDEX: case RELKIND_VIEW: + case RELKIND_MATVIEW: break; default: return; @@ -1641,8 +1642,9 @@ static Relation relation_build_desc(Oid targetRelId, bool insertIt, bool buildke /* * if no such tuple exists, return NULL */ - if (!HeapTupleIsValid(pg_class_tuple)) + if (!HeapTupleIsValid(pg_class_tuple)) { return NULL; + } /* * get information from the pg_class_tuple @@ -2424,6 +2426,9 @@ static void formr_desc(const char* relationName, Oid relationReltype, bool issha /* formr_desc is used only for permanent relations */ relation->rd_rel->relpersistence = RELPERSISTENCE_PERMANENT; + /* ... and they're always populated, too */ + relation->relispopulated = true; + relation->rd_rel->relpages = 0; relation->rd_rel->reltuples = 0; relation->rd_rel->relallvisible = 0; @@ -3724,6 +3729,9 @@ Relation RelationBuildLocalRelation(const char* relname, Oid relnamespace, Tuple break; } + /* we keep this flag and set it true for all relations here, which may be useful for future */ + rel->relispopulated = true; + /* * Insert relation physical and logical identifiers (OIDs) into the right * places. For a mapped relation, we set relfilenode to zero and rely on diff --git a/src/common/pl/plpgsql/src/pl_comp.cpp b/src/common/pl/plpgsql/src/pl_comp.cpp index 8908ab5c0..3021b1ae7 100755 --- a/src/common/pl/plpgsql/src/pl_comp.cpp +++ b/src/common/pl/plpgsql/src/pl_comp.cpp @@ -1902,11 +1902,11 @@ PLpgSQL_type* plpgsql_parse_cwordtype(List* idents) class_struct = (Form_pg_class)GETSTRUCT(classtup); /* - * It must be a relation, sequence, view, composite type, or foreign table + * It must be a relation, sequence, view, materialized view, composite type, or foreign table */ if (class_struct->relkind != RELKIND_RELATION && class_struct->relkind != RELKIND_SEQUENCE && class_struct->relkind != RELKIND_VIEW && class_struct->relkind != RELKIND_COMPOSITE_TYPE && - class_struct->relkind != RELKIND_FOREIGN_TABLE) { + class_struct->relkind != RELKIND_FOREIGN_TABLE && class_struct->relkind != RELKIND_MATVIEW) { goto done; } /* @@ -2259,10 +2259,10 @@ static PLpgSQL_row* build_row_from_class(Oid class_oid) class_struct = RelationGetForm(rel); relname = RelationGetRelationName(rel); - /* accept relation, sequence, view, composite type, or foreign table */ + /* accept relation, sequence, view, composite type, materialized view or foreign table */ if (class_struct->relkind != RELKIND_RELATION && class_struct->relkind != RELKIND_SEQUENCE && class_struct->relkind != RELKIND_VIEW && class_struct->relkind != RELKIND_COMPOSITE_TYPE && - class_struct->relkind != RELKIND_FOREIGN_TABLE) { + class_struct->relkind != RELKIND_FOREIGN_TABLE && class_struct->relkind != RELKIND_MATVIEW) { ereport(ERROR, (errmodule(MOD_PLSQL), errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/gausskernel/optimizer/commands/Makefile b/src/gausskernel/optimizer/commands/Makefile index d916b45b3..a9da7dbad 100644 --- a/src/gausskernel/optimizer/commands/Makefile +++ b/src/gausskernel/optimizer/commands/Makefile @@ -20,7 +20,7 @@ OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ collationcmds.o constraint.o conversioncmds.o copy.o createas.o \ dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \ foreigncmds.o functioncmds.o \ - indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ + indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ schemacmds.o seclabel.o sec_rls_cmds.o sequence.o tablecmds.o tablespace.o trigger.o \ tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \ diff --git a/src/gausskernel/optimizer/commands/alter.cpp b/src/gausskernel/optimizer/commands/alter.cpp index 37489158c..a462f59be 100755 --- a/src/gausskernel/optimizer/commands/alter.cpp +++ b/src/gausskernel/optimizer/commands/alter.cpp @@ -133,6 +133,7 @@ void ExecRenameStmt(RenameStmt* stmt) case OBJECT_TABLE: case OBJECT_SEQUENCE: case OBJECT_VIEW: + case OBJECT_MATVIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: RenameRelation(stmt); @@ -221,6 +222,7 @@ void ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt* stmt) case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: + case OBJECT_MATVIEW: case OBJECT_FOREIGN_TABLE: if (stmt->objectType == OBJECT_FOREIGN_TABLE) ereport(ERROR, diff --git a/src/gausskernel/optimizer/commands/analyze.cpp b/src/gausskernel/optimizer/commands/analyze.cpp index d6339a20e..4112321a3 100755 --- a/src/gausskernel/optimizer/commands/analyze.cpp +++ b/src/gausskernel/optimizer/commands/analyze.cpp @@ -516,11 +516,11 @@ static void analyze_rel_internal(Relation onerel, VacuumStmt* vacstmt, BufferAcc } /* - * Check that it's a plain table or foreign table; we used to do this in - * get_rel_oids() but seems safer to check after we've locked the - * relation. - */ - if (onerel->rd_rel->relkind == RELKIND_RELATION) { + * Check that it's a plain table, materialized view, or foreign table; we + * used to do this in get_rel_oids() but seems safer to check after we've + * locked the relation. + */ + if (onerel->rd_rel->relkind == RELKIND_RELATION || onerel->rd_rel->relkind == RELKIND_MATVIEW) { /* Regular table, so we'll use the regular row acquisition function */ /* Also get regular table's size */ if (RelationIsPartitioned(onerel)) { diff --git a/src/gausskernel/optimizer/commands/cluster.cpp b/src/gausskernel/optimizer/commands/cluster.cpp index 609e5bd7d..c5756519d 100644 --- a/src/gausskernel/optimizer/commands/cluster.cpp +++ b/src/gausskernel/optimizer/commands/cluster.cpp @@ -475,6 +475,18 @@ void cluster_rel(Oid tableOid, Oid partitionOid, Oid indexOid, bool recheck, boo if (OidIsValid(indexOid)) check_index_is_clusterable(OldHeap, indexOid, recheck, lockMode, &amid); + /* + * Quietly ignore the request if this is a materialized view which has not + * been populated from its query. No harm is done because there is no data + * to deal with, and we don't want to throw an error if this is part of a + * multi-relation request -- for example, CLUSTER was run on the entire + * database. + */ + if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW && !RelationIsPopulated(OldHeap)) { + relation_close(OldHeap, AccessExclusiveLock); + return; + } + /* * There is no data on Coordinator except system tables, it is no sense to rewrite a relation * on Coordinator.so we can skip to vacuum full user-define tables @@ -1278,6 +1290,8 @@ Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, int lockMode) bool isNull = false; int ss_c = 0; HashBucketInfo bucketinfo; + Oid namespaceid; + char relpersistence; OldHeap = heap_open(OIDOldHeap, lockMode); OldHeapDesc = RelationGetDescr(OldHeap); @@ -1298,6 +1312,9 @@ Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, int lockMode) if (isNull) reloptions = (Datum)0; + namespaceid = RelationGetNamespace(OldHeap); + relpersistence = OldHeap->rd_rel->relpersistence; + /* * Create the new heap, using a temporary name in the same namespace as * the existing table. NOTE: there is some risk of collision with user @@ -1315,7 +1332,7 @@ Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, int lockMode) bucketinfo.bucketOid = RelationGetBucketOid(OldHeap); OIDNewHeap = heap_create_with_catalog(NewHeapName, - RelationGetNamespace(OldHeap), + namespaceid, NewTableSpace, InvalidOid, InvalidOid, @@ -1323,8 +1340,8 @@ Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, int lockMode) OldHeap->rd_rel->relowner, OldHeapDesc, NIL, - OldHeap->rd_rel->relkind, - OldHeap->rd_rel->relpersistence, + RELKIND_RELATION, + relpersistence, false, RelationIsMapped(OldHeap), true, diff --git a/src/gausskernel/optimizer/commands/comment.cpp b/src/gausskernel/optimizer/commands/comment.cpp index 3590bf4ed..d96cfcd5a 100644 --- a/src/gausskernel/optimizer/commands/comment.cpp +++ b/src/gausskernel/optimizer/commands/comment.cpp @@ -91,20 +91,22 @@ void CommentObject(CommentStmt* stmt) case OBJECT_COLUMN: /* - * Allow comments only on columns of tables, views, composite - * types, and foreign tables (which are the only relkinds for - * which pg_dump will dump per-column comments). In particular we - * wish to disallow comments on index columns, because the naming - * of an index's columns may change across PG versions, so dumping - * per-column comments could create reload failures. + * Allow comments only on columns of tables, views, materialized + * views, composite types, and foreign tables (which are the only + * relkinds for which pg_dump will dump per-column comments). In + * particular we wish to disallow comments on index columns, + * because the naming of an index's columns may change across PG + * versions, so dumping per-column comments could create reload + * failures. */ if (relation != NULL) { if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_VIEW && + relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, view, composite type, or foreign table", + errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table", RelationGetRelationName(relation)))); } break; diff --git a/src/gausskernel/optimizer/commands/copy.cpp b/src/gausskernel/optimizer/commands/copy.cpp index f83538db7..5d733e1f3 100644 --- a/src/gausskernel/optimizer/commands/copy.cpp +++ b/src/gausskernel/optimizer/commands/copy.cpp @@ -2235,6 +2235,11 @@ static CopyState BeginCopyTo( (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot copy from view \"%s\"", RelationGetRelationName(rel)), errhint("Try the COPY (SELECT ...) TO variant."))); + else if (rel->rd_rel->relkind == RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot copy from materialized view \"%s\"", RelationGetRelationName(rel)), + errhint("Try the COPY (SELECT ...) TO variant."))); else if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) { ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -3404,7 +3409,11 @@ static uint64 CopyFrom(CopyState cstate) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot copy to view \"%s\"", RelationGetRelationName(cstate->rel)))); - else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) { + else if (cstate->rel->rd_rel->relkind == RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot copy to materialized view \"%s\"", RelationGetRelationName(cstate->rel)))); + else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) { if (!CheckSupportedFDWType(RelationGetRelid(cstate->rel))) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/gausskernel/optimizer/commands/createas.cpp b/src/gausskernel/optimizer/commands/createas.cpp index f278b36f8..7a6a9d944 100755 --- a/src/gausskernel/optimizer/commands/createas.cpp +++ b/src/gausskernel/optimizer/commands/createas.cpp @@ -1,14 +1,17 @@ /* ------------------------------------------------------------------------- * * createas.cpp - * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO + * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO. + * Since CREATE MATERIALIZED VIEW shares syntax and most behaviors, + * we implement that here, too. * * We implement this by diverting the query's normal output to a * specialized DestReceiver type. * - * Formerly, this command was implemented as a variant of SELECT, which led + * Formerly, CTAS was implemented as a variant of SELECT, which led * to assorted legacy behaviors that we still try to preserve, notably that - * we must return a tuples-processed count in the completionTag. + * we must return a tuples-processed count in the completionTag. (We no + * longer do that for CTAS ... WITH NO DATA, however.) * * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group @@ -29,8 +32,13 @@ #include "access/xlog.h" #include "catalog/toasting.h" #include "commands/createas.h" +#include "commands/matview.h" #include "commands/prepare.h" #include "commands/tablecmds.h" +#include "commands/view.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" #include "parser/parse_clause.h" #include "rewrite/rewriteHandler.h" #include "storage/smgr.h" @@ -39,6 +47,7 @@ #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/rel_gs.h" +#include "utils/sec_rls_utils.h" #include "utils/snapmgr.h" typedef struct { @@ -46,16 +55,145 @@ typedef struct { IntoClause* into; /* target relation specification */ /* These fields are filled by intorel_startup: */ Relation rel; /* relation to write to */ + ObjectAddress reladdr; /* address of rel, for ExecCreateTableAs */ CommandId output_cid; /* cmin to insert in output tuples */ int hi_options; /* heap_insert performance options */ BulkInsertState bistate; /* bulk insert state */ } DR_intorel; +/* utility functions for CTAS definition creation */ +static ObjectAddress create_ctas_internal(List* attrList, IntoClause* into); +static ObjectAddress create_ctas_nodata(const List* tlist, IntoClause* into); +/* the OID of the created table, for ExecCreateTableAs consumption */ static void intorel_startup(DestReceiver* self, int operation, TupleDesc typeinfo); static void intorel_receive(TupleTableSlot* slot, DestReceiver* self); static void intorel_shutdown(DestReceiver* self); static void intorel_destroy(DestReceiver* self); +/* + * create_ctas_internal + * + * Internal utility used for the creation of the definition of a relation + * created via CREATE TABLE AS or a materialized view. Caller needs to + * provide a list of attributes (ColumnDef nodes). + */ +static ObjectAddress create_ctas_internal(List* attrList, IntoClause* into) +{ + CreateStmt *create = makeNode(CreateStmt); + bool is_matview; + char relkind; + Datum toast_options; + static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + ObjectAddress intoRelationAddr; + Oid relOid; + + /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ + is_matview = (into->viewQuery != NULL); + relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION; + + /* + * Create the target relation by faking up a CREATE TABLE parsetree and + * passing it to DefineRelation. + */ + create->relation = into->rel; + create->tableElts = attrList; + create->inhRelations = NIL; + create->ofTypename = NULL; + create->constraints = NIL; + create->options = into->options; + create->oncommit = into->onCommit; + create->tablespacename = into->tableSpaceName; + create->if_not_exists = false; + + /* + * Create the relation. (This will error out if there's an existing view, + * so we don't need more code to complain if "replace" is false.) + */ + relOid = DefineRelation(create, relkind, InvalidOid); + intoRelationAddr.classId = RelationRelationId; + intoRelationAddr.objectId = relOid; + intoRelationAddr.objectSubId = 0; + + /* + * If necessary, create a TOAST table for the target table. Note that + * NewRelationCreateToastTable ends with CommandCounterIncrement(), so + * that the TOAST table will be visible for insertion. + */ + CommandCounterIncrement(); + + /* parse and validate reloptions for the toast table */ + toast_options = transformRelOptions((Datum)0, create->options, "toast", validnsps, true, false); + + (void)heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); + + NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); + + /* Create the "view" part of a materialized view. */ + if (is_matview) { + /* StoreViewQuery scribbles on tree, so make a copy */ + Query *query = (Query*)copyObject(into->viewQuery); + + StoreViewQuery(intoRelationAddr.objectId, query, false); + CommandCounterIncrement(); + } + + return intoRelationAddr; +} + +static ObjectAddress create_ctas_nodata(const List* tlist, IntoClause* into) +{ + List *attrList; + ListCell *t, *lc; + + /* + * Build list of ColumnDefs from non-junk elements of the tlist. If a + * column name list was specified in CREATE TABLE AS, override the column + * names in the query. (Too few column names are OK, too many are not.) + */ + attrList = NIL; + lc = list_head(into->colNames); + foreach (t, tlist) { + TargetEntry* tle = (TargetEntry*)lfirst(t); + + if (!tle->resjunk) { + ColumnDef *col; + char *colname; + + if (lc) { + colname = strVal(lfirst(lc)); + lc = lnext(lc); + } else + colname = tle->resname; + + col = makeColumnDef(colname, exprType((Node*)tle->expr), exprTypmod((Node*)tle->expr), + exprCollation((Node*)tle->expr)); + + /* + * It's possible that the column is of a collatable type but the + * collation could not be resolved, so double-check. (We must + * check this here because DefineRelation would adopt the type's + * default collation rather than complaining.) + */ + if (!OidIsValid(col->collOid) && type_is_collatable(col->typname->typeOid)) + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("no collation was derived for column \"%s\" with collatable type %s", + col->colname, + format_type_be(col->typname->typeOid)), + errhint("Use the COLLATE clause to set the collation explicitly."))); + + attrList = lappend(attrList, col); + } + } + + if (lc != NULL) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too many column names were specified"))); + + /* Create the relation definition using the ColumnDef list */ + return create_ctas_internal(attrList, into); +} + + /* * ExecCreateTableAs -- execute a CREATE TABLE AS command */ @@ -63,11 +201,14 @@ void ExecCreateTableAs(CreateTableAsStmt* stmt, const char* queryString, ParamLi { Query* query = (Query*)stmt->query; IntoClause* into = stmt->into; + bool is_matview = (into->viewQuery != NULL); DestReceiver* dest = NULL; - List* rewritten = NIL; + Oid save_userid = InvalidOid; + int save_sec_context = 0; + int save_nestlevel = 0; + List *rewritten = NULL; PlannedStmt* plan = NULL; QueryDesc* queryDesc = NULL; - ScanDirection dir; /* * Create the tuple receiver object and insert info it will need @@ -82,92 +223,110 @@ void ExecCreateTableAs(CreateTableAsStmt* stmt, const char* queryString, ParamLi if (query->commandType == CMD_UTILITY && IsA(query->utilityStmt, ExecuteStmt)) { ExecuteStmt* estmt = (ExecuteStmt*)query->utilityStmt; + Assert(!is_matview); /* excluded by syntax */ ExecuteQuery(estmt, into, queryString, params, dest, completionTag); - return; } Assert(query->commandType == CMD_SELECT); /* - * Parse analysis was done already, but we still have to run the rule - * rewriter. We do not do AcquireRewriteLocks: we assume the query either - * came straight from the parser, or suitable locks were acquired by - * plancache.c. - * - * Because the rewriter and planner tend to scribble on the input, we make - * a preliminary copy of the source querytree. This prevents problems in - * the case that CTAS is in a portal or plpgsql function and is executed - * repeatedly. (See also the same hack in EXPLAIN and PREPARE.) + * For materialized views, lock down security-restricted operations and + * arrange to make GUC variable changes local to this command. This is + * not necessary for security, but this keeps the behavior similar to + * REFRESH MATERIALIZED VIEW. Otherwise, one could create a materialized + * view not possible to refresh. */ - rewritten = QueryRewrite((Query*)copyObject(stmt->query)); + if (is_matview) { + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(save_userid, save_sec_context | SECURITY_RESTRICTED_OPERATION); + save_nestlevel = NewGUCNestLevel(); + } + if (into->skipData) { + /* + * If WITH NO DATA was specified, do not go through the rewriter, + * planner and executor. Just define the relation using a code path + * similar to CREATE VIEW. This avoids dump/restore problems stemming + * from running the planner before all dependencies are set up. + */ + (void)create_ctas_nodata(query->targetList, into); + } else { + /* + * Parse analysis was done already, but we still have to run the rule + * rewriter. We do not do AcquireRewriteLocks: we assume the query either + * came straight from the parser, or suitable locks were acquired by + * plancache.c. + * + * Because the rewriter and planner tend to scribble on the input, we make + * a preliminary copy of the source querytree. This prevents problems in + * the case that CTAS is in a portal or plpgsql function and is executed + * repeatedly. (See also the same hack in EXPLAIN and PREPARE.) + */ + rewritten = QueryRewrite((Query*)copyObject(query)); - /* SELECT should never rewrite to more or less than one SELECT query */ - if (list_length(rewritten) != 1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unexpected rewrite result for CREATE TABLE AS SELECT"))); - query = (Query*)linitial(rewritten); - Assert(query->commandType == CMD_SELECT); + /* SELECT should never rewrite to more or less than one SELECT query */ + if (list_length(rewritten) != 1) + elog(ERROR, "unexpected rewrite result for %s", + is_matview ? "CREATE MATERIALIZED VIEW" : "CREATE TABLE AS SELECT"); + query = (Query*)linitial(rewritten); + Assert(query->commandType == CMD_SELECT); - /* plan the query */ - plan = pg_plan_query(query, 0, params); + /* plan the query */ + plan = pg_plan_query(query, 0, params); - /* - * Use a snapshot with an updated command ID to ensure this query sees - * results of any previously executed queries. (This could only matter if - * the planner executed an allegedly-stable function that changed the - * database contents, but let's do it anyway to be parallel to the EXPLAIN - * code path.) - */ - PushCopiedSnapshot(GetActiveSnapshot()); - UpdateActiveSnapshotCommandId(); + /* + * Use a snapshot with an updated command ID to ensure this query sees + * results of any previously executed queries. (This could only matter if + * the planner executed an allegedly-stable function that changed the + * database contents, but let's do it anyway to be parallel to the EXPLAIN + * code path.) + */ + PushCopiedSnapshot(GetActiveSnapshot()); + UpdateActiveSnapshotCommandId(); - /* Create a QueryDesc, redirecting output to our tuple receiver */ - queryDesc = CreateQueryDesc(plan, queryString, GetActiveSnapshot(), InvalidSnapshot, dest, params, 0); + /* Create a QueryDesc, redirecting output to our tuple receiver */ + queryDesc = CreateQueryDesc(plan, queryString, GetActiveSnapshot(), InvalidSnapshot, dest, params, 0); - if (ENABLE_WORKLOAD_CONTROL && (IS_PGXC_COORDINATOR || IS_SINGLE_NODE)) { - /* Check if need track resource */ - u_sess->exec_cxt.need_track_resource = WLMNeedTrackResource(queryDesc); + if (ENABLE_WORKLOAD_CONTROL && (IS_PGXC_COORDINATOR || IS_SINGLE_NODE)) { + /* Check if need track resource */ + u_sess->exec_cxt.need_track_resource = WLMNeedTrackResource(queryDesc); + } + + /* call ExecutorStart to prepare the plan for execution */ + ExecutorStart(queryDesc, GetIntoRelEFlags(into)); + + /* workload client manager */ + if (ENABLE_WORKLOAD_CONTROL) { + WLMInitQueryPlan(queryDesc); + dywlm_client_manager(queryDesc); + } + + /* run the plan to completion */ + ExecutorRun(queryDesc, ForwardScanDirection, 0L); + + /* save the rowcount if we're given a completionTag to fill */ + if (completionTag != NULL) { + errno_t rc; + rc = snprintf_s(completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1, "SELECT %lu", + queryDesc->estate->es_processed); + securec_check_ss(rc, "\0", "\0"); + } + + /* and clean up */ + ExecutorFinish(queryDesc); + ExecutorEnd(queryDesc); + + FreeQueryDesc(queryDesc); + + PopActiveSnapshot(); } - /* call ExecutorStart to prepare the plan for execution */ - ExecutorStart(queryDesc, GetIntoRelEFlags(into)); + if (is_matview) { + /* Roll back any GUC changes */ + AtEOXact_GUC(false, save_nestlevel); - /* workload client manager */ - if (ENABLE_WORKLOAD_CONTROL) { - WLMInitQueryPlan(queryDesc); - dywlm_client_manager(queryDesc); + /* Restore userid and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); } - - /* - * Normally, we run the plan to completion; but if skipData is specified, - * just do tuple receiver startup and shutdown. - */ - if (into->skipData) - dir = NoMovementScanDirection; - else - dir = ForwardScanDirection; - - /* run the plan */ - ExecutorRun(queryDesc, dir, 0L); - - /* save the rowcount if we're given a completionTag to fill */ - if (completionTag != NULL) { - errno_t rc; - rc = snprintf_s(completionTag, - COMPLETION_TAG_BUFSIZE, - COMPLETION_TAG_BUFSIZE - 1, - "SELECT %lu", - queryDesc->estate->es_processed); - securec_check_ss(rc, "\0", "\0"); - } - - /* and clean up */ - ExecutorFinish(queryDesc); - ExecutorEnd(queryDesc); - - FreeQueryDesc(queryDesc); - - PopActiveSnapshot(); } /* @@ -180,15 +339,23 @@ void ExecCreateTableAs(CreateTableAsStmt* stmt, const char* queryString, ParamLi */ int GetIntoRelEFlags(IntoClause* intoClause) { + int flags; /* * We need to tell the executor whether it has to produce OIDs or not, * because it doesn't have enough information to do so itself (since we * can't build the target relation until after ExecutorStart). + * + * Disallow the OIDS option for materialized views. */ - if (interpretOidsOption(intoClause->options)) - return EXEC_FLAG_WITH_OIDS; + if (interpretOidsOption(intoClause->options, (intoClause->viewQuery == NULL))) + flags = EXEC_FLAG_WITH_OIDS; else - return EXEC_FLAG_WITHOUT_OIDS; + flags = EXEC_FLAG_WITHOUT_OIDS; + + if (intoClause->skipData) + flags |= EXEC_FLAG_WITH_NO_DATA; + + return flags; } /* @@ -219,32 +386,20 @@ static void intorel_startup(DestReceiver* self, int operation, TupleDesc typeinf { DR_intorel* myState = (DR_intorel*)self; IntoClause* into = myState->into; - CreateStmt* create = NULL; - Oid intoRelationId; + bool is_matview; + char relkind; + List* attrList; + ObjectAddress intoRelationAddr; Relation intoRelationDesc; - RangeTblEntry* rte = NULL; - Datum toast_options; - ListCell* lc = NULL; + RangeTblEntry* rte; + ListCell* lc; int attnum; - static const char* const validnsps[] = HEAP_RELOPT_NAMESPACES; Assert(into != NULL); /* else somebody forgot to set it */ - /* - * Create the target relation by faking up a CREATE TABLE parsetree and - * passing it to DefineRelation. - */ - create = makeNode(CreateStmt); - create->relation = into->rel; - create->tableElts = NIL; /* will fill below */ - create->inhRelations = NIL; - create->ofTypename = NULL; - create->constraints = NIL; - create->options = into->options; - create->oncommit = into->onCommit; - create->row_compress = into->row_compress; - create->tablespacename = into->tableSpaceName; - create->if_not_exists = false; + /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ + is_matview = (into->viewQuery != NULL); + relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION; /* * Build column definitions using "pre-cooked" type and collation info. If @@ -252,40 +407,20 @@ static void intorel_startup(DestReceiver* self, int operation, TupleDesc typeinf * column names derived from the query. (Too few column names are OK, too * many are not.) */ + attrList = NIL; lc = list_head(into->colNames); for (attnum = 0; attnum < typeinfo->natts; attnum++) { Form_pg_attribute attribute = typeinfo->attrs[attnum]; - ColumnDef* col = makeNode(ColumnDef); - TypeName* coltype = makeNode(TypeName); + ColumnDef *col; + char *colname; - if (lc != NULL) { - col->colname = strVal(lfirst(lc)); + if (lc) { + colname = strVal(lfirst(lc)); lc = lnext(lc); } else - col->colname = NameStr(attribute->attname); - col->typname = coltype; - col->inhcount = 0; - col->is_local = true; - col->is_not_null = false; - col->is_from_type = false; - col->storage = 0; - col->kvtype = attribute->attkvtype; - col->cmprs_mode = attribute->attcmprmode; - col->raw_default = NULL; - col->cooked_default = NULL; - col->collClause = NULL; - col->collOid = attribute->attcollation; - col->constraints = NIL; - col->fdwoptions = NIL; + colname = NameStr(attribute->attname); - coltype->names = NIL; - coltype->typeOid = attribute->atttypid; - coltype->setof = false; - coltype->pct_type = false; - coltype->typmods = NIL; - coltype->typemod = attribute->atttypmod; - coltype->arrayBounds = NIL; - coltype->location = -1; + col = makeColumnDef(colname, attribute->atttypid, attribute->atttypmod, attribute->attcollation); /* * It's possible that the column is of a collatable type but the @@ -293,43 +428,29 @@ static void intorel_startup(DestReceiver* self, int operation, TupleDesc typeinf * this here because DefineRelation would adopt the type's default * collation rather than complaining.) */ - if (!OidIsValid(col->collOid) && type_is_collatable(coltype->typeOid)) - ereport(ERROR, + if (!OidIsValid(col->collOid) && type_is_collatable(col->typname->typeOid)) + ereport(ERROR, (errcode(ERRCODE_INDETERMINATE_COLLATION), - errmsg("no collation was derived for column \"%s\" with collatable type %s", + errmsg("no collation was derived for column \"%s\" with collatable type %s", col->colname, - format_type_be(coltype->typeOid)), - errhint("Use the COLLATE clause to set the collation explicitly."))); + format_type_be(col->typname->typeOid)), + errhint("Use the COLLATE clause to set the collation explicitly."))); - create->tableElts = lappend(create->tableElts, col); + attrList = lappend(attrList, col); } if (lc != NULL) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("CREATE TABLE AS specifies too many column names"))); + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too many column names were specified"))); /* * Actually create the target table */ - intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid); - - /* - * If necessary, create a TOAST table for the target table. Note that - * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that - * the TOAST table will be visible for insertion. - */ - CommandCounterIncrement(); - - /* parse and validate reloptions for the toast table */ - toast_options = transformRelOptions((Datum)0, create->options, "toast", validnsps, true, false); - - (void)heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); - - AlterTableCreateToastTable(intoRelationId, toast_options); + intoRelationAddr = create_ctas_internal(attrList, into); /* * Finally we can open the target table */ - intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); + intoRelationDesc = heap_open(intoRelationAddr.objectId, AccessExclusiveLock); /* * Check INSERT permission on the constructed table. @@ -339,19 +460,33 @@ static void intorel_startup(DestReceiver* self, int operation, TupleDesc typeinf */ rte = makeNode(RangeTblEntry); rte->rtekind = RTE_RELATION; - rte->relid = intoRelationId; - rte->relkind = RELKIND_RELATION; + rte->relid = intoRelationAddr.objectId; + rte->relkind = relkind; rte->requiredPerms = ACL_INSERT; for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++) - rte->insertedCols = bms_add_member(rte->insertedCols, attnum - FirstLowInvalidHeapAttributeNumber); + rte->insertedCols = bms_add_member(rte->insertedCols, - (void)ExecCheckRTPerms(list_make1(rte), true); + attnum - FirstLowInvalidHeapAttributeNumber); + ExecCheckRTPerms(list_make1(rte), true); + + /* + * Make sure the constructed table does not have RLS enabled. + * + * check_enable_rls() will ereport(ERROR) itself if the user has requested + * something invalid, and otherwise will return RLS_ENABLED if RLS should + * be enabled here. We don't actually support that currently, so throw + * our own ereport(ERROR) if that happens. + */ + if (CheckEnableRlsPolicies(intoRelationDesc, GetUserId()) == RLS_ENABLED) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errmsg("policies not yet implemented for this command")))); /* * Fill private fields of myState for use by later routines */ myState->rel = intoRelationDesc; + myState->reladdr = intoRelationAddr; myState->output_cid = GetCurrentCommandId(true); /* diff --git a/src/gausskernel/optimizer/commands/explain.cpp b/src/gausskernel/optimizer/commands/explain.cpp index 35b766b54..a627fd21c 100755 --- a/src/gausskernel/optimizer/commands/explain.cpp +++ b/src/gausskernel/optimizer/commands/explain.cpp @@ -611,23 +611,32 @@ static void ExplainOneQuery( CreateTableAsStmt* ctas = (CreateTableAsStmt*)query->utilityStmt; List* rewritten = NIL; - // INSERT INTO statement needs target table to be created first, - // so we just support EXPLAIN ANALYZE. - // - if (!es->analyze) { - const char* stmt = ctas->is_select_into ? "SELECT INTO" : "CREATE TABLE AS SELECT"; - - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("EXPLAIN %s requires ANALYZE", stmt))); - } - // CREATE TABLE AS SELECT and SELECT INTO are rewritten so that the // target table is created first. The SELECT query is then transformed // into an INSERT INTO statement. // - rewritten = QueryRewriteCTAS(query); - AssertEreport(list_length(rewritten) == 1, MOD_EXECUTOR, "unexpect list length"); - ExplainOneQuery((Query*)linitial(rewritten), ctas->into, es, queryString, params); + if (ctas->relkind == OBJECT_MATVIEW) { + query = (Query*)ctas->query; + rewritten = QueryRewrite((Query*) copyObject(query)); + } else { + rewritten = QueryRewriteCTAS(query); + } + Assert(list_length(rewritten) == 1); + // INSERT INTO statement needs target table to be created first, + // so we just support EXPLAIN ANALYZE. + // + if (!es->analyze) { + if (ctas->relkind == OBJECT_MATVIEW) { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("EXPLAIN CREATE MATERIALIZED VIEW requires ANALYZE"))); + } + const char* stmt = ctas->is_select_into ? "SELECT INTO" : "CREATE TABLE AS SELECT"; + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("EXPLAIN %s requires ANALYZE", stmt))); + } + + ExplainOneQuery((Query*) linitial(rewritten), ctas->into, es, queryString, params); + return; } @@ -885,9 +894,13 @@ void ExplainOnePlan( UpdateActiveSnapshotCommandId(); /* - * Normally we discard the query's output. + * Normally we discard the query's output, but if explaining CREATE TABLE + * AS, we'd better use the appropriate tuple receiver. */ - dest = None_Receiver; + if (into != NULL && into->viewQuery != NULL) + dest = CreateIntoRelDestReceiver(into); + else + dest = None_Receiver; /* Create a QueryDesc for the query */ queryDesc = CreateQueryDesc( diff --git a/src/gausskernel/optimizer/commands/indexcmds.cpp b/src/gausskernel/optimizer/commands/indexcmds.cpp index e1a1413e2..275f58c25 100755 --- a/src/gausskernel/optimizer/commands/indexcmds.cpp +++ b/src/gausskernel/optimizer/commands/indexcmds.cpp @@ -2476,14 +2476,14 @@ void ReindexDatabase(const char* databaseName, bool do_system, bool do_user, Ada /* * Scan pg_class to build a list of the relations we need to reindex. * - * We only consider plain relations here (toast rels will be processed - * indirectly by reindex_relation). + * We only consider plain relations and materialized views here (toast + * rels will be processed indirectly by reindex_relation). */ relationRelation = heap_open(RelationRelationId, AccessShareLock); scan = heap_beginscan(relationRelation, SnapshotNow, 0, NULL); while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { Form_pg_class classtuple = (Form_pg_class)GETSTRUCT(tuple); - if (classtuple->relkind != RELKIND_RELATION) + if (classtuple->relkind != RELKIND_RELATION && classtuple->relkind != RELKIND_MATVIEW) continue; /* Skip temp tables of other backends; we can't reindex them at all */ @@ -2777,7 +2777,7 @@ static bool relationHasInformationalPrimaryKey(const Relation rel) */ static void handleErrMsgForInfoCnstrnt(const IndexStmt* stmt, const Relation rel) { - if (rel->rd_rel->relkind != RELKIND_RELATION) { + if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_MATVIEW) { Oid relationId = RelationGetRelid(rel); /* * @hdfs @@ -2810,7 +2810,8 @@ static void handleErrMsgForInfoCnstrnt(const IndexStmt* stmt, const Relation rel } } else { ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", RelationGetRelationName(rel)))); + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table or materialized view", RelationGetRelationName(rel)))); } } } diff --git a/src/gausskernel/optimizer/commands/matview.cpp b/src/gausskernel/optimizer/commands/matview.cpp new file mode 100644 index 000000000..19fcb0be2 --- /dev/null +++ b/src/gausskernel/optimizer/commands/matview.cpp @@ -0,0 +1,367 @@ +/* ------------------------------------------------------------------------- + * + * matview.c + * materialized view support + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/matview.c + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/multixact.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_am.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "commands/cluster.h" +#include "commands/matview.h" +#include "commands/tablecmds.h" +#include "commands/tablespace.h" +#include "executor/executor.h" +#include "executor/spi.h" +#include "miscadmin.h" +#include "parser/parse_relation.h" +#include "pgstat.h" +#include "rewrite/rewriteHandler.h" +#include "storage/smgr.h" +#include "tcop/tcopprot.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" +#include "storage/lmgr.h" + +typedef struct { + DestReceiver pub; /* publicly-known function pointers */ + Oid transientoid; /* OID of new heap into which to store */ + /* These fields are filled by transientrel_startup: */ + Relation transientrel; /* relation to write to */ + CommandId output_cid; /* cmin to insert in output tuples */ + int hi_options; /* heap_insert performance options */ + BulkInsertState bistate; /* bulk insert state */ +} DR_transientrel; + +static const int g_matview_maintenance_depth = 0; +static void transientrel_startup(DestReceiver* self, int operation, TupleDesc typeinfo); +static void transientrel_receive(TupleTableSlot* slot, DestReceiver* self); +static void transientrel_shutdown(DestReceiver* self); +static void transientrel_destroy(DestReceiver* self); +static uint64 refresh_matview_datafill(DestReceiver* dest, Query* query, const char* queryString); + +static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap); + +/* + * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command + * + * This refreshes the materialized view by creating a new table and swapping + * the relfilenodes of the new table and the old materialized view, so the OID + * of the original materialized view is preserved. Thus we do not lose GRANT + * nor references to this materialized view. + * + * If WITH NO DATA was specified, this is effectively like a TRUNCATE; + * otherwise it is like a TRUNCATE followed by an INSERT using the SELECT + * statement associated with the materialized view. The statement node's + * skipData field shows whether the clause was used. + * + * Indexes are rebuilt too, via REINDEX. Since we are effectively bulk-loading + * the new heap, it's better to create the indexes afterwards than to fill them + * incrementally while we load. + * + * The matview's "populated" state is changed based on whether the contents + * reflect the result set of the materialized view's query. + */ +void ExecRefreshMatView(const RefreshMatViewStmt* stmt, const char* queryString, ParamListInfo params, + char* completionTag) +{ + Oid matviewOid; + Relation matviewRel; + RewriteRule *rule; + List *actions; + Query *dataQuery; + Oid tableSpace; + Oid relowner; + Oid OIDNewHeap; + DestReceiver *dest; + uint64 processed = 0; + Oid save_userid; + int save_sec_context; + int save_nestlevel; + + /* the flag will be init or reset at start of PostgresMain loop. */ + u_sess->cmd_cxt.isUnderRefreshMatview = true; + /* + * Get a lock until end of transaction. + */ + matviewOid = RangeVarGetRelidExtended(stmt->relation, ExclusiveLock, false, false, false, false, + RangeVarCallbackOwnsTable, NULL); + matviewRel = heap_open(matviewOid, NoLock); + + /* Make sure it is a materialized view. */ + if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"%s\" is not a materialized view", RelationGetRelationName(matviewRel)))); + + /* We're not using materialized views in the system catalogs. */ + Assert(!IsSystemRelation(matviewRel)); + + /* We don't allow an oid column for a materialized view. */ + Assert(!matviewRel->rd_rel->relhasoids); + + /* + * Check that everything is correct for a refresh. Problems at this point + * are internal errors, so elog is sufficient. + */ + if (matviewRel->rd_rel->relhasrules == false || matviewRel->rd_rules->numLocks < 1) + elog(ERROR, "materialized view \"%s\" is missing rewrite information", RelationGetRelationName(matviewRel)); + + if (matviewRel->rd_rules->numLocks > 1) + elog(ERROR, "materialized view \"%s\" has too many rules", RelationGetRelationName(matviewRel)); + + rule = matviewRel->rd_rules->rules[0]; + if (rule->event != CMD_SELECT || !(rule->isInstead)) + elog(ERROR, "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule", + RelationGetRelationName(matviewRel)); + + actions = rule->actions; + if (list_length(actions) != 1) + elog(ERROR, "the rule for materialized view \"%s\" is not a single action", + RelationGetRelationName(matviewRel)); + + /* + * The stored query was rewritten at the time of the MV definition, but + * has not been scribbled on by the planner. + */ + dataQuery = (Query*)linitial(actions); + Assert(IsA(dataQuery, Query)); + + /* + * Check for active uses of the relation in the current transaction, such + * as open scans. + * + * NB: We count on this to protect us against problems with refreshing the + * data using HEAP_INSERT_FROZEN. + */ + CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW"); + + relowner = matviewRel->rd_rel->relowner; + /* + * Switch to the owner's userid, so that any functions are run as that + * user. Also arrange to make GUC variable changes local to this command. + * Don't lock it down too tight to create a temporary table just yet. We + * will switch modes when we are about to execute user code. + */ + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(relowner, save_sec_context | SECURITY_LOCAL_USERID_CHANGE); + save_nestlevel = NewGUCNestLevel(); + + tableSpace = matviewRel->rd_rel->reltablespace; + + heap_close(matviewRel, NoLock); + + /* Create the transient table that will receive the regenerated data. */ + OIDNewHeap = make_new_heap(matviewOid, tableSpace, ExclusiveLock); + dest = CreateTransientRelDestReceiver(OIDNewHeap); + /* + * Now lock down security-restricted operations. + */ + SetUserIdAndSecContext(relowner, save_sec_context | SECURITY_RESTRICTED_OPERATION); + /* Generate the data, if wanted. */ + if (!stmt->skipData) + processed = refresh_matview_datafill(dest, dataQuery, queryString); + + /* Make the matview match the newly generated data. */ + t_thrd.storage_cxt.EnlargeDeadlockTimeout = true; + LockRelationOid(matviewOid, AccessExclusiveLock); + refresh_by_heap_swap(matviewOid, OIDNewHeap); + if (!stmt->skipData) + pgstat_count_heap_insert(matviewRel, processed); + + /* Roll back any GUC changes */ + AtEOXact_GUC(false, save_nestlevel); + + /* Restore userid and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); + + u_sess->cmd_cxt.isUnderRefreshMatview = false; +} + +/* + * refresh_matview_datafill + */ +static uint64 refresh_matview_datafill(DestReceiver* dest, Query* query, const char* queryString) +{ + List *rewritten; + PlannedStmt *plan; + QueryDesc *queryDesc; + Query *copied_query; + uint64 processed; + + /* Lock and rewrite, using a copy to preserve the original query. */ + copied_query = (Query*)copyObject(query); + AcquireRewriteLocks(copied_query, false); + rewritten = QueryRewrite(copied_query); + + /* SELECT should never rewrite to more or less than one SELECT query */ + if (list_length(rewritten) != 1) + elog(ERROR, "unexpected rewrite result for REFRESH MATERIALIZED VIEW"); + query = (Query*)linitial(rewritten); + + /* Check for user-requested abort. */ + CHECK_FOR_INTERRUPTS(); + + /* Plan the query which will generate data for the refresh. */ + plan = pg_plan_query(query, 0, NULL); + + /* + * Use a snapshot with an updated command ID to ensure this query sees + * results of any previously executed queries. (This could only matter if + * the planner executed an allegedly-stable function that changed the + * database contents, but let's do it anyway to be safe.) + */ + PushCopiedSnapshot(GetActiveSnapshot()); + UpdateActiveSnapshotCommandId(); + + /* Create a QueryDesc, redirecting output to our tuple receiver */ + queryDesc = CreateQueryDesc(plan, queryString, GetActiveSnapshot(), InvalidSnapshot, dest, NULL, 0); + + /* call ExecutorStart to prepare the plan for execution */ + ExecutorStart(queryDesc, EXEC_FLAG_WITHOUT_OIDS); + + /* run the plan */ + ExecutorRun(queryDesc, ForwardScanDirection, 0L); + + processed = queryDesc->estate->es_processed; + + /* and clean up */ + ExecutorFinish(queryDesc); + ExecutorEnd(queryDesc); + + PopActiveSnapshot(); + return processed; +} + +DestReceiver* CreateTransientRelDestReceiver(Oid transientoid) +{ + DR_transientrel *self = (DR_transientrel*)palloc0(sizeof(DR_transientrel)); + + self->pub.receiveSlot = transientrel_receive; + self->pub.rStartup = transientrel_startup; + self->pub.rShutdown = transientrel_shutdown; + self->pub.rDestroy = transientrel_destroy; + self->pub.mydest = DestTransientRel; + self->transientoid = transientoid; + + return (DestReceiver*)self; +} + +/* + * transientrel_startup --- executor startup + */ +static void transientrel_startup(DestReceiver* self, int operation, TupleDesc typeinfo) +{ + DR_transientrel* myState = (DR_transientrel*)self; + Relation transientrel; + + transientrel = heap_open(myState->transientoid, NoLock); + + /* + * Fill private fields of myState for use by later routines + */ + myState->transientrel = transientrel; + myState->output_cid = GetCurrentCommandId(true); + + /* + * We can skip WAL-logging the insertions, unless PITR or streaming + * replication is in use. We can skip the FSM in any case. + */ + myState->hi_options = HEAP_INSERT_SKIP_FSM | HEAP_INSERT_FROZEN; + if (!XLogIsNeeded()) + myState->hi_options |= HEAP_INSERT_SKIP_WAL; + myState->bistate = GetBulkInsertState(); + + /* Not using WAL requires smgr_targblock be initially invalid */ + Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber); +} + +/* + * transientrel_receive --- receive one tuple + */ +static void transientrel_receive(TupleTableSlot* slot, DestReceiver* self) +{ + DR_transientrel* myState = (DR_transientrel*)self; + HeapTuple tuple; + + /* + * get the heap tuple out of the tuple table slot, making sure we have a + * writable copy + */ + tuple = ExecMaterializeSlot(slot); + + heap_insert(myState->transientrel, tuple, myState->output_cid, myState->hi_options, myState->bistate); + + /* We know this is a newly created relation, so there are no indexes */ +} + +/* + * transientrel_shutdown --- executor end + */ +static void transientrel_shutdown(DestReceiver* self) +{ + DR_transientrel* myState = (DR_transientrel*)self; + + FreeBulkInsertState(myState->bistate); + + /* If we skipped using WAL, must heap_sync before commit */ + if (myState->hi_options & HEAP_INSERT_SKIP_WAL) + heap_sync(myState->transientrel); + + /* close transientrel, but keep lock until commit */ + heap_close(myState->transientrel, NoLock); + myState->transientrel = NULL; +} + +/* + * transientrel_destroy --- release DestReceiver object + */ +static void transientrel_destroy(DestReceiver* self) +{ + pfree(self); +} + +/* + * Swap the physical files of the target and transient tables, then rebuild + * the target's indexes and throw away the transient table. Security context + * swapping is handled by the called function, so it is not needed here. + */ +static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap) +{ + finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, u_sess->utils_cxt.RecentXmin); +} + +/* + * This should be used to test whether the backend is in a context where it is + * OK to allow DML statements to modify materialized views. We only want to + * allow that for internal code driven by the materialized view definition, + * not for arbitrary user-supplied code. + * + * While the function names reflect the fact that their main intended use is + * incremental maintenance of materialized views (in response to changes to + * the data in referenced relations), they are initially used to allow REFRESH + * without blocking concurrent reads. + */ +bool MatViewIncrementalMaintenanceIsEnabled(void) +{ + return g_matview_maintenance_depth > 0; +} + + diff --git a/src/gausskernel/optimizer/commands/seclabel.cpp b/src/gausskernel/optimizer/commands/seclabel.cpp index c3a7204fc..257cbc5f0 100644 --- a/src/gausskernel/optimizer/commands/seclabel.cpp +++ b/src/gausskernel/optimizer/commands/seclabel.cpp @@ -93,15 +93,16 @@ void ExecSecLabelStmt(SecLabelStmt* stmt) /* * Allow security labels only on columns of tables, views, - * composite types, and foreign tables (which are the only - * relkinds for which pg_dump will dump labels). + * materialized views, composite types, and foreign tables (which + * are the only relkinds for which pg_dump will dump labels). */ if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_VIEW && + relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, view, composite type, or foreign table", + errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table", RelationGetRelationName(relation)))); break; default: diff --git a/src/gausskernel/optimizer/commands/sequence.cpp b/src/gausskernel/optimizer/commands/sequence.cpp index b77028dce..eeeb1ffb5 100755 --- a/src/gausskernel/optimizer/commands/sequence.cpp +++ b/src/gausskernel/optimizer/commands/sequence.cpp @@ -548,7 +548,7 @@ void DefineSequence(CreateSeqStmt* seq) stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; - stmt->options = list_make1(defWithOids(false)); + stmt->options = NIL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = false; diff --git a/src/gausskernel/optimizer/commands/tablecmds.cpp b/src/gausskernel/optimizer/commands/tablecmds.cpp index 1323a6307..6b840dee4 100644 --- a/src/gausskernel/optimizer/commands/tablecmds.cpp +++ b/src/gausskernel/optimizer/commands/tablecmds.cpp @@ -288,6 +288,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = { gettext_noop("view \"%s\" does not exist, skipping"), gettext_noop("\"%s\" is not a view"), gettext_noop("Use DROP VIEW to remove a view.")}, + {RELKIND_MATVIEW, + ERRCODE_UNDEFINED_TABLE, + gettext_noop("materialized view \"%s\" does not exist"), + gettext_noop("materialized view \"%s\" does not exist, skipping"), + gettext_noop("\"%s\" is not a materialized view"), + gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")}, {RELKIND_INDEX, ERRCODE_UNDEFINED_OBJECT, gettext_noop("index \"%s\" does not exist"), @@ -334,10 +340,11 @@ typedef OldToNewChunkIdMappingData* OldToNewChunkIdMapping; #define ATT_NULL 0x0000 #define ATT_TABLE 0x0001 #define ATT_VIEW 0x0002 -#define ATT_INDEX 0x0004 -#define ATT_COMPOSITE_TYPE 0x0008 -#define ATT_FOREIGN_TABLE 0x0010 -#define ATT_SEQUENCE 0x0020 +#define ATT_MATVIEW 0x0004 +#define ATT_INDEX 0x0008 +#define ATT_COMPOSITE_TYPE 0x00010 +#define ATT_FOREIGN_TABLE 0x0020 +#define ATT_SEQUENCE 0x0040 #define CSTORE_SUPPORT_AT_CMD(cmd) \ ((cmd) == AT_AddPartition || (cmd) == AT_ExchangePartition || (cmd) == AT_TruncatePartition || \ @@ -519,6 +526,7 @@ static const char* storage_name(char c); static void RangeVarCallbackForDropRelation( const RangeVar* rel, Oid relOid, Oid oldRelOid, bool target_is_partition, void* arg); + static void RangeVarCallbackForAlterRelation( const RangeVar* rv, Oid relid, Oid oldrelid, bool target_is_partition, void* arg); @@ -1800,7 +1808,8 @@ Oid DefineRelation(CreateStmt* stmt, char relkind, Oid ownerId) list_free_ext(pos); } - localHasOids = interpretOidsOption(stmt->options); + localHasOids = interpretOidsOption(stmt->options, + (relkind == RELKIND_RELATION || relkind == RELKIND_FOREIGN_TABLE)); descriptor->tdhasoid = (localHasOids || parentOidCount > 0); if ((pg_strcasecmp(storeChar, ORIENTATION_COLUMN) == 0 || pg_strcasecmp(storeChar, ORIENTATION_TIMESERIES) == 0) && @@ -2210,6 +2219,9 @@ ObjectAddresses* PreCheckforRemoveRelation(DropStmt* drop, StringInfo tmp_queryS relkind = RELKIND_VIEW; relkind_s = "VIEW"; break; + case OBJECT_MATVIEW: + relkind = RELKIND_MATVIEW; + break; case OBJECT_FOREIGN_TABLE: relkind = RELKIND_FOREIGN_TABLE; relkind_s = "FOREIGN TABLE"; @@ -2386,7 +2398,7 @@ void RemoveRelationsonMainExecCN(DropStmt* drop, ObjectAddresses* objects) /* * RemoveRelations * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW, - * DROP FOREIGN TABLE on datanodes and none main execute coordinator. + * DROP MATERIALIZED VIEW, DROP FOREIGN TABLE on datanodes and none main execute coordinator. */ void RemoveRelations(DropStmt* drop, StringInfo tmp_queryString, RemoteQueryExecType* exec_type) { @@ -2436,6 +2448,9 @@ void RemoveRelations(DropStmt* drop, StringInfo tmp_queryString, RemoteQueryExec relkind = RELKIND_VIEW; relkind_s = "VIEW"; break; + case OBJECT_MATVIEW: + relkind = RELKIND_MATVIEW; + break; case OBJECT_FOREIGN_TABLE: relkind = RELKIND_FOREIGN_TABLE; relkind_s = "FOREIGN TABLE"; @@ -4179,7 +4194,8 @@ static void renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing * change names that are hardcoded into the system, hence the following * restriction. */ - if (relkind != RELKIND_RELATION && relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && + if (relkind != RELKIND_RELATION && relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW && + relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_INDEX && relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -4513,15 +4529,17 @@ void RenameConstraint(RenameStmt* stmt) } /* - * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE RENAME + * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE + * RENAME */ void RenameRelation(RenameStmt* stmt) { Oid relid; /* - * Grab an exclusive lock on the target table, index, sequence or view, - * which we will NOT release until end of transaction. + * Grab an exclusive lock on the target table, index, sequence, view, + * materialized view, or foreign table, which we will NOT release until + * end of transaction. * * Lock level used here should match RenameRelationInternal, to avoid lock * escalation. @@ -4561,8 +4579,9 @@ void RenameRelationInternal(Oid myrelid, const char* newrelname) Oid namespaceId; /* - * Grab an exclusive lock on the target table, index, sequence or view, - * which we will NOT release until end of transaction. + * Grab an exclusive lock on the target table, index, sequence, view, + * materialized view, or foreign table, which we will NOT release until + * end of transaction. */ targetrelation = relation_open(myrelid, AccessExclusiveLock); @@ -5965,12 +5984,12 @@ static void ATPrepCmd(List** wqueue, Relation rel, AlterTableCmd* cmd, bool recu break; case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */ - ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_FOREIGN_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE); /* This command never recurses */ pass = AT_PASS_MISC; break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ - ATSimplePermissions(rel, ATT_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -6053,7 +6072,7 @@ static void ATPrepCmd(List** wqueue, Relation rel, AlterTableCmd* cmd, bool recu break; case AT_ClusterOn: /* CLUSTER ON */ case AT_DropCluster: /* SET WITHOUT CLUSTER */ - ATSimplePermissions(rel, ATT_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); /* These commands never recurse */ /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -6098,7 +6117,7 @@ static void ATPrepCmd(List** wqueue, Relation rel, AlterTableCmd* cmd, bool recu break; case AT_SetTableSpace: /* SET TABLESPACE */ case AT_SetPartitionTableSpace: - ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX); /* This command never recurses */ ATPrepSetTableSpace(tab, rel, cmd->name, lockmode); pass = AT_PASS_MISC; /* doesn't actually matter */ @@ -6107,7 +6126,7 @@ static void ATPrepCmd(List** wqueue, Relation rel, AlterTableCmd* cmd, bool recu case AT_SetRelOptions: /* SET ... */ case AT_ResetRelOptions: /* RESET ... */ case AT_ReplaceRelOptions: /* reset them all, then set just these */ - ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_VIEW); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_VIEW); /* This command never recurses */ /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -6306,7 +6325,7 @@ static void ATRewriteCatalogs(List** wqueue, LOCKMODE lockmode) if (get_rel_persistence(tab->relid) == RELPERSISTENCE_GLOBAL_TEMP) { gtt_create_storage_files(tab->relid); } - if (tab->relkind == RELKIND_RELATION) + if (tab->relkind == RELKIND_RELATION || tab->relkind == RELKIND_MATVIEW) AlterTableCreateToastTable(tab->relid, (Datum)0); } } @@ -7157,6 +7176,9 @@ static void ATSimplePermissions(Relation rel, int allowed_targets) case RELKIND_VIEW: actual_target = ATT_VIEW; break; + case RELKIND_MATVIEW: + actual_target = ATT_MATVIEW; + break; case RELKIND_INDEX: actual_target = ATT_INDEX; break; @@ -7207,18 +7229,27 @@ static void ATWrongRelkindError(Relation rel, int allowed_targets) case ATT_TABLE: msg = _("\"%s\" is not a table"); break; - case ATT_TABLE | ATT_INDEX: - msg = _("\"%s\" is not a table or index"); - break; case ATT_TABLE | ATT_VIEW: msg = _("\"%s\" is not a table or view"); break; + case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX: + msg = _("\"%s\" is not a table, view, materialized view, or index"); + break; + case ATT_TABLE | ATT_MATVIEW: + msg = _("\"%s\" is not a table or materialized view"); + break; + case ATT_TABLE | ATT_MATVIEW | ATT_INDEX: + msg = _("\"%s\" is not a table, materialized view, or index"); + break; case ATT_TABLE | ATT_FOREIGN_TABLE: msg = _("\"%s\" is not a table or foreign table"); break; case ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE: msg = _("\"%s\" is not a table, composite type, or foreign table"); break; + case ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE: + msg = _("\"%s\" is not a table, materialized view, composite type, or foreign table"); + break; case ATT_VIEW: msg = _("\"%s\" is not a view"); break; @@ -7351,7 +7382,7 @@ void find_composite_type_dependencies(Oid typeOid, Relation origRelation, const rel = relation_open(pg_depend->objid, AccessShareLock); att = rel->rd_att->attrs[pg_depend->objsubid - 1]; - if (rel->rd_rel->relkind == RELKIND_RELATION) { + if (rel->rd_rel->relkind == RELKIND_RELATION || rel->rd_rel->relkind == RELKIND_MATVIEW) { if (origTypeName != NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -8258,11 +8289,12 @@ static void ATPrepSetStatistics(Relation rel, const char* colName, Node* newValu * to allow SET STATISTICS on system catalogs without requiring * allowSystemTableMods to be turned on. */ - if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_INDEX && + if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_MATVIEW && + rel->rd_rel->relkind != RELKIND_INDEX && rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, index, or foreign table", RelationGetRelationName(rel)))); + ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table, materialized view, index, or foreign table", + RelationGetRelationName(rel)))); /* Permissions checks */ if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) @@ -11653,6 +11685,7 @@ void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE switch (tuple_class->relkind) { case RELKIND_RELATION: case RELKIND_VIEW: + case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: /* ok to change owner */ if (IS_PGXC_COORDINATOR && in_logic_cluster()) { @@ -11880,11 +11913,12 @@ void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId, tuple_class->relkind == RELKIND_COMPOSITE_TYPE); /* - * If we are operating on a table, also change the ownership of any - * indexes and sequences that belong to the table, as well as the - * table's toast table (if it has one) + * If we are operating on a table or materialized view, also change + * the ownership of any indexes and sequences that belong to the + * relation, as well as its toast table (if it has one). */ - if (tuple_class->relkind == RELKIND_RELATION || tuple_class->relkind == RELKIND_TOASTVALUE) { + if (tuple_class->relkind == RELKIND_RELATION || tuple_class->relkind == RELKIND_MATVIEW || + tuple_class->relkind == RELKIND_TOASTVALUE) { List* index_oid_list = NIL; ListCell* i = NULL; @@ -11898,7 +11932,7 @@ void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE list_free_ext(index_oid_list); } - if (tuple_class->relkind == RELKIND_RELATION) { + if (tuple_class->relkind == RELKIND_RELATION || tuple_class->relkind == RELKIND_MATVIEW) { /* If it has a toast table, recurse to change its ownership */ if (tuple_class->reltoastrelid != InvalidOid) ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId, true, lockmode); @@ -12451,7 +12485,8 @@ static void ATExecSetRelOptions(Relation rel, List* defList, AlterTableType oper break; } case RELKIND_TOASTVALUE: - case RELKIND_VIEW: { + case RELKIND_VIEW: + case RELKIND_MATVIEW:{ (void)heap_reloptions(rel->rd_rel->relkind, newOptions, true); break; } @@ -12461,7 +12496,8 @@ static void ATExecSetRelOptions(Relation rel, List* defList, AlterTableType oper default: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, index, or TOAST table", RelationGetRelationName(rel)))); + errmsg("\"%s\" is not a table, view, materialized view, index, or TOAST table", + RelationGetRelationName(rel)))); break; } @@ -15001,8 +15037,9 @@ void AlterTableNamespace(AlterObjectSchemaStmt* stmt) } /* - * The guts of relocating a table to another namespace: besides moving - * the table itself, its dependent objects are relocated to the new schema. + * The guts of relocating a table or materialized view to another namespace: + * besides moving the relation itself, its dependent objects are relocated to + * the new schema. */ void AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid, ObjectAddresses* objsMoved) { @@ -15019,7 +15056,7 @@ void AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid, Object AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid, false, false, objsMoved); /* Fix other dependent stuff */ - if (rel->rd_rel->relkind == RELKIND_RELATION) { + if (rel->rd_rel->relkind == RELKIND_RELATION || rel->rd_rel->relkind == RELKIND_MATVIEW) { AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved); AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved, AccessExclusiveLock); AlterConstraintNamespaces(RelationGetRelid(rel), oldNspOid, nspOid, false, objsMoved); @@ -15383,9 +15420,10 @@ void AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid, SubT /* * This is intended as a callback for RangeVarGetRelidExtended(). It allows * the table to be locked only if (1) it's a plain table or TOAST table and - * (2) the current user is the owner (or the superuser). This meets the - * permission-checking needs of both CLUSTER and REINDEX TABLE; we expose it - * here so that it can be used by both. + * view, or TOAST table and (2) the current user is the owner (or the + * superuser). This meets the permission-checking needs of CLUSTER, REINDEX + * TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it can be + * used by all. */ void RangeVarCallbackOwnsTable(const RangeVar* relation, Oid relId, Oid oldRelId, bool target_is_partition, void* arg) { @@ -15405,8 +15443,9 @@ void RangeVarCallbackOwnsTable(const RangeVar* relation, Oid relId, Oid oldRelId if (!relkind) { return; } - if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE) { - ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", relation->relname))); + if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE && relkind != RELKIND_MATVIEW) { + ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table or materialized view", + relation->relname))); } /* Check permissions */ if (!pg_class_ownercheck(relId, GetUserId())) { @@ -15447,7 +15486,7 @@ void RangeVarCallbackOwnsRelation( ReleaseSysCache(tuple); } -/* +/* RangeVarCallbackForAlterRelation * Common RangeVarGetRelid callback for rename, set schema, and alter table * processing. */ @@ -15538,7 +15577,9 @@ static void RangeVarCallbackForAlterRelation( if (reltype == OBJECT_VIEW && relkind != RELKIND_VIEW) { ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a view", rv->relname))); } - + if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW) + ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a materialized view", rv->relname))); if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE) { ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a foreign table", rv->relname))); } @@ -15576,11 +15617,11 @@ static void RangeVarCallbackForAlterRelation( * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved * to a different schema, such as indexes and TOAST tables. */ - if (IsA(stmt, AlterObjectSchemaStmt) && relkind != RELKIND_RELATION && relkind != RELKIND_VIEW && - relkind != RELKIND_SEQUENCE && relkind != RELKIND_FOREIGN_TABLE) { + if (IsA(stmt, AlterObjectSchemaStmt) && relkind != RELKIND_RELATION && relkind != RELKIND_VIEW && + relkind != RELKIND_MATVIEW && relkind != RELKIND_SEQUENCE && relkind != RELKIND_FOREIGN_TABLE) { ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, view, sequence, or foreign table", rv->relname))); + errmsg("\"%s\" is not a table, view, materialized view, sequence, or foreign table", rv->relname))); } ReleaseSysCache(tuple); @@ -20293,7 +20334,7 @@ static void ExecRewriteRowTable(AlteredTableInfo* tab, Oid NewTableSpace, LOCKMO { ForbidToRewriteOrTestCstoreIndex(tab); - Oid OIDNewHeap = make_new_heap(tab->relid, NewTableSpace); + Oid OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, AccessExclusiveLock); /* * Copy the heap data into the new table with the desired diff --git a/src/gausskernel/optimizer/commands/tablespace.cpp b/src/gausskernel/optimizer/commands/tablespace.cpp index 34db89c4c..7458aa858 100644 --- a/src/gausskernel/optimizer/commands/tablespace.cpp +++ b/src/gausskernel/optimizer/commands/tablespace.cpp @@ -63,6 +63,7 @@ #include "commands/defrem.h" #include "commands/seclabel.h" #include "commands/tablespace.h" +#include "commands/user.h" #include "miscadmin.h" #include "nodes/bitmapset.h" #include "nodes/makefuncs.h" diff --git a/src/gausskernel/optimizer/commands/typecmds.cpp b/src/gausskernel/optimizer/commands/typecmds.cpp index 9281dd812..03b4110cf 100755 --- a/src/gausskernel/optimizer/commands/typecmds.cpp +++ b/src/gausskernel/optimizer/commands/typecmds.cpp @@ -1928,7 +1928,7 @@ Oid DefineCompositeType(RangeVar* typevar, List* coldeflist) createStmt->tableElts = coldeflist; createStmt->inhRelations = NIL; createStmt->constraints = NIL; - createStmt->options = list_make1(defWithOids(false)); + createStmt->options = NIL; createStmt->oncommit = ONCOMMIT_NOOP; createStmt->tablespacename = NULL; createStmt->if_not_exists = false; @@ -2603,9 +2603,16 @@ static List* get_rels_with_domain(Oid domainOid, LOCKMODE lockmode) */ if (OidIsValid(rel->rd_rel->reltype)) find_composite_type_dependencies(rel->rd_rel->reltype, NULL, format_type_be(domainOid)); - - /* Otherwise we can ignore views, composite types, etc */ - if (rel->rd_rel->relkind != RELKIND_RELATION) { + + /* + * Otherwise, we can ignore relations except those with both + * storage and user-chosen column types. + * + * XXX If an index-only scan could satisfy "col::some_domain" from + * a suitable expression index, this should also check expression + * index columns. + */ + if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_MATVIEW) { relation_close(rel, lockmode); continue; } diff --git a/src/gausskernel/optimizer/commands/user.cpp b/src/gausskernel/optimizer/commands/user.cpp index 907dc8b45..5f0f3c541 100755 --- a/src/gausskernel/optimizer/commands/user.cpp +++ b/src/gausskernel/optimizer/commands/user.cpp @@ -85,7 +85,6 @@ MemoryContext WaitCountGlobalContext = NULL; /* Hook to check passwords in CreateRole() and AlterRole() */ THR_LOCAL check_password_hook_type check_password_hook = NULL; -static List* roleNamesToIds(const List* memberNames); static void AddRoleMems( const char* rolename, Oid roleid, const List* memberNames, List* memberIds, Oid grantorId, bool admin_opt); static void DelRoleMems(const char* rolename, Oid roleid, const List* memberNames, List* memberIds, bool admin_opt); @@ -3237,7 +3236,7 @@ void ReassignOwnedObjects(ReassignOwnedStmt* stmt) * Given a list of role names (as String nodes), generate a list of role OIDs * in the same order. */ -static List* roleNamesToIds(const List* memberNames) +List* roleNamesToIds(const List* memberNames) { List* result = NIL; ListCell* l = NULL; diff --git a/src/gausskernel/optimizer/commands/vacuum.cpp b/src/gausskernel/optimizer/commands/vacuum.cpp index dd58d0fcc..7aa70c3a8 100755 --- a/src/gausskernel/optimizer/commands/vacuum.cpp +++ b/src/gausskernel/optimizer/commands/vacuum.cpp @@ -677,14 +677,11 @@ List* get_rel_oids(Oid relid, VacuumStmt* vacstmt) Relation pgclass; HeapScanDesc scan; HeapTuple tuple; - ScanKeyData key; /* Process all plain relations listed in pg_class */ - ScanKeyInit(&key, Anum_pg_class_relkind, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(RELKIND_RELATION)); - pgclass = heap_open(RelationRelationId, AccessShareLock); - scan = heap_beginscan(pgclass, SnapshotNow, 1, &key); + scan = heap_beginscan(pgclass, SnapshotNow, 0, NULL); while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { /* for dfs special vacuum, just skip for non-dfs table. */ @@ -696,6 +693,11 @@ List* get_rel_oids(Oid relid, VacuumStmt* vacstmt) Form_pg_class classForm = (Form_pg_class)GETSTRUCT(tuple); + /* Only vacuum table and materialized view */ + if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_MATVIEW) { + continue; + } + /* Plain relation and valued partition relation */ if (classForm->parttype == PARTTYPE_NON_PARTITIONED_RELATION || classForm->parttype == PARTTYPE_VALUE_PARTITIONED_RELATION || @@ -1155,10 +1157,11 @@ void vac_update_datfrozenxid(void) Form_pg_class classForm = (Form_pg_class)GETSTRUCT(classTup); /* - * Only consider heap and TOAST tables (anything else should have - * InvalidTransactionId in relfrozenxid anyway.) + * Only consider relations able to hold unfrozen XIDs (anything else + * should have InvalidTransactionId in relfrozenxid anyway.) */ - if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_TOASTVALUE) + if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_MATVIEW && + classForm->relkind != RELKIND_TOASTVALUE) continue; /* global temp table relstats not in pg_class */ @@ -1811,13 +1814,14 @@ static bool vacuum_rel(Oid relid, VacuumStmt* vacstmt, bool do_toast) } /* - * Check that it's a vacuumable table; we used to do this in + * Check that it's a vacuumable relation; we used to do this in * get_rel_oids() but seems safer to check after we've locked the * relation. */ if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(onerel->rd_id)) { ; - } else if (onerel->rd_rel->relkind != RELKIND_RELATION && onerel->rd_rel->relkind != RELKIND_TOASTVALUE) { + } else if (onerel->rd_rel->relkind != RELKIND_RELATION && onerel->rd_rel->relkind != RELKIND_MATVIEW && + onerel->rd_rel->relkind != RELKIND_TOASTVALUE) { if (vacstmt->options & VACOPT_VERBOSE) messageLevel = VERBOSEMESSAGE; diff --git a/src/gausskernel/optimizer/commands/view.cpp b/src/gausskernel/optimizer/commands/view.cpp index b81f44071..3f06357ac 100755 --- a/src/gausskernel/optimizer/commands/view.cpp +++ b/src/gausskernel/optimizer/commands/view.cpp @@ -42,47 +42,6 @@ #endif static void checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc); -static bool isViewOnTempTable_walker(Node* node, void* context); - -/* --------------------------------------------------------------------- - * isViewOnTempTable - * - * Returns true iff any of the relations underlying this view are - * temporary tables. - * --------------------------------------------------------------------- - */ -static bool isViewOnTempTable(Query* viewParse) -{ - return isViewOnTempTable_walker((Node*)viewParse, NULL); -} - -static bool isViewOnTempTable_walker(Node* node, void* context) -{ - if (node == NULL) - return false; - - if (IsA(node, Query)) { - Query* query = (Query*)node; - ListCell* rtable = NULL; - - foreach (rtable, query->rtable) { - RangeTblEntry* rte = (RangeTblEntry*)lfirst(rtable); - - if (rte->rtekind == RTE_RELATION) { - Relation rel = heap_open(rte->relid, AccessShareLock); - char relpersistence = rel->rd_rel->relpersistence; - - heap_close(rel, AccessShareLock); - if (relpersistence == RELPERSISTENCE_TEMP) - return true; - } - } - - return query_tree_walker(query, (bool (*)())isViewOnTempTable_walker, context, QTW_IGNORE_JOINALIASES); - } - - return expression_tree_walker(node, (bool (*)())isViewOnTempTable_walker, context); -} /* --------------------------------------------------------------------- * DefineVirtualRelation @@ -109,20 +68,8 @@ static Oid DefineVirtualRelation(RangeVar* relation, List* tlist, bool replace, TargetEntry* tle = (TargetEntry*)lfirst(t); if (!tle->resjunk) { - ColumnDef* def = makeNode(ColumnDef); - - def->colname = pstrdup(tle->resname); - def->typname = makeTypeNameFromOid(exprType((Node*)tle->expr), exprTypmod((Node*)tle->expr)); - def->inhcount = 0; - def->is_local = true; - def->is_not_null = false; - def->is_from_type = false; - def->storage = 0; - def->cmprs_mode = ATT_CMPR_NOCOMPRESS; /* dont compress */ - def->raw_default = NULL; - def->cooked_default = NULL; - def->collClause = NULL; - def->collOid = exprCollation((Node*)tle->expr); + ColumnDef *def = makeColumnDef(tle->resname, exprType((Node *)tle->expr), exprTypmod((Node *)tle->expr), + exprCollation((Node *)tle->expr)); /* * It's possible that the column is of a collatable type but the @@ -136,8 +83,7 @@ static Oid DefineVirtualRelation(RangeVar* relation, List* tlist, bool replace, errhint("Use the COLLATE clause to set the collation explicitly."))); } else Assert(!OidIsValid(def->collOid)); - def->constraints = NIL; - + attrList = lappend(attrList, def); } } @@ -262,7 +208,6 @@ static Oid DefineVirtualRelation(RangeVar* relation, List* tlist, bool replace, createStmt->inhRelations = NIL; createStmt->constraints = NIL; createStmt->options = options; - createStmt->options = lappend(options, defWithOids(false)); createStmt->oncommit = ONCOMMIT_NOOP; createStmt->tablespacename = NULL; createStmt->if_not_exists = false; @@ -488,7 +433,7 @@ void DefineView(ViewStmt* stmt, const char* queryString, bool isFirstNode) * schema name. */ view = (RangeVar*)copyObject(stmt->view); /* don't corrupt original command */ - if (view->relpersistence == RELPERSISTENCE_PERMANENT && isViewOnTempTable(viewParse)) { + if (view->relpersistence == RELPERSISTENCE_PERMANENT && isQueryUsingTempRelation(viewParse)) { view->relpersistence = RELPERSISTENCE_TEMP; ereport(NOTICE, (errmsg("view \"%s\" will be a temporary view", view->relname))); } @@ -513,6 +458,15 @@ void DefineView(ViewStmt* stmt, const char* queryString, bool isFirstNode) * command id counter (but do NOT pfree any memory!!!!) */ CommandCounterIncrement(); + StoreViewQuery(viewOid, viewParse, stmt->replace); + +} + +/* + * Use the rules system to store the query for the view. + */ +void StoreViewQuery(Oid viewOid, Query* viewParse, bool replace) +{ /* * The range table of 'viewParse' does not contain entries for the "OLD" @@ -523,7 +477,7 @@ void DefineView(ViewStmt* stmt, const char* queryString, bool isFirstNode) /* * Now create the rules associated with the view. */ - DefineViewRules(viewOid, viewParse, stmt->replace); + DefineViewRules(viewOid, viewParse, replace); } bool IsViewTemp(ViewStmt* stmt, const char* queryString) @@ -539,7 +493,7 @@ bool IsViewTemp(ViewStmt* stmt, const char* queryString) * long as the CREATE command is consistent with that --- no explicit * schema name. */ - if (view->relpersistence == RELPERSISTENCE_PERMANENT && isViewOnTempTable(viewParse)) { + if (view->relpersistence == RELPERSISTENCE_PERMANENT && isQueryUsingTempRelation(viewParse)) { view->relpersistence = RELPERSISTENCE_TEMP; } diff --git a/src/gausskernel/optimizer/path/allpaths.cpp b/src/gausskernel/optimizer/path/allpaths.cpp index 986a10ef2..43d6e5d4a 100755 --- a/src/gausskernel/optimizer/path/allpaths.cpp +++ b/src/gausskernel/optimizer/path/allpaths.cpp @@ -1768,7 +1768,9 @@ static void set_subquery_pathlist(PlannerInfo* root, RelOptInfo* rel, Index rti, /* * It's possible that constraint exclusion proved the subquery empty. If * so, it's convenient to turn it back into a dummy path so that we will - * recognize appropriate optimizations at this level. + * recognize appropriate optimizations at this query level. (But see + * create_append_plan in createplan.c, which has to reverse this + * substitution.) */ if (is_dummy_plan(rel->subplan)) { set_dummy_rel_pathlist(rel); diff --git a/src/gausskernel/optimizer/plan/createplan.cpp b/src/gausskernel/optimizer/plan/createplan.cpp index 9979d44b8..6c3171404 100644 --- a/src/gausskernel/optimizer/plan/createplan.cpp +++ b/src/gausskernel/optimizer/plan/createplan.cpp @@ -1045,30 +1045,49 @@ static Plan* create_join_plan(PlannerInfo* root, JoinPath* best_path) static Plan* create_append_plan(PlannerInfo* root, AppendPath* best_path) { Append* plan = NULL; - List* tlist = build_relation_tlist(best_path->path.parent); + RelOptInfo* rel = best_path->path.parent; + List* tlist = build_relation_tlist(rel); List* subplans = NIL; ListCell* subpaths = NULL; /* - * It is possible for the subplans list to contain only one entry, or even - * no entries. Handle these cases specially. + * The subpaths list could be empty, if every child was proven empty by + * constraint exclusion. In that case generate a dummy plan that returns + * no rows. * - * XXX ideally, if there's just one entry, we'd not bother to generate an - * Append node but just return the single child. At the moment this does - * not work because the varno of the child scan plan won't match the - * parent-rel Vars it'll be asked to emit. + * Note that an AppendPath with no members is also generated in certain + * cases where there was no appending construct at all, but we know the + * relation is empty (see set_dummy_rel_pathlist). */ if (best_path->subpaths == NIL) { - /* Generate a Result plan with constant-FALSE gating qual */ - return (Plan*)make_result(root, tlist, (Node*)list_make1(makeBoolConst(false, false)), NULL); + /* + * If this is a dummy path for a subquery, we have to wrap the + * subquery's original plan in a SubqueryScan so that setrefs.c will + * do the right things. (In particular, it must pull up the + * subquery's rangetable so that the executor will apply permissions + * checks to those rels at runtime.) + */ + if (rel->rtekind == RTE_SUBQUERY) { + Assert(is_dummy_plan(rel->subplan)); + return (Plan*)make_subqueryscan(tlist, NIL, rel->relid, rel->subplan); + } else { + /* Generate a Result plan with constant-FALSE gating qual */ + return (Plan*)make_result(root, tlist, (Node*)list_make1(makeBoolConst(false, false)), NULL); + } } - /* Normal case with multiple subpaths */ + /* Build the plan for each child */ foreach (subpaths, best_path->subpaths) { Path* subpath = (Path*)lfirst(subpaths); subplans = lappend(subplans, create_plan_recurse(root, subpath)); } + /* + * XXX ideally, if there's just one child, we'd not bother to generate an + * Append node but just return the single child. At the moment this does + * not work because the varno of the child scan plan won't match the + * parent-rel Vars it'll be asked to emit. + */ plan = make_append(subplans, tlist); diff --git a/src/gausskernel/optimizer/plan/planner.cpp b/src/gausskernel/optimizer/plan/planner.cpp index 6553eb7ef..8f202bd76 100644 --- a/src/gausskernel/optimizer/plan/planner.cpp +++ b/src/gausskernel/optimizer/plan/planner.cpp @@ -6950,7 +6950,7 @@ bool plan_cluster_use_sort(Oid tableOid, Oid indexOid) rte = makeNode(RangeTblEntry); rte->rtekind = RTE_RELATION; rte->relid = tableOid; - rte->relkind = RELKIND_RELATION; + rte->relkind = RELKIND_RELATION; /* Don't be too picky. */ rte->inh = false; rte->inFromCl = true; query->rtable = list_make1(rte); diff --git a/src/gausskernel/optimizer/rewrite/rewriteDefine.cpp b/src/gausskernel/optimizer/rewrite/rewriteDefine.cpp index 0cce979ae..8a8eefc62 100755 --- a/src/gausskernel/optimizer/rewrite/rewriteDefine.cpp +++ b/src/gausskernel/optimizer/rewrite/rewriteDefine.cpp @@ -51,7 +51,7 @@ #include "utils/syscache.h" #include "utils/tqual.h" -static void checkRuleResultList(List* targetList, TupleDesc resultDesc, bool isSelect); +static void checkRuleResultList(List* targetList, TupleDesc resultDesc, bool isSelect, bool requireColumnNameMatch); static bool setRuleCheckAsUser_walker(Node* node, Oid* context); static void setRuleCheckAsUser_Query(Query* qry, Oid userid); @@ -236,8 +236,11 @@ void DefineQueryRewrite( /* * Verify relation is of a type that rules can sensibly be applied to. + * Internal callers can target materialized views, but transformRuleStmt() + * blocks them for users. Don't mention them in the error message. */ - if (event_relation->rd_rel->relkind != RELKIND_RELATION && event_relation->rd_rel->relkind != RELKIND_VIEW) + if (event_relation->rd_rel->relkind != RELKIND_RELATION && event_relation->rd_rel->relkind != RELKIND_MATVIEW && + event_relation->rd_rel->relkind != RELKIND_VIEW) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table or view", RelationGetRelationName(event_relation)))); @@ -324,7 +327,8 @@ void DefineQueryRewrite( * ... the targetlist of the SELECT action must exactly match the * event relation, ... */ - checkRuleResultList(query->targetList, RelationGetDescr(event_relation), true); + checkRuleResultList(query->targetList, RelationGetDescr(event_relation), + true, event_relation->rd_rel->relkind != RELKIND_MATVIEW); /* * ... there must not be another ON SELECT rule already ... @@ -377,7 +381,7 @@ void DefineQueryRewrite( * business of converting relations to views is just a kluge to allow * loading ancient pg_dump files.) */ - if (event_relation->rd_rel->relkind != RELKIND_VIEW) { + if (event_relation->rd_rel->relkind != RELKIND_VIEW && event_relation->rd_rel->relkind != RELKIND_MATVIEW) { HeapScanDesc scanDesc; if (RELATION_IS_PARTITIONED(event_relation)) { ereport(ERROR, @@ -455,7 +459,7 @@ void DefineQueryRewrite( ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("RETURNING lists are not supported in non-INSTEAD rules"))); - checkRuleResultList(query->returningList, RelationGetDescr(event_relation), false); + checkRuleResultList(query->returningList, RelationGetDescr(event_relation), false, false); } } @@ -611,10 +615,11 @@ void DefineQueryRewrite( * Verify that targetList produces output compatible with a tupledesc * * The targetList might be either a SELECT targetlist, or a RETURNING list; - * isSelect tells which. (This is mostly used for choosing error messages, - * but also we don't enforce column name matching for RETURNING.) + * isSelect tells which. This is used for choosing error messages. + * + * A SELECT targetlist may optionally require that column names match. */ -static void checkRuleResultList(List* targetList, TupleDesc resultDesc, bool isSelect) +static void checkRuleResultList(List* targetList, TupleDesc resultDesc, bool isSelect, bool requireColumnNameMatch) { ListCell* tllist = NULL; int i; @@ -652,7 +657,7 @@ static void checkRuleResultList(List* targetList, TupleDesc resultDesc, bool isS (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert relation containing dropped columns to view"))); - if (isSelect && strcmp(tle->resname, attname) != 0) + if (requireColumnNameMatch && strcmp(tle->resname, attname) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("SELECT rule's target entry %d has different column name from \"%s\"", i, attname))); diff --git a/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp b/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp index 3b0ab1d1b..a6a345704 100644 --- a/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp +++ b/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp @@ -1229,7 +1229,7 @@ static void rewriteTargetListUD(Query* parsetree, RangeTblEntry* target_rte, Rel } #endif - if (target_relation->rd_rel->relkind == RELKIND_RELATION) { + if (target_relation->rd_rel->relkind == RELKIND_RELATION || target_relation->rd_rel->relkind == RELKIND_MATVIEW) { /* * Emit CTID so that executor can find the row to update or delete. */ @@ -1705,6 +1705,19 @@ static Query* fireRIRrules(Query* parsetree, List* activeRIRs, bool forUpdatePus continue; } + /* + * Always ignore RIR rules for materialized views referenced in + * queries. (This does not prevent refreshing MVs, since they aren't + * referenced in their own query definitions.) + * + * Note: in the future we might want to allow MVs to be conditionally + * expanded as if they were regular views, if they are not scannable. + * In that case this test would need to be postponed till after we've + * opened the rel, so that we could check its state. + */ + if (rte->relkind == RELKIND_MATVIEW) + continue; + /* * If the table is not referenced in the query, then we ignore it. * This prevents infinite expansion loop due to new rtable entries diff --git a/src/gausskernel/optimizer/util/plancat.cpp b/src/gausskernel/optimizer/util/plancat.cpp index 34b6b26ed..70b2d7bcb 100644 --- a/src/gausskernel/optimizer/util/plancat.cpp +++ b/src/gausskernel/optimizer/util/plancat.cpp @@ -526,6 +526,7 @@ void estimate_rel_size(Relation rel, int32* attr_widths, RelPageType* pages, dou #endif /* fall through */ case RELKIND_INDEX: + case RELKIND_MATVIEW: /* fall through */ case RELKIND_TOASTVALUE: /* diff --git a/src/gausskernel/process/postmaster/autovacuum.cpp b/src/gausskernel/process/postmaster/autovacuum.cpp index d7e4a5ef8..767e7d533 100755 --- a/src/gausskernel/process/postmaster/autovacuum.cpp +++ b/src/gausskernel/process/postmaster/autovacuum.cpp @@ -2140,19 +2140,17 @@ static void do_autovacuum(void) * Scan pg_class to determine which tables to vacuum. * * We do this in two passes: on the first one we collect the list of plain - * relations, and on the second one we collect TOAST tables. The reason - * for doing the second pass is that during it we want to use the main - * relation's pg_class.reloptions entry if the TOAST table does not have - * any, and we cannot obtain it unless we know beforehand what's the main - * table OID. + * relations and materialized views, and on the second one we collect + * TOAST tables. The reason for doing the second pass is that during it we + * want to use the main relation's pg_class.reloptions entry if the TOAST + * table does not have any, and we cannot obtain it unless we know + * beforehand what's the main table OID. * * We need to check TOAST tables separately because in cases with short, * wide tables there might be proportionally much more activity in the * TOAST table than in its parent. */ - ScanKeyInit(&key[0], Anum_pg_class_relkind, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(RELKIND_RELATION)); - - relScan = heap_beginscan(classRel, SnapshotNow, 1, key); + relScan = heap_beginscan(classRel, SnapshotNow, 0, NULL); /* * On the first pass, we collect main tables to vacuum, and also the main @@ -2169,6 +2167,11 @@ static void do_autovacuum(void) bool enable_analyze = false; bool enable_vacuum = false; + /* Only autovacuum table and materialized view */ + if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_MATVIEW) { + continue; + } + /* We cannot safely process other backends' temp tables, so skip 'em. */ if (classForm->relpersistence == RELPERSISTENCE_TEMP || classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) { @@ -2808,7 +2811,8 @@ AutoVacOpts* extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) int rc = 0; Assert(((Form_pg_class)GETSTRUCT(tup))->relkind == RELKIND_RELATION || - ((Form_pg_class)GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE); + ((Form_pg_class)GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE || + ((Form_pg_class)GETSTRUCT(tup))->relkind == RELKIND_MATVIEW); relopts = extractRelOptions(tup, pg_class_desc, InvalidOid); if (relopts == NULL) diff --git a/src/gausskernel/process/postmaster/pgstat.cpp b/src/gausskernel/process/postmaster/pgstat.cpp index 561b7636e..d7d783a9a 100644 --- a/src/gausskernel/process/postmaster/pgstat.cpp +++ b/src/gausskernel/process/postmaster/pgstat.cpp @@ -1824,8 +1824,8 @@ void pgstat_initstats(Relation rel) char relkind = rel->rd_rel->relkind; /* We only count stats for things that have storage */ - if (!(relkind == RELKIND_RELATION || relkind == RELKIND_INDEX || relkind == RELKIND_TOASTVALUE || - relkind == RELKIND_SEQUENCE)) { + if (!(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || relkind == RELKIND_INDEX || + relkind == RELKIND_TOASTVALUE || relkind == RELKIND_SEQUENCE)) { rel->pgstat_info = NULL; return; } @@ -2001,7 +2001,7 @@ static void add_tabstat_xact_level(PgStat_TableStatus* pgstat_info, int nest_lev /* * pgstat_count_heap_insert - count a tuple insertion of n tuples */ -void pgstat_count_heap_insert(Relation rel, int n) +void pgstat_count_heap_insert(Relation rel, PgStat_Counter n) { PgStat_TableStatus* pgstat_info = rel->pgstat_info; diff --git a/src/gausskernel/process/tcop/dest.cpp b/src/gausskernel/process/tcop/dest.cpp index e6bb64e5b..7ceea9744 100755 --- a/src/gausskernel/process/tcop/dest.cpp +++ b/src/gausskernel/process/tcop/dest.cpp @@ -33,6 +33,7 @@ #include "access/xact.h" #include "commands/copy.h" #include "commands/createas.h" +#include "commands/matview.h" #include "executor/functions.h" #include "executor/spi.h" #include "executor/tstoreReceiver.h" @@ -133,6 +134,8 @@ DestReceiver* CreateDestReceiver(CommandDest dest) case DestSQLFunction: return CreateSQLFunctionDestReceiver(); + case DestTransientRel: + return CreateTransientRelDestReceiver(InvalidOid); case DestTupleBroadCast: case DestTupleLocalBroadCast: case DestTupleRedistribute: @@ -183,6 +186,7 @@ void EndCommand(const char* commandTag, CommandDest dest) case DestIntoRel: case DestCopyOut: case DestSQLFunction: + case DestTransientRel: default: break; } @@ -259,6 +263,7 @@ void NullCommand(CommandDest dest) case DestIntoRel: case DestCopyOut: case DestSQLFunction: + case DestTransientRel: default: break; } diff --git a/src/gausskernel/process/tcop/postgres.cpp b/src/gausskernel/process/tcop/postgres.cpp index 72150d6c9..55a04c5c5 100755 --- a/src/gausskernel/process/tcop/postgres.cpp +++ b/src/gausskernel/process/tcop/postgres.cpp @@ -909,7 +909,9 @@ static List* pg_rewrite_query(Query* query) PGSTAT_START_TIME_RECORD(); #ifdef PGXC - if (query->commandType == CMD_UTILITY && IsA(query->utilityStmt, CreateTableAsStmt)) { + if (query->commandType == CMD_UTILITY && IsA(query->utilityStmt, CreateTableAsStmt) && + ((CreateTableAsStmt*)query->utilityStmt)->relkind != OBJECT_MATVIEW && + u_sess->cmd_cxt.isUnderRefreshMatview == false) { /* * CREATE TABLE AS SELECT and SELECT INTO are rewritten so that the * target table is created first. The SELECT query is then transformed @@ -7637,6 +7639,7 @@ int PostgresMain(int argc, char* argv[], const char* dbname, const char* usernam t_thrd.postgres_cxt.debug_query_string = NULL; t_thrd.postgres_cxt.g_NoAnalyzeRelNameList = NIL; u_sess->analyze_cxt.is_under_analyze = false; + u_sess->cmd_cxt.isUnderRefreshMatview = false; t_thrd.postgres_cxt.mark_explain_analyze = false; t_thrd.postgres_cxt.mark_explain_only = false; if (unlikely(t_thrd.log_cxt.msgbuf->data != NULL)) { diff --git a/src/gausskernel/process/tcop/utility.cpp b/src/gausskernel/process/tcop/utility.cpp index de24f87e9..10fbe49cb 100644 --- a/src/gausskernel/process/tcop/utility.cpp +++ b/src/gausskernel/process/tcop/utility.cpp @@ -46,6 +46,7 @@ #include "commands/discard.h" #include "commands/explain.h" #include "commands/extension.h" +#include "commands/matview.h" #include "commands/lockcmds.h" #include "commands/portalcmds.h" #include "commands/prepare.h" @@ -362,6 +363,7 @@ static void check_xact_readonly(Node* parse_tree) case T_CreateSeqStmt: case T_CreateStmt: case T_CreateTableAsStmt: + case T_RefreshMatViewStmt: case T_CreateTableSpaceStmt: case T_CreateTrigStmt: case T_CompositeTypeStmt: @@ -2076,6 +2078,7 @@ void ReindexCommand(ReindexStmt* stmt, bool is_top_level) ReindexIndex(stmt->relation, (const char*)stmt->name, &stmt->memUsage); break; case OBJECT_TABLE: + case OBJECT_MATVIEW: case OBJECT_TABLE_PARTITION: ReindexTable(stmt->relation, (const char*)stmt->name, &stmt->memUsage); break; @@ -2661,8 +2664,8 @@ void standard_ProcessUtility(Node* parse_tree, const char* query_string, ParamLi #else AlterTableSpaceOptions((AlterTableSpaceOptionsStmt*)parse_tree); #endif - break; + break; case T_CreateExtensionStmt: CreateExtension((CreateExtensionStmt*)parse_tree); #ifdef PGXC @@ -2823,6 +2826,7 @@ void standard_ProcessUtility(Node* parse_tree, const char* query_string, ParamLi } /* fall through */ case OBJECT_SEQUENCE: + case OBJECT_MATVIEW: case OBJECT_VIEW: #ifdef PGXC { @@ -4786,6 +4790,9 @@ void standard_ProcessUtility(Node* parse_tree, const char* query_string, ParamLi case T_CreateTableAsStmt: ExecCreateTableAs((CreateTableAsStmt*)parse_tree, query_string, params, completion_tag); break; + case T_RefreshMatViewStmt: + ExecRefreshMatView((RefreshMatViewStmt*)parse_tree, query_string, params, completion_tag); + break; case T_AlterSystemStmt: PreventTransactionChain(is_top_level, "ALTER SYSTEM SET"); @@ -6531,9 +6538,10 @@ bool QueryReturnsTuples(Query* parse_tree) * We assume it is invoked only on already-parse-analyzed statements * (else the contained parse_tree isn't a Query yet). * - * In some cases (currently, only EXPLAIN of CREATE TABLE AS/SELECT INTO), - * potentially Query-containing utility statements can be nested. This - * function will drill down to a non-utility Query, or return NULL if none. + * In some cases (currently, only EXPLAIN of CREATE TABLE AS/SELECT INTO and + * CREATE MATERIALIZED VIEW), potentially Query-containing utility statements + * can be nested. This function will drill down to a non-utility Query, or + * return NULL if none. */ Query* UtilityContainsQuery(Node* parse_tree) { @@ -6681,6 +6689,9 @@ static const char* AlterObjectTypeCommandTag(ObjectType obj_type) case OBJECT_VIEW: tag = "ALTER VIEW"; break; + case OBJECT_MATVIEW: + tag = "ALTER MATERIALIZED VIEW"; + break; case OBJECT_DATA_SOURCE: tag = "ALTER DATA SOURCE"; break; @@ -6905,6 +6916,9 @@ const char* CreateCommandTag(Node* parse_tree) case OBJECT_VIEW: tag = "DROP VIEW"; break; + case OBJECT_MATVIEW: + tag = "DROP MATERIALIZED VIEW"; + break; case OBJECT_INDEX: tag = "DROP INDEX"; break; @@ -7005,7 +7019,10 @@ const char* CreateCommandTag(Node* parse_tree) break; case T_RenameStmt: - tag = AlterObjectTypeCommandTag(((RenameStmt*)parse_tree)->renameType); + tag = AlterObjectTypeCommandTag( + ((RenameStmt*)parse_tree)->renameType == OBJECT_COLUMN ? + ((RenameStmt*)parse_tree)->relationType : + ((RenameStmt*)parse_tree)->renameType); break; case T_AlterObjectSchemaStmt: @@ -7174,10 +7191,23 @@ const char* CreateCommandTag(Node* parse_tree) break; case T_CreateTableAsStmt: - if (((CreateTableAsStmt*)parse_tree)->is_select_into) - tag = "SELECT INTO"; - else - tag = "CREATE TABLE AS"; + switch (((CreateTableAsStmt*)parse_tree)->relkind) + { + case OBJECT_TABLE: + if (((CreateTableAsStmt*)parse_tree)->is_select_into) + tag = "SELECT INTO"; + else + tag = "CREATE TABLE AS"; + break; + case OBJECT_MATVIEW: + tag = "CREATE MATERIALIZED VIEW"; + break; + default: + tag = "?\?\?"; + } + break; + case T_RefreshMatViewStmt: + tag = "REFRESH MATERIALIZED VIEW"; break; case T_AlterSystemStmt: @@ -8029,6 +8059,7 @@ LogStmtLevel GetCommandLogLevel(Node* parse_tree) lev = LOGSTMT_DDL; break; + case T_RefreshMatViewStmt: case T_AlterSystemStmt: lev = LOGSTMT_DDL; break; diff --git a/src/gausskernel/process/threadpool/knl_session.cpp b/src/gausskernel/process/threadpool/knl_session.cpp index 6f7ac037b..84dbdaf16 100755 --- a/src/gausskernel/process/threadpool/knl_session.cpp +++ b/src/gausskernel/process/threadpool/knl_session.cpp @@ -465,6 +465,7 @@ void knl_u_commands_init(knl_u_commands_context* cmd_cxt) { cmd_cxt->TableSpaceUsageArray = NULL; cmd_cxt->isUnderCreateForeignTable = false; + cmd_cxt->isUnderRefreshMatview = false; cmd_cxt->CurrentExtensionObject = InvalidOid; cmd_cxt->PendingLibraryDeletes = NIL; diff --git a/src/gausskernel/runtime/executor/execMain.cpp b/src/gausskernel/runtime/executor/execMain.cpp index d6b41deca..bce75c951 100644 --- a/src/gausskernel/runtime/executor/execMain.cpp +++ b/src/gausskernel/runtime/executor/execMain.cpp @@ -48,6 +48,7 @@ #include "catalog/pg_statistic.h" #include "catalog/pg_statistic_ext.h" #include "catalog/namespace.h" +#include "commands/matview.h" #include "commands/trigger.h" #include "executor/execdebug.h" #include "executor/nodeRecursiveunion.h" @@ -1318,7 +1319,7 @@ static void InitPlan(QueryDesc *queryDesc, int eflags) * it is a parameterless subplan (not initplan), we suggest that it be * prepared to handle REWIND efficiently; otherwise there is no need. */ - sp_eflags = eflags & EXEC_FLAG_EXPLAIN_ONLY; + sp_eflags = eflags & (EXEC_FLAG_EXPLAIN_ONLY | EXEC_FLAG_WITH_NO_DATA); if (bms_is_member(i, plannedstmt->rewindPlanIDs)) { sp_eflags |= EXEC_FLAG_REWIND; } @@ -1463,6 +1464,11 @@ void CheckValidResultRel(Relation resultRel, CmdType operation) break; } break; + case RELKIND_MATVIEW: + if (!MatViewIncrementalMaintenanceIsEnabled()) + ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change materialized view \"%s\"", RelationGetRelationName(resultRel)))); + break; case RELKIND_FOREIGN_TABLE: /* Okay only if the FDW supports it */ fdwroutine = GetFdwRoutineForRelation(resultRel, false); @@ -1536,9 +1542,17 @@ static void CheckValidRowMarkRel(Relation rel, RowMarkType markType) errmsg("cannot lock rows in TOAST relation \"%s\"", RelationGetRelationName(rel)))); break; case RELKIND_VIEW: - /* Should not get here */ + /* Allow referencing a matview, but not actual locking clauses */ + if (markType != ROW_MARK_REFERENCE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot lock rows in materialized view \"%s\"", + RelationGetRelationName(rel)))); + break; + case RELKIND_MATVIEW: + /* Should not get here; planner should have used ROW_MARK_COPY */ ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot lock rows in view \"%s\"", RelationGetRelationName(rel)))); + errmsg("cannot lock rows in materialized view \"%s\"", RelationGetRelationName(rel)))); break; case RELKIND_FOREIGN_TABLE: /* Should not get here; planner should have used ROW_MARK_COPY */ diff --git a/src/gausskernel/runtime/executor/execUtils.cpp b/src/gausskernel/runtime/executor/execUtils.cpp index 97e129dfc..30dad3e92 100755 --- a/src/gausskernel/runtime/executor/execUtils.cpp +++ b/src/gausskernel/runtime/executor/execUtils.cpp @@ -1031,7 +1031,7 @@ Relation ExecOpenScanRelation(EState* estate, Index scanrelid) } } - /* OK, open the relation and acquire lock as needed */ + /* Open the relation and acquire lock as needed */ reloid = getrelid(scanrelid, estate->es_range_table); rel = heap_open(reloid, lockmode); diff --git a/src/gausskernel/runtime/executor/nodeModifyTable.cpp b/src/gausskernel/runtime/executor/nodeModifyTable.cpp index decd04027..d40c94d5b 100644 --- a/src/gausskernel/runtime/executor/nodeModifyTable.cpp +++ b/src/gausskernel/runtime/executor/nodeModifyTable.cpp @@ -1866,7 +1866,7 @@ TupleTableSlot* ExecModifyTable(ModifyTableState* node) bool isNull = false; relkind = result_rel_info->ri_RelationDesc->rd_rel->relkind; - if (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE) { + if (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE || relkind == RELKIND_MATVIEW) { datum = ExecGetJunkAttribute(slot, junk_filter->jf_junkAttNo, &isNull); /* shouldn't ever get a null result... */ if (isNull) { @@ -2404,7 +2404,7 @@ ModifyTableState* ExecInitModifyTable(ModifyTable* node, EState* estate, int efl char relkind; relkind = result_rel_info->ri_RelationDesc->rd_rel->relkind; - if (relkind == RELKIND_RELATION) { + if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW) { j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); if (!AttributeNumberIsValid(j->jf_junkAttNo)) { ereport(ERROR, diff --git a/src/gausskernel/runtime/executor/nodeSeqscan.cpp b/src/gausskernel/runtime/executor/nodeSeqscan.cpp index b851b5fde..a7611dbac 100755 --- a/src/gausskernel/runtime/executor/nodeSeqscan.cpp +++ b/src/gausskernel/runtime/executor/nodeSeqscan.cpp @@ -322,8 +322,7 @@ static AbsTblScanDesc InitBeginScan(SeqScanState* node, Relation current_relatio /* ---------------------------------------------------------------- * InitScanRelation * - * This does the initialization for scan relations and - * subplans of scans. + * Set up to access the scan relation. * ---------------------------------------------------------------- */ void InitScanRelation(SeqScanState* node, EState* estate) @@ -410,6 +409,7 @@ void InitScanRelation(SeqScanState* node, EState* estate) node->ss_currentRelation = current_relation; node->ss_currentScanDesc = current_scan_desc; + /* and report the scan tuple slot's rowtype */ ExecAssignScanType(node, RelationGetDescr(current_relation)); } static inline void InitSeqNextMtd(SeqScan* node, SeqScanState* scanstate) diff --git a/src/gausskernel/runtime/executor/spi.cpp b/src/gausskernel/runtime/executor/spi.cpp index 53ed8b539..821887964 100755 --- a/src/gausskernel/runtime/executor/spi.cpp +++ b/src/gausskernel/runtime/executor/spi.cpp @@ -2069,9 +2069,19 @@ static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot sn * SELECT INTO. */ if (IsA(stmt, CreateTableAsStmt)) { - Assert(strncmp(completionTag, "SELECT ", 7) == 0); - u_sess->SPI_cxt._current->processed = strtoul(completionTag + 7, NULL, 10); - if (((CreateTableAsStmt *)stmt)->is_select_into) { + CreateTableAsStmt *ctastmt = (CreateTableAsStmt *) stmt; + if (strncmp(completionTag, "SELECT ", 7) == 0) { + u_sess->SPI_cxt._current->processed = strtoul(completionTag + 7, NULL, 10); + } else { + /* + * Must be an IF NOT EXISTS that did nothing, or a + * CREATE ... WITH NO DATA. + */ + Assert(ctastmt->into->skipData); + u_sess->SPI_cxt._current->processed = 0; + } + + if (ctastmt->is_select_into) { res = SPI_OK_SELINTO; } else { res = SPI_OK_UTILITY; diff --git a/src/gausskernel/storage/access/common/reloptions.cpp b/src/gausskernel/storage/access/common/reloptions.cpp index 8d4dd6634..c93262ada 100644 --- a/src/gausskernel/storage/access/common/reloptions.cpp +++ b/src/gausskernel/storage/access/common/reloptions.cpp @@ -863,6 +863,7 @@ bytea* extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions) case RELKIND_RELATION: case RELKIND_TOASTVALUE: case RELKIND_VIEW: + case RELKIND_MATVIEW: options = heap_reloptions(classForm->relkind, datum, false); break; case RELKIND_INDEX: @@ -1531,6 +1532,7 @@ bytea* heap_reloptions(char relkind, Datum reloptions, bool validate) } return (bytea*)rdopts; case RELKIND_RELATION: + case RELKIND_MATVIEW: return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP); case RELKIND_VIEW: return default_reloptions(reloptions, validate, RELOPT_KIND_VIEW); diff --git a/src/gausskernel/storage/access/heap/heapam.cpp b/src/gausskernel/storage/access/heap/heapam.cpp index aa9fcde36..4605999b7 100644 --- a/src/gausskernel/storage/access/heap/heapam.cpp +++ b/src/gausskernel/storage/access/heap/heapam.cpp @@ -3189,7 +3189,8 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup, CommandId * If the new tuple is too big for storage or contains already toasted * out-of-line attributes from some other relation, invoke the toaster. */ - if (relation->rd_rel->relkind != RELKIND_RELATION) { + if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_MATVIEW) { /* toast table entries should never be recursively toasted */ Assert(!HeapTupleHasExternal(tup)); return tup; @@ -3934,7 +3935,8 @@ l1: * because we need to look at the contents of the tuple, but it's OK to * release the content lock on the buffer first. */ - if (relation->rd_rel->relkind != RELKIND_RELATION) { + if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_MATVIEW) { /* toast table entries should never be recursively toasted */ Assert(!HeapTupleHasExternal(&tp)); } else if (HeapTupleHasExternal(&tp)) @@ -4346,7 +4348,8 @@ l2: * We need to invoke the toaster if there are already any out-of-line * toasted values present, or if the new tuple is over-threshold. */ - if (relation->rd_rel->relkind != RELKIND_RELATION) { + if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_MATVIEW) { /* toast table entries should never be recursively toasted */ Assert(!HeapTupleHasExternal(&oldtup)); Assert(!HeapTupleHasExternal(newtup)); diff --git a/src/gausskernel/storage/access/heap/tuptoaster.cpp b/src/gausskernel/storage/access/heap/tuptoaster.cpp index 0bf875f0a..c1a5d5a83 100644 --- a/src/gausskernel/storage/access/heap/tuptoaster.cpp +++ b/src/gausskernel/storage/access/heap/tuptoaster.cpp @@ -342,10 +342,10 @@ void toast_delete(Relation rel, HeapTuple oldtup) bool toast_isnull[MaxHeapAttributeNumber]; /* - * We should only ever be called for tuples of plain relations --- - * recursing on a toast rel is bad news. + * We should only ever be called for tuples of plain relations or + * materialized views --- recursing on a toast rel is bad news. */ - Assert(rel->rd_rel->relkind == RELKIND_RELATION); + Assert(rel->rd_rel->relkind == RELKIND_RELATION || rel->rd_rel->relkind == RELKIND_MATVIEW); /* * Get the tuple descriptor and break down the tuple into fields. diff --git a/src/gausskernel/storage/lmgr/predicate.cpp b/src/gausskernel/storage/lmgr/predicate.cpp index 66414a6ca..5b9832923 100755 --- a/src/gausskernel/storage/lmgr/predicate.cpp +++ b/src/gausskernel/storage/lmgr/predicate.cpp @@ -378,11 +378,11 @@ static void OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *read /* ------------------------------------------------------------------------ */ /* * Does this relation participate in predicate locking? Temporary and system - * relations are exempt. + * relations are exempt, as are materialized views. */ static inline bool PredicateLockingNeededForRelation(Relation relation) { - return !(relation->rd_id < FirstBootstrapObjectId); + return !(relation->rd_id < FirstBootstrapObjectId || relation->rd_rel->relkind == RELKIND_MATVIEW); } /* diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index d7316815f..3dc09f0ef 100755 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -149,4 +149,7 @@ extern bool tupledesc_have_pck(TupleConstr* constr); extern void copyDroppedAttribute(Form_pg_attribute target, Form_pg_attribute source); +/* Accessor for the i'th attribute of tupdesc. */ +#define TupleDescAttr(tupdesc, i) (&(tupdesc)->attrs[(i)]) + #endif /* TUPDESC_H */ diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h index 0f57510e7..1e9c548d2 100755 --- a/src/include/catalog/pg_amop.h +++ b/src/include/catalog/pg_amop.h @@ -508,6 +508,16 @@ DATA(insert ( 2994 2249 2249 3 s 2988 403 0 )); DATA(insert ( 2994 2249 2249 4 s 2993 403 0 )); DATA(insert ( 2994 2249 2249 5 s 2991 403 0 )); +/* + * btree record_image_ops + */ + +DATA(insert ( 3194 2249 2249 1 s 3190 403 0 )); +DATA(insert ( 3194 2249 2249 2 s 3192 403 0 )); +DATA(insert ( 3194 2249 2249 3 s 3188 403 0 )); +DATA(insert ( 3194 2249 2249 4 s 3193 403 0 )); +DATA(insert ( 3194 2249 2249 5 s 3191 403 0 )); + /* * btree uuid_ops */ diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index 6dd02c566..dbb0d8f8e 100755 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -126,6 +126,7 @@ DATA(insert ( 1989 26 26 1 356 )); DATA(insert ( 1989 26 26 2 3134 )); DATA(insert ( 1991 30 30 1 404 )); DATA(insert ( 2994 2249 2249 1 2987 )); +DATA(insert ( 3194 2249 2249 1 3997 )); DATA(insert ( 1994 25 25 1 360 )); DATA(insert ( 1994 25 25 2 3255 )); DATA(insert ( 1996 1083 1083 1 1107 )); diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index ff565fefc..dd0f30e7a 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -168,6 +168,7 @@ DESCR(""); #define RELKIND_VIEW 'v' /* view */ #define RELKIND_COMPOSITE_TYPE 'c' /* composite type */ #define RELKIND_FOREIGN_TABLE 'f' /* foreign table */ +#define RELKIND_MATVIEW 'm' /* materialized view */ #define PARTTYPE_PARTITIONED_RELATION 'p' /* partitioned relation */ #define PARTTYPE_VALUE_PARTITIONED_RELATION 'v' /* value partitioned relation */ #define PARTTYPE_NON_PARTITIONED_RELATION 'n' /* non-partitioned relation */ diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h index 03ad24095..2a00c6bf5 100755 --- a/src/include/catalog/pg_opclass.h +++ b/src/include/catalog/pg_opclass.h @@ -143,6 +143,7 @@ DATA(insert ( 405 oid_ops PGNSP PGUID 1990 26 t 0 )); DATA(insert ( 403 oidvector_ops PGNSP PGUID 1991 30 t 0 )); DATA(insert ( 405 oidvector_ops PGNSP PGUID 1992 30 t 0 )); DATA(insert ( 403 record_ops PGNSP PGUID 2994 2249 t 0 )); +DATA(insert ( 403 record_image_ops PGNSP PGUID 3194 2249 f 0 )); DATA(insert OID = 3126 ( 403 text_ops PGNSP PGUID 1994 25 t 0 )); #define TEXT_BTREE_OPS_OID 3126 DATA(insert ( 405 text_ops PGNSP PGUID 1995 25 t 0 )); diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index 2fdf59b67..8b99d4417 100755 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -1861,6 +1861,20 @@ DESCR("less than or equal"); DATA(insert OID = 2993 (">=" PGNSP PGUID b f f 2249 2249 16 2992 2990 record_ge scalargtsel scalargtjoinsel)); DESCR("greater than or equal"); +/* byte-oriented tests for identical rows and fast sorting */ +DATA(insert OID = 3188 ( "*=" PGNSP PGUID b t f 2249 2249 16 3188 3189 record_image_eq eqsel eqjoinsel)); +DESCR("identical"); +DATA(insert OID = 3189 ( "*<>" PGNSP PGUID b f f 2249 2249 16 3189 3188 record_image_ne neqsel neqjoinsel)); +DESCR("not identical"); +DATA(insert OID = 3190 ( "*<" PGNSP PGUID b f f 2249 2249 16 3191 3193 record_image_lt scalarltsel scalarltjoinsel)); +DESCR("less than"); +DATA(insert OID = 3191 ( "*>" PGNSP PGUID b f f 2249 2249 16 3190 3192 record_image_gt scalargtsel scalargtjoinsel)); +DESCR("greater than"); +DATA(insert OID = 3192 ( "*<=" PGNSP PGUID b f f 2249 2249 16 3193 3191 record_image_le scalarltsel scalarltjoinsel)); +DESCR("less than or equal"); +DATA(insert OID = 3193 ( "*>=" PGNSP PGUID b f f 2249 2249 16 3192 3190 record_image_ge scalargtsel scalargtjoinsel)); +DESCR("greater than or equal"); + /* generic range type operators */ DATA(insert OID = 3882 ("=" PGNSP PGUID b t t 3831 3831 16 3882 3883 range_eq eqsel eqjoinsel)); DESCR("equal"); diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index 849a79362..e863dd773 100755 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -96,6 +96,7 @@ DATA(insert OID = 1990 (405 oid_ops PGNSP PGUID)); DATA(insert OID = 1991 (403 oidvector_ops PGNSP PGUID)); DATA(insert OID = 1992 (405 oidvector_ops PGNSP PGUID)); DATA(insert OID = 2994 (403 record_ops PGNSP PGUID)); +DATA(insert OID = 3194 (403 record_image_ops PGNSP PGUID)); DATA(insert OID = 1994 (403 text_ops PGNSP PGUID)); #define TEXT_BTREE_FAM_OID 1994 DATA(insert OID = 1995 (405 text_ops PGNSP PGUID)); diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h index b1a180d2e..0f295ed55 100755 --- a/src/include/catalog/toasting.h +++ b/src/include/catalog/toasting.h @@ -26,6 +26,7 @@ extern bool createToastTableForPartition(Oid relOid, Datum reloptions, List *relfilenode); +extern void NewRelationCreateToastTable(Oid relOid, Datum reloptions); /* * This macro is just to keep the C compiler from spitting up on the * upcoming commands for genbki.pl. diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h new file mode 100644 index 000000000..35d0c3d46 --- /dev/null +++ b/src/include/commands/matview.h @@ -0,0 +1,30 @@ +/* ------------------------------------------------------------------------- + * + * matview.h + * prototypes for matview.c. + * + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/matview.h + * + * ------------------------------------------------------------------------- + */ +#ifndef MATVIEW_H +#define MATVIEW_H + +#include "nodes/params.h" +#include "nodes/parsenodes.h" +#include "tcop/dest.h" +#include "utils/relcache.h" + +extern void SetMatViewPopulatedState(const Relation relation, bool newstate); + +extern void ExecRefreshMatView(const RefreshMatViewStmt* stmt, const char* queryString, ParamListInfo params, + char* completionTag); + +extern DestReceiver *CreateTransientRelDestReceiver(Oid oid); +extern bool MatViewIncrementalMaintenanceIsEnabled(void); + +#endif /* MATVIEW_H */ diff --git a/src/include/commands/user.h b/src/include/commands/user.h index b414b5a85..0d20f686e 100755 --- a/src/include/commands/user.h +++ b/src/include/commands/user.h @@ -66,7 +66,7 @@ int64 SearchAllAccounts(); void InitAccountLockHashTable(); extern USER_STATUS GetAccountLockedStatusFromHashTable(Oid roleid); extern void UpdateAccountInfoFromHashTable(); - +extern List* roleNamesToIds(const List* memberNames); extern inline void str_reset(char* str) { diff --git a/src/include/commands/view.h b/src/include/commands/view.h index e7b7e35d7..47d99667e 100644 --- a/src/include/commands/view.h +++ b/src/include/commands/view.h @@ -18,5 +18,6 @@ extern void DefineView(ViewStmt* stmt, const char* queryString, bool isFirstNode = true); extern bool IsViewTemp(ViewStmt* stmt, const char* queryString); +extern void StoreViewQuery(Oid viewOid, Query* viewParse, bool replace); #endif /* VIEW_H */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 1003df2f0..d3d16347c 100755 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -67,6 +67,7 @@ #define EXEC_FLAG_SKIP_TRIGGERS 0x0010 /* skip AfterTrigger calls */ #define EXEC_FLAG_WITH_OIDS 0x0020 /* force OIDs in returned tuples */ #define EXEC_FLAG_WITHOUT_OIDS 0x0040 /* force no OIDs in returned tuples */ +#define EXEC_FLAG_WITH_NO_DATA 0x0080 /* rel scannability doesn't matter */ extern inline bool is_errmodule_enable(int elevel, ModuleId mod_id); diff --git a/src/include/knl/knl_session.h b/src/include/knl/knl_session.h index 169729724..a5fe56fff 100644 --- a/src/include/knl/knl_session.h +++ b/src/include/knl/knl_session.h @@ -539,6 +539,7 @@ typedef struct knl_u_commands_context { /* Tablespace usage information management struct */ struct TableSpaceUsageStruct* TableSpaceUsageArray; bool isUnderCreateForeignTable; + bool isUnderRefreshMatview; Oid CurrentExtensionObject; diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 465b0676b..a43502969 100755 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -71,6 +71,7 @@ extern Node* makeTidConst(ItemPointer item); extern FuncCall* makeFuncCall(List* funcname, List* args, int location); extern Param* makeParam( ParamKind paramkind, int paramid, Oid paramtype, int32 paramtypmod, Oid paramcollid, int location); +extern ColumnDef* makeColumnDef(const char* colname, Oid typeOid, int32 typmod, Oid collOid); extern IndexInfo* makeIndexInfo(int numattrs, List *expressions, List *predicates, bool unique, bool isready, bool concurrent); #endif /* MAKEFUNC_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 067fe7c96..1f5398918 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -452,6 +452,7 @@ typedef enum NodeTag { T_DropDirectoryStmt, T_CreateRlsPolicyStmt, T_AlterRlsPolicyStmt, + T_RefreshMatViewStmt, T_AlterSystemStmt, /* diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ea707833e..3c1faae88 100755 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1406,6 +1406,7 @@ typedef enum ObjectType { OBJECT_INTERNAL_PARTITION, OBJECT_LANGUAGE, OBJECT_LARGEOBJECT, + OBJECT_MATVIEW, OBJECT_OPCLASS, OBJECT_OPERATOR, OBJECT_OPFAMILY, @@ -3291,6 +3292,9 @@ typedef struct ExplainStmt { * A query written as SELECT ... INTO will be transformed to this form during * parse analysis. * + * A query written as CREATE MATERIALIZED view will produce this node type, + * during parse analysis, since it needs all the same data. + * * The "query" field is handled similarly to EXPLAIN, though note that it * can be a SELECT or an EXECUTE, but not other DML statements. * ---------------------- @@ -3299,6 +3303,7 @@ typedef struct CreateTableAsStmt { NodeTag type; Node* query; /* the query (see comments above) */ IntoClause* into; /* destination table */ + ObjectType relkind; /* OBJECT_TABLE or OBJECT_MATVIEW */ bool is_select_into; /* it was written as SELECT INTO */ #ifdef PGXC void* parserSetup; @@ -3306,6 +3311,17 @@ typedef struct CreateTableAsStmt { #endif } CreateTableAsStmt; +/* ---------------------- + * REFRESH MATERIALIZED VIEW Statement + * ---------------------- + */ +typedef struct RefreshMatViewStmt +{ + NodeTag type; + bool skipData; /* true for WITH NO DATA */ + RangeVar *relation; /* relation to insert into */ +} RefreshMatViewStmt; + /* ---------------------- * Checkpoint Statement * ---------------------- @@ -3352,7 +3368,7 @@ typedef struct ConstraintsSetStmt { */ typedef struct ReindexStmt { NodeTag type; - ObjectType kind; /* OBJECT_INDEX, OBJECT_TABLE, OBJECT_INTERNAL, OBJECT_DATABASE */ + ObjectType kind; /* OBJECT_INDEX, OBJECT_TABLE, OBJECT_INTERNAL, etc */ RangeVar* relation; /* Table or index to reindex */ const char* name; /* name of database to reindex */ bool do_system; /* include system tables in database case */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 31b2f0057..c161e16a2 100755 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -93,7 +93,11 @@ typedef struct RangeVar { } RangeVar; /* - * IntoClause - target information for SELECT INTO and CREATE TABLE AS + * IntoClause - target information for SELECT INTO, CREATE MATERIALIZED VIEW and CREATE TABLE AS + * + * For CREATE MATERIALIZED VIEW, viewQuery is the parsed-but-not-rewritten + * SELECT Query for the view; otherwise it's NULL. (Although it's actually + * Query*, we declare it as Node* to avoid a forward reference.) */ typedef struct IntoClause { NodeTag type; @@ -104,6 +108,7 @@ typedef struct IntoClause { OnCommitAction onCommit; /* what do we do at COMMIT? */ int8 row_compress; /* row compression flag */ char* tableSpaceName; /* table space to use, or NULL */ + Node* viewQuery; /* materialized view's SELECT query */ bool skipData; /* true for WITH NO DATA */ #ifdef PGXC struct DistributeBy* distributeby; /* distribution to use, or NULL */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index b41bbc999..6fa202ba7 100755 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -298,6 +298,7 @@ PG_KEYWORD("loop", LOOP, UNRESERVED_KEYWORD) PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD) PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD) PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD) +PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD) PG_KEYWORD("maxextents", MAXEXTENTS, UNRESERVED_KEYWORD) PG_KEYWORD("maxsize", MAXSIZE, UNRESERVED_KEYWORD) PG_KEYWORD("maxtrans", MAXTRANS, UNRESERVED_KEYWORD) @@ -409,6 +410,7 @@ PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD) PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD) PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD) PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD) +PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD) PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD) PG_KEYWORD("reject", REJECT_P, RESERVED_KEYWORD) PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD) diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h index 68267966e..d84419e87 100755 --- a/src/include/parser/parse_clause.h +++ b/src/include/parser/parse_clause.h @@ -20,7 +20,7 @@ extern void transformFromClause(ParseState* pstate, List* frmList, bool isFirstNode = true, bool isCreateView = false); extern int setTargetTable(ParseState* pstate, RangeVar* relation, bool inh, bool alsoSource, AclMode requiredPerms); extern bool interpretInhOption(InhOption inhOpt); -extern bool interpretOidsOption(List* defList); +extern bool interpretOidsOption(List* defList, bool allowOids); extern Node* transformFromClauseItem(ParseState* pstate, Node* n, RangeTblEntry** top_rte, int* top_rti, RangeTblEntry** right_rte, int* right_rti, List** relnamespace, Relids* containedRels, bool isFirstNode = true, diff --git a/src/include/parser/parse_param.h b/src/include/parser/parse_param.h index eded99b7f..2798a0ad9 100644 --- a/src/include/parser/parse_param.h +++ b/src/include/parser/parse_param.h @@ -18,5 +18,6 @@ extern void parse_fixed_parameters(ParseState* pstate, Oid* paramTypes, int numParams); extern void parse_variable_parameters(ParseState* pstate, Oid** paramTypes, int* numParams); extern void check_variable_parameters(ParseState* pstate, Query* query); +extern bool query_contains_extern_params(Query* query); #endif /* PARSE_PARAM_H */ diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index ca430ffcc..7db2ffa4e 100755 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -54,7 +54,7 @@ extern int attnameAttNum(Relation rd, const char* attname, bool sysColOK); extern Name attnumAttName(Relation rd, int attid); extern Oid attnumTypeId(Relation rd, int attid); extern Oid attnumCollationId(Relation rd, int attid); - +extern bool isQueryUsingTempRelation(Query* query); #ifdef PGXC extern int specialAttNum(const char* attname); #endif diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 343343462..1a116cabf 100755 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -1683,7 +1683,7 @@ static inline void pgstat_reset_waitStatePhase(WaitState waitstatus, WaitStatePh (rel)->pgstat_info->t_counts.t_cu_hdd_asyn += (n); \ } while (0) -extern void pgstat_count_heap_insert(Relation rel, int n); +extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n); extern void pgstat_count_heap_update(Relation rel, bool hot); extern void pgstat_count_heap_delete(Relation rel); extern void pgstat_update_heap_dead_tuples(Relation rel, int delta); diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h index d83f9f814..464324e08 100755 --- a/src/include/tcop/dest.h +++ b/src/include/tcop/dest.h @@ -91,6 +91,7 @@ typedef enum { DestIntoRel, /* results sent to relation (SELECT INTO) */ DestCopyOut, /* results sent to COPY TO code */ DestSQLFunction, /* results sent to SQL-language func mgr */ + DestTransientRel, /* results sent to transient relation */ DestSPITupleAnalyze, /* results sent to SPI manager when analyze for table sample */ DestTupleBroadCast, /* results send to consumer thread in a broadcast way */ diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index f2aacdca0..058b46292 100755 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -737,6 +737,13 @@ extern Datum record_gt(PG_FUNCTION_ARGS); extern Datum record_le(PG_FUNCTION_ARGS); extern Datum record_ge(PG_FUNCTION_ARGS); extern Datum btrecordcmp(PG_FUNCTION_ARGS); +extern Datum record_image_eq(PG_FUNCTION_ARGS); +extern Datum record_image_ne(PG_FUNCTION_ARGS); +extern Datum record_image_lt(PG_FUNCTION_ARGS); +extern Datum record_image_gt(PG_FUNCTION_ARGS); +extern Datum record_image_le(PG_FUNCTION_ARGS); +extern Datum record_image_ge(PG_FUNCTION_ARGS); +extern Datum btrecordimagecmp(PG_FUNCTION_ARGS); /* ruleutils.c */ extern Datum pg_get_ruledef(PG_FUNCTION_ARGS); @@ -779,6 +786,8 @@ extern List* deparse_context_for_plan(Node* plan, List* ancestors, List* rtable) #endif extern const char* quote_identifier(const char* ident); extern char* quote_qualified_identifier(const char* qualifier, const char* ident); +extern void generate_operator_clause(fmStringInfo buf, const char* leftop, Oid leftoptype, Oid opoid, + const char* rightop, Oid rightoptype); extern char* generate_collation_name(Oid collid); /* tid.c */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 0fdaf7b8f..98b02b6e9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -101,6 +101,7 @@ typedef struct RelationData { int rd_refcnt; /* reference count */ BackendId rd_backend; /* owning backend id, if temporary relation */ bool rd_isnailed; /* rel is nailed in cache */ + bool relispopulated; /* matview has query results */ bool rd_isvalid; /* relcache entry is valid */ char rd_indexvalid; /* state of rd_indexlist: 0 = not valid, 1 = * valid, 2 = temporarily forced */ @@ -318,7 +319,9 @@ typedef struct StdRdOptions { * from the pov of logical decoding. */ #define RelationIsUsedAsCatalogTable(relation) \ - ((relation)->rd_options ? ((StdRdOptions*)(relation)->rd_options)->user_catalog_table : false) + ((relation)->rd_options && \ + ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_MATVIEW) ? \ + ((StdRdOptions*)(relation)->rd_options)->user_catalog_table : false) #define RelationIsInternal(relation) (RelationGetInternalMask(relation) != INTERNAL_MASK_DISABLE) @@ -474,6 +477,23 @@ typedef struct StdRdOptions { ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT || \ (((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP) && STMT_RETRY_ENABLED)) + +/* + * RelationIsScannable + * Currently can only be false for a materialized view which has not been + * populated by its query. This is likely to get more complicated later, + * so use a macro which looks like a function. + */ +#define RelationIsScannable(relation) ((relation)->relispopulated) + +/* + * RelationIsPopulated + * Currently, we don't physically distinguish the "populated" and + * "scannable" properties of matviews, but that may change later. + * Hence, use the appropriate one of these macros in code tests. + */ +#define RelationIsPopulated(relation) ((relation)->relispopulated) + /* * RelationUsesLocalBuffers * True if relation's pages are stored in local buffers. diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index 6ccab8eb6..3fff11e19 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -240,7 +240,7 @@ NOTICE: drop cascades to table inhe CREATE TABLE ctlt4 (a int, b text); CREATE SEQUENCE ctlseq1; CREATE TABLE ctlt10 (LIKE ctlseq1); -- fail -ERROR: "ctlseq1" is not a table, view, composite type, or foreign table +ERROR: "ctlseq1" is not a table, view, materialized view, composite type, or foreign table LINE 1: CREATE TABLE ctlt10 (LIKE ctlseq1); ^ CREATE VIEW ctlv1 AS SELECT * FROM ctlt4; diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out new file mode 100644 index 000000000..fa9700ba3 --- /dev/null +++ b/src/test/regress/expected/matview.out @@ -0,0 +1,546 @@ +-- create a table to use as a basis for views and materialized views in various combinations +CREATE TABLE mvtest_t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "mvtest_t_pkey" for table "mvtest_t" +INSERT INTO mvtest_t VALUES + (1, 'x', 2), + (2, 'x', 3), + (3, 'y', 5), + (4, 'y', 7), + (5, 'z', 11); +-- we want a view based on the table, too, since views present additional challenges +CREATE VIEW mvtest_tv AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type; +SELECT * FROM mvtest_tv ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +-- create a materialized view with no data, and confirm correct behavior +EXPLAIN (analyze on, costs off) + CREATE MATERIALIZED VIEW mvtest_tm AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type WITH NO DATA; + QUERY PLAN +---------------------------------------------------------- + HashAggregate (Actual time: never executed) + Group By Key: type + -> Seq Scan on mvtest_t (Actual time: never executed) +--? Total runtime: .* ms +(4 rows) + +SELECT * FROM mvtest_tm ORDER BY type; + type | totamt +------+-------- +(0 rows) + +REFRESH MATERIALIZED VIEW mvtest_tm; +CREATE UNIQUE INDEX mvtest_tm_type ON mvtest_tm (type); +SELECT * FROM mvtest_tm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +-- create various views +EXPLAIN (analyze on, costs off) + CREATE MATERIALIZED VIEW mvtest_tvm AS SELECT * FROM mvtest_tv ORDER BY type; + QUERY PLAN +---------------------------------------------------------------------------- +--? Sort (actual time=.* rows=3 loops=1) + Sort Key: mvtest_t.type + Sort Method: quicksort Memory: 25kB +--? -> HashAggregate (actual time=.* rows=3 loops=1) + Group By Key: mvtest_t.type +--? -> Seq Scan on mvtest_t (actual time=.* rows=5 loops=1) +--? Total runtime: .* ms +(7 rows) + +SELECT * FROM mvtest_tvm; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +CREATE MATERIALIZED VIEW mvtest_tmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tm; +CREATE MATERIALIZED VIEW mvtest_tvmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tvm; +CREATE UNIQUE INDEX mvtest_tvmm_expr ON mvtest_tvmm ((grandtot > 0)); +CREATE UNIQUE INDEX mvtest_tvmm_pred ON mvtest_tvmm (grandtot) WHERE grandtot < 0; +CREATE VIEW mvtest_tvv AS SELECT sum(totamt) AS grandtot FROM mvtest_tv; +EXPLAIN (analyze on, costs off) + CREATE MATERIALIZED VIEW mvtest_tvvm AS SELECT * FROM mvtest_tvv; + QUERY PLAN +---------------------------------------------------------------------------- +--? Aggregate (actual time=.* rows=1 loops=1) +--? -> HashAggregate (actual time=.* rows=3 loops=1) + Group By Key: mvtest_t.type +--? -> Seq Scan on mvtest_t (actual time=0.008..0.009 rows=5 loops=1) +--? Total runtime: .* ms +(5 rows) + +CREATE VIEW mvtest_tvvmv AS SELECT * FROM mvtest_tvvm; +CREATE MATERIALIZED VIEW mvtest_bb AS SELECT * FROM mvtest_tvvmv; +CREATE INDEX mvtest_aa ON mvtest_bb (grandtot); +-- check that plans seem reasonable +\d+ mvtest_tvm + Materialized view "public.mvtest_tvm" + Column | Type | Modifiers | Storage | Stats target | Description +--------+---------+-----------+----------+--------------+------------- + type | text | | extended | | + totamt | numeric | | main | | +View definition: + SELECT * + FROM mvtest_tv + ORDER BY mvtest_tv.type; +Replica Identity: NOTHING + +\d+ mvtest_tvm + Materialized view "public.mvtest_tvm" + Column | Type | Modifiers | Storage | Stats target | Description +--------+---------+-----------+----------+--------------+------------- + type | text | | extended | | + totamt | numeric | | main | | +View definition: + SELECT * + FROM mvtest_tv + ORDER BY mvtest_tv.type; +Replica Identity: NOTHING + +\d+ mvtest_tvvm + Materialized view "public.mvtest_tvvm" + Column | Type | Modifiers | Storage | Stats target | Description +----------+---------+-----------+---------+--------------+------------- + grandtot | numeric | | main | | +View definition: + SELECT * + FROM mvtest_tvv; +Replica Identity: NOTHING + +\d+ mvtest_bb + Materialized view "public.mvtest_bb" + Column | Type | Modifiers | Storage | Stats target | Description +----------+---------+-----------+---------+--------------+------------- + grandtot | numeric | | main | | +Indexes: + "mvtest_aa" btree (grandtot) TABLESPACE pg_default +View definition: + SELECT * + FROM mvtest_tvvmv; +Replica Identity: NOTHING + +-- test schema behavior +CREATE SCHEMA mvtest_mvschema; +ALTER MATERIALIZED VIEW mvtest_tvm SET SCHEMA mvtest_mvschema; +\d+ mvtest_tvm +\d+ mvtest_tvmm + Materialized view "public.mvtest_tvmm" + Column | Type | Modifiers | Storage | Stats target | Description +----------+---------+-----------+---------+--------------+------------- + grandtot | numeric | | main | | +Indexes: + "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric)) TABLESPACE pg_default + "mvtest_tvmm_pred" UNIQUE, btree (grandtot) TABLESPACE pg_default WHERE grandtot < 0::numeric +View definition: + SELECT sum(mvtest_tvm.totamt) AS grandtot + FROM mvtest_mvschema.mvtest_tvm; +Replica Identity: NOTHING + +SET search_path = mvtest_mvschema, public; +\d+ mvtest_tvm + Materialized view "mvtest_mvschema.mvtest_tvm" + Column | Type | Modifiers | Storage | Stats target | Description +--------+---------+-----------+----------+--------------+------------- + type | text | | extended | | + totamt | numeric | | main | | +View definition: + SELECT * + FROM mvtest_tv + ORDER BY mvtest_tv.type; +Replica Identity: NOTHING + +-- modify the underlying table data +INSERT INTO mvtest_t VALUES (6, 'z', 13); +-- confirm pre- and post-refresh contents of fairly simple materialized views +SELECT * FROM mvtest_tm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +SELECT * FROM mvtest_tvm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +REFRESH MATERIALIZED VIEW mvtest_tm; +REFRESH MATERIALIZED VIEW mvtest_tvm; +SELECT * FROM mvtest_tm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 24 +(3 rows) + +SELECT * FROM mvtest_tvm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 24 +(3 rows) + +RESET search_path; +-- confirm pre- and post-refresh contents of nested materialized views +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tmm; + QUERY PLAN +------------------------------------------------------------------ +--? Seq Scan on mvtest_tmm (actual time=.* rows=1 loops=1) +--? Total runtime: .* ms +(2 rows) + +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tvmm; + QUERY PLAN +------------------------------------------------------------------- +--? Seq Scan on mvtest_tvmm (actual time=.* rows=1 loops=1) +--? Total runtime: .* ms +(2 rows) + +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tvvm; + QUERY PLAN +------------------------------------------------------------------- +--? Seq Scan on mvtest_tvvm (actual time=.* rows=1 loops=1) +--? Total runtime: .* ms +(2 rows) + +SELECT * FROM mvtest_tmm; + grandtot +---------- + 28 +(1 row) + +SELECT * FROM mvtest_tvmm; + grandtot +---------- + 28 +(1 row) + +SELECT * FROM mvtest_tvvm; + grandtot +---------- + 28 +(1 row) + +REFRESH MATERIALIZED VIEW mvtest_tmm; +REFRESH MATERIALIZED VIEW mvtest_tvmm; +REFRESH MATERIALIZED VIEW mvtest_tvvm; +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tmm; + QUERY PLAN +------------------------------------------------------------------ +--? Seq Scan on mvtest_tmm (actual time=.* rows=1 loops=1) +--? Total runtime: .* ms +(2 rows) + +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tvmm; + QUERY PLAN +------------------------------------------------------------------- +--? Seq Scan on mvtest_tvmm (actual time=.* rows=1 loops=1) +--? Total runtime: .* ms +(2 rows) + +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tvvm; + QUERY PLAN +------------------------------------------------------------------- +--? Seq Scan on mvtest_tvvm (actual time=.* rows=1 loops=1) +--? Total runtime: .* ms +(2 rows) + +SELECT * FROM mvtest_tmm; + grandtot +---------- + 41 +(1 row) + +SELECT * FROM mvtest_tvmm; + grandtot +---------- + 41 +(1 row) + +SELECT * FROM mvtest_tvvm; + grandtot +---------- + 41 +(1 row) + +-- test diemv when the mv does not exist +DROP MATERIALIZED VIEW IF EXISTS no_such_mv; +NOTICE: materialized view "no_such_mv" does not exist, skipping +-- no tuple locks on materialized views +SELECT * FROM mvtest_tvvm FOR SHARE; +ERROR: cannot lock rows in materialized view "mvtest_tvvm" +-- test join of mv and view +SELECT type, m.totamt AS mtot, v.totamt AS vtot FROM mvtest_tm m LEFT JOIN mvtest_tv v USING (type) ORDER BY type; + type | mtot | vtot +------+------+------ + x | 5 | 5 + y | 12 | 12 + z | 24 | 24 +(3 rows) + +-- some additional tests not using base tables +CREATE VIEW mvtest_vt1 AS SELECT 1 moo; +CREATE VIEW mvtest_vt2 AS SELECT moo, 2*moo FROM mvtest_vt1 UNION ALL SELECT moo, 3*moo FROM mvtest_vt1; +\d+ mvtest_vt2 + View "public.mvtest_vt2" + Column | Type | Modifiers | Storage | Description +----------+---------+-----------+---------+------------- + moo | integer | | plain | + ?column? | integer | | plain | +View definition: + SELECT mvtest_vt1.moo, 2 * mvtest_vt1.moo AS "?column?" + FROM mvtest_vt1 +UNION ALL + SELECT mvtest_vt1.moo, 3 * mvtest_vt1.moo AS "?column?" + FROM mvtest_vt1; + +CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM mvtest_vt2 UNION ALL SELECT moo, 3*moo FROM mvtest_vt2; +\d+ mv_test2 + Materialized view "public.mv_test2" + Column | Type | Modifiers | Storage | Stats target | Description +----------+---------+-----------+---------+--------------+------------- + moo | integer | | plain | | + ?column? | integer | | plain | | +View definition: + SELECT mvtest_vt2.moo, 2 * mvtest_vt2.moo AS "?column?" + FROM mvtest_vt2 +UNION ALL + SELECT mvtest_vt2.moo, 3 * mvtest_vt2.moo AS "?column?" + FROM mvtest_vt2; +Replica Identity: NOTHING + +CREATE MATERIALIZED VIEW mv_test3 AS SELECT * FROM mv_test2 WHERE moo = 12345; +DROP VIEW mvtest_vt1 CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to view mvtest_vt2 +drop cascades to materialized view mv_test2 +drop cascades to materialized view mv_test3 +-- test that duplicate values on unique index prevent refresh +CREATE TABLE mvtest_foo(a, b) AS VALUES(1, 10); +CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; +CREATE UNIQUE INDEX ON mvtest_mv(a); +INSERT INTO mvtest_foo SELECT * FROM mvtest_foo; +REFRESH MATERIALIZED VIEW mvtest_mv; +ERROR: could not create unique index "mvtest_mv_a_idx" +DETAIL: Key (a)=(1) is duplicated. +DROP TABLE mvtest_foo CASCADE; +NOTICE: drop cascades to materialized view mvtest_mv +-- make sure that all columns covered by unique indexes works +CREATE TABLE mvtest_foo(a, b, c) AS VALUES(1, 2, 3); +CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; +CREATE UNIQUE INDEX ON mvtest_mv (a); +CREATE UNIQUE INDEX ON mvtest_mv (b); +CREATE UNIQUE INDEX on mvtest_mv (c); +INSERT INTO mvtest_foo VALUES(2, 3, 4); +INSERT INTO mvtest_foo VALUES(3, 4, 5); +REFRESH MATERIALIZED VIEW mvtest_mv; +DROP TABLE mvtest_foo CASCADE; +NOTICE: drop cascades to materialized view mvtest_mv +-- allow subquery to reference unpopulated matview if WITH NO DATA is specified +CREATE MATERIALIZED VIEW mvtest_mv1 AS SELECT 1 AS col1 WITH NO DATA; +CREATE MATERIALIZED VIEW mvtest_mv2 AS SELECT * FROM mvtest_mv1 + WHERE col1 = (SELECT LEAST(col1) FROM mvtest_mv1) WITH NO DATA; +DROP MATERIALIZED VIEW mvtest_mv1 CASCADE; +NOTICE: drop cascades to materialized view mvtest_mv2 +-- make sure that types with unusual equality tests work +CREATE TABLE mvtest_boxes (id serial primary key, b box); +NOTICE: CREATE TABLE will create implicit sequence "mvtest_boxes_id_seq" for serial column "mvtest_boxes.id" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "mvtest_boxes_pkey" for table "mvtest_boxes" +INSERT INTO mvtest_boxes (b) VALUES + ('(32,32),(31,31)'), + ('(2.0000004,2.0000004),(1,1)'), + ('(1.9999996,1.9999996),(1,1)'); +CREATE MATERIALIZED VIEW mvtest_boxmv AS SELECT * FROM mvtest_boxes; +CREATE UNIQUE INDEX mvtest_boxmv_id ON mvtest_boxmv (id); +UPDATE mvtest_boxes SET b = '(2,2),(1,1)' WHERE id = 2; +REFRESH MATERIALIZED VIEW mvtest_boxmv; +SELECT * FROM mvtest_boxmv ORDER BY id; + id | b +----+----------------------------- + 1 | (32,32),(31,31) + 2 | (2,2),(1,1) + 3 | (1.9999996,1.9999996),(1,1) +(3 rows) + +DROP TABLE mvtest_boxes CASCADE; +NOTICE: drop cascades to materialized view mvtest_boxmv +-- make sure that column names are handled correctly +CREATE TABLE mvtest_v (i int, j int); +CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj, kk) AS SELECT i, j FROM mvtest_v; -- error +ERROR: too many column names were specified +CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj) AS SELECT i, j FROM mvtest_v; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_2 (ii) AS SELECT i, j FROM mvtest_v; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj, kk) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- error +ERROR: too many column names were specified +CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_4 (ii) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok +ALTER TABLE mvtest_v RENAME COLUMN i TO x; +INSERT INTO mvtest_v values (1, 2); +CREATE UNIQUE INDEX mvtest_mv_v_ii ON mvtest_mv_v (ii); +REFRESH MATERIALIZED VIEW mvtest_mv_v; +UPDATE mvtest_v SET j = 3 WHERE x = 1; +REFRESH MATERIALIZED VIEW mvtest_mv_v; +REFRESH MATERIALIZED VIEW mvtest_mv_v_2; +REFRESH MATERIALIZED VIEW mvtest_mv_v_3; +REFRESH MATERIALIZED VIEW mvtest_mv_v_4; +SELECT * FROM mvtest_v; + x | j +---+--- + 1 | 3 +(1 row) + +SELECT * FROM mvtest_mv_v; + ii | jj +----+---- + 1 | 3 +(1 row) + +SELECT * FROM mvtest_mv_v_2; + ii | j +----+--- + 1 | 3 +(1 row) + +SELECT * FROM mvtest_mv_v_3; + ii | jj +----+---- + 1 | 3 +(1 row) + +SELECT * FROM mvtest_mv_v_4; + ii | j +----+--- + 1 | 3 +(1 row) + +DROP TABLE mvtest_v CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to materialized view mvtest_mv_v +drop cascades to materialized view mvtest_mv_v_2 +drop cascades to materialized view mvtest_mv_v_3 +drop cascades to materialized view mvtest_mv_v_4 +-- Check that unknown literals are converted to "text" in CREATE MATVIEW, +-- so that we don't end up with unknown-type columns. +CREATE MATERIALIZED VIEW mv_unspecified_types AS + SELECT 42 as i, 42.5 as num, 'foo' as u, 'foo'::unknown as u2, null as n; +\d+ mv_unspecified_types + Materialized view "public.mv_unspecified_types" + Column | Type | Modifiers | Storage | Stats target | Description +--------+---------+-----------+----------+--------------+------------- + i | integer | | plain | | + num | numeric | | main | | + u | text | | extended | | + u2 | text | | extended | | + n | text | | extended | | +View definition: + SELECT 42 AS i, 42.5 AS num, 'foo'::text AS u, 'foo'::text AS u2, + NULL::text AS n; +Replica Identity: NOTHING + +SELECT * FROM mv_unspecified_types; + i | num | u | u2 | n +----+------+-----+-----+--- + 42 | 42.5 | foo | foo | +(1 row) + +DROP MATERIALIZED VIEW mv_unspecified_types; +-- make sure that create WITH NO DATA does not plan the query (bug #13907) +create materialized view mvtest_error as select 1/0 as x; -- fail +ERROR: division by zero +CONTEXT: referenced column: x +create materialized view mvtest_error as select 1/0 as x with no data; +refresh materialized view mvtest_error; -- fail here +ERROR: division by zero +CONTEXT: referenced column: x +drop materialized view mvtest_error; +-- make sure that matview rows can be referenced as source rows (bug #9398) +CREATE TABLE mvtest_v AS SELECT generate_series(1,10) AS a; +CREATE MATERIALIZED VIEW mvtest_mv_v AS SELECT a FROM mvtest_v WHERE a <= 5; +DELETE FROM mvtest_v WHERE ( SELECT * FROM mvtest_mv_v WHERE mvtest_mv_v.a = mvtest_v.a ); +SELECT * FROM mvtest_v; + a +---- + 6 + 7 + 8 + 9 + 10 +(5 rows) + +SELECT * FROM mvtest_mv_v; + a +--- + 1 + 2 + 3 + 4 + 5 +(5 rows) + +DROP TABLE mvtest_v CASCADE; +NOTICE: drop cascades to materialized view mvtest_mv_v +-- make sure running as superuser works when MV owned by another role (bug #11208) +CREATE ROLE regress_user_mvtest IDENTIFIED BY 'test@123'; +CREATE SCHEMA regress_user_mvtest AUTHORIZATION regress_user_mvtest; +SET ROLE regress_user_mvtest PASSWORD 'test@123'; +CREATE TABLE regress_user_mvtest.mvtest_foo_data AS SELECT i, md5(random()::text) + FROM generate_series(1, 10) i; +CREATE MATERIALIZED VIEW regress_user_mvtest.mvtest_mv_foo AS SELECT * FROM regress_user_mvtest.mvtest_foo_data; +CREATE MATERIALIZED VIEW regress_user_mvtest.mvtest_mv_foo AS SELECT * FROM regress_user_mvtest.mvtest_foo_data; +ERROR: relation "mvtest_mv_foo" already exists +CREATE UNIQUE INDEX ON regress_user_mvtest.mvtest_mv_foo (i); +RESET ROLE; +REFRESH MATERIALIZED VIEW regress_user_mvtest.mvtest_mv_foo; +DROP OWNED BY regress_user_mvtest CASCADE; +DROP ROLE regress_user_mvtest; +-- make sure that create WITH NO DATA works via SPI +BEGIN; +CREATE FUNCTION mvtest_func() + RETURNS void AS $$ +BEGIN + CREATE MATERIALIZED VIEW mvtest1 AS SELECT 1 AS x; + CREATE MATERIALIZED VIEW mvtest2 AS SELECT 1 AS x WITH NO DATA; +END; +$$ LANGUAGE plpgsql; +SELECT mvtest_func(); + mvtest_func +------------- + +(1 row) + +SELECT * FROM mvtest1; + x +--- + 1 +(1 row) + +SELECT * FROM mvtest2; + x +--- +(0 rows) + +ROLLBACK; \ No newline at end of file diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 5dd1bae06..94ffb71ba 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -2257,6 +2257,7 @@ WHERE d.classoid IS NULL AND p1.oid <= 9999 order by 1; 3986 | pg_shared_memory_detail 3987 | pg_shared_memctx_detail 3989 | gin_clean_pending_list + 3997 | btrecordimagecmp 3998 | update_pgjob 3999 | pg_tde_info 4000 | gs_fault_inject @@ -2636,7 +2637,7 @@ WHERE d.classoid IS NULL AND p1.oid <= 9999 order by 1; 9016 | pg_advisory_lock 9017 | pgxc_unlock_for_sp_database 9999 | pg_test_err_contain_err -(2274 rows) +(2275 rows) -- **************** pg_cast **************** -- Catch bogus values in pg_cast columns (other than cases detected by diff --git a/src/test/regress/expected/opr_sanity_2.out b/src/test/regress/expected/opr_sanity_2.out index bd4f9a4eb..6e1238a1b 100644 --- a/src/test/regress/expected/opr_sanity_2.out +++ b/src/test/regress/expected/opr_sanity_2.out @@ -179,13 +179,18 @@ FROM pg_amop p1 LEFT JOIN pg_operator p2 ON amopopr = p2.oid ORDER BY 1, 2, 3; amopmethod | amopstrategy | oprname ------------+--------------+--------- + 403 | 1 | *< 403 | 1 | < 403 | 1 | ~<~ + 403 | 2 | *<= 403 | 2 | <= 403 | 2 | ~<=~ + 403 | 3 | *= 403 | 3 | = + 403 | 4 | *>= 403 | 4 | >= 403 | 4 | ~>=~ + 403 | 5 | *> 403 | 5 | > 403 | 5 | ~>~ 405 | 1 | = @@ -246,7 +251,7 @@ ORDER BY 1, 2, 3; 4239 | 5 | > 4444 | 1 | @@ 4444 | 2 | @@@ -(67 rows) +(72 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 6d3ad294f..ad4568226 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1885,4 +1885,4 @@ View definition: SELECT v.q AS x, v.w FROM ( VALUES (1,2)) v(q, w); -drop view rule_v1; +drop view rule_v1; \ No newline at end of file diff --git a/src/test/regress/expected/single_node_opr_sanity.out b/src/test/regress/expected/single_node_opr_sanity.out index 01d19f172..4178e37e2 100755 --- a/src/test/regress/expected/single_node_opr_sanity.out +++ b/src/test/regress/expected/single_node_opr_sanity.out @@ -2296,6 +2296,7 @@ WHERE d.classoid IS NULL AND p1.oid <= 9999 order by 1; 3986 | pg_shared_memory_detail 3987 | pg_shared_memctx_detail 3989 | gin_clean_pending_list + 3997 | btrecordimagecmp 3998 | update_pgjob 3999 | pg_tde_info 4000 | gs_fault_inject @@ -2675,7 +2676,7 @@ WHERE d.classoid IS NULL AND p1.oid <= 9999 order by 1; 9016 | pg_advisory_lock 9017 | pgxc_unlock_for_sp_database 9999 | pg_test_err_contain_err -(2274 rows) +(2275 rows) -- Check prokind select count(*) from pg_proc where prokind = 'a'; @@ -2693,7 +2694,7 @@ select count(*) from pg_proc where prokind = 'w'; select count(*) from pg_proc where prokind = 'f'; count ------- - 3142 + 3149 (1 row) select count(*) from pg_proc where prokind = 'p'; @@ -3422,13 +3423,18 @@ FROM pg_amop p1 LEFT JOIN pg_operator p2 ON amopopr = p2.oid ORDER BY 1, 2, 3; amopmethod | amopstrategy | oprname ------------+--------------+--------- + 403 | 1 | *< 403 | 1 | < 403 | 1 | ~<~ + 403 | 2 | *<= 403 | 2 | <= 403 | 2 | ~<=~ + 403 | 3 | *= 403 | 3 | = + 403 | 4 | *>= 403 | 4 | >= 403 | 4 | ~>=~ + 403 | 5 | *> 403 | 5 | > 403 | 5 | ~>~ 405 | 1 | = @@ -3489,7 +3495,7 @@ ORDER BY 1, 2, 3; 4239 | 5 | > 4444 | 1 | @@ 4444 | 2 | @@@ -(67 rows) +(72 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/output/gsql.source b/src/test/regress/output/gsql.source index ef072c8b8..467cbc8ab 100644 --- a/src/test/regress/output/gsql.source +++ b/src/test/regress/output/gsql.source @@ -109,6 +109,7 @@ Informational \di[S+] [PATTERN] list indexes \dl list large objects, same as \lo_list \dL[S+] [PATTERN] list procedural languages + \dm[S+] [PATTERN] list materialized views \dn[S+] [PATTERN] list schemas \do[S] [PATTERN] list operators \dO[S+] [PATTERN] list collations diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 22ff7b266..2c49c0347 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -302,7 +302,7 @@ test: bypass_preparedexecute_support test: string_digit_to_numeric # Another group of parallel tests # ---------- -test: collate tablesample tablesample_1 tablesample_2 +test: collate tablesample tablesample_1 tablesample_2 matview # ---------- # Another group of parallel tests diff --git a/src/test/regress/parallel_schedule0 b/src/test/regress/parallel_schedule0 index 37ca35417..1b6f8ef2c 100644 --- a/src/test/regress/parallel_schedule0 +++ b/src/test/regress/parallel_schedule0 @@ -125,6 +125,8 @@ test: single_node_privileges2 #test: single_node_misc # rules cannot run concurrently with any test that creates a view #test: single_node_rules +#test for materialized view +test: matview # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index d55dca964..fca2bff2f 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -94,6 +94,7 @@ test: prepared_xacts test: privileges test: security_label test: collate +test: matview test: replica_identity test: misc test: rules diff --git a/src/test/regress/sql/matview.sql b/src/test/regress/sql/matview.sql new file mode 100644 index 000000000..756077192 --- /dev/null +++ b/src/test/regress/sql/matview.sql @@ -0,0 +1,215 @@ +-- create a table to use as a basis for views and materialized views in various combinations +CREATE TABLE mvtest_t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL); +INSERT INTO mvtest_t VALUES + (1, 'x', 2), + (2, 'x', 3), + (3, 'y', 5), + (4, 'y', 7), + (5, 'z', 11); + +-- we want a view based on the table, too, since views present additional challenges +CREATE VIEW mvtest_tv AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type; +SELECT * FROM mvtest_tv ORDER BY type; + +-- create a materialized view with no data, and confirm correct behavior +EXPLAIN (analyze on, costs off) + CREATE MATERIALIZED VIEW mvtest_tm AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type WITH NO DATA; +SELECT * FROM mvtest_tm ORDER BY type; +REFRESH MATERIALIZED VIEW mvtest_tm; +CREATE UNIQUE INDEX mvtest_tm_type ON mvtest_tm (type); +SELECT * FROM mvtest_tm ORDER BY type; + +-- create various views +EXPLAIN (analyze on, costs off) + CREATE MATERIALIZED VIEW mvtest_tvm AS SELECT * FROM mvtest_tv ORDER BY type; +SELECT * FROM mvtest_tvm; +CREATE MATERIALIZED VIEW mvtest_tmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tm; +CREATE MATERIALIZED VIEW mvtest_tvmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tvm; +CREATE UNIQUE INDEX mvtest_tvmm_expr ON mvtest_tvmm ((grandtot > 0)); +CREATE UNIQUE INDEX mvtest_tvmm_pred ON mvtest_tvmm (grandtot) WHERE grandtot < 0; +CREATE VIEW mvtest_tvv AS SELECT sum(totamt) AS grandtot FROM mvtest_tv; +EXPLAIN (analyze on, costs off) + CREATE MATERIALIZED VIEW mvtest_tvvm AS SELECT * FROM mvtest_tvv; +CREATE VIEW mvtest_tvvmv AS SELECT * FROM mvtest_tvvm; +CREATE MATERIALIZED VIEW mvtest_bb AS SELECT * FROM mvtest_tvvmv; +CREATE INDEX mvtest_aa ON mvtest_bb (grandtot); + +-- check that plans seem reasonable +\d+ mvtest_tvm +\d+ mvtest_tvm +\d+ mvtest_tvvm +\d+ mvtest_bb + +-- test schema behavior +CREATE SCHEMA mvtest_mvschema; +ALTER MATERIALIZED VIEW mvtest_tvm SET SCHEMA mvtest_mvschema; +\d+ mvtest_tvm +\d+ mvtest_tvmm +SET search_path = mvtest_mvschema, public; +\d+ mvtest_tvm + +-- modify the underlying table data +INSERT INTO mvtest_t VALUES (6, 'z', 13); + +-- confirm pre- and post-refresh contents of fairly simple materialized views +SELECT * FROM mvtest_tm ORDER BY type; +SELECT * FROM mvtest_tvm ORDER BY type; +REFRESH MATERIALIZED VIEW mvtest_tm; +REFRESH MATERIALIZED VIEW mvtest_tvm; +SELECT * FROM mvtest_tm ORDER BY type; +SELECT * FROM mvtest_tvm ORDER BY type; +RESET search_path; + +-- confirm pre- and post-refresh contents of nested materialized views +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tmm; +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tvmm; +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tvvm; +SELECT * FROM mvtest_tmm; +SELECT * FROM mvtest_tvmm; +SELECT * FROM mvtest_tvvm; +REFRESH MATERIALIZED VIEW mvtest_tmm; +REFRESH MATERIALIZED VIEW mvtest_tvmm; +REFRESH MATERIALIZED VIEW mvtest_tvvm; +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tmm; +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tvmm; +EXPLAIN (analyze on, costs off) + SELECT * FROM mvtest_tvvm; +SELECT * FROM mvtest_tmm; +SELECT * FROM mvtest_tvmm; +SELECT * FROM mvtest_tvvm; + +-- test diemv when the mv does not exist +DROP MATERIALIZED VIEW IF EXISTS no_such_mv; + +-- no tuple locks on materialized views +SELECT * FROM mvtest_tvvm FOR SHARE; + +-- test join of mv and view +SELECT type, m.totamt AS mtot, v.totamt AS vtot FROM mvtest_tm m LEFT JOIN mvtest_tv v USING (type) ORDER BY type; + +-- some additional tests not using base tables +CREATE VIEW mvtest_vt1 AS SELECT 1 moo; +CREATE VIEW mvtest_vt2 AS SELECT moo, 2*moo FROM mvtest_vt1 UNION ALL SELECT moo, 3*moo FROM mvtest_vt1; +\d+ mvtest_vt2 +CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM mvtest_vt2 UNION ALL SELECT moo, 3*moo FROM mvtest_vt2; +\d+ mv_test2 +CREATE MATERIALIZED VIEW mv_test3 AS SELECT * FROM mv_test2 WHERE moo = 12345; + +DROP VIEW mvtest_vt1 CASCADE; + +-- test that duplicate values on unique index prevent refresh +CREATE TABLE mvtest_foo(a, b) AS VALUES(1, 10); +CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; +CREATE UNIQUE INDEX ON mvtest_mv(a); +INSERT INTO mvtest_foo SELECT * FROM mvtest_foo; +REFRESH MATERIALIZED VIEW mvtest_mv; +DROP TABLE mvtest_foo CASCADE; + +-- make sure that all columns covered by unique indexes works +CREATE TABLE mvtest_foo(a, b, c) AS VALUES(1, 2, 3); +CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; +CREATE UNIQUE INDEX ON mvtest_mv (a); +CREATE UNIQUE INDEX ON mvtest_mv (b); +CREATE UNIQUE INDEX on mvtest_mv (c); +INSERT INTO mvtest_foo VALUES(2, 3, 4); +INSERT INTO mvtest_foo VALUES(3, 4, 5); +REFRESH MATERIALIZED VIEW mvtest_mv; +DROP TABLE mvtest_foo CASCADE; + +-- allow subquery to reference unpopulated matview if WITH NO DATA is specified +CREATE MATERIALIZED VIEW mvtest_mv1 AS SELECT 1 AS col1 WITH NO DATA; +CREATE MATERIALIZED VIEW mvtest_mv2 AS SELECT * FROM mvtest_mv1 + WHERE col1 = (SELECT LEAST(col1) FROM mvtest_mv1) WITH NO DATA; +DROP MATERIALIZED VIEW mvtest_mv1 CASCADE; + +-- make sure that types with unusual equality tests work +CREATE TABLE mvtest_boxes (id serial primary key, b box); +INSERT INTO mvtest_boxes (b) VALUES + ('(32,32),(31,31)'), + ('(2.0000004,2.0000004),(1,1)'), + ('(1.9999996,1.9999996),(1,1)'); +CREATE MATERIALIZED VIEW mvtest_boxmv AS SELECT * FROM mvtest_boxes; +CREATE UNIQUE INDEX mvtest_boxmv_id ON mvtest_boxmv (id); +UPDATE mvtest_boxes SET b = '(2,2),(1,1)' WHERE id = 2; +REFRESH MATERIALIZED VIEW mvtest_boxmv; +SELECT * FROM mvtest_boxmv ORDER BY id; +DROP TABLE mvtest_boxes CASCADE; + +-- make sure that column names are handled correctly +CREATE TABLE mvtest_v (i int, j int); +CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj, kk) AS SELECT i, j FROM mvtest_v; -- error +CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj) AS SELECT i, j FROM mvtest_v; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_2 (ii) AS SELECT i, j FROM mvtest_v; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj, kk) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- error +CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_4 (ii) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok +ALTER TABLE mvtest_v RENAME COLUMN i TO x; +INSERT INTO mvtest_v values (1, 2); +CREATE UNIQUE INDEX mvtest_mv_v_ii ON mvtest_mv_v (ii); +REFRESH MATERIALIZED VIEW mvtest_mv_v; +UPDATE mvtest_v SET j = 3 WHERE x = 1; +REFRESH MATERIALIZED VIEW mvtest_mv_v; +REFRESH MATERIALIZED VIEW mvtest_mv_v_2; +REFRESH MATERIALIZED VIEW mvtest_mv_v_3; +REFRESH MATERIALIZED VIEW mvtest_mv_v_4; +SELECT * FROM mvtest_v; +SELECT * FROM mvtest_mv_v; +SELECT * FROM mvtest_mv_v_2; +SELECT * FROM mvtest_mv_v_3; +SELECT * FROM mvtest_mv_v_4; +DROP TABLE mvtest_v CASCADE; + +-- Check that unknown literals are converted to "text" in CREATE MATVIEW, +-- so that we don't end up with unknown-type columns. +CREATE MATERIALIZED VIEW mv_unspecified_types AS + SELECT 42 as i, 42.5 as num, 'foo' as u, 'foo'::unknown as u2, null as n; +\d+ mv_unspecified_types +SELECT * FROM mv_unspecified_types; +DROP MATERIALIZED VIEW mv_unspecified_types; + +-- make sure that create WITH NO DATA does not plan the query (bug #13907) +create materialized view mvtest_error as select 1/0 as x; -- fail +create materialized view mvtest_error as select 1/0 as x with no data; +refresh materialized view mvtest_error; -- fail here +drop materialized view mvtest_error; + +-- make sure that matview rows can be referenced as source rows (bug #9398) +CREATE TABLE mvtest_v AS SELECT generate_series(1,10) AS a; +CREATE MATERIALIZED VIEW mvtest_mv_v AS SELECT a FROM mvtest_v WHERE a <= 5; +DELETE FROM mvtest_v WHERE ( SELECT * FROM mvtest_mv_v WHERE mvtest_mv_v.a = mvtest_v.a ); +SELECT * FROM mvtest_v; +SELECT * FROM mvtest_mv_v; +DROP TABLE mvtest_v CASCADE; + +-- make sure running as superuser works when MV owned by another role (bug #11208) +CREATE ROLE regress_user_mvtest IDENTIFIED BY 'test@123'; +CREATE SCHEMA regress_user_mvtest AUTHORIZATION regress_user_mvtest; +SET ROLE regress_user_mvtest PASSWORD 'test@123'; +CREATE TABLE regress_user_mvtest.mvtest_foo_data AS SELECT i, md5(random()::text) + FROM generate_series(1, 10) i; +CREATE MATERIALIZED VIEW regress_user_mvtest.mvtest_mv_foo AS SELECT * FROM regress_user_mvtest.mvtest_foo_data; +CREATE MATERIALIZED VIEW regress_user_mvtest.mvtest_mv_foo AS SELECT * FROM regress_user_mvtest.mvtest_foo_data; +CREATE UNIQUE INDEX ON regress_user_mvtest.mvtest_mv_foo (i); +RESET ROLE; +REFRESH MATERIALIZED VIEW regress_user_mvtest.mvtest_mv_foo; +DROP OWNED BY regress_user_mvtest CASCADE; +DROP ROLE regress_user_mvtest; + +-- make sure that create WITH NO DATA works via SPI +BEGIN; +CREATE FUNCTION mvtest_func() + RETURNS void AS $$ +BEGIN + CREATE MATERIALIZED VIEW mvtest1 AS SELECT 1 AS x; + CREATE MATERIALIZED VIEW mvtest2 AS SELECT 1 AS x WITH NO DATA; +END; +$$ LANGUAGE plpgsql; +SELECT mvtest_func(); +SELECT * FROM mvtest1; +SELECT * FROM mvtest2; +ROLLBACK; diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index 45fdb274e..05e16e421 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -129,6 +129,105 @@ insert into cc values('("(1,2)",3)'); insert into cc values('("(4,5)",6)'); select * from cc order by f1; -- fail, but should complain about cantcompare + +-- +-- Tests for record_{eq,cmp} +-- + +create type testtype1 as (a int, b int); + +-- all true +select row(1, 2)::testtype1 < row(1, 3)::testtype1; +select row(1, 2)::testtype1 <= row(1, 3)::testtype1; +select row(1, 2)::testtype1 = row(1, 2)::testtype1; +select row(1, 2)::testtype1 <> row(1, 3)::testtype1; +select row(1, 3)::testtype1 >= row(1, 2)::testtype1; +select row(1, 3)::testtype1 > row(1, 2)::testtype1; + +-- all false +select row(1, -2)::testtype1 < row(1, -3)::testtype1; +select row(1, -2)::testtype1 <= row(1, -3)::testtype1; +select row(1, -2)::testtype1 = row(1, -3)::testtype1; +select row(1, -2)::testtype1 <> row(1, -2)::testtype1; +select row(1, -3)::testtype1 >= row(1, -2)::testtype1; +select row(1, -3)::testtype1 > row(1, -2)::testtype1; + +-- true, but see *< below +select row(1, -2)::testtype1 < row(1, 3)::testtype1; + +-- mismatches +create type testtype3 as (a int, b text); +select row(1, 2)::testtype1 < row(1, 'abc')::testtype3; +select row(1, 2)::testtype1 <> row(1, 'abc')::testtype3; +create type testtype5 as (a int); +select row(1, 2)::testtype1 < row(1)::testtype5; +select row(1, 2)::testtype1 <> row(1)::testtype5; + +-- non-comparable types +create type testtype6 as (a int, b point); +select row(1, '(1,2)')::testtype6 < row(1, '(1,3)')::testtype6; +select row(1, '(1,2)')::testtype6 <> row(1, '(1,3)')::testtype6; + +drop type testtype1, testtype3, testtype5, testtype6; + +-- +-- Tests for record_image_{eq,cmp} +-- + +create type testtype1 as (a int, b int); + +-- all true +select row(1, 2)::testtype1 *< row(1, 3)::testtype1; +select row(1, 2)::testtype1 *<= row(1, 3)::testtype1; +select row(1, 2)::testtype1 *= row(1, 2)::testtype1; +select row(1, 2)::testtype1 *<> row(1, 3)::testtype1; +select row(1, 3)::testtype1 *>= row(1, 2)::testtype1; +select row(1, 3)::testtype1 *> row(1, 2)::testtype1; + +-- all false +select row(1, -2)::testtype1 *< row(1, -3)::testtype1; +select row(1, -2)::testtype1 *<= row(1, -3)::testtype1; +select row(1, -2)::testtype1 *= row(1, -3)::testtype1; +select row(1, -2)::testtype1 *<> row(1, -2)::testtype1; +select row(1, -3)::testtype1 *>= row(1, -2)::testtype1; +select row(1, -3)::testtype1 *> row(1, -2)::testtype1; + +-- This returns the "wrong" order because record_image_cmp works on +-- unsigned datums without knowing about the actual data type. +select row(1, -2)::testtype1 *< row(1, 3)::testtype1; + +-- other types +create type testtype2 as (a smallint, b bool); -- byval different sizes +select row(1, true)::testtype2 *< row(2, true)::testtype2; +select row(-2, true)::testtype2 *< row(-1, true)::testtype2; +select row(0, false)::testtype2 *< row(0, true)::testtype2; +select row(0, false)::testtype2 *<> row(0, true)::testtype2; + +create type testtype3 as (a int, b text); -- variable length +select row(1, 'abc')::testtype3 *< row(1, 'abd')::testtype3; +select row(1, 'abc')::testtype3 *< row(1, 'abcd')::testtype3; +select row(1, 'abc')::testtype3 *> row(1, 'abd')::testtype3; +select row(1, 'abc')::testtype3 *<> row(1, 'abd')::testtype3; + +create type testtype4 as (a int, b point); -- by ref, fixed length +select row(1, '(1,2)')::testtype4 *< row(1, '(1,3)')::testtype4; +select row(1, '(1,2)')::testtype4 *<> row(1, '(1,3)')::testtype4; + +-- mismatches +select row(1, 2)::testtype1 *< row(1, 'abc')::testtype3; +select row(1, 2)::testtype1 *<> row(1, 'abc')::testtype3; +create type testtype5 as (a int); +select row(1, 2)::testtype1 *< row(1)::testtype5; +select row(1, 2)::testtype1 *<> row(1)::testtype5; + +-- non-comparable types +create type testtype6 as (a int, b point); +select row(1, '(1,2)')::testtype6 *< row(1, '(1,3)')::testtype6; +select row(1, '(1,2)')::testtype6 *>= row(1, '(1,3)')::testtype6; +select row(1, '(1,2)')::testtype6 *<> row(1, '(1,3)')::testtype6; + +drop type testtype1, testtype2, testtype3, testtype4, testtype5, testtype6; + -- -- Test case derived from bug #5716: check multiple uses of a rowtype result --