Merge remote-tracking branch 'origin/separate-extensions' into dev/yanwjin/upgrade1

pull/5911/head
Yanwen Jin 2022-06-14 15:19:45 -07:00
commit e99eb59dd9
349 changed files with 14632 additions and 6267 deletions

View File

@ -468,7 +468,7 @@ jobs:
when: on_fail when: on_fail
- store_artifacts: - store_artifacts:
name: 'Save tap logs' name: 'Save tap logs'
path: /home/circleci/project/src/test/recovery/tmp_check/log path: /home/circleci/project/src/test/<< parameters.suite >>/tmp_check/log
- store_artifacts: - store_artifacts:
name: 'Save core dumps' name: 'Save core dumps'
path: /tmp/core_dumps path: /tmp/core_dumps
@ -598,6 +598,12 @@ workflows:
image_tag: '<< pipeline.parameters.pg13_version >>' image_tag: '<< pipeline.parameters.pg13_version >>'
suite: recovery suite: recovery
requires: [build-13] requires: [build-13]
- tap-test-citus:
name: 'test-13_tap-columnar-freezing'
pg_major: 13
image_tag: '<< pipeline.parameters.pg13_version >>'
suite: columnar_freezing
requires: [build-13]
- test-citus: - test-citus:
name: 'test-13_check-failure' name: 'test-13_check-failure'
pg_major: 13 pg_major: 13
@ -666,6 +672,12 @@ workflows:
image_tag: '<< pipeline.parameters.pg14_version >>' image_tag: '<< pipeline.parameters.pg14_version >>'
suite: recovery suite: recovery
requires: [build-14] requires: [build-14]
- tap-test-citus:
name: 'test-14_tap-columnar-freezing'
pg_major: 14
image_tag: '<< pipeline.parameters.pg14_version >>'
suite: columnar_freezing
requires: [build-14]
- test-citus: - test-citus:
name: 'test-14_check-failure' name: 'test-14_check-failure'
pg_major: 14 pg_major: 14

View File

@ -1,3 +1,22 @@
### citus v11.0.1_beta (April 11, 2022) ###
* Adds propagation of `DOMAIN` objects
* Adds support for `TABLESAMPLE`
* Allows adding a unique constraint with an index
* Fixes a bug that could cause `EXPLAIN ANALYZE` to fail for prepared statements
with custom type
* Fixes a bug that could cause Citus not to create function in transaction block
properly
* Fixes a bug that could cause returning invalid JSON when running
`EXPLAIN ANALYZE` with subplans
* Fixes a bug that prevents non-client backends from accessing shards
### citus v11.0.0_beta (March 22, 2022) ### ### citus v11.0.0_beta (March 22, 2022) ###
* Drops support for PostgreSQL 12 * Drops support for PostgreSQL 12

View File

@ -90,38 +90,25 @@ data.
Set options using: Set options using:
```sql ```sql
alter_columnar_table_set( ALTER TABLE my_columnar_table SET
relid REGCLASS, (columnar.compression = none, columnar.stripe_row_limit = 10000);
chunk_group_row_limit INT4 DEFAULT NULL,
stripe_row_limit INT4 DEFAULT NULL,
compression NAME DEFAULT NULL,
compression_level INT4)
```
For example:
```sql
SELECT alter_columnar_table_set(
'my_columnar_table',
compression => 'none',
stripe_row_limit => 10000);
``` ```
The following options are available: The following options are available:
* **compression**: `[none|pglz|zstd|lz4|lz4hc]` - set the compression type * **columnar.compression**: `[none|pglz|zstd|lz4|lz4hc]` - set the compression type
for _newly-inserted_ data. Existing data will not be for _newly-inserted_ data. Existing data will not be
recompressed/decompressed. The default value is `zstd` (if support recompressed/decompressed. The default value is `zstd` (if support
has been compiled in). has been compiled in).
* **compression_level**: ``<integer>`` - Sets compression level. Valid * **columnar.compression_level**: ``<integer>`` - Sets compression level. Valid
settings are from 1 through 19. If the compression method does not settings are from 1 through 19. If the compression method does not
support the level chosen, the closest level will be selected support the level chosen, the closest level will be selected
instead. instead.
* **stripe_row_limit**: ``<integer>`` - the maximum number of rows per * **columnar.stripe_row_limit**: ``<integer>`` - the maximum number of rows per
stripe for _newly-inserted_ data. Existing stripes of data will not stripe for _newly-inserted_ data. Existing stripes of data will not
be changed and may have more rows than this maximum value. The be changed and may have more rows than this maximum value. The
default value is `150000`. default value is `150000`.
* **chunk_group_row_limit**: ``<integer>`` - the maximum number of rows per * **columnar.chunk_group_row_limit**: ``<integer>`` - the maximum number of rows per
chunk for _newly-inserted_ data. Existing chunks of data will not be chunk for _newly-inserted_ data. Existing chunks of data will not be
changed and may have more rows than this maximum value. The default changed and may have more rows than this maximum value. The default
value is `10000`. value is `10000`.

View File

@ -115,8 +115,6 @@ columnar_storage_info(PG_FUNCTION_ARGS)
RelationGetRelationName(rel)))); RelationGetRelationName(rel))));
} }
RelationOpenSmgr(rel);
Datum values[STORAGE_INFO_NATTS] = { 0 }; Datum values[STORAGE_INFO_NATTS] = { 0 };
bool nulls[STORAGE_INFO_NATTS] = { 0 }; bool nulls[STORAGE_INFO_NATTS] = { 0 };

View File

@ -59,6 +59,7 @@
#include "utils/rel.h" #include "utils/rel.h"
#include "utils/relfilenodemap.h" #include "utils/relfilenodemap.h"
#define COLUMNAR_RELOPTION_NAMESPACE "columnar"
typedef struct typedef struct
{ {
@ -82,6 +83,7 @@ typedef enum RowNumberLookupMode
FIND_GREATER FIND_GREATER
} RowNumberLookupMode; } RowNumberLookupMode;
static void ParseColumnarRelOptions(List *reloptions, ColumnarOptions *options);
static void InsertEmptyStripeMetadataRow(uint64 storageId, uint64 stripeId, static void InsertEmptyStripeMetadataRow(uint64 storageId, uint64 stripeId,
uint32 columnCount, uint32 chunkGroupRowCount, uint32 columnCount, uint32 chunkGroupRowCount,
uint64 firstRowNumber); uint64 firstRowNumber);
@ -218,6 +220,154 @@ InitColumnarOptions(Oid regclass)
} }
/*
* ParseColumnarRelOptions - update the given 'options' using the given list
* of DefElem.
*/
static void
ParseColumnarRelOptions(List *reloptions, ColumnarOptions *options)
{
ListCell *lc = NULL;
foreach(lc, reloptions)
{
DefElem *elem = castNode(DefElem, lfirst(lc));
if (elem->defnamespace == NULL ||
strcmp(elem->defnamespace, COLUMNAR_RELOPTION_NAMESPACE) != 0)
{
ereport(ERROR, (errmsg("columnar options must have the prefix \"%s\"",
COLUMNAR_RELOPTION_NAMESPACE)));
}
if (strcmp(elem->defname, "chunk_group_row_limit") == 0)
{
options->chunkRowCount = (elem->arg == NULL) ?
columnar_chunk_group_row_limit : defGetInt64(elem);
if (options->chunkRowCount < CHUNK_ROW_COUNT_MINIMUM ||
options->chunkRowCount > CHUNK_ROW_COUNT_MAXIMUM)
{
ereport(ERROR, (errmsg("chunk group row count limit out of range"),
errhint("chunk group row count limit must be between "
UINT64_FORMAT " and " UINT64_FORMAT,
(uint64) CHUNK_ROW_COUNT_MINIMUM,
(uint64) CHUNK_ROW_COUNT_MAXIMUM)));
}
}
else if (strcmp(elem->defname, "stripe_row_limit") == 0)
{
options->stripeRowCount = (elem->arg == NULL) ?
columnar_stripe_row_limit : defGetInt64(elem);
if (options->stripeRowCount < STRIPE_ROW_COUNT_MINIMUM ||
options->stripeRowCount > STRIPE_ROW_COUNT_MAXIMUM)
{
ereport(ERROR, (errmsg("stripe row count limit out of range"),
errhint("stripe row count limit must be between "
UINT64_FORMAT " and " UINT64_FORMAT,
(uint64) STRIPE_ROW_COUNT_MINIMUM,
(uint64) STRIPE_ROW_COUNT_MAXIMUM)));
}
}
else if (strcmp(elem->defname, "compression") == 0)
{
options->compressionType = (elem->arg == NULL) ?
columnar_compression : ParseCompressionType(
defGetString(elem));
if (options->compressionType == COMPRESSION_TYPE_INVALID)
{
ereport(ERROR, (errmsg("unknown compression type for columnar table: %s",
quote_identifier(defGetString(elem)))));
}
}
else if (strcmp(elem->defname, "compression_level") == 0)
{
options->compressionLevel = (elem->arg == NULL) ?
columnar_compression_level : defGetInt64(elem);
if (options->compressionLevel < COMPRESSION_LEVEL_MIN ||
options->compressionLevel > COMPRESSION_LEVEL_MAX)
{
ereport(ERROR, (errmsg("compression level out of range"),
errhint("compression level must be between %d and %d",
COMPRESSION_LEVEL_MIN,
COMPRESSION_LEVEL_MAX)));
}
}
else
{
ereport(ERROR, (errmsg("unrecognized columnar storage parameter \"%s\"",
elem->defname)));
}
}
}
/*
* ExtractColumnarOptions - extract columnar options from inOptions, appending
* to inoutColumnarOptions. Return the remaining (non-columnar) options.
*/
List *
ExtractColumnarRelOptions(List *inOptions, List **inoutColumnarOptions)
{
List *otherOptions = NIL;
ListCell *lc = NULL;
foreach(lc, inOptions)
{
DefElem *elem = castNode(DefElem, lfirst(lc));
if (elem->defnamespace != NULL &&
strcmp(elem->defnamespace, COLUMNAR_RELOPTION_NAMESPACE) == 0)
{
*inoutColumnarOptions = lappend(*inoutColumnarOptions, elem);
}
else
{
otherOptions = lappend(otherOptions, elem);
}
}
/* validate options */
ColumnarOptions dummy = { 0 };
ParseColumnarRelOptions(*inoutColumnarOptions, &dummy);
return otherOptions;
}
/*
* SetColumnarRelOptions - apply the list of DefElem options to the
* relation. If there are duplicates, the last one in the list takes effect.
*/
void
SetColumnarRelOptions(RangeVar *rv, List *reloptions)
{
ColumnarOptions options = { 0 };
if (reloptions == NIL)
{
return;
}
Relation rel = relation_openrv(rv, AccessShareLock);
Oid relid = RelationGetRelid(rel);
relation_close(rel, NoLock);
/* get existing or default options */
if (!ReadColumnarOptions(relid, &options))
{
/* if extension doesn't exist, just return */
return;
}
ParseColumnarRelOptions(reloptions, &options);
SetColumnarOptions(relid, &options);
}
/* /*
* SetColumnarOptions writes the passed table options as the authoritive options to the * SetColumnarOptions writes the passed table options as the authoritive options to the
* table irregardless of the optiones already existing or not. This can be used to put a * table irregardless of the optiones already existing or not. This can be used to put a
@ -1433,7 +1583,7 @@ DeleteTupleAndEnforceConstraints(ModifyState *state, HeapTuple heapTuple)
simple_heap_delete(state->rel, tid); simple_heap_delete(state->rel, tid);
/* execute AFTER ROW DELETE Triggers to enforce constraints */ /* execute AFTER ROW DELETE Triggers to enforce constraints */
ExecARDeleteTriggers(estate, resultRelInfo, tid, NULL, NULL); ExecARDeleteTriggers_compat(estate, resultRelInfo, tid, NULL, NULL, false);
} }
@ -1670,7 +1820,15 @@ ColumnarChunkGroupIndexRelationId(void)
static Oid static Oid
ColumnarNamespaceId(void) ColumnarNamespaceId(void)
{ {
return get_namespace_oid("columnar", false); Oid namespace = get_namespace_oid("columnar_internal", true);
/* if schema is earlier than 11.1-1 */
if (!OidIsValid(namespace))
{
namespace = get_namespace_oid("columnar", false);
}
return namespace;
} }
@ -1712,6 +1870,13 @@ columnar_relation_storageid(PG_FUNCTION_ARGS)
{ {
Oid relationId = PG_GETARG_OID(0); Oid relationId = PG_GETARG_OID(0);
Relation relation = relation_open(relationId, AccessShareLock); Relation relation = relation_open(relationId, AccessShareLock);
if (!pg_class_ownercheck(relationId, GetUserId()))
{
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE,
get_rel_name(relationId));
}
if (!IsColumnarTableAmTable(relationId)) if (!IsColumnarTableAmTable(relationId))
{ {
elog(ERROR, "relation \"%s\" is not a columnar table", elog(ERROR, "relation \"%s\" is not a columnar table",
@ -1738,11 +1903,10 @@ ColumnarStorageUpdateIfNeeded(Relation rel, bool isUpgrade)
return; return;
} }
RelationOpenSmgr(rel); BlockNumber nblocks = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
BlockNumber nblocks = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
if (nblocks < 2) if (nblocks < 2)
{ {
ColumnarStorageInit(rel->rd_smgr, ColumnarMetadataNewStorageId()); ColumnarStorageInit(RelationGetSmgr(rel), ColumnarMetadataNewStorageId());
return; return;
} }

View File

@ -44,6 +44,8 @@
#include "storage/bufmgr.h" #include "storage/bufmgr.h"
#include "storage/lmgr.h" #include "storage/lmgr.h"
#include "pg_version_compat.h"
#include "columnar/columnar.h" #include "columnar/columnar.h"
#include "columnar/columnar_storage.h" #include "columnar/columnar_storage.h"
@ -354,8 +356,7 @@ ColumnarStorageGetReservedOffset(Relation rel, bool force)
bool bool
ColumnarStorageIsCurrent(Relation rel) ColumnarStorageIsCurrent(Relation rel)
{ {
RelationOpenSmgr(rel); BlockNumber nblocks = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
BlockNumber nblocks = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
if (nblocks < 2) if (nblocks < 2)
{ {
@ -439,8 +440,7 @@ ColumnarStorageReserveData(Relation rel, uint64 amount)
PhysicalAddr final = LogicalToPhysical(nextReservation - 1); PhysicalAddr final = LogicalToPhysical(nextReservation - 1);
/* extend with new pages */ /* extend with new pages */
RelationOpenSmgr(rel); BlockNumber nblocks = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
BlockNumber nblocks = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
while (nblocks <= final.blockno) while (nblocks <= final.blockno)
{ {
@ -547,8 +547,7 @@ ColumnarStorageTruncate(Relation rel, uint64 newDataReservation)
rel->rd_id, newDataReservation); rel->rd_id, newDataReservation);
} }
RelationOpenSmgr(rel); BlockNumber old_rel_pages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
BlockNumber old_rel_pages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
if (old_rel_pages == 0) if (old_rel_pages == 0)
{ {
/* nothing to do */ /* nothing to do */
@ -627,8 +626,7 @@ ColumnarOverwriteMetapage(Relation relation, ColumnarMetapage columnarMetapage)
static ColumnarMetapage static ColumnarMetapage
ColumnarMetapageRead(Relation rel, bool force) ColumnarMetapageRead(Relation rel, bool force)
{ {
RelationOpenSmgr(rel); BlockNumber nblocks = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
BlockNumber nblocks = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
if (nblocks == 0) if (nblocks == 0)
{ {
/* /*

View File

@ -20,6 +20,7 @@
#include "access/xact.h" #include "access/xact.h"
#include "catalog/catalog.h" #include "catalog/catalog.h"
#include "catalog/index.h" #include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h" #include "catalog/objectaccess.h"
#include "catalog/pg_am.h" #include "catalog/pg_am.h"
#include "catalog/pg_publication.h" #include "catalog/pg_publication.h"
@ -104,9 +105,6 @@ typedef struct IndexFetchColumnarData
MemoryContext scanContext; MemoryContext scanContext;
} IndexFetchColumnarData; } IndexFetchColumnarData;
/* available to other extensions using find_rendezvous_variable() */
static ColumnarTableSetOptions_hook_type ColumnarTableSetOptions_hook = NULL;
static object_access_hook_type PrevObjectAccessHook = NULL; static object_access_hook_type PrevObjectAccessHook = NULL;
static ProcessUtility_hook_type PrevProcessUtilityHook = NULL; static ProcessUtility_hook_type PrevProcessUtilityHook = NULL;
@ -117,6 +115,8 @@ static void ColumnarTriggerCreateHook(Oid tgid);
static void ColumnarTableAMObjectAccessHook(ObjectAccessType access, Oid classId, static void ColumnarTableAMObjectAccessHook(ObjectAccessType access, Oid classId,
Oid objectId, int subId, Oid objectId, int subId,
void *arg); void *arg);
static RangeVar * ColumnarProcessAlterTable(AlterTableStmt *alterTableStmt,
List **columnarOptions);
static void ColumnarProcessUtility(PlannedStmt *pstmt, static void ColumnarProcessUtility(PlannedStmt *pstmt,
const char *queryString, const char *queryString,
#if PG_VERSION_NUM >= PG_VERSION_14 #if PG_VERSION_NUM >= PG_VERSION_14
@ -882,7 +882,7 @@ columnar_relation_set_new_filenode(Relation rel,
*freezeXid = RecentXmin; *freezeXid = RecentXmin;
*minmulti = GetOldestMultiXactId(); *minmulti = GetOldestMultiXactId();
SMgrRelation srel = RelationCreateStorage(*newrnode, persistence); SMgrRelation srel = RelationCreateStorage_compat(*newrnode, persistence, true);
ColumnarStorageInit(srel, ColumnarMetadataNewStorageId()); ColumnarStorageInit(srel, ColumnarMetadataNewStorageId());
InitColumnarOptions(rel->rd_id); InitColumnarOptions(rel->rd_id);
@ -914,8 +914,7 @@ columnar_relation_nontransactional_truncate(Relation rel)
RelationTruncate(rel, 0); RelationTruncate(rel, 0);
uint64 storageId = ColumnarMetadataNewStorageId(); uint64 storageId = ColumnarMetadataNewStorageId();
RelationOpenSmgr(rel); ColumnarStorageInit(RelationGetSmgr(rel), storageId);
ColumnarStorageInit(rel->rd_smgr, storageId);
} }
@ -1034,6 +1033,27 @@ NeededColumnsList(TupleDesc tupdesc, Bitmapset *attr_needed)
} }
/*
* ColumnarTableTupleCount returns the number of tuples that columnar
* table with relationId has by using stripe metadata.
*/
static uint64
ColumnarTableTupleCount(Relation relation)
{
List *stripeList = StripesForRelfilenode(relation->rd_node);
uint64 tupleCount = 0;
ListCell *lc = NULL;
foreach(lc, stripeList)
{
StripeMetadata *stripe = lfirst(lc);
tupleCount += stripe->rowCount;
}
return tupleCount;
}
/* /*
* columnar_vacuum_rel implements VACUUM without FULL option. * columnar_vacuum_rel implements VACUUM without FULL option.
*/ */
@ -1050,6 +1070,9 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
return; return;
} }
pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM,
RelationGetRelid(rel));
/* /*
* If metapage version of relation is older, then we hint users to VACUUM * If metapage version of relation is older, then we hint users to VACUUM
* the relation in ColumnarMetapageCheckVersion. So if needed, upgrade * the relation in ColumnarMetapageCheckVersion. So if needed, upgrade
@ -1073,6 +1096,78 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
{ {
TruncateColumnar(rel, elevel); TruncateColumnar(rel, elevel);
} }
BlockNumber new_rel_pages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
/* get the number of indexes */
List *indexList = RelationGetIndexList(rel);
int nindexes = list_length(indexList);
TransactionId oldestXmin;
TransactionId freezeLimit;
MultiXactId multiXactCutoff;
/* initialize xids */
#if PG_VERSION_NUM >= PG_VERSION_15
MultiXactId oldestMxact;
vacuum_set_xid_limits(rel,
params->freeze_min_age,
params->freeze_table_age,
params->multixact_freeze_min_age,
params->multixact_freeze_table_age,
&oldestXmin, &oldestMxact,
&freezeLimit, &multiXactCutoff);
Assert(MultiXactIdPrecedesOrEquals(multiXactCutoff, oldestMxact));
#else
TransactionId xidFullScanLimit;
MultiXactId mxactFullScanLimit;
vacuum_set_xid_limits(rel,
params->freeze_min_age,
params->freeze_table_age,
params->multixact_freeze_min_age,
params->multixact_freeze_table_age,
&oldestXmin, &freezeLimit, &xidFullScanLimit,
&multiXactCutoff, &mxactFullScanLimit);
#endif
Assert(TransactionIdPrecedesOrEquals(freezeLimit, oldestXmin));
/*
* Columnar storage doesn't hold any transaction IDs, so we can always
* just advance to the most aggressive value.
*/
TransactionId newRelFrozenXid = oldestXmin;
#if PG_VERSION_NUM >= PG_VERSION_15
MultiXactId newRelminMxid = oldestMxact;
#else
MultiXactId newRelminMxid = multiXactCutoff;
#endif
double new_live_tuples = ColumnarTableTupleCount(rel);
/* all visible pages are always 0 */
BlockNumber new_rel_allvisible = 0;
#if PG_VERSION_NUM >= PG_VERSION_15
bool frozenxid_updated;
bool minmulti_updated;
vac_update_relstats(rel, new_rel_pages, new_live_tuples,
new_rel_allvisible, nindexes > 0,
newRelFrozenXid, newRelminMxid,
&frozenxid_updated, &minmulti_updated, false);
#else
vac_update_relstats(rel, new_rel_pages, new_live_tuples,
new_rel_allvisible, nindexes > 0,
newRelFrozenXid, newRelminMxid, false);
#endif
pgstat_report_vacuum(RelationGetRelid(rel),
rel->rd_rel->relisshared,
Max(new_live_tuples, 0),
0);
pgstat_progress_end_command();
} }
@ -1137,8 +1232,7 @@ LogRelationStats(Relation rel, int elevel)
totalStripeLength += stripe->dataLength; totalStripeLength += stripe->dataLength;
} }
RelationOpenSmgr(rel); uint64 relPages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
uint64 relPages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
RelationCloseSmgr(rel); RelationCloseSmgr(rel);
Datum storageId = DirectFunctionCall1(columnar_relation_storageid, Datum storageId = DirectFunctionCall1(columnar_relation_storageid,
@ -1240,8 +1334,7 @@ TruncateColumnar(Relation rel, int elevel)
uint64 newDataReservation = Max(GetHighestUsedAddress(rel->rd_node) + 1, uint64 newDataReservation = Max(GetHighestUsedAddress(rel->rd_node) + 1,
ColumnarFirstLogicalOffset); ColumnarFirstLogicalOffset);
RelationOpenSmgr(rel); BlockNumber old_rel_pages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
BlockNumber old_rel_pages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
if (!ColumnarStorageTruncate(rel, newDataReservation)) if (!ColumnarStorageTruncate(rel, newDataReservation))
{ {
@ -1249,8 +1342,7 @@ TruncateColumnar(Relation rel, int elevel)
return; return;
} }
RelationOpenSmgr(rel); BlockNumber new_rel_pages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
BlockNumber new_rel_pages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
/* /*
* We can release the exclusive lock as soon as we have truncated. * We can release the exclusive lock as soon as we have truncated.
@ -1785,20 +1877,17 @@ columnar_relation_size(Relation rel, ForkNumber forkNumber)
uint64 nblocks = 0; uint64 nblocks = 0;
/* Open it at the smgr level if not already done */
RelationOpenSmgr(rel);
/* InvalidForkNumber indicates returning the size for all forks */ /* InvalidForkNumber indicates returning the size for all forks */
if (forkNumber == InvalidForkNumber) if (forkNumber == InvalidForkNumber)
{ {
for (int i = 0; i < MAX_FORKNUM; i++) for (int i = 0; i < MAX_FORKNUM; i++)
{ {
nblocks += smgrnblocks(rel->rd_smgr, i); nblocks += smgrnblocks(RelationGetSmgr(rel), i);
} }
} }
else else
{ {
nblocks = smgrnblocks(rel->rd_smgr, forkNumber); nblocks = smgrnblocks(RelationGetSmgr(rel), forkNumber);
} }
return nblocks * BLCKSZ; return nblocks * BLCKSZ;
@ -1820,8 +1909,7 @@ columnar_estimate_rel_size(Relation rel, int32 *attr_widths,
double *allvisfrac) double *allvisfrac)
{ {
CheckCitusColumnarVersion(ERROR); CheckCitusColumnarVersion(ERROR);
RelationOpenSmgr(rel); *pages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
*pages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
*tuples = ColumnarTableRowCount(rel); *tuples = ColumnarTableRowCount(rel);
/* /*
@ -1911,11 +1999,6 @@ ColumnarSubXactCallback(SubXactEvent event, SubTransactionId mySubid,
void void
columnar_tableam_init() columnar_tableam_init()
{ {
ColumnarTableSetOptions_hook_type **ColumnarTableSetOptions_hook_ptr =
(ColumnarTableSetOptions_hook_type **) find_rendezvous_variable(
COLUMNAR_SETOPTIONS_HOOK_SYM);
*ColumnarTableSetOptions_hook_ptr = &ColumnarTableSetOptions_hook;
RegisterXactCallback(ColumnarXactCallback, NULL); RegisterXactCallback(ColumnarXactCallback, NULL);
RegisterSubXactCallback(ColumnarSubXactCallback, NULL); RegisterSubXactCallback(ColumnarSubXactCallback, NULL);
@ -2089,6 +2172,71 @@ ColumnarTableAMObjectAccessHook(ObjectAccessType access, Oid classId, Oid object
} }
/*
* ColumnarProcessAlterTable - if modifying a columnar table, extract columnar
* options and return the table's RangeVar.
*/
static RangeVar *
ColumnarProcessAlterTable(AlterTableStmt *alterTableStmt, List **columnarOptions)
{
RangeVar *columnarRangeVar = NULL;
Relation rel = relation_openrv_extended(alterTableStmt->relation, AccessShareLock,
alterTableStmt->missing_ok);
if (rel == NULL)
{
return NULL;
}
/* track separately in case of ALTER TABLE ... SET ACCESS METHOD */
bool srcIsColumnar = rel->rd_tableam == GetColumnarTableAmRoutine();
bool destIsColumnar = srcIsColumnar;
ListCell *lc = NULL;
foreach(lc, alterTableStmt->cmds)
{
AlterTableCmd *alterTableCmd = castNode(AlterTableCmd, lfirst(lc));
if (alterTableCmd->subtype == AT_SetRelOptions ||
alterTableCmd->subtype == AT_ResetRelOptions)
{
List *options = castNode(List, alterTableCmd->def);
alterTableCmd->def = (Node *) ExtractColumnarRelOptions(
options, columnarOptions);
if (destIsColumnar)
{
columnarRangeVar = alterTableStmt->relation;
}
}
#if PG_VERSION_NUM >= PG_VERSION_15
else if (alterTableCmd->subtype == AT_SetAccessMethod)
{
if (columnarRangeVar || *columnarOptions)
{
ereport(ERROR, (errmsg(
"ALTER TABLE cannot alter the access method after altering storage parameters"),
errhint(
"Specify SET ACCESS METHOD before storage parameters, or use separate ALTER TABLE commands.")));
}
destIsColumnar = (strcmp(alterTableCmd->name, COLUMNAR_AM_NAME) == 0);
if (srcIsColumnar && !destIsColumnar)
{
DeleteColumnarTableOptions(RelationGetRelid(rel), true);
}
}
#endif /* PG_VERSION_15 */
}
relation_close(rel, NoLock);
return columnarRangeVar;
}
/* /*
* Utility hook for columnar tables. * Utility hook for columnar tables.
*/ */
@ -2113,27 +2261,160 @@ ColumnarProcessUtility(PlannedStmt *pstmt,
Node *parsetree = pstmt->utilityStmt; Node *parsetree = pstmt->utilityStmt;
if (IsA(parsetree, IndexStmt)) RangeVar *columnarRangeVar = NULL;
List *columnarOptions = NIL;
switch (nodeTag(parsetree))
{ {
IndexStmt *indexStmt = (IndexStmt *) parsetree; case T_IndexStmt:
Relation rel = relation_openrv(indexStmt->relation,
indexStmt->concurrent ? ShareUpdateExclusiveLock :
ShareLock);
if (rel->rd_tableam == GetColumnarTableAmRoutine())
{ {
CheckCitusColumnarVersion(ERROR); IndexStmt *indexStmt = (IndexStmt *) parsetree;
if (!ColumnarSupportsIndexAM(indexStmt->accessMethod))
Relation rel = relation_openrv(indexStmt->relation,
indexStmt->concurrent ?
ShareUpdateExclusiveLock :
ShareLock);
if (rel->rd_tableam == GetColumnarTableAmRoutine())
{ {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), CheckCitusColumnarVersion(ERROR);
errmsg("unsupported access method for the " if (!ColumnarSupportsIndexAM(indexStmt->accessMethod))
"index on columnar table %s", {
RelationGetRelationName(rel)))); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported access method for the "
"index on columnar table %s",
RelationGetRelationName(rel))));
}
} }
RelationClose(rel);
break;
} }
RelationClose(rel); case T_CreateStmt:
{
CreateStmt *createStmt = castNode(CreateStmt, parsetree);
bool no_op = false;
if (createStmt->if_not_exists)
{
Oid existing_relid;
/* use same check as transformCreateStmt */
(void) RangeVarGetAndCheckCreationNamespace(
createStmt->relation, AccessShareLock, &existing_relid);
no_op = OidIsValid(existing_relid);
}
if (!no_op && createStmt->accessMethod != NULL &&
!strcmp(createStmt->accessMethod, COLUMNAR_AM_NAME))
{
columnarRangeVar = createStmt->relation;
createStmt->options = ExtractColumnarRelOptions(createStmt->options,
&columnarOptions);
}
break;
}
case T_CreateTableAsStmt:
{
CreateTableAsStmt *createTableAsStmt = castNode(CreateTableAsStmt, parsetree);
IntoClause *into = createTableAsStmt->into;
bool no_op = false;
if (createTableAsStmt->if_not_exists)
{
Oid existing_relid;
/* use same check as transformCreateStmt */
(void) RangeVarGetAndCheckCreationNamespace(
into->rel, AccessShareLock, &existing_relid);
no_op = OidIsValid(existing_relid);
}
if (!no_op && into->accessMethod != NULL &&
!strcmp(into->accessMethod, COLUMNAR_AM_NAME))
{
columnarRangeVar = into->rel;
into->options = ExtractColumnarRelOptions(into->options,
&columnarOptions);
}
break;
}
case T_AlterTableStmt:
{
AlterTableStmt *alterTableStmt = castNode(AlterTableStmt, parsetree);
columnarRangeVar = ColumnarProcessAlterTable(alterTableStmt,
&columnarOptions);
break;
}
default:
/* FALL THROUGH */
break;
}
if (columnarOptions != NIL && columnarRangeVar == NULL)
{
ereport(ERROR,
(errmsg("columnar storage parameters specified on non-columnar table")));
}
if (IsA(parsetree, CreateExtensionStmt))
{
CreateExtensionStmt *createExtensionStmt = castNode(CreateExtensionStmt,
parsetree);
if (get_extension_oid("citus_columnar", true) == InvalidOid)
{
if (strcmp(createExtensionStmt->extname, "citus_columnar") == 0)
{
DefElem *newVersionValue = GetExtensionOption(
createExtensionStmt->options,
"new_version");
if (newVersionValue)
{
const char *newVersion = defGetString(newVersionValue);
if (strcmp(newVersion, "11.1-0") == 0)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"unsupported citus_columnar version 11.1-0")));
}
}
/*latest citus requires install columnar first, existing citus can only be an older version */
if (get_extension_oid("citus", true) != InvalidOid)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"must upgrade citus to version 11.1-1 first")));
}
}
}
}
if (IsA(parsetree, AlterExtensionStmt))
{
AlterExtensionStmt *alterExtensionStmt = castNode(AlterExtensionStmt, parsetree);
if (strcmp(alterExtensionStmt->extname, "citus_columnar") == 0)
{
DefElem *newVersionValue = GetExtensionOption(alterExtensionStmt->options,
"new_version");
if (newVersionValue)
{
const char *newVersion = defGetString(newVersionValue);
if (strcmp(newVersion, "11.1-0") == 0)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported citus_columnar version 11.1-0")));
}
}
}
} }
if (IsA(parsetree, CreateExtensionStmt)) if (IsA(parsetree, CreateExtensionStmt))
@ -2191,6 +2472,11 @@ ColumnarProcessUtility(PlannedStmt *pstmt,
PrevProcessUtilityHook_compat(pstmt, queryString, false, context, PrevProcessUtilityHook_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag); params, queryEnv, dest, completionTag);
if (columnarOptions != NIL)
{
SetColumnarRelOptions(columnarRangeVar, columnarOptions);
}
} }
@ -2354,18 +2640,30 @@ detoast_values(TupleDesc tupleDesc, Datum *orig_values, bool *isnull)
static void static void
ColumnarCheckLogicalReplication(Relation rel) ColumnarCheckLogicalReplication(Relation rel)
{ {
bool pubActionInsert = false;
if (!is_publishable_relation(rel)) if (!is_publishable_relation(rel))
{ {
return; return;
} }
#if PG_VERSION_NUM >= PG_VERSION_15
{
PublicationDesc pubdesc;
RelationBuildPublicationDesc(rel, &pubdesc);
pubActionInsert = pubdesc.pubactions.pubinsert;
}
#else
if (rel->rd_pubactions == NULL) if (rel->rd_pubactions == NULL)
{ {
GetRelationPublicationActions(rel); GetRelationPublicationActions(rel);
Assert(rel->rd_pubactions != NULL); Assert(rel->rd_pubactions != NULL);
} }
pubActionInsert = rel->rd_pubactions->pubinsert;
#endif
if (rel->rd_pubactions->pubinsert) if (pubActionInsert)
{ {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg( errmsg(
@ -2375,219 +2673,36 @@ ColumnarCheckLogicalReplication(Relation rel)
/* /*
* alter_columnar_table_set is a UDF exposed in postgres to change settings on a columnar * alter_columnar_table_set()
* table. Calling this function on a non-columnar table gives an error.
* *
* sql syntax: * Deprecated in 11.1-1: should issue ALTER TABLE ... SET instead. Function
* pg_catalog.alter_columnar_table_set( * still available, but implemented in PL/pgSQL instead of C.
* table_name regclass,
* chunk_group_row_limit int DEFAULT NULL,
* stripe_row_limit int DEFAULT NULL,
* compression name DEFAULT null)
* *
* All arguments except the table name are optional. The UDF is supposed to be called * C code is removed -- the symbol may still be required in some
* like: * upgrade/downgrade paths, but it should not be called.
* SELECT alter_columnar_table_set('table', compression => 'pglz');
*
* This will only update the compression of the table, keeping all other settings the
* same. Multiple settings can be changed at the same time by providing multiple
* arguments. Calling the argument with the NULL value will be interperted as not having
* provided the argument.
*/ */
PG_FUNCTION_INFO_V1(alter_columnar_table_set); PG_FUNCTION_INFO_V1(alter_columnar_table_set);
Datum Datum
alter_columnar_table_set(PG_FUNCTION_ARGS) alter_columnar_table_set(PG_FUNCTION_ARGS)
{ {
CheckCitusColumnarVersion(ERROR); elog(ERROR, "alter_columnar_table_set is deprecated");
Oid relationId = PG_GETARG_OID(0);
Relation rel = table_open(relationId, AccessExclusiveLock); /* ALTER TABLE LOCK */
if (!IsColumnarTableAmTable(relationId))
{
ereport(ERROR, (errmsg("table %s is not a columnar table",
quote_identifier(RelationGetRelationName(rel)))));
}
if (!pg_class_ownercheck(relationId, GetUserId()))
{
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE,
get_rel_name(relationId));
}
ColumnarOptions options = { 0 };
if (!ReadColumnarOptions(relationId, &options))
{
ereport(ERROR, (errmsg("unable to read current options for table")));
}
/* chunk_group_row_limit => not null */
if (!PG_ARGISNULL(1))
{
options.chunkRowCount = PG_GETARG_INT32(1);
if (options.chunkRowCount < CHUNK_ROW_COUNT_MINIMUM ||
options.chunkRowCount > CHUNK_ROW_COUNT_MAXIMUM)
{
ereport(ERROR, (errmsg("chunk group row count limit out of range"),
errhint("chunk group row count limit must be between "
UINT64_FORMAT " and " UINT64_FORMAT,
(uint64) CHUNK_ROW_COUNT_MINIMUM,
(uint64) CHUNK_ROW_COUNT_MAXIMUM)));
}
ereport(DEBUG1,
(errmsg("updating chunk row count to %d", options.chunkRowCount)));
}
/* stripe_row_limit => not null */
if (!PG_ARGISNULL(2))
{
options.stripeRowCount = PG_GETARG_INT32(2);
if (options.stripeRowCount < STRIPE_ROW_COUNT_MINIMUM ||
options.stripeRowCount > STRIPE_ROW_COUNT_MAXIMUM)
{
ereport(ERROR, (errmsg("stripe row count limit out of range"),
errhint("stripe row count limit must be between "
UINT64_FORMAT " and " UINT64_FORMAT,
(uint64) STRIPE_ROW_COUNT_MINIMUM,
(uint64) STRIPE_ROW_COUNT_MAXIMUM)));
}
ereport(DEBUG1, (errmsg(
"updating stripe row count to " UINT64_FORMAT,
options.stripeRowCount)));
}
/* compression => not null */
if (!PG_ARGISNULL(3))
{
Name compressionName = PG_GETARG_NAME(3);
options.compressionType = ParseCompressionType(NameStr(*compressionName));
if (options.compressionType == COMPRESSION_TYPE_INVALID)
{
ereport(ERROR, (errmsg("unknown compression type for columnar table: %s",
quote_identifier(NameStr(*compressionName)))));
}
ereport(DEBUG1, (errmsg("updating compression to %s",
CompressionTypeStr(options.compressionType))));
}
/* compression_level => not null */
if (!PG_ARGISNULL(4))
{
options.compressionLevel = PG_GETARG_INT32(4);
if (options.compressionLevel < COMPRESSION_LEVEL_MIN ||
options.compressionLevel > COMPRESSION_LEVEL_MAX)
{
ereport(ERROR, (errmsg("compression level out of range"),
errhint("compression level must be between %d and %d",
COMPRESSION_LEVEL_MIN,
COMPRESSION_LEVEL_MAX)));
}
ereport(DEBUG1, (errmsg("updating compression level to %d",
options.compressionLevel)));
}
if (ColumnarTableSetOptions_hook != NULL)
{
ColumnarTableSetOptions_hook(relationId, options);
}
SetColumnarOptions(relationId, &options);
table_close(rel, NoLock);
PG_RETURN_VOID();
} }
/* /*
* alter_columnar_table_reset is a UDF exposed in postgres to reset the settings on a * alter_columnar_table_reset()
* columnar table. Calling this function on a non-columnar table gives an error.
* *
* sql syntax: * Deprecated in 11.1-1: should issue ALTER TABLE ... RESET instead. Function
* pg_catalog.alter_columnar_table_re * still available, but implemented in PL/pgSQL instead of C.
* teset(
* table_name regclass,
* chunk_group_row_limit bool DEFAULT FALSE,
* stripe_row_limit bool DEFAULT FALSE,
* compression bool DEFAULT FALSE)
* *
* All arguments except the table name are optional. The UDF is supposed to be called * C code is removed -- the symbol may still be required in some
* like: * upgrade/downgrade paths, but it should not be called.
* SELECT alter_columnar_table_set('table', compression => true);
*
* All options set to true will be reset to the default system value.
*/ */
PG_FUNCTION_INFO_V1(alter_columnar_table_reset); PG_FUNCTION_INFO_V1(alter_columnar_table_reset);
Datum Datum
alter_columnar_table_reset(PG_FUNCTION_ARGS) alter_columnar_table_reset(PG_FUNCTION_ARGS)
{ {
CheckCitusColumnarVersion(ERROR); elog(ERROR, "alter_columnar_table_reset is deprecated");
Oid relationId = PG_GETARG_OID(0);
Relation rel = table_open(relationId, AccessExclusiveLock); /* ALTER TABLE LOCK */
if (!IsColumnarTableAmTable(relationId))
{
ereport(ERROR, (errmsg("table %s is not a columnar table",
quote_identifier(RelationGetRelationName(rel)))));
}
if (!pg_class_ownercheck(relationId, GetUserId()))
{
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE,
get_rel_name(relationId));
}
ColumnarOptions options = { 0 };
if (!ReadColumnarOptions(relationId, &options))
{
ereport(ERROR, (errmsg("unable to read current options for table")));
}
/* chunk_group_row_limit => true */
if (!PG_ARGISNULL(1) && PG_GETARG_BOOL(1))
{
options.chunkRowCount = columnar_chunk_group_row_limit;
ereport(DEBUG1,
(errmsg("resetting chunk row count to %d", options.chunkRowCount)));
}
/* stripe_row_limit => true */
if (!PG_ARGISNULL(2) && PG_GETARG_BOOL(2))
{
options.stripeRowCount = columnar_stripe_row_limit;
ereport(DEBUG1,
(errmsg("resetting stripe row count to " UINT64_FORMAT,
options.stripeRowCount)));
}
/* compression => true */
if (!PG_ARGISNULL(3) && PG_GETARG_BOOL(3))
{
options.compressionType = columnar_compression;
ereport(DEBUG1, (errmsg("resetting compression to %s",
CompressionTypeStr(options.compressionType))));
}
/* compression_level => true */
if (!PG_ARGISNULL(4) && PG_GETARG_BOOL(4))
{
options.compressionLevel = columnar_compression_level;
ereport(DEBUG1, (errmsg("reseting compression level to %d",
columnar_compression_level)));
}
if (ColumnarTableSetOptions_hook != NULL)
{
ColumnarTableSetOptions_hook(relationId, options);
}
SetColumnarOptions(relationId, &options);
table_close(rel, NoLock);
PG_RETURN_VOID();
} }

View File

@ -6,18 +6,233 @@ ALTER EXTENSION citus_columnar ADD TABLE columnar.options;
ALTER EXTENSION citus_columnar ADD TABLE columnar.stripe; ALTER EXTENSION citus_columnar ADD TABLE columnar.stripe;
ALTER EXTENSION citus_columnar ADD TABLE columnar.chunk_group; ALTER EXTENSION citus_columnar ADD TABLE columnar.chunk_group;
ALTER EXTENSION citus_columnar ADD TABLE columnar.chunk; ALTER EXTENSION citus_columnar ADD TABLE columnar.chunk;
DO $proc$
BEGIN ALTER EXTENSION citus_columnar ADD FUNCTION columnar.columnar_handler;
-- columnar functions ALTER EXTENSION citus_columnar ADD ACCESS METHOD columnar;
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN ALTER EXTENSION citus_columnar ADD FUNCTION pg_catalog.alter_columnar_table_set;
EXECUTE $$ ALTER EXTENSION citus_columnar ADD FUNCTION pg_catalog.alter_columnar_table_reset;
ALTER EXTENSION citus_columnar ADD FUNCTION columnar.columnar_handler;
ALTER EXTENSION citus_columnar ADD ACCESS METHOD columnar;
ALTER EXTENSION citus_columnar ADD FUNCTION pg_catalog.alter_columnar_table_set;
ALTER EXTENSION citus_columnar ADD FUNCTION pg_catalog.alter_columnar_table_reset;
$$;
END IF;
END$proc$;
ALTER EXTENSION citus_columnar ADD FUNCTION citus_internal.upgrade_columnar_storage; ALTER EXTENSION citus_columnar ADD FUNCTION citus_internal.upgrade_columnar_storage;
ALTER EXTENSION citus_columnar ADD FUNCTION citus_internal.downgrade_columnar_storage; ALTER EXTENSION citus_columnar ADD FUNCTION citus_internal.downgrade_columnar_storage;
ALTER EXTENSION citus_columnar ADD FUNCTION citus_internal.columnar_ensure_am_depends_catalog; ALTER EXTENSION citus_columnar ADD FUNCTION citus_internal.columnar_ensure_am_depends_catalog;
CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_set(
table_name regclass,
chunk_group_row_limit int DEFAULT NULL,
stripe_row_limit int DEFAULT NULL,
compression name DEFAULT null,
compression_level int DEFAULT NULL)
RETURNS void
LANGUAGE plpgsql AS
$alter_columnar_table_set$
declare
noop BOOLEAN := true;
cmd TEXT := 'ALTER TABLE ' || table_name::text || ' SET (';
begin
if (chunk_group_row_limit is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.chunk_group_row_limit=' || chunk_group_row_limit;
noop := false;
end if;
if (stripe_row_limit is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.stripe_row_limit=' || stripe_row_limit;
noop := false;
end if;
if (compression is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression=' || compression;
noop := false;
end if;
if (compression_level is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression_level=' || compression_level;
noop := false;
end if;
cmd := cmd || ')';
if (not noop) then
execute cmd;
end if;
return;
end;
$alter_columnar_table_set$;
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_set(
table_name regclass,
chunk_group_row_limit int,
stripe_row_limit int,
compression name,
compression_level int)
IS 'set one or more options on a columnar table, when set to NULL no change is made';
CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_reset(
table_name regclass,
chunk_group_row_limit bool DEFAULT false,
stripe_row_limit bool DEFAULT false,
compression bool DEFAULT false,
compression_level bool DEFAULT false)
RETURNS void
LANGUAGE plpgsql AS
$alter_columnar_table_reset$
declare
noop BOOLEAN := true;
cmd TEXT := 'ALTER TABLE ' || table_name::text || ' RESET (';
begin
if (chunk_group_row_limit) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.chunk_group_row_limit';
noop := false;
end if;
if (stripe_row_limit) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.stripe_row_limit';
noop := false;
end if;
if (compression) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression';
noop := false;
end if;
if (compression_level) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression_level';
noop := false;
end if;
cmd := cmd || ')';
if (not noop) then
execute cmd;
end if;
return;
end;
$alter_columnar_table_reset$;
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_reset(
table_name regclass,
chunk_group_row_limit bool,
stripe_row_limit bool,
compression bool,
compression_level bool)
IS 'reset on or more options on a columnar table to the system defaults';
-- rename columnar schema to columnar_internal and tighten security
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA columnar FROM PUBLIC;
ALTER SCHEMA columnar RENAME TO columnar_internal;
REVOKE ALL PRIVILEGES ON SCHEMA columnar_internal FROM PUBLIC;
-- move citus_internal functions to columnar_internal
ALTER FUNCTION citus_internal.upgrade_columnar_storage(regclass) SET SCHEMA columnar_internal;
ALTER FUNCTION citus_internal.downgrade_columnar_storage(regclass) SET SCHEMA columnar_internal;
ALTER FUNCTION citus_internal.columnar_ensure_am_depends_catalog() SET SCHEMA columnar_internal;
-- create columnar schema with public usage privileges
CREATE SCHEMA columnar;
GRANT USAGE ON SCHEMA columnar TO PUBLIC;
-- update UDF to account for columnar_internal schema
CREATE OR REPLACE FUNCTION columnar_internal.columnar_ensure_am_depends_catalog()
RETURNS void
LANGUAGE plpgsql
SET search_path = pg_catalog
AS $func$
BEGIN
INSERT INTO pg_depend
WITH columnar_schema_members(relid) AS (
SELECT pg_class.oid AS relid FROM pg_class
WHERE relnamespace =
COALESCE(
(SELECT pg_namespace.oid FROM pg_namespace WHERE nspname = 'columnar_internal'),
(SELECT pg_namespace.oid FROM pg_namespace WHERE nspname = 'columnar')
)
AND relname IN ('chunk',
'chunk_group',
'chunk_group_pkey',
'chunk_pkey',
'options',
'options_pkey',
'storageid_seq',
'stripe',
'stripe_first_row_number_idx',
'stripe_pkey')
)
SELECT -- Define a dependency edge from "columnar table access method" ..
'pg_am'::regclass::oid as classid,
(select oid from pg_am where amname = 'columnar') as objid,
0 as objsubid,
-- ... to each object that is registered to pg_class and that lives
-- in "columnar" schema. That contains catalog tables, indexes
-- created on them and the sequences created in "columnar" schema.
--
-- Given the possibility of user might have created their own objects
-- in columnar schema, we explicitly specify list of objects that we
-- are interested in.
'pg_class'::regclass::oid as refclassid,
columnar_schema_members.relid as refobjid,
0 as refobjsubid,
'n' as deptype
FROM columnar_schema_members
-- Avoid inserting duplicate entries into pg_depend.
EXCEPT TABLE pg_depend;
END;
$func$;
COMMENT ON FUNCTION columnar_internal.columnar_ensure_am_depends_catalog()
IS 'internal function responsible for creating dependencies from columnar '
'table access method to the rel objects in columnar schema';
-- add utility function
CREATE FUNCTION columnar.get_storage_id(regclass) RETURNS bigint
LANGUAGE C STRICT
AS 'citus_columnar', $$columnar_relation_storageid$$;
-- create views for columnar table information
CREATE VIEW columnar.storage WITH (security_barrier) AS
SELECT c.oid::regclass AS relation,
columnar.get_storage_id(c.oid) AS storage_id
FROM pg_class c, pg_am am
WHERE c.relam = am.oid AND am.amname = 'columnar'
AND pg_has_role(c.relowner, 'USAGE');
COMMENT ON VIEW columnar.storage IS 'Columnar relation ID to storage ID mapping.';
GRANT SELECT ON columnar.storage TO PUBLIC;
CREATE VIEW columnar.options WITH (security_barrier) AS
SELECT regclass AS relation, chunk_group_row_limit,
stripe_row_limit, compression, compression_level
FROM columnar_internal.options o, pg_class c
WHERE o.regclass = c.oid
AND pg_has_role(c.relowner, 'USAGE');
COMMENT ON VIEW columnar.options
IS 'Columnar options for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.options TO PUBLIC;
CREATE VIEW columnar.stripe WITH (security_barrier) AS
SELECT relation, storage.storage_id, stripe_num, file_offset, data_length,
column_count, chunk_row_count, row_count, chunk_group_count, first_row_number
FROM columnar_internal.stripe stripe, columnar.storage storage
WHERE stripe.storage_id = storage.storage_id;
COMMENT ON VIEW columnar.stripe
IS 'Columnar stripe information for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.stripe TO PUBLIC;
CREATE VIEW columnar.chunk_group WITH (security_barrier) AS
SELECT relation, storage.storage_id, stripe_num, chunk_group_num, row_count
FROM columnar_internal.chunk_group cg, columnar.storage storage
WHERE cg.storage_id = storage.storage_id;
COMMENT ON VIEW columnar.chunk_group
IS 'Columnar chunk group information for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.chunk_group TO PUBLIC;
CREATE VIEW columnar.chunk WITH (security_barrier) AS
SELECT relation, storage.storage_id, stripe_num, attr_num, chunk_group_num,
minimum_value, maximum_value, value_stream_offset, value_stream_length,
exists_stream_offset, exists_stream_length, value_compression_type,
value_compression_level, value_decompressed_length, value_count
FROM columnar_internal.chunk chunk, columnar.storage storage
WHERE chunk.storage_id = storage.storage_id;
COMMENT ON VIEW columnar.chunk
IS 'Columnar chunk information for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.chunk TO PUBLIC;

View File

@ -134,9 +134,6 @@ IS 'reset on or more options on a columnar table to the system defaults';
END IF; END IF;
END$proc$; END$proc$;
-- add citus_internal schema
CREATE SCHEMA IF NOT EXISTS citus_internal;
-- (this function being dropped in 10.0.3)->#include "udfs/columnar_ensure_objects_exist/10.0-1.sql" -- (this function being dropped in 10.0.3)->#include "udfs/columnar_ensure_objects_exist/10.0-1.sql"
RESET search_path; RESET search_path;
@ -175,30 +172,6 @@ BEGIN
END; END;
$$; $$;
--#include "udfs/upgrade_columnar_storage/10.2-1.sql"
CREATE OR REPLACE FUNCTION citus_internal.upgrade_columnar_storage(rel regclass)
RETURNS VOID
STRICT
LANGUAGE c AS 'MODULE_PATHNAME', $$upgrade_columnar_storage$$;
COMMENT ON FUNCTION citus_internal.upgrade_columnar_storage(regclass)
IS 'function to upgrade the columnar storage, if necessary';
--#include "udfs/downgrade_columnar_storage/10.2-1.sql"
CREATE OR REPLACE FUNCTION citus_internal.downgrade_columnar_storage(rel regclass)
RETURNS VOID
STRICT
LANGUAGE c AS 'MODULE_PATHNAME', $$downgrade_columnar_storage$$;
COMMENT ON FUNCTION citus_internal.downgrade_columnar_storage(regclass)
IS 'function to downgrade the columnar storage, if necessary';
-- upgrade storage for all columnar relations
SELECT citus_internal.upgrade_columnar_storage(c.oid) FROM pg_class c, pg_am a
WHERE c.relam = a.oid AND amname = 'columnar';
-- columnar--10.2-1--10.2-2.sql -- columnar--10.2-1--10.2-2.sql
-- revoke read access for columnar.chunk from unprivileged -- revoke read access for columnar.chunk from unprivileged
@ -221,13 +194,164 @@ REVOKE SELECT ON columnar.chunk FROM PUBLIC;
-- columnar--10.2-3--10.2-4.sql -- columnar--10.2-3--10.2-4.sql
CREATE OR REPLACE FUNCTION citus_internal.columnar_ensure_am_depends_catalog()
-- columnar--11.0-2--11.1-1.sql
CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_set(
table_name regclass,
chunk_group_row_limit int DEFAULT NULL,
stripe_row_limit int DEFAULT NULL,
compression name DEFAULT null,
compression_level int DEFAULT NULL)
RETURNS void
LANGUAGE plpgsql AS
$alter_columnar_table_set$
declare
noop BOOLEAN := true;
cmd TEXT := 'ALTER TABLE ' || table_name::text || ' SET (';
begin
if (chunk_group_row_limit is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.chunk_group_row_limit=' || chunk_group_row_limit;
noop := false;
end if;
if (stripe_row_limit is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.stripe_row_limit=' || stripe_row_limit;
noop := false;
end if;
if (compression is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression=' || compression;
noop := false;
end if;
if (compression_level is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression_level=' || compression_level;
noop := false;
end if;
cmd := cmd || ')';
if (not noop) then
execute cmd;
end if;
return;
end;
$alter_columnar_table_set$;
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_set(
table_name regclass,
chunk_group_row_limit int,
stripe_row_limit int,
compression name,
compression_level int)
IS 'set one or more options on a columnar table, when set to NULL no change is made';
CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_reset(
table_name regclass,
chunk_group_row_limit bool DEFAULT false,
stripe_row_limit bool DEFAULT false,
compression bool DEFAULT false,
compression_level bool DEFAULT false)
RETURNS void
LANGUAGE plpgsql AS
$alter_columnar_table_reset$
declare
noop BOOLEAN := true;
cmd TEXT := 'ALTER TABLE ' || table_name::text || ' RESET (';
begin
if (chunk_group_row_limit) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.chunk_group_row_limit';
noop := false;
end if;
if (stripe_row_limit) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.stripe_row_limit';
noop := false;
end if;
if (compression) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression';
noop := false;
end if;
if (compression_level) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression_level';
noop := false;
end if;
cmd := cmd || ')';
if (not noop) then
execute cmd;
end if;
return;
end;
$alter_columnar_table_reset$;
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_reset(
table_name regclass,
chunk_group_row_limit bool,
stripe_row_limit bool,
compression bool,
compression_level bool)
IS 'reset on or more options on a columnar table to the system defaults';
-- rename columnar schema to columnar_internal and tighten security
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA columnar FROM PUBLIC;
ALTER SCHEMA columnar RENAME TO columnar_internal;
REVOKE ALL PRIVILEGES ON SCHEMA columnar_internal FROM PUBLIC;
-- create columnar schema with public usage privileges
CREATE SCHEMA columnar;
GRANT USAGE ON SCHEMA columnar TO PUBLIC;
--#include "udfs/upgrade_columnar_storage/10.2-1.sql"
CREATE OR REPLACE FUNCTION columnar_internal.upgrade_columnar_storage(rel regclass)
RETURNS VOID
STRICT
LANGUAGE c AS 'MODULE_PATHNAME', $$upgrade_columnar_storage$$;
COMMENT ON FUNCTION columnar_internal.upgrade_columnar_storage(regclass)
IS 'function to upgrade the columnar storage, if necessary';
--#include "udfs/downgrade_columnar_storage/10.2-1.sql"
CREATE OR REPLACE FUNCTION columnar_internal.downgrade_columnar_storage(rel regclass)
RETURNS VOID
STRICT
LANGUAGE c AS 'MODULE_PATHNAME', $$downgrade_columnar_storage$$;
COMMENT ON FUNCTION columnar_internal.downgrade_columnar_storage(regclass)
IS 'function to downgrade the columnar storage, if necessary';
-- update UDF to account for columnar_internal schema
CREATE OR REPLACE FUNCTION columnar_internal.columnar_ensure_am_depends_catalog()
RETURNS void RETURNS void
LANGUAGE plpgsql LANGUAGE plpgsql
SET search_path = pg_catalog SET search_path = pg_catalog
AS $func$ AS $func$
BEGIN BEGIN
INSERT INTO pg_depend INSERT INTO pg_depend
WITH columnar_schema_members(relid) AS (
SELECT pg_class.oid AS relid FROM pg_class
WHERE relnamespace =
COALESCE(
(SELECT pg_namespace.oid FROM pg_namespace WHERE nspname = 'columnar_internal'),
(SELECT pg_namespace.oid FROM pg_namespace WHERE nspname = 'columnar')
)
AND relname IN ('chunk',
'chunk_group',
'chunk_group_pkey',
'chunk_pkey',
'options',
'options_pkey',
'storageid_seq',
'stripe',
'stripe_first_row_number_idx',
'stripe_pkey')
)
SELECT -- Define a dependency edge from "columnar table access method" .. SELECT -- Define a dependency edge from "columnar table access method" ..
'pg_am'::regclass::oid as classid, 'pg_am'::regclass::oid as classid,
(select oid from pg_am where amname = 'columnar') as objid, (select oid from pg_am where amname = 'columnar') as objid,
@ -240,27 +364,72 @@ BEGIN
-- in columnar schema, we explicitly specify list of objects that we -- in columnar schema, we explicitly specify list of objects that we
-- are interested in. -- are interested in.
'pg_class'::regclass::oid as refclassid, 'pg_class'::regclass::oid as refclassid,
columnar_schema_members.relname::regclass::oid as refobjid, columnar_schema_members.relid as refobjid,
0 as refobjsubid, 0 as refobjsubid,
'n' as deptype 'n' as deptype
FROM (VALUES ('columnar.chunk'), FROM columnar_schema_members
('columnar.chunk_group'),
('columnar.chunk_group_pkey'),
('columnar.chunk_pkey'),
('columnar.options'),
('columnar.options_pkey'),
('columnar.storageid_seq'),
('columnar.stripe'),
('columnar.stripe_first_row_number_idx'),
('columnar.stripe_pkey')
) columnar_schema_members(relname)
-- Avoid inserting duplicate entries into pg_depend. -- Avoid inserting duplicate entries into pg_depend.
EXCEPT TABLE pg_depend; EXCEPT TABLE pg_depend;
END; END;
$func$; $func$;
COMMENT ON FUNCTION citus_internal.columnar_ensure_am_depends_catalog() COMMENT ON FUNCTION columnar_internal.columnar_ensure_am_depends_catalog()
IS 'internal function responsible for creating dependencies from columnar ' IS 'internal function responsible for creating dependencies from columnar '
'table access method to the rel objects in columnar schema'; 'table access method to the rel objects in columnar schema';
SELECT columnar_internal.columnar_ensure_am_depends_catalog();
-- add utility function
CREATE FUNCTION columnar.get_storage_id(regclass) RETURNS bigint
LANGUAGE C STRICT
AS 'citus_columnar', $$columnar_relation_storageid$$;
-- create views for columnar table information
CREATE VIEW columnar.storage WITH (security_barrier) AS
SELECT c.oid::regclass AS relation,
columnar.get_storage_id(c.oid) AS storage_id
FROM pg_class c, pg_am am
WHERE c.relam = am.oid AND am.amname = 'columnar'
AND pg_has_role(c.relowner, 'USAGE');
COMMENT ON VIEW columnar.storage IS 'Columnar relation ID to storage ID mapping.';
GRANT SELECT ON columnar.storage TO PUBLIC;
CREATE VIEW columnar.options WITH (security_barrier) AS
SELECT regclass AS relation, chunk_group_row_limit,
stripe_row_limit, compression, compression_level
FROM columnar_internal.options o, pg_class c
WHERE o.regclass = c.oid
AND pg_has_role(c.relowner, 'USAGE');
COMMENT ON VIEW columnar.options
IS 'Columnar options for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.options TO PUBLIC;
CREATE VIEW columnar.stripe WITH (security_barrier) AS
SELECT relation, storage.storage_id, stripe_num, file_offset, data_length,
column_count, chunk_row_count, row_count, chunk_group_count, first_row_number
FROM columnar_internal.stripe stripe, columnar.storage storage
WHERE stripe.storage_id = storage.storage_id;
COMMENT ON VIEW columnar.stripe
IS 'Columnar stripe information for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.stripe TO PUBLIC;
CREATE VIEW columnar.chunk_group WITH (security_barrier) AS
SELECT relation, storage.storage_id, stripe_num, chunk_group_num, row_count
FROM columnar_internal.chunk_group cg, columnar.storage storage
WHERE cg.storage_id = storage.storage_id;
COMMENT ON VIEW columnar.chunk_group
IS 'Columnar chunk group information for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.chunk_group TO PUBLIC;
CREATE VIEW columnar.chunk WITH (security_barrier) AS
SELECT relation, storage.storage_id, stripe_num, attr_num, chunk_group_num,
minimum_value, maximum_value, value_stream_offset, value_stream_length,
exists_stream_offset, exists_stream_length, value_compression_type,
value_compression_level, value_decompressed_length, value_count
FROM columnar_internal.chunk chunk, columnar.storage storage
WHERE chunk.storage_id = storage.storage_id;
COMMENT ON VIEW columnar.chunk
IS 'Columnar chunk information for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.chunk TO PUBLIC;
SELECT citus_internal.columnar_ensure_am_depends_catalog();

View File

@ -0,0 +1,71 @@
#include "udfs/alter_columnar_table_set/11.1-1.sql"
#include "udfs/alter_columnar_table_reset/11.1-1.sql"
-- rename columnar schema to columnar_internal and tighten security
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA columnar FROM PUBLIC;
ALTER SCHEMA columnar RENAME TO columnar_internal;
REVOKE ALL PRIVILEGES ON SCHEMA columnar_internal FROM PUBLIC;
-- create columnar schema with public usage privileges
CREATE SCHEMA columnar;
GRANT USAGE ON SCHEMA columnar TO PUBLIC;
-- update UDF to account for columnar_internal schema
#include "udfs/columnar_ensure_am_depends_catalog/11.1-1.sql"
-- add utility function
CREATE FUNCTION columnar.get_storage_id(regclass) RETURNS bigint
LANGUAGE C STRICT
AS 'citus_columnar', $$columnar_relation_storageid$$;
-- create views for columnar table information
CREATE VIEW columnar.storage WITH (security_barrier) AS
SELECT c.oid::regclass AS relation,
columnar.get_storage_id(c.oid) AS storage_id
FROM pg_class c, pg_am am
WHERE c.relam = am.oid AND am.amname = 'columnar'
AND pg_has_role(c.relowner, 'USAGE');
COMMENT ON VIEW columnar.storage IS 'Columnar relation ID to storage ID mapping.';
GRANT SELECT ON columnar.storage TO PUBLIC;
CREATE VIEW columnar.options WITH (security_barrier) AS
SELECT regclass AS relation, chunk_group_row_limit,
stripe_row_limit, compression, compression_level
FROM columnar_internal.options o, pg_class c
WHERE o.regclass = c.oid
AND pg_has_role(c.relowner, 'USAGE');
COMMENT ON VIEW columnar.options
IS 'Columnar options for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.options TO PUBLIC;
CREATE VIEW columnar.stripe WITH (security_barrier) AS
SELECT relation, storage.storage_id, stripe_num, file_offset, data_length,
column_count, chunk_row_count, row_count, chunk_group_count, first_row_number
FROM columnar_internal.stripe stripe, columnar.storage storage
WHERE stripe.storage_id = storage.storage_id;
COMMENT ON VIEW columnar.stripe
IS 'Columnar stripe information for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.stripe TO PUBLIC;
CREATE VIEW columnar.chunk_group WITH (security_barrier) AS
SELECT relation, storage.storage_id, stripe_num, chunk_group_num, row_count
FROM columnar_internal.chunk_group cg, columnar.storage storage
WHERE cg.storage_id = storage.storage_id;
COMMENT ON VIEW columnar.chunk_group
IS 'Columnar chunk group information for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.chunk_group TO PUBLIC;
CREATE VIEW columnar.chunk WITH (security_barrier) AS
SELECT relation, storage.storage_id, stripe_num, attr_num, chunk_group_num,
minimum_value, maximum_value, value_stream_offset, value_stream_length,
exists_stream_offset, exists_stream_length, value_compression_type,
value_compression_level, value_decompressed_length, value_count
FROM columnar_internal.chunk chunk, columnar.storage storage
WHERE chunk.storage_id = storage.storage_id;
COMMENT ON VIEW columnar.chunk
IS 'Columnar chunk information for tables on which the current user has ownership privileges.';
GRANT SELECT ON columnar.chunk TO PUBLIC;

View File

@ -1,3 +1,100 @@
CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_set(
table_name regclass,
chunk_group_row_limit int DEFAULT NULL,
stripe_row_limit int DEFAULT NULL,
compression name DEFAULT null,
compression_level int DEFAULT NULL)
RETURNS void
LANGUAGE C
AS 'MODULE_PATHNAME', 'alter_columnar_table_set';
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_set(
table_name regclass,
chunk_group_row_limit int,
stripe_row_limit int,
compression name,
compression_level int)
IS 'set one or more options on a columnar table, when set to NULL no change is made';
CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_reset(
table_name regclass,
chunk_group_row_limit bool DEFAULT false,
stripe_row_limit bool DEFAULT false,
compression bool DEFAULT false,
compression_level bool DEFAULT false)
RETURNS void
LANGUAGE C
AS 'MODULE_PATHNAME', 'alter_columnar_table_reset';
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_reset(
table_name regclass,
chunk_group_row_limit bool,
stripe_row_limit bool,
compression bool,
compression_level bool)
IS 'reset on or more options on a columnar table to the system defaults';
CREATE OR REPLACE FUNCTION columnar_internal.columnar_ensure_am_depends_catalog()
RETURNS void
LANGUAGE plpgsql
SET search_path = pg_catalog
AS $func$
BEGIN
INSERT INTO pg_depend
SELECT -- Define a dependency edge from "columnar table access method" ..
'pg_am'::regclass::oid as classid,
(select oid from pg_am where amname = 'columnar') as objid,
0 as objsubid,
-- ... to each object that is registered to pg_class and that lives
-- in "columnar" schema. That contains catalog tables, indexes
-- created on them and the sequences created in "columnar" schema.
--
-- Given the possibility of user might have created their own objects
-- in columnar schema, we explicitly specify list of objects that we
-- are interested in.
'pg_class'::regclass::oid as refclassid,
columnar_schema_members.relname::regclass::oid as refobjid,
0 as refobjsubid,
'n' as deptype
FROM (VALUES ('columnar.chunk'),
('columnar.chunk_group'),
('columnar.chunk_group_pkey'),
('columnar.chunk_pkey'),
('columnar.options'),
('columnar.options_pkey'),
('columnar.storageid_seq'),
('columnar.stripe'),
('columnar.stripe_first_row_number_idx'),
('columnar.stripe_pkey')
) columnar_schema_members(relname)
-- Avoid inserting duplicate entries into pg_depend.
EXCEPT TABLE pg_depend;
END;
$func$;
COMMENT ON FUNCTION columnar_internal.columnar_ensure_am_depends_catalog()
IS 'internal function responsible for creating dependencies from columnar '
'table access method to the rel objects in columnar schema';
DROP VIEW columnar.options;
DROP VIEW columnar.stripe;
DROP VIEW columnar.chunk_group;
DROP VIEW columnar.chunk;
DROP VIEW columnar.storage;
DROP FUNCTION columnar.get_storage_id(regclass);
DROP SCHEMA columnar;
-- move columnar_internal functions back to citus_internal
ALTER FUNCTION columnar_internal.upgrade_columnar_storage(regclass) SET SCHEMA citus_internal;
ALTER FUNCTION columnar_internal.downgrade_columnar_storage(regclass) SET SCHEMA citus_internal;
ALTER FUNCTION columnar_internal.columnar_ensure_am_depends_catalog() SET SCHEMA citus_internal;
ALTER SCHEMA columnar_internal RENAME TO columnar;
GRANT USAGE ON SCHEMA columnar TO PUBLIC;
GRANT SELECT ON columnar.options TO PUBLIC;
GRANT SELECT ON columnar.stripe TO PUBLIC;
GRANT SELECT ON columnar.chunk_group TO PUBLIC;
-- detach relations from citus_columnar -- detach relations from citus_columnar
ALTER EXTENSION citus_columnar DROP SCHEMA columnar; ALTER EXTENSION citus_columnar DROP SCHEMA columnar;
@ -8,18 +105,10 @@ ALTER EXTENSION citus_columnar DROP TABLE columnar.stripe;
ALTER EXTENSION citus_columnar DROP TABLE columnar.chunk_group; ALTER EXTENSION citus_columnar DROP TABLE columnar.chunk_group;
ALTER EXTENSION citus_columnar DROP TABLE columnar.chunk; ALTER EXTENSION citus_columnar DROP TABLE columnar.chunk;
DO $proc$ ALTER EXTENSION citus_columnar DROP FUNCTION columnar.columnar_handler;
BEGIN ALTER EXTENSION citus_columnar DROP ACCESS METHOD columnar;
-- columnar functions ALTER EXTENSION citus_columnar DROP FUNCTION pg_catalog.alter_columnar_table_set;
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN ALTER EXTENSION citus_columnar DROP FUNCTION pg_catalog.alter_columnar_table_reset;
EXECUTE $$
ALTER EXTENSION citus_columnar DROP FUNCTION columnar.columnar_handler;
ALTER EXTENSION citus_columnar DROP ACCESS METHOD columnar;
ALTER EXTENSION citus_columnar DROP FUNCTION pg_catalog.alter_columnar_table_set;
ALTER EXTENSION citus_columnar DROP FUNCTION pg_catalog.alter_columnar_table_reset;
$$;
END IF;
END$proc$;
-- functions under citus_internal for columnar -- functions under citus_internal for columnar
ALTER EXTENSION citus_columnar DROP FUNCTION citus_internal.upgrade_columnar_storage; ALTER EXTENSION citus_columnar DROP FUNCTION citus_internal.upgrade_columnar_storage;

View File

@ -0,0 +1,19 @@
#include "../udfs/alter_columnar_table_set/10.0-1.sql"
#include "../udfs/alter_columnar_table_reset/10.0-1.sql"
#include "../udfs/columnar_ensure_am_depends_catalog/10.2-4.sql"
DROP VIEW columnar.options;
DROP VIEW columnar.stripe;
DROP VIEW columnar.chunk_group;
DROP VIEW columnar.chunk;
DROP VIEW columnar.storage;
DROP FUNCTION columnar.get_storage_id(regclass);
DROP SCHEMA columnar;
ALTER SCHEMA columnar_internal RENAME TO columnar;
GRANT USAGE ON SCHEMA columnar TO PUBLIC;
GRANT SELECT ON columnar.options TO PUBLIC;
GRANT SELECT ON columnar.stripe TO PUBLIC;
GRANT SELECT ON columnar.chunk_group TO PUBLIC;

View File

@ -0,0 +1,48 @@
CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_reset(
table_name regclass,
chunk_group_row_limit bool DEFAULT false,
stripe_row_limit bool DEFAULT false,
compression bool DEFAULT false,
compression_level bool DEFAULT false)
RETURNS void
LANGUAGE plpgsql AS
$alter_columnar_table_reset$
declare
noop BOOLEAN := true;
cmd TEXT := 'ALTER TABLE ' || table_name::text || ' RESET (';
begin
if (chunk_group_row_limit) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.chunk_group_row_limit';
noop := false;
end if;
if (stripe_row_limit) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.stripe_row_limit';
noop := false;
end if;
if (compression) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression';
noop := false;
end if;
if (compression_level) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression_level';
noop := false;
end if;
cmd := cmd || ')';
if (not noop) then
execute cmd;
end if;
return;
end;
$alter_columnar_table_reset$;
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_reset(
table_name regclass,
chunk_group_row_limit bool,
stripe_row_limit bool,
compression bool,
compression_level bool)
IS 'reset on or more options on a columnar table to the system defaults';

View File

@ -5,8 +5,39 @@ CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_reset(
compression bool DEFAULT false, compression bool DEFAULT false,
compression_level bool DEFAULT false) compression_level bool DEFAULT false)
RETURNS void RETURNS void
LANGUAGE C LANGUAGE plpgsql AS
AS 'MODULE_PATHNAME', 'alter_columnar_table_reset'; $alter_columnar_table_reset$
declare
noop BOOLEAN := true;
cmd TEXT := 'ALTER TABLE ' || table_name::text || ' RESET (';
begin
if (chunk_group_row_limit) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.chunk_group_row_limit';
noop := false;
end if;
if (stripe_row_limit) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.stripe_row_limit';
noop := false;
end if;
if (compression) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression';
noop := false;
end if;
if (compression_level) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression_level';
noop := false;
end if;
cmd := cmd || ')';
if (not noop) then
execute cmd;
end if;
return;
end;
$alter_columnar_table_reset$;
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_reset( COMMENT ON FUNCTION pg_catalog.alter_columnar_table_reset(
table_name regclass, table_name regclass,

View File

@ -0,0 +1,48 @@
CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_set(
table_name regclass,
chunk_group_row_limit int DEFAULT NULL,
stripe_row_limit int DEFAULT NULL,
compression name DEFAULT null,
compression_level int DEFAULT NULL)
RETURNS void
LANGUAGE plpgsql AS
$alter_columnar_table_set$
declare
noop BOOLEAN := true;
cmd TEXT := 'ALTER TABLE ' || table_name::text || ' SET (';
begin
if (chunk_group_row_limit is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.chunk_group_row_limit=' || chunk_group_row_limit;
noop := false;
end if;
if (stripe_row_limit is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.stripe_row_limit=' || stripe_row_limit;
noop := false;
end if;
if (compression is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression=' || compression;
noop := false;
end if;
if (compression_level is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression_level=' || compression_level;
noop := false;
end if;
cmd := cmd || ')';
if (not noop) then
execute cmd;
end if;
return;
end;
$alter_columnar_table_set$;
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_set(
table_name regclass,
chunk_group_row_limit int,
stripe_row_limit int,
compression name,
compression_level int)
IS 'set one or more options on a columnar table, when set to NULL no change is made';

View File

@ -5,8 +5,39 @@ CREATE OR REPLACE FUNCTION pg_catalog.alter_columnar_table_set(
compression name DEFAULT null, compression name DEFAULT null,
compression_level int DEFAULT NULL) compression_level int DEFAULT NULL)
RETURNS void RETURNS void
LANGUAGE C LANGUAGE plpgsql AS
AS 'MODULE_PATHNAME', 'alter_columnar_table_set'; $alter_columnar_table_set$
declare
noop BOOLEAN := true;
cmd TEXT := 'ALTER TABLE ' || table_name::text || ' SET (';
begin
if (chunk_group_row_limit is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.chunk_group_row_limit=' || chunk_group_row_limit;
noop := false;
end if;
if (stripe_row_limit is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.stripe_row_limit=' || stripe_row_limit;
noop := false;
end if;
if (compression is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression=' || compression;
noop := false;
end if;
if (compression_level is not null) then
if (not noop) then cmd := cmd || ', '; end if;
cmd := cmd || 'columnar.compression_level=' || compression_level;
noop := false;
end if;
cmd := cmd || ')';
if (not noop) then
execute cmd;
end if;
return;
end;
$alter_columnar_table_set$;
COMMENT ON FUNCTION pg_catalog.alter_columnar_table_set( COMMENT ON FUNCTION pg_catalog.alter_columnar_table_set(
table_name regclass, table_name regclass,

View File

@ -0,0 +1,48 @@
CREATE OR REPLACE FUNCTION citus_internal.columnar_ensure_am_depends_catalog()
RETURNS void
LANGUAGE plpgsql
SET search_path = pg_catalog
AS $func$
BEGIN
INSERT INTO pg_depend
WITH columnar_schema_members(relid) AS (
SELECT pg_class.oid AS relid FROM pg_class
WHERE relnamespace =
COALESCE(
(SELECT pg_namespace.oid FROM pg_namespace WHERE nspname = 'columnar_internal'),
(SELECT pg_namespace.oid FROM pg_namespace WHERE nspname = 'columnar')
)
AND relname IN ('chunk',
'chunk_group',
'chunk_group_pkey',
'chunk_pkey',
'options',
'options_pkey',
'storageid_seq',
'stripe',
'stripe_first_row_number_idx',
'stripe_pkey')
)
SELECT -- Define a dependency edge from "columnar table access method" ..
'pg_am'::regclass::oid as classid,
(select oid from pg_am where amname = 'columnar') as objid,
0 as objsubid,
-- ... to each object that is registered to pg_class and that lives
-- in "columnar" schema. That contains catalog tables, indexes
-- created on them and the sequences created in "columnar" schema.
--
-- Given the possibility of user might have created their own objects
-- in columnar schema, we explicitly specify list of objects that we
-- are interested in.
'pg_class'::regclass::oid as refclassid,
columnar_schema_members.relid as refobjid,
0 as refobjsubid,
'n' as deptype
FROM columnar_schema_members
-- Avoid inserting duplicate entries into pg_depend.
EXCEPT TABLE pg_depend;
END;
$func$;
COMMENT ON FUNCTION citus_internal.columnar_ensure_am_depends_catalog()
IS 'internal function responsible for creating dependencies from columnar '
'table access method to the rel objects in columnar schema';

View File

@ -5,6 +5,24 @@ CREATE OR REPLACE FUNCTION citus_internal.columnar_ensure_am_depends_catalog()
AS $func$ AS $func$
BEGIN BEGIN
INSERT INTO pg_depend INSERT INTO pg_depend
WITH columnar_schema_members(relid) AS (
SELECT pg_class.oid AS relid FROM pg_class
WHERE relnamespace =
COALESCE(
(SELECT pg_namespace.oid FROM pg_namespace WHERE nspname = 'columnar_internal'),
(SELECT pg_namespace.oid FROM pg_namespace WHERE nspname = 'columnar')
)
AND relname IN ('chunk',
'chunk_group',
'chunk_group_pkey',
'chunk_pkey',
'options',
'options_pkey',
'storageid_seq',
'stripe',
'stripe_first_row_number_idx',
'stripe_pkey')
)
SELECT -- Define a dependency edge from "columnar table access method" .. SELECT -- Define a dependency edge from "columnar table access method" ..
'pg_am'::regclass::oid as classid, 'pg_am'::regclass::oid as classid,
(select oid from pg_am where amname = 'columnar') as objid, (select oid from pg_am where amname = 'columnar') as objid,
@ -17,20 +35,10 @@ BEGIN
-- in columnar schema, we explicitly specify list of objects that we -- in columnar schema, we explicitly specify list of objects that we
-- are interested in. -- are interested in.
'pg_class'::regclass::oid as refclassid, 'pg_class'::regclass::oid as refclassid,
columnar_schema_members.relname::regclass::oid as refobjid, columnar_schema_members.relid as refobjid,
0 as refobjsubid, 0 as refobjsubid,
'n' as deptype 'n' as deptype
FROM (VALUES ('columnar.chunk'), FROM columnar_schema_members
('columnar.chunk_group'),
('columnar.chunk_group_pkey'),
('columnar.chunk_pkey'),
('columnar.options'),
('columnar.options_pkey'),
('columnar.storageid_seq'),
('columnar.stripe'),
('columnar.stripe_first_row_number_idx'),
('columnar.stripe_pkey')
) columnar_schema_members(relname)
-- Avoid inserting duplicate entries into pg_depend. -- Avoid inserting duplicate entries into pg_depend.
EXCEPT TABLE pg_depend; EXCEPT TABLE pg_depend;
END; END;

View File

@ -0,0 +1 @@
requires = 'citus_columnar'

View File

@ -1,89 +0,0 @@
/*-------------------------------------------------------------------------
*
* aggregate.c
* Commands for distributing AGGREGATE statements.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/metadata/dependency.h"
#include "distributed/metadata_sync.h"
#include "distributed/metadata/distobject.h"
#include "distributed/multi_executor.h"
#include "nodes/parsenodes.h"
#include "utils/lsyscache.h"
/*
* PreprocessDefineAggregateStmt only qualifies the node with schema name.
* We will handle the rest in the Postprocess phase.
*/
List *
PreprocessDefineAggregateStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
QualifyTreeNode((Node *) node);
return NIL;
}
/*
* PostprocessDefineAggregateStmt actually creates the plan we need to execute for
* aggregate propagation.
* This is the downside of using the locally created aggregate to get the sql statement.
*
* If the aggregate depends on any non-distributed relation, Citus can not distribute it.
* In order to not to prevent users from creating local aggregates on the coordinator,
* a WARNING message will be sent to the user about the case instead of erroring out.
*
* Besides creating the plan we also make sure all (new) dependencies of the aggregate
* are created on all nodes.
*/
List *
PostprocessDefineAggregateStmt(Node *node, const char *queryString)
{
DefineStmt *stmt = castNode(DefineStmt, node);
if (!ShouldPropagate())
{
return NIL;
}
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
EnsureCoordinator();
EnsureSequentialMode(OBJECT_AGGREGATE);
/* If the aggregate has any unsupported dependency, create it locally */
DeferredErrorMessage *depError = DeferErrorIfHasUnsupportedDependency(&address);
if (depError != NULL)
{
RaiseDeferredError(depError, WARNING);
return NIL;
}
EnsureDependenciesExistOnAllNodes(&address);
List *commands = CreateFunctionDDLCommandsIdempotent(&address);
commands = lcons(DISABLE_DDL_PROPAGATION, commands);
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}

View File

@ -206,6 +206,7 @@ static char * CreateWorkerChangeSequenceDependencyCommand(char *sequenceSchemaNa
char *sourceName, char *sourceName,
char *targetSchemaName, char *targetSchemaName,
char *targetName); char *targetName);
static char * CreateMaterializedViewDDLCommand(Oid matViewOid);
static char * GetAccessMethodForMatViewIfExists(Oid viewOid); static char * GetAccessMethodForMatViewIfExists(Oid viewOid);
static bool WillRecreateForeignKeyToReferenceTable(Oid relationId, static bool WillRecreateForeignKeyToReferenceTable(Oid relationId,
CascadeToColocatedOption cascadeOption); CascadeToColocatedOption cascadeOption);
@ -216,6 +217,9 @@ PG_FUNCTION_INFO_V1(alter_distributed_table);
PG_FUNCTION_INFO_V1(alter_table_set_access_method); PG_FUNCTION_INFO_V1(alter_table_set_access_method);
PG_FUNCTION_INFO_V1(worker_change_sequence_dependency); PG_FUNCTION_INFO_V1(worker_change_sequence_dependency);
/* global variable keeping track of whether we are in a table type conversion function */
bool InTableTypeConversionFunctionCall = false;
/* /*
* undistribute_table gets a distributed table name and * undistribute_table gets a distributed table name and
@ -504,10 +508,16 @@ AlterTableSetAccessMethod(TableConversionParameters *params)
* *
* The function returns a TableConversionReturn object that can stores variables that * The function returns a TableConversionReturn object that can stores variables that
* can be used at the caller operations. * can be used at the caller operations.
*
* To be able to provide more meaningful messages while converting a table type,
* Citus keeps InTableTypeConversionFunctionCall flag. Don't forget to set it properly
* in case you add a new way to return from this function.
*/ */
TableConversionReturn * TableConversionReturn *
ConvertTable(TableConversionState *con) ConvertTable(TableConversionState *con)
{ {
InTableTypeConversionFunctionCall = true;
/* /*
* We undistribute citus local tables that are not chained with any reference * We undistribute citus local tables that are not chained with any reference
* tables via foreign keys at the end of the utility hook. * tables via foreign keys at the end of the utility hook.
@ -536,6 +546,7 @@ ConvertTable(TableConversionState *con)
* subgraph including itself, so return here. * subgraph including itself, so return here.
*/ */
SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys); SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys);
InTableTypeConversionFunctionCall = false;
return NULL; return NULL;
} }
char *newAccessMethod = con->accessMethod ? con->accessMethod : char *newAccessMethod = con->accessMethod ? con->accessMethod :
@ -701,7 +712,7 @@ ConvertTable(TableConversionState *con)
char *columnarOptionsSql = GetShardedTableDDLCommandColumnar(con->hashOfName, char *columnarOptionsSql = GetShardedTableDDLCommandColumnar(con->hashOfName,
context); context);
ExecuteQueryViaSPI(columnarOptionsSql, SPI_OK_SELECT); ExecuteQueryViaSPI(columnarOptionsSql, SPI_OK_UTILITY);
} }
con->newRelationId = get_relname_relid(con->tempName, con->schemaId); con->newRelationId = get_relname_relid(con->tempName, con->schemaId);
@ -820,6 +831,7 @@ ConvertTable(TableConversionState *con)
SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys); SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys);
InTableTypeConversionFunctionCall = false;
return ret; return ret;
} }
@ -1252,33 +1264,22 @@ GetViewCreationCommandsOfTable(Oid relationId)
Oid viewOid = InvalidOid; Oid viewOid = InvalidOid;
foreach_oid(viewOid, views) foreach_oid(viewOid, views)
{ {
Datum viewDefinitionDatum = DirectFunctionCall1(pg_get_viewdef,
ObjectIdGetDatum(viewOid));
char *viewDefinition = TextDatumGetCString(viewDefinitionDatum);
StringInfo query = makeStringInfo(); StringInfo query = makeStringInfo();
char *viewName = get_rel_name(viewOid);
char *schemaName = get_namespace_name(get_rel_namespace(viewOid));
char *qualifiedViewName = quote_qualified_identifier(schemaName, viewName);
bool isMatView = get_rel_relkind(viewOid) == RELKIND_MATVIEW;
/* here we need to get the access method of the view to recreate it */ /* See comments on CreateMaterializedViewDDLCommand for its limitations */
char *accessMethodName = GetAccessMethodForMatViewIfExists(viewOid); if (get_rel_relkind(viewOid) == RELKIND_MATVIEW)
appendStringInfoString(query, "CREATE ");
if (isMatView)
{ {
appendStringInfoString(query, "MATERIALIZED "); char *matViewCreateCommands = CreateMaterializedViewDDLCommand(viewOid);
appendStringInfoString(query, matViewCreateCommands);
}
else
{
char *viewCreateCommand = CreateViewDDLCommand(viewOid);
appendStringInfoString(query, viewCreateCommand);
} }
appendStringInfo(query, "VIEW %s ", qualifiedViewName); char *alterViewCommmand = AlterViewOwnerCommand(viewOid);
appendStringInfoString(query, alterViewCommmand);
if (accessMethodName)
{
appendStringInfo(query, "USING %s ", accessMethodName);
}
appendStringInfo(query, "AS %s", viewDefinition);
commands = lappend(commands, makeTableDDLCommandString(query->data)); commands = lappend(commands, makeTableDDLCommandString(query->data));
} }
@ -1287,6 +1288,64 @@ GetViewCreationCommandsOfTable(Oid relationId)
} }
/*
* CreateMaterializedViewDDLCommand creates the command to create materialized view.
* Note that this function doesn't support
* - Aliases
* - Storage parameters
* - Tablespace
* - WITH [NO] DATA
* options for the given materialized view. Parser functions for materialized views
* should be added to handle them.
*
* Related issue: https://github.com/citusdata/citus/issues/5968
*/
static char *
CreateMaterializedViewDDLCommand(Oid matViewOid)
{
StringInfo query = makeStringInfo();
char *viewName = get_rel_name(matViewOid);
char *schemaName = get_namespace_name(get_rel_namespace(matViewOid));
char *qualifiedViewName = quote_qualified_identifier(schemaName, viewName);
/* here we need to get the access method of the view to recreate it */
char *accessMethodName = GetAccessMethodForMatViewIfExists(matViewOid);
appendStringInfo(query, "CREATE MATERIALIZED VIEW %s ", qualifiedViewName);
if (accessMethodName)
{
appendStringInfo(query, "USING %s ", accessMethodName);
}
/*
* Set search_path to NIL so that all objects outside of pg_catalog will be
* schema-prefixed.
*/
OverrideSearchPath *overridePath = GetOverrideSearchPath(CurrentMemoryContext);
overridePath->schemas = NIL;
overridePath->addCatalog = true;
PushOverrideSearchPath(overridePath);
/*
* Push the transaction snapshot to be able to get vief definition with pg_get_viewdef
*/
PushActiveSnapshot(GetTransactionSnapshot());
Datum viewDefinitionDatum = DirectFunctionCall1(pg_get_viewdef,
ObjectIdGetDatum(matViewOid));
char *viewDefinition = TextDatumGetCString(viewDefinitionDatum);
PopActiveSnapshot();
PopOverrideSearchPath();
appendStringInfo(query, "AS %s", viewDefinition);
return query->data;
}
/* /*
* ReplaceTable replaces the source table with the target table. * ReplaceTable replaces the source table with the target table.
* It moves all the rows of the source table to target table with INSERT SELECT. * It moves all the rows of the source table to target table with INSERT SELECT.

View File

@ -17,6 +17,7 @@
#include "catalog/pg_proc.h" #include "catalog/pg_proc.h"
#include "commands/defrem.h" #include "commands/defrem.h"
#include "distributed/backend_data.h"
#include "distributed/citus_ruleutils.h" #include "distributed/citus_ruleutils.h"
#include "distributed/colocation_utils.h" #include "distributed/colocation_utils.h"
#include "distributed/commands.h" #include "distributed/commands.h"

View File

@ -15,6 +15,7 @@
#include "distributed/backend_data.h" #include "distributed/backend_data.h"
#include "distributed/metadata_cache.h" #include "distributed/metadata_cache.h"
#include "distributed/remote_commands.h"
#include "distributed/worker_manager.h" #include "distributed/worker_manager.h"
#include "lib/stringinfo.h" #include "lib/stringinfo.h"
#include "signal.h" #include "signal.h"
@ -111,18 +112,39 @@ CitusSignalBackend(uint64 globalPID, uint64 timeout, int sig)
#endif #endif
} }
StringInfo queryResult = makeStringInfo(); int connectionFlags = 0;
MultiConnection *connection = GetNodeConnection(connectionFlags,
workerNode->workerName,
workerNode->workerPort);
bool reportResultError = true; if (!SendRemoteCommand(connection, cancelQuery->data))
bool success = ExecuteRemoteQueryOrCommand(workerNode->workerName,
workerNode->workerPort, cancelQuery->data,
queryResult, reportResultError);
if (success && queryResult && strcmp(queryResult->data, "f") == 0)
{ {
/* if we cannot connect, we warn and report false */
ReportConnectionError(connection, WARNING);
return false;
}
bool raiseInterrupts = true;
PGresult *queryResult = GetRemoteCommandResult(connection, raiseInterrupts);
/* if remote node throws an error, we also throw an error */
if (!IsResponseOK(queryResult))
{
ReportResultError(connection, queryResult, ERROR);
}
StringInfo queryResultString = makeStringInfo();
bool success = EvaluateSingleQueryResult(connection, queryResult, queryResultString);
if (success && strcmp(queryResultString->data, "f") == 0)
{
/* worker node returned "f" */
success = false; success = false;
} }
PQclear(queryResult);
bool raiseErrors = false;
ClearResults(connection, raiseErrors);
return success; return success;
} }

View File

@ -10,6 +10,8 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include "pg_version_compat.h"
#include "access/htup_details.h" #include "access/htup_details.h"
#include "access/xact.h" #include "access/xact.h"
#include "catalog/pg_collation.h" #include "catalog/pg_collation.h"
@ -36,9 +38,6 @@
static char * CreateCollationDDLInternal(Oid collationId, Oid *collowner, static char * CreateCollationDDLInternal(Oid collationId, Oid *collowner,
char **quotedCollationName); char **quotedCollationName);
static List * FilterNameListForDistributedCollations(List *objects, bool missing_ok,
List **addresses);
static bool ShouldPropagateDefineCollationStmt(void);
/* /*
* GetCreateCollationDDLInternal returns a CREATE COLLATE sql string for the * GetCreateCollationDDLInternal returns a CREATE COLLATE sql string for the
@ -60,12 +59,30 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati
Form_pg_collation collationForm = (Form_pg_collation) GETSTRUCT(heapTuple); Form_pg_collation collationForm = (Form_pg_collation) GETSTRUCT(heapTuple);
char collprovider = collationForm->collprovider; char collprovider = collationForm->collprovider;
const char *collcollate = NameStr(collationForm->collcollate);
const char *collctype = NameStr(collationForm->collctype);
Oid collnamespace = collationForm->collnamespace; Oid collnamespace = collationForm->collnamespace;
const char *collname = NameStr(collationForm->collname); const char *collname = NameStr(collationForm->collname);
bool collisdeterministic = collationForm->collisdeterministic; bool collisdeterministic = collationForm->collisdeterministic;
#if PG_VERSION_NUM >= PG_VERSION_15
bool isnull;
Datum datum = SysCacheGetAttr(COLLOID, heapTuple, Anum_pg_collation_collcollate,
&isnull);
Assert(!isnull);
char *collcollate = TextDatumGetCString(datum);
datum = SysCacheGetAttr(COLLOID, heapTuple, Anum_pg_collation_collctype, &isnull);
Assert(!isnull);
char *collctype = TextDatumGetCString(datum);
#else
/*
* In versions before 15, collcollate and collctype were type "name". Use
* pstrdup() to match the interface of 15 so that we consistently free the
* result later.
*/
char *collcollate = pstrdup(NameStr(collationForm->collcollate));
char *collctype = pstrdup(NameStr(collationForm->collctype));
#endif
if (collowner != NULL) if (collowner != NULL)
{ {
*collowner = collationForm->collowner; *collowner = collationForm->collowner;
@ -103,6 +120,9 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati
quote_literal_cstr(collctype)); quote_literal_cstr(collctype));
} }
pfree(collcollate);
pfree(collctype);
if (!collisdeterministic) if (!collisdeterministic)
{ {
appendStringInfoString(&collationNameDef, ", deterministic = false"); appendStringInfoString(&collationNameDef, ", deterministic = false");
@ -162,267 +182,6 @@ AlterCollationOwnerObjectAddress(Node *node, bool missing_ok)
} }
/*
* FilterNameListForDistributedCollations takes a list of objects to delete.
* This list is filtered against the collations that are distributed.
*
* The original list will not be touched, a new list will be created with only the objects
* in there.
*
* objectAddresses is replaced with a list of object addresses for the filtered objects.
*/
static List *
FilterNameListForDistributedCollations(List *objects, bool missing_ok,
List **objectAddresses)
{
List *result = NIL;
*objectAddresses = NIL;
List *collName = NULL;
foreach_ptr(collName, objects)
{
Oid collOid = get_collation_oid(collName, true);
ObjectAddress collAddress = { 0 };
if (!OidIsValid(collOid))
{
continue;
}
ObjectAddressSet(collAddress, CollationRelationId, collOid);
if (IsObjectDistributed(&collAddress))
{
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
*address = collAddress;
*objectAddresses = lappend(*objectAddresses, address);
result = lappend(result, collName);
}
}
return result;
}
List *
PreprocessDropCollationStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
/*
* We swap the list of objects to remove during deparse so we need a reference back to
* the old list to put back
*/
List *distributedTypeAddresses = NIL;
if (!ShouldPropagate())
{
return NIL;
}
QualifyTreeNode((Node *) stmt);
List *oldCollations = stmt->objects;
List *distributedCollations =
FilterNameListForDistributedCollations(oldCollations, stmt->missing_ok,
&distributedTypeAddresses);
if (list_length(distributedCollations) <= 0)
{
/* no distributed types to drop */
return NIL;
}
/*
* managing collations can only be done on the coordinator if ddl propagation is on. when
* it is off we will never get here. MX workers don't have a notion of distributed
* collations, so we block the call.
*/
EnsureCoordinator();
/*
* remove the entries for the distributed objects on dropping
*/
ObjectAddress *addressItem = NULL;
foreach_ptr(addressItem, distributedTypeAddresses)
{
UnmarkObjectDistributed(addressItem);
}
/*
* temporary swap the lists of objects to delete with the distributed objects and
* deparse to an executable sql statement for the workers
*/
stmt->objects = distributedCollations;
char *dropStmtSql = DeparseTreeNode((Node *) stmt);
stmt->objects = oldCollations;
EnsureSequentialMode(OBJECT_COLLATION);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterCollationOwnerStmt is called for change of ownership of collations
* before the ownership is changed on the local instance.
*
* If the type for which the owner is changed is distributed we execute the change on all
* the workers to keep the type in sync across the cluster.
*/
List *
PreprocessAlterCollationOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_COLLATION);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
EnsureCoordinator();
QualifyTreeNode((Node *) stmt);
char *sql = DeparseTreeNode((Node *) stmt);
EnsureSequentialMode(OBJECT_COLLATION);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterCollationOwnerStmt is invoked after the owner has been changed locally.
* Since changing the owner could result in new dependencies being found for this object
* we re-ensure all the dependencies for the collation do exist.
*
* This is solely to propagate the new owner (and all its dependencies) if it was not
* already distributed in the cluster.
*/
List *
PostprocessAlterCollationOwnerStmt(Node *node, const char *queryString)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_COLLATION);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&collationAddress);
return NIL;
}
/*
* PreprocessRenameCollationStmt is called when the user is renaming the collation. The invocation happens
* before the statement is applied locally.
*
* As the collation already exists we have access to the ObjectAddress for the collation, this is
* used to check if the collation is distributed. If the collation is distributed the rename is
* executed on all the workers to keep the collation in sync across the cluster.
*/
List *
PreprocessRenameCollationStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
EnsureCoordinator();
/* fully qualify */
QualifyTreeNode((Node *) stmt);
/* deparse sql*/
char *renameStmtSql = DeparseTreeNode((Node *) stmt);
EnsureSequentialMode(OBJECT_COLLATION);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) renameStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterCollationSchemaStmt is executed before the statement is applied to the local
* postgres instance.
*
* In this stage we can prepare the commands that need to be run on all workers.
*/
List *
PreprocessAlterCollationSchemaStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_COLLATION);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
EnsureCoordinator();
QualifyTreeNode((Node *) stmt);
char *sql = DeparseTreeNode((Node *) stmt);
EnsureSequentialMode(OBJECT_COLLATION);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterCollationSchemaStmt is executed after the change has been applied locally, we
* can now use the new dependencies of the type to ensure all its dependencies exist on
* the workers before we apply the commands remotely.
*/
List *
PostprocessAlterCollationSchemaStmt(Node *node, const char *queryString)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_COLLATION);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
/* dependencies have changed (schema) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&collationAddress);
return NIL;
}
/* /*
* RenameCollationStmtObjectAddress returns the ObjectAddress of the type that is the object * RenameCollationStmtObjectAddress returns the ObjectAddress of the type that is the object
* of the RenameStmt. Errors if missing_ok is false. * of the RenameStmt. Errors if missing_ok is false.
@ -500,7 +259,7 @@ GenerateBackupNameForCollationCollision(const ObjectAddress *address)
return NULL; return NULL;
} }
Form_pg_collation collationForm = (Form_pg_collation) GETSTRUCT(collationTuple); Form_pg_collation collationForm = (Form_pg_collation) GETSTRUCT(collationTuple);
Value *namespace = makeString(get_namespace_name(collationForm->collnamespace)); String *namespace = makeString(get_namespace_name(collationForm->collnamespace));
ReleaseSysCache(collationTuple); ReleaseSysCache(collationTuple);
while (true) while (true)
@ -544,89 +303,3 @@ DefineCollationStmtObjectAddress(Node *node, bool missing_ok)
return address; return address;
} }
/*
* PreprocessDefineCollationStmt executed before the collation has been
* created locally to ensure that if the collation create statement will
* be propagated, the node is a coordinator node
*/
List *
PreprocessDefineCollationStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
Assert(castNode(DefineStmt, node)->kind == OBJECT_COLLATION);
if (!ShouldPropagateDefineCollationStmt())
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_COLLATION);
return NIL;
}
/*
* PostprocessDefineCollationStmt executed after the collation has been
* created locally and before we create it on the worker nodes.
* As we now have access to ObjectAddress of the collation that is just
* created, we can mark it as distributed to make sure that its
* dependencies exist on all nodes.
*/
List *
PostprocessDefineCollationStmt(Node *node, const char *queryString)
{
Assert(castNode(DefineStmt, node)->kind == OBJECT_COLLATION);
if (!ShouldPropagateDefineCollationStmt())
{
return NIL;
}
ObjectAddress collationAddress =
DefineCollationStmtObjectAddress(node, false);
DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(
&collationAddress);
if (errMsg != NULL)
{
RaiseDeferredError(errMsg, WARNING);
return NIL;
}
EnsureDependenciesExistOnAllNodes(&collationAddress);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make1(DISABLE_DDL_PROPAGATION);
commands = list_concat(commands, CreateCollationDDLsIdempotent(
collationAddress.objectId));
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* ShouldPropagateDefineCollationStmt checks if collation define
* statement should be propagated. Don't propagate if:
* - metadata syncing if off
* - create statement should be propagated according the the ddl propagation policy
*/
static bool
ShouldPropagateDefineCollationStmt()
{
if (!ShouldPropagate())
{
return false;
}
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return false;
}
return true;
}

View File

@ -0,0 +1,274 @@
/*-------------------------------------------------------------------------
*
* common.c
*
* Most of the object propagation code consists of mostly the same
* operations, varying slightly in parameters passed around. This
* file contains most of the reusable logic in object propagation.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/objectaddress.h"
#include "nodes/parsenodes.h"
#include "tcop/utility.h"
#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/metadata_sync.h"
#include "distributed/metadata/dependency.h"
#include "distributed/metadata/distobject.h"
#include "distributed/multi_executor.h"
#include "distributed/worker_transaction.h"
/*
* PostprocessCreateDistributedObjectFromCatalogStmt is a common function that can be used
* for most objects during their creation phase. After the creation has happened locally
* this function creates idempotent statements to recreate the object addressed by the
* ObjectAddress of resolved from the creation statement.
*
* Since object already need to be able to create idempotent creation sql to support
* scaleout operations we can reuse this logic during the initial creation of the objects
* to reduce the complexity of implementation of new DDL commands.
*/
List *
PostprocessCreateDistributedObjectFromCatalogStmt(Node *stmt, const char *queryString)
{
const DistributeObjectOps *ops = GetDistributeObjectOps(stmt);
Assert(ops != NULL);
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
if (ops->featureFlag && *ops->featureFlag == false)
{
/* not propagating when a configured feature flag is turned off by the user */
return NIL;
}
ObjectAddress address = GetObjectAddressFromParseTree(stmt, false);
EnsureCoordinator();
EnsureSequentialMode(ops->objectType);
/* If the object has any unsupported dependency warn, and only create locally */
DeferredErrorMessage *depError = DeferErrorIfHasUnsupportedDependency(&address);
if (depError != NULL)
{
RaiseDeferredError(depError, WARNING);
return NIL;
}
EnsureDependenciesExistOnAllNodes(&address);
List *commands = GetDependencyCreateDDLCommands(&address);
commands = lcons(DISABLE_DDL_PROPAGATION, commands);
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterDistributedObjectStmt handles any updates to distributed objects by
* creating the fully qualified sql to apply to all workers after checking all
* predconditions that apply to propagating changes.
*
* Preconditions are (in order):
* - not in a CREATE/ALTER EXTENSION code block
* - citus.enable_metadata_sync is turned on
* - object being altered is distributed
* - any object specific feature flag is turned on when a feature flag is available
*
* Once we conclude to propagate the changes to the workers we make sure that the command
* has been executed on the coordinator and force any ongoing transaction to run in
* sequential mode. If any of these steps fail we raise an error to inform the user.
*
* Lastly we recreate a fully qualified version of the original sql and prepare the tasks
* to send these sql commands to the workers. These tasks include instructions to prevent
* recursion of propagation with Citus' MX functionality.
*/
List *
PreprocessAlterDistributedObjectStmt(Node *stmt, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
const DistributeObjectOps *ops = GetDistributeObjectOps(stmt);
Assert(ops != NULL);
ObjectAddress address = GetObjectAddressFromParseTree(stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
if (ops->featureFlag && *ops->featureFlag == false)
{
/* not propagating when a configured feature flag is turned off by the user */
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(ops->objectType);
QualifyTreeNode(stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterDistributedObjectStmt is the counter part of
* PreprocessAlterDistributedObjectStmt that should be executed after the object has been
* changed locally.
*
* We perform the same precondition checks as before to skip this operation if any of the
* failed during preprocessing. Since we already raised an error on other checks we don't
* have to repeat them here, as they will never fail during postprocessing.
*
* When objects get altered they can start depending on undistributed objects. Now that
* the objects has been changed locally we can find these new dependencies and make sure
* they get created on the workers before we send the command list to the workers.
*/
List *
PostprocessAlterDistributedObjectStmt(Node *stmt, const char *queryString)
{
const DistributeObjectOps *ops = GetDistributeObjectOps(stmt);
Assert(ops != NULL);
ObjectAddress address = GetObjectAddressFromParseTree(stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
if (ops->featureFlag && *ops->featureFlag == false)
{
/* not propagating when a configured feature flag is turned off by the user */
return NIL;
}
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* PreprocessDropDistributedObjectStmt is a general purpose hook that can propagate any
* DROP statement.
*
* DROP statements are one of the few DDL statements that can work on many different
* objects at once. Instead of resolving just one ObjectAddress and check it is
* distributed we will need to lookup many different object addresses. Only if an object
* was _not_ distributed we will need to remove it from the list of objects before we
* recreate the sql statement.
*
* Given that we actually _do_ need to drop them locally we can't simply remove them from
* the object list. Instead we create a new list where we only add distributed objects to.
* Before we recreate the sql statement we put this list on the drop statement, so that
* the SQL created will only contain the objects that are actually distributed in the
* cluster. After we have the SQL we restore the old list so that all objects get deleted
* locally.
*
* The reason we need to go through all this effort is taht we can't resolve the object
* addresses anymore after the objects have been removed locally. Meaning during the
* postprocessing we cannot understand which objects were distributed to begin with.
*/
List *
PreprocessDropDistributedObjectStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
/*
* We swap the list of objects to remove during deparse so we need a reference back to
* the old list to put back
*/
List *originalObjects = stmt->objects;
if (!ShouldPropagate())
{
return NIL;
}
QualifyTreeNode(node);
List *distributedObjects = NIL;
List *distributedObjectAddresses = NIL;
Node *object = NULL;
foreach_ptr(object, stmt->objects)
{
/* TODO understand if the lock should be sth else */
Relation rel = NULL; /* not used, but required to pass to get_object_address */
ObjectAddress address = get_object_address(stmt->removeType, object, &rel,
AccessShareLock, stmt->missing_ok);
if (IsObjectDistributed(&address))
{
ObjectAddress *addressPtr = palloc0(sizeof(ObjectAddress));
*addressPtr = address;
distributedObjects = lappend(distributedObjects, object);
distributedObjectAddresses = lappend(distributedObjectAddresses, addressPtr);
}
}
if (list_length(distributedObjects) <= 0)
{
/* no distributed objects to drop */
return NIL;
}
/*
* managing objects can only be done on the coordinator if ddl propagation is on. when
* it is off we will never get here. MX workers don't have a notion of distributed
* types, so we block the call.
*/
EnsureCoordinator();
/*
* remove the entries for the distributed objects on dropping
*/
ObjectAddress *address = NULL;
foreach_ptr(address, distributedObjectAddresses)
{
UnmarkObjectDistributed(address);
}
/*
* temporary swap the lists of objects to delete with the distributed objects and
* deparse to an executable sql statement for the workers
*/
stmt->objects = distributedObjects;
char *dropStmtSql = DeparseTreeNode((Node *) stmt);
stmt->objects = originalObjects;
EnsureSequentialMode(stmt->removeType);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}

View File

@ -36,75 +36,6 @@ static Oid get_database_owner(Oid db_oid);
bool EnableAlterDatabaseOwner = false; bool EnableAlterDatabaseOwner = false;
/*
* PreprocessAlterDatabaseOwnerStmt is called during the utility hook before the alter
* command is applied locally on the coordinator. This will verify if the command needs to
* be propagated to the workers and if so prepares a list of ddl commands to execute.
*/
List *
PreprocessAlterDatabaseOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_DATABASE);
ObjectAddress typeAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&typeAddress))
{
return NIL;
}
if (!EnableAlterDatabaseOwner)
{
/* don't propagate if GUC is turned off */
return NIL;
}
EnsureCoordinator();
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
EnsureSequentialMode(OBJECT_DATABASE);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterDatabaseOwnerStmt is called during the utility hook after the alter
* database command has been applied locally.
*
* Its main purpose is to propagate the newly formed dependencies onto the nodes before
* applying the change of owner of the databse. This ensures, for systems that have role
* management, that the roles will be created before applying the alter owner command.
*/
List *
PostprocessAlterDatabaseOwnerStmt(Node *node, const char *queryString)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_DATABASE);
ObjectAddress typeAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&typeAddress))
{
return NIL;
}
if (!EnableAlterDatabaseOwner)
{
/* don't propagate if GUC is turned off */
return NIL;
}
EnsureDependenciesExistOnAllNodes(&typeAddress);
return NIL;
}
/* /*
* AlterDatabaseOwnerObjectAddress returns the ObjectAddress of the database that is the * AlterDatabaseOwnerObjectAddress returns the ObjectAddress of the database that is the
* object of the AlterOwnerStmt. Errors if missing_ok is false. * object of the AlterOwnerStmt. Errors if missing_ok is false.
@ -115,7 +46,7 @@ AlterDatabaseOwnerObjectAddress(Node *node, bool missing_ok)
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_DATABASE); Assert(stmt->objectType == OBJECT_DATABASE);
Oid databaseOid = get_database_oid(strVal((Value *) stmt->object), missing_ok); Oid databaseOid = get_database_oid(strVal((String *) stmt->object), missing_ok);
ObjectAddress address = { 0 }; ObjectAddress address = { 0 };
ObjectAddressSet(address, DatabaseRelationId, databaseOid); ObjectAddressSet(address, DatabaseRelationId, databaseOid);

View File

@ -34,7 +34,6 @@ typedef bool (*AddressPredicate)(const ObjectAddress *);
static void EnsureDependenciesCanBeDistributed(const ObjectAddress *relationAddress); static void EnsureDependenciesCanBeDistributed(const ObjectAddress *relationAddress);
static void ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress); static void ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress);
static int ObjectAddressComparator(const void *a, const void *b); static int ObjectAddressComparator(const void *a, const void *b);
static List * GetDependencyCreateDDLCommands(const ObjectAddress *dependency);
static List * FilterObjectAddressListByPredicate(List *objectAddressList, static List * FilterObjectAddressListByPredicate(List *objectAddressList,
AddressPredicate predicate); AddressPredicate predicate);
@ -166,11 +165,28 @@ EnsureDependenciesCanBeDistributed(const ObjectAddress *objectAddress)
/* /*
* ErrorIfCircularDependencyExists checks whether given object has circular dependency * ErrorIfCircularDependencyExists is a wrapper around
* with itself via existing objects of pg_dist_object. * DeferErrorIfCircularDependencyExists(), and throws error
* if circular dependency exists.
*/ */
static void static void
ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress) ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress)
{
DeferredErrorMessage *depError =
DeferErrorIfCircularDependencyExists(objectAddress);
if (depError != NULL)
{
RaiseDeferredError(depError, ERROR);
}
}
/*
* DeferErrorIfCircularDependencyExists checks whether given object has
* circular dependency with itself via existing objects of pg_dist_object.
*/
DeferredErrorMessage *
DeferErrorIfCircularDependencyExists(const ObjectAddress *objectAddress)
{ {
List *dependencies = GetAllSupportedDependenciesForObject(objectAddress); List *dependencies = GetAllSupportedDependenciesForObject(objectAddress);
@ -189,13 +205,18 @@ ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress)
objectDescription = getObjectDescription(objectAddress); objectDescription = getObjectDescription(objectAddress);
#endif #endif
ereport(ERROR, (errmsg("Citus can not handle circular dependencies " StringInfo detailInfo = makeStringInfo();
"between distributed objects"), appendStringInfo(detailInfo, "\"%s\" circularly depends itself, resolve "
errdetail("\"%s\" circularly depends itself, resolve " "circular dependency first", objectDescription);
"circular dependency first",
objectDescription))); return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
"Citus can not handle circular dependencies "
"between distributed objects", detailInfo->data,
NULL);
} }
} }
return NULL;
} }
@ -289,7 +310,7 @@ GetDistributableDependenciesForObject(const ObjectAddress *target)
* GetDependencyCreateDDLCommands returns a list (potentially empty or NIL) of ddl * GetDependencyCreateDDLCommands returns a list (potentially empty or NIL) of ddl
* commands to execute on a worker to create the object. * commands to execute on a worker to create the object.
*/ */
static List * List *
GetDependencyCreateDDLCommands(const ObjectAddress *dependency) GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
{ {
switch (getObjectClass(dependency)) switch (getObjectClass(dependency))
@ -349,6 +370,14 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
return DDLCommandsForSequence(dependency->objectId, sequenceOwnerName); return DDLCommandsForSequence(dependency->objectId, sequenceOwnerName);
} }
if (relKind == RELKIND_VIEW)
{
char *createViewCommand = CreateViewDDLCommand(dependency->objectId);
char *alterViewOwnerCommand = AlterViewOwnerCommand(dependency->objectId);
return list_make2(createViewCommand, alterViewOwnerCommand);
}
/* if this relation is not supported, break to the error at the end */ /* if this relation is not supported, break to the error at the end */
break; break;
} }

View File

@ -16,6 +16,7 @@
#include "distributed/deparser.h" #include "distributed/deparser.h"
#include "distributed/pg_version_constants.h" #include "distributed/pg_version_constants.h"
#include "distributed/version_compat.h" #include "distributed/version_compat.h"
#include "distributed/commands/utility_hook.h"
static DistributeObjectOps NoDistributeOps = { static DistributeObjectOps NoDistributeOps = {
.deparse = NULL, .deparse = NULL,
@ -28,31 +29,34 @@ static DistributeObjectOps NoDistributeOps = {
static DistributeObjectOps Aggregate_AlterObjectSchema = { static DistributeObjectOps Aggregate_AlterObjectSchema = {
.deparse = DeparseAlterFunctionSchemaStmt, .deparse = DeparseAlterFunctionSchemaStmt,
.qualify = QualifyAlterFunctionSchemaStmt, .qualify = QualifyAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterFunctionSchemaStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterFunctionSchemaStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionSchemaStmtObjectAddress, .address = AlterFunctionSchemaStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Aggregate_AlterOwner = { static DistributeObjectOps Aggregate_AlterOwner = {
.deparse = DeparseAlterFunctionOwnerStmt, .deparse = DeparseAlterFunctionOwnerStmt,
.qualify = QualifyAlterFunctionOwnerStmt, .qualify = QualifyAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterFunctionOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterFunctionOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionOwnerObjectAddress, .address = AlterFunctionOwnerObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Aggregate_Define = { static DistributeObjectOps Aggregate_Define = {
.deparse = NULL, .deparse = NULL,
.qualify = QualifyDefineAggregateStmt, .qualify = QualifyDefineAggregateStmt,
.preprocess = PreprocessDefineAggregateStmt, .preprocess = NULL,
.postprocess = PostprocessDefineAggregateStmt, .postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_AGGREGATE,
.address = DefineAggregateStmtObjectAddress, .address = DefineAggregateStmtObjectAddress,
.markDistributed = true, .markDistributed = true,
}; };
static DistributeObjectOps Aggregate_Drop = { static DistributeObjectOps Aggregate_Drop = {
.deparse = DeparseDropFunctionStmt, .deparse = DeparseDropFunctionStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessDropFunctionStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -60,16 +64,18 @@ static DistributeObjectOps Aggregate_Drop = {
static DistributeObjectOps Aggregate_Rename = { static DistributeObjectOps Aggregate_Rename = {
.deparse = DeparseRenameFunctionStmt, .deparse = DeparseRenameFunctionStmt,
.qualify = QualifyRenameFunctionStmt, .qualify = QualifyRenameFunctionStmt,
.preprocess = PreprocessRenameFunctionStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_FUNCTION,
.address = RenameFunctionStmtObjectAddress, .address = RenameFunctionStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Any_AlterEnum = { static DistributeObjectOps Any_AlterEnum = {
.deparse = DeparseAlterEnumStmt, .deparse = DeparseAlterEnumStmt,
.qualify = QualifyAlterEnumStmt, .qualify = QualifyAlterEnumStmt,
.preprocess = PreprocessAlterEnumStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_TYPE,
.address = AlterEnumStmtObjectAddress, .address = AlterEnumStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -92,9 +98,10 @@ static DistributeObjectOps Any_AlterExtensionContents = {
static DistributeObjectOps Any_AlterForeignServer = { static DistributeObjectOps Any_AlterForeignServer = {
.deparse = DeparseAlterForeignServerStmt, .deparse = DeparseAlterForeignServerStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessAlterForeignServerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .objectType = OBJECT_FOREIGN_SERVER,
.address = AlterForeignServerStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Any_AlterFunction = { static DistributeObjectOps Any_AlterFunction = {
@ -148,24 +155,29 @@ static DistributeObjectOps Any_Cluster = {
static DistributeObjectOps Any_CompositeType = { static DistributeObjectOps Any_CompositeType = {
.deparse = DeparseCompositeTypeStmt, .deparse = DeparseCompositeTypeStmt,
.qualify = QualifyCompositeTypeStmt, .qualify = QualifyCompositeTypeStmt,
.preprocess = PreprocessCompositeTypeStmt, .preprocess = NULL,
.postprocess = PostprocessCompositeTypeStmt, .postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_TYPE,
.featureFlag = &EnableCreateTypePropagation,
.address = CompositeTypeStmtObjectAddress, .address = CompositeTypeStmtObjectAddress,
.markDistributed = true, .markDistributed = true,
}; };
static DistributeObjectOps Any_CreateDomain = { static DistributeObjectOps Any_CreateDomain = {
.deparse = DeparseCreateDomainStmt, .deparse = DeparseCreateDomainStmt,
.qualify = QualifyCreateDomainStmt, .qualify = QualifyCreateDomainStmt,
.preprocess = PreprocessCreateDomainStmt, .preprocess = NULL,
.postprocess = PostprocessCreateDomainStmt, .postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_DOMAIN,
.address = CreateDomainStmtObjectAddress, .address = CreateDomainStmtObjectAddress,
.markDistributed = true, .markDistributed = true,
}; };
static DistributeObjectOps Any_CreateEnum = { static DistributeObjectOps Any_CreateEnum = {
.deparse = DeparseCreateEnumStmt, .deparse = DeparseCreateEnumStmt,
.qualify = QualifyCreateEnumStmt, .qualify = QualifyCreateEnumStmt,
.preprocess = PreprocessCreateEnumStmt, .preprocess = NULL,
.postprocess = PostprocessCreateEnumStmt, .postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_TYPE,
.featureFlag = &EnableCreateTypePropagation,
.address = CreateEnumStmtObjectAddress, .address = CreateEnumStmtObjectAddress,
.markDistributed = true, .markDistributed = true,
}; };
@ -185,6 +197,14 @@ static DistributeObjectOps Any_CreateFunction = {
.address = CreateFunctionStmtObjectAddress, .address = CreateFunctionStmtObjectAddress,
.markDistributed = true, .markDistributed = true,
}; };
static DistributeObjectOps Any_View = {
.deparse = NULL,
.qualify = NULL,
.preprocess = PreprocessViewStmt,
.postprocess = PostprocessViewStmt,
.address = ViewStmtObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps Any_CreatePolicy = { static DistributeObjectOps Any_CreatePolicy = {
.deparse = NULL, .deparse = NULL,
.qualify = NULL, .qualify = NULL,
@ -196,8 +216,9 @@ static DistributeObjectOps Any_CreatePolicy = {
static DistributeObjectOps Any_CreateForeignServer = { static DistributeObjectOps Any_CreateForeignServer = {
.deparse = DeparseCreateForeignServerStmt, .deparse = DeparseCreateForeignServerStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessCreateForeignServerStmt, .preprocess = NULL,
.postprocess = PostprocessCreateForeignServerStmt, .postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_FOREIGN_SERVER,
.address = CreateForeignServerStmtObjectAddress, .address = CreateForeignServerStmtObjectAddress,
.markDistributed = true, .markDistributed = true,
}; };
@ -268,31 +289,34 @@ static DistributeObjectOps Attribute_Rename = {
static DistributeObjectOps Collation_AlterObjectSchema = { static DistributeObjectOps Collation_AlterObjectSchema = {
.deparse = DeparseAlterCollationSchemaStmt, .deparse = DeparseAlterCollationSchemaStmt,
.qualify = QualifyAlterCollationSchemaStmt, .qualify = QualifyAlterCollationSchemaStmt,
.preprocess = PreprocessAlterCollationSchemaStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterCollationSchemaStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_COLLATION,
.address = AlterCollationSchemaStmtObjectAddress, .address = AlterCollationSchemaStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Collation_AlterOwner = { static DistributeObjectOps Collation_AlterOwner = {
.deparse = DeparseAlterCollationOwnerStmt, .deparse = DeparseAlterCollationOwnerStmt,
.qualify = QualifyAlterCollationOwnerStmt, .qualify = QualifyAlterCollationOwnerStmt,
.preprocess = PreprocessAlterCollationOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterCollationOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_COLLATION,
.address = AlterCollationOwnerObjectAddress, .address = AlterCollationOwnerObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Collation_Define = { static DistributeObjectOps Collation_Define = {
.deparse = NULL, .deparse = NULL,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessDefineCollationStmt, .preprocess = NULL,
.postprocess = PostprocessDefineCollationStmt, .postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_COLLATION,
.address = DefineCollationStmtObjectAddress, .address = DefineCollationStmtObjectAddress,
.markDistributed = true, .markDistributed = true,
}; };
static DistributeObjectOps Collation_Drop = { static DistributeObjectOps Collation_Drop = {
.deparse = DeparseDropCollationStmt, .deparse = DeparseDropCollationStmt,
.qualify = QualifyDropCollationStmt, .qualify = QualifyDropCollationStmt,
.preprocess = PreprocessDropCollationStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -300,47 +324,53 @@ static DistributeObjectOps Collation_Drop = {
static DistributeObjectOps Collation_Rename = { static DistributeObjectOps Collation_Rename = {
.deparse = DeparseRenameCollationStmt, .deparse = DeparseRenameCollationStmt,
.qualify = QualifyRenameCollationStmt, .qualify = QualifyRenameCollationStmt,
.preprocess = PreprocessRenameCollationStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_COLLATION,
.address = RenameCollationStmtObjectAddress, .address = RenameCollationStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Database_AlterOwner = { static DistributeObjectOps Database_AlterOwner = {
.deparse = DeparseAlterDatabaseOwnerStmt, .deparse = DeparseAlterDatabaseOwnerStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessAlterDatabaseOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDatabaseOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_DATABASE,
.featureFlag = &EnableAlterDatabaseOwner,
.address = AlterDatabaseOwnerObjectAddress, .address = AlterDatabaseOwnerObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Domain_Alter = { static DistributeObjectOps Domain_Alter = {
.deparse = DeparseAlterDomainStmt, .deparse = DeparseAlterDomainStmt,
.qualify = QualifyAlterDomainStmt, .qualify = QualifyAlterDomainStmt,
.preprocess = PreprocessAlterDomainStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDomainStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_DOMAIN,
.address = AlterDomainStmtObjectAddress, .address = AlterDomainStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Domain_AlterObjectSchema = { static DistributeObjectOps Domain_AlterObjectSchema = {
.deparse = DeparseAlterDomainSchemaStmt, .deparse = DeparseAlterDomainSchemaStmt,
.qualify = QualifyAlterDomainSchemaStmt, .qualify = QualifyAlterDomainSchemaStmt,
.preprocess = PreprocessAlterDomainSchemaStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDomainSchemaStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_DOMAIN,
.address = AlterTypeSchemaStmtObjectAddress, .address = AlterTypeSchemaStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Domain_AlterOwner = { static DistributeObjectOps Domain_AlterOwner = {
.deparse = DeparseAlterDomainOwnerStmt, .deparse = DeparseAlterDomainOwnerStmt,
.qualify = QualifyAlterDomainOwnerStmt, .qualify = QualifyAlterDomainOwnerStmt,
.preprocess = PreprocessAlterDomainOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDomainOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_DOMAIN,
.address = AlterDomainOwnerStmtObjectAddress, .address = AlterDomainOwnerStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Domain_Drop = { static DistributeObjectOps Domain_Drop = {
.deparse = DeparseDropDomainStmt, .deparse = DeparseDropDomainStmt,
.qualify = QualifyDropDomainStmt, .qualify = QualifyDropDomainStmt,
.preprocess = PreprocessDropDomainStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -348,8 +378,9 @@ static DistributeObjectOps Domain_Drop = {
static DistributeObjectOps Domain_Rename = { static DistributeObjectOps Domain_Rename = {
.deparse = DeparseRenameDomainStmt, .deparse = DeparseRenameDomainStmt,
.qualify = QualifyRenameDomainStmt, .qualify = QualifyRenameDomainStmt,
.preprocess = PreprocessRenameDomainStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_DOMAIN,
.address = RenameDomainStmtObjectAddress, .address = RenameDomainStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -357,8 +388,9 @@ static DistributeObjectOps Domain_Rename = {
static DistributeObjectOps Domain_RenameConstraint = { static DistributeObjectOps Domain_RenameConstraint = {
.deparse = DeparseDomainRenameConstraintStmt, .deparse = DeparseDomainRenameConstraintStmt,
.qualify = QualifyDomainRenameConstraintStmt, .qualify = QualifyDomainRenameConstraintStmt,
.preprocess = PreprocessDomainRenameConstraintStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_DOMAIN,
.address = DomainRenameConstraintStmtObjectAddress, .address = DomainRenameConstraintStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -381,7 +413,7 @@ static DistributeObjectOps Extension_Drop = {
static DistributeObjectOps ForeignServer_Drop = { static DistributeObjectOps ForeignServer_Drop = {
.deparse = DeparseDropForeignServerStmt, .deparse = DeparseDropForeignServerStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessDropForeignServerStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -389,16 +421,18 @@ static DistributeObjectOps ForeignServer_Drop = {
static DistributeObjectOps ForeignServer_Rename = { static DistributeObjectOps ForeignServer_Rename = {
.deparse = DeparseAlterForeignServerRenameStmt, .deparse = DeparseAlterForeignServerRenameStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessRenameForeignServerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .objectType = OBJECT_FOREIGN_SERVER,
.address = RenameForeignServerStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps ForeignServer_AlterOwner = { static DistributeObjectOps ForeignServer_AlterOwner = {
.deparse = DeparseAlterForeignServerOwnerStmt, .deparse = DeparseAlterForeignServerOwnerStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessAlterForeignServerOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterForeignServerOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FOREIGN_SERVER,
.address = AlterForeignServerOwnerStmtObjectAddress, .address = AlterForeignServerOwnerStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -421,23 +455,33 @@ static DistributeObjectOps Function_AlterObjectDepends = {
static DistributeObjectOps Function_AlterObjectSchema = { static DistributeObjectOps Function_AlterObjectSchema = {
.deparse = DeparseAlterFunctionSchemaStmt, .deparse = DeparseAlterFunctionSchemaStmt,
.qualify = QualifyAlterFunctionSchemaStmt, .qualify = QualifyAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterFunctionSchemaStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterFunctionSchemaStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionSchemaStmtObjectAddress, .address = AlterFunctionSchemaStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Function_AlterOwner = { static DistributeObjectOps Function_AlterOwner = {
.deparse = DeparseAlterFunctionOwnerStmt, .deparse = DeparseAlterFunctionOwnerStmt,
.qualify = QualifyAlterFunctionOwnerStmt, .qualify = QualifyAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterFunctionOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterFunctionOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionOwnerObjectAddress, .address = AlterFunctionOwnerObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Function_Drop = { static DistributeObjectOps Function_Drop = {
.deparse = DeparseDropFunctionStmt, .deparse = DeparseDropFunctionStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessDropFunctionStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps View_Drop = {
.deparse = DeparseDropViewStmt,
.qualify = QualifyDropViewStmt,
.preprocess = PreprocessDropViewStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -445,8 +489,9 @@ static DistributeObjectOps Function_Drop = {
static DistributeObjectOps Function_Rename = { static DistributeObjectOps Function_Rename = {
.deparse = DeparseRenameFunctionStmt, .deparse = DeparseRenameFunctionStmt,
.qualify = QualifyRenameFunctionStmt, .qualify = QualifyRenameFunctionStmt,
.preprocess = PreprocessRenameFunctionStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_FUNCTION,
.address = RenameFunctionStmtObjectAddress, .address = RenameFunctionStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -485,23 +530,25 @@ static DistributeObjectOps Procedure_AlterObjectDepends = {
static DistributeObjectOps Procedure_AlterObjectSchema = { static DistributeObjectOps Procedure_AlterObjectSchema = {
.deparse = DeparseAlterFunctionSchemaStmt, .deparse = DeparseAlterFunctionSchemaStmt,
.qualify = QualifyAlterFunctionSchemaStmt, .qualify = QualifyAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterFunctionSchemaStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterFunctionSchemaStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionSchemaStmtObjectAddress, .address = AlterFunctionSchemaStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Procedure_AlterOwner = { static DistributeObjectOps Procedure_AlterOwner = {
.deparse = DeparseAlterFunctionOwnerStmt, .deparse = DeparseAlterFunctionOwnerStmt,
.qualify = QualifyAlterFunctionOwnerStmt, .qualify = QualifyAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterFunctionOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterFunctionOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionOwnerObjectAddress, .address = AlterFunctionOwnerObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Procedure_Drop = { static DistributeObjectOps Procedure_Drop = {
.deparse = DeparseDropFunctionStmt, .deparse = DeparseDropFunctionStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessDropFunctionStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -509,8 +556,9 @@ static DistributeObjectOps Procedure_Drop = {
static DistributeObjectOps Procedure_Rename = { static DistributeObjectOps Procedure_Rename = {
.deparse = DeparseRenameFunctionStmt, .deparse = DeparseRenameFunctionStmt,
.qualify = QualifyRenameFunctionStmt, .qualify = QualifyRenameFunctionStmt,
.preprocess = PreprocessRenameFunctionStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_FUNCTION,
.address = RenameFunctionStmtObjectAddress, .address = RenameFunctionStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -548,7 +596,7 @@ static DistributeObjectOps Sequence_AlterOwner = {
}; };
static DistributeObjectOps Sequence_Drop = { static DistributeObjectOps Sequence_Drop = {
.deparse = DeparseDropSequenceStmt, .deparse = DeparseDropSequenceStmt,
.qualify = NULL, .qualify = QualifyDropSequenceStmt,
.preprocess = PreprocessDropSequenceStmt, .preprocess = PreprocessDropSequenceStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
@ -565,32 +613,36 @@ static DistributeObjectOps Sequence_Rename = {
static DistributeObjectOps TextSearchConfig_Alter = { static DistributeObjectOps TextSearchConfig_Alter = {
.deparse = DeparseAlterTextSearchConfigurationStmt, .deparse = DeparseAlterTextSearchConfigurationStmt,
.qualify = QualifyAlterTextSearchConfigurationStmt, .qualify = QualifyAlterTextSearchConfigurationStmt,
.preprocess = PreprocessAlterTextSearchConfigurationStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_TSCONFIGURATION,
.address = AlterTextSearchConfigurationStmtObjectAddress, .address = AlterTextSearchConfigurationStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps TextSearchConfig_AlterObjectSchema = { static DistributeObjectOps TextSearchConfig_AlterObjectSchema = {
.deparse = DeparseAlterTextSearchConfigurationSchemaStmt, .deparse = DeparseAlterTextSearchConfigurationSchemaStmt,
.qualify = QualifyAlterTextSearchConfigurationSchemaStmt, .qualify = QualifyAlterTextSearchConfigurationSchemaStmt,
.preprocess = PreprocessAlterTextSearchConfigurationSchemaStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterTextSearchConfigurationSchemaStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TSCONFIGURATION,
.address = AlterTextSearchConfigurationSchemaStmtObjectAddress, .address = AlterTextSearchConfigurationSchemaStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps TextSearchConfig_AlterOwner = { static DistributeObjectOps TextSearchConfig_AlterOwner = {
.deparse = DeparseAlterTextSearchConfigurationOwnerStmt, .deparse = DeparseAlterTextSearchConfigurationOwnerStmt,
.qualify = QualifyAlterTextSearchConfigurationOwnerStmt, .qualify = QualifyAlterTextSearchConfigurationOwnerStmt,
.preprocess = PreprocessAlterTextSearchConfigurationOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterTextSearchConfigurationOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TSCONFIGURATION,
.address = AlterTextSearchConfigurationOwnerObjectAddress, .address = AlterTextSearchConfigurationOwnerObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps TextSearchConfig_Comment = { static DistributeObjectOps TextSearchConfig_Comment = {
.deparse = DeparseTextSearchConfigurationCommentStmt, .deparse = DeparseTextSearchConfigurationCommentStmt,
.qualify = QualifyTextSearchConfigurationCommentStmt, .qualify = QualifyTextSearchConfigurationCommentStmt,
.preprocess = PreprocessTextSearchConfigurationCommentStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_TSCONFIGURATION,
.address = TextSearchConfigurationCommentObjectAddress, .address = TextSearchConfigurationCommentObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -598,14 +650,15 @@ static DistributeObjectOps TextSearchConfig_Define = {
.deparse = DeparseCreateTextSearchConfigurationStmt, .deparse = DeparseCreateTextSearchConfigurationStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = NULL, .preprocess = NULL,
.postprocess = PostprocessCreateTextSearchConfigurationStmt, .postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_TSCONFIGURATION,
.address = CreateTextSearchConfigurationObjectAddress, .address = CreateTextSearchConfigurationObjectAddress,
.markDistributed = true, .markDistributed = true,
}; };
static DistributeObjectOps TextSearchConfig_Drop = { static DistributeObjectOps TextSearchConfig_Drop = {
.deparse = DeparseDropTextSearchConfigurationStmt, .deparse = DeparseDropTextSearchConfigurationStmt,
.qualify = QualifyDropTextSearchConfigurationStmt, .qualify = QualifyDropTextSearchConfigurationStmt,
.preprocess = PreprocessDropTextSearchConfigurationStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -613,40 +666,45 @@ static DistributeObjectOps TextSearchConfig_Drop = {
static DistributeObjectOps TextSearchConfig_Rename = { static DistributeObjectOps TextSearchConfig_Rename = {
.deparse = DeparseRenameTextSearchConfigurationStmt, .deparse = DeparseRenameTextSearchConfigurationStmt,
.qualify = QualifyRenameTextSearchConfigurationStmt, .qualify = QualifyRenameTextSearchConfigurationStmt,
.preprocess = PreprocessRenameTextSearchConfigurationStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_TSCONFIGURATION,
.address = RenameTextSearchConfigurationStmtObjectAddress, .address = RenameTextSearchConfigurationStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps TextSearchDict_Alter = { static DistributeObjectOps TextSearchDict_Alter = {
.deparse = DeparseAlterTextSearchDictionaryStmt, .deparse = DeparseAlterTextSearchDictionaryStmt,
.qualify = QualifyAlterTextSearchDictionaryStmt, .qualify = QualifyAlterTextSearchDictionaryStmt,
.preprocess = PreprocessAlterTextSearchDictionaryStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_TSDICTIONARY,
.address = AlterTextSearchDictionaryStmtObjectAddress, .address = AlterTextSearchDictionaryStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps TextSearchDict_AlterObjectSchema = { static DistributeObjectOps TextSearchDict_AlterObjectSchema = {
.deparse = DeparseAlterTextSearchDictionarySchemaStmt, .deparse = DeparseAlterTextSearchDictionarySchemaStmt,
.qualify = QualifyAlterTextSearchDictionarySchemaStmt, .qualify = QualifyAlterTextSearchDictionarySchemaStmt,
.preprocess = PreprocessAlterTextSearchDictionarySchemaStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterTextSearchDictionarySchemaStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TSDICTIONARY,
.address = AlterTextSearchDictionarySchemaStmtObjectAddress, .address = AlterTextSearchDictionarySchemaStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps TextSearchDict_AlterOwner = { static DistributeObjectOps TextSearchDict_AlterOwner = {
.deparse = DeparseAlterTextSearchDictionaryOwnerStmt, .deparse = DeparseAlterTextSearchDictionaryOwnerStmt,
.qualify = QualifyAlterTextSearchDictionaryOwnerStmt, .qualify = QualifyAlterTextSearchDictionaryOwnerStmt,
.preprocess = PreprocessAlterTextSearchDictionaryOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterTextSearchDictionaryOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TSDICTIONARY,
.address = AlterTextSearchDictOwnerObjectAddress, .address = AlterTextSearchDictOwnerObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps TextSearchDict_Comment = { static DistributeObjectOps TextSearchDict_Comment = {
.deparse = DeparseTextSearchDictionaryCommentStmt, .deparse = DeparseTextSearchDictionaryCommentStmt,
.qualify = QualifyTextSearchDictionaryCommentStmt, .qualify = QualifyTextSearchDictionaryCommentStmt,
.preprocess = PreprocessTextSearchDictionaryCommentStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_TSDICTIONARY,
.address = TextSearchDictCommentObjectAddress, .address = TextSearchDictCommentObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -654,14 +712,15 @@ static DistributeObjectOps TextSearchDict_Define = {
.deparse = DeparseCreateTextSearchDictionaryStmt, .deparse = DeparseCreateTextSearchDictionaryStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = NULL, .preprocess = NULL,
.postprocess = PostprocessCreateTextSearchDictionaryStmt, .postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_TSDICTIONARY,
.address = CreateTextSearchDictObjectAddress, .address = CreateTextSearchDictObjectAddress,
.markDistributed = true, .markDistributed = true,
}; };
static DistributeObjectOps TextSearchDict_Drop = { static DistributeObjectOps TextSearchDict_Drop = {
.deparse = DeparseDropTextSearchDictionaryStmt, .deparse = DeparseDropTextSearchDictionaryStmt,
.qualify = QualifyDropTextSearchDictionaryStmt, .qualify = QualifyDropTextSearchDictionaryStmt,
.preprocess = PreprocessDropTextSearchDictionaryStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -669,8 +728,9 @@ static DistributeObjectOps TextSearchDict_Drop = {
static DistributeObjectOps TextSearchDict_Rename = { static DistributeObjectOps TextSearchDict_Rename = {
.deparse = DeparseRenameTextSearchDictionaryStmt, .deparse = DeparseRenameTextSearchDictionaryStmt,
.qualify = QualifyRenameTextSearchDictionaryStmt, .qualify = QualifyRenameTextSearchDictionaryStmt,
.preprocess = PreprocessRenameTextSearchDictionaryStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_TSDICTIONARY,
.address = RenameTextSearchDictionaryStmtObjectAddress, .address = RenameTextSearchDictionaryStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -685,23 +745,25 @@ static DistributeObjectOps Trigger_AlterObjectDepends = {
static DistributeObjectOps Routine_AlterObjectSchema = { static DistributeObjectOps Routine_AlterObjectSchema = {
.deparse = DeparseAlterFunctionSchemaStmt, .deparse = DeparseAlterFunctionSchemaStmt,
.qualify = QualifyAlterFunctionSchemaStmt, .qualify = QualifyAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterFunctionSchemaStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterFunctionSchemaStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionSchemaStmtObjectAddress, .address = AlterFunctionSchemaStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Routine_AlterOwner = { static DistributeObjectOps Routine_AlterOwner = {
.deparse = DeparseAlterFunctionOwnerStmt, .deparse = DeparseAlterFunctionOwnerStmt,
.qualify = QualifyAlterFunctionOwnerStmt, .qualify = QualifyAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterFunctionOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterFunctionOwnerStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionOwnerObjectAddress, .address = AlterFunctionOwnerObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Routine_Drop = { static DistributeObjectOps Routine_Drop = {
.deparse = DeparseDropFunctionStmt, .deparse = DeparseDropFunctionStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessDropFunctionStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -709,8 +771,9 @@ static DistributeObjectOps Routine_Drop = {
static DistributeObjectOps Routine_Rename = { static DistributeObjectOps Routine_Rename = {
.deparse = DeparseRenameFunctionStmt, .deparse = DeparseRenameFunctionStmt,
.qualify = QualifyRenameFunctionStmt, .qualify = QualifyRenameFunctionStmt,
.preprocess = PreprocessRenameFunctionStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_FUNCTION,
.address = RenameFunctionStmtObjectAddress, .address = RenameFunctionStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -733,8 +796,9 @@ static DistributeObjectOps Schema_Grant = {
static DistributeObjectOps Schema_Rename = { static DistributeObjectOps Schema_Rename = {
.deparse = DeparseAlterSchemaRenameStmt, .deparse = DeparseAlterSchemaRenameStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessAlterSchemaRenameStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_SCHEMA,
.address = AlterSchemaRenameStmtObjectAddress, .address = AlterSchemaRenameStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
@ -807,31 +871,66 @@ static DistributeObjectOps Table_Drop = {
static DistributeObjectOps Type_AlterObjectSchema = { static DistributeObjectOps Type_AlterObjectSchema = {
.deparse = DeparseAlterTypeSchemaStmt, .deparse = DeparseAlterTypeSchemaStmt,
.qualify = QualifyAlterTypeSchemaStmt, .qualify = QualifyAlterTypeSchemaStmt,
.preprocess = PreprocessAlterTypeSchemaStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterTypeSchemaStmt, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TYPE,
.address = AlterTypeSchemaStmtObjectAddress, .address = AlterTypeSchemaStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
/*
* PreprocessAlterViewSchemaStmt and PostprocessAlterViewSchemaStmt functions can be called
* internally by ALTER TABLE view_name SET SCHEMA ... if the ALTER TABLE command targets a
* view. In other words ALTER VIEW view_name SET SCHEMA will use the View_AlterObjectSchema
* but ALTER TABLE view_name SET SCHEMA will use Table_AlterObjectSchema but call process
* functions of View_AlterObjectSchema internally.
*/
static DistributeObjectOps View_AlterObjectSchema = {
.deparse = DeparseAlterViewSchemaStmt,
.qualify = QualifyAlterViewSchemaStmt,
.preprocess = PreprocessAlterViewSchemaStmt,
.postprocess = PostprocessAlterViewSchemaStmt,
.address = AlterViewSchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Type_AlterOwner = { static DistributeObjectOps Type_AlterOwner = {
.deparse = DeparseAlterTypeOwnerStmt, .deparse = DeparseAlterTypeOwnerStmt,
.qualify = QualifyAlterTypeOwnerStmt, .qualify = QualifyAlterTypeOwnerStmt,
.preprocess = PreprocessAlterTypeOwnerStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TYPE,
.address = AlterTypeOwnerObjectAddress, .address = AlterTypeOwnerObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
static DistributeObjectOps Type_AlterTable = { static DistributeObjectOps Type_AlterTable = {
.deparse = DeparseAlterTypeStmt, .deparse = DeparseAlterTypeStmt,
.qualify = QualifyAlterTypeStmt, .qualify = QualifyAlterTypeStmt,
.preprocess = PreprocessAlterTypeStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_TYPE,
.address = AlterTypeStmtObjectAddress, .address = AlterTypeStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
/*
* PreprocessAlterViewStmt and PostprocessAlterViewStmt functions can be called internally
* by ALTER TABLE view_name SET/RESET ... if the ALTER TABLE command targets a view. In
* other words ALTER VIEW view_name SET/RESET will use the View_AlterView
* but ALTER TABLE view_name SET/RESET will use Table_AlterTable but call process
* functions of View_AlterView internally.
*/
static DistributeObjectOps View_AlterView = {
.deparse = DeparseAlterViewStmt,
.qualify = QualifyAlterViewStmt,
.preprocess = PreprocessAlterViewStmt,
.postprocess = PostprocessAlterViewStmt,
.address = AlterViewStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Type_Drop = { static DistributeObjectOps Type_Drop = {
.deparse = DeparseDropTypeStmt, .deparse = DeparseDropTypeStmt,
.qualify = NULL, .qualify = NULL,
.preprocess = PreprocessDropTypeStmt, .preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.address = NULL, .address = NULL,
.markDistributed = false, .markDistributed = false,
@ -847,11 +946,27 @@ static DistributeObjectOps Trigger_Drop = {
static DistributeObjectOps Type_Rename = { static DistributeObjectOps Type_Rename = {
.deparse = DeparseRenameTypeStmt, .deparse = DeparseRenameTypeStmt,
.qualify = QualifyRenameTypeStmt, .qualify = QualifyRenameTypeStmt,
.preprocess = PreprocessRenameTypeStmt, .preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL, .postprocess = NULL,
.objectType = OBJECT_TYPE,
.address = RenameTypeStmtObjectAddress, .address = RenameTypeStmtObjectAddress,
.markDistributed = false, .markDistributed = false,
}; };
/*
* PreprocessRenameViewStmt function can be called internally by ALTER TABLE view_name
* RENAME ... if the ALTER TABLE command targets a view or a view's column. In other words
* ALTER VIEW view_name RENAME will use the View_Rename but ALTER TABLE view_name RENAME
* will use Any_Rename but call process functions of View_Rename internally.
*/
static DistributeObjectOps View_Rename = {
.deparse = DeparseRenameViewStmt,
.qualify = QualifyRenameViewStmt,
.preprocess = PreprocessRenameViewStmt,
.postprocess = NULL,
.address = RenameViewStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Trigger_Rename = { static DistributeObjectOps Trigger_Rename = {
.deparse = NULL, .deparse = NULL,
.qualify = NULL, .qualify = NULL,
@ -1005,6 +1120,11 @@ GetDistributeObjectOps(Node *node)
return &Type_AlterObjectSchema; return &Type_AlterObjectSchema;
} }
case OBJECT_VIEW:
{
return &View_AlterObjectSchema;
}
default: default:
{ {
return &NoDistributeOps; return &NoDistributeOps;
@ -1141,6 +1261,11 @@ GetDistributeObjectOps(Node *node)
return &Sequence_AlterOwner; return &Sequence_AlterOwner;
} }
case OBJECT_VIEW:
{
return &View_AlterView;
}
default: default:
{ {
return &NoDistributeOps; return &NoDistributeOps;
@ -1367,6 +1492,11 @@ GetDistributeObjectOps(Node *node)
return &Trigger_Drop; return &Trigger_Drop;
} }
case OBJECT_VIEW:
{
return &View_Drop;
}
default: default:
{ {
return &NoDistributeOps; return &NoDistributeOps;
@ -1396,6 +1526,11 @@ GetDistributeObjectOps(Node *node)
return &Any_Index; return &Any_Index;
} }
case T_ViewStmt:
{
return &Any_View;
}
case T_ReindexStmt: case T_ReindexStmt:
{ {
return &Any_Reindex; return &Any_Reindex;
@ -1486,6 +1621,27 @@ GetDistributeObjectOps(Node *node)
return &Trigger_Rename; return &Trigger_Rename;
} }
case OBJECT_VIEW:
{
return &View_Rename;
}
case OBJECT_COLUMN:
{
switch (stmt->relationType)
{
case OBJECT_VIEW:
{
return &View_Rename;
}
default:
{
return &Any_Rename;
}
}
}
default: default:
{ {
return &Any_Rename; return &Any_Rename;

View File

@ -37,382 +37,8 @@
static CollateClause * MakeCollateClauseFromOid(Oid collationOid); static CollateClause * MakeCollateClauseFromOid(Oid collationOid);
static List * FilterNameListForDistributedDomains(List *domainNames, bool missing_ok,
List **distributedDomainAddresses);
static ObjectAddress GetDomainAddressByName(TypeName *domainName, bool missing_ok); static ObjectAddress GetDomainAddressByName(TypeName *domainName, bool missing_ok);
/*
* PreprocessCreateDomainStmt handles the propagation of the create domain statements.
*/
List *
PreprocessCreateDomainStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_DOMAIN);
QualifyTreeNode(node);
const char *sql = DeparseTreeNode(node);
sql = WrapCreateOrReplace(sql);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessCreateDomainStmt gets called after the domain has been created locally. When
* the domain is decided to be propagated we make sure all the domains dependencies exist
* on all workers.
*/
List *
PostprocessCreateDomainStmt(Node *node, const char *queryString)
{
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
/*
* find object address of the just created object, because the domain has been created
* locally it can't be missing
*/
ObjectAddress typeAddress = GetObjectAddressFromParseTree(node, false);
EnsureDependenciesExistOnAllNodes(&typeAddress);
return NIL;
}
/*
* PreprocessDropDomainStmt gets called before dropping the domain locally. For
* distributed domains it will make sure the fully qualified statement is forwarded to all
* the workers reflecting the drop of the domain.
*/
List *
PreprocessDropDomainStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
if (!ShouldPropagate())
{
return NIL;
}
QualifyTreeNode((Node *) stmt);
List *oldDomains = stmt->objects;
List *distributedDomainAddresses = NIL;
List *distributedDomains = FilterNameListForDistributedDomains(
oldDomains,
stmt->missing_ok,
&distributedDomainAddresses);
if (list_length(distributedDomains) <= 0)
{
/* no distributed domains to drop */
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_DOMAIN);
ObjectAddress *addressItem = NULL;
foreach_ptr(addressItem, distributedDomainAddresses)
{
UnmarkObjectDistributed(addressItem);
}
/*
* temporary swap the lists of objects to delete with the distributed objects and
* deparse to an executable sql statement for the workers
*/
stmt->objects = distributedDomains;
char *dropStmtSql = DeparseTreeNode((Node *) stmt);
stmt->objects = oldDomains;
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterDomainStmt gets called for all domain specific alter statements. When
* the change happens on a distributed domain we reflect the changes on the workers.
*/
List *
PreprocessAlterDomainStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterDomainStmt *stmt = castNode(AlterDomainStmt, node);
ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&domainAddress))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_DOMAIN);
QualifyTreeNode((Node *) stmt);
char *sqlStmt = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sqlStmt,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterDomainStmt gets called after the domain has been altered locally. A
* change on the constraints could cause new (undistributed) objects to be dependencies of
* the domain. Here we recreate any new dependencies on the workers before we forward the
* alter statement to the workers.
*/
List *
PostprocessAlterDomainStmt(Node *node, const char *queryString)
{
AlterDomainStmt *stmt = castNode(AlterDomainStmt, node);
ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&domainAddress))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&domainAddress);
return NIL;
}
/*
* PreprocessDomainRenameConstraintStmt gets called locally when a constraint on a domain
* is renamed. When the constraint is on a distributed domain we forward the statement
* appropriately.
*/
List *
PreprocessDomainRenameConstraintStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_DOMCONSTRAINT);
ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&domainAddress))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_DOMAIN);
QualifyTreeNode((Node *) stmt);
char *sqlStmt = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sqlStmt,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterDomainOwnerStmt called locally when the owner of a constraint is
* changed. For distributed domains the statement is forwarded to all the workers.
*/
List *
PreprocessAlterDomainOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_DOMAIN);
ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&domainAddress))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_DOMAIN);
QualifyTreeNode((Node *) stmt);
char *sqlStmt = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sqlStmt,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterDomainOwnerStmt given the change of ownership could cause new
* dependencies to exist for the domain we make sure all dependencies for the domain are
* created before we forward the statement to the workers.
*/
List *
PostprocessAlterDomainOwnerStmt(Node *node, const char *queryString)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&domainAddress))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&domainAddress);
return NIL;
}
/*
* PreprocessRenameDomainStmt creates the statements to execute on the workers when the
* domain being renamed is distributed.
*/
List *
PreprocessRenameDomainStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_DOMAIN);
ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&domainAddress))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_DOMAIN);
QualifyTreeNode((Node *) stmt);
char *sqlStmt = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sqlStmt,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterDomainSchemaStmt cretes the statements to execute on the workers when
* the domain being moved to a new schema has been distributed.
*/
List *
PreprocessAlterDomainSchemaStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_DOMAIN);
ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&domainAddress))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_DOMAIN);
QualifyTreeNode((Node *) stmt);
char *sqlStmt = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sqlStmt,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterDomainSchemaStmt makes sure any new dependencies (as result of the
* schema move) are created on the workers before we forward the statement.
*/
List *
PostprocessAlterDomainSchemaStmt(Node *node, const char *queryString)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&domainAddress))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&domainAddress);
return NIL;
}
/*
* FilterNameListForDistributedDomains given a liost of domain names we will return a list
* filtered with only the names of distributed domains remaining. The pointer to the list
* distributedDomainAddresses is populated with a list of ObjectAddresses of the domains
* that are distributed. Indices between the returned list and the object addresses are
* synchronizes.
* Note: the pointer in distributedDomainAddresses should not be omitted
*
* When missing_ok is false this function will raise an error if a domain identified by a
* domain name cannot be found.
*/
static List *
FilterNameListForDistributedDomains(List *domainNames, bool missing_ok,
List **distributedDomainAddresses)
{
Assert(distributedDomainAddresses != NULL);
List *distributedDomainNames = NIL;
TypeName *domainName = NULL;
foreach_ptr(domainName, domainNames)
{
ObjectAddress objectAddress = GetDomainAddressByName(domainName, missing_ok);
if (IsObjectDistributed(&objectAddress))
{
distributedDomainNames = lappend(distributedDomainNames, domainName);
if (distributedDomainAddresses)
{
ObjectAddress *allocatedAddress = palloc0(sizeof(ObjectAddress));
*allocatedAddress = objectAddress;
*distributedDomainAddresses = lappend(*distributedDomainAddresses,
allocatedAddress);
}
}
}
return distributedDomainNames;
}
/* /*
* GetDomainAddressByName returns the ObjectAddress of the domain identified by * GetDomainAddressByName returns the ObjectAddress of the domain identified by
* domainName. When missing_ok is true the object id part of the ObjectAddress can be * domainName. When missing_ok is true the object id part of the ObjectAddress can be

View File

@ -9,6 +9,8 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include "access/genam.h"
#include "access/xact.h" #include "access/xact.h"
#include "citus_version.h" #include "citus_version.h"
#include "catalog/pg_extension_d.h" #include "catalog/pg_extension_d.h"
@ -37,6 +39,8 @@ static void AddSchemaFieldIfMissing(CreateExtensionStmt *stmt);
static List * FilterDistributedExtensions(List *extensionObjectList); static List * FilterDistributedExtensions(List *extensionObjectList);
static List * ExtensionNameListToObjectAddressList(List *extensionObjectList); static List * ExtensionNameListToObjectAddressList(List *extensionObjectList);
static void MarkExistingObjectDependenciesDistributedIfSupported(void); static void MarkExistingObjectDependenciesDistributedIfSupported(void);
static List * GetAllViews(void);
static bool ShouldMarkRelationDistributedOnUpgrade(Oid relationId);
static bool ShouldPropagateExtensionCommand(Node *parseTree); static bool ShouldPropagateExtensionCommand(Node *parseTree);
static bool IsAlterExtensionSetSchemaCitus(Node *parseTree); static bool IsAlterExtensionSetSchemaCitus(Node *parseTree);
static Node * RecreateExtensionStmt(Oid extensionOid); static Node * RecreateExtensionStmt(Oid extensionOid);
@ -295,7 +299,7 @@ FilterDistributedExtensions(List *extensionObjectList)
{ {
List *extensionNameList = NIL; List *extensionNameList = NIL;
Value *objectName = NULL; String *objectName = NULL;
foreach_ptr(objectName, extensionObjectList) foreach_ptr(objectName, extensionObjectList)
{ {
const char *extensionName = strVal(objectName); const char *extensionName = strVal(objectName);
@ -334,7 +338,7 @@ ExtensionNameListToObjectAddressList(List *extensionObjectList)
{ {
List *extensionObjectAddressList = NIL; List *extensionObjectAddressList = NIL;
Value *objectName; String *objectName;
foreach_ptr(objectName, extensionObjectList) foreach_ptr(objectName, extensionObjectList)
{ {
/* /*
@ -510,26 +514,78 @@ MarkExistingObjectDependenciesDistributedIfSupported()
Oid citusTableId = InvalidOid; Oid citusTableId = InvalidOid;
foreach_oid(citusTableId, citusTableIdList) foreach_oid(citusTableId, citusTableIdList)
{ {
ObjectAddress tableAddress = { 0 }; if (!ShouldMarkRelationDistributedOnUpgrade(citusTableId))
ObjectAddressSet(tableAddress, RelationRelationId, citusTableId);
if (ShouldSyncTableMetadata(citusTableId))
{ {
/* we need to pass pointer allocated in the heap */ continue;
ObjectAddress *addressPointer = palloc0(sizeof(ObjectAddress));
*addressPointer = tableAddress;
/* as of Citus 11, tables that should be synced are also considered object */
resultingObjectAddresses = lappend(resultingObjectAddresses, addressPointer);
} }
List *distributableDependencyObjectAddresses = /* refrain reading the metadata cache for all tables */
GetDistributableDependenciesForObject(&tableAddress); if (ShouldSyncTableMetadataViaCatalog(citusTableId))
{
ObjectAddress tableAddress = { 0 };
ObjectAddressSet(tableAddress, RelationRelationId, citusTableId);
resultingObjectAddresses = list_concat(resultingObjectAddresses, /*
distributableDependencyObjectAddresses); * We mark tables distributed immediately because we also need to mark
* views as distributed. We check whether the views that depend on
* the table has any auto-distirbutable dependencies below. Citus
* currently cannot "auto" distribute tables as dependencies, so we
* mark them distributed immediately.
*/
MarkObjectDistributedLocally(&tableAddress);
/*
* All the distributable dependencies of a table should be marked as
* distributed.
*/
List *distributableDependencyObjectAddresses =
GetDistributableDependenciesForObject(&tableAddress);
resultingObjectAddresses =
list_concat(resultingObjectAddresses,
distributableDependencyObjectAddresses);
}
} }
/*
* As of Citus 11, views on Citus tables that do not have any unsupported
* dependency should also be distributed.
*
* In general, we mark views distributed as long as it does not have
* any unsupported dependencies.
*/
List *viewList = GetAllViews();
Oid viewOid = InvalidOid;
foreach_oid(viewOid, viewList)
{
if (!ShouldMarkRelationDistributedOnUpgrade(viewOid))
{
continue;
}
ObjectAddress viewAddress = { 0 };
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
/*
* If a view depends on multiple views, that view will be marked
* as distributed while it is processed for the last view
* table.
*/
MarkObjectDistributedLocally(&viewAddress);
/* we need to pass pointer allocated in the heap */
ObjectAddress *addressPointer = palloc0(sizeof(ObjectAddress));
*addressPointer = viewAddress;
List *distributableDependencyObjectAddresses =
GetDistributableDependenciesForObject(&viewAddress);
resultingObjectAddresses =
list_concat(resultingObjectAddresses,
distributableDependencyObjectAddresses);
}
/* resolve dependencies of the objects in pg_dist_object*/ /* resolve dependencies of the objects in pg_dist_object*/
List *distributedObjectAddressList = GetDistributedObjectAddressList(); List *distributedObjectAddressList = GetDistributedObjectAddressList();
@ -565,6 +621,85 @@ MarkExistingObjectDependenciesDistributedIfSupported()
} }
/*
* GetAllViews returns list of view oids that exists on this server.
*/
static List *
GetAllViews(void)
{
List *viewOidList = NIL;
Relation pgClass = table_open(RelationRelationId, AccessShareLock);
SysScanDesc scanDescriptor = systable_beginscan(pgClass, InvalidOid, false, NULL,
0, NULL);
HeapTuple heapTuple = systable_getnext(scanDescriptor);
while (HeapTupleIsValid(heapTuple))
{
Form_pg_class relationForm = (Form_pg_class) GETSTRUCT(heapTuple);
/* we're only interested in views */
if (relationForm->relkind == RELKIND_VIEW)
{
viewOidList = lappend_oid(viewOidList, relationForm->oid);
}
heapTuple = systable_getnext(scanDescriptor);
}
systable_endscan(scanDescriptor);
table_close(pgClass, NoLock);
return viewOidList;
}
/*
* ShouldMarkRelationDistributedOnUpgrade is a helper function that
* decides whether the input relation should be marked as distributed
* during the upgrade.
*/
static bool
ShouldMarkRelationDistributedOnUpgrade(Oid relationId)
{
if (!EnableMetadataSync)
{
/*
* Just in case anything goes wrong, we should still be able
* to continue to the version upgrade.
*/
return false;
}
ObjectAddress relationAddress = { 0 };
ObjectAddressSet(relationAddress, RelationRelationId, relationId);
bool pgObject = (relationId < FirstNormalObjectId);
bool ownedByExtension = IsTableOwnedByExtension(relationId);
bool alreadyDistributed = IsObjectDistributed(&relationAddress);
bool hasUnsupportedDependency =
DeferErrorIfHasUnsupportedDependency(&relationAddress) != NULL;
bool hasCircularDependency =
DeferErrorIfCircularDependencyExists(&relationAddress) != NULL;
/*
* pgObject: Citus never marks pg objects as distributed
* ownedByExtension: let extensions manage its own objects
* alreadyDistributed: most likely via earlier versions
* hasUnsupportedDependency: Citus doesn't know how to distribute its dependencies
* hasCircularDependency: Citus cannot handle circular dependencies
*/
if (pgObject || ownedByExtension || alreadyDistributed ||
hasUnsupportedDependency || hasCircularDependency)
{
return false;
}
return true;
}
/* /*
* PreprocessAlterExtensionContentsStmt issues a notice. It does not propagate. * PreprocessAlterExtensionContentsStmt issues a notice. It does not propagate.
*/ */
@ -671,7 +806,7 @@ IsDropCitusExtensionStmt(Node *parseTree)
} }
/* now that we have a DropStmt, check if citus extension is among the objects to dropped */ /* now that we have a DropStmt, check if citus extension is among the objects to dropped */
Value *objectName; String *objectName;
foreach_ptr(objectName, dropStmt->objects) foreach_ptr(objectName, dropStmt->objects)
{ {
const char *extensionName = strVal(objectName); const char *extensionName = strVal(objectName);
@ -878,6 +1013,19 @@ CreateExtensionWithVersion(char *extname, char *extVersion)
} }
/*
* GetExtensionVersionNumber convert extension version to real value
*/
double
GetExtensionVersionNumber(char *extVersion)
{
char *strtokPosition = NULL;
char *versionVal = strtok_r(extVersion, "-", &strtokPosition);
double versionNumber = strtod(versionVal, NULL);
return versionNumber;
}
/* /*
* AlterExtensionUpdateStmt builds and execute Alter extension statements * AlterExtensionUpdateStmt builds and execute Alter extension statements
* per given extension name and updates extension verision * per given extension name and updates extension verision

View File

@ -25,238 +25,9 @@
#include "nodes/primnodes.h" #include "nodes/primnodes.h"
static Node * RecreateForeignServerStmt(Oid serverId); static Node * RecreateForeignServerStmt(Oid serverId);
static bool NameListHasDistributedServer(List *serverNames);
static ObjectAddress GetObjectAddressByServerName(char *serverName, bool missing_ok); static ObjectAddress GetObjectAddressByServerName(char *serverName, bool missing_ok);
/*
* PreprocessCreateForeignServerStmt is called during the planning phase for
* CREATE SERVER.
*/
List *
PreprocessCreateForeignServerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FOREIGN_SERVER);
char *sql = DeparseTreeNode(node);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterForeignServerStmt is called during the planning phase for
* ALTER SERVER .. OPTIONS ..
*/
List *
PreprocessAlterForeignServerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterForeignServerStmt *stmt = castNode(AlterForeignServerStmt, node);
ObjectAddress address = GetObjectAddressByServerName(stmt->servername, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
char *sql = DeparseTreeNode(node);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessRenameForeignServerStmt is called during the planning phase for
* ALTER SERVER RENAME.
*/
List *
PreprocessRenameForeignServerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_FOREIGN_SERVER);
ObjectAddress address = GetObjectAddressByServerName(strVal(stmt->object), false);
/* filter distributed servers */
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
char *sql = DeparseTreeNode(node);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterForeignServerOwnerStmt is called during the planning phase for
* ALTER SERVER .. OWNER TO.
*/
List *
PreprocessAlterForeignServerOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_FOREIGN_SERVER);
ObjectAddress address = GetObjectAddressByServerName(strVal(stmt->object), false);
/* filter distributed servers */
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
char *sql = DeparseTreeNode(node);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessDropForeignServerStmt is called during the planning phase for
* DROP SERVER.
*/
List *
PreprocessDropForeignServerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
Assert(stmt->removeType == OBJECT_FOREIGN_SERVER);
bool includesDistributedServer = NameListHasDistributedServer(stmt->objects);
if (!includesDistributedServer)
{
return NIL;
}
if (list_length(stmt->objects) > 1)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot drop distributed server with other servers"),
errhint("Try dropping each object in a separate DROP command")));
}
if (!ShouldPropagate())
{
return NIL;
}
EnsureCoordinator();
Assert(list_length(stmt->objects) == 1);
Value *serverValue = linitial(stmt->objects);
ObjectAddress address = GetObjectAddressByServerName(strVal(serverValue), false);
/* unmark distributed server */
UnmarkObjectDistributed(&address);
const char *deparsedStmt = DeparseTreeNode((Node *) stmt);
/*
* To prevent recursive propagation in mx architecture, we disable ddl
* propagation before sending the command to workers.
*/
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) deparsedStmt,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessCreateForeignServerStmt is called after a CREATE SERVER command has
* been executed by standard process utility.
*/
List *
PostprocessCreateForeignServerStmt(Node *node, const char *queryString)
{
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
const bool missingOk = false;
ObjectAddress address = GetObjectAddressFromParseTree(node, missingOk);
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* PostprocessAlterForeignServerOwnerStmt is called after a ALTER SERVER OWNER command
* has been executed by standard process utility.
*/
List *
PostprocessAlterForeignServerOwnerStmt(Node *node, const char *queryString)
{
const bool missingOk = false;
ObjectAddress address = GetObjectAddressFromParseTree(node, missingOk);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/* /*
* CreateForeignServerStmtObjectAddress finds the ObjectAddress for the server * CreateForeignServerStmtObjectAddress finds the ObjectAddress for the server
* that is created by given CreateForeignServerStmt. If missingOk is false and if * that is created by given CreateForeignServerStmt. If missingOk is false and if
@ -274,6 +45,41 @@ CreateForeignServerStmtObjectAddress(Node *node, bool missing_ok)
} }
/*
* AlterForeignServerStmtObjectAddress finds the ObjectAddress for the server that is
* changed by given AlterForeignServerStmt. If missingOk is false and if
* the server does not exist, then it errors out.
*
* Never returns NULL, but the objid in the address can be invalid if missingOk
* was set to true.
*/
ObjectAddress
AlterForeignServerStmtObjectAddress(Node *node, bool missing_ok)
{
AlterForeignServerStmt *stmt = castNode(AlterForeignServerStmt, node);
return GetObjectAddressByServerName(stmt->servername, missing_ok);
}
/*
* RenameForeignServerStmtObjectAddress finds the ObjectAddress for the server that is
* renamed by given RenmaeStmt. If missingOk is false and if the server does not exist,
* then it errors out.
*
* Never returns NULL, but the objid in the address can be invalid if missingOk
* was set to true.
*/
ObjectAddress
RenameForeignServerStmtObjectAddress(Node *node, bool missing_ok)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_FOREIGN_SERVER);
return GetObjectAddressByServerName(strVal(stmt->object), missing_ok);
}
/* /*
* AlterForeignServerOwnerStmtObjectAddress finds the ObjectAddress for the server * AlterForeignServerOwnerStmtObjectAddress finds the ObjectAddress for the server
* given in AlterOwnerStmt. If missingOk is false and if * given in AlterOwnerStmt. If missingOk is false and if
@ -355,28 +161,6 @@ RecreateForeignServerStmt(Oid serverId)
} }
/*
* NameListHasDistributedServer takes a namelist of servers and returns true if at least
* one of them is distributed. Returns false otherwise.
*/
static bool
NameListHasDistributedServer(List *serverNames)
{
Value *serverValue = NULL;
foreach_ptr(serverValue, serverNames)
{
ObjectAddress address = GetObjectAddressByServerName(strVal(serverValue), false);
if (IsObjectDistributed(&address))
{
return true;
}
}
return false;
}
static ObjectAddress static ObjectAddress
GetObjectAddressByServerName(char *serverName, bool missing_ok) GetObjectAddressByServerName(char *serverName, bool missing_ok)
{ {

View File

@ -490,7 +490,7 @@ GetDistributionArgIndex(Oid functionOid, char *distributionArgumentName,
distributionArgumentName++; distributionArgumentName++;
/* throws error if the input is not an integer */ /* throws error if the input is not an integer */
distributionArgumentIndex = pg_atoi(distributionArgumentName, 4, 0); distributionArgumentIndex = pg_strtoint32(distributionArgumentName);
if (distributionArgumentIndex < 1 || distributionArgumentIndex > numberOfArgs) if (distributionArgumentIndex < 1 || distributionArgumentIndex > numberOfArgs)
{ {
@ -1502,234 +1502,6 @@ PreprocessAlterFunctionStmt(Node *node, const char *queryString,
} }
/*
* PreprocessRenameFunctionStmt is called when the user is renaming a function. The invocation
* happens before the statement is applied locally.
*
* As the function already exists we have access to the ObjectAddress, this is used to
* check if it is distributed. If so the rename is executed on all the workers to keep the
* types in sync across the cluster.
*/
List *
PreprocessRenameFunctionStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
AssertObjectTypeIsFunctional(stmt->renameType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FUNCTION);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterFunctionSchemaStmt is executed before the statement is applied to the local
* postgres instance.
*
* In this stage we can prepare the commands that need to be run on all workers.
*/
List *
PreprocessAlterFunctionSchemaStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
AssertObjectTypeIsFunctional(stmt->objectType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FUNCTION);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterFunctionOwnerStmt is called for change of owner ship of functions before the owner
* ship is changed on the local instance.
*
* If the function for which the owner is changed is distributed we execute the change on
* all the workers to keep the type in sync across the cluster.
*/
List *
PreprocessAlterFunctionOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
AssertObjectTypeIsFunctional(stmt->objectType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FUNCTION);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterFunctionOwnerStmt is invoked after the owner has been changed locally.
* Since changing the owner could result in new dependencies being found for this object
* we re-ensure all the dependencies for the function do exist.
*
* This is solely to propagate the new owner (and all its dependencies) if it was not
* already distributed in the cluster.
*/
List *
PostprocessAlterFunctionOwnerStmt(Node *node, const char *queryString)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
AssertObjectTypeIsFunctional(stmt->objectType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* PreprocessDropFunctionStmt gets called during the planning phase of a DROP FUNCTION statement
* and returns a list of DDLJob's that will drop any distributed functions from the
* workers.
*
* The DropStmt could have multiple objects to drop, the list of objects will be filtered
* to only keep the distributed functions for deletion on the workers. Non-distributed
* functions will still be dropped locally but not on the workers.
*/
List *
PreprocessDropFunctionStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
List *deletingObjectWithArgsList = stmt->objects;
List *distributedObjectWithArgsList = NIL;
List *distributedFunctionAddresses = NIL;
AssertObjectTypeIsFunctional(stmt->removeType);
if (creating_extension)
{
/*
* extensions should be created separately on the workers, types cascading from an
* extension should therefore not be propagated here.
*/
return NIL;
}
if (!EnableMetadataSync)
{
/*
* we are configured to disable object propagation, should not propagate anything
*/
return NIL;
}
/*
* Our statements need to be fully qualified so we can drop them from the right schema
* on the workers
*/
QualifyTreeNode((Node *) stmt);
/*
* iterate over all functions to be dropped and filter to keep only distributed
* functions.
*/
ObjectWithArgs *func = NULL;
foreach_ptr(func, deletingObjectWithArgsList)
{
ObjectAddress address = FunctionToObjectAddress(stmt->removeType, func,
stmt->missing_ok);
if (!IsObjectDistributed(&address))
{
continue;
}
/* collect information for all distributed functions */
ObjectAddress *addressp = palloc(sizeof(ObjectAddress));
*addressp = address;
distributedFunctionAddresses = lappend(distributedFunctionAddresses, addressp);
distributedObjectWithArgsList = lappend(distributedObjectWithArgsList, func);
}
if (list_length(distributedObjectWithArgsList) <= 0)
{
/* no distributed functions to drop */
return NIL;
}
/*
* managing types can only be done on the coordinator if ddl propagation is on. when
* it is off we will never get here. MX workers don't have a notion of distributed
* types, so we block the call.
*/
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FUNCTION);
/* remove the entries for the distributed objects on dropping */
ObjectAddress *address = NULL;
foreach_ptr(address, distributedFunctionAddresses)
{
UnmarkObjectDistributed(address);
}
/*
* Swap the list of objects before deparsing and restore the old list after. This
* ensures we only have distributed functions in the deparsed drop statement.
*/
DropStmt *stmtCopy = copyObject(stmt);
stmtCopy->objects = distributedObjectWithArgsList;
const char *dropStmtSql = DeparseTreeNode((Node *) stmtCopy);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/* /*
* PreprocessAlterFunctionDependsStmt is called during the planning phase of an * PreprocessAlterFunctionDependsStmt is called during the planning phase of an
* ALTER FUNCION ... DEPENDS ON EXTENSION ... statement. Since functions depending on * ALTER FUNCION ... DEPENDS ON EXTENSION ... statement. Since functions depending on
@ -1803,30 +1575,6 @@ AlterFunctionDependsStmtObjectAddress(Node *node, bool missing_ok)
} }
/*
* PostprocessAlterFunctionSchemaStmt is executed after the change has been applied locally,
* we can now use the new dependencies of the function to ensure all its dependencies
* exist on the workers before we apply the commands remotely.
*/
List *
PostprocessAlterFunctionSchemaStmt(Node *node, const char *queryString)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
AssertObjectTypeIsFunctional(stmt->objectType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
/* dependencies have changed (schema) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/* /*
* AlterFunctionStmtObjectAddress returns the ObjectAddress of the subject in the * AlterFunctionStmtObjectAddress returns the ObjectAddress of the subject in the
* AlterFunctionStmt. If missing_ok is set to false an error will be raised if postgres * AlterFunctionStmt. If missing_ok is set to false an error will be raised if postgres
@ -1893,7 +1641,7 @@ AlterFunctionSchemaStmtObjectAddress(Node *node, bool missing_ok)
*/ */
/* the name of the function is the last in the list of names */ /* the name of the function is the last in the list of names */
Value *funcNameStr = lfirst(list_tail(names)); String *funcNameStr = lfirst(list_tail(names));
List *newNames = list_make2(makeString(stmt->newschema), funcNameStr); List *newNames = list_make2(makeString(stmt->newschema), funcNameStr);
/* /*
@ -1938,8 +1686,8 @@ GenerateBackupNameForProcCollision(const ObjectAddress *address)
char *newName = palloc0(NAMEDATALEN); char *newName = palloc0(NAMEDATALEN);
char suffix[NAMEDATALEN] = { 0 }; char suffix[NAMEDATALEN] = { 0 };
int count = 0; int count = 0;
Value *namespace = makeString(get_namespace_name(get_func_namespace( String *namespace = makeString(get_namespace_name(get_func_namespace(
address->objectId))); address->objectId)));
char *baseName = get_func_name(address->objectId); char *baseName = get_func_name(address->objectId);
int baseLength = strlen(baseName); int baseLength = strlen(baseName);
Oid *argtypes = NULL; Oid *argtypes = NULL;

View File

@ -42,6 +42,7 @@
#include "lib/stringinfo.h" #include "lib/stringinfo.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
#include "parser/parse_utilcmd.h"
#include "storage/lmgr.h" #include "storage/lmgr.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/fmgroids.h" #include "utils/fmgroids.h"
@ -184,9 +185,18 @@ PreprocessIndexStmt(Node *node, const char *createIndexCommand,
*/ */
ErrorIfCreateIndexHasTooManyColumns(createIndexStatement); ErrorIfCreateIndexHasTooManyColumns(createIndexStatement);
/*
* If there are expressions on the index, we should first transform
* the statement as the default index name depends on that. We do
* it on a copy not to interfere with standard process utility.
*/
IndexStmt *copyCreateIndexStatement =
transformIndexStmt(relation->rd_id, copyObject(createIndexStatement),
createIndexCommand);
/* ensure we copy string into proper context */ /* ensure we copy string into proper context */
MemoryContext relationContext = GetMemoryChunkContext(relationRangeVar); MemoryContext relationContext = GetMemoryChunkContext(relationRangeVar);
char *defaultIndexName = GenerateDefaultIndexName(createIndexStatement); char *defaultIndexName = GenerateDefaultIndexName(copyCreateIndexStatement);
createIndexStatement->idxname = MemoryContextStrdup(relationContext, createIndexStatement->idxname = MemoryContextStrdup(relationContext,
defaultIndexName); defaultIndexName);
} }
@ -464,7 +474,8 @@ GenerateCreateIndexDDLJob(IndexStmt *createIndexStatement, const char *createInd
{ {
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = CreateIndexStmtGetRelationId(createIndexStatement); ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId,
CreateIndexStmtGetRelationId(createIndexStatement));
ddlJob->startNewTransaction = createIndexStatement->concurrent; ddlJob->startNewTransaction = createIndexStatement->concurrent;
ddlJob->metadataSyncCommand = createIndexCommand; ddlJob->metadataSyncCommand = createIndexCommand;
ddlJob->taskList = CreateIndexTaskList(createIndexStatement); ddlJob->taskList = CreateIndexTaskList(createIndexStatement);
@ -598,7 +609,7 @@ PreprocessReindexStmt(Node *node, const char *reindexCommand,
} }
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = IsReindexWithParam_compat(reindexStatement, ddlJob->startNewTransaction = IsReindexWithParam_compat(reindexStatement,
"concurrently"); "concurrently");
ddlJob->metadataSyncCommand = reindexCommand; ddlJob->metadataSyncCommand = reindexCommand;
@ -695,7 +706,8 @@ PreprocessDropIndexStmt(Node *node, const char *dropIndexCommand,
MarkInvalidateForeignKeyGraph(); MarkInvalidateForeignKeyGraph();
} }
ddlJob->targetRelationId = distributedRelationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId,
distributedRelationId);
/* /*
* We do not want DROP INDEX CONCURRENTLY to commit locally before * We do not want DROP INDEX CONCURRENTLY to commit locally before

View File

@ -2009,7 +2009,7 @@ CitusCopyDestReceiverStartup(DestReceiver *dest, int operation,
foreach(columnNameCell, columnNameList) foreach(columnNameCell, columnNameList)
{ {
char *columnName = (char *) lfirst(columnNameCell); char *columnName = (char *) lfirst(columnNameCell);
Value *columnNameValue = makeString(columnName); String *columnNameValue = makeString(columnName);
attributeList = lappend(attributeList, columnNameValue); attributeList = lappend(attributeList, columnNameValue);
} }
@ -3430,10 +3430,7 @@ InitializeCopyShardState(CopyShardState *shardState,
ereport(ERROR, (errmsg("could not connect to any active placements"))); ereport(ERROR, (errmsg("could not connect to any active placements")));
} }
if (hasRemoteCopy) EnsureTaskExecutionAllowed(hasRemoteCopy);
{
EnsureRemoteTaskExecutionAllowed();
}
/* /*
* We just error out and code execution should never reach to this * We just error out and code execution should never reach to this

View File

@ -36,11 +36,12 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
/* /*
* We only support some of the PostgreSQL supported RENAME statements, and * We only support some of the PostgreSQL supported RENAME statements, and
* our list include only renaming table and index (related) objects. * our list include only renaming table, index, policy and view (related) objects.
*/ */
if (!IsAlterTableRenameStmt(renameStmt) && if (!IsAlterTableRenameStmt(renameStmt) &&
!IsIndexRenameStmt(renameStmt) && !IsIndexRenameStmt(renameStmt) &&
!IsPolicyRenameStmt(renameStmt)) !IsPolicyRenameStmt(renameStmt) &&
!IsViewRenameStmt(renameStmt))
{ {
return NIL; return NIL;
} }
@ -48,7 +49,7 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
/* /*
* The lock levels here should be same as the ones taken in * The lock levels here should be same as the ones taken in
* RenameRelation(), renameatt() and RenameConstraint(). However, since all * RenameRelation(), renameatt() and RenameConstraint(). However, since all
* three statements have identical lock levels, we just use a single statement. * four statements have identical lock levels, we just use a single statement.
*/ */
objectRelationId = RangeVarGetRelid(renameStmt->relation, objectRelationId = RangeVarGetRelid(renameStmt->relation,
AccessExclusiveLock, AccessExclusiveLock,
@ -63,14 +64,31 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
return NIL; return NIL;
} }
/* check whether we are dealing with a sequence here */ /*
if (get_rel_relkind(objectRelationId) == RELKIND_SEQUENCE) * Check whether we are dealing with a sequence or view here and route queries
* accordingly to the right processor function. We need to check both objects here
* since PG supports targeting sequences and views with ALTER TABLE commands.
*/
char relKind = get_rel_relkind(objectRelationId);
if (relKind == RELKIND_SEQUENCE)
{ {
RenameStmt *stmtCopy = copyObject(renameStmt); RenameStmt *stmtCopy = copyObject(renameStmt);
stmtCopy->renameType = OBJECT_SEQUENCE; stmtCopy->renameType = OBJECT_SEQUENCE;
return PreprocessRenameSequenceStmt((Node *) stmtCopy, renameCommand, return PreprocessRenameSequenceStmt((Node *) stmtCopy, renameCommand,
processUtilityContext); processUtilityContext);
} }
else if (relKind == RELKIND_VIEW)
{
RenameStmt *stmtCopy = copyObject(renameStmt);
stmtCopy->relationType = OBJECT_VIEW;
if (stmtCopy->renameType == OBJECT_TABLE)
{
stmtCopy->renameType = OBJECT_VIEW;
}
return PreprocessRenameViewStmt((Node *) stmtCopy, renameCommand,
processUtilityContext);
}
/* we have no planning to do unless the table is distributed */ /* we have no planning to do unless the table is distributed */
switch (renameStmt->renameType) switch (renameStmt->renameType)
@ -127,7 +145,7 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
} }
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = tableRelationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, tableRelationId);
ddlJob->metadataSyncCommand = renameCommand; ddlJob->metadataSyncCommand = renameCommand;
ddlJob->taskList = DDLTaskList(tableRelationId, renameCommand); ddlJob->taskList = DDLTaskList(tableRelationId, renameCommand);

View File

@ -150,7 +150,7 @@ PostprocessAlterRoleStmt(Node *node, const char *queryString)
if (encryptedPassword != NULL) if (encryptedPassword != NULL)
{ {
Value *encryptedPasswordValue = makeString((char *) encryptedPassword); String *encryptedPasswordValue = makeString((char *) encryptedPassword);
option->arg = (Node *) encryptedPasswordValue; option->arg = (Node *) encryptedPasswordValue;
} }
else else
@ -741,8 +741,13 @@ makeStringConst(char *str, int location)
{ {
A_Const *n = makeNode(A_Const); A_Const *n = makeNode(A_Const);
#if PG_VERSION_NUM >= PG_VERSION_15
n->val.sval.type = T_String;
n->val.sval.sval = str;
#else
n->val.type = T_String; n->val.type = T_String;
n->val.val.str = str; n->val.val.str = str;
#endif
n->location = location; n->location = location;
return (Node *) n; return (Node *) n;
@ -759,8 +764,13 @@ makeIntConst(int val, int location)
{ {
A_Const *n = makeNode(A_Const); A_Const *n = makeNode(A_Const);
#if PG_VERSION_NUM >= PG_VERSION_15
n->val.ival.type = T_Integer;
n->val.ival.ival = val;
#else
n->val.type = T_Integer; n->val.type = T_Integer;
n->val.val.ival = val; n->val.val.ival = val;
#endif
n->location = location; n->location = location;
return (Node *) n; return (Node *) n;
@ -777,8 +787,13 @@ makeFloatConst(char *str, int location)
{ {
A_Const *n = makeNode(A_Const); A_Const *n = makeNode(A_Const);
#if PG_VERSION_NUM >= PG_VERSION_15
n->val.fval.type = T_Float;
n->val.fval.fval = str;
#else
n->val.type = T_Float; n->val.type = T_Float;
n->val.val.str = str; n->val.val.str = str;
#endif
n->location = location; n->location = location;
return (Node *) n; return (Node *) n;

View File

@ -107,7 +107,7 @@ PreprocessDropSchemaStmt(Node *node, const char *queryString,
EnsureSequentialMode(OBJECT_SCHEMA); EnsureSequentialMode(OBJECT_SCHEMA);
Value *schemaVal = NULL; String *schemaVal = NULL;
foreach_ptr(schemaVal, distributedSchemas) foreach_ptr(schemaVal, distributedSchemas)
{ {
if (SchemaHasDistributedTableWithFKey(strVal(schemaVal))) if (SchemaHasDistributedTableWithFKey(strVal(schemaVal)))
@ -182,43 +182,6 @@ PreprocessGrantOnSchemaStmt(Node *node, const char *queryString,
} }
/*
* PreprocessAlterSchemaRenameStmt is called when the user is renaming a schema.
* The invocation happens before the statement is applied locally.
*
* As the schema already exists we have access to the ObjectAddress for the schema, this
* is used to check if the schmea is distributed. If the schema is distributed the rename
* is executed on all the workers to keep the schemas in sync across the cluster.
*/
List *
PreprocessAlterSchemaRenameStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
ObjectAddress schemaAddress = GetObjectAddressFromParseTree(node, false);
if (!ShouldPropagateObject(&schemaAddress))
{
return NIL;
}
EnsureCoordinator();
/* fully qualify */
QualifyTreeNode(node);
/* deparse sql*/
const char *renameStmtSql = DeparseTreeNode(node);
EnsureSequentialMode(OBJECT_SCHEMA);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) renameStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/* /*
* CreateSchemaStmtObjectAddress returns the ObjectAddress of the schema that is * CreateSchemaStmtObjectAddress returns the ObjectAddress of the schema that is
* the object of the CreateSchemaStmt. Errors if missing_ok is false. * the object of the CreateSchemaStmt. Errors if missing_ok is false.
@ -288,7 +251,7 @@ FilterDistributedSchemas(List *schemas)
{ {
List *distributedSchemas = NIL; List *distributedSchemas = NIL;
Value *schemaValue = NULL; String *schemaValue = NULL;
foreach_ptr(schemaValue, schemas) foreach_ptr(schemaValue, schemas)
{ {
const char *schemaName = strVal(schemaValue); const char *schemaName = strVal(schemaValue);

View File

@ -226,7 +226,6 @@ PreprocessDropSequenceStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext) ProcessUtilityContext processUtilityContext)
{ {
DropStmt *stmt = castNode(DropStmt, node); DropStmt *stmt = castNode(DropStmt, node);
List *deletingSequencesList = stmt->objects;
List *distributedSequencesList = NIL; List *distributedSequencesList = NIL;
List *distributedSequenceAddresses = NIL; List *distributedSequenceAddresses = NIL;
@ -259,6 +258,7 @@ PreprocessDropSequenceStmt(Node *node, const char *queryString,
* iterate over all sequences to be dropped and filter to keep only distributed * iterate over all sequences to be dropped and filter to keep only distributed
* sequences. * sequences.
*/ */
List *deletingSequencesList = stmt->objects;
List *objectNameList = NULL; List *objectNameList = NULL;
foreach_ptr(objectNameList, deletingSequencesList) foreach_ptr(objectNameList, deletingSequencesList)
{ {

View File

@ -92,7 +92,7 @@ PreprocessCreateStatisticsStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false; ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand; ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand); ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -197,7 +197,7 @@ PreprocessDropStatisticsStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false; ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand; ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand); ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -236,7 +236,7 @@ PreprocessAlterStatisticsRenameStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false; ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand; ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand); ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -274,7 +274,7 @@ PreprocessAlterStatisticsSchemaStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false; ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand; ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand); ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -295,7 +295,7 @@ PostprocessAlterStatisticsSchemaStmt(Node *node, const char *queryString)
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_STATISTIC_EXT); Assert(stmt->objectType == OBJECT_STATISTIC_EXT);
Value *statName = llast((List *) stmt->object); String *statName = llast((List *) stmt->object);
Oid statsOid = get_statistics_object_oid(list_make2(makeString(stmt->newschema), Oid statsOid = get_statistics_object_oid(list_make2(makeString(stmt->newschema),
statName), false); statName), false);
Oid relationId = GetRelIdByStatsOid(statsOid); Oid relationId = GetRelIdByStatsOid(statsOid);
@ -328,7 +328,7 @@ AlterStatisticsSchemaStmtObjectAddress(Node *node, bool missingOk)
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
ObjectAddress address = { 0 }; ObjectAddress address = { 0 };
Value *statName = llast((List *) stmt->object); String *statName = llast((List *) stmt->object);
Oid statsOid = get_statistics_object_oid(list_make2(makeString(stmt->newschema), Oid statsOid = get_statistics_object_oid(list_make2(makeString(stmt->newschema),
statName), missingOk); statName), missingOk);
ObjectAddressSet(address, StatisticExtRelationId, statsOid); ObjectAddressSet(address, StatisticExtRelationId, statsOid);
@ -376,7 +376,7 @@ PreprocessAlterStatisticsStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false; ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand; ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand); ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -416,7 +416,7 @@ PreprocessAlterStatisticsOwnerStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false; ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand; ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand); ddlJob->taskList = DDLTaskList(relationId, ddlCommand);

View File

@ -651,12 +651,21 @@ PostprocessAlterTableSchemaStmt(Node *node, const char *queryString)
*/ */
ObjectAddress tableAddress = GetObjectAddressFromParseTree((Node *) stmt, true); ObjectAddress tableAddress = GetObjectAddressFromParseTree((Node *) stmt, true);
/* check whether we are dealing with a sequence here */ /*
if (get_rel_relkind(tableAddress.objectId) == RELKIND_SEQUENCE) * Check whether we are dealing with a sequence or view here and route queries
* accordingly to the right processor function.
*/
char relKind = get_rel_relkind(tableAddress.objectId);
if (relKind == RELKIND_SEQUENCE)
{ {
stmt->objectType = OBJECT_SEQUENCE; stmt->objectType = OBJECT_SEQUENCE;
return PostprocessAlterSequenceSchemaStmt((Node *) stmt, queryString); return PostprocessAlterSequenceSchemaStmt((Node *) stmt, queryString);
} }
else if (relKind == RELKIND_VIEW)
{
stmt->objectType = OBJECT_VIEW;
return PostprocessAlterViewSchemaStmt((Node *) stmt, queryString);
}
if (!ShouldPropagate() || !IsCitusTable(tableAddress.objectId)) if (!ShouldPropagate() || !IsCitusTable(tableAddress.objectId))
{ {
@ -699,18 +708,26 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand,
} }
/* /*
* check whether we are dealing with a sequence here * check whether we are dealing with a sequence or view here
* if yes, it must be ALTER TABLE .. OWNER TO .. command * if yes, it must be ALTER TABLE .. OWNER TO .. command
* since this is the only ALTER command of a sequence that * since this is the only ALTER command of a sequence or view that
* passes through an AlterTableStmt * passes through an AlterTableStmt
*/ */
if (get_rel_relkind(leftRelationId) == RELKIND_SEQUENCE) char relKind = get_rel_relkind(leftRelationId);
if (relKind == RELKIND_SEQUENCE)
{ {
AlterTableStmt *stmtCopy = copyObject(alterTableStatement); AlterTableStmt *stmtCopy = copyObject(alterTableStatement);
AlterTableStmtObjType_compat(stmtCopy) = OBJECT_SEQUENCE; AlterTableStmtObjType_compat(stmtCopy) = OBJECT_SEQUENCE;
return PreprocessAlterSequenceOwnerStmt((Node *) stmtCopy, alterTableCommand, return PreprocessAlterSequenceOwnerStmt((Node *) stmtCopy, alterTableCommand,
processUtilityContext); processUtilityContext);
} }
else if (relKind == RELKIND_VIEW)
{
AlterTableStmt *stmtCopy = copyObject(alterTableStatement);
AlterTableStmtObjType_compat(stmtCopy) = OBJECT_VIEW;
return PreprocessAlterViewStmt((Node *) stmtCopy, alterTableCommand,
processUtilityContext);
}
/* /*
* AlterTableStmt applies also to INDEX relations, and we have support for * AlterTableStmt applies also to INDEX relations, and we have support for
@ -1102,7 +1119,7 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand,
/* fill them here as it is possible to use them in some conditional blocks below */ /* fill them here as it is possible to use them in some conditional blocks below */
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = leftRelationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, leftRelationId);
const char *sqlForTaskList = alterTableCommand; const char *sqlForTaskList = alterTableCommand;
if (deparseAT) if (deparseAT)
@ -1758,18 +1775,31 @@ PreprocessAlterTableSchemaStmt(Node *node, const char *queryString,
{ {
return NIL; return NIL;
} }
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
stmt->missing_ok); stmt->missing_ok);
Oid relationId = address.objectId; Oid relationId = address.objectId;
/* check whether we are dealing with a sequence here */ /*
if (get_rel_relkind(relationId) == RELKIND_SEQUENCE) * Check whether we are dealing with a sequence or view here and route queries
* accordingly to the right processor function. We need to check both objects here
* since PG supports targeting sequences and views with ALTER TABLE commands.
*/
char relKind = get_rel_relkind(relationId);
if (relKind == RELKIND_SEQUENCE)
{ {
AlterObjectSchemaStmt *stmtCopy = copyObject(stmt); AlterObjectSchemaStmt *stmtCopy = copyObject(stmt);
stmtCopy->objectType = OBJECT_SEQUENCE; stmtCopy->objectType = OBJECT_SEQUENCE;
return PreprocessAlterSequenceSchemaStmt((Node *) stmtCopy, queryString, return PreprocessAlterSequenceSchemaStmt((Node *) stmtCopy, queryString,
processUtilityContext); processUtilityContext);
} }
else if (relKind == RELKIND_VIEW)
{
AlterObjectSchemaStmt *stmtCopy = copyObject(stmt);
stmtCopy->objectType = OBJECT_VIEW;
return PreprocessAlterViewSchemaStmt((Node *) stmtCopy, queryString,
processUtilityContext);
}
/* first check whether a distributed relation is affected */ /* first check whether a distributed relation is affected */
if (!OidIsValid(relationId) || !IsCitusTable(relationId)) if (!OidIsValid(relationId) || !IsCitusTable(relationId))
@ -1779,7 +1809,7 @@ PreprocessAlterTableSchemaStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
QualifyTreeNode((Node *) stmt); QualifyTreeNode((Node *) stmt);
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = DeparseTreeNode((Node *) stmt); ddlJob->metadataSyncCommand = DeparseTreeNode((Node *) stmt);
ddlJob->taskList = DDLTaskList(relationId, ddlJob->metadataSyncCommand); ddlJob->taskList = DDLTaskList(relationId, ddlJob->metadataSyncCommand);
return list_make1(ddlJob); return list_make1(ddlJob);
@ -1939,12 +1969,19 @@ PostprocessAlterTableStmt(AlterTableStmt *alterTableStatement)
* since this is the only ALTER command of a sequence that * since this is the only ALTER command of a sequence that
* passes through an AlterTableStmt * passes through an AlterTableStmt
*/ */
if (get_rel_relkind(relationId) == RELKIND_SEQUENCE) char relKind = get_rel_relkind(relationId);
if (relKind == RELKIND_SEQUENCE)
{ {
AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_SEQUENCE; AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_SEQUENCE;
PostprocessAlterSequenceOwnerStmt((Node *) alterTableStatement, NULL); PostprocessAlterSequenceOwnerStmt((Node *) alterTableStatement, NULL);
return; return;
} }
else if (relKind == RELKIND_VIEW)
{
AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_VIEW;
PostprocessAlterViewStmt((Node *) alterTableStatement, NULL);
return;
}
/* /*
* Before ensuring each dependency exist, update dependent sequences * Before ensuring each dependency exist, update dependent sequences

View File

@ -42,8 +42,6 @@
#include "distributed/worker_create_or_replace.h" #include "distributed/worker_create_or_replace.h"
static List * GetDistributedTextSearchConfigurationNames(DropStmt *stmt);
static List * GetDistributedTextSearchDictionaryNames(DropStmt *stmt);
static DefineStmt * GetTextSearchConfigDefineStmt(Oid tsconfigOid); static DefineStmt * GetTextSearchConfigDefineStmt(Oid tsconfigOid);
static DefineStmt * GetTextSearchDictionaryDefineStmt(Oid tsdictOid); static DefineStmt * GetTextSearchDictionaryDefineStmt(Oid tsdictOid);
static List * GetTextSearchDictionaryInitOptions(HeapTuple tup, Form_pg_ts_dict dict); static List * GetTextSearchDictionaryInitOptions(HeapTuple tup, Form_pg_ts_dict dict);
@ -59,113 +57,6 @@ static List * get_ts_template_namelist(Oid tstemplateOid);
static Oid get_ts_config_parser_oid(Oid tsconfigOid); static Oid get_ts_config_parser_oid(Oid tsconfigOid);
static char * get_ts_parser_tokentype_name(Oid parserOid, int32 tokentype); static char * get_ts_parser_tokentype_name(Oid parserOid, int32 tokentype);
/*
* PostprocessCreateTextSearchConfigurationStmt is called after the TEXT SEARCH
* CONFIGURATION has been created locally.
*
* Contrary to many other objects a text search configuration is often created as a copy
* of an existing configuration. After the copy there is no relation to the configuration
* that has been copied. This prevents our normal approach of ensuring dependencies to
* exist before forwarding a close ressemblance of the statement the user executed.
*
* Instead we recreate the object based on what we find in our own catalog, hence the
* amount of work we perform in the postprocess function, contrary to other objects.
*/
List *
PostprocessCreateTextSearchConfigurationStmt(Node *node, const char *queryString)
{
DefineStmt *stmt = castNode(DefineStmt, node);
Assert(stmt->kind == OBJECT_TSCONFIGURATION);
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(&address);
if (errMsg != NULL)
{
RaiseDeferredError(errMsg, WARNING);
return NIL;
}
EnsureDependenciesExistOnAllNodes(&address);
/*
* TEXT SEARCH CONFIGURATION objects are more complex with their mappings and the
* possibility of copying from existing templates that we will require the idempotent
* recreation commands to be run for successful propagation
*/
List *commands = CreateTextSearchConfigDDLCommandsIdempotent(&address);
commands = lcons(DISABLE_DDL_PROPAGATION, commands);
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessCreateTextSearchDictionaryStmt is called after the TEXT SEARCH DICTIONARY has been
* created locally.
*/
List *
PostprocessCreateTextSearchDictionaryStmt(Node *node, const char *queryString)
{
DefineStmt *stmt = castNode(DefineStmt, node);
Assert(stmt->kind == OBJECT_TSDICTIONARY);
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSDICTIONARY);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(&address);
if (errMsg != NULL)
{
RaiseDeferredError(errMsg, WARNING);
return NIL;
}
EnsureDependenciesExistOnAllNodes(&address);
QualifyTreeNode(node);
const char *createTSDictionaryStmtSql = DeparseTreeNode(node);
/*
* To prevent recursive propagation in mx architecture, we disable ddl
* propagation before sending the command to workers.
*/
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) createTSDictionaryStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
List * List *
GetCreateTextSearchConfigStatements(const ObjectAddress *address) GetCreateTextSearchConfigStatements(const ObjectAddress *address)
{ {
@ -234,602 +125,6 @@ CreateTextSearchDictDDLCommandsIdempotent(const ObjectAddress *address)
} }
/*
* PreprocessDropTextSearchConfigurationStmt prepares the statements we need to send to
* the workers. After we have dropped the configurations locally they also got removed from
* pg_dist_object so it is important to do all distribution checks before the change is
* made locally.
*/
List *
PreprocessDropTextSearchConfigurationStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
Assert(stmt->removeType == OBJECT_TSCONFIGURATION);
if (!ShouldPropagate())
{
return NIL;
}
List *distributedObjects = GetDistributedTextSearchConfigurationNames(stmt);
if (list_length(distributedObjects) == 0)
{
/* no distributed objects to remove */
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
/*
* Temporarily replace the list of objects being dropped with only the list
* containing the distributed objects. After we have created the sql statement we
* restore the original list of objects to execute on locally.
*
* Because searchpaths on coordinator and workers might not be in sync we fully
* qualify the list before deparsing. This is safe because qualification doesn't
* change the original names in place, but insteads creates new ones.
*/
List *originalObjects = stmt->objects;
stmt->objects = distributedObjects;
QualifyTreeNode((Node *) stmt);
const char *dropStmtSql = DeparseTreeNode((Node *) stmt);
stmt->objects = originalObjects;
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessDropTextSearchDictionaryStmt prepares the statements we need to send to
* the workers. After we have dropped the dictionaries locally they also got removed from
* pg_dist_object so it is important to do all distribution checks before the change is
* made locally.
*/
List *
PreprocessDropTextSearchDictionaryStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
Assert(stmt->removeType == OBJECT_TSDICTIONARY);
if (!ShouldPropagate())
{
return NIL;
}
List *distributedObjects = GetDistributedTextSearchDictionaryNames(stmt);
if (list_length(distributedObjects) == 0)
{
/* no distributed objects to remove */
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSDICTIONARY);
/*
* Temporarily replace the list of objects being dropped with only the list
* containing the distributed objects. After we have created the sql statement we
* restore the original list of objects to execute on locally.
*
* Because searchpaths on coordinator and workers might not be in sync we fully
* qualify the list before deparsing. This is safe because qualification doesn't
* change the original names in place, but insteads creates new ones.
*/
List *originalObjects = stmt->objects;
stmt->objects = distributedObjects;
QualifyTreeNode((Node *) stmt);
const char *dropStmtSql = DeparseTreeNode((Node *) stmt);
stmt->objects = originalObjects;
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* GetDistributedTextSearchConfigurationNames iterates over all text search configurations
* dropped, and create a list containing all configurations that are distributed.
*/
static List *
GetDistributedTextSearchConfigurationNames(DropStmt *stmt)
{
List *objName = NULL;
List *distributedObjects = NIL;
foreach_ptr(objName, stmt->objects)
{
Oid tsconfigOid = get_ts_config_oid(objName, stmt->missing_ok);
if (!OidIsValid(tsconfigOid))
{
/* skip missing configuration names, they can't be distributed */
continue;
}
ObjectAddress address = { 0 };
ObjectAddressSet(address, TSConfigRelationId, tsconfigOid);
if (!IsObjectDistributed(&address))
{
continue;
}
distributedObjects = lappend(distributedObjects, objName);
}
return distributedObjects;
}
/*
* GetDistributedTextSearchDictionaryNames iterates over all text search dictionaries
* dropped, and create a list containing all dictionaries that are distributed.
*/
static List *
GetDistributedTextSearchDictionaryNames(DropStmt *stmt)
{
List *objName = NULL;
List *distributedObjects = NIL;
foreach_ptr(objName, stmt->objects)
{
Oid tsdictOid = get_ts_dict_oid(objName, stmt->missing_ok);
if (!OidIsValid(tsdictOid))
{
/* skip missing dictionary names, they can't be distributed */
continue;
}
ObjectAddress address = { 0 };
ObjectAddressSet(address, TSDictionaryRelationId, tsdictOid);
if (!IsObjectDistributed(&address))
{
continue;
}
distributedObjects = lappend(distributedObjects, objName);
}
return distributedObjects;
}
/*
* PreprocessAlterTextSearchConfigurationStmt verifies if the configuration being altered
* is distributed in the cluster. If that is the case it will prepare the list of commands
* to send to the worker to apply the same changes remote.
*/
List *
PreprocessAlterTextSearchConfigurationStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterTSConfigurationStmt *stmt = castNode(AlterTSConfigurationStmt, node);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
QualifyTreeNode((Node *) stmt);
const char *alterStmtSql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) alterStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterTextSearchDictionaryStmt verifies if the dictionary being altered is
* distributed in the cluster. If that is the case it will prepare the list of commands to
* send to the worker to apply the same changes remote.
*/
List *
PreprocessAlterTextSearchDictionaryStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterTSDictionaryStmt *stmt = castNode(AlterTSDictionaryStmt, node);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSDICTIONARY);
QualifyTreeNode((Node *) stmt);
const char *alterStmtSql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) alterStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessRenameTextSearchConfigurationStmt verifies if the configuration being altered
* is distributed in the cluster. If that is the case it will prepare the list of commands
* to send to the worker to apply the same changes remote.
*/
List *
PreprocessRenameTextSearchConfigurationStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_TSCONFIGURATION);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
QualifyTreeNode((Node *) stmt);
char *ddlCommand = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) ddlCommand,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessRenameTextSearchDictionaryStmt verifies if the dictionary being altered
* is distributed in the cluster. If that is the case it will prepare the list of commands
* to send to the worker to apply the same changes remote.
*/
List *
PreprocessRenameTextSearchDictionaryStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_TSDICTIONARY);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSDICTIONARY);
QualifyTreeNode((Node *) stmt);
char *ddlCommand = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) ddlCommand,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterTextSearchConfigurationSchemaStmt verifies if the configuration being
* altered is distributed in the cluster. If that is the case it will prepare the list of
* commands to send to the worker to apply the same changes remote.
*/
List *
PreprocessAlterTextSearchConfigurationSchemaStmt(Node *node, const char *queryString,
ProcessUtilityContext
processUtilityContext)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
stmt->missing_ok);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterTextSearchDictionarySchemaStmt verifies if the dictionary being
* altered is distributed in the cluster. If that is the case it will prepare the list of
* commands to send to the worker to apply the same changes remote.
*/
List *
PreprocessAlterTextSearchDictionarySchemaStmt(Node *node, const char *queryString,
ProcessUtilityContext
processUtilityContext)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
stmt->missing_ok);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSDICTIONARY);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterTextSearchConfigurationSchemaStmt is invoked after the schema has been
* changed locally. Since changing the schema could result in new dependencies being found
* for this object we re-ensure all the dependencies for the configuration do exist. This
* is solely to propagate the new schema (and all its dependencies) if it was not already
* distributed in the cluster.
*/
List *
PostprocessAlterTextSearchConfigurationSchemaStmt(Node *node, const char *queryString)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
stmt->missing_ok);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
/* dependencies have changed (schema) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* PostprocessAlterTextSearchDictionarySchemaStmt is invoked after the schema has been
* changed locally. Since changing the schema could result in new dependencies being found
* for this object we re-ensure all the dependencies for the dictionary do exist. This
* is solely to propagate the new schema (and all its dependencies) if it was not already
* distributed in the cluster.
*/
List *
PostprocessAlterTextSearchDictionarySchemaStmt(Node *node, const char *queryString)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
stmt->missing_ok);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
/* dependencies have changed (schema) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* PreprocessTextSearchConfigurationCommentStmt propagates any comment on a distributed
* configuration to the workers. Since comments for configurations are promenently shown
* when listing all text search configurations this is purely a cosmetic thing when
* running in MX.
*/
List *
PreprocessTextSearchConfigurationCommentStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
CommentStmt *stmt = castNode(CommentStmt, node);
Assert(stmt->objtype == OBJECT_TSCONFIGURATION);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessTextSearchDictionaryCommentStmt propagates any comment on a distributed
* dictionary to the workers. Since comments for dictionaries are promenently shown
* when listing all text search dictionaries this is purely a cosmetic thing when
* running in MX.
*/
List *
PreprocessTextSearchDictionaryCommentStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
CommentStmt *stmt = castNode(CommentStmt, node);
Assert(stmt->objtype == OBJECT_TSDICTIONARY);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSDICTIONARY);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterTextSearchConfigurationOwnerStmt verifies if the configuration being
* altered is distributed in the cluster. If that is the case it will prepare the list of
* commands to send to the worker to apply the same changes remote.
*/
List *
PreprocessAlterTextSearchConfigurationOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext
processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
QualifyTreeNode((Node *) stmt);
char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterTextSearchDictionaryOwnerStmt verifies if the dictionary being
* altered is distributed in the cluster. If that is the case it will prepare the list of
* commands to send to the worker to apply the same changes remote.
*/
List *
PreprocessAlterTextSearchDictionaryOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext
processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_TSDICTIONARY);
QualifyTreeNode((Node *) stmt);
char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterTextSearchConfigurationOwnerStmt is invoked after the owner has been
* changed locally. Since changing the owner could result in new dependencies being found
* for this object we re-ensure all the dependencies for the configuration do exist. This
* is solely to propagate the new owner (and all its dependencies) if it was not already
* distributed in the cluster.
*/
List *
PostprocessAlterTextSearchConfigurationOwnerStmt(Node *node, const char *queryString)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
/* dependencies have changed (owner) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* PostprocessAlterTextSearchDictionaryOwnerStmt is invoked after the owner has been
* changed locally. Since changing the owner could result in new dependencies being found
* for this object we re-ensure all the dependencies for the dictionary do exist. This
* is solely to propagate the new owner (and all its dependencies) if it was not already
* distributed in the cluster.
*/
List *
PostprocessAlterTextSearchDictionaryOwnerStmt(Node *node, const char *queryString)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
/* dependencies have changed (owner) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/* /*
* GetTextSearchConfigDefineStmt returns the DefineStmt for a TEXT SEARCH CONFIGURATION * GetTextSearchConfigDefineStmt returns the DefineStmt for a TEXT SEARCH CONFIGURATION
* based on the configuration as defined in the catalog identified by tsconfigOid. * based on the configuration as defined in the catalog identified by tsconfigOid.

View File

@ -44,8 +44,8 @@
/* local function forward declarations */ /* local function forward declarations */
static bool IsCreateCitusTruncateTriggerStmt(CreateTrigStmt *createTriggerStmt); static bool IsCreateCitusTruncateTriggerStmt(CreateTrigStmt *createTriggerStmt);
static Value * GetAlterTriggerDependsTriggerNameValue(AlterObjectDependsStmt * static String * GetAlterTriggerDependsTriggerNameValue(AlterObjectDependsStmt *
alterTriggerDependsStmt); alterTriggerDependsStmt);
static void ErrorIfUnsupportedDropTriggerCommand(DropStmt *dropTriggerStmt); static void ErrorIfUnsupportedDropTriggerCommand(DropStmt *dropTriggerStmt);
static RangeVar * GetDropTriggerStmtRelation(DropStmt *dropTriggerStmt); static RangeVar * GetDropTriggerStmtRelation(DropStmt *dropTriggerStmt);
static void ExtractDropStmtTriggerAndRelationName(DropStmt *dropTriggerStmt, static void ExtractDropStmtTriggerAndRelationName(DropStmt *dropTriggerStmt,
@ -416,7 +416,7 @@ PreprocessAlterTriggerDependsStmt(Node *node, const char *queryString,
* workers * workers
*/ */
Value *triggerNameValue = String *triggerNameValue =
GetAlterTriggerDependsTriggerNameValue(alterTriggerDependsStmt); GetAlterTriggerDependsTriggerNameValue(alterTriggerDependsStmt);
ereport(ERROR, (errmsg( ereport(ERROR, (errmsg(
"Triggers \"%s\" on distributed tables and local tables added to metadata " "Triggers \"%s\" on distributed tables and local tables added to metadata "
@ -454,7 +454,7 @@ PostprocessAlterTriggerDependsStmt(Node *node, const char *queryString)
EnsureCoordinator(); EnsureCoordinator();
ErrorOutForTriggerIfNotSupported(relationId); ErrorOutForTriggerIfNotSupported(relationId);
Value *triggerNameValue = String *triggerNameValue =
GetAlterTriggerDependsTriggerNameValue(alterTriggerDependsStmt); GetAlterTriggerDependsTriggerNameValue(alterTriggerDependsStmt);
return CitusCreateTriggerCommandDDLJob(relationId, strVal(triggerNameValue), return CitusCreateTriggerCommandDDLJob(relationId, strVal(triggerNameValue),
queryString); queryString);
@ -476,7 +476,7 @@ AlterTriggerDependsEventExtendNames(AlterObjectDependsStmt *alterTriggerDependsS
char **relationName = &(relation->relname); char **relationName = &(relation->relname);
AppendShardIdToName(relationName, shardId); AppendShardIdToName(relationName, shardId);
Value *triggerNameValue = String *triggerNameValue =
GetAlterTriggerDependsTriggerNameValue(alterTriggerDependsStmt); GetAlterTriggerDependsTriggerNameValue(alterTriggerDependsStmt);
AppendShardIdToName(&strVal(triggerNameValue), shardId); AppendShardIdToName(&strVal(triggerNameValue), shardId);
@ -489,7 +489,7 @@ AlterTriggerDependsEventExtendNames(AlterObjectDependsStmt *alterTriggerDependsS
* GetAlterTriggerDependsTriggerName returns Value object for the trigger * GetAlterTriggerDependsTriggerName returns Value object for the trigger
* name that given AlterObjectDependsStmt is executed for. * name that given AlterObjectDependsStmt is executed for.
*/ */
static Value * static String *
GetAlterTriggerDependsTriggerNameValue(AlterObjectDependsStmt *alterTriggerDependsStmt) GetAlterTriggerDependsTriggerNameValue(AlterObjectDependsStmt *alterTriggerDependsStmt)
{ {
List *triggerObjectNameList = (List *) alterTriggerDependsStmt->object; List *triggerObjectNameList = (List *) alterTriggerDependsStmt->object;
@ -503,7 +503,7 @@ GetAlterTriggerDependsTriggerNameValue(AlterObjectDependsStmt *alterTriggerDepen
* be the name of the trigger in either before or after standard process * be the name of the trigger in either before or after standard process
* utility. * utility.
*/ */
Value *triggerNameValue = llast(triggerObjectNameList); String *triggerNameValue = llast(triggerObjectNameList);
return triggerNameValue; return triggerNameValue;
} }
@ -642,12 +642,12 @@ DropTriggerEventExtendNames(DropStmt *dropTriggerStmt, char *schemaName, uint64
ExtractDropStmtTriggerAndRelationName(dropTriggerStmt, &triggerName, &relationName); ExtractDropStmtTriggerAndRelationName(dropTriggerStmt, &triggerName, &relationName);
AppendShardIdToName(&triggerName, shardId); AppendShardIdToName(&triggerName, shardId);
Value *triggerNameValue = makeString(triggerName); String *triggerNameValue = makeString(triggerName);
AppendShardIdToName(&relationName, shardId); AppendShardIdToName(&relationName, shardId);
Value *relationNameValue = makeString(relationName); String *relationNameValue = makeString(relationName);
Value *schemaNameValue = makeString(pstrdup(schemaName)); String *schemaNameValue = makeString(pstrdup(schemaName));
List *shardTriggerNameList = List *shardTriggerNameList =
list_make3(schemaNameValue, relationNameValue, triggerNameValue); list_make3(schemaNameValue, relationNameValue, triggerNameValue);
@ -712,7 +712,7 @@ CitusCreateTriggerCommandDDLJob(Oid relationId, char *triggerName,
const char *queryString) const char *queryString)
{ {
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = queryString; ddlJob->metadataSyncCommand = queryString;
if (!triggerName) if (!triggerName)

View File

@ -40,15 +40,10 @@
#include "utils/rel.h" #include "utils/rel.h"
#define LOCK_RELATION_IF_EXISTS "SELECT lock_relation_if_exists(%s, '%s');"
/* Local functions forward declarations for unsupported command checks */ /* Local functions forward declarations for unsupported command checks */
static void ErrorIfUnsupportedTruncateStmt(TruncateStmt *truncateStatement); static void ErrorIfUnsupportedTruncateStmt(TruncateStmt *truncateStatement);
static void ExecuteTruncateStmtSequentialIfNecessary(TruncateStmt *command); static void ExecuteTruncateStmtSequentialIfNecessary(TruncateStmt *command);
static void EnsurePartitionTableNotReplicatedForTruncate(TruncateStmt *truncateStatement); static void EnsurePartitionTableNotReplicatedForTruncate(TruncateStmt *truncateStatement);
static void LockTruncatedRelationMetadataInWorkers(TruncateStmt *truncateStatement);
static void AcquireDistributedLockOnRelations(List *relationIdList, LOCKMODE lockMode);
static List * TruncateTaskList(Oid relationId); static List * TruncateTaskList(Oid relationId);
@ -248,7 +243,13 @@ PreprocessTruncateStatement(TruncateStmt *truncateStatement)
ErrorIfUnsupportedTruncateStmt(truncateStatement); ErrorIfUnsupportedTruncateStmt(truncateStatement);
EnsurePartitionTableNotReplicatedForTruncate(truncateStatement); EnsurePartitionTableNotReplicatedForTruncate(truncateStatement);
ExecuteTruncateStmtSequentialIfNecessary(truncateStatement); ExecuteTruncateStmtSequentialIfNecessary(truncateStatement);
LockTruncatedRelationMetadataInWorkers(truncateStatement);
uint32 lockAcquiringMode = truncateStatement->behavior == DROP_CASCADE ?
DIST_LOCK_REFERENCING_TABLES :
DIST_LOCK_DEFAULT;
AcquireDistributedLockOnRelations(truncateStatement->relations, AccessExclusiveLock,
lockAcquiringMode);
} }
@ -345,131 +346,3 @@ ExecuteTruncateStmtSequentialIfNecessary(TruncateStmt *command)
} }
} }
} }
/*
* LockTruncatedRelationMetadataInWorkers determines if distributed
* lock is necessary for truncated relations, and acquire locks.
*
* LockTruncatedRelationMetadataInWorkers handles distributed locking
* of truncated tables before standard utility takes over.
*
* Actual distributed truncation occurs inside truncate trigger.
*
* This is only for distributed serialization of truncate commands.
* The function assumes that there is no foreign key relation between
* non-distributed and distributed relations.
*/
static void
LockTruncatedRelationMetadataInWorkers(TruncateStmt *truncateStatement)
{
List *distributedRelationList = NIL;
/* nothing to do if there is no metadata at worker nodes */
if (!ClusterHasKnownMetadataWorkers())
{
return;
}
RangeVar *rangeVar = NULL;
foreach_ptr(rangeVar, truncateStatement->relations)
{
Oid relationId = RangeVarGetRelid(rangeVar, NoLock, false);
Oid referencingRelationId = InvalidOid;
if (!IsCitusTable(relationId))
{
continue;
}
if (list_member_oid(distributedRelationList, relationId))
{
continue;
}
distributedRelationList = lappend_oid(distributedRelationList, relationId);
CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId);
Assert(cacheEntry != NULL);
List *referencingTableList = cacheEntry->referencingRelationsViaForeignKey;
foreach_oid(referencingRelationId, referencingTableList)
{
distributedRelationList = list_append_unique_oid(distributedRelationList,
referencingRelationId);
}
}
if (distributedRelationList != NIL)
{
AcquireDistributedLockOnRelations(distributedRelationList, AccessExclusiveLock);
}
}
/*
* AcquireDistributedLockOnRelations acquire a distributed lock on worker nodes
* for given list of relations ids. Relation id list and worker node list
* sorted so that the lock is acquired in the same order regardless of which
* node it was run on. Notice that no lock is acquired on coordinator node.
*
* Notice that the locking functions is sent to all workers regardless of if
* it has metadata or not. This is because a worker node only knows itself
* and previous workers that has metadata sync turned on. The node does not
* know about other nodes that have metadata sync turned on afterwards.
*/
static void
AcquireDistributedLockOnRelations(List *relationIdList, LOCKMODE lockMode)
{
Oid relationId = InvalidOid;
List *workerNodeList = ActivePrimaryNodeList(NoLock);
const char *lockModeText = LockModeToLockModeText(lockMode);
/*
* We want to acquire locks in the same order across the nodes.
* Although relation ids may change, their ordering will not.
*/
relationIdList = SortList(relationIdList, CompareOids);
workerNodeList = SortList(workerNodeList, CompareWorkerNodes);
UseCoordinatedTransaction();
int32 localGroupId = GetLocalGroupId();
foreach_oid(relationId, relationIdList)
{
/*
* We only acquire distributed lock on relation if
* the relation is sync'ed between mx nodes.
*
* Even if users disable metadata sync, we cannot
* allow them not to acquire the remote locks.
* Hence, we have !IsCoordinator() check.
*/
if (ShouldSyncTableMetadata(relationId) || !IsCoordinator())
{
char *qualifiedRelationName = generate_qualified_relation_name(relationId);
StringInfo lockRelationCommand = makeStringInfo();
appendStringInfo(lockRelationCommand, LOCK_RELATION_IF_EXISTS,
quote_literal_cstr(qualifiedRelationName),
lockModeText);
WorkerNode *workerNode = NULL;
foreach_ptr(workerNode, workerNodeList)
{
const char *nodeName = workerNode->workerName;
int nodePort = workerNode->workerPort;
/* if local node is one of the targets, acquire the lock locally */
if (workerNode->groupId == localGroupId)
{
LockRelationOid(relationId, lockMode);
continue;
}
SendCommandToWorker(nodeName, nodePort, lockRelationCommand->data);
}
}
}
}

View File

@ -90,8 +90,6 @@
bool EnableCreateTypePropagation = true; bool EnableCreateTypePropagation = true;
/* forward declaration for helper functions*/ /* forward declaration for helper functions*/
static List * FilterNameListForDistributedTypes(List *objects, bool missing_ok);
static List * TypeNameListToObjectAddresses(List *objects);
static TypeName * MakeTypeNameFromRangeVar(const RangeVar *relation); static TypeName * MakeTypeNameFromRangeVar(const RangeVar *relation);
static Oid GetTypeOwner(Oid typeOid); static Oid GetTypeOwner(Oid typeOid);
static Oid LookupNonAssociatedArrayTypeNameOid(ParseState *pstate, static Oid LookupNonAssociatedArrayTypeNameOid(ParseState *pstate,
@ -104,365 +102,6 @@ static List * CompositeTypeColumnDefList(Oid typeOid);
static CreateEnumStmt * RecreateEnumStmt(Oid typeOid); static CreateEnumStmt * RecreateEnumStmt(Oid typeOid);
static List * EnumValsList(Oid typeOid); static List * EnumValsList(Oid typeOid);
static bool ShouldPropagateTypeCreate(void);
/*
* PreprocessCompositeTypeStmt is called during the creation of a composite type. It is executed
* before the statement is applied locally.
*
* We decide if the compisite type needs to be replicated to the worker, and if that is
* the case return a list of DDLJob's that describe how and where the type needs to be
* created.
*
* Since the planning happens before the statement has been applied locally we do not have
* access to the ObjectAddress of the new type.
*/
List *
PreprocessCompositeTypeStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
if (!ShouldPropagateTypeCreate())
{
return NIL;
}
/*
* managing types can only be done on the coordinator if ddl propagation is on. when
* it is off we will never get here
*/
EnsureCoordinator();
/* fully qualify before lookup and later deparsing */
QualifyTreeNode(node);
return NIL;
}
/*
* PostprocessCompositeTypeStmt is executed after the type has been created locally and before
* we create it on the remote servers. Here we have access to the ObjectAddress of the new
* type which we use to make sure the type's dependencies are on all nodes.
*/
List *
PostprocessCompositeTypeStmt(Node *node, const char *queryString)
{
/* same check we perform during planning of the statement */
if (!ShouldPropagateTypeCreate())
{
return NIL;
}
/*
* find object address of the just created object, because the type has been created
* locally it can't be missing
*/
ObjectAddress typeAddress = GetObjectAddressFromParseTree(node, false);
/* If the type has any unsupported dependency, create it locally */
DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(&typeAddress);
if (errMsg != NULL)
{
RaiseDeferredError(errMsg, WARNING);
return NIL;
}
/*
* when we allow propagation within a transaction block we should make sure to only
* allow this in sequential mode
*/
EnsureSequentialMode(OBJECT_TYPE);
EnsureDependenciesExistOnAllNodes(&typeAddress);
/*
* reconstruct creation statement in a portable fashion. The create_or_replace helper
* function will be used to create the type in an idempotent manner on the workers.
*
* Types could exist on the worker prior to being created on the coordinator when the
* type previously has been attempted to be created in a transaction which did not
* commit on the coordinator.
*/
const char *compositeTypeStmtSql = DeparseCompositeTypeStmt(node);
compositeTypeStmtSql = WrapCreateOrReplace(compositeTypeStmtSql);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) compositeTypeStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterTypeStmt is invoked for alter type statements for composite types.
*
* Normally we would have a process step as well to re-ensure dependencies exists, however
* this is already implemented by the post processing for adding columns to tables.
*/
List *
PreprocessAlterTypeStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterTableStmt *stmt = castNode(AlterTableStmt, node);
Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_TYPE);
ObjectAddress typeAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&typeAddress))
{
return NIL;
}
EnsureCoordinator();
/* reconstruct alter statement in a portable fashion */
QualifyTreeNode((Node *) stmt);
const char *alterTypeStmtSql = DeparseTreeNode((Node *) stmt);
/*
* all types that are distributed will need their alter statements propagated
* regardless if in a transaction or not. If we would not propagate the alter
* statement the types would be different on worker and coordinator.
*/
EnsureSequentialMode(OBJECT_TYPE);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) alterTypeStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessCreateEnumStmt is called before the statement gets applied locally.
*
* It decides if the create statement will be applied to the workers and if that is the
* case returns a list of DDLJobs that will be executed _after_ the statement has been
* applied locally.
*
* Since planning is done before we have created the object locally we do not have an
* ObjectAddress for the new type just yet.
*/
List *
PreprocessCreateEnumStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
if (!ShouldPropagateTypeCreate())
{
return NIL;
}
/* managing types can only be done on the coordinator */
EnsureCoordinator();
/* enforce fully qualified typeName for correct deparsing and lookup */
QualifyTreeNode(node);
return NIL;
}
/*
* PostprocessCreateEnumStmt is called after the statement has been applied locally, but
* before the plan on how to create the types on the workers has been executed.
*
* We apply the same checks to verify if the type should be distributed, if that is the
* case we resolve the ObjectAddress for the just created object, distribute its
* dependencies to all the nodes, and mark the object as distributed.
*/
List *
PostprocessCreateEnumStmt(Node *node, const char *queryString)
{
if (!ShouldPropagateTypeCreate())
{
return NIL;
}
/* lookup type address of just created type */
ObjectAddress typeAddress = GetObjectAddressFromParseTree(node, false);
DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(&typeAddress);
if (errMsg != NULL)
{
RaiseDeferredError(errMsg, WARNING);
return NIL;
}
/*
* when we allow propagation within a transaction block we should make sure to only
* allow this in sequential mode
*/
EnsureSequentialMode(OBJECT_TYPE);
EnsureDependenciesExistOnAllNodes(&typeAddress);
/* reconstruct creation statement in a portable fashion */
const char *createEnumStmtSql = DeparseCreateEnumStmt(node);
createEnumStmtSql = WrapCreateOrReplace(createEnumStmtSql);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) createEnumStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterEnumStmt handles ALTER TYPE ... ADD VALUE for enum based types. Planning
* happens before the statement has been applied locally.
*
* Since it is an alter of an existing type we actually have the ObjectAddress. This is
* used to check if the type is distributed, if so the alter will be executed on the
* workers directly to keep the types in sync across the cluster.
*/
List *
PreprocessAlterEnumStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
ObjectAddress typeAddress = GetObjectAddressFromParseTree(node, false);
if (!ShouldPropagateObject(&typeAddress))
{
return NIL;
}
/*
* alter enum will run for all distributed enums, regardless if in a transaction or
* not since the enum will be different on the coordinator and workers if we didn't.
* (adding values to an enum can not run in a transaction anyway and would error by
* postgres already).
*/
EnsureSequentialMode(OBJECT_TYPE);
/*
* managing types can only be done on the coordinator if ddl propagation is on. when
* it is off we will never get here
*/
EnsureCoordinator();
QualifyTreeNode(node);
const char *alterEnumStmtSql = DeparseTreeNode(node);
/*
* Before pg12 ALTER ENUM ... ADD VALUE could not be within a xact block. Instead of
* creating a DDLTaksList we won't return anything here. During the processing phase
* we directly connect to workers and execute the commands remotely.
*/
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) alterEnumStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessDropTypeStmt is called for all DROP TYPE statements. For all types in the list that
* citus has distributed to the workers it will drop the type on the workers as well. If
* no types in the drop list are distributed no calls will be made to the workers.
*/
List *
PreprocessDropTypeStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
/*
* We swap the list of objects to remove during deparse so we need a reference back to
* the old list to put back
*/
List *oldTypes = stmt->objects;
if (!ShouldPropagate())
{
return NIL;
}
List *distributedTypes = FilterNameListForDistributedTypes(oldTypes,
stmt->missing_ok);
if (list_length(distributedTypes) <= 0)
{
/* no distributed types to drop */
return NIL;
}
/*
* managing types can only be done on the coordinator if ddl propagation is on. when
* it is off we will never get here. MX workers don't have a notion of distributed
* types, so we block the call.
*/
EnsureCoordinator();
/*
* remove the entries for the distributed objects on dropping
*/
List *distributedTypeAddresses = TypeNameListToObjectAddresses(distributedTypes);
ObjectAddress *address = NULL;
foreach_ptr(address, distributedTypeAddresses)
{
UnmarkObjectDistributed(address);
}
/*
* temporary swap the lists of objects to delete with the distributed objects and
* deparse to an executable sql statement for the workers
*/
stmt->objects = distributedTypes;
char *dropStmtSql = DeparseTreeNode((Node *) stmt);
stmt->objects = oldTypes;
EnsureSequentialMode(OBJECT_TYPE);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessRenameTypeStmt is called when the user is renaming the type. The invocation happens
* before the statement is applied locally.
*
* As the type already exists we have access to the ObjectAddress for the type, this is
* used to check if the type is distributed. If the type is distributed the rename is
* executed on all the workers to keep the types in sync across the cluster.
*/
List *
PreprocessRenameTypeStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
ObjectAddress typeAddress = GetObjectAddressFromParseTree(node, false);
if (!ShouldPropagateObject(&typeAddress))
{
return NIL;
}
EnsureCoordinator();
/* fully qualify */
QualifyTreeNode(node);
/* deparse sql*/
const char *renameStmtSql = DeparseTreeNode(node);
EnsureSequentialMode(OBJECT_TYPE);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) renameStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/* /*
* PreprocessRenameTypeAttributeStmt is called for changes of attribute names for composite * PreprocessRenameTypeAttributeStmt is called for changes of attribute names for composite
* types. Planning is called before the statement is applied locally. * types. Planning is called before the statement is applied locally.
@ -499,98 +138,6 @@ PreprocessRenameTypeAttributeStmt(Node *node, const char *queryString,
} }
/*
* PreprocessAlterTypeSchemaStmt is executed before the statement is applied to the local
* postgres instance.
*
* In this stage we can prepare the commands that need to be run on all workers.
*/
List *
PreprocessAlterTypeSchemaStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_TYPE);
ObjectAddress typeAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&typeAddress))
{
return NIL;
}
EnsureCoordinator();
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
EnsureSequentialMode(OBJECT_TYPE);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterTypeSchemaStmt is executed after the change has been applied locally, we
* can now use the new dependencies of the type to ensure all its dependencies exist on
* the workers before we apply the commands remotely.
*/
List *
PostprocessAlterTypeSchemaStmt(Node *node, const char *queryString)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_TYPE);
ObjectAddress typeAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&typeAddress))
{
return NIL;
}
/* dependencies have changed (schema) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&typeAddress);
return NIL;
}
/*
* PreprocessAlterTypeOwnerStmt is called for change of ownership of types before the
* ownership is changed on the local instance.
*
* If the type for which the owner is changed is distributed we execute the change on all
* the workers to keep the type in sync across the cluster.
*/
List *
PreprocessAlterTypeOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_TYPE);
ObjectAddress typeAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&typeAddress))
{
return NIL;
}
EnsureCoordinator();
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
EnsureSequentialMode(OBJECT_TYPE);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/* /*
* CreateTypeStmtByObjectAddress returns a parsetree for the CREATE TYPE statement to * CreateTypeStmtByObjectAddress returns a parsetree for the CREATE TYPE statement to
* recreate the type by its object address. * recreate the type by its object address.
@ -878,7 +425,7 @@ AlterTypeSchemaStmtObjectAddress(Node *node, bool missing_ok)
*/ */
/* typename is the last in the list of names */ /* typename is the last in the list of names */
Value *typeNameStr = lfirst(list_tail(names)); String *typeNameStr = lfirst(list_tail(names));
/* /*
* we don't error here either, as the error would be not a good user facing * we don't error here either, as the error would be not a good user facing
@ -1051,60 +598,6 @@ GenerateBackupNameForTypeCollision(const ObjectAddress *address)
} }
/*
* FilterNameListForDistributedTypes takes a list of objects to delete, for Types this
* will be a list of TypeName. This list is filtered against the types that are
* distributed.
*
* The original list will not be touched, a new list will be created with only the objects
* in there.
*/
static List *
FilterNameListForDistributedTypes(List *objects, bool missing_ok)
{
List *result = NIL;
TypeName *typeName = NULL;
foreach_ptr(typeName, objects)
{
Oid typeOid = LookupTypeNameOid(NULL, typeName, missing_ok);
ObjectAddress typeAddress = { 0 };
if (!OidIsValid(typeOid))
{
continue;
}
ObjectAddressSet(typeAddress, TypeRelationId, typeOid);
if (IsObjectDistributed(&typeAddress))
{
result = lappend(result, typeName);
}
}
return result;
}
/*
* TypeNameListToObjectAddresses transforms a List * of TypeName *'s into a List * of
* ObjectAddress *'s. For this to succeed all Types identified by the TypeName *'s should
* exist on this postgres, an error will be thrown otherwise.
*/
static List *
TypeNameListToObjectAddresses(List *objects)
{
List *result = NIL;
TypeName *typeName = NULL;
foreach_ptr(typeName, objects)
{
Oid typeOid = LookupTypeNameOid(NULL, typeName, false);
ObjectAddress *typeAddress = palloc0(sizeof(ObjectAddress));
ObjectAddressSet(*typeAddress, TypeRelationId, typeOid);
result = lappend(result, typeAddress);
}
return result;
}
/* /*
* GetTypeOwner * GetTypeOwner
* *
@ -1145,47 +638,6 @@ MakeTypeNameFromRangeVar(const RangeVar *relation)
} }
/*
* ShouldPropagateTypeCreate returns if we should propagate the creation of a type.
*
* There are two moments we decide to not directly propagate the creation of a type.
* - During the creation of an Extension; we assume the type will be created by creating
* the extension on the worker
* - During a transaction block; if types are used in a distributed table in the same
* block we can only provide parallelism on the table if we do not change to sequential
* mode. Types will be propagated outside of this transaction to the workers so that
* the transaction can use 1 connection per shard and fully utilize citus' parallelism
*/
static bool
ShouldPropagateTypeCreate()
{
if (!ShouldPropagate())
{
return false;
}
if (!EnableCreateTypePropagation)
{
/*
* Administrator has turned of type creation propagation
*/
return false;
}
/*
* by not propagating in a transaction block we allow for parallelism to be used when
* this type will be used as a column in a table that will be created and distributed
* in this same transaction.
*/
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return false;
}
return true;
}
/* /*
* LookupNonAssociatedArrayTypeNameOid returns the oid of the type with the given type name * LookupNonAssociatedArrayTypeNameOid returns the oid of the type with the given type name
* that is not an array type that is associated to another user defined type. * that is not an array type that is associated to another user defined type.

View File

@ -44,6 +44,7 @@
#include "commands/extension.h" #include "commands/extension.h"
#include "commands/tablecmds.h" #include "commands/tablecmds.h"
#include "distributed/adaptive_executor.h" #include "distributed/adaptive_executor.h"
#include "distributed/backend_data.h"
#include "distributed/colocation_utils.h" #include "distributed/colocation_utils.h"
#include "distributed/commands.h" #include "distributed/commands.h"
#include "distributed/commands/multi_copy.h" #include "distributed/commands/multi_copy.h"
@ -76,6 +77,7 @@
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
#include "tcop/utility.h" #include "tcop/utility.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h" #include "utils/syscache.h"
@ -88,6 +90,7 @@ static int activeDropSchemaOrDBs = 0;
static bool ConstraintDropped = false; static bool ConstraintDropped = false;
ProcessUtility_hook_type PrevProcessUtility = NULL;
int UtilityHookLevel = 0; int UtilityHookLevel = 0;
@ -166,7 +169,6 @@ multi_ProcessUtility(PlannedStmt *pstmt,
parsetree = pstmt->utilityStmt; parsetree = pstmt->utilityStmt;
if (IsA(parsetree, TransactionStmt) || if (IsA(parsetree, TransactionStmt) ||
IsA(parsetree, LockStmt) ||
IsA(parsetree, ListenStmt) || IsA(parsetree, ListenStmt) ||
IsA(parsetree, NotifyStmt) || IsA(parsetree, NotifyStmt) ||
IsA(parsetree, ExecuteStmt) || IsA(parsetree, ExecuteStmt) ||
@ -183,8 +185,8 @@ multi_ProcessUtility(PlannedStmt *pstmt,
* that state. Since we never need to intercept transaction statements, * that state. Since we never need to intercept transaction statements,
* skip our checks and immediately fall into standard_ProcessUtility. * skip our checks and immediately fall into standard_ProcessUtility.
*/ */
standard_ProcessUtility_compat(pstmt, queryString, false, context, PrevProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag); params, queryEnv, dest, completionTag);
return; return;
} }
@ -203,9 +205,22 @@ multi_ProcessUtility(PlannedStmt *pstmt,
parsetree); parsetree);
if (strcmp(createExtensionStmt->extname, "citus") == 0) if (strcmp(createExtensionStmt->extname, "citus") == 0)
{ {
if (get_extension_oid("citus_columnar", true) == InvalidOid) double versionNumber = strtod(CITUS_MAJORVERSION, NULL);
DefElem *newVersionValue = GetExtensionOption(createExtensionStmt->options,
"new_version");
if (newVersionValue)
{ {
CreateExtensionWithVersion("citus_columnar", NULL); char *newVersion = strdup(defGetString(newVersionValue));
versionNumber = GetExtensionVersionNumber(newVersion);
}
if (versionNumber * 100 >= 1110.0)
{
if (get_extension_oid("citus_columnar", true) == InvalidOid)
{
CreateExtensionWithVersion("citus_columnar", NULL);
}
} }
} }
} }
@ -216,8 +231,8 @@ multi_ProcessUtility(PlannedStmt *pstmt,
* Ensure that utility commands do not behave any differently until CREATE * Ensure that utility commands do not behave any differently until CREATE
* EXTENSION is invoked. * EXTENSION is invoked.
*/ */
standard_ProcessUtility_compat(pstmt, queryString, false, context, PrevProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag); params, queryEnv, dest, completionTag);
return; return;
} }
@ -248,8 +263,8 @@ multi_ProcessUtility(PlannedStmt *pstmt,
PG_TRY(); PG_TRY();
{ {
standard_ProcessUtility_compat(pstmt, queryString, false, context, PrevProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag); params, queryEnv, dest, completionTag);
StoredProcedureLevel -= 1; StoredProcedureLevel -= 1;
@ -282,8 +297,8 @@ multi_ProcessUtility(PlannedStmt *pstmt,
PG_TRY(); PG_TRY();
{ {
standard_ProcessUtility_compat(pstmt, queryString, false, context, PrevProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag); params, queryEnv, dest, completionTag);
DoBlockLevel -= 1; DoBlockLevel -= 1;
} }
@ -485,6 +500,18 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
PreprocessTruncateStatement((TruncateStmt *) parsetree); PreprocessTruncateStatement((TruncateStmt *) parsetree);
} }
if (IsA(parsetree, LockStmt))
{
/*
* PreprocessLockStatement might lock the relations locally if the
* node executing the command is in pg_dist_node. Even though the process
* utility will re-acquire the locks across the same relations if the node
* is in the metadata (in the pg_dist_node table) that should not be a problem,
* plus it ensures consistent locking order between the nodes.
*/
PreprocessLockStatement((LockStmt *) parsetree, context);
}
/* /*
* We only process ALTER TABLE ... ATTACH PARTITION commands in the function below * We only process ALTER TABLE ... ATTACH PARTITION commands in the function below
* and distribute the partition if necessary. * and distribute the partition if necessary.
@ -505,6 +532,20 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
parsetree = pstmt->utilityStmt; parsetree = pstmt->utilityStmt;
ops = GetDistributeObjectOps(parsetree); ops = GetDistributeObjectOps(parsetree);
/*
* For some statements Citus defines a Qualify function. The goal of this function
* is to take any ambiguity from the statement that is contextual on either the
* search_path or current settings.
* Instead of relying on the search_path and settings we replace any deduced bits
* and fill them out how postgres would resolve them. This makes subsequent
* deserialize calls for the statement portable to other postgres servers, the
* workers in our case.
*/
if (ops && ops->qualify)
{
ops->qualify(parsetree);
}
if (ops && ops->preprocess) if (ops && ops->preprocess)
{ {
ddlJobs = ops->preprocess(parsetree, queryString, context); ddlJobs = ops->preprocess(parsetree, queryString, context);
@ -610,30 +651,81 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
/*upgrade citus */ /*upgrade citus */
DefElem *newVersionValue = GetExtensionOption( DefElem *newVersionValue = GetExtensionOption(
((AlterExtensionStmt *) parsetree)->options, "new_version"); ((AlterExtensionStmt *) parsetree)->options, "new_version");
Oid citusExtensionOid = get_extension_oid("citus", true);
char *curExtensionVersion = get_extension_version(citusExtensionOid);
Oid citusColumnarOid = get_extension_oid("citus_columnar", true); Oid citusColumnarOid = get_extension_oid("citus_columnar", true);
if (newVersionValue) if (newVersionValue)
{ {
const char *newVersion = defGetString(newVersionValue); char *newVersion = defGetString(newVersionValue);
double newVersionNumber = GetExtensionVersionNumber(strdup(newVersion));
/*alter extension citus update to 11.1-1, and no citus_columnar installed */ /*alter extension citus update to version >= 11.1-1, and no citus_columnar installed */
if (strcmp(newVersion, "11.1-1") == 0 && citusColumnarOid == InvalidOid) if (newVersionNumber * 100 >= 1110 && citusColumnarOid == InvalidOid)
{ {
/*it's upgrade citus to 11.1-1 */ /*it's upgrade citus to version later or equal to 11.1-1 */
CreateExtensionWithVersion("citus_columnar", "11.1-0"); CreateExtensionWithVersion("citus_columnar", "11.1-0");
} }
else if (strcmp(curExtensionVersion, "11.1-1") == 0 && citusColumnarOid != else if (newVersionNumber * 100 < 1110 && citusColumnarOid != InvalidOid)
InvalidOid)
{ {
/*downgrade citus_columnar to Y */ /*downgrade citus_columnar to Y */
AlterExtensionUpdateStmt("citus_columnar", "11.1-0"); AlterExtensionUpdateStmt("citus_columnar", "11.1-0");
} }
} }
else
{
double versionNumber = strtod(CITUS_MAJORVERSION, NULL);
if (versionNumber * 100 >= 1110)
{
if (citusColumnarOid == InvalidOid)
{
CreateExtensionWithVersion("citus_columnar", "11.1-0");
}
}
}
} }
standard_ProcessUtility_compat(pstmt, queryString, false, context, PrevProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag); params, queryEnv, dest, completionTag);
if (isAlterExtensionUpdateCitusStmt)
{
DefElem *newVersionValue = GetExtensionOption(
((AlterExtensionStmt *) parsetree)->options, "new_version");
Oid citusColumnarOid = get_extension_oid("citus_columnar", true);
if (newVersionValue)
{
char *newVersion = defGetString(newVersionValue);
double newVersionNumber = GetExtensionVersionNumber(strdup(newVersion));
if (newVersionNumber * 100 >= 1110 && citusColumnarOid != InvalidOid)
{
/*after "ALTER EXTENSION citus" updates citus_columnar Y to version Z. */
char *curColumnarVersion = get_extension_version(citusColumnarOid);
if (strcmp(curColumnarVersion, "11.1-0") == 0)
{
AlterExtensionUpdateStmt("citus_columnar", "11.1-1");
}
}
else if (newVersionNumber * 100 < 1110 && citusColumnarOid != InvalidOid)
{
/*after "ALTER EXTENSION citus" drops citus_columnar extension */
char *curColumnarVersion = get_extension_version(citusColumnarOid);
if (strcmp(curColumnarVersion, "11.1-0") == 0)
{
RemoveExtensionById(citusColumnarOid);
}
}
}
else
{
double versionNumber = strtod(CITUS_MAJORVERSION, NULL);
if (versionNumber * 100 >= 1110)
{
char *curColumnarVersion = get_extension_version(citusColumnarOid);
if (strcmp(curColumnarVersion, "11.1-0") == 0)
{
AlterExtensionUpdateStmt("citus_columnar", "11.1-1");
}
}
}
}
if (isAlterExtensionUpdateCitusStmt) if (isAlterExtensionUpdateCitusStmt)
{ {
@ -1115,16 +1207,20 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob)
EnsureCoordinator(); EnsureCoordinator();
Oid targetRelationId = ddlJob->targetRelationId; ObjectAddress targetObjectAddress = ddlJob->targetObjectAddress;
if (OidIsValid(targetRelationId)) if (OidIsValid(targetObjectAddress.classId))
{ {
/* /*
* Only for ddlJobs that are targetting a relation (table) we want to sync * Only for ddlJobs that are targetting an object we want to sync
* its metadata and verify some properties around the table. * its metadata.
*/ */
shouldSyncMetadata = ShouldSyncTableMetadata(targetRelationId); shouldSyncMetadata = ShouldSyncUserCommandForObject(targetObjectAddress);
EnsurePartitionTableNotReplicated(targetRelationId);
if (targetObjectAddress.classId == RelationRelationId)
{
EnsurePartitionTableNotReplicated(targetObjectAddress.objectId);
}
} }
bool localExecutionSupported = true; bool localExecutionSupported = true;
@ -1375,7 +1471,7 @@ CreateCustomDDLTaskList(Oid relationId, TableDDLCommand *command)
} }
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId; ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = GetTableDDLCommand(command); ddlJob->metadataSyncCommand = GetTableDDLCommand(command);
ddlJob->taskList = taskList; ddlJob->taskList = taskList;
@ -1626,7 +1722,7 @@ NodeDDLTaskList(TargetWorkerSet targets, List *commands)
} }
DDLJob *ddlJob = palloc0(sizeof(DDLJob)); DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = InvalidOid; ddlJob->targetObjectAddress = InvalidObjectAddress;
ddlJob->metadataSyncCommand = NULL; ddlJob->metadataSyncCommand = NULL;
ddlJob->taskList = list_make1(task); ddlJob->taskList = list_make1(task);
@ -1654,26 +1750,3 @@ DropSchemaOrDBInProgress(void)
{ {
return activeDropSchemaOrDBs > 0; return activeDropSchemaOrDBs > 0;
} }
/*
* ColumnarTableSetOptionsHook propagates columnar table options to shards, if
* necessary.
*/
void
ColumnarTableSetOptionsHook(Oid relationId, ColumnarOptions options)
{
if (EnableDDLPropagation && IsCitusTable(relationId))
{
/* when a columnar table is distributed update all settings on the shards */
Oid namespaceId = get_rel_namespace(relationId);
char *schemaName = get_namespace_name(namespaceId);
char *relationName = get_rel_name(relationId);
TableDDLCommand *command = ColumnarGetCustomTableOptionsDDL(schemaName,
relationName,
options);
DDLJob *ddljob = CreateCustomDDLTaskList(relationId, command);
ExecuteDistributedDDLJob(ddljob);
}
}

View File

@ -432,7 +432,7 @@ DeparseVacuumColumnNames(List *columnNameList)
appendStringInfoString(columnNames, " ("); appendStringInfoString(columnNames, " (");
Value *columnName = NULL; String *columnName = NULL;
foreach_ptr(columnName, columnNameList) foreach_ptr(columnName, columnNameList)
{ {
appendStringInfo(columnNames, "%s,", strVal(columnName)); appendStringInfo(columnNames, "%s,", strVal(columnName));

View File

@ -0,0 +1,705 @@
/*-------------------------------------------------------------------------
*
* view.c
* Commands for distributing CREATE OR REPLACE VIEW statements.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "fmgr.h"
#include "access/genam.h"
#include "catalog/objectaddress.h"
#include "commands/extension.h"
#include "distributed/commands.h"
#include "distributed/citus_ruleutils.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/deparser.h"
#include "distributed/errormessage.h"
#include "distributed/listutils.h"
#include "distributed/metadata_sync.h"
#include "distributed/metadata/dependency.h"
#include "distributed/metadata/distobject.h"
#include "distributed/multi_executor.h"
#include "distributed/namespace_utils.h"
#include "distributed/worker_transaction.h"
#include "executor/spi.h"
#include "nodes/nodes.h"
#include "nodes/pg_list.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
static List * FilterNameListForDistributedViews(List *viewNamesList, bool missing_ok);
static void AppendQualifiedViewNameToCreateViewCommand(StringInfo buf, Oid viewOid);
static void AppendViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid);
static void AppendAliasesToCreateViewCommand(StringInfo createViewCommand, Oid viewOid);
static void AppendOptionsToCreateViewCommand(StringInfo createViewCommand, Oid viewOid);
/*
* PreprocessViewStmt is called during the planning phase for CREATE OR REPLACE VIEW
* before it is created on the local node internally.
*/
List *
PreprocessViewStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
EnsureCoordinator();
return NIL;
}
/*
* PostprocessViewStmt actually creates the commmands we need to run on workers to
* propagate views.
*
* If view depends on any undistributable object, Citus can not distribute it. In order to
* not to prevent users from creating local views on the coordinator WARNING message will
* be sent to the customer about the case instead of erroring out. If no worker nodes exist
* at all, view will be created locally without any WARNING message.
*
* Besides creating the plan we also make sure all (new) dependencies of the view are
* created on all nodes.
*/
List *
PostprocessViewStmt(Node *node, const char *queryString)
{
ViewStmt *stmt = castNode(ViewStmt, node);
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
ObjectAddress viewAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (IsObjectAddressOwnedByExtension(&viewAddress, NULL))
{
return NIL;
}
/* If the view has any unsupported dependency, create it locally */
if (ErrorOrWarnIfObjectHasUnsupportedDependency(&viewAddress))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&viewAddress);
char *command = CreateViewDDLCommand(viewAddress.objectId);
/*
* We'd typically use NodeDDLTaskList() for generating node-level DDL commands,
* such as when creating a type. However, views are different in a sense that
* views do not depend on citus tables. Instead, they are `depending` on citus tables.
*
* When NodeDDLTaskList() used, it should be accompanied with sequential execution.
* Here, we do something equivalent to NodeDDLTaskList(), but using metadataSyncCommand
* field. This hack allows us to use the metadata connection
* (see `REQUIRE_METADATA_CONNECTION` flag). Meaning that, view creation is treated as
* a metadata operation.
*
* We do this mostly for performance reasons, because we cannot afford to switch to
* sequential execution, for instance when we are altering or creating distributed
* tables -- which may require significant resources.
*
* The downside of using this hack is that if a view is re-used in the same transaction
* that creates the view on the workers, we might get errors such as the below which
* we consider a decent trade-off currently:
*
* BEGIN;
* CREATE VIEW dist_view ..
* CRETAE TABLE t2(id int, val dist_view);
*
* -- shard creation fails on one of the connections
* SELECT create_distributed_table('t2', 'id');
* ERROR: type "public.dist_view" does not exist
*
*/
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetObjectAddress = viewAddress;
ddlJob->metadataSyncCommand = command;
ddlJob->taskList = NIL;
return list_make1(ddlJob);
}
/*
* ViewStmtObjectAddress returns the ObjectAddress for the subject of the
* CREATE [OR REPLACE] VIEW statement.
*/
ObjectAddress
ViewStmtObjectAddress(Node *node, bool missing_ok)
{
ViewStmt *stmt = castNode(ViewStmt, node);
Oid viewOid = RangeVarGetRelid(stmt->view, NoLock, missing_ok);
ObjectAddress viewAddress = { 0 };
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
return viewAddress;
}
/*
* PreprocessDropViewStmt gets called during the planning phase of a DROP VIEW statement
* and returns a list of DDLJob's that will drop any distributed view from the
* workers.
*
* The DropStmt could have multiple objects to drop, the list of objects will be filtered
* to only keep the distributed views for deletion on the workers. Non-distributed
* views will still be dropped locally but not on the workers.
*/
List *
PreprocessDropViewStmt(Node *node, const char *queryString, ProcessUtilityContext
processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
if (!ShouldPropagate())
{
return NIL;
}
List *distributedViewNames = FilterNameListForDistributedViews(stmt->objects,
stmt->missing_ok);
if (list_length(distributedViewNames) < 1)
{
/* no distributed view to drop */
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_VIEW);
/*
* Swap the list of objects before deparsing and restore the old list after. This
* ensures we only have distributed views in the deparsed drop statement.
*/
DropStmt *stmtCopy = copyObject(stmt);
stmtCopy->objects = distributedViewNames;
QualifyTreeNode((Node *) stmtCopy);
const char *dropStmtSql = DeparseTreeNode((Node *) stmtCopy);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* FilterNameListForDistributedViews takes a list of view names and filters against the
* views that are distributed.
*
* The original list will not be touched, a new list will be created with only the objects
* in there.
*/
static List *
FilterNameListForDistributedViews(List *viewNamesList, bool missing_ok)
{
List *distributedViewNames = NIL;
List *possiblyQualifiedViewName = NULL;
foreach_ptr(possiblyQualifiedViewName, viewNamesList)
{
char *viewName = NULL;
char *schemaName = NULL;
DeconstructQualifiedName(possiblyQualifiedViewName, &schemaName, &viewName);
if (schemaName == NULL)
{
char *objName = NULL;
Oid schemaOid = QualifiedNameGetCreationNamespace(possiblyQualifiedViewName,
&objName);
schemaName = get_namespace_name(schemaOid);
}
Oid schemaId = get_namespace_oid(schemaName, missing_ok);
Oid viewOid = get_relname_relid(viewName, schemaId);
if (!OidIsValid(viewOid))
{
continue;
}
if (IsViewDistributed(viewOid))
{
distributedViewNames = lappend(distributedViewNames,
possiblyQualifiedViewName);
}
}
return distributedViewNames;
}
/*
* CreateViewDDLCommand returns the DDL command to create the view addressed by
* the viewAddress.
*/
char *
CreateViewDDLCommand(Oid viewOid)
{
StringInfo createViewCommand = makeStringInfo();
appendStringInfoString(createViewCommand, "CREATE OR REPLACE VIEW ");
AppendQualifiedViewNameToCreateViewCommand(createViewCommand, viewOid);
AppendAliasesToCreateViewCommand(createViewCommand, viewOid);
AppendOptionsToCreateViewCommand(createViewCommand, viewOid);
AppendViewDefinitionToCreateViewCommand(createViewCommand, viewOid);
return createViewCommand->data;
}
/*
* AppendQualifiedViewNameToCreateViewCommand adds the qualified view of the given view
* oid to the given create view command.
*/
static void
AppendQualifiedViewNameToCreateViewCommand(StringInfo buf, Oid viewOid)
{
char *viewName = get_rel_name(viewOid);
char *schemaName = get_namespace_name(get_rel_namespace(viewOid));
char *qualifiedViewName = quote_qualified_identifier(schemaName, viewName);
appendStringInfo(buf, "%s ", qualifiedViewName);
}
/*
* AppendAliasesToCreateViewCommand appends aliases to the create view
* command for the existing view.
*/
static void
AppendAliasesToCreateViewCommand(StringInfo createViewCommand, Oid viewOid)
{
/* Get column name aliases from pg_attribute */
ScanKeyData key[1];
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(viewOid));
Relation maprel = table_open(AttributeRelationId, AccessShareLock);
Relation mapidx = index_open(AttributeRelidNumIndexId, AccessShareLock);
SysScanDesc pgAttributeScan = systable_beginscan_ordered(maprel, mapidx, NULL, 1,
key);
bool isInitialAlias = true;
bool hasAlias = false;
HeapTuple attributeTuple;
while (HeapTupleIsValid(attributeTuple = systable_getnext_ordered(pgAttributeScan,
ForwardScanDirection)))
{
Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
const char *aliasName = quote_identifier(NameStr(att->attname));
if (isInitialAlias)
{
appendStringInfoString(createViewCommand, "(");
}
else
{
appendStringInfoString(createViewCommand, ",");
}
appendStringInfoString(createViewCommand, aliasName);
hasAlias = true;
isInitialAlias = false;
}
if (hasAlias)
{
appendStringInfoString(createViewCommand, ") ");
}
systable_endscan_ordered(pgAttributeScan);
index_close(mapidx, AccessShareLock);
table_close(maprel, AccessShareLock);
}
/*
* AppendOptionsToCreateViewCommand add relation options to create view command
* for an existing view
*/
static void
AppendOptionsToCreateViewCommand(StringInfo createViewCommand, Oid viewOid)
{
/* Add rel options to create view command */
char *relOptions = flatten_reloptions(viewOid);
if (relOptions != NULL)
{
appendStringInfo(createViewCommand, "WITH (%s) ", relOptions);
}
}
/*
* AppendViewDefinitionToCreateViewCommand adds the definition of the given view to the
* given create view command.
*/
static void
AppendViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid)
{
/*
* Set search_path to NIL so that all objects outside of pg_catalog will be
* schema-prefixed.
*/
OverrideSearchPath *overridePath = GetOverrideSearchPath(CurrentMemoryContext);
overridePath->schemas = NIL;
overridePath->addCatalog = true;
PushOverrideSearchPath(overridePath);
/*
* Push the transaction snapshot to be able to get vief definition with pg_get_viewdef
*/
PushActiveSnapshot(GetTransactionSnapshot());
Datum viewDefinitionDatum = DirectFunctionCall1(pg_get_viewdef,
ObjectIdGetDatum(viewOid));
char *viewDefinition = TextDatumGetCString(viewDefinitionDatum);
PopActiveSnapshot();
PopOverrideSearchPath();
appendStringInfo(buf, "AS %s ", viewDefinition);
}
/*
* AlterViewOwnerCommand returns the command to alter view owner command for the
* given view or materialized view oid.
*/
char *
AlterViewOwnerCommand(Oid viewOid)
{
/* Add alter owner commmand */
StringInfo alterOwnerCommand = makeStringInfo();
char *viewName = get_rel_name(viewOid);
Oid schemaOid = get_rel_namespace(viewOid);
char *schemaName = get_namespace_name(schemaOid);
char *viewOwnerName = TableOwner(viewOid);
char *qualifiedViewName = NameListToQuotedString(list_make2(makeString(schemaName),
makeString(viewName)));
if (get_rel_relkind(viewOid) == RELKIND_MATVIEW)
{
appendStringInfo(alterOwnerCommand, "ALTER MATERIALIZED VIEW %s ",
qualifiedViewName);
}
else
{
appendStringInfo(alterOwnerCommand, "ALTER VIEW %s ", qualifiedViewName);
}
appendStringInfo(alterOwnerCommand, "OWNER TO %s", quote_identifier(viewOwnerName));
return alterOwnerCommand->data;
}
/*
* IsViewDistributed checks if a view is distributed
*/
bool
IsViewDistributed(Oid viewOid)
{
Assert(get_rel_relkind(viewOid) == RELKIND_VIEW);
ObjectAddress viewAddress = { 0 };
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
return IsObjectDistributed(&viewAddress);
}
/*
* PreprocessAlterViewStmt is invoked for alter view statements.
*/
List *
PreprocessAlterViewStmt(Node *node, const char *queryString, ProcessUtilityContext
processUtilityContext)
{
AlterTableStmt *stmt = castNode(AlterTableStmt, node);
ObjectAddress viewAddress = GetObjectAddressFromParseTree((Node *) stmt, true);
if (!ShouldPropagateObject(&viewAddress))
{
return NIL;
}
QualifyTreeNode((Node *) stmt);
EnsureCoordinator();
/* reconstruct alter statement in a portable fashion */
const char *alterViewStmtSql = DeparseTreeNode((Node *) stmt);
/*
* To avoid sequential mode, we are using metadata connection. For the
* detailed explanation, please check the comment on PostprocessViewStmt.
*/
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetObjectAddress = viewAddress;
ddlJob->metadataSyncCommand = alterViewStmtSql;
ddlJob->taskList = NIL;
return list_make1(ddlJob);
}
/*
* PostprocessAlterViewStmt is invoked for alter view statements.
*/
List *
PostprocessAlterViewStmt(Node *node, const char *queryString)
{
AlterTableStmt *stmt = castNode(AlterTableStmt, node);
Assert(AlterTableStmtObjType_compat(stmt) == OBJECT_VIEW);
ObjectAddress viewAddress = GetObjectAddressFromParseTree((Node *) stmt, true);
if (!ShouldPropagateObject(&viewAddress))
{
return NIL;
}
if (IsObjectAddressOwnedByExtension(&viewAddress, NULL))
{
return NIL;
}
/* If the view has any unsupported dependency, create it locally */
if (ErrorOrWarnIfObjectHasUnsupportedDependency(&viewAddress))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&viewAddress);
return NIL;
}
/*
* AlterViewStmtObjectAddress returns the ObjectAddress for the subject of the
* ALTER VIEW statement.
*/
ObjectAddress
AlterViewStmtObjectAddress(Node *node, bool missing_ok)
{
AlterTableStmt *stmt = castNode(AlterTableStmt, node);
Oid viewOid = RangeVarGetRelid(stmt->relation, NoLock, missing_ok);
ObjectAddress viewAddress = { 0 };
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
return viewAddress;
}
/*
* PreprocessRenameViewStmt is called when the user is renaming the view or the column of
* the view.
*/
List *
PreprocessRenameViewStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
ObjectAddress viewAddress = GetObjectAddressFromParseTree(node, true);
if (!ShouldPropagateObject(&viewAddress))
{
return NIL;
}
EnsureCoordinator();
/* fully qualify */
QualifyTreeNode(node);
/* deparse sql*/
const char *renameStmtSql = DeparseTreeNode(node);
/*
* To avoid sequential mode, we are using metadata connection. For the
* detailed explanation, please check the comment on PostprocessViewStmt.
*/
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetObjectAddress = viewAddress;
ddlJob->metadataSyncCommand = renameStmtSql;
ddlJob->taskList = NIL;
return list_make1(ddlJob);
}
/*
* RenameViewStmtObjectAddress returns the ObjectAddress of the view that is the object
* of the RenameStmt. Errors if missing_ok is false.
*/
ObjectAddress
RenameViewStmtObjectAddress(Node *node, bool missing_ok)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Oid viewOid = RangeVarGetRelid(stmt->relation, NoLock, missing_ok);
ObjectAddress viewAddress = { 0 };
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
return viewAddress;
}
/*
* PreprocessAlterViewSchemaStmt is executed before the statement is applied to the local
* postgres instance.
*/
List *
PreprocessAlterViewSchemaStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
ObjectAddress viewAddress = GetObjectAddressFromParseTree((Node *) stmt, true);
if (!ShouldPropagateObject(&viewAddress))
{
return NIL;
}
EnsureCoordinator();
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
/*
* To avoid sequential mode, we are using metadata connection. For the
* detailed explanation, please check the comment on PostprocessViewStmt.
*/
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetObjectAddress = viewAddress;
ddlJob->metadataSyncCommand = sql;
ddlJob->taskList = NIL;
return list_make1(ddlJob);
}
/*
* PostprocessAlterViewSchemaStmt is executed after the change has been applied locally, we
* can now use the new dependencies of the view to ensure all its dependencies exist on
* the workers before we apply the commands remotely.
*/
List *
PostprocessAlterViewSchemaStmt(Node *node, const char *queryString)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
ObjectAddress viewAddress = GetObjectAddressFromParseTree((Node *) stmt, true);
if (!ShouldPropagateObject(&viewAddress))
{
return NIL;
}
/* dependencies have changed (schema) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&viewAddress);
return NIL;
}
/*
* AlterViewSchemaStmtObjectAddress returns the ObjectAddress of the view that is the object
* of the alter schema statement.
*/
ObjectAddress
AlterViewSchemaStmtObjectAddress(Node *node, bool missing_ok)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Oid viewOid = RangeVarGetRelid(stmt->relation, NoLock, true);
/*
* Since it can be called both before and after executing the standardProcess utility,
* we need to check both old and new schemas
*/
if (viewOid == InvalidOid)
{
Oid schemaId = get_namespace_oid(stmt->newschema, missing_ok);
viewOid = get_relname_relid(stmt->relation->relname, schemaId);
/*
* if the view is still invalid we couldn't find the view, error with the same
* message postgres would error with it missing_ok is false (not ok to miss)
*/
if (!missing_ok && viewOid == InvalidOid)
{
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("view \"%s\" does not exist",
stmt->relation->relname)));
}
}
ObjectAddress viewAddress = { 0 };
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
return viewAddress;
}
/*
* IsViewRenameStmt returns whether the passed-in RenameStmt is the following
* form:
*
* - ALTER VIEW RENAME
* - ALTER VIEW RENAME COLUMN
*/
bool
IsViewRenameStmt(RenameStmt *renameStmt)
{
bool isViewRenameStmt = false;
if (renameStmt->renameType == OBJECT_VIEW ||
(renameStmt->renameType == OBJECT_COLUMN &&
renameStmt->relationType == OBJECT_VIEW))
{
isViewRenameStmt = true;
}
return isViewRenameStmt;
}

View File

@ -1466,28 +1466,6 @@ ShouldShutdownConnection(MultiConnection *connection, const int cachedConnection
} }
/*
* IsRebalancerInitiatedBackend returns true if we are in a backend that citus
* rebalancer initiated.
*/
bool
IsRebalancerInternalBackend(void)
{
return application_name && strcmp(application_name, CITUS_REBALANCER_NAME) == 0;
}
/*
* IsCitusInitiatedRemoteBackend returns true if we are in a backend that citus
* initiated via remote connection.
*/
bool
IsCitusInternalBackend(void)
{
return ExtractGlobalPID(application_name) != INVALID_CITUS_INTERNAL_BACKEND_GPID;
}
/* /*
* ResetConnection preserves the given connection for later usage by * ResetConnection preserves the given connection for later usage by
* resetting its states. * resetting its states.

View File

@ -1115,3 +1115,92 @@ SendCancelationRequest(MultiConnection *connection)
return cancelSent; return cancelSent;
} }
/*
* EvaluateSingleQueryResult gets the query result from connection and returns
* true if the query is executed successfully, false otherwise. A query result
* or an error message is returned in queryResultString. The function requires
* that the query returns a single column/single row result. It returns an
* error otherwise.
*/
bool
EvaluateSingleQueryResult(MultiConnection *connection, PGresult *queryResult,
StringInfo queryResultString)
{
bool success = false;
ExecStatusType resultStatus = PQresultStatus(queryResult);
if (resultStatus == PGRES_COMMAND_OK)
{
char *commandStatus = PQcmdStatus(queryResult);
appendStringInfo(queryResultString, "%s", commandStatus);
success = true;
}
else if (resultStatus == PGRES_TUPLES_OK)
{
int ntuples = PQntuples(queryResult);
int nfields = PQnfields(queryResult);
/* error if query returns more than 1 rows, or more than 1 fields */
if (nfields != 1)
{
appendStringInfo(queryResultString,
"expected a single column in query target");
}
else if (ntuples > 1)
{
appendStringInfo(queryResultString,
"expected a single row in query result");
}
else
{
int row = 0;
int column = 0;
if (!PQgetisnull(queryResult, row, column))
{
char *queryResultValue = PQgetvalue(queryResult, row, column);
appendStringInfo(queryResultString, "%s", queryResultValue);
}
success = true;
}
}
else
{
StoreErrorMessage(connection, queryResultString);
}
return success;
}
/*
* StoreErrorMessage gets the error message from connection and stores it
* in queryResultString. It should be called only when error is present
* otherwise it would return a default error message.
*/
void
StoreErrorMessage(MultiConnection *connection, StringInfo queryResultString)
{
char *errorMessage = PQerrorMessage(connection->pgConn);
if (errorMessage != NULL)
{
/* copy the error message to a writable memory */
errorMessage = pnstrdup(errorMessage, strlen(errorMessage));
char *firstNewlineIndex = strchr(errorMessage, '\n');
/* trim the error message at the line break */
if (firstNewlineIndex != NULL)
{
*firstNewlineIndex = '\0';
}
}
else
{
/* put a default error message if no error message is reported */
errorMessage = "An error occurred while running the query";
}
appendStringInfo(queryResultString, "%s", errorMessage);
}

View File

@ -80,7 +80,6 @@ static void deparse_index_columns(StringInfo buffer, List *indexParameterList,
static void AppendStorageParametersToString(StringInfo stringBuffer, static void AppendStorageParametersToString(StringInfo stringBuffer,
List *optionList); List *optionList);
static void simple_quote_literal(StringInfo buf, const char *val); static void simple_quote_literal(StringInfo buf, const char *val);
static char * flatten_reloptions(Oid relid);
static void AddVacuumParams(ReindexStmt *reindexStmt, StringInfo buffer); static void AddVacuumParams(ReindexStmt *reindexStmt, StringInfo buffer);
@ -1231,7 +1230,7 @@ pg_get_replica_identity_command(Oid tableRelationId)
* This function comes from PostgreSQL source code in * This function comes from PostgreSQL source code in
* src/backend/utils/adt/ruleutils.c * src/backend/utils/adt/ruleutils.c
*/ */
static char * char *
flatten_reloptions(Oid relid) flatten_reloptions(Oid relid)
{ {
char *result = NULL; char *result = NULL;

View File

@ -11,6 +11,8 @@
#include "postgres.h" #include "postgres.h"
#include "pg_version_compat.h"
#include "catalog/namespace.h" #include "catalog/namespace.h"
#include "lib/stringinfo.h" #include "lib/stringinfo.h"
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
@ -44,6 +46,6 @@ AppendAlterDatabaseOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt)
appendStringInfo(buf, appendStringInfo(buf,
"ALTER DATABASE %s OWNER TO %s;", "ALTER DATABASE %s OWNER TO %s;",
quote_identifier(strVal((Value *) stmt->object)), quote_identifier(strVal((String *) stmt->object)),
RoleSpecString(stmt->newowner, true)); RoleSpecString(stmt->newowner, true));
} }

View File

@ -223,7 +223,7 @@ AppendDropForeignServerStmt(StringInfo buf, DropStmt *stmt)
static void static void
AppendServerNames(StringInfo buf, DropStmt *stmt) AppendServerNames(StringInfo buf, DropStmt *stmt)
{ {
Value *serverValue = NULL; String *serverValue = NULL;
foreach_ptr(serverValue, stmt->objects) foreach_ptr(serverValue, stmt->objects)
{ {
const char *serverString = quote_identifier(strVal(serverValue)); const char *serverString = quote_identifier(strVal(serverValue));

View File

@ -396,18 +396,18 @@ AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
appendStringInfo(buf, " SET %s =", quote_identifier(setStmt->name)); appendStringInfo(buf, " SET %s =", quote_identifier(setStmt->name));
} }
Value value = varArgConst->val; Node *value = (Node *) &varArgConst->val;
switch (value.type) switch (value->type)
{ {
case T_Integer: case T_Integer:
{ {
appendStringInfo(buf, " %d", intVal(&value)); appendStringInfo(buf, " %d", intVal(value));
break; break;
} }
case T_Float: case T_Float:
{ {
appendStringInfo(buf, " %s", strVal(&value)); appendStringInfo(buf, " %s", strVal(value));
break; break;
} }
@ -428,7 +428,7 @@ AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
Datum interval = Datum interval =
DirectFunctionCall3(interval_in, DirectFunctionCall3(interval_in,
CStringGetDatum(strVal(&value)), CStringGetDatum(strVal(value)),
ObjectIdGetDatum(InvalidOid), ObjectIdGetDatum(InvalidOid),
Int32GetDatum(typmod)); Int32GetDatum(typmod));
@ -440,7 +440,7 @@ AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
else else
{ {
appendStringInfo(buf, " %s", quote_literal_cstr(strVal( appendStringInfo(buf, " %s", quote_literal_cstr(strVal(
&value))); value)));
} }
break; break;
} }

View File

@ -126,7 +126,7 @@ AppendDropSchemaStmt(StringInfo buf, DropStmt *stmt)
appendStringInfoString(buf, "IF EXISTS "); appendStringInfoString(buf, "IF EXISTS ");
} }
Value *schemaValue = NULL; String *schemaValue = NULL;
foreach_ptr(schemaValue, stmt->objects) foreach_ptr(schemaValue, stmt->objects)
{ {
const char *schemaString = quote_identifier(strVal(schemaValue)); const char *schemaString = quote_identifier(strVal(schemaValue));

View File

@ -86,12 +86,6 @@ AppendSequenceNameList(StringInfo buf, List *objects, ObjectType objtype)
RangeVar *seq = makeRangeVarFromNameList((List *) lfirst(objectCell)); RangeVar *seq = makeRangeVarFromNameList((List *) lfirst(objectCell));
if (seq->schemaname == NULL)
{
Oid schemaOid = RangeVarGetCreationNamespace(seq);
seq->schemaname = get_namespace_name(schemaOid);
}
char *qualifiedSequenceName = quote_qualified_identifier(seq->schemaname, char *qualifiedSequenceName = quote_qualified_identifier(seq->schemaname,
seq->relname); seq->relname);
appendStringInfoString(buf, qualifiedSequenceName); appendStringInfoString(buf, qualifiedSequenceName);

View File

@ -200,10 +200,10 @@ AppendAlterStatisticsOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt)
static void static void
AppendStatisticsName(StringInfo buf, CreateStatsStmt *stmt) AppendStatisticsName(StringInfo buf, CreateStatsStmt *stmt)
{ {
Value *schemaNameVal = (Value *) linitial(stmt->defnames); String *schemaNameVal = (String *) linitial(stmt->defnames);
const char *schemaName = quote_identifier(strVal(schemaNameVal)); const char *schemaName = quote_identifier(strVal(schemaNameVal));
Value *statNameVal = (Value *) lsecond(stmt->defnames); String *statNameVal = (String *) lsecond(stmt->defnames);
const char *statName = quote_identifier(strVal(statNameVal)); const char *statName = quote_identifier(strVal(statNameVal));
appendStringInfo(buf, "%s.%s", schemaName, statName); appendStringInfo(buf, "%s.%s", schemaName, statName);
@ -220,7 +220,7 @@ AppendStatTypes(StringInfo buf, CreateStatsStmt *stmt)
appendStringInfoString(buf, " ("); appendStringInfoString(buf, " (");
Value *statType = NULL; String *statType = NULL;
foreach_ptr(statType, stmt->stat_types) foreach_ptr(statType, stmt->stat_types)
{ {
appendStringInfoString(buf, strVal(statType)); appendStringInfoString(buf, strVal(statType));

View File

@ -464,7 +464,7 @@ DeparseTextSearchDictionaryCommentStmt(Node *node)
static void static void
AppendStringInfoTokentypeList(StringInfo buf, List *tokentypes) AppendStringInfoTokentypeList(StringInfo buf, List *tokentypes)
{ {
Value *tokentype = NULL; String *tokentype = NULL;
bool first = true; bool first = true;
foreach_ptr(tokentype, tokentypes) foreach_ptr(tokentype, tokentypes)
{ {

View File

@ -0,0 +1,310 @@
/*-------------------------------------------------------------------------
*
* deparse_view_stmts.c
*
* All routines to deparse view statements.
*
* Copyright (c), Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/namespace.h"
#include "commands/defrem.h"
#include "distributed/citus_ruleutils.h"
#include "distributed/commands.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "lib/stringinfo.h"
#include "nodes/parsenodes.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
static void AppendDropViewStmt(StringInfo buf, DropStmt *stmt);
static void AppendViewNameList(StringInfo buf, List *objects);
static void AppendAlterViewStmt(StringInfo buf, AlterTableStmt *stmt);
static void AppendAlterViewCmd(StringInfo buf, AlterTableCmd *alterTableCmd);
static void AppendAlterViewOwnerStmt(StringInfo buf, AlterTableCmd *alterTableCmd);
static void AppendAlterViewSetOptionsStmt(StringInfo buf, AlterTableCmd *alterTableCmd);
static void AppendAlterViewResetOptionsStmt(StringInfo buf, AlterTableCmd *alterTableCmd);
static void AppendRenameViewStmt(StringInfo buf, RenameStmt *stmt);
static void AppendAlterViewSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt);
/*
* DeparseDropViewStmt deparses the given DROP VIEW statement.
*/
char *
DeparseDropViewStmt(Node *node)
{
DropStmt *stmt = castNode(DropStmt, node);
StringInfoData str = { 0 };
initStringInfo(&str);
Assert(stmt->removeType == OBJECT_VIEW);
AppendDropViewStmt(&str, stmt);
return str.data;
}
/*
* AppendDropViewStmt appends the deparsed representation of given drop stmt
* to the given string info buffer.
*/
static void
AppendDropViewStmt(StringInfo buf, DropStmt *stmt)
{
/*
* already tested at call site, but for future it might be collapsed in a
* DeparseDropStmt so be safe and check again
*/
Assert(stmt->removeType == OBJECT_VIEW);
appendStringInfo(buf, "DROP VIEW ");
if (stmt->missing_ok)
{
appendStringInfoString(buf, "IF EXISTS ");
}
AppendViewNameList(buf, stmt->objects);
if (stmt->behavior == DROP_CASCADE)
{
appendStringInfoString(buf, " CASCADE");
}
appendStringInfoString(buf, ";");
}
/*
* AppendViewNameList appends the qualified view names by constructing them from the given
* objects list to the given string info buffer. Note that, objects must hold schema
* qualified view names as its' members.
*/
static void
AppendViewNameList(StringInfo buf, List *viewNamesList)
{
bool isFirstView = true;
List *qualifiedViewName = NULL;
foreach_ptr(qualifiedViewName, viewNamesList)
{
char *quotedQualifiedVieName = NameListToQuotedString(qualifiedViewName);
if (!isFirstView)
{
appendStringInfo(buf, ", ");
}
appendStringInfoString(buf, quotedQualifiedVieName);
isFirstView = false;
}
}
/*
* DeparseAlterViewStmt deparses the given ALTER VIEW statement.
*/
char *
DeparseAlterViewStmt(Node *node)
{
AlterTableStmt *stmt = castNode(AlterTableStmt, node);
StringInfoData str = { 0 };
initStringInfo(&str);
AppendAlterViewStmt(&str, stmt);
return str.data;
}
static void
AppendAlterViewStmt(StringInfo buf, AlterTableStmt *stmt)
{
const char *identifier = quote_qualified_identifier(stmt->relation->schemaname,
stmt->relation->relname);
appendStringInfo(buf, "ALTER VIEW %s ", identifier);
AlterTableCmd *alterTableCmd = castNode(AlterTableCmd, lfirst(list_head(stmt->cmds)));
AppendAlterViewCmd(buf, alterTableCmd);
appendStringInfoString(buf, ";");
}
static void
AppendAlterViewCmd(StringInfo buf, AlterTableCmd *alterTableCmd)
{
switch (alterTableCmd->subtype)
{
case AT_ChangeOwner:
{
AppendAlterViewOwnerStmt(buf, alterTableCmd);
break;
}
case AT_SetRelOptions:
{
AppendAlterViewSetOptionsStmt(buf, alterTableCmd);
break;
}
case AT_ResetRelOptions:
{
AppendAlterViewResetOptionsStmt(buf, alterTableCmd);
break;
}
case AT_ColumnDefault:
{
elog(ERROR, "Citus doesn't support setting or resetting default values for a "
"column of view");
break;
}
default:
{
/*
* ALTER VIEW command only supports for the cases checked above but an
* ALTER TABLE commands targeting views may have different cases. To let
* PG throw the right error locally, we don't throw any error here
*/
break;
}
}
}
static void
AppendAlterViewOwnerStmt(StringInfo buf, AlterTableCmd *alterTableCmd)
{
appendStringInfo(buf, "OWNER TO %s", RoleSpecString(alterTableCmd->newowner, true));
}
static void
AppendAlterViewSetOptionsStmt(StringInfo buf, AlterTableCmd *alterTableCmd)
{
ListCell *lc = NULL;
bool initialOption = true;
foreach(lc, (List *) alterTableCmd->def)
{
DefElem *def = (DefElem *) lfirst(lc);
if (initialOption)
{
appendStringInfo(buf, "SET (");
initialOption = false;
}
else
{
appendStringInfo(buf, ",");
}
appendStringInfo(buf, "%s", def->defname);
if (def->arg != NULL)
{
appendStringInfo(buf, "=");
appendStringInfo(buf, "%s", defGetString(def));
}
}
appendStringInfo(buf, ")");
}
static void
AppendAlterViewResetOptionsStmt(StringInfo buf, AlterTableCmd *alterTableCmd)
{
ListCell *lc = NULL;
bool initialOption = true;
foreach(lc, (List *) alterTableCmd->def)
{
DefElem *def = (DefElem *) lfirst(lc);
if (initialOption)
{
appendStringInfo(buf, "RESET (");
initialOption = false;
}
else
{
appendStringInfo(buf, ",");
}
appendStringInfo(buf, "%s", def->defname);
}
appendStringInfo(buf, ")");
}
char *
DeparseRenameViewStmt(Node *node)
{
RenameStmt *stmt = castNode(RenameStmt, node);
StringInfoData str = { 0 };
initStringInfo(&str);
AppendRenameViewStmt(&str, stmt);
return str.data;
}
static void
AppendRenameViewStmt(StringInfo buf, RenameStmt *stmt)
{
switch (stmt->renameType)
{
case OBJECT_COLUMN:
{
const char *identifier =
quote_qualified_identifier(stmt->relation->schemaname,
stmt->relation->relname);
appendStringInfo(buf, "ALTER VIEW %s RENAME COLUMN %s TO %s;", identifier,
quote_identifier(stmt->subname), quote_identifier(
stmt->newname));
break;
}
case OBJECT_VIEW:
{
const char *identifier =
quote_qualified_identifier(stmt->relation->schemaname,
stmt->relation->relname);
appendStringInfo(buf, "ALTER VIEW %s RENAME TO %s;", identifier,
quote_identifier(stmt->newname));
break;
}
default:
{
ereport(ERROR, (errmsg("unsupported subtype for alter view rename command"),
errdetail("sub command type: %d", stmt->renameType)));
}
}
}
char *
DeparseAlterViewSchemaStmt(Node *node)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
StringInfoData str = { 0 };
initStringInfo(&str);
AppendAlterViewSchemaStmt(&str, stmt);
return str.data;
}
static void
AppendAlterViewSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt)
{
const char *identifier = quote_qualified_identifier(stmt->relation->schemaname,
stmt->relation->relname);
appendStringInfo(buf, "ALTER VIEW %s SET SCHEMA %s;", identifier, quote_identifier(
stmt->newschema));
}

View File

@ -17,7 +17,9 @@
#include "postgres.h" #include "postgres.h"
#include "distributed/commands.h"
#include "distributed/deparser.h" #include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/version_compat.h" #include "distributed/version_compat.h"
#include "parser/parse_func.h" #include "parser/parse_func.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
@ -38,8 +40,13 @@ QualifyAlterSequenceOwnerStmt(Node *node)
if (seq->schemaname == NULL) if (seq->schemaname == NULL)
{ {
Oid schemaOid = RangeVarGetCreationNamespace(seq); Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
seq->schemaname = get_namespace_name(schemaOid);
if (OidIsValid(seqOid))
{
Oid schemaOid = get_rel_namespace(seqOid);
seq->schemaname = get_namespace_name(schemaOid);
}
} }
} }
@ -59,8 +66,13 @@ QualifyAlterSequenceSchemaStmt(Node *node)
if (seq->schemaname == NULL) if (seq->schemaname == NULL)
{ {
Oid schemaOid = RangeVarGetCreationNamespace(seq); Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
seq->schemaname = get_namespace_name(schemaOid);
if (OidIsValid(seqOid))
{
Oid schemaOid = get_rel_namespace(seqOid);
seq->schemaname = get_namespace_name(schemaOid);
}
} }
} }
@ -80,7 +92,48 @@ QualifyRenameSequenceStmt(Node *node)
if (seq->schemaname == NULL) if (seq->schemaname == NULL)
{ {
Oid schemaOid = RangeVarGetCreationNamespace(seq); Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
seq->schemaname = get_namespace_name(schemaOid);
if (OidIsValid(seqOid))
{
Oid schemaOid = get_rel_namespace(seqOid);
seq->schemaname = get_namespace_name(schemaOid);
}
} }
} }
/*
* QualifyDropSequenceStmt transforms a DROP SEQUENCE
* statement in place and makes the sequence name fully qualified.
*/
void
QualifyDropSequenceStmt(Node *node)
{
DropStmt *stmt = castNode(DropStmt, node);
Assert(stmt->removeType == OBJECT_SEQUENCE);
List *objectNameListWithSchema = NIL;
List *objectNameList = NULL;
foreach_ptr(objectNameList, stmt->objects)
{
RangeVar *seq = makeRangeVarFromNameList(objectNameList);
if (seq->schemaname == NULL)
{
Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
if (OidIsValid(seqOid))
{
Oid schemaOid = get_rel_namespace(seqOid);
seq->schemaname = get_namespace_name(schemaOid);
}
}
objectNameListWithSchema = lappend(objectNameListWithSchema,
MakeNameListFromRangeVar(seq));
}
stmt->objects = objectNameListWithSchema;
}

View File

@ -15,15 +15,19 @@
#include "postgres.h" #include "postgres.h"
#include "catalog/namespace.h" #include "catalog/namespace.h"
#include "catalog/pg_statistic_ext.h"
#include "distributed/commands.h" #include "distributed/commands.h"
#include "distributed/deparser.h" #include "distributed/deparser.h"
#include "distributed/listutils.h" #include "distributed/listutils.h"
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
#include "nodes/value.h" #include "nodes/value.h"
#include "utils/syscache.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/rel.h" #include "utils/rel.h"
#include "utils/relcache.h" #include "utils/relcache.h"
static Oid GetStatsNamespaceOid(Oid statsOid);
void void
QualifyCreateStatisticsStmt(Node *node) QualifyCreateStatisticsStmt(Node *node)
{ {
@ -68,8 +72,14 @@ QualifyDropStatisticsStmt(Node *node)
if (stat->schemaname == NULL) if (stat->schemaname == NULL)
{ {
Oid schemaOid = RangeVarGetCreationNamespace(stat); Oid statsOid = get_statistics_object_oid(objectNameList,
stat->schemaname = get_namespace_name(schemaOid); dropStatisticsStmt->missing_ok);
if (OidIsValid(statsOid))
{
Oid schemaOid = GetStatsNamespaceOid(statsOid);
stat->schemaname = get_namespace_name(schemaOid);
}
} }
objectNameListWithSchema = lappend(objectNameListWithSchema, objectNameListWithSchema = lappend(objectNameListWithSchema,
@ -94,7 +104,14 @@ QualifyAlterStatisticsRenameStmt(Node *node)
if (list_length(nameList) == 1) if (list_length(nameList) == 1)
{ {
RangeVar *stat = makeRangeVarFromNameList(nameList); RangeVar *stat = makeRangeVarFromNameList(nameList);
Oid schemaOid = RangeVarGetCreationNamespace(stat); Oid statsOid = get_statistics_object_oid(nameList, renameStmt->missing_ok);
if (!OidIsValid(statsOid))
{
return;
}
Oid schemaOid = GetStatsNamespaceOid(statsOid);
stat->schemaname = get_namespace_name(schemaOid); stat->schemaname = get_namespace_name(schemaOid);
renameStmt->object = (Node *) MakeNameListFromRangeVar(stat); renameStmt->object = (Node *) MakeNameListFromRangeVar(stat);
} }
@ -115,7 +132,14 @@ QualifyAlterStatisticsSchemaStmt(Node *node)
if (list_length(nameList) == 1) if (list_length(nameList) == 1)
{ {
RangeVar *stat = makeRangeVarFromNameList(nameList); RangeVar *stat = makeRangeVarFromNameList(nameList);
Oid schemaOid = RangeVarGetCreationNamespace(stat); Oid statsOid = get_statistics_object_oid(nameList, stmt->missing_ok);
if (!OidIsValid(statsOid))
{
return;
}
Oid schemaOid = GetStatsNamespaceOid(statsOid);
stat->schemaname = get_namespace_name(schemaOid); stat->schemaname = get_namespace_name(schemaOid);
stmt->object = (Node *) MakeNameListFromRangeVar(stat); stmt->object = (Node *) MakeNameListFromRangeVar(stat);
} }
@ -136,7 +160,14 @@ QualifyAlterStatisticsStmt(Node *node)
if (list_length(stmt->defnames) == 1) if (list_length(stmt->defnames) == 1)
{ {
RangeVar *stat = makeRangeVarFromNameList(stmt->defnames); RangeVar *stat = makeRangeVarFromNameList(stmt->defnames);
Oid schemaOid = RangeVarGetCreationNamespace(stat); Oid statsOid = get_statistics_object_oid(stmt->defnames, stmt->missing_ok);
if (!OidIsValid(statsOid))
{
return;
}
Oid schemaOid = GetStatsNamespaceOid(statsOid);
stat->schemaname = get_namespace_name(schemaOid); stat->schemaname = get_namespace_name(schemaOid);
stmt->defnames = MakeNameListFromRangeVar(stat); stmt->defnames = MakeNameListFromRangeVar(stat);
} }
@ -159,8 +190,40 @@ QualifyAlterStatisticsOwnerStmt(Node *node)
if (list_length(nameList) == 1) if (list_length(nameList) == 1)
{ {
RangeVar *stat = makeRangeVarFromNameList(nameList); RangeVar *stat = makeRangeVarFromNameList(nameList);
Oid schemaOid = RangeVarGetCreationNamespace(stat); Oid statsOid = get_statistics_object_oid(nameList, /* missing_ok */ true);
if (!OidIsValid(statsOid))
{
return;
}
Oid schemaOid = GetStatsNamespaceOid(statsOid);
stat->schemaname = get_namespace_name(schemaOid); stat->schemaname = get_namespace_name(schemaOid);
stmt->object = (Node *) MakeNameListFromRangeVar(stat); stmt->object = (Node *) MakeNameListFromRangeVar(stat);
} }
} }
/*
* GetStatsNamespaceOid takes the id of a Statistics object and returns
* the id of the schema that the statistics object belongs to.
* Errors out if the stats object is not found.
*/
static Oid
GetStatsNamespaceOid(Oid statsOid)
{
HeapTuple heapTuple = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statsOid));
if (!HeapTupleIsValid(heapTuple))
{
ereport(ERROR, (errmsg("cache lookup failed for statistics "
"object with oid %u", statsOid)));
}
FormData_pg_statistic_ext *statisticsForm =
(FormData_pg_statistic_ext *) GETSTRUCT(heapTuple);
Oid result = statisticsForm->stxnamespace;
ReleaseSysCache(heapTuple);
return result;
}

View File

@ -0,0 +1,107 @@
/*-------------------------------------------------------------------------
*
* qualify_view_stmt.c
* Functions specialized in fully qualifying all view statements. These
* functions are dispatched from qualify.c
*
* Copyright (c), Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/namespace.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "nodes/nodes.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
static void QualifyViewRangeVar(RangeVar *view);
/*
* QualifyDropViewStmt quailifies the view names of the DROP VIEW statement.
*/
void
QualifyDropViewStmt(Node *node)
{
DropStmt *stmt = castNode(DropStmt, node);
List *qualifiedViewNames = NIL;
List *possiblyQualifiedViewName = NULL;
foreach_ptr(possiblyQualifiedViewName, stmt->objects)
{
char *viewName = NULL;
char *schemaName = NULL;
DeconstructQualifiedName(possiblyQualifiedViewName, &schemaName, &viewName);
if (schemaName == NULL)
{
char *objname = NULL;
Oid schemaOid = QualifiedNameGetCreationNamespace(possiblyQualifiedViewName,
&objname);
schemaName = get_namespace_name(schemaOid);
List *qualifiedViewName = list_make2(makeString(schemaName),
makeString(viewName));
qualifiedViewNames = lappend(qualifiedViewNames, qualifiedViewName);
}
else
{
qualifiedViewNames = lappend(qualifiedViewNames, possiblyQualifiedViewName);
}
}
stmt->objects = qualifiedViewNames;
}
/*
* QualifyAlterViewStmt quailifies the view name of the ALTER VIEW statement.
*/
void
QualifyAlterViewStmt(Node *node)
{
AlterTableStmt *stmt = castNode(AlterTableStmt, node);
RangeVar *view = stmt->relation;
QualifyViewRangeVar(view);
}
/*
* QualifyRenameViewStmt quailifies the view name of the ALTER VIEW ... RENAME statement.
*/
void
QualifyRenameViewStmt(Node *node)
{
RenameStmt *stmt = castNode(RenameStmt, node);
RangeVar *view = stmt->relation;
QualifyViewRangeVar(view);
}
/*
* QualifyAlterViewSchemaStmt quailifies the view name of the ALTER VIEW ... SET SCHEMA statement.
*/
void
QualifyAlterViewSchemaStmt(Node *node)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
RangeVar *view = stmt->relation;
QualifyViewRangeVar(view);
}
/*
* QualifyViewRangeVar qualifies the given view RangeVar if it is not qualified.
*/
static void
QualifyViewRangeVar(RangeVar *view)
{
if (view->schemaname == NULL)
{
Oid viewOid = RelnameGetRelid(view->relname);
Oid schemaOid = get_rel_namespace(viewOid);
view->schemaname = get_namespace_name(schemaOid);
}
}

View File

@ -152,6 +152,7 @@
#include "distributed/multi_partitioning_utils.h" #include "distributed/multi_partitioning_utils.h"
#include "distributed/multi_physical_planner.h" #include "distributed/multi_physical_planner.h"
#include "distributed/multi_server_executor.h" #include "distributed/multi_server_executor.h"
#include "distributed/param_utils.h"
#include "distributed/placement_access.h" #include "distributed/placement_access.h"
#include "distributed/placement_connection.h" #include "distributed/placement_connection.h"
#include "distributed/relation_access_tracking.h" #include "distributed/relation_access_tracking.h"
@ -171,7 +172,6 @@
#include "storage/fd.h" #include "storage/fd.h"
#include "storage/latch.h" #include "storage/latch.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/int8.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/memutils.h" #include "utils/memutils.h"
#include "utils/syscache.h" #include "utils/syscache.h"
@ -831,6 +831,19 @@ AdaptiveExecutor(CitusScanState *scanState)
distributedPlan->modLevel, taskList, excludeFromXact); distributedPlan->modLevel, taskList, excludeFromXact);
bool localExecutionSupported = true; bool localExecutionSupported = true;
/*
* In some rare cases, we have prepared statements that pass a parameter
* and never used in the query, mark such parameters' type as Invalid(0),
* which will be used later in ExtractParametersFromParamList() to map them
* to a generic datatype. Skip for dynamic parameters.
*/
if (paramListInfo && !paramListInfo->paramFetch)
{
paramListInfo = copyParamList(paramListInfo);
MarkUnreferencedExternParams((Node *) job->jobQuery, paramListInfo);
}
DistributedExecution *execution = CreateDistributedExecution( DistributedExecution *execution = CreateDistributedExecution(
distributedPlan->modLevel, distributedPlan->modLevel,
taskList, taskList,
@ -1321,7 +1334,8 @@ StartDistributedExecution(DistributedExecution *execution)
/* make sure we are not doing remote execution from within a task */ /* make sure we are not doing remote execution from within a task */
if (execution->remoteTaskList != NIL) if (execution->remoteTaskList != NIL)
{ {
EnsureRemoteTaskExecutionAllowed(); bool isRemote = true;
EnsureTaskExecutionAllowed(isRemote);
} }
} }
@ -4513,7 +4527,7 @@ ReceiveResults(WorkerSession *session, bool storeRows)
/* if there are multiple replicas, make sure to consider only one */ /* if there are multiple replicas, make sure to consider only one */
if (storeRows && *currentAffectedTupleString != '\0') if (storeRows && *currentAffectedTupleString != '\0')
{ {
scanint8(currentAffectedTupleString, false, &currentAffectedTupleCount); currentAffectedTupleCount = pg_strtoint64(currentAffectedTupleString);
Assert(currentAffectedTupleCount >= 0); Assert(currentAffectedTupleCount >= 0);
execution->rowsProcessed += currentAffectedTupleCount; execution->rowsProcessed += currentAffectedTupleCount;
} }

View File

@ -108,26 +108,26 @@
bool EnableLocalExecution = true; bool EnableLocalExecution = true;
bool LogLocalCommands = false; bool LogLocalCommands = false;
int LocalExecutorLevel = 0; /* global variable that tracks whether the local execution is on a shard */
uint64 LocalExecutorShardId = INVALID_SHARD_ID;
static LocalExecutionStatus CurrentLocalExecutionStatus = LOCAL_EXECUTION_OPTIONAL; static LocalExecutionStatus CurrentLocalExecutionStatus = LOCAL_EXECUTION_OPTIONAL;
static uint64 ExecuteLocalTaskListInternal(List *taskList,
ParamListInfo paramListInfo,
DistributedPlan *distributedPlan,
TupleDestination *defaultTupleDest,
bool isUtilityCommand);
static void SplitLocalAndRemotePlacements(List *taskPlacementList, static void SplitLocalAndRemotePlacements(List *taskPlacementList,
List **localTaskPlacementList, List **localTaskPlacementList,
List **remoteTaskPlacementList); List **remoteTaskPlacementList);
static uint64 ExecuteLocalTaskPlan(PlannedStmt *taskPlan, char *queryString, static uint64 LocallyExecuteTaskPlan(PlannedStmt *taskPlan, char *queryString,
TupleDestination *tupleDest, Task *task, TupleDestination *tupleDest, Task *task,
ParamListInfo paramListInfo); ParamListInfo paramListInfo);
static uint64 ExecuteTaskPlan(PlannedStmt *taskPlan, char *queryString,
TupleDestination *tupleDest, Task *task,
ParamListInfo paramListInfo);
static void RecordNonDistTableAccessesForTask(Task *task); static void RecordNonDistTableAccessesForTask(Task *task);
static void LogLocalCommand(Task *task); static void LogLocalCommand(Task *task);
static uint64 LocallyPlanAndExecuteMultipleQueries(List *queryStrings, static uint64 LocallyPlanAndExecuteMultipleQueries(List *queryStrings,
TupleDestination *tupleDest, TupleDestination *tupleDest,
Task *task); Task *task);
static void LocallyExecuteUtilityTask(Task *task);
static void ExecuteUdfTaskQuery(Query *localUdfCommandQuery); static void ExecuteUdfTaskQuery(Query *localUdfCommandQuery);
static void EnsureTransitionPossible(LocalExecutionStatus from, static void EnsureTransitionPossible(LocalExecutionStatus from,
LocalExecutionStatus to); LocalExecutionStatus to);
@ -204,50 +204,7 @@ ExecuteLocalTaskListExtended(List *taskList,
TupleDestination *defaultTupleDest, TupleDestination *defaultTupleDest,
bool isUtilityCommand) bool isUtilityCommand)
{ {
uint64 totalRowsProcessed = 0;
ParamListInfo paramListInfo = copyParamList(orig_paramListInfo); ParamListInfo paramListInfo = copyParamList(orig_paramListInfo);
/*
* Even if we are executing local tasks, we still enable
* coordinated transaction. This is because
* (a) we might be in a transaction, and the next commands may
* require coordinated transaction
* (b) we might be executing some tasks locally and the others
* via remote execution
*
* Also, there is no harm enabling coordinated transaction even if
* we only deal with local tasks in the transaction.
*/
UseCoordinatedTransaction();
LocalExecutorLevel++;
PG_TRY();
{
totalRowsProcessed = ExecuteLocalTaskListInternal(taskList, paramListInfo,
distributedPlan,
defaultTupleDest,
isUtilityCommand);
}
PG_CATCH();
{
LocalExecutorLevel--;
PG_RE_THROW();
}
PG_END_TRY();
LocalExecutorLevel--;
return totalRowsProcessed;
}
static uint64
ExecuteLocalTaskListInternal(List *taskList,
ParamListInfo paramListInfo,
DistributedPlan *distributedPlan,
TupleDestination *defaultTupleDest,
bool isUtilityCommand)
{
uint64 totalRowsProcessed = 0; uint64 totalRowsProcessed = 0;
int numParams = 0; int numParams = 0;
Oid *parameterTypes = NULL; Oid *parameterTypes = NULL;
@ -263,6 +220,12 @@ ExecuteLocalTaskListInternal(List *taskList,
numParams = paramListInfo->numParams; numParams = paramListInfo->numParams;
} }
if (taskList != NIL)
{
bool isRemote = false;
EnsureTaskExecutionAllowed(isRemote);
}
/* /*
* Use a new memory context that gets reset after every task to free * Use a new memory context that gets reset after every task to free
* the deparsed query string and query plan. * the deparsed query string and query plan.
@ -304,7 +267,7 @@ ExecuteLocalTaskListInternal(List *taskList,
if (isUtilityCommand) if (isUtilityCommand)
{ {
ExecuteUtilityCommand(TaskQueryString(task)); LocallyExecuteUtilityTask(task);
MemoryContextSwitchTo(oldContext); MemoryContextSwitchTo(oldContext);
MemoryContextReset(loopContext); MemoryContextReset(loopContext);
@ -391,8 +354,8 @@ ExecuteLocalTaskListInternal(List *taskList,
} }
totalRowsProcessed += totalRowsProcessed +=
ExecuteLocalTaskPlan(localPlan, shardQueryString, LocallyExecuteTaskPlan(localPlan, shardQueryString,
tupleDest, task, paramListInfo); tupleDest, task, paramListInfo);
MemoryContextSwitchTo(oldContext); MemoryContextSwitchTo(oldContext);
MemoryContextReset(loopContext); MemoryContextReset(loopContext);
@ -421,9 +384,9 @@ LocallyPlanAndExecuteMultipleQueries(List *queryStrings, TupleDestination *tuple
ParamListInfo paramListInfo = NULL; ParamListInfo paramListInfo = NULL;
PlannedStmt *localPlan = planner_compat(shardQuery, cursorOptions, PlannedStmt *localPlan = planner_compat(shardQuery, cursorOptions,
paramListInfo); paramListInfo);
totalProcessedRows += ExecuteLocalTaskPlan(localPlan, queryString, totalProcessedRows += LocallyExecuteTaskPlan(localPlan, queryString,
tupleDest, task, tupleDest, task,
paramListInfo); paramListInfo);
} }
return totalProcessedRows; return totalProcessedRows;
} }
@ -444,6 +407,39 @@ ExtractParametersForLocalExecution(ParamListInfo paramListInfo, Oid **parameterT
} }
/*
* LocallyExecuteUtilityTask runs a utility command via local execution.
*/
static void
LocallyExecuteUtilityTask(Task *task)
{
/*
* If we roll back to a savepoint, we may no longer be in a query on
* a shard. Reset the value as we go back up the stack.
*/
uint64 prevLocalExecutorShardId = LocalExecutorShardId;
if (task->anchorShardId != INVALID_SHARD_ID)
{
LocalExecutorShardId = task->anchorShardId;
}
PG_TRY();
{
ExecuteUtilityCommand(TaskQueryString(task));
}
PG_CATCH();
{
LocalExecutorShardId = prevLocalExecutorShardId;
PG_RE_THROW();
}
PG_END_TRY();
LocalExecutorShardId = prevLocalExecutorShardId;
}
/* /*
* ExecuteUtilityCommand executes the given task query in the current * ExecuteUtilityCommand executes the given task query in the current
* session. * session.
@ -569,9 +565,8 @@ ExtractLocalAndRemoteTasks(bool readOnly, List *taskList, List **localTaskList,
* At this point, we're dealing with a task that has placements on both * At this point, we're dealing with a task that has placements on both
* local and remote nodes. * local and remote nodes.
*/ */
task->partiallyLocalOrRemote = true;
Task *localTask = copyObject(task); Task *localTask = copyObject(task);
localTask->partiallyLocalOrRemote = true;
localTask->taskPlacementList = localTaskPlacementList; localTask->taskPlacementList = localTaskPlacementList;
*localTaskList = lappend(*localTaskList, localTask); *localTaskList = lappend(*localTaskList, localTask);
@ -585,6 +580,7 @@ ExtractLocalAndRemoteTasks(bool readOnly, List *taskList, List **localTaskList,
/* since shard replication factor > 1, we should have at least 1 remote task */ /* since shard replication factor > 1, we should have at least 1 remote task */
Assert(remoteTaskPlacementList != NIL); Assert(remoteTaskPlacementList != NIL);
Task *remoteTask = copyObject(task); Task *remoteTask = copyObject(task);
remoteTask->partiallyLocalOrRemote = true;
remoteTask->taskPlacementList = remoteTaskPlacementList; remoteTask->taskPlacementList = remoteTaskPlacementList;
*remoteTaskList = lappend(*remoteTaskList, remoteTask); *remoteTaskList = lappend(*remoteTaskList, remoteTask);
@ -630,9 +626,50 @@ SplitLocalAndRemotePlacements(List *taskPlacementList, List **localTaskPlacement
* case of DML. * case of DML.
*/ */
static uint64 static uint64
ExecuteLocalTaskPlan(PlannedStmt *taskPlan, char *queryString, LocallyExecuteTaskPlan(PlannedStmt *taskPlan, char *queryString,
TupleDestination *tupleDest, Task *task, TupleDestination *tupleDest, Task *task,
ParamListInfo paramListInfo) ParamListInfo paramListInfo)
{
volatile uint64 processedRows = 0;
/*
* If we roll back to a savepoint, we may no longer be in a query on
* a shard. Reset the value as we go back up the stack.
*/
uint64 prevLocalExecutorShardId = LocalExecutorShardId;
if (task->anchorShardId != INVALID_SHARD_ID)
{
LocalExecutorShardId = task->anchorShardId;
}
PG_TRY();
{
processedRows = ExecuteTaskPlan(taskPlan, queryString, tupleDest, task,
paramListInfo);
}
PG_CATCH();
{
LocalExecutorShardId = prevLocalExecutorShardId;
PG_RE_THROW();
}
PG_END_TRY();
LocalExecutorShardId = prevLocalExecutorShardId;
return processedRows;
}
/*
* ExecuteTaskPlan executes the given planned statement and writes the results
* to tupleDest.
*/
static uint64
ExecuteTaskPlan(PlannedStmt *taskPlan, char *queryString,
TupleDestination *tupleDest, Task *task,
ParamListInfo paramListInfo)
{ {
ScanDirection scanDirection = ForwardScanDirection; ScanDirection scanDirection = ForwardScanDirection;
QueryEnvironment *queryEnv = create_queryEnv(); QueryEnvironment *queryEnv = create_queryEnv();
@ -642,7 +679,7 @@ ExecuteLocalTaskPlan(PlannedStmt *taskPlan, char *queryString,
RecordNonDistTableAccessesForTask(task); RecordNonDistTableAccessesForTask(task);
MemoryContext localContext = AllocSetContextCreate(CurrentMemoryContext, MemoryContext localContext = AllocSetContextCreate(CurrentMemoryContext,
"ExecuteLocalTaskPlan", "ExecuteTaskPlan",
ALLOCSET_DEFAULT_SIZES); ALLOCSET_DEFAULT_SIZES);
MemoryContext oldContext = MemoryContextSwitchTo(localContext); MemoryContext oldContext = MemoryContextSwitchTo(localContext);

View File

@ -18,6 +18,7 @@
#include "catalog/dependency.h" #include "catalog/dependency.h"
#include "catalog/pg_class.h" #include "catalog/pg_class.h"
#include "catalog/namespace.h" #include "catalog/namespace.h"
#include "distributed/backend_data.h"
#include "distributed/citus_custom_scan.h" #include "distributed/citus_custom_scan.h"
#include "distributed/commands/multi_copy.h" #include "distributed/commands/multi_copy.h"
#include "distributed/commands/utility_hook.h" #include "distributed/commands/utility_hook.h"
@ -50,6 +51,7 @@
#include "tcop/dest.h" #include "tcop/dest.h"
#include "tcop/pquery.h" #include "tcop/pquery.h"
#include "tcop/utility.h" #include "tcop/utility.h"
#include "utils/fmgrprotos.h"
#include "utils/snapmgr.h" #include "utils/snapmgr.h"
#include "utils/memutils.h" #include "utils/memutils.h"
@ -62,6 +64,12 @@ int MultiShardConnectionType = PARALLEL_CONNECTION;
bool WritableStandbyCoordinator = false; bool WritableStandbyCoordinator = false;
bool AllowModificationsFromWorkersToReplicatedTables = true; bool AllowModificationsFromWorkersToReplicatedTables = true;
/*
* Setting that controls whether distributed queries should be
* allowed within a task execution.
*/
bool AllowNestedDistributedExecution = false;
/* /*
* Pointer to bound parameters of the current ongoing call to ExecutorRun. * Pointer to bound parameters of the current ongoing call to ExecutorRun.
* If executor is not running, then this value is meaningless. * If executor is not running, then this value is meaningless.
@ -87,6 +95,11 @@ static bool AlterTableConstraintCheck(QueryDesc *queryDesc);
static List * FindCitusCustomScanStates(PlanState *planState); static List * FindCitusCustomScanStates(PlanState *planState);
static bool CitusCustomScanStateWalker(PlanState *planState, static bool CitusCustomScanStateWalker(PlanState *planState,
List **citusCustomScanStates); List **citusCustomScanStates);
static bool IsTaskExecutionAllowed(bool isRemote);
static bool InLocalTaskExecutionOnShard(void);
static bool MaybeInRemoteTaskExecution(void);
static bool InTrigger(void);
/* /*
* CitusExecutorStart is the ExecutorStart_hook that gets called when * CitusExecutorStart is the ExecutorStart_hook that gets called when
@ -617,7 +630,8 @@ RewriteRawQueryStmt(RawStmt *rawStmt, const char *queryString, Oid *paramOids, i
numParams) numParams)
{ {
List *queryTreeList = List *queryTreeList =
pg_analyze_and_rewrite(rawStmt, queryString, paramOids, numParams, NULL); pg_analyze_and_rewrite_fixedparams(rawStmt, queryString, paramOids, numParams,
NULL);
if (list_length(queryTreeList) != 1) if (list_length(queryTreeList) != 1)
{ {
@ -803,6 +817,11 @@ GetObjectTypeString(ObjectType objType)
return "type"; return "type";
} }
case OBJECT_VIEW:
{
return "view";
}
default: default:
{ {
ereport(DEBUG1, (errmsg("unsupported object type"), ereport(DEBUG1, (errmsg("unsupported object type"),
@ -865,43 +884,146 @@ ExecutorBoundParams(void)
/* /*
* EnsureRemoteTaskExecutionAllowed ensures that we do not perform remote * EnsureTaskExecutionAllowed ensures that we do not perform remote
* execution from within a task. That could happen when the user calls * execution from within a task. That could happen when the user calls
* a function in a query that gets pushed down to the worker, and the * a function in a query that gets pushed down to the worker, and the
* function performs a query on a distributed table. * function performs a query on a distributed table.
*/ */
void void
EnsureRemoteTaskExecutionAllowed(void) EnsureTaskExecutionAllowed(bool isRemote)
{ {
if (!InTaskExecution()) if (IsTaskExecutionAllowed(isRemote))
{ {
/* we are not within a task, distributed execution is allowed */
return; return;
} }
ereport(ERROR, (errmsg("cannot execute a distributed query from a query on a " ereport(ERROR, (errmsg("cannot execute a distributed query from a query on a "
"shard"))); "shard"),
errdetail("Executing a distributed query in a function call that "
"may be pushed to a remote node can lead to incorrect "
"results."),
errhint("Avoid nesting of distributed queries or use alter user "
"current_user set citus.allow_nested_distributed_execution "
"to on to allow it with possible incorrectness.")));
} }
/* /*
* InTaskExecution determines whether we are currently in a task execution. * IsTaskExecutionAllowed determines whether task execution is currently allowed.
* In general, nested distributed execution is not allowed, except in a few cases
* (forced function call delegation, triggers).
*
* We distinguish between local and remote tasks because triggers only disallow
* remote task execution.
*/ */
bool static bool
InTaskExecution(void) IsTaskExecutionAllowed(bool isRemote)
{ {
if (LocalExecutorLevel > 0) if (AllowNestedDistributedExecution)
{ {
/* in a local task */ /* user explicitly allows nested execution */
return true; return true;
} }
/* if (!isRemote)
* Normally, any query execution within a citus-initiated backend {
* is considered a task execution, but an exception is when we if (AllowedDistributionColumnValue.isActive)
* are in a delegated function/procedure call. {
*/ /*
return IsCitusInternalBackend() && * When we are in a forced delegated function call, we explicitly check
!InTopLevelDelegatedFunctionCall && * whether local tasks use the same distribution column value in
!InDelegatedProcedureCall; * EnsureForceDelegationDistributionKey.
*/
return true;
}
if (InTrigger())
{
/*
* In triggers on shards we only disallow remote tasks. This has a few
* reasons:
*
* - We want to enable access to co-located shards, but do not have additional
* checks yet.
* - Users need to explicitly set enable_unsafe_triggers in order to create
* triggers on distributed tables.
* - Triggers on Citus local tables should be able to access other Citus local
* tables.
*/
return true;
}
}
return !InLocalTaskExecutionOnShard() && !MaybeInRemoteTaskExecution();
}
/*
* InLocalTaskExecutionOnShard returns whether we are currently in the local executor
* and it is working on a shard of a distributed table.
*
* In general, we can allow distributed queries inside of local executor, because
* we can correctly assign tasks to connections. However, we preemptively protect
* against distributed queries inside of queries on shards of a distributed table,
* because those might start failing after a shard move.
*/
static bool
InLocalTaskExecutionOnShard(void)
{
if (LocalExecutorShardId == INVALID_SHARD_ID)
{
/* local executor is not active or is processing a task without shards */
return false;
}
if (!DistributedTableShardId(LocalExecutorShardId))
{
/*
* Local executor is processing a query on a shard, but the shard belongs
* to a reference table or Citus local table. We do not expect those to
* move.
*/
return false;
}
return true;
}
/*
* MaybeInRemoteTaskExecution returns whether we could in a remote task execution.
*
* We consider anything that happens in a Citus-internal backend, except deleged
* function or procedure calls as a potential task execution.
*
* This function will also return true in other scenarios, such as during metadata
* syncing. However, since this function is mainly used for restricting (dangerous)
* nested executions, it is good to be pessimistic.
*/
static bool
MaybeInRemoteTaskExecution(void)
{
if (!IsCitusInternalBackend())
{
/* in a regular, client-initiated backend doing a regular task */
return false;
}
if (InTopLevelDelegatedFunctionCall || InDelegatedProcedureCall)
{
/* in a citus-initiated backend, but also in a delegated a procedure call */
return false;
}
return true;
}
/*
* InTrigger returns whether the execution is currently in a trigger.
*/
static bool
InTrigger(void)
{
return DatumGetInt32(pg_trigger_depth(NULL)) > 0;
} }

View File

@ -165,6 +165,7 @@ static bool FollowAllDependencies(ObjectAddressCollector *collector,
DependencyDefinition *definition); DependencyDefinition *definition);
static void ApplyAddToDependencyList(ObjectAddressCollector *collector, static void ApplyAddToDependencyList(ObjectAddressCollector *collector,
DependencyDefinition *definition); DependencyDefinition *definition);
static List * GetViewRuleReferenceDependencyList(Oid relationId);
static List * ExpandCitusSupportedTypes(ObjectAddressCollector *collector, static List * ExpandCitusSupportedTypes(ObjectAddressCollector *collector,
ObjectAddress target); ObjectAddress target);
static ViewDependencyNode * BuildViewDependencyGraph(Oid relationId, HTAB *nodeMap); static ViewDependencyNode * BuildViewDependencyGraph(Oid relationId, HTAB *nodeMap);
@ -425,7 +426,7 @@ DependencyDefinitionFromPgDepend(ObjectAddress target)
/* /*
* DependencyDefinitionFromPgDepend loads all pg_shdepend records describing the * DependencyDefinitionFromPgShDepend loads all pg_shdepend records describing the
* dependencies of target. * dependencies of target.
*/ */
static List * static List *
@ -747,7 +748,8 @@ SupportedDependencyByCitus(const ObjectAddress *address)
relKind == RELKIND_FOREIGN_TABLE || relKind == RELKIND_FOREIGN_TABLE ||
relKind == RELKIND_SEQUENCE || relKind == RELKIND_SEQUENCE ||
relKind == RELKIND_INDEX || relKind == RELKIND_INDEX ||
relKind == RELKIND_PARTITIONED_INDEX) relKind == RELKIND_PARTITIONED_INDEX ||
relKind == RELKIND_VIEW)
{ {
return true; return true;
} }
@ -764,6 +766,58 @@ SupportedDependencyByCitus(const ObjectAddress *address)
} }
/*
* ErrorOrWarnIfObjectHasUnsupportedDependency returns false without throwing any message if
* object doesn't have any unsupported dependency, else throws a message with proper level
* (except the cluster doesn't have any node) and return true.
*/
bool
ErrorOrWarnIfObjectHasUnsupportedDependency(ObjectAddress *objectAddress)
{
DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(objectAddress);
if (errMsg != NULL)
{
/*
* Don't need to give any messages if there is no worker nodes in
* the cluster as user's experience won't be affected on the single node even
* if the object won't be distributed.
*/
if (!HasAnyNodes())
{
return true;
}
/*
* Since Citus drops and recreates some object while converting a table type
* giving a DEBUG1 message is enough if the process in table type conversion
* function call
*/
if (InTableTypeConversionFunctionCall)
{
RaiseDeferredError(errMsg, DEBUG1);
}
/*
* If the view is object distributed, we should provide an error to not have
* different definition of object on coordinator and worker nodes. If the object
* is not distributed yet, we can create it locally to not affect user's local
* usage experience.
*/
else if (IsObjectDistributed(objectAddress))
{
RaiseDeferredError(errMsg, ERROR);
}
else
{
RaiseDeferredError(errMsg, WARNING);
}
return true;
}
return false;
}
/* /*
* DeferErrorIfHasUnsupportedDependency returns deferred error message if the given * DeferErrorIfHasUnsupportedDependency returns deferred error message if the given
* object has any undistributable dependency. * object has any undistributable dependency.
@ -801,8 +855,11 @@ DeferErrorIfHasUnsupportedDependency(const ObjectAddress *objectAddress)
* Otherwise, callers are expected to throw the error returned from this * Otherwise, callers are expected to throw the error returned from this
* function as a hard one by ignoring the detail part. * function as a hard one by ignoring the detail part.
*/ */
appendStringInfo(detailInfo, "\"%s\" will be created only locally", if (!IsObjectDistributed(objectAddress))
objectDescription); {
appendStringInfo(detailInfo, "\"%s\" will be created only locally",
objectDescription);
}
if (SupportedDependencyByCitus(undistributableDependency)) if (SupportedDependencyByCitus(undistributableDependency))
{ {
@ -813,9 +870,19 @@ DeferErrorIfHasUnsupportedDependency(const ObjectAddress *objectAddress)
objectDescription, objectDescription,
dependencyDescription); dependencyDescription);
appendStringInfo(hintInfo, "Distribute \"%s\" first to distribute \"%s\"", if (IsObjectDistributed(objectAddress))
dependencyDescription, {
objectDescription); appendStringInfo(hintInfo,
"Distribute \"%s\" first to modify \"%s\" on worker nodes",
dependencyDescription,
objectDescription);
}
else
{
appendStringInfo(hintInfo, "Distribute \"%s\" first to distribute \"%s\"",
dependencyDescription,
objectDescription);
}
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
errorInfo->data, detailInfo->data, hintInfo->data); errorInfo->data, detailInfo->data, hintInfo->data);
@ -893,7 +960,9 @@ GetUndistributableDependency(const ObjectAddress *objectAddress)
{ {
char relKind = get_rel_relkind(dependency->objectId); char relKind = get_rel_relkind(dependency->objectId);
if (relKind == RELKIND_SEQUENCE || relKind == RELKIND_COMPOSITE_TYPE) if (relKind == RELKIND_SEQUENCE ||
relKind == RELKIND_COMPOSITE_TYPE ||
relKind == RELKIND_VIEW)
{ {
/* citus knows how to auto-distribute these dependencies */ /* citus knows how to auto-distribute these dependencies */
continue; continue;
@ -1307,9 +1376,26 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
* create all objects required by the indices before we create the table * create all objects required by the indices before we create the table
* including indices. * including indices.
*/ */
List *indexDependencyList = GetRelationIndicesDependencyList(relationId); List *indexDependencyList = GetRelationIndicesDependencyList(relationId);
result = list_concat(result, indexDependencyList); result = list_concat(result, indexDependencyList);
/*
* Get the dependencies of the rule for the given view. PG keeps internal
* dependency between view and rule. As it is stated on the PG doc, if
* there is an internal dependency, dependencies of the dependent object
* behave much like they were dependencies of the referenced object.
*
* We need to expand dependencies by including dependencies of the rule
* internally dependent to the view. PG doesn't keep any dependencies
* from view to any object, but it keeps an internal dependency to the
* rule and that rule has dependencies to other objects.
*/
char relKind = get_rel_relkind(relationId);
if (relKind == RELKIND_VIEW)
{
List *ruleRefDepList = GetViewRuleReferenceDependencyList(relationId);
result = list_concat(result, ruleRefDepList);
}
} }
default: default:
@ -1322,6 +1408,64 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
} }
/*
* GetViewRuleReferenceDependencyList returns the dependencies of the view's
* internal rule dependencies.
*/
static List *
GetViewRuleReferenceDependencyList(Oid viewId)
{
List *dependencyTupleList = GetPgDependTuplesForDependingObjects(RelationRelationId,
viewId);
List *nonInternalDependenciesOfDependingRules = NIL;
HeapTuple depTup = NULL;
foreach_ptr(depTup, dependencyTupleList)
{
Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
/*
* Dependencies of the internal rule dependency should be handled as the dependency
* of referenced view object.
*
* PG doesn't keep dependency relation between views and dependent objects directly
* but it keeps an internal dependency relation between the view and the rule, then
* keeps the dependent objects of the view as non-internal dependencies of the
* internally dependent rule object.
*/
if (pg_depend->deptype == DEPENDENCY_INTERNAL && pg_depend->classid ==
RewriteRelationId)
{
ObjectAddress ruleAddress = { 0 };
ObjectAddressSet(ruleAddress, RewriteRelationId, pg_depend->objid);
/* Expand results with the noninternal dependencies of it */
List *ruleDependencies = DependencyDefinitionFromPgDepend(ruleAddress);
DependencyDefinition *dependencyDef = NULL;
foreach_ptr(dependencyDef, ruleDependencies)
{
/*
* Follow all dependencies of the internally dependent rule dependencies
* except it is an internal dependency of view itself.
*/
if (dependencyDef->data.pg_depend.deptype == DEPENDENCY_INTERNAL ||
(dependencyDef->data.pg_depend.refclassid == RelationRelationId &&
dependencyDef->data.pg_depend.refobjid == viewId))
{
continue;
}
nonInternalDependenciesOfDependingRules =
lappend(nonInternalDependenciesOfDependingRules, dependencyDef);
}
}
}
return nonInternalDependenciesOfDependingRules;
}
/* /*
* GetRelationSequenceDependencyList returns the sequence dependency definition * GetRelationSequenceDependencyList returns the sequence dependency definition
* list for the given relation. * list for the given relation.

View File

@ -46,7 +46,6 @@
#include "utils/rel.h" #include "utils/rel.h"
static void MarkObjectDistributedLocally(const ObjectAddress *distAddress);
static char * CreatePgDistObjectEntryCommand(const ObjectAddress *objectAddress); static char * CreatePgDistObjectEntryCommand(const ObjectAddress *objectAddress);
static int ExecuteCommandAsSuperuser(char *query, int paramCount, Oid *paramTypes, static int ExecuteCommandAsSuperuser(char *query, int paramCount, Oid *paramTypes,
Datum *paramValues); Datum *paramValues);
@ -195,7 +194,7 @@ MarkObjectDistributedViaSuperUser(const ObjectAddress *distAddress)
* This function should never be called alone, MarkObjectDistributed() or * This function should never be called alone, MarkObjectDistributed() or
* MarkObjectDistributedViaSuperUser() should be called. * MarkObjectDistributedViaSuperUser() should be called.
*/ */
static void void
MarkObjectDistributedLocally(const ObjectAddress *distAddress) MarkObjectDistributedLocally(const ObjectAddress *distAddress)
{ {
int paramCount = 3; int paramCount = 3;

View File

@ -7,7 +7,9 @@
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
#include "postgres.h"
#include "distributed/pg_version_constants.h" #include "distributed/pg_version_constants.h"
#include "pg_version_compat.h"
#include "stdint.h" #include "stdint.h"
#include "postgres.h" #include "postgres.h"
@ -150,6 +152,7 @@ typedef struct MetadataCacheData
Oid distShardShardidIndexId; Oid distShardShardidIndexId;
Oid distPlacementShardidIndexId; Oid distPlacementShardidIndexId;
Oid distPlacementPlacementidIndexId; Oid distPlacementPlacementidIndexId;
Oid distColocationidIndexId;
Oid distPlacementGroupidIndexId; Oid distPlacementGroupidIndexId;
Oid distTransactionRelationId; Oid distTransactionRelationId;
Oid distTransactionGroupIndexId; Oid distTransactionGroupIndexId;
@ -183,6 +186,9 @@ bool EnableVersionChecks = true; /* version checks are enabled */
static bool citusVersionKnownCompatible = false; static bool citusVersionKnownCompatible = false;
/* Variable to determine if we are in the process of creating citus */
static int CreateCitusTransactionLevel = 0;
/* Hash table for informations about each partition */ /* Hash table for informations about each partition */
static HTAB *DistTableCacheHash = NULL; static HTAB *DistTableCacheHash = NULL;
static List *DistTableCacheExpired = NIL; static List *DistTableCacheExpired = NIL;
@ -720,6 +726,24 @@ ReferenceTableShardId(uint64 shardId)
} }
/*
* DistributedTableShardId returns true if the given shardId belongs to
* a distributed table.
*/
bool
DistributedTableShardId(uint64 shardId)
{
if (shardId == INVALID_SHARD_ID)
{
return false;
}
ShardIdCacheEntry *shardIdEntry = LookupShardIdCacheEntry(shardId);
CitusTableCacheEntry *tableEntry = shardIdEntry->tableEntry;
return IsCitusTableTypeCacheEntry(tableEntry, DISTRIBUTED_TABLE);
}
/* /*
* LoadGroupShardPlacement returns the cached shard placement metadata * LoadGroupShardPlacement returns the cached shard placement metadata
* *
@ -1965,6 +1989,27 @@ CitusHasBeenLoadedInternal(void)
} }
/*
* GetCitusCreationLevel returns the level of the transaction creating citus
*/
int
GetCitusCreationLevel(void)
{
return CreateCitusTransactionLevel;
}
/*
* Sets the value of CreateCitusTransactionLevel based on int received which represents the
* nesting level of the transaction that created the Citus extension
*/
void
SetCreateCitusTransactionLevel(int val)
{
CreateCitusTransactionLevel = val;
}
/* /*
* CheckCitusVersion checks whether there is a version mismatch between the * CheckCitusVersion checks whether there is a version mismatch between the
* available version and the loaded version or between the installed version * available version and the loaded version or between the installed version
@ -2504,6 +2549,17 @@ DistPlacementPlacementidIndexId(void)
} }
/* return oid of pg_dist_colocation_pkey */
Oid
DistColocationIndexId(void)
{
CachedRelationLookup("pg_dist_colocation_pkey",
&MetadataCache.distColocationidIndexId);
return MetadataCache.distColocationidIndexId;
}
/* return oid of pg_dist_transaction relation */ /* return oid of pg_dist_transaction relation */
Oid Oid
DistTransactionRelationId(void) DistTransactionRelationId(void)
@ -2864,8 +2920,8 @@ CurrentUserName(void)
Oid Oid
LookupTypeOid(char *schemaNameSting, char *typeNameString) LookupTypeOid(char *schemaNameSting, char *typeNameString)
{ {
Value *schemaName = makeString(schemaNameSting); String *schemaName = makeString(schemaNameSting);
Value *typeName = makeString(typeNameString); String *typeName = makeString(typeNameString);
List *qualifiedName = list_make2(schemaName, typeName); List *qualifiedName = list_make2(schemaName, typeName);
TypeName *enumTypeName = makeTypeNameFromNameList(qualifiedName); TypeName *enumTypeName = makeTypeNameFromNameList(qualifiedName);

View File

@ -97,6 +97,7 @@ static char * SchemaOwnerName(Oid objectId);
static bool HasMetadataWorkers(void); static bool HasMetadataWorkers(void);
static void CreateShellTableOnWorkers(Oid relationId); static void CreateShellTableOnWorkers(Oid relationId);
static void CreateTableMetadataOnWorkers(Oid relationId); static void CreateTableMetadataOnWorkers(Oid relationId);
static NodeMetadataSyncResult SyncNodeMetadataToNodesOptional(void);
static bool ShouldSyncTableMetadataInternal(bool hashDistributed, static bool ShouldSyncTableMetadataInternal(bool hashDistributed,
bool citusTableWithNoDistKey); bool citusTableWithNoDistKey);
static bool SyncNodeMetadataSnapshotToNode(WorkerNode *workerNode, bool raiseOnError); static bool SyncNodeMetadataSnapshotToNode(WorkerNode *workerNode, bool raiseOnError);
@ -138,6 +139,7 @@ static char * RemoteTypeIdExpression(Oid typeId);
static char * RemoteCollationIdExpression(Oid colocationId); static char * RemoteCollationIdExpression(Oid colocationId);
PG_FUNCTION_INFO_V1(start_metadata_sync_to_all_nodes);
PG_FUNCTION_INFO_V1(start_metadata_sync_to_node); PG_FUNCTION_INFO_V1(start_metadata_sync_to_node);
PG_FUNCTION_INFO_V1(stop_metadata_sync_to_node); PG_FUNCTION_INFO_V1(stop_metadata_sync_to_node);
PG_FUNCTION_INFO_V1(worker_record_sequence_dependency); PG_FUNCTION_INFO_V1(worker_record_sequence_dependency);
@ -194,6 +196,33 @@ start_metadata_sync_to_node(PG_FUNCTION_ARGS)
} }
/*
* start_metadata_sync_to_all_nodes function sets hasmetadata column of
* all the primary worker nodes to true, and then activate nodes without
* replicating reference tables.
*/
Datum
start_metadata_sync_to_all_nodes(PG_FUNCTION_ARGS)
{
CheckCitusVersion(ERROR);
EnsureSuperUser();
EnsureCoordinator();
List *workerNodes = ActivePrimaryNonCoordinatorNodeList(RowShareLock);
bool prevReplicateRefTablesOnActivate = ReplicateReferenceTablesOnActivate;
SetLocalReplicateReferenceTablesOnActivate(false);
ActivateNodeList(workerNodes);
TransactionModifiedNodeMetadata = true;
SetLocalReplicateReferenceTablesOnActivate(prevReplicateRefTablesOnActivate);
PG_RETURN_BOOL(true);
}
/* /*
* SyncNodeMetadataToNode is the internal API for * SyncNodeMetadataToNode is the internal API for
* start_metadata_sync_to_node(). * start_metadata_sync_to_node().
@ -425,6 +454,24 @@ ClusterHasKnownMetadataWorkers()
} }
/*
* ShouldSyncUserCommandForObject checks if the user command should be synced to the
* worker nodes for the given object.
*/
bool
ShouldSyncUserCommandForObject(ObjectAddress objectAddress)
{
if (objectAddress.classId == RelationRelationId)
{
Oid relOid = objectAddress.objectId;
return ShouldSyncTableMetadata(relOid) ||
get_rel_relkind(relOid) == RELKIND_VIEW;
}
return false;
}
/* /*
* ShouldSyncTableMetadata checks if the metadata of a distributed table should be * ShouldSyncTableMetadata checks if the metadata of a distributed table should be
* propagated to metadata workers, i.e. the table is a hash distributed table or * propagated to metadata workers, i.e. the table is a hash distributed table or
@ -524,10 +571,10 @@ SyncNodeMetadataSnapshotToNode(WorkerNode *workerNode, bool raiseOnError)
*/ */
if (raiseOnError) if (raiseOnError)
{ {
SendMetadataCommandListToWorkerInCoordinatedTransaction(workerNode->workerName, SendMetadataCommandListToWorkerListInCoordinatedTransaction(list_make1(
workerNode->workerPort, workerNode),
currentUser, currentUser,
recreateMetadataSnapshotCommandList); recreateMetadataSnapshotCommandList);
return true; return true;
} }
else else
@ -2219,16 +2266,16 @@ DetachPartitionCommandList(void)
/* /*
* SyncNodeMetadataToNodes tries recreating the metadata snapshot in the * SyncNodeMetadataToNodesOptional tries recreating the metadata
* metadata workers that are out of sync. Returns the result of * snapshot in the metadata workers that are out of sync.
* synchronization. * Returns the result of synchronization.
* *
* This function must be called within coordinated transaction * This function must be called within coordinated transaction
* since updates on the pg_dist_node metadata must be rollbacked if anything * since updates on the pg_dist_node metadata must be rollbacked if anything
* goes wrong. * goes wrong.
*/ */
static NodeMetadataSyncResult static NodeMetadataSyncResult
SyncNodeMetadataToNodes(void) SyncNodeMetadataToNodesOptional(void)
{ {
NodeMetadataSyncResult result = NODE_METADATA_SYNC_SUCCESS; NodeMetadataSyncResult result = NODE_METADATA_SYNC_SUCCESS;
if (!IsCoordinator()) if (!IsCoordinator())
@ -2288,6 +2335,46 @@ SyncNodeMetadataToNodes(void)
} }
/*
* SyncNodeMetadataToNodes recreates the node metadata snapshot in all the
* metadata workers.
*
* This function runs within a coordinated transaction since updates on
* the pg_dist_node metadata must be rollbacked if anything
* goes wrong.
*/
void
SyncNodeMetadataToNodes(void)
{
EnsureCoordinator();
/*
* Request a RowExclusiveLock so we don't run concurrently with other
* functions updating pg_dist_node, but allow concurrency with functions
* which are just reading from pg_dist_node.
*/
if (!ConditionalLockRelationOid(DistNodeRelationId(), RowExclusiveLock))
{
ereport(ERROR, (errmsg("cannot sync metadata because a concurrent "
"metadata syncing operation is in progress")));
}
List *workerList = ActivePrimaryNonCoordinatorNodeList(NoLock);
WorkerNode *workerNode = NULL;
foreach_ptr(workerNode, workerList)
{
if (workerNode->hasMetadata)
{
SetWorkerColumnLocalOnly(workerNode, Anum_pg_dist_node_metadatasynced,
BoolGetDatum(true));
bool raiseOnError = true;
SyncNodeMetadataSnapshotToNode(workerNode, raiseOnError);
}
}
}
/* /*
* SyncNodeMetadataToNodesMain is the main function for syncing node metadata to * SyncNodeMetadataToNodesMain is the main function for syncing node metadata to
* MX nodes. It retries until success and then exits. * MX nodes. It retries until success and then exits.
@ -2334,7 +2421,7 @@ SyncNodeMetadataToNodesMain(Datum main_arg)
{ {
UseCoordinatedTransaction(); UseCoordinatedTransaction();
NodeMetadataSyncResult result = SyncNodeMetadataToNodes(); NodeMetadataSyncResult result = SyncNodeMetadataToNodesOptional();
syncedAllNodes = (result == NODE_METADATA_SYNC_SUCCESS); syncedAllNodes = (result == NODE_METADATA_SYNC_SUCCESS);
/* we use LISTEN/NOTIFY to wait for metadata syncing in tests */ /* we use LISTEN/NOTIFY to wait for metadata syncing in tests */
@ -3393,12 +3480,19 @@ ColocationGroupCreateCommandList(void)
"distributioncolumncollationschema) AS (VALUES "); "distributioncolumncollationschema) AS (VALUES ");
Relation pgDistColocation = table_open(DistColocationRelationId(), AccessShareLock); Relation pgDistColocation = table_open(DistColocationRelationId(), AccessShareLock);
Relation colocationIdIndexRel = index_open(DistColocationIndexId(), AccessShareLock);
bool indexOK = false; /*
SysScanDesc scanDescriptor = systable_beginscan(pgDistColocation, InvalidOid, indexOK, * It is not strictly necessary to read the tuples in order.
NULL, 0, NULL); * However, it is useful to get consistent behavior, both for regression
* tests and also in production systems.
*/
SysScanDesc scanDescriptor =
systable_beginscan_ordered(pgDistColocation, colocationIdIndexRel,
NULL, 0, NULL);
HeapTuple colocationTuple = systable_getnext(scanDescriptor); HeapTuple colocationTuple = systable_getnext_ordered(scanDescriptor,
ForwardScanDirection);
while (HeapTupleIsValid(colocationTuple)) while (HeapTupleIsValid(colocationTuple))
{ {
@ -3456,10 +3550,11 @@ ColocationGroupCreateCommandList(void)
"NULL, NULL)"); "NULL, NULL)");
} }
colocationTuple = systable_getnext(scanDescriptor); colocationTuple = systable_getnext_ordered(scanDescriptor, ForwardScanDirection);
} }
systable_endscan(scanDescriptor); systable_endscan_ordered(scanDescriptor);
index_close(colocationIdIndexRel, AccessShareLock);
table_close(pgDistColocation, AccessShareLock); table_close(pgDistColocation, AccessShareLock);
if (!hasColocations) if (!hasColocations)

View File

@ -106,17 +106,18 @@ static void InsertPlaceholderCoordinatorRecord(void);
static void InsertNodeRow(int nodeid, char *nodename, int32 nodeport, NodeMetadata static void InsertNodeRow(int nodeid, char *nodename, int32 nodeport, NodeMetadata
*nodeMetadata); *nodeMetadata);
static void DeleteNodeRow(char *nodename, int32 nodeport); static void DeleteNodeRow(char *nodename, int32 nodeport);
static void SyncDistributedObjectsToNode(WorkerNode *workerNode); static void SyncDistributedObjectsToNodeList(List *workerNodeList);
static void UpdateLocalGroupIdOnNode(WorkerNode *workerNode); static void UpdateLocalGroupIdOnNode(WorkerNode *workerNode);
static void SyncPgDistTableMetadataToNode(WorkerNode *workerNode); static void SyncPgDistTableMetadataToNodeList(List *nodeList);
static List * InterTableRelationshipCommandList(); static List * InterTableRelationshipCommandList();
static void BlockDistributedQueriesOnMetadataNodes(void);
static WorkerNode * TupleToWorkerNode(TupleDesc tupleDescriptor, HeapTuple heapTuple); static WorkerNode * TupleToWorkerNode(TupleDesc tupleDescriptor, HeapTuple heapTuple);
static List * PropagateNodeWideObjectsCommandList(); static List * PropagateNodeWideObjectsCommandList();
static WorkerNode * ModifiableWorkerNode(const char *nodeName, int32 nodePort); static WorkerNode * ModifiableWorkerNode(const char *nodeName, int32 nodePort);
static bool NodeIsLocal(WorkerNode *worker); static bool NodeIsLocal(WorkerNode *worker);
static void SetLockTimeoutLocally(int32 lock_cooldown); static void SetLockTimeoutLocally(int32 lock_cooldown);
static void UpdateNodeLocation(int32 nodeId, char *newNodeName, int32 newNodePort); static void UpdateNodeLocation(int32 nodeId, char *newNodeName, int32 newNodePort);
static bool UnsetMetadataSyncedForAll(void); static bool UnsetMetadataSyncedForAllWorkers(void);
static char * GetMetadataSyncCommandToSetNodeColumn(WorkerNode *workerNode, static char * GetMetadataSyncCommandToSetNodeColumn(WorkerNode *workerNode,
int columnIndex, int columnIndex,
Datum value); Datum value);
@ -150,6 +151,7 @@ PG_FUNCTION_INFO_V1(get_shard_id_for_distribution_column);
PG_FUNCTION_INFO_V1(citus_nodename_for_nodeid); PG_FUNCTION_INFO_V1(citus_nodename_for_nodeid);
PG_FUNCTION_INFO_V1(citus_nodeport_for_nodeid); PG_FUNCTION_INFO_V1(citus_nodeport_for_nodeid);
PG_FUNCTION_INFO_V1(citus_coordinator_nodeid); PG_FUNCTION_INFO_V1(citus_coordinator_nodeid);
PG_FUNCTION_INFO_V1(citus_is_coordinator);
/* /*
@ -451,7 +453,7 @@ citus_disable_node(PG_FUNCTION_ARGS)
{ {
text *nodeNameText = PG_GETARG_TEXT_P(0); text *nodeNameText = PG_GETARG_TEXT_P(0);
int32 nodePort = PG_GETARG_INT32(1); int32 nodePort = PG_GETARG_INT32(1);
bool forceDisableNode = PG_GETARG_BOOL(2); bool synchronousDisableNode = PG_GETARG_BOOL(2);
char *nodeName = text_to_cstring(nodeNameText); char *nodeName = text_to_cstring(nodeNameText);
WorkerNode *workerNode = ModifiableWorkerNode(nodeName, nodePort); WorkerNode *workerNode = ModifiableWorkerNode(nodeName, nodePort);
@ -462,8 +464,10 @@ citus_disable_node(PG_FUNCTION_ARGS)
"isactive"); "isactive");
WorkerNode *firstWorkerNode = GetFirstPrimaryWorkerNode(); WorkerNode *firstWorkerNode = GetFirstPrimaryWorkerNode();
if (!forceDisableNode && firstWorkerNode && bool disablingFirstNode =
firstWorkerNode->nodeId == workerNode->nodeId) (firstWorkerNode && firstWorkerNode->nodeId == workerNode->nodeId);
if (disablingFirstNode && !synchronousDisableNode)
{ {
/* /*
* We sync metadata async and optionally in the background worker, * We sync metadata async and optionally in the background worker,
@ -477,16 +481,21 @@ citus_disable_node(PG_FUNCTION_ARGS)
* possibility of diverged shard placements for the same shard. * possibility of diverged shard placements for the same shard.
* *
* To prevent that, we currently do not allow disabling the first * To prevent that, we currently do not allow disabling the first
* worker node. * worker node unless it is explicitly opted synchronous.
*/ */
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("disabling the first worker node in the " errmsg("disabling the first worker node in the "
"metadata is not allowed"), "metadata is not allowed"),
errhint("You can force disabling node, but this operation " errhint("You can force disabling node, SELECT "
"might cause replicated shards to diverge: SELECT " "citus_disable_node('%s', %d, "
"citus_disable_node('%s', %d, force:=true);", "synchronous:=true);", workerNode->workerName,
workerNode->workerName, nodePort),
nodePort))); errdetail("Citus uses the first worker node in the "
"metadata for certain internal operations when "
"replicated tables are modified. Synchronous mode "
"ensures that all nodes have the same view of the "
"first worker node, which is used for certain "
"locking operations.")));
} }
/* /*
@ -505,38 +514,55 @@ citus_disable_node(PG_FUNCTION_ARGS)
* for any given shard. * for any given shard.
*/ */
ErrorIfNodeContainsNonRemovablePlacements(workerNode); ErrorIfNodeContainsNonRemovablePlacements(workerNode);
bool onlyConsiderActivePlacements = false;
if (NodeGroupHasShardPlacements(workerNode->groupId,
onlyConsiderActivePlacements))
{
ereport(NOTICE, (errmsg(
"Node %s:%d has active shard placements. Some queries "
"may fail after this operation. Use "
"SELECT citus_activate_node('%s', %d) to activate this "
"node back.",
workerNode->workerName, nodePort,
workerNode->workerName,
nodePort)));
}
} }
TransactionModifiedNodeMetadata = true; TransactionModifiedNodeMetadata = true;
/* if (synchronousDisableNode)
* We have not propagated the node metadata changes yet, make sure that all the
* active nodes get the metadata updates. We defer this operation to the
* background worker to make it possible disabling nodes when multiple nodes
* are down.
*
* Note that the active placements reside on the active nodes. Hence, when
* Citus finds active placements, it filters out the placements that are on
* the disabled nodes. That's why, we don't have to change/sync placement
* metadata at this point. Instead, we defer that to citus_activate_node()
* where we expect all nodes up and running.
*/
if (UnsetMetadataSyncedForAll())
{ {
/*
* The user might pick between sync vs async options.
* - Pros for the sync option:
* (a) the changes become visible on the cluster immediately
* (b) even if the first worker node is disabled, there is no
* risk of divergence of the placements of replicated shards
* - Cons for the sync options:
* (a) Does not work within 2PC transaction (e.g., BEGIN;
* citus_disable_node(); PREPARE TRANSACTION ...);
* (b) If there are multiple node failures (e.g., one another node
* than the current node being disabled), the sync option would
* fail because it'd try to sync the metadata changes to a node
* that is not up and running.
*/
if (firstWorkerNode && firstWorkerNode->nodeId == workerNode->nodeId)
{
/*
* We cannot let any modification query on a replicated table to run
* concurrently with citus_disable_node() on the first worker node. If
* we let that, some worker nodes might calculate FirstWorkerNode()
* different than others. See LockShardListResourcesOnFirstWorker()
* for the details.
*/
BlockDistributedQueriesOnMetadataNodes();
}
SyncNodeMetadataToNodes();
}
else if (UnsetMetadataSyncedForAllWorkers())
{
/*
* We have not propagated the node metadata changes yet, make sure that all the
* active nodes get the metadata updates. We defer this operation to the
* background worker to make it possible disabling nodes when multiple nodes
* are down.
*
* Note that the active placements reside on the active nodes. Hence, when
* Citus finds active placements, it filters out the placements that are on
* the disabled nodes. That's why, we don't have to change/sync placement
* metadata at this point. Instead, we defer that to citus_activate_node()
* where we expect all nodes up and running.
*/
TriggerNodeMetadataSyncOnCommit(); TriggerNodeMetadataSyncOnCommit();
} }
@ -544,6 +570,33 @@ citus_disable_node(PG_FUNCTION_ARGS)
} }
/*
* BlockDistributedQueriesOnMetadataNodes blocks all the modification queries on
* all nodes. Hence, should be used with caution.
*/
static void
BlockDistributedQueriesOnMetadataNodes(void)
{
/* first, block on the coordinator */
LockRelationOid(DistNodeRelationId(), ExclusiveLock);
/*
* Note that we might re-design this lock to be more granular than
* pg_dist_node, scoping only for modifications on the replicated
* tables. However, we currently do not have any such mechanism and
* given that citus_disable_node() runs instantly, it seems acceptable
* to block reads (or modifications on non-replicated tables) for
* a while.
*/
/* only superuser can disable node */
Assert(superuser());
SendCommandToWorkersWithMetadata(
"LOCK TABLE pg_catalog.pg_dist_node IN EXCLUSIVE MODE;");
}
/* /*
* master_disable_node is a wrapper function for old UDF name. * master_disable_node is a wrapper function for old UDF name.
*/ */
@ -790,7 +843,7 @@ SyncDistributedObjectsCommandList(WorkerNode *workerNode)
/* /*
* SyncDistributedObjectsToNode sync the distributed objects to the node. It includes * SyncDistributedObjectsToNodeList sync the distributed objects to the node. It includes
* - All dependencies (e.g., types, schemas, sequences) * - All dependencies (e.g., types, schemas, sequences)
* - All shell distributed table * - All shell distributed table
* - Inter relation between those shell tables * - Inter relation between those shell tables
@ -799,17 +852,29 @@ SyncDistributedObjectsCommandList(WorkerNode *workerNode)
* since all the dependencies should be present in the coordinator already. * since all the dependencies should be present in the coordinator already.
*/ */
static void static void
SyncDistributedObjectsToNode(WorkerNode *workerNode) SyncDistributedObjectsToNodeList(List *workerNodeList)
{ {
if (NodeIsCoordinator(workerNode)) List *workerNodesToSync = NIL;
WorkerNode *workerNode = NULL;
foreach_ptr(workerNode, workerNodeList)
{ {
/* coordinator has all the objects */ if (NodeIsCoordinator(workerNode))
return; {
/* coordinator has all the objects */
continue;
}
if (!NodeIsPrimary(workerNode))
{
/* secondary nodes gets the objects from their primaries via replication */
continue;
}
workerNodesToSync = lappend(workerNodesToSync, workerNode);
} }
if (!NodeIsPrimary(workerNode)) if (workerNodesToSync == NIL)
{ {
/* secondary nodes gets the objects from their primaries via replication */
return; return;
} }
@ -821,9 +886,8 @@ SyncDistributedObjectsToNode(WorkerNode *workerNode)
/* send commands to new workers, the current user should be a superuser */ /* send commands to new workers, the current user should be a superuser */
Assert(superuser()); Assert(superuser());
SendMetadataCommandListToWorkerInCoordinatedTransaction( SendMetadataCommandListToWorkerListInCoordinatedTransaction(
workerNode->workerName, workerNodesToSync,
workerNode->workerPort,
CurrentUserName(), CurrentUserName(),
commandList); commandList);
} }
@ -841,9 +905,8 @@ UpdateLocalGroupIdOnNode(WorkerNode *workerNode)
/* send commands to new workers, the current user should be a superuser */ /* send commands to new workers, the current user should be a superuser */
Assert(superuser()); Assert(superuser());
SendMetadataCommandListToWorkerInCoordinatedTransaction( SendMetadataCommandListToWorkerListInCoordinatedTransaction(
workerNode->workerName, list_make1(workerNode),
workerNode->workerPort,
CurrentUserName(), CurrentUserName(),
commandList); commandList);
} }
@ -851,25 +914,33 @@ UpdateLocalGroupIdOnNode(WorkerNode *workerNode)
/* /*
* SyncPgDistTableMetadataToNode syncs the pg_dist_partition, pg_dist_shard * SyncPgDistTableMetadataToNodeList syncs the pg_dist_partition, pg_dist_shard
* pg_dist_placement and pg_dist_object metadata entries. * pg_dist_placement and pg_dist_object metadata entries.
* *
*/ */
static void static void
SyncPgDistTableMetadataToNode(WorkerNode *workerNode) SyncPgDistTableMetadataToNodeList(List *nodeList)
{ {
if (NodeIsPrimary(workerNode) && !NodeIsCoordinator(workerNode)) /* send commands to new workers, the current user should be a superuser */
{ Assert(superuser());
List *syncPgDistMetadataCommandList = PgDistTableMetadataSyncCommandList();
/* send commands to new workers, the current user should be a superuser */ List *syncPgDistMetadataCommandList = PgDistTableMetadataSyncCommandList();
Assert(superuser());
SendMetadataCommandListToWorkerInCoordinatedTransaction( List *nodesWithMetadata = NIL;
workerNode->workerName, WorkerNode *workerNode = NULL;
workerNode->workerPort, foreach_ptr(workerNode, nodeList)
CurrentUserName(), {
syncPgDistMetadataCommandList); if (NodeIsPrimary(workerNode) && !NodeIsCoordinator(workerNode))
{
nodesWithMetadata = lappend(nodesWithMetadata, workerNode);
}
} }
SendMetadataCommandListToWorkerListInCoordinatedTransaction(
nodesWithMetadata,
CurrentUserName(),
syncPgDistMetadataCommandList);
} }
@ -1065,15 +1136,14 @@ PrimaryNodeForGroup(int32 groupId, bool *groupContainsNodes)
/* /*
* ActivateNode activates the node with nodeName and nodePort. Currently, activation * ActivateNodeList iterates over the nodeList and activates the nodes.
* includes only replicating the reference tables and setting isactive column of the * Some part of the node activation is done parallel across the nodes,
* given node. * such as syncing the metadata. However, reference table replication is
* done one by one across nodes.
*/ */
int void
ActivateNode(char *nodeName, int nodePort) ActivateNodeList(List *nodeList)
{ {
bool isActive = true;
/* /*
* We currently require the object propagation to happen via superuser, * We currently require the object propagation to happen via superuser,
* see #5139. While activating a node, we sync both metadata and object * see #5139. While activating a node, we sync both metadata and object
@ -1090,86 +1160,130 @@ ActivateNode(char *nodeName, int nodePort)
/* take an exclusive lock on pg_dist_node to serialize pg_dist_node changes */ /* take an exclusive lock on pg_dist_node to serialize pg_dist_node changes */
LockRelationOid(DistNodeRelationId(), ExclusiveLock); LockRelationOid(DistNodeRelationId(), ExclusiveLock);
/*
* First, locally mark the node is active, if everything goes well,
* we are going to sync this information to all the metadata nodes.
*/
WorkerNode *workerNode = FindWorkerNodeAnyCluster(nodeName, nodePort);
if (workerNode == NULL)
{
ereport(ERROR, (errmsg("node at \"%s:%u\" does not exist", nodeName, nodePort)));
}
/* List *nodeToSyncMetadata = NIL;
* Delete existing reference and replicated table placements on the WorkerNode *node = NULL;
* given groupId if the group has been disabled earlier (e.g., isActive foreach_ptr(node, nodeList)
* set to false).
*
* Sync the metadata changes to all existing metadata nodes irrespective
* of the current nodes' metadata sync state. We expect all nodes up
* and running when another node is activated.
*/
if (!workerNode->isActive && NodeIsPrimary(workerNode))
{
bool localOnly = false;
DeleteAllReplicatedTablePlacementsFromNodeGroup(workerNode->groupId,
localOnly);
}
workerNode =
SetWorkerColumnLocalOnly(workerNode, Anum_pg_dist_node_isactive,
BoolGetDatum(isActive));
/* TODO: Once all tests will be enabled for MX, we can remove sync by default check */
bool syncMetadata = EnableMetadataSync && NodeIsPrimary(workerNode);
if (syncMetadata)
{ {
/* /*
* We are going to sync the metadata anyway in this transaction, so do * First, locally mark the node is active, if everything goes well,
* not fail just because the current metadata is not synced. * we are going to sync this information to all the metadata nodes.
*/ */
SetWorkerColumn(workerNode, Anum_pg_dist_node_metadatasynced, WorkerNode *workerNode =
BoolGetDatum(true)); FindWorkerNodeAnyCluster(node->workerName, node->workerPort);
if (workerNode == NULL)
/*
* Update local group id first, as object dependency logic requires to have
* updated local group id.
*/
UpdateLocalGroupIdOnNode(workerNode);
/*
* Sync distributed objects first. We must sync distributed objects before
* replicating reference tables to the remote node, as reference tables may
* need such objects.
*/
SyncDistributedObjectsToNode(workerNode);
/*
* We need to replicate reference tables before syncing node metadata, otherwise
* reference table replication logic would try to get lock on the new node before
* having the shard placement on it
*/
if (ReplicateReferenceTablesOnActivate)
{ {
ReplicateAllReferenceTablesToNode(workerNode); ereport(ERROR, (errmsg("node at \"%s:%u\" does not exist", node->workerName,
node->workerPort)));
} }
/* /* both nodes should be the same */
* Sync node metadata. We must sync node metadata before syncing table Assert(workerNode->nodeId == node->nodeId);
* related pg_dist_xxx metadata. Since table related metadata requires
* to have right pg_dist_node entries.
*/
SyncNodeMetadataToNode(nodeName, nodePort);
/* /*
* As the last step, sync the table related metadata to the remote node. * Delete existing reference and replicated table placements on the
* We must handle it as the last step because of limitations shared with * given groupId if the group has been disabled earlier (e.g., isActive
* above comments. * set to false).
*
* Sync the metadata changes to all existing metadata nodes irrespective
* of the current nodes' metadata sync state. We expect all nodes up
* and running when another node is activated.
*/ */
SyncPgDistTableMetadataToNode(workerNode); if (!workerNode->isActive && NodeIsPrimary(workerNode))
{
bool localOnly = false;
DeleteAllReplicatedTablePlacementsFromNodeGroup(workerNode->groupId,
localOnly);
}
workerNode =
SetWorkerColumnLocalOnly(workerNode, Anum_pg_dist_node_isactive,
BoolGetDatum(true));
/* TODO: Once all tests will be enabled for MX, we can remove sync by default check */
bool syncMetadata = EnableMetadataSync && NodeIsPrimary(workerNode);
if (syncMetadata)
{
/*
* We are going to sync the metadata anyway in this transaction, so do
* not fail just because the current metadata is not synced.
*/
SetWorkerColumn(workerNode, Anum_pg_dist_node_metadatasynced,
BoolGetDatum(true));
/*
* Update local group id first, as object dependency logic requires to have
* updated local group id.
*/
UpdateLocalGroupIdOnNode(workerNode);
nodeToSyncMetadata = lappend(nodeToSyncMetadata, workerNode);
}
} }
/*
* Sync distributed objects first. We must sync distributed objects before
* replicating reference tables to the remote node, as reference tables may
* need such objects.
*/
SyncDistributedObjectsToNodeList(nodeToSyncMetadata);
if (ReplicateReferenceTablesOnActivate)
{
foreach_ptr(node, nodeList)
{
/*
* We need to replicate reference tables before syncing node metadata, otherwise
* reference table replication logic would try to get lock on the new node before
* having the shard placement on it
*/
if (NodeIsPrimary(node))
{
ReplicateAllReferenceTablesToNode(node);
}
}
}
/*
* Sync node metadata. We must sync node metadata before syncing table
* related pg_dist_xxx metadata. Since table related metadata requires
* to have right pg_dist_node entries.
*/
foreach_ptr(node, nodeToSyncMetadata)
{
SyncNodeMetadataToNode(node->workerName, node->workerPort);
}
/*
* As the last step, sync the table related metadata to the remote node.
* We must handle it as the last step because of limitations shared with
* above comments.
*/
SyncPgDistTableMetadataToNodeList(nodeToSyncMetadata);
foreach_ptr(node, nodeList)
{
bool isActive = true;
/* finally, let all other active metadata nodes to learn about this change */
SetNodeState(node->workerName, node->workerPort, isActive);
}
}
/*
* ActivateNode activates the node with nodeName and nodePort. Currently, activation
* includes only replicating the reference tables and setting isactive column of the
* given node.
*/
int
ActivateNode(char *nodeName, int nodePort)
{
bool isActive = true;
WorkerNode *workerNode = ModifiableWorkerNode(nodeName, nodePort);
ActivateNodeList(list_make1(workerNode));
/* finally, let all other active metadata nodes to learn about this change */ /* finally, let all other active metadata nodes to learn about this change */
WorkerNode *newWorkerNode = SetNodeState(nodeName, nodePort, isActive); WorkerNode *newWorkerNode = SetNodeState(nodeName, nodePort, isActive);
Assert(newWorkerNode->nodeId == workerNode->nodeId); Assert(newWorkerNode->nodeId == workerNode->nodeId);
@ -1319,7 +1433,7 @@ citus_update_node(PG_FUNCTION_ARGS)
* early, but that's fine, since this will start a retry loop with * early, but that's fine, since this will start a retry loop with
* 5 second intervals until sync is complete. * 5 second intervals until sync is complete.
*/ */
if (UnsetMetadataSyncedForAll()) if (UnsetMetadataSyncedForAllWorkers())
{ {
TriggerNodeMetadataSyncOnCommit(); TriggerNodeMetadataSyncOnCommit();
} }
@ -1558,6 +1672,29 @@ citus_coordinator_nodeid(PG_FUNCTION_ARGS)
} }
/*
* citus_is_coordinator returns whether the current node is a coordinator.
* We consider the node a coordinator if its group ID is 0 and it has
* pg_dist_node entries (only group ID 0 could indicate a worker without
* metadata).
*/
Datum
citus_is_coordinator(PG_FUNCTION_ARGS)
{
CheckCitusVersion(ERROR);
bool isCoordinator = false;
if (GetLocalGroupId() == COORDINATOR_GROUP_ID &&
ActivePrimaryNodeCount() > 0)
{
isCoordinator = true;
}
PG_RETURN_BOOL(isCoordinator);
}
/* /*
* FindWorkerNode searches over the worker nodes and returns the workerNode * FindWorkerNode searches over the worker nodes and returns the workerNode
* if it already exists. Else, the function returns NULL. * if it already exists. Else, the function returns NULL.
@ -2646,15 +2783,15 @@ DatumToString(Datum datum, Oid dataType)
/* /*
* UnsetMetadataSyncedForAll sets the metadatasynced column of all metadata * UnsetMetadataSyncedForAllWorkers sets the metadatasynced column of all metadata
* nodes to false. It returns true if it updated at least a node. * worker nodes to false. It returns true if it updated at least a node.
*/ */
static bool static bool
UnsetMetadataSyncedForAll(void) UnsetMetadataSyncedForAllWorkers(void)
{ {
bool updatedAtLeastOne = false; bool updatedAtLeastOne = false;
ScanKeyData scanKey[2]; ScanKeyData scanKey[3];
int scanKeyCount = 2; int scanKeyCount = 3;
bool indexOK = false; bool indexOK = false;
/* /*
@ -2669,6 +2806,11 @@ UnsetMetadataSyncedForAll(void)
ScanKeyInit(&scanKey[1], Anum_pg_dist_node_metadatasynced, ScanKeyInit(&scanKey[1], Anum_pg_dist_node_metadatasynced,
BTEqualStrategyNumber, F_BOOLEQ, BoolGetDatum(true)); BTEqualStrategyNumber, F_BOOLEQ, BoolGetDatum(true));
/* coordinator always has the up to date metadata */
ScanKeyInit(&scanKey[2], Anum_pg_dist_node_groupid,
BTGreaterStrategyNumber, F_INT4GT,
Int32GetDatum(COORDINATOR_GROUP_ID));
CatalogIndexState indstate = CatalogOpenIndexes(relation); CatalogIndexState indstate = CatalogOpenIndexes(relation);
SysScanDesc scanDescriptor = systable_beginscan(relation, SysScanDesc scanDescriptor = systable_beginscan(relation,

View File

@ -1,6 +1,6 @@
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* *
* pg_get_object_address_12_13_14.c * pg_get_object_address_13_14_15.c
* *
* Copied functions from Postgres pg_get_object_address with acl/owner check. * Copied functions from Postgres pg_get_object_address with acl/owner check.
* Since we need to use intermediate data types Relation and Node from * Since we need to use intermediate data types Relation and Node from
@ -40,11 +40,6 @@ static void ErrorIfCurrentUserCanNotDistributeObject(ObjectType type,
Relation *relation); Relation *relation);
static List * textarray_to_strvaluelist(ArrayType *arr); static List * textarray_to_strvaluelist(ArrayType *arr);
/* It is defined on PG >= 13 versions by default */
#if PG_VERSION_NUM < PG_VERSION_13
#define TYPALIGN_INT 'i'
#endif
/* /*
* PgGetObjectAddress gets the object address. This function is mostly copied from * PgGetObjectAddress gets the object address. This function is mostly copied from
* pg_get_object_address of the PG code. We need to copy that function to use * pg_get_object_address of the PG code. We need to copy that function to use
@ -283,6 +278,9 @@ PgGetObjectAddress(char *ttype, ArrayType *namearr, ArrayType *argsarr)
case OBJECT_FDW: case OBJECT_FDW:
case OBJECT_FOREIGN_SERVER: case OBJECT_FOREIGN_SERVER:
case OBJECT_LANGUAGE: case OBJECT_LANGUAGE:
#if PG_VERSION_NUM >= PG_VERSION_15
case OBJECT_PARAMETER_ACL:
#endif
case OBJECT_PUBLICATION: case OBJECT_PUBLICATION:
case OBJECT_ROLE: case OBJECT_ROLE:
case OBJECT_SCHEMA: case OBJECT_SCHEMA:
@ -320,6 +318,9 @@ PgGetObjectAddress(char *ttype, ArrayType *namearr, ArrayType *argsarr)
break; break;
} }
#if PG_VERSION_NUM >= PG_VERSION_15
case OBJECT_PUBLICATION_NAMESPACE:
#endif
case OBJECT_USER_MAPPING: case OBJECT_USER_MAPPING:
{ {
objnode = (Node *) list_make2(linitial(name), linitial(args)); objnode = (Node *) list_make2(linitial(name), linitial(args));
@ -419,6 +420,7 @@ ErrorIfCurrentUserCanNotDistributeObject(ObjectType type, ObjectAddress *addr,
case OBJECT_TABLE: case OBJECT_TABLE:
case OBJECT_EXTENSION: case OBJECT_EXTENSION:
case OBJECT_COLLATION: case OBJECT_COLLATION:
case OBJECT_VIEW:
{ {
check_object_ownership(userId, type, *addr, node, *relation); check_object_ownership(userId, type, *addr, node, *relation);
break; break;

View File

@ -31,6 +31,9 @@
#include "utils/builtins.h" #include "utils/builtins.h"
#define SET_APPLICATION_NAME_QUERY \
"SET application_name TO '" CITUS_RUN_COMMAND_APPLICATION_NAME "'"
PG_FUNCTION_INFO_V1(master_run_on_worker); PG_FUNCTION_INFO_V1(master_run_on_worker);
static int ParseCommandParameters(FunctionCallInfo fcinfo, StringInfo **nodeNameArray, static int ParseCommandParameters(FunctionCallInfo fcinfo, StringInfo **nodeNameArray,
@ -44,15 +47,15 @@ static void ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray,
int commandCount); int commandCount);
static bool GetConnectionStatusAndResult(MultiConnection *connection, bool *resultStatus, static bool GetConnectionStatusAndResult(MultiConnection *connection, bool *resultStatus,
StringInfo queryResultString); StringInfo queryResultString);
static bool EvaluateQueryResult(MultiConnection *connection, PGresult *queryResult,
StringInfo queryResultString);
static void StoreErrorMessage(MultiConnection *connection, StringInfo queryResultString);
static void ExecuteCommandsAndStoreResults(StringInfo *nodeNameArray, static void ExecuteCommandsAndStoreResults(StringInfo *nodeNameArray,
int *nodePortArray, int *nodePortArray,
StringInfo *commandStringArray, StringInfo *commandStringArray,
bool *statusArray, bool *statusArray,
StringInfo *resultStringArray, StringInfo *resultStringArray,
int commandCount); int commandCount);
static bool ExecuteOptionalSingleResultCommand(MultiConnection *connection,
char *queryString, StringInfo
queryResultString);
static Tuplestorestate * CreateTupleStore(TupleDesc tupleDescriptor, static Tuplestorestate * CreateTupleStore(TupleDesc tupleDescriptor,
StringInfo *nodeNameArray, int *nodePortArray, StringInfo *nodeNameArray, int *nodePortArray,
bool *statusArray, bool *statusArray,
@ -241,18 +244,66 @@ ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray, int *nodePor
FinishConnectionEstablishment(connection); FinishConnectionEstablishment(connection);
/* check whether connection attempt was successful */
if (PQstatus(connection->pgConn) != CONNECTION_OK) if (PQstatus(connection->pgConn) != CONNECTION_OK)
{ {
appendStringInfo(queryResultString, "failed to connect to %s:%d", nodeName, appendStringInfo(queryResultString, "failed to connect to %s:%d", nodeName,
(int) nodePort); nodePort);
statusArray[commandIndex] = false; statusArray[commandIndex] = false;
CloseConnection(connection);
connectionArray[commandIndex] = NULL; connectionArray[commandIndex] = NULL;
finishedCount++; finishedCount++;
continue;
} }
else
/* set the application_name to avoid nested execution checks */
int querySent = SendRemoteCommand(connection, SET_APPLICATION_NAME_QUERY);
if (querySent == 0)
{ {
statusArray[commandIndex] = true; StoreErrorMessage(connection, queryResultString);
statusArray[commandIndex] = false;
CloseConnection(connection);
connectionArray[commandIndex] = NULL;
finishedCount++;
continue;
} }
statusArray[commandIndex] = true;
}
/* send queries at once */
for (int commandIndex = 0; commandIndex < commandCount; commandIndex++)
{
MultiConnection *connection = connectionArray[commandIndex];
if (connection == NULL)
{
continue;
}
bool raiseInterrupts = true;
PGresult *queryResult = GetRemoteCommandResult(connection, raiseInterrupts);
/* write the result value or error message to queryResultString */
StringInfo queryResultString = resultStringArray[commandIndex];
bool success = EvaluateSingleQueryResult(connection, queryResult,
queryResultString);
if (!success)
{
statusArray[commandIndex] = false;
CloseConnection(connection);
connectionArray[commandIndex] = NULL;
finishedCount++;
continue;
}
/* clear results for the next command */
PQclear(queryResult);
bool raiseErrors = false;
ClearResults(connection, raiseErrors);
/* we only care about the SET application_name result on failure */
resetStringInfo(queryResultString);
} }
/* send queries at once */ /* send queries at once */
@ -359,7 +410,7 @@ GetConnectionStatusAndResult(MultiConnection *connection, bool *resultStatus,
/* query result is available at this point */ /* query result is available at this point */
PGresult *queryResult = PQgetResult(connection->pgConn); PGresult *queryResult = PQgetResult(connection->pgConn);
bool success = EvaluateQueryResult(connection, queryResult, queryResultString); bool success = EvaluateSingleQueryResult(connection, queryResult, queryResultString);
PQclear(queryResult); PQclear(queryResult);
*resultStatus = success; *resultStatus = success;
@ -368,95 +419,6 @@ GetConnectionStatusAndResult(MultiConnection *connection, bool *resultStatus,
} }
/*
* EvaluateQueryResult gets the query result from connection and returns
* true if the query is executed successfully, false otherwise. A query result
* or an error message is returned in queryResultString. The function requires
* that the query returns a single column/single row result. It returns an
* error otherwise.
*/
static bool
EvaluateQueryResult(MultiConnection *connection, PGresult *queryResult,
StringInfo queryResultString)
{
bool success = false;
ExecStatusType resultStatus = PQresultStatus(queryResult);
if (resultStatus == PGRES_COMMAND_OK)
{
char *commandStatus = PQcmdStatus(queryResult);
appendStringInfo(queryResultString, "%s", commandStatus);
success = true;
}
else if (resultStatus == PGRES_TUPLES_OK)
{
int ntuples = PQntuples(queryResult);
int nfields = PQnfields(queryResult);
/* error if query returns more than 1 rows, or more than 1 fields */
if (nfields != 1)
{
appendStringInfo(queryResultString,
"expected a single column in query target");
}
else if (ntuples > 1)
{
appendStringInfo(queryResultString,
"expected a single row in query result");
}
else
{
int row = 0;
int column = 0;
if (!PQgetisnull(queryResult, row, column))
{
char *queryResultValue = PQgetvalue(queryResult, row, column);
appendStringInfo(queryResultString, "%s", queryResultValue);
}
success = true;
}
}
else
{
StoreErrorMessage(connection, queryResultString);
}
return success;
}
/*
* StoreErrorMessage gets the error message from connection and stores it
* in queryResultString. It should be called only when error is present
* otherwise it would return a default error message.
*/
static void
StoreErrorMessage(MultiConnection *connection, StringInfo queryResultString)
{
char *errorMessage = PQerrorMessage(connection->pgConn);
if (errorMessage != NULL)
{
/* copy the error message to a writable memory */
errorMessage = pnstrdup(errorMessage, strlen(errorMessage));
char *firstNewlineIndex = strchr(errorMessage, '\n');
/* trim the error message at the line break */
if (firstNewlineIndex != NULL)
{
*firstNewlineIndex = '\0';
}
}
else
{
/* put a default error message if no error message is reported */
errorMessage = "An error occurred while running the query";
}
appendStringInfo(queryResultString, "%s", errorMessage);
}
/* /*
* ExecuteCommandsAndStoreResults connects to each node specified in * ExecuteCommandsAndStoreResults connects to each node specified in
* nodeNameArray and nodePortArray, and executes command in commandStringArray * nodeNameArray and nodePortArray, and executes command in commandStringArray
@ -471,63 +433,76 @@ ExecuteCommandsAndStoreResults(StringInfo *nodeNameArray, int *nodePortArray,
{ {
for (int commandIndex = 0; commandIndex < commandCount; commandIndex++) for (int commandIndex = 0; commandIndex < commandCount; commandIndex++)
{ {
CHECK_FOR_INTERRUPTS();
char *nodeName = nodeNameArray[commandIndex]->data; char *nodeName = nodeNameArray[commandIndex]->data;
int32 nodePort = nodePortArray[commandIndex]; int32 nodePort = nodePortArray[commandIndex];
char *queryString = commandStringArray[commandIndex]->data; char *queryString = commandStringArray[commandIndex]->data;
StringInfo queryResultString = resultStringArray[commandIndex]; StringInfo queryResultString = resultStringArray[commandIndex];
bool reportResultError = false;
bool success = ExecuteRemoteQueryOrCommand(nodeName, nodePort, queryString, int connectionFlags = FORCE_NEW_CONNECTION;
queryResultString, reportResultError); MultiConnection *connection =
GetNodeConnection(connectionFlags, nodeName, nodePort);
/* set the application_name to avoid nested execution checks */
bool success = ExecuteOptionalSingleResultCommand(connection,
SET_APPLICATION_NAME_QUERY,
queryResultString);
if (!success)
{
statusArray[commandIndex] = false;
CloseConnection(connection);
continue;
}
/* we only care about the SET application_name result on failure */
resetStringInfo(queryResultString);
/* send the actual query string */
success = ExecuteOptionalSingleResultCommand(connection, queryString,
queryResultString);
statusArray[commandIndex] = success; statusArray[commandIndex] = success;
CloseConnection(connection);
CHECK_FOR_INTERRUPTS();
} }
} }
/* /*
* ExecuteRemoteQueryOrCommand executes a query at specified remote node using * ExecuteOptionalSingleResultCommand executes a query at specified remote node using
* the calling user's credentials. The function returns the query status * the calling user's credentials. The function returns the query status
* (success/failure), and query result. The query is expected to return a single * (success/failure), and query result. The query is expected to return a single
* target containing zero or one rows. * target containing zero or one rows.
*/ */
bool static bool
ExecuteRemoteQueryOrCommand(char *nodeName, uint32 nodePort, char *queryString, ExecuteOptionalSingleResultCommand(MultiConnection *connection, char *queryString,
StringInfo queryResultString, bool reportResultError) StringInfo queryResultString)
{ {
int connectionFlags = FORCE_NEW_CONNECTION;
MultiConnection *connection =
GetNodeConnection(connectionFlags, nodeName, nodePort);
bool raiseInterrupts = true;
if (PQstatus(connection->pgConn) != CONNECTION_OK) if (PQstatus(connection->pgConn) != CONNECTION_OK)
{ {
appendStringInfo(queryResultString, "failed to connect to %s:%d", nodeName, appendStringInfo(queryResultString, "failed to connect to %s:%d",
(int) nodePort); connection->hostname, connection->port);
return false; return false;
} }
if (!SendRemoteCommand(connection, queryString)) if (!SendRemoteCommand(connection, queryString))
{ {
appendStringInfo(queryResultString, "failed to send query to %s:%d", nodeName, appendStringInfo(queryResultString, "failed to send query to %s:%d",
(int) nodePort); connection->hostname, connection->port);
return false; return false;
} }
bool raiseInterrupts = true;
PGresult *queryResult = GetRemoteCommandResult(connection, raiseInterrupts); PGresult *queryResult = GetRemoteCommandResult(connection, raiseInterrupts);
bool success = EvaluateQueryResult(connection, queryResult, queryResultString);
if (!success && reportResultError) /* write the result value or error message to queryResultString */
{ bool success = EvaluateSingleQueryResult(connection, queryResult, queryResultString);
ReportResultError(connection, queryResult, ERROR);
}
/* clear result and close the connection */
PQclear(queryResult); PQclear(queryResult);
/* close the connection */ bool raiseErrors = false;
CloseConnection(connection); ClearResults(connection, raiseErrors);
return success; return success;
} }

View File

@ -1039,12 +1039,12 @@ CitusCreateAlterColumnarTableSet(char *qualifiedRelationName,
initStringInfo(&buf); initStringInfo(&buf);
appendStringInfo(&buf, appendStringInfo(&buf,
"SELECT alter_columnar_table_set(%s, " "ALTER TABLE %s SET ("
"chunk_group_row_limit => %d, " "columnar.chunk_group_row_limit = %d, "
"stripe_row_limit => %lu, " "columnar.stripe_row_limit = %lu, "
"compression_level => %d, " "columnar.compression_level = %d, "
"compression => %s);", "columnar.compression = %s);",
quote_literal_cstr(qualifiedRelationName), qualifiedRelationName,
options->chunkRowCount, options->chunkRowCount,
options->stripeRowCount, options->stripeRowCount,
options->compressionLevel, options->compressionLevel,

View File

@ -54,7 +54,6 @@
#include "storage/lmgr.h" #include "storage/lmgr.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/fmgroids.h" #include "utils/fmgroids.h"
#include "utils/int8.h"
#include "utils/json.h" #include "utils/json.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/memutils.h" #include "utils/memutils.h"
@ -1396,9 +1395,9 @@ GetShardStatistics(MultiConnection *connection, HTAB *shardIds)
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
{ {
char *shardIdString = PQgetvalue(result, rowIndex, 0); char *shardIdString = PQgetvalue(result, rowIndex, 0);
uint64 shardId = pg_strtouint64(shardIdString, NULL, 10); uint64 shardId = strtou64(shardIdString, NULL, 10);
char *sizeString = PQgetvalue(result, rowIndex, 1); char *sizeString = PQgetvalue(result, rowIndex, 1);
uint64 totalSize = pg_strtouint64(sizeString, NULL, 10); uint64 totalSize = strtou64(sizeString, NULL, 10);
ShardStatistics *statistics = ShardStatistics *statistics =
hash_search(shardStatistics, &shardId, HASH_ENTER, NULL); hash_search(shardStatistics, &shardId, HASH_ENTER, NULL);

View File

@ -923,7 +923,7 @@ WorkerShardStats(ShardPlacement *placement, Oid relationId, const char *shardNam
} }
errno = 0; errno = 0;
uint64 tableSize = pg_strtouint64(tableSizeString, &tableSizeStringEnd, 0); uint64 tableSize = strtou64(tableSizeString, &tableSizeStringEnd, 0);
if (errno != 0 || (*tableSizeStringEnd) != '\0') if (errno != 0 || (*tableSizeStringEnd) != '\0')
{ {
PQclear(queryResult); PQclear(queryResult);

View File

@ -393,7 +393,7 @@ NodeNamePortCompare(const char *workerLhsName, const char *workerRhsName,
WorkerNode * WorkerNode *
GetFirstPrimaryWorkerNode(void) GetFirstPrimaryWorkerNode(void)
{ {
List *workerNodeList = ActivePrimaryNonCoordinatorNodeList(NoLock); List *workerNodeList = ActivePrimaryNonCoordinatorNodeList(RowShareLock);
WorkerNode *firstWorkerNode = NULL; WorkerNode *firstWorkerNode = NULL;
WorkerNode *workerNode = NULL; WorkerNode *workerNode = NULL;
foreach_ptr(workerNode, workerNodeList) foreach_ptr(workerNode, workerNodeList)

View File

@ -12,6 +12,7 @@
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
#include "postgres.h" #include "postgres.h"
#include "pg_version_compat.h"
#include "distributed/pg_version_constants.h" #include "distributed/pg_version_constants.h"
#include "distributed/cte_inline.h" #include "distributed/cte_inline.h"
@ -309,7 +310,7 @@ inline_cte_walker(Node *node, inline_cte_walker_context *context)
*/ */
if (columnAliasCount >= columnIndex) if (columnAliasCount >= columnIndex)
{ {
Value *columnAlias = (Value *) list_nth(columnAliasList, columnIndex - 1); String *columnAlias = (String *) list_nth(columnAliasList, columnIndex - 1);
Assert(IsA(columnAlias, String)); Assert(IsA(columnAlias, String));
TargetEntry *targetEntry = TargetEntry *targetEntry =
list_nth(rte->subquery->targetList, columnIndex - 1); list_nth(rte->subquery->targetList, columnIndex - 1);

View File

@ -1065,7 +1065,7 @@ CreateDistributedPlan(uint64 planId, Query *originalQuery, Query *query, ParamLi
/* /*
* EnsurePartitionTableNotReplicated errors out if the infput relation is * EnsurePartitionTableNotReplicated errors out if the input relation is
* a partition table and the table has a replication factor greater than * a partition table and the table has a replication factor greater than
* one. * one.
* *
@ -1353,7 +1353,7 @@ FinalizeRouterPlan(PlannedStmt *localPlan, CustomScan *customScan)
TargetEntry *targetEntry = NULL; TargetEntry *targetEntry = NULL;
foreach_ptr(targetEntry, customScan->scan.plan.targetlist) foreach_ptr(targetEntry, customScan->scan.plan.targetlist)
{ {
Value *columnName = makeString(targetEntry->resname); String *columnName = makeString(targetEntry->resname);
columnNameList = lappend(columnNameList, columnName); columnNameList = lappend(columnNameList, columnName);
} }

View File

@ -102,15 +102,15 @@ PlannedStmt *
GeneratePlaceHolderPlannedStmt(Query *parse) GeneratePlaceHolderPlannedStmt(Query *parse)
{ {
PlannedStmt *result = makeNode(PlannedStmt); PlannedStmt *result = makeNode(PlannedStmt);
SeqScan *seqScanNode = makeNode(SeqScan); Scan *scanNode = makeNode(Scan);
Plan *plan = &seqScanNode->plan; Plan *plan = &scanNode->plan;
Node *distKey PG_USED_FOR_ASSERTS_ONLY = NULL; Node *distKey PG_USED_FOR_ASSERTS_ONLY = NULL;
AssertArg(FastPathRouterQuery(parse, &distKey)); AssertArg(FastPathRouterQuery(parse, &distKey));
/* there is only a single relation rte */ /* there is only a single relation rte */
seqScanNode->scanrelid = 1; scanNode->scanrelid = 1;
plan->targetlist = plan->targetlist =
copyObject(FetchStatementTargetList((Node *) parse)); copyObject(FetchStatementTargetList((Node *) parse));

View File

@ -17,6 +17,7 @@
#include "catalog/pg_proc.h" #include "catalog/pg_proc.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/defrem.h" #include "commands/defrem.h"
#include "distributed/backend_data.h"
#include "distributed/metadata_utility.h" #include "distributed/metadata_utility.h"
#include "distributed/citus_ruleutils.h" #include "distributed/citus_ruleutils.h"
#include "distributed/colocation_utils.h" #include "distributed/colocation_utils.h"

View File

@ -1062,8 +1062,8 @@ worker_save_query_explain_analyze(PG_FUNCTION_ARGS)
} }
/* resolve OIDs of unknown (user-defined) types */ /* resolve OIDs of unknown (user-defined) types */
Query *analyzedQuery = parse_analyze_varparams(parseTree, queryString, Query *analyzedQuery = parse_analyze_varparams_compat(parseTree, queryString,
&paramTypes, &numParams); &paramTypes, &numParams, NULL);
#if PG_VERSION_NUM >= PG_VERSION_14 #if PG_VERSION_NUM >= PG_VERSION_14

View File

@ -798,7 +798,7 @@ DerivedColumnNameList(uint32 columnCount, uint64 generatingJobId)
appendStringInfo(columnName, UINT64_FORMAT "_", generatingJobId); appendStringInfo(columnName, UINT64_FORMAT "_", generatingJobId);
appendStringInfo(columnName, "%u", columnIndex); appendStringInfo(columnName, "%u", columnIndex);
Value *columnValue = makeString(columnName->data); String *columnValue = makeString(columnName->data);
columnNameList = lappend(columnNameList, columnValue); columnNameList = lappend(columnNameList, columnValue);
} }

View File

@ -151,7 +151,7 @@ static Job * RouterJob(Query *originalQuery,
static bool RelationPrunesToMultipleShards(List *relationShardList); static bool RelationPrunesToMultipleShards(List *relationShardList);
static void NormalizeMultiRowInsertTargetList(Query *query); static void NormalizeMultiRowInsertTargetList(Query *query);
static void AppendNextDummyColReference(Alias *expendedReferenceNames); static void AppendNextDummyColReference(Alias *expendedReferenceNames);
static Value * MakeDummyColumnString(int dummyColumnId); static String * MakeDummyColumnString(int dummyColumnId);
static List * BuildRoutesForInsert(Query *query, DeferredErrorMessage **planningError); static List * BuildRoutesForInsert(Query *query, DeferredErrorMessage **planningError);
static List * GroupInsertValuesByShardId(List *insertValuesList); static List * GroupInsertValuesByShardId(List *insertValuesList);
static List * ExtractInsertValuesList(Query *query, Var *partitionColumn); static List * ExtractInsertValuesList(Query *query, Var *partitionColumn);
@ -3249,7 +3249,7 @@ AppendNextDummyColReference(Alias *expendedReferenceNames)
{ {
int existingColReferences = list_length(expendedReferenceNames->colnames); int existingColReferences = list_length(expendedReferenceNames->colnames);
int nextColReferenceId = existingColReferences + 1; int nextColReferenceId = existingColReferences + 1;
Value *missingColumnString = MakeDummyColumnString(nextColReferenceId); String *missingColumnString = MakeDummyColumnString(nextColReferenceId);
expendedReferenceNames->colnames = lappend(expendedReferenceNames->colnames, expendedReferenceNames->colnames = lappend(expendedReferenceNames->colnames,
missingColumnString); missingColumnString);
} }
@ -3259,12 +3259,12 @@ AppendNextDummyColReference(Alias *expendedReferenceNames)
* MakeDummyColumnString returns a String (Value) object by appending given * MakeDummyColumnString returns a String (Value) object by appending given
* integer to end of the "column" string. * integer to end of the "column" string.
*/ */
static Value * static String *
MakeDummyColumnString(int dummyColumnId) MakeDummyColumnString(int dummyColumnId)
{ {
StringInfo dummyColumnStringInfo = makeStringInfo(); StringInfo dummyColumnStringInfo = makeStringInfo();
appendStringInfo(dummyColumnStringInfo, "column%d", dummyColumnId); appendStringInfo(dummyColumnStringInfo, "column%d", dummyColumnId);
Value *dummyColumnString = makeString(dummyColumnStringInfo->data); String *dummyColumnString = makeString(dummyColumnStringInfo->data);
return dummyColumnString; return dummyColumnString;
} }

View File

@ -1952,7 +1952,7 @@ BuildReadIntermediateResultsQuery(List *targetEntryList, List *columnAliasList,
*/ */
if (columnAliasCount >= columnNumber) if (columnAliasCount >= columnNumber)
{ {
Value *columnAlias = (Value *) list_nth(columnAliasList, columnNumber - 1); String *columnAlias = (String *) list_nth(columnAliasList, columnNumber - 1);
Assert(IsA(columnAlias, String)); Assert(IsA(columnAlias, String));
newTargetEntry->resname = strVal(columnAlias); newTargetEntry->resname = strVal(columnAlias);
} }

View File

@ -326,8 +326,8 @@ RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId)
if (objectType == OBJECT_TABLE || objectType == OBJECT_INDEX || if (objectType == OBJECT_TABLE || objectType == OBJECT_INDEX ||
objectType == OBJECT_FOREIGN_TABLE || objectType == OBJECT_FOREIGN_SERVER) objectType == OBJECT_FOREIGN_TABLE || objectType == OBJECT_FOREIGN_SERVER)
{ {
Value *relationSchemaNameValue = NULL; String *relationSchemaNameValue = NULL;
Value *relationNameValue = NULL; String *relationNameValue = NULL;
uint32 dropCount = list_length(dropStmt->objects); uint32 dropCount = list_length(dropStmt->objects);
if (dropCount > 1) if (dropCount > 1)
@ -381,11 +381,11 @@ RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId)
/* prefix with schema name if it is not added already */ /* prefix with schema name if it is not added already */
if (relationSchemaNameValue == NULL) if (relationSchemaNameValue == NULL)
{ {
Value *schemaNameValue = makeString(pstrdup(schemaName)); String *schemaNameValue = makeString(pstrdup(schemaName));
relationNameList = lcons(schemaNameValue, relationNameList); relationNameList = lcons(schemaNameValue, relationNameList);
} }
char **relationName = &(relationNameValue->val.str); char **relationName = &(strVal(relationNameValue));
AppendShardIdToName(relationName, shardId); AppendShardIdToName(relationName, shardId);
} }
else if (objectType == OBJECT_POLICY) else if (objectType == OBJECT_POLICY)
@ -750,10 +750,10 @@ UpdateWholeRowColumnReferencesWalker(Node *node, uint64 *shardId)
* extend the penultimate element with the shardId. * extend the penultimate element with the shardId.
*/ */
int colrefFieldCount = list_length(columnRef->fields); int colrefFieldCount = list_length(columnRef->fields);
Value *relnameValue = list_nth(columnRef->fields, colrefFieldCount - 2); String *relnameValue = list_nth(columnRef->fields, colrefFieldCount - 2);
Assert(IsA(relnameValue, String)); Assert(IsA(relnameValue, String));
AppendShardIdToName(&relnameValue->val.str, *shardId); AppendShardIdToName(&strVal(relnameValue), *shardId);
} }
/* might be more than one ColumnRef to visit */ /* might be more than one ColumnRef to visit */

View File

@ -24,8 +24,11 @@
#include "safe_lib.h" #include "safe_lib.h"
#include "catalog/pg_authid.h" #include "catalog/pg_authid.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_extension.h"
#include "citus_version.h" #include "citus_version.h"
#include "commands/explain.h" #include "commands/explain.h"
#include "commands/extension.h"
#include "common/string.h" #include "common/string.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "distributed/backend_data.h" #include "distributed/backend_data.h"
@ -74,7 +77,7 @@
#include "distributed/shared_library_init.h" #include "distributed/shared_library_init.h"
#include "distributed/statistics_collection.h" #include "distributed/statistics_collection.h"
#include "distributed/subplan_execution.h" #include "distributed/subplan_execution.h"
#include "distributed/resource_lock.h"
#include "distributed/transaction_management.h" #include "distributed/transaction_management.h"
#include "distributed/transaction_recovery.h" #include "distributed/transaction_recovery.h"
#include "distributed/utils/directory.h" #include "distributed/utils/directory.h"
@ -141,9 +144,12 @@ static int ReplicationModel = REPLICATION_MODEL_STREAMING;
/* we override the application_name assign_hook and keep a pointer to the old one */ /* we override the application_name assign_hook and keep a pointer to the old one */
static GucStringAssignHook OldApplicationNameAssignHook = NULL; static GucStringAssignHook OldApplicationNameAssignHook = NULL;
static object_access_hook_type PrevObjectAccessHook = NULL;
void _PG_init(void); void _PG_init(void);
static void CitusObjectAccessHook(ObjectAccessType access, Oid classId, Oid objectId, int
subId, void *arg);
static void DoInitialCleanup(void); static void DoInitialCleanup(void);
static void ResizeStackToMaximumDepth(void); static void ResizeStackToMaximumDepth(void);
static void multi_log_hook(ErrorData *edata); static void multi_log_hook(ErrorData *edata);
@ -159,9 +165,9 @@ static bool ErrorIfNotASuitableDeadlockFactor(double *newval, void **extra,
static bool WarnIfDeprecatedExecutorUsed(int *newval, void **extra, GucSource source); static bool WarnIfDeprecatedExecutorUsed(int *newval, void **extra, GucSource source);
static bool WarnIfReplicationModelIsSet(int *newval, void **extra, GucSource source); static bool WarnIfReplicationModelIsSet(int *newval, void **extra, GucSource source);
static bool NoticeIfSubqueryPushdownEnabled(bool *newval, void **extra, GucSource source); static bool NoticeIfSubqueryPushdownEnabled(bool *newval, void **extra, GucSource source);
static bool HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, static bool ShowShardsForAppNamePrefixesCheckHook(char **newval, void **extra,
GucSource source); GucSource source);
static void HideShardsFromAppNamePrefixesAssignHook(const char *newval, void *extra); static void ShowShardsForAppNamePrefixesAssignHook(const char *newval, void *extra);
static void ApplicationNameAssignHook(const char *newval, void *extra); static void ApplicationNameAssignHook(const char *newval, void *extra);
static bool NodeConninfoGucCheckHook(char **newval, void **extra, GucSource source); static bool NodeConninfoGucCheckHook(char **newval, void **extra, GucSource source);
static void NodeConninfoGucAssignHook(const char *newval, void *extra); static void NodeConninfoGucAssignHook(const char *newval, void *extra);
@ -334,9 +340,6 @@ _PG_init(void)
/* intercept planner */ /* intercept planner */
planner_hook = distributed_planner; planner_hook = distributed_planner;
/* register utility hook */
ProcessUtility_hook = multi_ProcessUtility;
/* register for planner hook */ /* register for planner hook */
set_rel_pathlist_hook = multi_relation_restriction_hook; set_rel_pathlist_hook = multi_relation_restriction_hook;
set_join_pathlist_hook = multi_join_restriction_hook; set_join_pathlist_hook = multi_join_restriction_hook;
@ -384,23 +387,22 @@ _PG_init(void)
DoInitialCleanup(); DoInitialCleanup();
} }
PrevObjectAccessHook = object_access_hook;
object_access_hook = CitusObjectAccessHook;
/* ensure columnar module is loaded at the right time */ /* ensure columnar module is loaded at the right time */
load_file(COLUMNAR_MODULE_NAME, false); load_file(COLUMNAR_MODULE_NAME, false);
/* /*
* Now, acquire symbols from columnar module. First, acquire * Register utility hook. This must be done after loading columnar, so
* the address of the set options hook, and set it so that we * that the citus hook is called first, followed by the columnar hook,
* can propagate options changes. * followed by standard_ProcessUtility. That allows citus to distribute
* ALTER TABLE commands before columnar strips out the columnar-specific
* options.
*/ */
ColumnarTableSetOptions_hook_type **ColumnarTableSetOptions_hook_ptr = PrevProcessUtility = (ProcessUtility_hook != NULL) ?
(ColumnarTableSetOptions_hook_type **) find_rendezvous_variable( ProcessUtility_hook : standard_ProcessUtility;
COLUMNAR_SETOPTIONS_HOOK_SYM); ProcessUtility_hook = multi_ProcessUtility;
/* rendezvous variable registered during columnar initialization */
Assert(ColumnarTableSetOptions_hook_ptr != NULL);
Assert(*ColumnarTableSetOptions_hook_ptr != NULL);
**ColumnarTableSetOptions_hook_ptr = ColumnarTableSetOptionsHook;
/* /*
* Acquire symbols for columnar functions that citus calls. * Acquire symbols for columnar functions that citus calls.
@ -670,6 +672,43 @@ RegisterCitusConfigVariables(void)
GUC_NO_SHOW_ALL, GUC_NO_SHOW_ALL,
NULL, NULL, NULL); NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.allow_nested_distributed_execution",
gettext_noop("Enables distributed execution within a task "
"of another distributed execution."),
gettext_noop("Nested distributed execution can happen when Citus "
"pushes down a call to a user-defined function within "
"a distributed query, and the function contains another "
"distributed query. In this scenario, Citus makes no "
"guarantess with regards to correctness and it is therefore "
"disallowed by default. This setting can be used to allow "
"nested distributed execution."),
&AllowNestedDistributedExecution,
false,
PGC_USERSET,
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.allow_unsafe_locks_from_workers",
gettext_noop("Enables acquiring a distributed lock from a worker "
"when the coordinator is not in the metadata"),
gettext_noop("Set to false by default. If set to true, enables "
"acquiring a distributed lock from a worker "
"when the coordinator is not in the metadata. "
"This type of lock is unsafe because the worker will not be "
"able to lock the coordinator; the coordinator will be able to "
"intialize distributed operations on the resources locked "
"by the worker. This can lead to concurrent operations from the "
"coordinator and distributed deadlocks since the coordinator "
"and the workers would not acquire locks across the same nodes "
"in the same order."),
&EnableAcquiringUnsafeLockFromWorkers,
false,
PGC_USERSET,
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomBoolVariable( DefineCustomBoolVariable(
"citus.check_available_space_before_move", "citus.check_available_space_before_move",
gettext_noop("When enabled will check free disk space before a shard move"), gettext_noop("When enabled will check free disk space before a shard move"),
@ -1174,24 +1213,6 @@ RegisterCitusConfigVariables(void)
GUC_NO_SHOW_ALL, GUC_NO_SHOW_ALL,
NULL, NULL, NULL); NULL, NULL, NULL);
DefineCustomStringVariable(
"citus.hide_shards_from_app_name_prefixes",
gettext_noop("If application_name starts with one of these values, hide shards"),
gettext_noop("Citus places distributed tables and shards in the same schema. "
"That can cause confusion when inspecting the list of tables on "
"a node with shards. This GUC can be used to hide the shards from "
"pg_class for certain applications based on the application_name "
"of the connection. The default is *, which hides shards from all "
"applications. This behaviour can be overridden using the "
"citus.override_table_visibility setting"),
&HideShardsFromAppNamePrefixes,
"*",
PGC_USERSET,
GUC_STANDARD,
HideShardsFromAppNamePrefixesCheckHook,
HideShardsFromAppNamePrefixesAssignHook,
NULL);
DefineCustomIntVariable( DefineCustomIntVariable(
"citus.isolation_test_session_process_id", "citus.isolation_test_session_process_id",
NULL, NULL,
@ -1716,6 +1737,25 @@ RegisterCitusConfigVariables(void)
GUC_STANDARD, GUC_STANDARD,
NULL, NULL, NULL); NULL, NULL, NULL);
DefineCustomStringVariable(
"citus.show_shards_for_app_name_prefixes",
gettext_noop("If application_name starts with one of these values, show shards"),
gettext_noop("Citus places distributed tables and shards in the same schema. "
"That can cause confusion when inspecting the list of tables on "
"a node with shards. By default the shards are hidden from "
"pg_class. This GUC can be used to show the shards to certain "
"applications based on the application_name of the connection. "
"The default is empty string, which hides shards from all "
"applications. This behaviour can be overridden using the "
"citus.override_table_visibility setting"),
&ShowShardsForAppNamePrefixes,
"",
PGC_USERSET,
GUC_STANDARD,
ShowShardsForAppNamePrefixesCheckHook,
ShowShardsForAppNamePrefixesAssignHook,
NULL);
DefineCustomBoolVariable( DefineCustomBoolVariable(
"citus.sort_returning", "citus.sort_returning",
gettext_noop("Sorts the RETURNING clause to get consistent test output"), gettext_noop("Sorts the RETURNING clause to get consistent test output"),
@ -1985,12 +2025,12 @@ WarnIfReplicationModelIsSet(int *newval, void **extra, GucSource source)
/* /*
* HideShardsFromAppNamePrefixesCheckHook ensures that the * ShowShardsForAppNamePrefixesCheckHook ensures that the
* citus.hide_shards_from_app_name_prefixes holds a valid list of application_name * citus.show_shards_for_app_name_prefixes holds a valid list of application_name
* values. * values.
*/ */
static bool static bool
HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, GucSource source) ShowShardsForAppNamePrefixesCheckHook(char **newval, void **extra, GucSource source)
{ {
List *prefixList = NIL; List *prefixList = NIL;
@ -2020,7 +2060,7 @@ HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, GucSource so
if (strcmp(prefixAscii, appNamePrefix) != 0) if (strcmp(prefixAscii, appNamePrefix) != 0)
{ {
GUC_check_errdetail("prefix %s in citus.hide_shards_from_app_name_prefixes " GUC_check_errdetail("prefix %s in citus.show_shards_for_app_name_prefixes "
"contains non-ascii characters", appNamePrefix); "contains non-ascii characters", appNamePrefix);
return false; return false;
} }
@ -2031,12 +2071,12 @@ HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, GucSource so
/* /*
* HideShardsFromAppNamePrefixesAssignHook ensures changes to * ShowShardsForAppNamePrefixesAssignHook ensures changes to
* citus.hide_shards_from_app_name_prefixes are reflected in the decision * citus.show_shards_for_app_name_prefixes are reflected in the decision
* whether or not to show shards. * whether or not to show shards.
*/ */
static void static void
HideShardsFromAppNamePrefixesAssignHook(const char *newval, void *extra) ShowShardsForAppNamePrefixesAssignHook(const char *newval, void *extra)
{ {
ResetHideShardsDecision(); ResetHideShardsDecision();
} }
@ -2050,6 +2090,7 @@ static void
ApplicationNameAssignHook(const char *newval, void *extra) ApplicationNameAssignHook(const char *newval, void *extra)
{ {
ResetHideShardsDecision(); ResetHideShardsDecision();
ResetCitusBackendType();
OldApplicationNameAssignHook(newval, extra); OldApplicationNameAssignHook(newval, extra);
} }
@ -2295,3 +2336,29 @@ IsSuperuser(char *roleName)
return isSuperuser; return isSuperuser;
} }
/*
* CitusObjectAccessHook is called when an object is created.
*
* We currently use it to track CREATE EXTENSION citus; operations to make sure we
* clear the metadata if the transaction is rolled back.
*/
static void
CitusObjectAccessHook(ObjectAccessType access, Oid classId, Oid objectId, int subId,
void *arg)
{
if (PrevObjectAccessHook)
{
PrevObjectAccessHook(access, classId, objectId, subId, arg);
}
/* Checks if the access is post_create and that it's an extension id */
if (access == OAT_POST_CREATE && classId == ExtensionRelationId)
{
/* There's currently an engine bug that makes it difficult to check
* the provided objectId with extension oid so we will set the value
* regardless if it's citus being created */
SetCreateCitusTransactionLevel(GetCurrentTransactionNestLevel());
}
}

View File

@ -1,12 +1,18 @@
-- citus--10.0-1--10.0-2 -- citus--10.0-1--10.0-2
--#include "../../columnar/sql/columnar--10.0-1--10.0-2.sql" --#include "../../columnar/sql/columnar--10.0-1--10.0-2.sql"
DO $$ begin raise log '%', 'begin 10.0-1--10.0-2'; end; $$;
DO $check_columnar$ DO $check_columnar$
BEGIN BEGIN
IF NOT EXISTS (select 1 from pg_extension where extname='citus_columnar') THEN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus_columnar' and p.proname = 'columnar_handler'
) THEN
#include "../../columnar/sql/columnar--10.0-1--10.0-2.sql" #include "../../columnar/sql/columnar--10.0-1--10.0-2.sql"
END IF; END IF;
END; END;
$check_columnar$; $check_columnar$;
GRANT SELECT ON public.citus_tables TO public; GRANT SELECT ON public.citus_tables TO public;
DO $$ begin raise log '%', ' 10.0-1--10.0-2'; end; $$;

View File

@ -3,6 +3,7 @@
-- add the current database to the distributed objects if not already in there. -- add the current database to the distributed objects if not already in there.
-- this is to reliably propagate some of the alter database commands that might be -- this is to reliably propagate some of the alter database commands that might be
-- supported. -- supported.
INSERT INTO citus.pg_dist_object SELECT INSERT INTO citus.pg_dist_object SELECT
'pg_catalog.pg_database'::regclass::oid AS oid, 'pg_catalog.pg_database'::regclass::oid AS oid,
(SELECT oid FROM pg_database WHERE datname = current_database()) as objid, (SELECT oid FROM pg_database WHERE datname = current_database()) as objid,
@ -12,7 +13,11 @@ ON CONFLICT DO NOTHING;
--#include "../../columnar/sql/columnar--10.0-3--10.1-1.sql" --#include "../../columnar/sql/columnar--10.0-3--10.1-1.sql"
DO $check_columnar$ DO $check_columnar$
BEGIN BEGIN
IF NOT EXISTS (select 1 from pg_extension where extname='citus_columnar') THEN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus_columnar' and p.proname = 'columnar_handler'
) THEN
#include "../../columnar/sql/columnar--10.0-3--10.1-1.sql" #include "../../columnar/sql/columnar--10.0-3--10.1-1.sql"
END IF; END IF;
END; END;
@ -55,4 +60,5 @@ WHERE repmodel = 'c'
DROP TRIGGER pg_dist_rebalance_strategy_enterprise_check_trigger ON pg_catalog.pg_dist_rebalance_strategy; DROP TRIGGER pg_dist_rebalance_strategy_enterprise_check_trigger ON pg_catalog.pg_dist_rebalance_strategy;
DROP FUNCTION citus_internal.pg_dist_rebalance_strategy_enterprise_check(); DROP FUNCTION citus_internal.pg_dist_rebalance_strategy_enterprise_check();
DO $$ begin raise log '%', '10.0-4--10.1-1'; end; $$;
#include "udfs/citus_cleanup_orphaned_shards/10.1-1.sql" #include "udfs/citus_cleanup_orphaned_shards/10.1-1.sql"

View File

@ -12,7 +12,11 @@ ALTER TABLE pg_catalog.pg_dist_placement ADD CONSTRAINT placement_shardid_groupi
--#include "../../columnar/sql/columnar--10.1-1--10.2-1.sql" --#include "../../columnar/sql/columnar--10.1-1--10.2-1.sql"
DO $check_columnar$ DO $check_columnar$
BEGIN BEGIN
IF NOT EXISTS (select 1 from pg_extension where extname='citus_columnar') THEN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus_columnar' and p.proname = 'columnar_handler'
) THEN
#include "../../columnar/sql/columnar--10.1-1--10.2-1.sql" #include "../../columnar/sql/columnar--10.1-1--10.2-1.sql"
END IF; END IF;
END; END;
@ -29,6 +33,8 @@ $check_columnar$;
#include "udfs/get_missing_time_partition_ranges/10.2-1.sql" #include "udfs/get_missing_time_partition_ranges/10.2-1.sql"
#include "udfs/worker_nextval/10.2-1.sql" #include "udfs/worker_nextval/10.2-1.sql"
DO $$ begin raise log '%', 'begin 10.1-1--10.2-1'; end; $$;
DROP FUNCTION pg_catalog.citus_drop_all_shards(regclass, text, text); DROP FUNCTION pg_catalog.citus_drop_all_shards(regclass, text, text);
CREATE FUNCTION pg_catalog.citus_drop_all_shards(logicalrelid regclass, CREATE FUNCTION pg_catalog.citus_drop_all_shards(logicalrelid regclass,
schema_name text, schema_name text,
@ -42,3 +48,4 @@ COMMENT ON FUNCTION pg_catalog.citus_drop_all_shards(regclass, text, text, boole
#include "udfs/citus_drop_trigger/10.2-1.sql"; #include "udfs/citus_drop_trigger/10.2-1.sql";
#include "udfs/citus_prepare_pg_upgrade/10.2-1.sql" #include "udfs/citus_prepare_pg_upgrade/10.2-1.sql"
#include "udfs/citus_finish_pg_upgrade/10.2-1.sql" #include "udfs/citus_finish_pg_upgrade/10.2-1.sql"
DO $$ begin raise log '%', '10.1-1--10.2-1'; end; $$;

View File

@ -3,10 +3,16 @@
-- bump version to 10.2-2 -- bump version to 10.2-2
--#include "../../columnar/sql/columnar--10.2-1--10.2-2.sql" --#include "../../columnar/sql/columnar--10.2-1--10.2-2.sql"
DO $$ begin raise log '%', 'begin 10.2-1--10.2-2'; end; $$;
DO $check_columnar$ DO $check_columnar$
BEGIN BEGIN
IF NOT EXISTS (select 1 from pg_extension where extname='citus_columnar') THEN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus_columnar' and p.proname = 'columnar_handler'
) THEN
#include "../../columnar/sql/columnar--10.2-1--10.2-2.sql" #include "../../columnar/sql/columnar--10.2-1--10.2-2.sql"
END IF; END IF;
END; END;
$check_columnar$; $check_columnar$;
DO $$ begin raise log '%', '10.2-1--10.2-2'; end; $$;

View File

@ -5,7 +5,11 @@
--#include "../../columnar/sql/columnar--10.2-2--10.2-3.sql" --#include "../../columnar/sql/columnar--10.2-2--10.2-3.sql"
DO $check_columnar$ DO $check_columnar$
BEGIN BEGIN
IF NOT EXISTS (select 1 from pg_extension where extname='citus_columnar') THEN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus_columnar' and p.proname = 'columnar_handler'
) THEN
#include "../../columnar/sql/columnar--10.2-2--10.2-3.sql" #include "../../columnar/sql/columnar--10.2-2--10.2-3.sql"
END IF; END IF;
END; END;

View File

@ -3,11 +3,16 @@
-- bump version to 10.2-4 -- bump version to 10.2-4
--#include "../../columnar/sql/columnar--10.2-3--10.2-4.sql" --#include "../../columnar/sql/columnar--10.2-3--10.2-4.sql"
DO $$ begin raise log '%', 'begin 10.2-3--10.2-4'; end; $$;
DO $check_columnar$ DO $check_columnar$
BEGIN BEGIN
IF NOT EXISTS (select 1 from pg_extension where extname='citus_columnar') THEN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e
#include "../../columnar/sql/columnar--10.2-3--10.2-4.sql" INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
END IF; INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus_columnar' and p.proname = 'columnar_handler'
) THEN
#include "../../columnar/sql/columnar--10.2-3--10.2-4.sql"
END IF;
END; END;
$check_columnar$; $check_columnar$;
@ -15,3 +20,4 @@ $check_columnar$;
#include "udfs/fix_all_partition_shard_index_names/10.2-4.sql" #include "udfs/fix_all_partition_shard_index_names/10.2-4.sql"
#include "udfs/worker_fix_partition_shard_index_names/10.2-4.sql" #include "udfs/worker_fix_partition_shard_index_names/10.2-4.sql"
#include "udfs/citus_finish_pg_upgrade/10.2-4.sql" #include "udfs/citus_finish_pg_upgrade/10.2-4.sql"
DO $$ begin raise log '%', '10.2-3--10.2-4'; end; $$;

View File

@ -1 +1,7 @@
-- bump version to 11.0-2 #include "udfs/citus_shards_on_worker/11.0-2.sql"
#include "udfs/citus_shard_indexes_on_worker/11.0-2.sql"
#include "udfs/citus_is_coordinator/11.0-2.sql"
#include "udfs/citus_disable_node/11.0-2.sql"
#include "udfs/run_command_on_coordinator/11.0-2.sql"
#include "udfs/start_metadata_sync_to_all_nodes/11.0-2.sql"
#include "udfs/citus_finalize_upgrade_to_citus11/11.0-2.sql"

View File

@ -1,3 +1,4 @@
DO $$ begin raise log '%', 'begin 11.0-2--11.1-1'; end; $$;
DROP FUNCTION pg_catalog.worker_create_schema(bigint,text); DROP FUNCTION pg_catalog.worker_create_schema(bigint,text);
DROP FUNCTION pg_catalog.worker_cleanup_job_schema_cache(); DROP FUNCTION pg_catalog.worker_cleanup_job_schema_cache();
DROP FUNCTION pg_catalog.worker_fetch_foreign_file(text, text, bigint, text[], integer[]); DROP FUNCTION pg_catalog.worker_fetch_foreign_file(text, text, bigint, text[], integer[]);
@ -7,43 +8,36 @@ DROP FUNCTION pg_catalog.worker_merge_files_into_table(bigint, integer, text[],
DROP FUNCTION pg_catalog.worker_range_partition_table(bigint, integer, text, text, oid, anyarray); DROP FUNCTION pg_catalog.worker_range_partition_table(bigint, integer, text, text, oid, anyarray);
DROP FUNCTION pg_catalog.worker_repartition_cleanup(bigint); DROP FUNCTION pg_catalog.worker_repartition_cleanup(bigint);
-- If upgrading citus, the columnar objects are already being a part of the
-- bump version to 11.1-1 as version 'Z' -- citus extension, and must be detached so that they can be attached
-- drop columnar objects if they exists in citus extension -- to the citus_columnar extension.
DO $check_citus$ DO $check_citus$
BEGIN BEGIN
IF EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e IF EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid) INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid) INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus' and p.proname = 'columnar_handler' WHERE e.extname='citus' and p.proname = 'columnar_handler'
) THEN ) THEN
ALTER EXTENSION citus DROP SCHEMA columnar; ALTER EXTENSION citus DROP SCHEMA columnar;
ALTER EXTENSION citus DROP SEQUENCE columnar.storageid_seq; ALTER EXTENSION citus DROP SEQUENCE columnar.storageid_seq;
-- columnar tables -- columnar tables
ALTER EXTENSION citus DROP TABLE columnar.options; ALTER EXTENSION citus DROP TABLE columnar.options;
ALTER EXTENSION citus DROP TABLE columnar.stripe; ALTER EXTENSION citus DROP TABLE columnar.stripe;
ALTER EXTENSION citus DROP TABLE columnar.chunk_group; ALTER EXTENSION citus DROP TABLE columnar.chunk_group;
ALTER EXTENSION citus DROP TABLE columnar.chunk; ALTER EXTENSION citus DROP TABLE columnar.chunk;
DO $proc$ ALTER EXTENSION citus DROP FUNCTION columnar.columnar_handler;
BEGIN ALTER EXTENSION citus DROP ACCESS METHOD columnar;
-- columnar functions ALTER EXTENSION citus DROP FUNCTION pg_catalog.alter_columnar_table_set;
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN ALTER EXTENSION citus DROP FUNCTION pg_catalog.alter_columnar_table_reset;
EXECUTE $$
ALTER EXTENSION citus DROP FUNCTION columnar.columnar_handler;
ALTER EXTENSION citus DROP ACCESS METHOD columnar;
ALTER EXTENSION citus DROP FUNCTION pg_catalog.alter_columnar_table_set;
ALTER EXTENSION citus DROP FUNCTION pg_catalog.alter_columnar_table_reset;
$$;
END IF;
END$proc$;
-- functions under citus_internal for columnar -- functions under citus_internal for columnar
ALTER EXTENSION citus DROP FUNCTION citus_internal.upgrade_columnar_storage; ALTER EXTENSION citus DROP FUNCTION citus_internal.upgrade_columnar_storage;
ALTER EXTENSION citus DROP FUNCTION citus_internal.downgrade_columnar_storage; ALTER EXTENSION citus DROP FUNCTION citus_internal.downgrade_columnar_storage;
ALTER EXTENSION citus DROP FUNCTION citus_internal.columnar_ensure_am_depends_catalog; ALTER EXTENSION citus DROP FUNCTION citus_internal.columnar_ensure_am_depends_catalog;
END IF; END IF;
END $check_citus$; END $check_citus$;
#include "udfs/citus_finish_pg_upgrade/11.1-1.sql"
DO $$ begin raise log '%', ' 11.0-2--11.1-1'; end; $$;

View File

@ -6,6 +6,6 @@ RETURNS BOOL
LANGUAGE C STRICT as 'MODULE_PATHNAME', LANGUAGE C STRICT as 'MODULE_PATHNAME',
$$lock_relation_if_exists$$; $$lock_relation_if_exists$$;
COMMENT ON FUNCTION lock_relation_if_exists(table_name text, lock_mode text) COMMENT ON FUNCTION lock_relation_if_exists(table_name text, lock_mode text)
IS 'locks relation in the lock_mode if the relation exists'; IS 'used internally to locks relation in the lock_mode if the relation exists without throwing errors; consider using LOCK * IN * MODE instead';
RESET search_path; RESET search_path;

View File

@ -6,7 +6,7 @@
-- cat citus--9.5-1--10.0-1.sql citus--10.0-1--10.0-2.sql citus--10.0-2--10.0-3.sql > citus--9.5-1--10.0-4.sql -- cat citus--9.5-1--10.0-1.sql citus--10.0-1--10.0-2.sql citus--10.0-2--10.0-3.sql > citus--9.5-1--10.0-4.sql
-- copy of citus--9.5-1--10.0-1 -- copy of citus--9.5-1--10.0-1
DO $$ begin raise log '%', 'begin 9.5-1--10.0-4'; end; $$;
DROP FUNCTION pg_catalog.upgrade_to_reference_table(regclass); DROP FUNCTION pg_catalog.upgrade_to_reference_table(regclass);
DROP FUNCTION IF EXISTS pg_catalog.citus_total_relation_size(regclass); DROP FUNCTION IF EXISTS pg_catalog.citus_total_relation_size(regclass);
@ -38,9 +38,13 @@ DROP FUNCTION IF EXISTS pg_catalog.citus_total_relation_size(regclass);
--#include "../../columnar/sql/columnar--9.5-1--10.0-1.sql" --#include "../../columnar/sql/columnar--9.5-1--10.0-1.sql"
DO $check_columnar$ DO $check_columnar$
BEGIN BEGIN
IF NOT EXISTS (select 1 from pg_extension where extname='citus_columnar') THEN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e
#include "../../columnar/sql/columnar--9.5-1--10.0-1.sql" INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
END IF; INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus_columnar' and p.proname = 'columnar_handler'
) THEN
#include "../../columnar/sql/columnar--9.5-1--10.0-1.sql"
END IF;
END; END;
$check_columnar$; $check_columnar$;
@ -179,7 +183,19 @@ GRANT SELECT ON pg_catalog.citus_worker_stat_activity TO PUBLIC;
-- copy of citus--10.0-1--10.0-2 -- copy of citus--10.0-1--10.0-2
--#include "../../columnar/sql/columnar--10.0-1--10.0-2.sql"
DO $check_columnar$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus_columnar' and p.proname = 'columnar_handler'
) THEN
#include "../../columnar/sql/columnar--10.0-1--10.0-2.sql" #include "../../columnar/sql/columnar--10.0-1--10.0-2.sql"
END IF;
END;
$check_columnar$;
-- copy of citus--10.0-2--10.0-3 -- copy of citus--10.0-2--10.0-3
@ -222,3 +238,4 @@ COMMENT ON FUNCTION pg_catalog.citus_get_active_worker_nodes()
RESET search_path; RESET search_path;
DO $$ begin raise log '%', ' 9.5-1--10.0-4'; end; $$;

View File

@ -1 +1,18 @@
-- bump down version to 11.0-1 #include "../udfs/citus_shards_on_worker/11.0-1.sql"
#include "../udfs/citus_shard_indexes_on_worker/11.0-1.sql"
DROP FUNCTION pg_catalog.citus_disable_node(text, integer, bool);
CREATE FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, force bool default false)
RETURNS void
LANGUAGE C STRICT
AS 'MODULE_PATHNAME', $$citus_disable_node$$;
COMMENT ON FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, force bool)
IS 'removes node from the cluster temporarily';
REVOKE ALL ON FUNCTION pg_catalog.citus_disable_node(text,int, bool) FROM PUBLIC;
DROP FUNCTION pg_catalog.citus_is_coordinator();
DROP FUNCTION pg_catalog.run_command_on_coordinator(text,boolean);
DROP FUNCTION pg_catalog.start_metadata_sync_to_all_nodes();
DROP FUNCTION pg_catalog.citus_finalize_upgrade_to_citus11(boolean);

View File

@ -54,18 +54,10 @@ ALTER EXTENSION citus ADD TABLE columnar.stripe;
ALTER EXTENSION citus ADD TABLE columnar.chunk_group; ALTER EXTENSION citus ADD TABLE columnar.chunk_group;
ALTER EXTENSION citus ADD TABLE columnar.chunk; ALTER EXTENSION citus ADD TABLE columnar.chunk;
DO $proc$ ALTER EXTENSION citus ADD FUNCTION columnar.columnar_handler;
BEGIN ALTER EXTENSION citus ADD ACCESS METHOD columnar;
-- columnar functions ALTER EXTENSION citus ADD FUNCTION pg_catalog.alter_columnar_table_set;
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN ALTER EXTENSION citus ADD FUNCTION pg_catalog.alter_columnar_table_reset;
EXECUTE $$
ALTER EXTENSION citus ADD FUNCTION columnar.columnar_handler;
ALTER EXTENSION citus ADD ACCESS METHOD columnar;
ALTER EXTENSION citus ADD FUNCTION pg_catalog.alter_columnar_table_set;
ALTER EXTENSION citus ADD FUNCTION pg_catalog.alter_columnar_table_reset;
$$;
END IF;
END$proc$;
ALTER EXTENSION citus ADD FUNCTION citus_internal.upgrade_columnar_storage; ALTER EXTENSION citus ADD FUNCTION citus_internal.upgrade_columnar_storage;
ALTER EXTENSION citus ADD FUNCTION citus_internal.downgrade_columnar_storage; ALTER EXTENSION citus ADD FUNCTION citus_internal.downgrade_columnar_storage;

View File

@ -0,0 +1,9 @@
DROP FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, force bool);
CREATE FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, synchronous bool default false)
RETURNS void
LANGUAGE C STRICT
AS 'MODULE_PATHNAME', $$citus_disable_node$$;
COMMENT ON FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, synchronous bool)
IS 'removes node from the cluster temporarily';
REVOKE ALL ON FUNCTION pg_catalog.citus_disable_node(text,int, bool) FROM PUBLIC;

View File

@ -1,9 +1,9 @@
DROP FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer); DROP FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, force bool);
CREATE FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, force bool default false) CREATE FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, synchronous bool default false)
RETURNS void RETURNS void
LANGUAGE C STRICT LANGUAGE C STRICT
AS 'MODULE_PATHNAME', $$citus_disable_node$$; AS 'MODULE_PATHNAME', $$citus_disable_node$$;
COMMENT ON FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, force bool) COMMENT ON FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, synchronous bool)
IS 'removes node from the cluster temporarily'; IS 'removes node from the cluster temporarily';
REVOKE ALL ON FUNCTION pg_catalog.citus_disable_node(text,int, bool) FROM PUBLIC; REVOKE ALL ON FUNCTION pg_catalog.citus_disable_node(text,int, bool) FROM PUBLIC;

View File

@ -0,0 +1,221 @@
-- citus_finalize_upgrade_to_citus11() is a helper UDF ensures
-- the upgrade to Citus 11 is finished successfully. Upgrade to
-- Citus 11 requires all active primary worker nodes to get the
-- metadata. And, this function's job is to sync the metadata to
-- the nodes that does not already have
-- once the function finishes without any errors and returns true
-- the cluster is ready for running distributed queries from
-- the worker nodes. When debug is enabled, the function provides
-- more information to the user.
CREATE OR REPLACE FUNCTION pg_catalog.citus_finalize_upgrade_to_citus11(enforce_version_check bool default true)
RETURNS bool
LANGUAGE plpgsql
AS $$
BEGIN
---------------------------------------------
-- This script consists of N stages
-- Each step is documented, and if log level
-- is reduced to DEBUG1, each step is logged
-- as well
---------------------------------------------
------------------------------------------------------------------------------------------
-- STAGE 0: Ensure no concurrent node metadata changing operation happens while this
-- script is running via acquiring a strong lock on the pg_dist_node
------------------------------------------------------------------------------------------
BEGIN
LOCK TABLE pg_dist_node IN EXCLUSIVE MODE NOWAIT;
EXCEPTION WHEN OTHERS THEN
RAISE 'Another node metadata changing operation is in progress, try again.';
END;
------------------------------------------------------------------------------------------
-- STAGE 1: We want all the commands to run in the same transaction block. Without
-- sequential mode, metadata syncing cannot be done in a transaction block along with
-- other commands
------------------------------------------------------------------------------------------
SET LOCAL citus.multi_shard_modify_mode TO 'sequential';
------------------------------------------------------------------------------------------
-- STAGE 2: Ensure we have the prerequisites
-- (a) only superuser can run this script
-- (b) cannot be executed when enable_ddl_propagation is False
-- (c) can only be executed from the coordinator
------------------------------------------------------------------------------------------
DECLARE
is_superuser_running boolean := False;
enable_ddl_prop boolean:= False;
local_group_id int := 0;
BEGIN
SELECT rolsuper INTO is_superuser_running FROM pg_roles WHERE rolname = current_user;
IF is_superuser_running IS NOT True THEN
RAISE EXCEPTION 'This operation can only be initiated by superuser';
END IF;
SELECT current_setting('citus.enable_ddl_propagation') INTO enable_ddl_prop;
IF enable_ddl_prop IS NOT True THEN
RAISE EXCEPTION 'This operation cannot be completed when citus.enable_ddl_propagation is False.';
END IF;
SELECT groupid INTO local_group_id FROM pg_dist_local_group;
IF local_group_id != 0 THEN
RAISE EXCEPTION 'Operation is not allowed on this node. Connect to the coordinator and run it again.';
ELSE
RAISE DEBUG 'We are on the coordinator, continue to sync metadata';
END IF;
END;
------------------------------------------------------------------------------------------
-- STAGE 3: Ensure all primary nodes are active
------------------------------------------------------------------------------------------
DECLARE
primary_disabled_worker_node_count int := 0;
BEGIN
SELECT count(*) INTO primary_disabled_worker_node_count FROM pg_dist_node
WHERE groupid != 0 AND noderole = 'primary' AND NOT isactive;
IF primary_disabled_worker_node_count != 0 THEN
RAISE EXCEPTION 'There are inactive primary worker nodes, you need to activate the nodes first.'
'Use SELECT citus_activate_node() to activate the disabled nodes';
ELSE
RAISE DEBUG 'There are no disabled worker nodes, continue to sync metadata';
END IF;
END;
------------------------------------------------------------------------------------------
-- STAGE 4: Ensure there is no connectivity issues in the cluster
------------------------------------------------------------------------------------------
DECLARE
all_nodes_can_connect_to_each_other boolean := False;
BEGIN
SELECT bool_and(coalesce(result, false)) INTO all_nodes_can_connect_to_each_other FROM citus_check_cluster_node_health();
IF all_nodes_can_connect_to_each_other != True THEN
RAISE EXCEPTION 'There are unhealth primary nodes, you need to ensure all '
'nodes are up and runnnig. Also, make sure that all nodes can connect '
'to each other. Use SELECT * FROM citus_check_cluster_node_health(); '
'to check the cluster health';
ELSE
RAISE DEBUG 'Cluster is healthy, all nodes can connect to each other';
END IF;
END;
------------------------------------------------------------------------------------------
-- STAGE 5: Ensure all nodes are on the same version
------------------------------------------------------------------------------------------
DECLARE
coordinator_version text := '';
worker_node_version text := '';
worker_node_version_count int := 0;
BEGIN
SELECT extversion INTO coordinator_version from pg_extension WHERE extname = 'citus';
-- first, check if all nodes have the same versions
SELECT
count(distinct result) INTO worker_node_version_count
FROM
run_command_on_workers('SELECT extversion from pg_extension WHERE extname = ''citus''');
IF enforce_version_check AND worker_node_version_count != 1 THEN
RAISE EXCEPTION 'All nodes should have the same Citus version installed. Currently '
'some of the workers have different versions.';
ELSE
RAISE DEBUG 'All worker nodes have the same Citus version';
END IF;
-- second, check if all nodes have the same versions
SELECT
result INTO worker_node_version
FROM
run_command_on_workers('SELECT extversion from pg_extension WHERE extname = ''citus'';')
GROUP BY result;
IF enforce_version_check AND coordinator_version != worker_node_version THEN
RAISE EXCEPTION 'All nodes should have the same Citus version installed. Currently '
'the coordinator has version % and the worker(s) has %',
coordinator_version, worker_node_version;
ELSE
RAISE DEBUG 'All nodes have the same Citus version';
END IF;
END;
------------------------------------------------------------------------------------------
-- STAGE 6: Ensure all the partitioned tables have the proper naming structure
-- As described on https://github.com/citusdata/citus/issues/4962
-- existing indexes on partitioned distributed tables can collide
-- with the index names exists on the shards
-- luckily, we know how to fix it.
-- And, note that we should do this even if the cluster is a basic plan
-- (e.g., single node Citus) such that when cluster scaled out, everything
-- works as intended
-- And, this should be done only ONCE for a cluster as it can be a pretty
-- time consuming operation. Thus, even if the function is called multiple time,
-- we keep track of it and do not re-execute this part if not needed.
------------------------------------------------------------------------------------------
DECLARE
partitioned_table_exists_pre_11 boolean:=False;
BEGIN
-- we recorded if partitioned tables exists during upgrade to Citus 11
SELECT metadata->>'partitioned_citus_table_exists_pre_11' INTO partitioned_table_exists_pre_11
FROM pg_dist_node_metadata;
IF partitioned_table_exists_pre_11 IS NOT NULL AND partitioned_table_exists_pre_11 THEN
-- this might take long depending on the number of partitions and shards...
RAISE NOTICE 'Preparing all the existing partitioned table indexes';
PERFORM pg_catalog.fix_all_partition_shard_index_names();
-- great, we are done with fixing the existing wrong index names
-- so, lets remove this
UPDATE pg_dist_node_metadata
SET metadata=jsonb_delete(metadata, 'partitioned_citus_table_exists_pre_11');
ELSE
RAISE DEBUG 'There are no partitioned tables that should be fixed';
END IF;
END;
------------------------------------------------------------------------------------------
-- STAGE 7: Return early if there are no primary worker nodes
-- We don't strictly need this step, but it gives a nicer notice message
------------------------------------------------------------------------------------------
DECLARE
primary_worker_node_count bigint :=0;
BEGIN
SELECT count(*) INTO primary_worker_node_count FROM pg_dist_node WHERE groupid != 0 AND noderole = 'primary';
IF primary_worker_node_count = 0 THEN
RAISE NOTICE 'There are no primary worker nodes, no need to sync metadata to any node';
RETURN true;
ELSE
RAISE DEBUG 'There are % primary worker nodes, continue to sync metadata', primary_worker_node_count;
END IF;
END;
------------------------------------------------------------------------------------------
-- STAGE 8: Do the actual metadata & object syncing to the worker nodes
-- For the "already synced" metadata nodes, we do not strictly need to
-- sync the objects & metadata, but there is no harm to do it anyway
-- it'll only cost some execution time but makes sure that we have a
-- a consistent metadata & objects across all the nodes
------------------------------------------------------------------------------------------
DECLARE
BEGIN
-- this might take long depending on the number of tables & objects ...
RAISE NOTICE 'Preparing to sync the metadata to all nodes';
PERFORM start_metadata_sync_to_all_nodes();
END;
RETURN true;
END;
$$;
COMMENT ON FUNCTION pg_catalog.citus_finalize_upgrade_to_citus11(bool)
IS 'finalizes upgrade to Citus';
REVOKE ALL ON FUNCTION pg_catalog.citus_finalize_upgrade_to_citus11(bool) FROM PUBLIC;

Some files were not shown because too many files have changed in this diff Show More