diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index bede662907c..1e3981c7eb5 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -1153,6 +1153,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, rows = (qc && (qc->commandTag == CMDTAG_COPY || qc->commandTag == CMDTAG_FETCH || qc->commandTag == CMDTAG_SELECT || + qc->commandTag == CMDTAG_REFRESH_DYNAMIC_TABLE || qc->commandTag == CMDTAG_REFRESH_MATERIALIZED_VIEW)) ? qc->nprocessed : 0; diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 2b69b954af4..5389391fbde 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -66,6 +66,7 @@ Complete list of usable sgml source files in this directory. + @@ -114,6 +115,7 @@ Complete list of usable sgml source files in this directory. + @@ -166,6 +168,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/create_dynamic_table.sgml b/doc/src/sgml/ref/create_dynamic_table.sgml new file mode 100644 index 00000000000..536aebe4397 --- /dev/null +++ b/doc/src/sgml/ref/create_dynamic_table.sgml @@ -0,0 +1,185 @@ + + + + + CREATE DYNAMIC TABLE + + + + CREATE DYNAMIC TABLE + 7 + SQL - Language Statements + + + + CREATE DYNAMIC TABLE + define a new dynamic table + + + + +CREATE DYNAMIC TABLE [ IF NOT EXISTS ] table_name + [ (column_name [, ...] ) ] + [ USING method ] + [ WITH ( storage_parameter [= value] [, ... ] ) ] + [ TABLESPACE tablespace_name ] + AS query + [ WITH [ NO ] DATA ] + + + + + Description + + + CREATE DYNAMIC TABLE defines a dynamic table of + a query. The query is executed and used to populate the view at the time + the command is issued (unless WITH NO DATA is used) and may be + refreshed later using REFRESH DYNAMIC TABLE. + + + + CREATE DYNAMIC TABLE is similar to + CREATE TABLE AS, except that it also remembers the query used + to initialize the view, so that it can be refreshed later upon demand. + A dynamic table has many of the same properties as a table, but there + is no support for temporary dynamic tables. + + + + CREATE DYNAMIC TABLE requires + CREATE privilege on the schema used for the dynamic + table. + + + + + Parameters + + + + IF NOT EXISTS + + + Do not throw an error if a dynamic table with the same name already + exists. A notice is issued in this case. Note that there is no guarantee + that the existing dynamic table is anything like the one that would + have been created. + + + + + + table_name + + + The name (optionally schema-qualified) of the dynamic table to be + created. + + + + + + column_name + + + The name of a column in the new dynamic table. If column names are + not provided, they are taken from the output column names of the query. + + + + + + USING method + + + This optional clause specifies the table access method to use to store + the contents for the new dynamic table; the method needs be an + access method of type TABLE. See for more information. If this option is not + specified, the default table access method is chosen for the new + dynamic table. See + for more information. + + + + + + WITH ( storage_parameter [= value] [, ... ] ) + + + This clause specifies optional storage parameters for the new + dynamic table; see + in the + documentation for more + information. All parameters supported for CREATE + TABLE are also supported for CREATE DYNAMIC + TABLE. + See for more information. + + + + + + TABLESPACE tablespace_name + + + The tablespace_name is the name + of the tablespace in which the new dynamic table is to be created. + If not specified, is consulted. + + + + + + query + + + A SELECT, TABLE, + or VALUES command. This query will run within a + security-restricted operation; in particular, calls to functions that + themselves create temporary tables will fail. + + + + + + WITH [ NO ] DATA + + + This clause specifies whether or not the dynamic table should be + populated at creation time. If not, the dynamic table will be + flagged as unscannable and cannot be queried until REFRESH + DYNAMIC TABLE is used. Also, if the view is IMMV, + triggers for maintaining the view are not created. + + + + + + + + + Compatibility + + + CREATE DYNAMIC TABLE is a + Cloudberry extension. + + + + + See Also + + + + + + + + + + diff --git a/doc/src/sgml/ref/drop_dynamic_table.sgml b/doc/src/sgml/ref/drop_dynamic_table.sgml new file mode 100644 index 00000000000..005fee9b538 --- /dev/null +++ b/doc/src/sgml/ref/drop_dynamic_table.sgml @@ -0,0 +1,116 @@ + + + + + DROP DYNAMIC TABLE + + + + DROP DYNAMIC TABLE + 7 + SQL - Language Statements + + + + DROP DYNAMIC TABLE + remove a dynamic table + + + + +DROP DYNAMIC TABLE [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ] + + + + + Description + + + DROP DYNAMIC TABLE drops an existing dynamic + table. To execute this command you must be the owner of the dynamic + table. Since every dynamic table has a auto refresh process of pg_task + job, drop a dynamic table will drop that job too. + + + + + Parameters + + + + IF EXISTS + + + Do not throw an error if the dynamic table does not exist. A notice + is issued in this case. + + + + + + name + + + The name (optionally schema-qualified) of the dynamic table to + remove. + + + + + + CASCADE + + + Automatically drop objects that depend on the dynamic table (such as + other materialized views, or regular views or pg_task jobs), + and in turn all objects that depend on those objects + (see ). + + + + + + RESTRICT + + + Refuse to drop the dynamic table if any objects depend on it. This + is the default. + + + + + + + + Examples + + + This command will remove the dynamic table called + order_summary: + +DROP DYNAMIC TABLE order_summary; + + + + + Compatibility + + + DROP DYNAMIC TABLE is a + Cloudberry extension. + + + + + See Also + + + + + + + + diff --git a/doc/src/sgml/ref/refresh_dynamic_table.sgml b/doc/src/sgml/ref/refresh_dynamic_table.sgml new file mode 100644 index 00000000000..57506fd0f86 --- /dev/null +++ b/doc/src/sgml/ref/refresh_dynamic_table.sgml @@ -0,0 +1,142 @@ + + + + + REFRESH DYNAMIC TABLE + + + + REFRESH DYNAMIC TABLE + 7 + SQL - Language Statements + + + + REFRESH DYNAMIC TABLE + replace the contents of a dynamic table + + + + +REFRESH DYNAMIC TABLE [ CONCURRENTLY ] name + [ WITH [ NO ] DATA ] + + + + + Description + + + REFRESH DYNAMIC TABLE completely replaces the + contents of a dynamic table. To execute this command you must be the + owner of the dynamic table. The old contents are discarded. If + WITH DATA is specified (or defaults) the backing query + is executed to provide the new data, and the dynamic table is left in a + scannable state. If WITH NO DATA is specified no new + data is generated and the dynamic table is left in an unscannable + state. If the view is IMMV, the triggers are dropped. + + + CONCURRENTLY and WITH NO DATA may not + be specified together. + + + + + Parameters + + + + CONCURRENTLY + + + Refresh the dynamic table without locking out concurrent selects on + the dynamic table. Without this option a refresh which affects a + lot of rows will tend to use fewer resources and complete more quickly, + but could block other connections which are trying to read from the + dynamic table. This option may be faster in cases where a small + number of rows are affected. + + + This option is only allowed if there is at least one + UNIQUE index on the dynamic table which uses only + column names and includes all rows; that is, it must not be an + expression index or include a WHERE clause. + + + This option may not be used when the dynamic table is not already + populated. + + + Even with this option only one REFRESH at a time may + run against any one dynamic table. + + + + + + name + + + The name (optionally schema-qualified) of the dynamic table to + refresh. + + + + + + + + Notes + + + If there is an ORDER BY clause in the dynamic + table's defining query, the original contents of the dynamic table + will be ordered that way; but REFRESH DYNAMIC + TABLE does not guarantee to preserve that ordering. + + + + + Examples + + + This command will replace the contents of the dynamic table called + order_summary using the query from the dynamic + table's definition, and leave it in a scannable state: + +REFRESH DYNAMIC TABLE order_summary; + + + + + This command will free storage associated with the dynamic table + annual_statistics_basis and leave it in an unscannable + state: + +REFRESH DYNAMIC TABLE annual_statistics_basis WITH NO DATA; + + + + + Compatibility + + + REFRESH DYNAMIC TABLE is a + Cloudberry extension. + + + + + See Also + + + + + + + + diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index dff7a426452..e341f5e00a3 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -93,6 +93,7 @@ &createConversion; &createDatabase; &createDomain; + &createDynamicTable; &createEventTrigger; &createExtension; &createForeignDataWrapper; @@ -140,6 +141,7 @@ &dropConversion; &dropDatabase; &dropDomain; + &dropDynamicTable; &dropEventTrigger; &dropExtension; &dropForeignDataWrapper; @@ -191,6 +193,7 @@ &prepare; &prepareTransaction; &reassignOwned; + &refreshDynamicTable; &refreshMaterializedView; &reindex; &releaseSavepoint; diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index b33d0c2ecce..d877752ca2c 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1327,6 +1327,7 @@ InsertPgClassTuple(Relation pg_class_desc, values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid); values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid); values[Anum_pg_class_relisivm - 1] = BoolGetDatum(rd_rel->relisivm); + values[Anum_pg_class_relisdynamic - 1] = BoolGetDatum(rd_rel->relisdynamic); if (relacl != (Datum) 0) values[Anum_pg_class_relacl - 1] = relacl; else diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 35089deef1f..af5d24cc135 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1074,6 +1074,7 @@ index_create_internal(Relation heapRelation, indexRelation->rd_rel->relam = accessMethodObjectId; indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid); indexRelation->rd_rel->relisivm = false; + indexRelation->rd_rel->relisdynamic = false; /* * store index's pg_class entry diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 8f0ba5ebf90..3ff481e0f58 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -758,6 +758,9 @@ static const struct object_type_map { "materialized view", OBJECT_MATVIEW }, + { + "dynamic table", OBJECT_MATVIEW + }, { "composite type", -1 }, /* unmapped */ @@ -4384,8 +4387,16 @@ getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok) relname); break; case RELKIND_MATVIEW: - appendStringInfo(buffer, _("materialized view %s"), - relname); + if (relForm->relisdynamic) + { + appendStringInfo(buffer, _("dynamic table %s"), + relname); + } + else + { + appendStringInfo(buffer, _("materialized view %s"), + relname); + } break; case RELKIND_COMPOSITE_TYPE: appendStringInfo(buffer, _("composite type %s"), @@ -4954,7 +4965,10 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId, appendStringInfoString(buffer, "view"); break; case RELKIND_MATVIEW: - appendStringInfoString(buffer, "materialized view"); + if (relForm->relisdynamic) + appendStringInfoString(buffer, "dynamic table"); + else + appendStringInfoString(buffer, "materialized view"); break; case RELKIND_COMPOSITE_TYPE: appendStringInfoString(buffer, "composite type"); diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 19f89ad4dd7..6483985b402 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -44,6 +44,7 @@ #include "commands/prepare.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" +#include "commands/taskcmds.h" #include "commands/trigger.h" #include "commands/view.h" #include "miscadmin.h" @@ -74,6 +75,7 @@ #include "catalog/gp_matview_aux.h" #include "catalog/oid_dispatch.h" +#include "catalog/pg_task.h" #include "cdb/cdbappendonlyam.h" #include "cdb/cdbaocsam.h" #include "cdb/cdbdisp_query.h" @@ -119,6 +121,8 @@ static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_conte static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList); static bool check_aggregate_supports_ivm(Oid aggfnoid); +static void create_dynamic_table_auto_refresh_task(ParseState *pstate, Relation DynamicTableRel, char *schedule); + /* * create_ctas_internal * @@ -537,6 +541,14 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, CreateIvmTriggersOnBaseTables(query_immv, matviewOid); } } + + /* Set Dynamic Tables. */ + if (into->dynamicTbl) + { + SetDynamicTableState(matviewRel); + create_dynamic_table_auto_refresh_task(pstate, matviewRel, into->schedule); + } + table_close(matviewRel, NoLock); } @@ -1808,3 +1820,41 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList) return keys; } + +/* + * Create auto-refresh task for Dynamic Tables. + */ +static void +create_dynamic_table_auto_refresh_task(ParseState *pstate, Relation DynamicTableRel, char *schedule) +{ + ObjectAddress refaddr; + ObjectAddress address; + StringInfoData buf; + char *dtname = NULL; + + if (schedule == NULL) + schedule = DYNAMIC_TABLE_DEFAULT_REFRESH_INTERVAL; + + /* Create auto refresh task. */ + CreateTaskStmt *task_stmt = makeNode(CreateTaskStmt); + + initStringInfo(&buf); + appendStringInfo(&buf, "gp_dynamic_table_refresh_%u", RelationGetRelid(DynamicTableRel)); + task_stmt->taskname = pstrdup(buf.data); + task_stmt->schedule = pstrdup(schedule); + task_stmt->if_not_exists = false; /* report error if failed. */ + dtname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(DynamicTableRel)), + RelationGetRelationName(DynamicTableRel)); + resetStringInfo(&buf); + appendStringInfo(&buf, "REFRESH DYNAMIC TABLE %s", dtname); + task_stmt->sql = pstrdup(buf.data); + bool saved_allowSystemTableMods = allowSystemTableMods; + allowSystemTableMods = true; + address = DefineTask(pstate, task_stmt); + allowSystemTableMods = saved_allowSystemTableMods; + + refaddr.classId = RelationRelationId; + refaddr.objectId = RelationGetRelid(DynamicTableRel); + refaddr.objectSubId = 0; + recordDependencyOn(&address, &refaddr, DEPENDENCY_INTERNAL); +} diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 9e3ccd76bb2..865edf4ac24 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -550,7 +550,12 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, if (ctas->objtype == OBJECT_TABLE) ExplainDummyGroup("CREATE TABLE AS", NULL, es); else if (ctas->objtype == OBJECT_MATVIEW) - ExplainDummyGroup("CREATE MATERIALIZED VIEW", NULL, es); + { + if(ctas->into && ctas->into->dynamicTbl) + ExplainDummyGroup("CREATE DYNAMIC TABLE", NULL, es); + else + ExplainDummyGroup("CREATE MATERIALIZED VIEW", NULL, es); + } else elog(ERROR, "unexpected object type: %d", (int) ctas->objtype); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 8b14f1c9f51..67cde110284 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -289,6 +289,46 @@ MakeRefreshClause(bool concurrent, bool skipData, RangeVar *relation) return refreshClause; } +/* + * SetDynamicTableState + * Mark a materialized view as Dynamic Table, or not. + * + * NOTE: caller must be holding an appropriate lock on the relation. + */ +void +SetDynamicTableState(Relation relation) +{ + Relation pgrel; + HeapTuple tuple; + + Assert(relation->rd_rel->relkind == RELKIND_MATVIEW); + + /* + * Update relation's pg_class entry. Crucial side-effect: other backends + * (and this one too!) are sent SI message to make them rebuild relcache + * entries. + */ + pgrel = table_open(RelationRelationId, RowExclusiveLock); + tuple = SearchSysCacheCopy1(RELOID, + ObjectIdGetDatum(RelationGetRelid(relation))); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", + RelationGetRelid(relation)); + + ((Form_pg_class) GETSTRUCT(tuple))->relisdynamic = true; + + CatalogTupleUpdate(pgrel, &tuple->t_self, tuple); + + heap_freetuple(tuple); + table_close(pgrel, RowExclusiveLock); + + /* + * Advance command counter to make the updated pg_class row locally + * visible. + */ + CommandCounterIncrement(); +} + /* * SetMatViewIVMState * Mark a materialized view as IVM, or not. @@ -702,7 +742,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, * completion tag output might break applications using it. */ if (qc) - SetQueryCompletion(qc, CMDTAG_REFRESH_MATERIALIZED_VIEW, processed); + { + if (stmt->isdynamic) + SetQueryCompletion(qc, CMDTAG_REFRESH_DYNAMIC_TABLE, processed); + else + SetQueryCompletion(qc, CMDTAG_REFRESH_MATERIALIZED_VIEW, processed); + } return address; } diff --git a/src/backend/commands/taskcmds.c b/src/backend/commands/taskcmds.c index 6e5757ec047..026f05b9beb 100644 --- a/src/backend/commands/taskcmds.c +++ b/src/backend/commands/taskcmds.c @@ -109,6 +109,14 @@ DefineTask(ParseState *pstate, CreateTaskStmt *stmt) } } + if (!allowSystemTableMods && strncmp(stmt->taskname, DYNAMIC_TASK_PREFIX, 25) == 0) + { + ereport(ERROR, + (errcode(ERRCODE_RESERVED_NAME), + errmsg("unacceptable task name \"%s\"", stmt->taskname), + errdetail("The prefix \"%s\" is reserved for system tasks.", + DYNAMIC_TASK_PREFIX))); + } jobid = ScheduleCronJob(cstring_to_text(stmt->schedule), cstring_to_text(stmt->sql), cstring_to_text(dbname), cstring_to_text(username), true, cstring_to_text(stmt->taskname)); @@ -278,6 +286,13 @@ DropTask(ParseState *pstate, DropTaskStmt *stmt) /* delete from pg_task_run_history according to the jobid */ if (OidIsValid(jobid)) { + if (!allowSystemTableMods && strncmp(stmt->taskname, DYNAMIC_TASK_PREFIX, 25) == 0) + { + ereport(ERROR, + (errcode(ERRCODE_RESERVED_NAME), + errmsg("can not drop a internal task \"%s\" paried with dynamic table", stmt->taskname), + errdetail("please drop the dynamic table instead"))); + } RemoveTaskRunHistoryByJobId(jobid); ObjectAddressSet(address, TaskRelationId, jobid); /* Clean up dependencies */ diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index ed54da0526f..defdca28a3e 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1808,6 +1808,8 @@ _copyIntoClause(const IntoClause *from) COPY_SCALAR_FIELD(ivm); COPY_SCALAR_FIELD(matviewOid); COPY_STRING_FIELD(enrname); + COPY_SCALAR_FIELD(dynamicTbl); + COPY_STRING_FIELD(schedule); return newnode; } @@ -4218,6 +4220,7 @@ _copyDropStmt(const DropStmt *from) COPY_SCALAR_FIELD(behavior); COPY_SCALAR_FIELD(missing_ok); COPY_SCALAR_FIELD(concurrent); + COPY_SCALAR_FIELD(isdynamic); return newnode; } @@ -4786,6 +4789,7 @@ _copyRefreshMatViewStmt(const RefreshMatViewStmt *from) COPY_SCALAR_FIELD(concurrent); COPY_SCALAR_FIELD(skipData); COPY_NODE_FIELD(relation); + COPY_SCALAR_FIELD(isdynamic); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 1617dd38960..7c104efd263 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -176,6 +176,8 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b) COMPARE_SCALAR_FIELD(ivm); COMPARE_SCALAR_FIELD(matviewOid); COMPARE_STRING_FIELD(enrname); + COMPARE_SCALAR_FIELD(dynamicTbl); + COMPARE_STRING_FIELD(schedule); return true; } @@ -1480,6 +1482,7 @@ _equalDropStmt(const DropStmt *a, const DropStmt *b) COMPARE_SCALAR_FIELD(behavior); COMPARE_SCALAR_FIELD(missing_ok); COMPARE_SCALAR_FIELD(concurrent); + COMPARE_SCALAR_FIELD(isdynamic); return true; } @@ -1962,6 +1965,7 @@ _equalRefreshMatViewStmt(const RefreshMatViewStmt *a, const RefreshMatViewStmt * COMPARE_SCALAR_FIELD(concurrent); COMPARE_SCALAR_FIELD(skipData); COMPARE_NODE_FIELD(relation); + COMPARE_SCALAR_FIELD(isdynamic); return true; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index c5f7423b2eb..bc0e8b35bf5 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1210,6 +1210,8 @@ _outIntoClause(StringInfo str, const IntoClause *node) WRITE_BOOL_FIELD(ivm); WRITE_OID_FIELD(matviewOid); WRITE_STRING_FIELD(enrname); + WRITE_BOOL_FIELD(dynamicTbl); + WRITE_STRING_FIELD(schedule); } static void diff --git a/src/backend/nodes/outfuncs_common.c b/src/backend/nodes/outfuncs_common.c index 4cf0c1b8287..c1dd7744916 100644 --- a/src/backend/nodes/outfuncs_common.c +++ b/src/backend/nodes/outfuncs_common.c @@ -647,6 +647,7 @@ _outDropStmt(StringInfo str, const DropStmt *node) WRITE_ENUM_FIELD(behavior, DropBehavior); WRITE_BOOL_FIELD(missing_ok); WRITE_BOOL_FIELD(concurrent); + WRITE_BOOL_FIELD(isdynamic); } static void diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 6a3eaec5f46..da2da74925a 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -569,6 +569,8 @@ _readIntoClause(void) READ_BOOL_FIELD(ivm); READ_OID_FIELD(matviewOid); READ_STRING_FIELD(enrname); + READ_BOOL_FIELD(dynamicTbl); + READ_STRING_FIELD(schedule); READ_DONE(); } diff --git a/src/backend/nodes/readfuncs_common.c b/src/backend/nodes/readfuncs_common.c index 96b8c9f8690..886d49da513 100644 --- a/src/backend/nodes/readfuncs_common.c +++ b/src/backend/nodes/readfuncs_common.c @@ -1132,6 +1132,7 @@ _readDropStmt(void) READ_ENUM_FIELD(behavior,DropBehavior); READ_BOOL_FIELD(missing_ok); READ_BOOL_FIELD(concurrent); + READ_BOOL_FIELD(isdynamic); /* Force 'missing_ok' in QEs */ #ifdef COMPILING_BINARY_FUNCS diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 13de1876496..9f98286c556 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -764,7 +764,7 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_ DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC DETACH DICTIONARY DIRECTORY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P - DOUBLE_P DROP + DOUBLE_P DROP DYNAMIC EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENDPOINT ENUM_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION @@ -7139,6 +7139,74 @@ CreateMatViewStmt: ctas->into->distributedBy = $13; $$ = (Node *) ctas; } +/***************************************************************************** + * + * QUERY : + * CREATE DYNAMIC TABLE relname AS SelectStmt + * + *****************************************************************************/ + | CREATE OptNoLog DYNAMIC TABLE create_mv_target SCHEDULE task_schedule AS SelectStmt opt_with_data OptDistributedBy + { + CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); + ctas->query = $9; + ctas->into = $5; + ctas->objtype = OBJECT_MATVIEW; + ctas->is_select_into = false; + ctas->if_not_exists = false; + /* cram additional flags into the IntoClause */ + $5->rel->relpersistence = $2; + $5->skipData = !($10); + $5->dynamicTbl = true; + $5->schedule = $7; + ctas->into->distributedBy = $11; + $$ = (Node *) ctas; + } + | CREATE OptNoLog DYNAMIC TABLE create_mv_target AS SelectStmt opt_with_data OptDistributedBy + { + CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); + ctas->query = $7; + ctas->into = $5; + ctas->objtype = OBJECT_MATVIEW; + ctas->is_select_into = false; + ctas->if_not_exists = false; + /* cram additional flags into the IntoClause */ + $5->rel->relpersistence = $2; + $5->skipData = !($8); + $5->dynamicTbl = true; + ctas->into->distributedBy = $9; + $$ = (Node *) ctas; + } + | CREATE OptNoLog DYNAMIC TABLE create_mv_target IF_P NOT EXISTS SCHEDULE task_schedule AS SelectStmt opt_with_data OptDistributedBy + { + CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); + ctas->query = $12; + ctas->into = $5; + ctas->objtype = OBJECT_MATVIEW; + ctas->is_select_into = false; + ctas->if_not_exists = true; + /* cram additional flags into the IntoClause */ + $5->rel->relpersistence = $2; + $5->skipData = !($13); + $5->dynamicTbl = true; + $5->schedule = $10; + ctas->into->distributedBy = $14; + $$ = (Node *) ctas; + } + | CREATE OptNoLog DYNAMIC TABLE create_mv_target IF_P NOT EXISTS AS SelectStmt opt_with_data OptDistributedBy + { + CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); + ctas->query = $10; + ctas->into = $5; + ctas->objtype = OBJECT_MATVIEW; + ctas->is_select_into = false; + ctas->if_not_exists = true; + /* cram additional flags into the IntoClause */ + $5->rel->relpersistence = $2; + $5->skipData = !($11); + $5->dynamicTbl = true; + ctas->into->distributedBy = $12; + $$ = (Node *) ctas; + } ; create_mv_target: @@ -7154,6 +7222,8 @@ create_mv_target: $$->viewQuery = NULL; /* filled at analysis time */ $$->skipData = false; /* might get changed later */ $$->ivm = false; + $$->dynamicTbl = false; + $$->schedule = NULL; $$->accessMethod = greenplumLegacyAOoptions($$->accessMethod, &$$->options); } @@ -7167,11 +7237,11 @@ OptNoLog: UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; } | /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; } ; - /***************************************************************************** * * QUERY : * REFRESH MATERIALIZED VIEW qualified_name + * REFRESH DYNAMIC TABLE qualified_name * *****************************************************************************/ @@ -7182,6 +7252,16 @@ RefreshMatViewStmt: n->concurrent = $4; n->relation = $5; n->skipData = !($6); + n->isdynamic = false; + $$ = (Node *) n; + } + | REFRESH DYNAMIC TABLE opt_concurrently qualified_name opt_with_data + { + RefreshMatViewStmt *n = makeNode(RefreshMatViewStmt); + n->concurrent = $4; + n->relation = $5; + n->skipData = !($6); + n->isdynamic = true; $$ = (Node *) n; } ; @@ -9440,6 +9520,7 @@ DropOpClassStmt: n->behavior = $7; n->missing_ok = false; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } | DROP OPERATOR CLASS IF_P EXISTS any_name USING name opt_drop_behavior @@ -9450,6 +9531,7 @@ DropOpClassStmt: n->behavior = $9; n->missing_ok = true; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } ; @@ -9463,6 +9545,7 @@ DropOpFamilyStmt: n->behavior = $7; n->missing_ok = false; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } | DROP OPERATOR FAMILY IF_P EXISTS any_name USING name opt_drop_behavior @@ -9473,6 +9556,7 @@ DropOpFamilyStmt: n->behavior = $9; n->missing_ok = true; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } ; @@ -9523,6 +9607,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $5; n->behavior = $6; n->concurrent = false; + n->isdynamic = false; $$ = (Node *)n; } | DROP object_type_any_name any_name_list opt_drop_behavior @@ -9533,6 +9618,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $3; n->behavior = $4; n->concurrent = false; + n->isdynamic = false; $$ = (Node *)n; } | DROP drop_type_name IF_P EXISTS name_list opt_drop_behavior @@ -9543,6 +9629,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $5; n->behavior = $6; n->concurrent = false; + n->isdynamic = false; $$ = (Node *)n; } | DROP drop_type_name name_list opt_drop_behavior @@ -9553,6 +9640,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $3; n->behavior = $4; n->concurrent = false; + n->isdynamic = false; $$ = (Node *)n; } | DROP object_type_name_on_any_name name ON any_name opt_drop_behavior @@ -9563,6 +9651,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->behavior = $6; n->missing_ok = false; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } | DROP object_type_name_on_any_name IF_P EXISTS name ON any_name opt_drop_behavior @@ -9573,6 +9662,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->behavior = $8; n->missing_ok = true; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } | DROP TYPE_P type_name_list opt_drop_behavior @@ -9583,6 +9673,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $3; n->behavior = $4; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } | DROP TYPE_P IF_P EXISTS type_name_list opt_drop_behavior @@ -9593,6 +9684,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $5; n->behavior = $6; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } | DROP DOMAIN_P type_name_list opt_drop_behavior @@ -9603,6 +9695,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $3; n->behavior = $4; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } | DROP DOMAIN_P IF_P EXISTS type_name_list opt_drop_behavior @@ -9613,6 +9706,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $5; n->behavior = $6; n->concurrent = false; + n->isdynamic = false; $$ = (Node *) n; } | DROP INDEX CONCURRENTLY any_name_list opt_drop_behavior @@ -9623,6 +9717,7 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $4; n->behavior = $5; n->concurrent = true; + n->isdynamic = false; $$ = (Node *)n; } | DROP INDEX CONCURRENTLY IF_P EXISTS any_name_list opt_drop_behavior @@ -9633,6 +9728,30 @@ DropStmt: DROP object_type_any_name IF_P EXISTS any_name_list opt_drop_behavior n->objects = $6; n->behavior = $7; n->concurrent = true; + n->isdynamic = false; + $$ = (Node *)n; + } +/* DROP DYNAMIC TABLE */ + | DROP DYNAMIC TABLE IF_P EXISTS any_name_list opt_drop_behavior + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_MATVIEW; + n->missing_ok = true; + n->objects = $6; + n->behavior = $7; + n->concurrent = false; + n->isdynamic = true; + $$ = (Node *)n; + } + | DROP DYNAMIC TABLE any_name_list opt_drop_behavior + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_MATVIEW; + n->missing_ok = false; + n->objects = $4; + n->behavior = $5; + n->concurrent = false; + n->isdynamic = true; $$ = (Node *)n; } ; @@ -19384,6 +19503,7 @@ unreserved_keyword: | DOUBLE_P | DROP | DXL + | DYNAMIC | EACH | ENABLE_P | ENCODING @@ -20317,6 +20437,7 @@ bare_label_keyword: | DOUBLE_P | DROP | DXL + | DYNAMIC | EACH | ELSE | ENABLE_P diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 7c4c7f729d3..4592399b18f 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -3281,8 +3281,14 @@ CreateCommandTag(Node *parsetree) tag = CMDTAG_DROP_VIEW; break; case OBJECT_MATVIEW: - tag = CMDTAG_DROP_MATERIALIZED_VIEW; + { + if (((DropStmt *) parsetree)->isdynamic) + tag = CMDTAG_DROP_DYNAMIC_TABLE; + else + tag = CMDTAG_DROP_MATERIALIZED_VIEW; + break; + } case OBJECT_INDEX: tag = CMDTAG_DROP_INDEX; break; @@ -3642,15 +3648,26 @@ CreateCommandTag(Node *parsetree) tag = CMDTAG_CREATE_TABLE_AS; break; case OBJECT_MATVIEW: - tag = CMDTAG_CREATE_MATERIALIZED_VIEW; + { + if (((CreateTableAsStmt *) parsetree)->into->dynamicTbl) + tag = CMDTAG_CREATE_DYNAMIC_TABLE; + else + tag = CMDTAG_CREATE_MATERIALIZED_VIEW; + break; + } default: tag = CMDTAG_UNKNOWN; } break; case T_RefreshMatViewStmt: - tag = CMDTAG_REFRESH_MATERIALIZED_VIEW; + { + if (((RefreshMatViewStmt*) parsetree)->isdynamic) + tag = CMDTAG_REFRESH_DYNAMIC_TABLE; + else + tag = CMDTAG_REFRESH_MATERIALIZED_VIEW; + } break; case T_AlterSystemStmt: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index a90852798f9..4ec87638a7f 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -37,11 +37,13 @@ #include "catalog/pg_partitioned_table.h" #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" +#include "catalog/pg_task.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" +#include "common/fe_memutils.h" #include "common/keywords.h" #include "executor/spi.h" #include "funcapi.h" @@ -12351,6 +12353,71 @@ flatten_reloptions(Oid relid) return result; } +/* + * Get dynamic table's corresponding task SCHEDULE. + */ +Datum +pg_get_dynamic_table_schedule(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation pg_task; + StringInfoData buf; + char *username; + SysScanDesc scanDescriptor = NULL; + ScanKeyData scanKey[2]; + HeapTuple heapTuple = NULL; + Form_pg_task task = NULL; + + if (!get_rel_relisdynamic(relid)) + { + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("relation of oid \"%u\" is not dynamic table", relid))); + PG_RETURN_TEXT_P(cstring_to_text("")); + } + + pg_task = table_open(TaskRelationId, AccessShareLock); + if (!pg_task) + { + table_close(pg_task, AccessShareLock); + PG_RETURN_TEXT_P(cstring_to_text("")); + } + + initStringInfo(&buf); + appendStringInfo(&buf, "%s%u", DYNAMIC_TASK_PREFIX, relid); + + /* FIXME: is it possible that supersuers try to dump task? */ + username = GetUserNameFromId(GetUserId(), false); + + ScanKeyInit(&scanKey[0], Anum_pg_task_jobname, + BTEqualStrategyNumber, F_TEXTEQ, CStringGetTextDatum(buf.data)); + ScanKeyInit(&scanKey[1], Anum_pg_task_username, + BTEqualStrategyNumber, F_TEXTEQ, CStringGetTextDatum(username)); + + scanDescriptor = systable_beginscan(pg_task, TaskJobNameUserNameIndexId, false, + NULL, 2, scanKey); + + heapTuple = systable_getnext(scanDescriptor); + if (!HeapTupleIsValid(heapTuple)) + { + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("task \"%s\" does not exist", buf.data))); + table_close(pg_task, AccessShareLock); + PG_RETURN_TEXT_P(cstring_to_text("")); + } + + task = (Form_pg_task) GETSTRUCT(heapTuple); + + resetStringInfo(&buf); + appendStringInfo(&buf, "%s", text_to_cstring(&task->schedule)); + + systable_endscan(scanDescriptor); + table_close(pg_task, AccessShareLock); + + PG_RETURN_TEXT_P(string_to_text(buf.data)); +} + /* ---------- * get_table_distributedby - Get the DISTRIBUTED BY definition of a table. * ---------- diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 9799f5b2202..1a64d3870b8 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -2069,6 +2069,30 @@ get_rel_relisivm(Oid relid) return false; } +/* + * get_rel_relisdynamic + * + * Returns the relisdynamic flag associated with a given relation. + */ +bool +get_rel_relisdynamic(Oid relid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); + bool result; + + result = reltup->relisdynamic; + ReleaseSysCache(tp); + return result; + } + else + return false; +} + /* * get_rel_tablespace * diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 527aa9e18cf..30532663b40 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1986,6 +1986,8 @@ formrdesc(const char *relationName, Oid relationReltype, relation->rd_rel->relispopulated = true; /* ... and they're always no ivm, too */ relation->rd_rel->relisivm = false; + /* ... and they're always not dynamic, too */ + relation->rd_rel->relisdynamic = false; relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING; relation->rd_rel->relpages = 0; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index a2f8b4b91cf..bfdf20beab1 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -389,6 +389,7 @@ static bool testAttributeEncodingSupport(Archive *fout); static char *nextToken(register char **stringp, register const char *delim); static void addDistributedBy(Archive *fout, PQExpBuffer q, const TableInfo *tbinfo, int actual_atts); static void addDistributedByOld(Archive *fout, PQExpBuffer q, const TableInfo *tbinfo, int actual_atts); +static void addSchedule(Archive *fout, PQExpBuffer q, const TableInfo *tbinfo); static bool isGPDB4300OrLater(Archive *fout); static bool isGPDB(Archive *fout); static bool isGPDB5000OrLater(Archive *fout); @@ -2930,8 +2931,16 @@ refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo) q = createPQExpBuffer(); - appendPQExpBuffer(q, "REFRESH MATERIALIZED VIEW %s;\n", - fmtQualifiedDumpable(tbinfo)); + if (tbinfo->isdynamic) + { + appendPQExpBuffer(q, "REFRESH DYNAMIC TABLE %s;\n", + fmtQualifiedDumpable(tbinfo)); + } + else + { + appendPQExpBuffer(q, "REFRESH MATERIALIZED VIEW %s;\n", + fmtQualifiedDumpable(tbinfo)); + } if (tdinfo->dobj.dump & DUMP_COMPONENT_DATA) ArchiveEntry(fout, @@ -2940,7 +2949,7 @@ refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo) ARCHIVE_OPTS(.tag = tbinfo->dobj.name, .namespace = tbinfo->dobj.namespace->dobj.name, .owner = tbinfo->rolname, - .description = "MATERIALIZED VIEW DATA", + .description = tbinfo->isdynamic ? "DYNAMIC TABLE DATA": "MATERIALIZED VIEW DATA", .section = SECTION_POST_DATA, .createStmt = q->data, .deps = tdinfo->dobj.dependencies, @@ -7415,6 +7424,7 @@ getTables(Archive *fout, int *numTables) int i_amname; int i_amoid; int i_isivm; + int i_isdynamic; /* * Find all the tables and table-like objects. @@ -7535,7 +7545,8 @@ getTables(Archive *fout, int *numTables) "%s AS partkeydef, " "%s AS ispartition, " "%s AS partbound, " - "c.relisivm AS isivm " + "c.relisivm AS isivm, " + "c.relisdynamic AS isdynamic " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -8106,6 +8117,7 @@ getTables(Archive *fout, int *numTables) i_amname = PQfnumber(res, "amname"); i_amoid = PQfnumber(res, "amoid"); i_isivm = PQfnumber(res, "isivm"); + i_isdynamic = PQfnumber(res, "isdynamic"); if (dopt->lockWaitTimeout) { @@ -8238,6 +8250,7 @@ getTables(Archive *fout, int *numTables) tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0); tblinfo[i].partbound = pg_strdup(PQgetvalue(res, i, i_partbound)); tblinfo[i].isivm = (strcmp(PQgetvalue(res, i, i_isivm), "t") == 0); + tblinfo[i].isdynamic = (strcmp(PQgetvalue(res, i, i_isdynamic), "t") == 0); /* foreign server */ tblinfo[i].foreign_server = atooid(PQgetvalue(res, i, i_foreignserver)); @@ -18081,7 +18094,10 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) break; } case RELKIND_MATVIEW: - reltypename = "MATERIALIZED VIEW"; + if (tbinfo->isdynamic) + reltypename = "DYNAMIC TABLE"; + else + reltypename = "MATERIALIZED VIEW"; break; default: reltypename = "TABLE"; @@ -18154,14 +18170,23 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) } } - appendPQExpBuffer(q, "CREATE %s%s%s %s", - tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ? - "UNLOGGED " : "", - tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ? - "INCREMENTAL " : "", - reltypename, - qualrelname); + if (tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isdynamic) + { + /* We'r sure there is no UNLOGGED and this is a DYNAMIC TABLE. */ + appendPQExpBuffer(q, "CREATE DYNAMIC TABLE %s", qualrelname); + addSchedule(fout, q, tbinfo); + } + else + { + appendPQExpBuffer(q, "CREATE %s%s%s %s", + tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ? + "UNLOGGED " : "", + tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ? + "INCREMENTAL " : "", + reltypename, + qualrelname); + } /* * Attach to type, if reloftype; except in case of a binary upgrade, * we dump the table normally and attach it to the type afterward. @@ -21436,6 +21461,32 @@ testExtProtocolSupport(Archive *fout) return isSupported; } +/* + * addSchedule + * + * find the SCHEDULE of the job in pg_task with dynamic table + * and append the SCHEDULE clause to the passed in dump buffer (q). + */ +static void +addSchedule(Archive *fout, PQExpBuffer q, const TableInfo *tbinfo) +{ + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + char *dby; + + appendPQExpBuffer(query, + "SELECT pg_catalog.pg_get_dynamic_table_schedule(%u)", + tbinfo->dobj.catId.oid); + + res = ExecuteSqlQueryForSingleRow(fout, query->data); + + dby = PQgetvalue(res, 0, 0); + if (strcmp(dby, "") != 0) + appendPQExpBuffer(q, " SCHEDULE \'%s\'", PQgetvalue(res, 0, 0)); + + PQclear(res); + destroyPQExpBuffer(query); +} /* * addDistributedBy diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index a907a9af16e..8a79ea2d601 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -398,6 +398,7 @@ typedef struct _tableInfo int numTriggers; /* number of triggers for table */ struct _triggerInfo *triggers; /* array of TriggerInfo structs */ bool isivm; /* is incrementally maintainable materialized view? */ + bool isdynamic; /* is dynamic table? */ } TableInfo; typedef struct _tableAttachInfo diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index a06a994ed3e..8dc9086cb9a 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2186,6 +2186,21 @@ unlike => { exclude_dump_test_schema => 1, }, }, + 'CREATE DYNAMIC TABLE dynamic_table' => { + create_order => 28, + create_sql => 'CREATE DYNAMIC TABLE dump_test.dynamic_table (col1) AS + SELECT col1 FROM dump_test.test_table;', + regexp => qr/^ + \QCREATE DYNAMIC TABLE dump_test.dynamic_table AS\E + \n\s+\QSELECT test_table.col1\E + \n\s+\QFROM dump_test.test_table\E + \n\s+\QWITH NO DATA;\E + /xm, + like => + { %full_runs, %dump_test_schema_runs, section_pre_data => 1, }, + unlike => { exclude_dump_test_schema => 1, }, + }, + 'CREATE POLICY p1 ON test_table' => { create_order => 22, create_sql => 'CREATE POLICY p1 ON dump_test.test_table diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index db0c3c4f74e..ca48d71f6d4 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1332,7 +1332,7 @@ permissionsList(const char *pattern) " WHEN " CppAsString2(RELKIND_RELATION) " THEN '%s'" " WHEN " CppAsString2(RELKIND_DIRECTORY_TABLE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_VIEW) " THEN '%s'" - " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'" + " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN CASE c.relisdynamic WHEN true THEN '%s' ELSE '%s' END" " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'" @@ -1343,6 +1343,7 @@ permissionsList(const char *pattern) gettext_noop("table"), gettext_noop("directory table"), gettext_noop("view"), + gettext_noop("dynamic table"), gettext_noop("materialized view"), gettext_noop("sequence"), gettext_noop("foreign table"), @@ -1927,6 +1928,7 @@ describeOneTableDetails(const char *schemaname, char relreplident; char *relam; bool isivm; + bool isdynamic; char *compressionType; char *compressionLevel; @@ -1959,7 +1961,8 @@ describeOneTableDetails(const char *schemaname, "false AS relhasoids, c.relispartition, %s, c.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "c.relpersistence, c.relreplident, am.amname, " - "c.relisivm\n" + "c.relisivm, " + "c.relisdynamic \n" "FROM pg_catalog.pg_class c\n " "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n" @@ -2155,6 +2158,7 @@ describeOneTableDetails(const char *schemaname, else tableinfo.relam = NULL; tableinfo.isivm = strcmp(PQgetvalue(res, 0, 15), "t") == 0; + tableinfo.isdynamic = strcmp(PQgetvalue(res, 0, 16), "t") == 0; /* GPDB Only: relstorage */ if (pset.sversion < 120000 && isGPDB()) @@ -2484,8 +2488,18 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&title, _("Unlogged materialized view \"%s.%s\""), schemaname, relationname); else - printfPQExpBuffer(&title, _("Materialized view \"%s.%s\""), - schemaname, relationname); + { + /* + * Postgres has forbidden UNLOGGED materialized view, + * only consider below cases. + */ + if (!tableinfo.isdynamic) + printfPQExpBuffer(&title, _("Materialized view \"%s.%s\""), + schemaname, relationname); + else + printfPQExpBuffer(&title, _("Dynamic table \"%s.%s\""), + schemaname, relationname); + } break; case RELKIND_INDEX: if (tableinfo.relpersistence == 'u') @@ -5005,7 +5019,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys " WHEN " CppAsString2(RELKIND_RELATION) " THEN '%s'" " WHEN " CppAsString2(RELKIND_DIRECTORY_TABLE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_VIEW) " THEN '%s'" - " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'" + " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN CASE c.relisdynamic WHEN true THEN '%s' ELSE '%s' END" " WHEN " CppAsString2(RELKIND_INDEX) " THEN '%s'" " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_TOASTVALUE) " THEN '%s'" @@ -5019,6 +5033,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys gettext_noop("table"), gettext_noop("directory table"), gettext_noop("view"), + gettext_noop("dynamic table"), gettext_noop("materialized view"), gettext_noop("index"), gettext_noop("sequence"), diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 7af41799f41..5d0081c8110 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1121,6 +1121,7 @@ static const pgsql_thing_t words_after_create[] = { {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, NULL, THING_NO_SHOW}, {"DIRECTORY TABLE", NULL, NULL, &Query_for_list_of_directory_tables}, {"DOMAIN", NULL, NULL, &Query_for_list_of_domains}, + {"DYNAMIC TABLE", NULL, NULL, &Query_for_list_of_matviews, THING_NO_ALTER}, {"EVENT TRIGGER", NULL, NULL, NULL}, {"EXTENSION", Query_for_list_of_extensions}, {"FOREIGN DATA WRAPPER", NULL, NULL, NULL}, @@ -1573,7 +1574,7 @@ psql_completion(const char *text, int start, int end) "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE", - "REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE", + "REASSIGN", "REFRESH MATERIALIZED VIEW", "REFRESH DYNAMIC TABLE", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", "SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START", "TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH", @@ -2480,7 +2481,7 @@ psql_completion(const char *text, int start, int end) "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "SERVER", "INDEX", "LANGUAGE", "POLICY", "PUBLICATION", "RULE", "SCHEMA", "SEQUENCE", "STATISTICS", "SUBSCRIPTION", - "TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", + "TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "DYNAMIC TABLE", "COLUMN", "AGGREGATE", "FUNCTION", "STORAGE SERVER", "PROCEDURE", "PROFILE", "ROUTINE", "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", @@ -2849,7 +2850,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW"); /* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */ else if (TailMatches("CREATE", "UNLOGGED")) - COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW"); + COMPLETE_WITH("TABLE", "MATERIALIZED VIEW", "INCREMENTAL MATERIALIZED VIEW", "DYNAMIC TABLE"); /* Complete PARTITION BY with RANGE ( or LIST ( or ... */ else if (TailMatches("PARTITION", "BY")) COMPLETE_WITH("RANGE (", "LIST (", "HASH ("); @@ -3199,6 +3200,21 @@ psql_completion(const char *text, int start, int end) Matches("CREATE", "INCREMENTAL", "MATERIALIZED", "VIEW", MatchAny, "AS")) COMPLETE_WITH("SELECT"); +/* CREATE DYNAMIC TABLE */ + else if (Matches("CREATE", "DYNAMIC")) + COMPLETE_WITH("TABLE"); + /* Complete CREATE DYNAMIC TABLE with AS */ + /* Complete CREATE DYNAMIC TABLE SCHEDULE with AS */ + else if (Matches("CREATE", "DYNAMIC", "TABLE", MatchAny)) + COMPLETE_WITH("SCHEDULE", "AS"); + else if (Matches("CREATE", "DYNAMIC", "TABLE", MatchAny, "SCHEDULE", MatchAny)) + COMPLETE_WITH("AS"); + /* Complete "CREATE DYNAMIC TABLE AS with "SELECT" */ + /* Complete "CREATE DYNAMIC TABLE SCHEDULE AS with "SELECT" */ + else if (Matches("CREATE", "DYNAMIC", "TABLE", MatchAny, "AS") || + Matches("CREATE", "DYNAMIC", "TABLE", MatchAny, "SCHEDULE", MatchAny,"AS")) + COMPLETE_WITH("SELECT"); + /* CREATE EVENT TRIGGER */ else if (Matches("CREATE", "EVENT")) COMPLETE_WITH("TRIGGER"); @@ -3342,6 +3358,12 @@ psql_completion(const char *text, int start, int end) else if (Matches("DROP", "MATERIALIZED", "VIEW")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); + /* DROP DYNAMIC TABLE */ + else if (Matches("DROP", "DYNAMIC")) + COMPLETE_WITH("TABLE"); + else if (Matches("DROP", "DYNAMIC", "TABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); + /* DROP STORAGE */ else if (Matches("DROP", "STORAGE")) COMPLETE_WITH("SERVER", "USER MAPPING"); @@ -3825,7 +3847,7 @@ psql_completion(const char *text, int start, int end) /* REFRESH MATERIALIZED VIEW */ else if (Matches("REFRESH")) - COMPLETE_WITH("MATERIALIZED VIEW"); + COMPLETE_WITH("MATERIALIZED VIEW", "DYNAMIC TABLE"); else if (Matches("REFRESH", "MATERIALIZED")) COMPLETE_WITH("VIEW"); else if (Matches("REFRESH", "MATERIALIZED", "VIEW")) @@ -3846,6 +3868,27 @@ psql_completion(const char *text, int start, int end) else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO")) COMPLETE_WITH("DATA"); +/* REFRESH DYNAMIC TABLE */ + else if (Matches("REFRESH", "DYNAMIC")) + COMPLETE_WITH("TABLE"); + else if (Matches("REFRESH", "DYNAMIC", "TABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, + " UNION SELECT 'CONCURRENTLY'"); + else if (Matches("REFRESH", "DYNAMIC", "TABLE", "CONCURRENTLY")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); + else if (Matches("REFRESH", "DYNAMIC", "TABLE", MatchAny)) + COMPLETE_WITH("WITH"); + else if (Matches("REFRESH", "DYNAMIC", "TABLE", "CONCURRENTLY", MatchAny)) + COMPLETE_WITH("WITH"); + else if (Matches("REFRESH", "DYNAMIC", "TABLE", MatchAny, "WITH")) + COMPLETE_WITH("NO DATA", "DATA"); + else if (Matches("REFRESH", "DYNAMIC", "TABLE", "CONCURRENTLY", MatchAny, "WITH")) + COMPLETE_WITH("NO DATA", "DATA"); + else if (Matches("REFRESH", "DYNAMIC", "TABLE", MatchAny, "WITH", "NO")) + COMPLETE_WITH("DATA"); + else if (Matches("REFRESH", "DYNAMIC", "TABLE", "CONCURRENTLY", MatchAny, "WITH", "NO")) + COMPLETE_WITH("DATA"); + /* REINDEX */ else if (Matches("REINDEX") || Matches("REINDEX", "(*)")) diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 8ac4a14a7cf..1370f78a11b 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -56,6 +56,6 @@ */ /* 3yyymmddN */ -#define CATALOG_VERSION_NO 302412051 +#define CATALOG_VERSION_NO 302412061 #endif diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index ec525930d57..64a45b1793f 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -122,6 +122,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat /* is relation a matview with ivm? */ bool relisivm BKI_DEFAULT(f); + /* is relation a dynamic table? */ + bool relisdynamic BKI_DEFAULT(f); + /* link to original rel during table rewrite; otherwise 0 */ Oid relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index c21af38c991..9941c64988b 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12601,3 +12601,7 @@ proargtypes => '_oid text', proallargtypes => '{_oid,text,oid,int8}', proargmodes => '{i,i,o,o}', proargnames => '{reloids,forkname,reloid,size}', prosrc => 'cbdb_relation_size' }, + +{ oid => 8693, descr => 'deparse SCHEDULE clause for a given dynamic table', + proname => 'pg_get_dynamic_table_schedule', provolatile => 's', prorettype => 'text', + proargtypes => 'oid', prosrc => 'pg_get_dynamic_table_schedule' }, diff --git a/src/include/catalog/pg_task.h b/src/include/catalog/pg_task.h index 1b870b925e4..30c9c6613ca 100644 --- a/src/include/catalog/pg_task.h +++ b/src/include/catalog/pg_task.h @@ -55,4 +55,10 @@ extern Oid GetTaskJobId(const char *jobname, const char *username); extern char * GetTaskNameById(Oid jobid); +/* Reversed prefix for Dynamic Tables. */ +#define DYNAMIC_TASK_PREFIX "gp_dynamic_table_refresh_" + +/* Default Dynamic Table Schedule */ +#define DYNAMIC_TABLE_DEFAULT_REFRESH_INTERVAL "*/5 * * * *" + #endif /* PG_TASK_H */ diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h index 2a0cef359e1..8cfb1f55af1 100644 --- a/src/include/commands/matview.h +++ b/src/include/commands/matview.h @@ -26,6 +26,8 @@ extern void SetMatViewPopulatedState(Relation relation, bool newstate); extern void SetMatViewIVMState(Relation relation, bool newstate); +extern void SetDynamicTableState(Relation relation); + extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ParamListInfo params, QueryCompletion *qc); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 0e416ca0dae..1c2ecca1289 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3438,6 +3438,7 @@ typedef struct DropStmt DropBehavior behavior; /* RESTRICT or CASCADE behavior */ bool missing_ok; /* skip error if object is missing? */ bool concurrent; /* drop index concurrently? */ + bool isdynamic; /* drop a dynamic table? */ } DropStmt; /* ---------------------- @@ -4121,6 +4122,7 @@ typedef struct RefreshMatViewStmt bool concurrent; /* allow concurrent access? */ bool skipData; /* true for WITH NO DATA */ RangeVar *relation; /* relation to insert into */ + bool isdynamic; /* relation is dynamic table? */ } RefreshMatViewStmt; /* ---------------------- diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 68b47e76695..91def3cd710 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -125,6 +125,9 @@ typedef struct IntoClause bool ivm; /* true for WITH IVM */ Oid matviewOid; /* matview oid */ char *enrname; /* ENR name for materialized view delta */ + bool dynamicTbl; /* true for Dynamic Tables. */ + /* pg_task cron schedule, used for Dynamic Tables. */ + char *schedule; } IntoClause; typedef struct CopyIntoClause diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index a083093ebc9..aef5b144831 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -160,6 +160,7 @@ PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("dxl", DXL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("dynamic", DYNAMIC, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h index b0346e2b9b1..21f27de42ac 100644 --- a/src/include/tcop/cmdtaglist.h +++ b/src/include/tcop/cmdtaglist.h @@ -103,6 +103,7 @@ PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false) PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false) PG_CMDTAG(CMDTAG_CREATE_DIRECTORY_TABLE, "CREATE DIRECTORY TABLE", true, false, false) PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false) +PG_CMDTAG(CMDTAG_CREATE_DYNAMIC_TABLE, "CREATE DYNAMIC TABLE", true, false, false) PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false) PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false) PG_CMDTAG(CMDTAG_CREATE_EXTERNAL, "CREATE EXTERNAL TABLE", true, false, false) @@ -173,6 +174,7 @@ PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false) PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false) PG_CMDTAG(CMDTAG_DROP_DIRECTORY_TABLE, "DROP DIRECTORY TABLE", true, false, false) PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false) +PG_CMDTAG(CMDTAG_DROP_DYNAMIC_TABLE, "DROP DYNAMIC TABLE", true, false, false) PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false) PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false) PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false) @@ -243,6 +245,7 @@ PG_CMDTAG(CMDTAG_FTS_MSG_PROMOTE, "PROMOTE", false, false, false) PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false) PG_CMDTAG(CMDTAG_DTX_RECOVERY_ABORT_PREPARED, "Recovery Abort Prepared", false, false, false) PG_CMDTAG(CMDTAG_DTX_RECOVERY_COMMIT_PREPARED, "Recovery Commit Prepared", false, false, false) +PG_CMDTAG(CMDTAG_REFRESH_DYNAMIC_TABLE, "REFRESH DYNAMIC TABLE", true, false, false) PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false) PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false) PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false) diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index aa2d4014de6..b176b746b41 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -172,6 +172,7 @@ extern Oid get_rel_type_id(Oid relid); extern char get_rel_relkind(Oid relid); extern bool get_rel_relispartition(Oid relid); extern bool get_rel_relisivm(Oid relid); +extern bool get_rel_relisdynamic(Oid relid); extern Oid get_rel_tablespace(Oid relid); extern char get_rel_persistence(Oid relid); extern Oid get_transform_fromsql(Oid typid, Oid langid, List *trftypes); diff --git a/src/test/regress/expected/dynamic_table.out b/src/test/regress/expected/dynamic_table.out new file mode 100644 index 00000000000..d95af994ff0 --- /dev/null +++ b/src/test/regress/expected/dynamic_table.out @@ -0,0 +1,326 @@ +-- start_matchsubs +-- m/ERROR: can not drop a internal task "gp_dynamic_table_refresh_.*/ +-- s/ERROR: can not drop a internal task "gp_dynamic_table_refresh_.*/ERROR: can not drop a internal task "gp_dynamic_table_refresh_xxx"/g +-- m/WARNING: relation of oid "\d+" is not dynamic table/ +-- s/WARNING: relation of oid "\d+" is not dynamic table/WARNING: relation of oid "XXX" is not dynamic table/g +-- end_matchsubs +CREATE SCHEMA dynamic_table_schema; +SET search_path TO dynamic_table_schema; +SET optimizer = OFF; +CREATE TABLE t1(a int, b int, c int) DISTRIBUTED BY (b); +INSERT INTO t1 SELECT i, i + 1, i + 2 FROM GENERATE_SERIES(1, 10) i; +INSERT INTO t1 SELECT i, i + 1, i + 2 FROM GENERATE_SERIES(1, 5) i; +ANALYZE t1; +CREATE DYNAMIC TABLE dt0 SCHEDULE '5 * * * *' AS + SELECT a, b, sum(c) FROM t1 GROUP BY a, b DISTRIBUTED BY(b); +\d+ dt0 + Dynamic table "dynamic_table_schema.dt0" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | + sum | bigint | | | | plain | | +View definition: + SELECT t1.a, + t1.b, + sum(t1.c) AS sum + FROM t1 + GROUP BY t1.a, t1.b; +Distributed by: (b) + +ANALYZE dt0; +-- test backgroud auto-refresh +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%'; + schedule | command +-----------+------------------------------------------------ + 5 * * * * | REFRESH DYNAMIC TABLE dynamic_table_schema.dt0 +(1 row) + +EXPLAIN(COSTS OFF, VERBOSE) +SELECT a, b, sum(c) FROM t1 GROUP BY a, b; + QUERY PLAN +------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Output: a, b, (sum(c)) + -> HashAggregate + Output: a, b, sum(c) + Group Key: t1.a, t1.b + -> Seq Scan on dynamic_table_schema.t1 + Output: a, b, c + Settings: optimizer = 'off' + Optimizer: Postgres query optimizer +(9 rows) + +SELECT a, b, sum(c) FROM t1 GROUP BY a, b; + a | b | sum +----+----+----- + 9 | 10 | 11 + 8 | 9 | 10 + 5 | 6 | 14 + 10 | 11 | 12 + 4 | 5 | 12 + 1 | 2 | 6 + 2 | 3 | 8 + 7 | 8 | 9 + 3 | 4 | 10 + 6 | 7 | 8 +(10 rows) + +SELECT * FROM dt0; + a | b | sum +----+----+----- + 1 | 2 | 6 + 2 | 3 | 8 + 7 | 8 | 9 + 3 | 4 | 10 + 6 | 7 | 8 + 9 | 10 | 11 + 8 | 9 | 10 + 5 | 6 | 14 + 10 | 11 | 12 + 4 | 5 | 12 +(10 rows) + +-- test join on distributed keys +EXPLAIN(COSTS OFF, VERBOSE) +SELECT * FROM dt0 JOIN t1 USING(b); + QUERY PLAN +-------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Output: dt0.b, dt0.a, dt0.sum, t1.a, t1.c + -> Hash Join + Output: dt0.b, dt0.a, dt0.sum, t1.a, t1.c + Hash Cond: (t1.b = dt0.b) + -> Seq Scan on dynamic_table_schema.t1 + Output: t1.a, t1.b, t1.c + -> Hash + Output: dt0.b, dt0.a, dt0.sum + -> Seq Scan on dynamic_table_schema.dt0 + Output: dt0.b, dt0.a, dt0.sum + Settings: optimizer = 'off' + Optimizer: Postgres query optimizer +(13 rows) + +-- Create Dynamic Table without SCHEDULE. +CREATE DYNAMIC TABLE dt1 AS + SELECT * FROM t1 WHERE a = 1 DISTRIBUTED BY(b); +ANALYZE dt1; +-- Create Dynamic Table without DISTRIBUTION KEYS. +CREATE DYNAMIC TABLE dt2 AS + SELECT * FROM t1 WHERE a = 2 WITH NO DATA; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'b' as the Cloudberry Database data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +-- Refresh Dynamic Table WITH NO DATA +REFRESH DYNAMIC TABLE dt0 WITH NO DATA; +-- Refresh Dynamic Table +REFRESH DYNAMIC TABLE dt2; +ANALYZE dt2; +-- Test Answer Query using Dynamic Tables. +SET enable_answer_query_using_materialized_views = ON; +EXPLAIN(COSTS OFF, VERBOSE) +SELECT * FROM t1 WHERE a = 1; + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Output: a, b, c + -> Seq Scan on dynamic_table_schema.dt1 + Output: a, b, c + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' + Optimizer: Postgres query optimizer +(6 rows) + +SELECT * FROM t1 WHERE a = 1; + a | b | c +---+---+--- + 1 | 2 | 3 + 1 | 2 | 3 +(2 rows) + +EXPLAIN(COSTS OFF, VERBOSE) +SELECT * FROM t1 WHERE a = 2; + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Output: a, b, c + -> Seq Scan on dynamic_table_schema.dt2 + Output: a, b, c + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' + Optimizer: Postgres query optimizer +(6 rows) + +SELECT * FROM t1 WHERE a = 2; + a | b | c +---+---+--- + 2 | 3 | 4 + 2 | 3 | 4 +(2 rows) + +-- test DROP DYNAMIC TABLE +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%' AND command LIKE '%dt0'; + schedule | command +-----------+------------------------------------------------ + 5 * * * * | REFRESH DYNAMIC TABLE dynamic_table_schema.dt0 +(1 row) + +DROP DYNAMIC TABLE dt0; +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%' AND command LIKE '%dt0'; + schedule | command +----------+--------- +(0 rows) + +-- drop base tables will drop DYNAMIC TABLEs too. +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%'; + schedule | command +-------------+------------------------------------------------ + */5 * * * * | REFRESH DYNAMIC TABLE dynamic_table_schema.dt1 + */5 * * * * | REFRESH DYNAMIC TABLE dynamic_table_schema.dt2 +(2 rows) + +DROP TABLE t1 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to dynamic table dt1 +drop cascades to dynamic table dt2 +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%'; + schedule | command +----------+--------- +(0 rows) + +-- construct dynamic table +CREATE TABLE t2(a int, b int, c int) DISTRIBUTED BY (b); +CREATE MATERIALIZED VIEW mv_t2 AS + SELECT * FROM t2 WHERE a > 1; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'b' as the Cloudberry Database data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +-- construct dynamic table from materialized view +CREATE DYNAMIC TABLE dt3 AS + SELECT * FROM mv_t2 WHERE a = 2; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'b' as the Cloudberry Database data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +-- construct dynamic table from dynamic table +CREATE DYNAMIC TABLE dt4 AS + SELECT * FROM dt3 WHERE b = 3; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'b' as the Cloudberry Database data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +-- construct dynamic table from joins +CREATE DYNAMIC TABLE dt5 AS + SELECT * FROM dt3 natural join t2 natural join mv_t2; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'b' as the Cloudberry Database data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +-- construct dynamic table from external table +begin; +--start_ignore +CREATE OR REPLACE FUNCTION write_to_file() RETURNS integer as '$libdir/gpextprotocol.so', 'demoprot_export' LANGUAGE C STABLE NO SQL; +CREATE OR REPLACE FUNCTION read_from_file() RETURNS integer as '$libdir/gpextprotocol.so', 'demoprot_import' LANGUAGE C STABLE NO SQL; +DROP PROTOCOL IF EXISTS demoprot; +NOTICE: protocol "demoprot" does not exist, skipping +--end_ignore +CREATE TRUSTED PROTOCOL demoprot (readfunc = 'read_from_file', writefunc = 'write_to_file'); -- should succeed +CREATE WRITABLE EXTERNAL TABLE ext_w(id int) + LOCATION('demoprot://dynamic_table_text_file.txt') +FORMAT 'text' +DISTRIBUTED BY (id); +INSERT INTO ext_w SELECT * FROM generate_series(1, 10); +CREATE READABLE EXTERNAL TABLE ext_r(id int) + LOCATION('demoprot://dynamic_table_text_file.txt') +FORMAT 'text'; +EXPLAIN(COSTS OFF, VERBOSE) +SELECT sum(id) FROM ext_r where id > 5; + QUERY PLAN +---------------------------------------------------------------------------------- + Finalize Aggregate + Output: sum(id) + -> Gather Motion 3:1 (slice1; segments: 3) + Output: (PARTIAL sum(id)) + -> Partial Aggregate + Output: PARTIAL sum(id) + -> Foreign Scan on dynamic_table_schema.ext_r + Output: id + Filter: (ext_r.id > 5) + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' + Optimizer: Postgres query optimizer +(11 rows) + +SELECT sum(id) FROM ext_r where id > 5; + sum +----- + 40 +(1 row) + +CREATE DYNAMIC TABLE dt_external AS + SELECT * FROM ext_r where id > 5; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'id' as the Cloudberry Database data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +ANALYZE dt_external; +SHOW optimizer; + optimizer +----------- + off +(1 row) + +SET LOCAL enable_answer_query_using_materialized_views = ON; +SET LOCAL aqumv_allow_foreign_table = ON; +EXPLAIN(COSTS OFF, VERBOSE) +SELECT sum(id) FROM ext_r where id > 5; + QUERY PLAN +---------------------------------------------------------------------------------- + Finalize Aggregate + Output: sum(id) + -> Gather Motion 3:1 (slice1; segments: 3) + Output: (PARTIAL sum(id)) + -> Partial Aggregate + Output: PARTIAL sum(id) + -> Seq Scan on dynamic_table_schema.dt_external + Output: id + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' + Optimizer: Postgres query optimizer +(10 rows) + +SELECT sum(id) FROM ext_r where id > 5; + sum +----- + 40 +(1 row) + +DROP FOREIGN TABLE ext_r CASCADE; +NOTICE: drop cascades to dynamic table dt_external +DROP FOREIGN TABLE ext_w; +ABORT; +-- Test resevered job name for Dynamic Tables. +SELECT 'dt5'::regclass::oid AS dtoid \gset +-- should fail +CREATE TASK gp_dynamic_table_refresh_xxx SCHEDULE '1 second' AS 'REFRESH DYNAMIC TABLE dt5'; +ERROR: unacceptable task name "gp_dynamic_table_refresh_xxx" +DETAIL: The prefix "gp_dynamic_table_refresh_" is reserved for system tasks. +-- should fail +DROP TASK gp_dynamic_table_refresh_:dtoid; +ERROR: can not drop a internal task "gp_dynamic_table_refresh_17387" paried with dynamic table +DETAIL: please drop the dynamic table instead +\unset dtoid +CREATE DYNAMIC TABLE dt_schedule SCHEDULE '1 2 3 4 5' AS SELECT * FROM t2; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'b' as the Cloudberry Database data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +SELECT pg_catalog.pg_get_dynamic_table_schedule('dt_schedule'::regclass::oid); + pg_get_dynamic_table_schedule +------------------------------- + 1 2 3 4 5 +(1 row) + +-- not a dynamic table +SELECT pg_catalog.pg_get_dynamic_table_schedule('t2'::regclass::oid); +WARNING: relation of oid "XXX" is not dynamic table + pg_get_dynamic_table_schedule +------------------------------- + +(1 row) + +RESET enable_answer_query_using_materialized_views; +RESET optimizer; +--start_ignore +DROP SCHEMA dynamic_table_schema cascade; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table t2 +drop cascades to materialized view mv_t2 +drop cascades to dynamic table dt3 +drop cascades to dynamic table dt4 +drop cascades to dynamic table dt5 +--end_ignore diff --git a/src/test/regress/greenplum_schedule b/src/test/regress/greenplum_schedule index dbc67d4944d..3a1f7d9dd54 100755 --- a/src/test/regress/greenplum_schedule +++ b/src/test/regress/greenplum_schedule @@ -340,6 +340,8 @@ test: uao_dml/ao_unique_index_build_row uao_dml/ao_unique_index_build_column test: bfv_copy +test: dynamic_table + # run this at the end of the schedule for more chance to catch abnormalities # different CI env with GPDB # test: gp_check_files diff --git a/src/test/regress/sql/dynamic_table.sql b/src/test/regress/sql/dynamic_table.sql new file mode 100644 index 00000000000..69a190d5a46 --- /dev/null +++ b/src/test/regress/sql/dynamic_table.sql @@ -0,0 +1,144 @@ +-- start_matchsubs +-- m/ERROR: can not drop a internal task "gp_dynamic_table_refresh_.*/ +-- s/ERROR: can not drop a internal task "gp_dynamic_table_refresh_.*/ERROR: can not drop a internal task "gp_dynamic_table_refresh_xxx"/g +-- m/WARNING: relation of oid "\d+" is not dynamic table/ +-- s/WARNING: relation of oid "\d+" is not dynamic table/WARNING: relation of oid "XXX" is not dynamic table/g +-- end_matchsubs +CREATE SCHEMA dynamic_table_schema; +SET search_path TO dynamic_table_schema; +SET optimizer = OFF; + +CREATE TABLE t1(a int, b int, c int) DISTRIBUTED BY (b); +INSERT INTO t1 SELECT i, i + 1, i + 2 FROM GENERATE_SERIES(1, 10) i; +INSERT INTO t1 SELECT i, i + 1, i + 2 FROM GENERATE_SERIES(1, 5) i; +ANALYZE t1; +CREATE DYNAMIC TABLE dt0 SCHEDULE '5 * * * *' AS + SELECT a, b, sum(c) FROM t1 GROUP BY a, b DISTRIBUTED BY(b); +\d+ dt0 +ANALYZE dt0; +-- test backgroud auto-refresh +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%'; + +EXPLAIN(COSTS OFF, VERBOSE) +SELECT a, b, sum(c) FROM t1 GROUP BY a, b; +SELECT a, b, sum(c) FROM t1 GROUP BY a, b; +SELECT * FROM dt0; + +-- test join on distributed keys +EXPLAIN(COSTS OFF, VERBOSE) +SELECT * FROM dt0 JOIN t1 USING(b); + +-- Create Dynamic Table without SCHEDULE. +CREATE DYNAMIC TABLE dt1 AS + SELECT * FROM t1 WHERE a = 1 DISTRIBUTED BY(b); +ANALYZE dt1; + +-- Create Dynamic Table without DISTRIBUTION KEYS. +CREATE DYNAMIC TABLE dt2 AS + SELECT * FROM t1 WHERE a = 2 WITH NO DATA; + +-- Refresh Dynamic Table WITH NO DATA +REFRESH DYNAMIC TABLE dt0 WITH NO DATA; + +-- Refresh Dynamic Table +REFRESH DYNAMIC TABLE dt2; +ANALYZE dt2; + +-- Test Answer Query using Dynamic Tables. +SET enable_answer_query_using_materialized_views = ON; +EXPLAIN(COSTS OFF, VERBOSE) +SELECT * FROM t1 WHERE a = 1; +SELECT * FROM t1 WHERE a = 1; +EXPLAIN(COSTS OFF, VERBOSE) +SELECT * FROM t1 WHERE a = 2; +SELECT * FROM t1 WHERE a = 2; + +-- test DROP DYNAMIC TABLE +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%' AND command LIKE '%dt0'; +DROP DYNAMIC TABLE dt0; +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%' AND command LIKE '%dt0'; + +-- drop base tables will drop DYNAMIC TABLEs too. +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%'; +DROP TABLE t1 CASCADE; +SELECT schedule, command FROM pg_task WHERE jobname LIKE 'gp_dynamic_table_refresh%'; + +-- construct dynamic table +CREATE TABLE t2(a int, b int, c int) DISTRIBUTED BY (b); +CREATE MATERIALIZED VIEW mv_t2 AS + SELECT * FROM t2 WHERE a > 1; + +-- construct dynamic table from materialized view +CREATE DYNAMIC TABLE dt3 AS + SELECT * FROM mv_t2 WHERE a = 2; + +-- construct dynamic table from dynamic table +CREATE DYNAMIC TABLE dt4 AS + SELECT * FROM dt3 WHERE b = 3; + +-- construct dynamic table from joins +CREATE DYNAMIC TABLE dt5 AS + SELECT * FROM dt3 natural join t2 natural join mv_t2; + +-- construct dynamic table from external table +begin; + +--start_ignore +CREATE OR REPLACE FUNCTION write_to_file() RETURNS integer as '$libdir/gpextprotocol.so', 'demoprot_export' LANGUAGE C STABLE NO SQL; +CREATE OR REPLACE FUNCTION read_from_file() RETURNS integer as '$libdir/gpextprotocol.so', 'demoprot_import' LANGUAGE C STABLE NO SQL; +DROP PROTOCOL IF EXISTS demoprot; +--end_ignore +CREATE TRUSTED PROTOCOL demoprot (readfunc = 'read_from_file', writefunc = 'write_to_file'); -- should succeed + +CREATE WRITABLE EXTERNAL TABLE ext_w(id int) + LOCATION('demoprot://dynamic_table_text_file.txt') +FORMAT 'text' +DISTRIBUTED BY (id); + +INSERT INTO ext_w SELECT * FROM generate_series(1, 10); + +CREATE READABLE EXTERNAL TABLE ext_r(id int) + LOCATION('demoprot://dynamic_table_text_file.txt') +FORMAT 'text'; + +EXPLAIN(COSTS OFF, VERBOSE) +SELECT sum(id) FROM ext_r where id > 5; +SELECT sum(id) FROM ext_r where id > 5; + +CREATE DYNAMIC TABLE dt_external AS + SELECT * FROM ext_r where id > 5; + +ANALYZE dt_external; + +SHOW optimizer; +SET LOCAL enable_answer_query_using_materialized_views = ON; +SET LOCAL aqumv_allow_foreign_table = ON; + +EXPLAIN(COSTS OFF, VERBOSE) +SELECT sum(id) FROM ext_r where id > 5; +SELECT sum(id) FROM ext_r where id > 5; +DROP FOREIGN TABLE ext_r CASCADE; +DROP FOREIGN TABLE ext_w; +ABORT; + +-- Test resevered job name for Dynamic Tables. +SELECT 'dt5'::regclass::oid AS dtoid \gset + +-- should fail +CREATE TASK gp_dynamic_table_refresh_xxx SCHEDULE '1 second' AS 'REFRESH DYNAMIC TABLE dt5'; + +-- should fail +DROP TASK gp_dynamic_table_refresh_:dtoid; + +\unset dtoid + +CREATE DYNAMIC TABLE dt_schedule SCHEDULE '1 2 3 4 5' AS SELECT * FROM t2; +SELECT pg_catalog.pg_get_dynamic_table_schedule('dt_schedule'::regclass::oid); +-- not a dynamic table +SELECT pg_catalog.pg_get_dynamic_table_schedule('t2'::regclass::oid); + +RESET enable_answer_query_using_materialized_views; +RESET optimizer; +--start_ignore +DROP SCHEMA dynamic_table_schema cascade; +--end_ignore