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
- store_artifacts:
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:
name: 'Save core dumps'
path: /tmp/core_dumps
@ -598,6 +598,12 @@ workflows:
image_tag: '<< pipeline.parameters.pg13_version >>'
suite: recovery
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:
name: 'test-13_check-failure'
pg_major: 13
@ -666,6 +672,12 @@ workflows:
image_tag: '<< pipeline.parameters.pg14_version >>'
suite: recovery
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:
name: 'test-14_check-failure'
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) ###
* Drops support for PostgreSQL 12

View File

@ -90,38 +90,25 @@ data.
Set options using:
```sql
alter_columnar_table_set(
relid REGCLASS,
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);
ALTER TABLE my_columnar_table SET
(columnar.compression = none, columnar.stripe_row_limit = 10000);
```
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
recompressed/decompressed. The default value is `zstd` (if support
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
support the level chosen, the closest level will be selected
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
be changed and may have more rows than this maximum value. The
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
changed and may have more rows than this maximum value. The default
value is `10000`.

View File

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

View File

@ -59,6 +59,7 @@
#include "utils/rel.h"
#include "utils/relfilenodemap.h"
#define COLUMNAR_RELOPTION_NAMESPACE "columnar"
typedef struct
{
@ -82,6 +83,7 @@ typedef enum RowNumberLookupMode
FIND_GREATER
} RowNumberLookupMode;
static void ParseColumnarRelOptions(List *reloptions, ColumnarOptions *options);
static void InsertEmptyStripeMetadataRow(uint64 storageId, uint64 stripeId,
uint32 columnCount, uint32 chunkGroupRowCount,
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
* 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);
/* 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
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);
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))
{
elog(ERROR, "relation \"%s\" is not a columnar table",
@ -1738,11 +1903,10 @@ ColumnarStorageUpdateIfNeeded(Relation rel, bool isUpgrade)
return;
}
RelationOpenSmgr(rel);
BlockNumber nblocks = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
BlockNumber nblocks = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
if (nblocks < 2)
{
ColumnarStorageInit(rel->rd_smgr, ColumnarMetadataNewStorageId());
ColumnarStorageInit(RelationGetSmgr(rel), ColumnarMetadataNewStorageId());
return;
}

View File

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

View File

@ -20,6 +20,7 @@
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
#include "catalog/pg_publication.h"
@ -104,9 +105,6 @@ typedef struct IndexFetchColumnarData
MemoryContext scanContext;
} IndexFetchColumnarData;
/* available to other extensions using find_rendezvous_variable() */
static ColumnarTableSetOptions_hook_type ColumnarTableSetOptions_hook = NULL;
static object_access_hook_type PrevObjectAccessHook = NULL;
static ProcessUtility_hook_type PrevProcessUtilityHook = NULL;
@ -117,6 +115,8 @@ static void ColumnarTriggerCreateHook(Oid tgid);
static void ColumnarTableAMObjectAccessHook(ObjectAccessType access, Oid classId,
Oid objectId, int subId,
void *arg);
static RangeVar * ColumnarProcessAlterTable(AlterTableStmt *alterTableStmt,
List **columnarOptions);
static void ColumnarProcessUtility(PlannedStmt *pstmt,
const char *queryString,
#if PG_VERSION_NUM >= PG_VERSION_14
@ -882,7 +882,7 @@ columnar_relation_set_new_filenode(Relation rel,
*freezeXid = RecentXmin;
*minmulti = GetOldestMultiXactId();
SMgrRelation srel = RelationCreateStorage(*newrnode, persistence);
SMgrRelation srel = RelationCreateStorage_compat(*newrnode, persistence, true);
ColumnarStorageInit(srel, ColumnarMetadataNewStorageId());
InitColumnarOptions(rel->rd_id);
@ -914,8 +914,7 @@ columnar_relation_nontransactional_truncate(Relation rel)
RelationTruncate(rel, 0);
uint64 storageId = ColumnarMetadataNewStorageId();
RelationOpenSmgr(rel);
ColumnarStorageInit(rel->rd_smgr, storageId);
ColumnarStorageInit(RelationGetSmgr(rel), 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.
*/
@ -1050,6 +1070,9 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
return;
}
pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM,
RelationGetRelid(rel));
/*
* If metapage version of relation is older, then we hint users to VACUUM
* the relation in ColumnarMetapageCheckVersion. So if needed, upgrade
@ -1073,6 +1096,78 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
{
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;
}
RelationOpenSmgr(rel);
uint64 relPages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
uint64 relPages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
RelationCloseSmgr(rel);
Datum storageId = DirectFunctionCall1(columnar_relation_storageid,
@ -1240,8 +1334,7 @@ TruncateColumnar(Relation rel, int elevel)
uint64 newDataReservation = Max(GetHighestUsedAddress(rel->rd_node) + 1,
ColumnarFirstLogicalOffset);
RelationOpenSmgr(rel);
BlockNumber old_rel_pages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
BlockNumber old_rel_pages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
if (!ColumnarStorageTruncate(rel, newDataReservation))
{
@ -1249,8 +1342,7 @@ TruncateColumnar(Relation rel, int elevel)
return;
}
RelationOpenSmgr(rel);
BlockNumber new_rel_pages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
BlockNumber new_rel_pages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
/*
* 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;
/* Open it at the smgr level if not already done */
RelationOpenSmgr(rel);
/* InvalidForkNumber indicates returning the size for all forks */
if (forkNumber == InvalidForkNumber)
{
for (int i = 0; i < MAX_FORKNUM; i++)
{
nblocks += smgrnblocks(rel->rd_smgr, i);
nblocks += smgrnblocks(RelationGetSmgr(rel), i);
}
}
else
{
nblocks = smgrnblocks(rel->rd_smgr, forkNumber);
nblocks = smgrnblocks(RelationGetSmgr(rel), forkNumber);
}
return nblocks * BLCKSZ;
@ -1820,8 +1909,7 @@ columnar_estimate_rel_size(Relation rel, int32 *attr_widths,
double *allvisfrac)
{
CheckCitusColumnarVersion(ERROR);
RelationOpenSmgr(rel);
*pages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
*pages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
*tuples = ColumnarTableRowCount(rel);
/*
@ -1911,11 +1999,6 @@ ColumnarSubXactCallback(SubXactEvent event, SubTransactionId mySubid,
void
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);
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.
*/
@ -2113,27 +2261,160 @@ ColumnarProcessUtility(PlannedStmt *pstmt,
Node *parsetree = pstmt->utilityStmt;
if (IsA(parsetree, IndexStmt))
RangeVar *columnarRangeVar = NULL;
List *columnarOptions = NIL;
switch (nodeTag(parsetree))
{
IndexStmt *indexStmt = (IndexStmt *) parsetree;
Relation rel = relation_openrv(indexStmt->relation,
indexStmt->concurrent ? ShareUpdateExclusiveLock :
ShareLock);
if (rel->rd_tableam == GetColumnarTableAmRoutine())
case T_IndexStmt:
{
CheckCitusColumnarVersion(ERROR);
if (!ColumnarSupportsIndexAM(indexStmt->accessMethod))
IndexStmt *indexStmt = (IndexStmt *) parsetree;
Relation rel = relation_openrv(indexStmt->relation,
indexStmt->concurrent ?
ShareUpdateExclusiveLock :
ShareLock);
if (rel->rd_tableam == GetColumnarTableAmRoutine())
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported access method for the "
"index on columnar table %s",
RelationGetRelationName(rel))));
CheckCitusColumnarVersion(ERROR);
if (!ColumnarSupportsIndexAM(indexStmt->accessMethod))
{
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))
@ -2191,6 +2472,11 @@ ColumnarProcessUtility(PlannedStmt *pstmt,
PrevProcessUtilityHook_compat(pstmt, queryString, false, context,
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
ColumnarCheckLogicalReplication(Relation rel)
{
bool pubActionInsert = false;
if (!is_publishable_relation(rel))
{
return;
}
#if PG_VERSION_NUM >= PG_VERSION_15
{
PublicationDesc pubdesc;
RelationBuildPublicationDesc(rel, &pubdesc);
pubActionInsert = pubdesc.pubactions.pubinsert;
}
#else
if (rel->rd_pubactions == NULL)
{
GetRelationPublicationActions(rel);
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),
errmsg(
@ -2375,219 +2673,36 @@ ColumnarCheckLogicalReplication(Relation rel)
/*
* alter_columnar_table_set is a UDF exposed in postgres to change settings on a columnar
* table. Calling this function on a non-columnar table gives an error.
* alter_columnar_table_set()
*
* sql syntax:
* 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)
* Deprecated in 11.1-1: should issue ALTER TABLE ... SET instead. Function
* still available, but implemented in PL/pgSQL instead of C.
*
* All arguments except the table name are optional. The UDF is supposed to be called
* like:
* 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.
* C code is removed -- the symbol may still be required in some
* upgrade/downgrade paths, but it should not be called.
*/
PG_FUNCTION_INFO_V1(alter_columnar_table_set);
Datum
alter_columnar_table_set(PG_FUNCTION_ARGS)
{
CheckCitusColumnarVersion(ERROR);
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();
elog(ERROR, "alter_columnar_table_set is deprecated");
}
/*
* alter_columnar_table_reset is a UDF exposed in postgres to reset the settings on a
* columnar table. Calling this function on a non-columnar table gives an error.
* alter_columnar_table_reset()
*
* sql syntax:
* pg_catalog.alter_columnar_table_re
* teset(
* table_name regclass,
* chunk_group_row_limit bool DEFAULT FALSE,
* stripe_row_limit bool DEFAULT FALSE,
* compression bool DEFAULT FALSE)
* Deprecated in 11.1-1: should issue ALTER TABLE ... RESET instead. Function
* still available, but implemented in PL/pgSQL instead of C.
*
* All arguments except the table name are optional. The UDF is supposed to be called
* like:
* SELECT alter_columnar_table_set('table', compression => true);
*
* All options set to true will be reset to the default system value.
* C code is removed -- the symbol may still be required in some
* upgrade/downgrade paths, but it should not be called.
*/
PG_FUNCTION_INFO_V1(alter_columnar_table_reset);
Datum
alter_columnar_table_reset(PG_FUNCTION_ARGS)
{
CheckCitusColumnarVersion(ERROR);
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();
elog(ERROR, "alter_columnar_table_reset is deprecated");
}

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.chunk_group;
ALTER EXTENSION citus_columnar ADD TABLE columnar.chunk;
DO $proc$
BEGIN
-- columnar functions
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN
EXECUTE $$
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 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;
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.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$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"
RESET search_path;
@ -175,30 +172,6 @@ BEGIN
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
-- 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
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
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,
@ -240,27 +364,72 @@ BEGIN
-- 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,
columnar_schema_members.relid 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)
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()
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';
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
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;
DO $proc$
BEGIN
-- columnar functions
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN
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$;
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;
-- functions under citus_internal for columnar
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_level bool DEFAULT false)
RETURNS void
LANGUAGE C
AS 'MODULE_PATHNAME', 'alter_columnar_table_reset';
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,

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_level int DEFAULT NULL)
RETURNS void
LANGUAGE C
AS 'MODULE_PATHNAME', 'alter_columnar_table_set';
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,

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$
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,
@ -17,20 +35,10 @@ BEGIN
-- 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,
columnar_schema_members.relid 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)
FROM columnar_schema_members
-- Avoid inserting duplicate entries into pg_depend.
EXCEPT TABLE pg_depend;
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 *targetSchemaName,
char *targetName);
static char * CreateMaterializedViewDDLCommand(Oid matViewOid);
static char * GetAccessMethodForMatViewIfExists(Oid viewOid);
static bool WillRecreateForeignKeyToReferenceTable(Oid relationId,
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(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
@ -504,10 +508,16 @@ AlterTableSetAccessMethod(TableConversionParameters *params)
*
* The function returns a TableConversionReturn object that can stores variables that
* 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 *
ConvertTable(TableConversionState *con)
{
InTableTypeConversionFunctionCall = true;
/*
* We undistribute citus local tables that are not chained with any reference
* tables via foreign keys at the end of the utility hook.
@ -536,6 +546,7 @@ ConvertTable(TableConversionState *con)
* subgraph including itself, so return here.
*/
SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys);
InTableTypeConversionFunctionCall = false;
return NULL;
}
char *newAccessMethod = con->accessMethod ? con->accessMethod :
@ -701,7 +712,7 @@ ConvertTable(TableConversionState *con)
char *columnarOptionsSql = GetShardedTableDDLCommandColumnar(con->hashOfName,
context);
ExecuteQueryViaSPI(columnarOptionsSql, SPI_OK_SELECT);
ExecuteQueryViaSPI(columnarOptionsSql, SPI_OK_UTILITY);
}
con->newRelationId = get_relname_relid(con->tempName, con->schemaId);
@ -820,6 +831,7 @@ ConvertTable(TableConversionState *con)
SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys);
InTableTypeConversionFunctionCall = false;
return ret;
}
@ -1252,33 +1264,22 @@ GetViewCreationCommandsOfTable(Oid relationId)
Oid viewOid = InvalidOid;
foreach_oid(viewOid, views)
{
Datum viewDefinitionDatum = DirectFunctionCall1(pg_get_viewdef,
ObjectIdGetDatum(viewOid));
char *viewDefinition = TextDatumGetCString(viewDefinitionDatum);
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 */
char *accessMethodName = GetAccessMethodForMatViewIfExists(viewOid);
appendStringInfoString(query, "CREATE ");
if (isMatView)
/* See comments on CreateMaterializedViewDDLCommand for its limitations */
if (get_rel_relkind(viewOid) == RELKIND_MATVIEW)
{
appendStringInfoString(query, "MATERIALIZED ");
char *matViewCreateCommands = CreateMaterializedViewDDLCommand(viewOid);
appendStringInfoString(query, matViewCreateCommands);
}
else
{
char *viewCreateCommand = CreateViewDDLCommand(viewOid);
appendStringInfoString(query, viewCreateCommand);
}
appendStringInfo(query, "VIEW %s ", qualifiedViewName);
if (accessMethodName)
{
appendStringInfo(query, "USING %s ", accessMethodName);
}
appendStringInfo(query, "AS %s", viewDefinition);
char *alterViewCommmand = AlterViewOwnerCommand(viewOid);
appendStringInfoString(query, alterViewCommmand);
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.
* 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 "commands/defrem.h"
#include "distributed/backend_data.h"
#include "distributed/citus_ruleutils.h"
#include "distributed/colocation_utils.h"
#include "distributed/commands.h"

View File

@ -15,6 +15,7 @@
#include "distributed/backend_data.h"
#include "distributed/metadata_cache.h"
#include "distributed/remote_commands.h"
#include "distributed/worker_manager.h"
#include "lib/stringinfo.h"
#include "signal.h"
@ -111,18 +112,39 @@ CitusSignalBackend(uint64 globalPID, uint64 timeout, int sig)
#endif
}
StringInfo queryResult = makeStringInfo();
int connectionFlags = 0;
MultiConnection *connection = GetNodeConnection(connectionFlags,
workerNode->workerName,
workerNode->workerPort);
bool reportResultError = true;
bool success = ExecuteRemoteQueryOrCommand(workerNode->workerName,
workerNode->workerPort, cancelQuery->data,
queryResult, reportResultError);
if (success && queryResult && strcmp(queryResult->data, "f") == 0)
if (!SendRemoteCommand(connection, cancelQuery->data))
{
/* 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;
}
PQclear(queryResult);
bool raiseErrors = false;
ClearResults(connection, raiseErrors);
return success;
}

View File

@ -10,6 +10,8 @@
*/
#include "postgres.h"
#include "pg_version_compat.h"
#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/pg_collation.h"
@ -36,9 +38,6 @@
static char * CreateCollationDDLInternal(Oid collationId, Oid *collowner,
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
@ -60,12 +59,30 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati
Form_pg_collation collationForm = (Form_pg_collation) GETSTRUCT(heapTuple);
char collprovider = collationForm->collprovider;
const char *collcollate = NameStr(collationForm->collcollate);
const char *collctype = NameStr(collationForm->collctype);
Oid collnamespace = collationForm->collnamespace;
const char *collname = NameStr(collationForm->collname);
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)
{
*collowner = collationForm->collowner;
@ -103,6 +120,9 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati
quote_literal_cstr(collctype));
}
pfree(collcollate);
pfree(collctype);
if (!collisdeterministic)
{
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
* of the RenameStmt. Errors if missing_ok is false.
@ -500,7 +259,7 @@ GenerateBackupNameForCollationCollision(const ObjectAddress *address)
return NULL;
}
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);
while (true)
@ -544,89 +303,3 @@ DefineCollationStmtObjectAddress(Node *node, bool missing_ok)
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;
/*
* 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
* 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);
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 };
ObjectAddressSet(address, DatabaseRelationId, databaseOid);

View File

@ -34,7 +34,6 @@ typedef bool (*AddressPredicate)(const ObjectAddress *);
static void EnsureDependenciesCanBeDistributed(const ObjectAddress *relationAddress);
static void ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress);
static int ObjectAddressComparator(const void *a, const void *b);
static List * GetDependencyCreateDDLCommands(const ObjectAddress *dependency);
static List * FilterObjectAddressListByPredicate(List *objectAddressList,
AddressPredicate predicate);
@ -166,11 +165,28 @@ EnsureDependenciesCanBeDistributed(const ObjectAddress *objectAddress)
/*
* ErrorIfCircularDependencyExists checks whether given object has circular dependency
* with itself via existing objects of pg_dist_object.
* ErrorIfCircularDependencyExists is a wrapper around
* DeferErrorIfCircularDependencyExists(), and throws error
* if circular dependency exists.
*/
static void
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);
@ -189,13 +205,18 @@ ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress)
objectDescription = getObjectDescription(objectAddress);
#endif
ereport(ERROR, (errmsg("Citus can not handle circular dependencies "
"between distributed objects"),
errdetail("\"%s\" circularly depends itself, resolve "
"circular dependency first",
objectDescription)));
StringInfo detailInfo = makeStringInfo();
appendStringInfo(detailInfo, "\"%s\" circularly depends itself, resolve "
"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
* commands to execute on a worker to create the object.
*/
static List *
List *
GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
{
switch (getObjectClass(dependency))
@ -349,6 +370,14 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
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 */
break;
}

View File

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

View File

@ -37,382 +37,8 @@
static CollateClause * MakeCollateClauseFromOid(Oid collationOid);
static List * FilterNameListForDistributedDomains(List *domainNames, bool missing_ok,
List **distributedDomainAddresses);
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
* 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 "access/genam.h"
#include "access/xact.h"
#include "citus_version.h"
#include "catalog/pg_extension_d.h"
@ -37,6 +39,8 @@ static void AddSchemaFieldIfMissing(CreateExtensionStmt *stmt);
static List * FilterDistributedExtensions(List *extensionObjectList);
static List * ExtensionNameListToObjectAddressList(List *extensionObjectList);
static void MarkExistingObjectDependenciesDistributedIfSupported(void);
static List * GetAllViews(void);
static bool ShouldMarkRelationDistributedOnUpgrade(Oid relationId);
static bool ShouldPropagateExtensionCommand(Node *parseTree);
static bool IsAlterExtensionSetSchemaCitus(Node *parseTree);
static Node * RecreateExtensionStmt(Oid extensionOid);
@ -295,7 +299,7 @@ FilterDistributedExtensions(List *extensionObjectList)
{
List *extensionNameList = NIL;
Value *objectName = NULL;
String *objectName = NULL;
foreach_ptr(objectName, extensionObjectList)
{
const char *extensionName = strVal(objectName);
@ -334,7 +338,7 @@ ExtensionNameListToObjectAddressList(List *extensionObjectList)
{
List *extensionObjectAddressList = NIL;
Value *objectName;
String *objectName;
foreach_ptr(objectName, extensionObjectList)
{
/*
@ -510,26 +514,78 @@ MarkExistingObjectDependenciesDistributedIfSupported()
Oid citusTableId = InvalidOid;
foreach_oid(citusTableId, citusTableIdList)
{
ObjectAddress tableAddress = { 0 };
ObjectAddressSet(tableAddress, RelationRelationId, citusTableId);
if (ShouldSyncTableMetadata(citusTableId))
if (!ShouldMarkRelationDistributedOnUpgrade(citusTableId))
{
/* we need to pass pointer allocated in the heap */
ObjectAddress *addressPointer = palloc0(sizeof(ObjectAddress));
*addressPointer = tableAddress;
/* as of Citus 11, tables that should be synced are also considered object */
resultingObjectAddresses = lappend(resultingObjectAddresses, addressPointer);
continue;
}
List *distributableDependencyObjectAddresses =
GetDistributableDependenciesForObject(&tableAddress);
/* refrain reading the metadata cache for all tables */
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*/
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.
*/
@ -671,7 +806,7 @@ IsDropCitusExtensionStmt(Node *parseTree)
}
/* 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)
{
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
* per given extension name and updates extension verision

View File

@ -25,238 +25,9 @@
#include "nodes/primnodes.h"
static Node * RecreateForeignServerStmt(Oid serverId);
static bool NameListHasDistributedServer(List *serverNames);
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
* 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
* 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
GetObjectAddressByServerName(char *serverName, bool missing_ok)
{

View File

@ -490,7 +490,7 @@ GetDistributionArgIndex(Oid functionOid, char *distributionArgumentName,
distributionArgumentName++;
/* throws error if the input is not an integer */
distributionArgumentIndex = pg_atoi(distributionArgumentName, 4, 0);
distributionArgumentIndex = pg_strtoint32(distributionArgumentName);
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
* 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
* 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 */
Value *funcNameStr = lfirst(list_tail(names));
String *funcNameStr = lfirst(list_tail(names));
List *newNames = list_make2(makeString(stmt->newschema), funcNameStr);
/*
@ -1938,8 +1686,8 @@ GenerateBackupNameForProcCollision(const ObjectAddress *address)
char *newName = palloc0(NAMEDATALEN);
char suffix[NAMEDATALEN] = { 0 };
int count = 0;
Value *namespace = makeString(get_namespace_name(get_func_namespace(
address->objectId)));
String *namespace = makeString(get_namespace_name(get_func_namespace(
address->objectId)));
char *baseName = get_func_name(address->objectId);
int baseLength = strlen(baseName);
Oid *argtypes = NULL;

View File

@ -42,6 +42,7 @@
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "nodes/parsenodes.h"
#include "parser/parse_utilcmd.h"
#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
@ -184,9 +185,18 @@ PreprocessIndexStmt(Node *node, const char *createIndexCommand,
*/
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 */
MemoryContext relationContext = GetMemoryChunkContext(relationRangeVar);
char *defaultIndexName = GenerateDefaultIndexName(createIndexStatement);
char *defaultIndexName = GenerateDefaultIndexName(copyCreateIndexStatement);
createIndexStatement->idxname = MemoryContextStrdup(relationContext,
defaultIndexName);
}
@ -464,7 +474,8 @@ GenerateCreateIndexDDLJob(IndexStmt *createIndexStatement, const char *createInd
{
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = CreateIndexStmtGetRelationId(createIndexStatement);
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId,
CreateIndexStmtGetRelationId(createIndexStatement));
ddlJob->startNewTransaction = createIndexStatement->concurrent;
ddlJob->metadataSyncCommand = createIndexCommand;
ddlJob->taskList = CreateIndexTaskList(createIndexStatement);
@ -598,7 +609,7 @@ PreprocessReindexStmt(Node *node, const char *reindexCommand,
}
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = IsReindexWithParam_compat(reindexStatement,
"concurrently");
ddlJob->metadataSyncCommand = reindexCommand;
@ -695,7 +706,8 @@ PreprocessDropIndexStmt(Node *node, const char *dropIndexCommand,
MarkInvalidateForeignKeyGraph();
}
ddlJob->targetRelationId = distributedRelationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId,
distributedRelationId);
/*
* 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)
{
char *columnName = (char *) lfirst(columnNameCell);
Value *columnNameValue = makeString(columnName);
String *columnNameValue = makeString(columnName);
attributeList = lappend(attributeList, columnNameValue);
}
@ -3430,10 +3430,7 @@ InitializeCopyShardState(CopyShardState *shardState,
ereport(ERROR, (errmsg("could not connect to any active placements")));
}
if (hasRemoteCopy)
{
EnsureRemoteTaskExecutionAllowed();
}
EnsureTaskExecutionAllowed(hasRemoteCopy);
/*
* 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
* 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) &&
!IsIndexRenameStmt(renameStmt) &&
!IsPolicyRenameStmt(renameStmt))
!IsPolicyRenameStmt(renameStmt) &&
!IsViewRenameStmt(renameStmt))
{
return NIL;
}
@ -48,7 +49,7 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
/*
* The lock levels here should be same as the ones taken in
* 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,
AccessExclusiveLock,
@ -63,14 +64,31 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
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);
stmtCopy->renameType = OBJECT_SEQUENCE;
return PreprocessRenameSequenceStmt((Node *) stmtCopy, renameCommand,
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 */
switch (renameStmt->renameType)
@ -127,7 +145,7 @@ PreprocessRenameStmt(Node *node, const char *renameCommand,
}
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = tableRelationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, tableRelationId);
ddlJob->metadataSyncCommand = renameCommand;
ddlJob->taskList = DDLTaskList(tableRelationId, renameCommand);

View File

@ -150,7 +150,7 @@ PostprocessAlterRoleStmt(Node *node, const char *queryString)
if (encryptedPassword != NULL)
{
Value *encryptedPasswordValue = makeString((char *) encryptedPassword);
String *encryptedPasswordValue = makeString((char *) encryptedPassword);
option->arg = (Node *) encryptedPasswordValue;
}
else
@ -741,8 +741,13 @@ makeStringConst(char *str, int location)
{
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.val.str = str;
#endif
n->location = location;
return (Node *) n;
@ -759,8 +764,13 @@ makeIntConst(int val, int location)
{
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.val.ival = val;
#endif
n->location = location;
return (Node *) n;
@ -777,8 +787,13 @@ makeFloatConst(char *str, int location)
{
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.val.str = str;
#endif
n->location = location;
return (Node *) n;

View File

@ -107,7 +107,7 @@ PreprocessDropSchemaStmt(Node *node, const char *queryString,
EnsureSequentialMode(OBJECT_SCHEMA);
Value *schemaVal = NULL;
String *schemaVal = NULL;
foreach_ptr(schemaVal, distributedSchemas)
{
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
* the object of the CreateSchemaStmt. Errors if missing_ok is false.
@ -288,7 +251,7 @@ FilterDistributedSchemas(List *schemas)
{
List *distributedSchemas = NIL;
Value *schemaValue = NULL;
String *schemaValue = NULL;
foreach_ptr(schemaValue, schemas)
{
const char *schemaName = strVal(schemaValue);

View File

@ -226,7 +226,6 @@ PreprocessDropSequenceStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
List *deletingSequencesList = stmt->objects;
List *distributedSequencesList = 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
* sequences.
*/
List *deletingSequencesList = stmt->objects;
List *objectNameList = NULL;
foreach_ptr(objectNameList, deletingSequencesList)
{

View File

@ -92,7 +92,7 @@ PreprocessCreateStatisticsStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -197,7 +197,7 @@ PreprocessDropStatisticsStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -236,7 +236,7 @@ PreprocessAlterStatisticsRenameStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -274,7 +274,7 @@ PreprocessAlterStatisticsSchemaStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -295,7 +295,7 @@ PostprocessAlterStatisticsSchemaStmt(Node *node, const char *queryString)
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
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),
statName), false);
Oid relationId = GetRelIdByStatsOid(statsOid);
@ -328,7 +328,7 @@ AlterStatisticsSchemaStmtObjectAddress(Node *node, bool missingOk)
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
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),
statName), missingOk);
ObjectAddressSet(address, StatisticExtRelationId, statsOid);
@ -376,7 +376,7 @@ PreprocessAlterStatisticsStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -416,7 +416,7 @@ PreprocessAlterStatisticsOwnerStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);

View File

@ -651,12 +651,21 @@ PostprocessAlterTableSchemaStmt(Node *node, const char *queryString)
*/
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;
return PostprocessAlterSequenceSchemaStmt((Node *) stmt, queryString);
}
else if (relKind == RELKIND_VIEW)
{
stmt->objectType = OBJECT_VIEW;
return PostprocessAlterViewSchemaStmt((Node *) stmt, queryString);
}
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
* 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
*/
if (get_rel_relkind(leftRelationId) == RELKIND_SEQUENCE)
char relKind = get_rel_relkind(leftRelationId);
if (relKind == RELKIND_SEQUENCE)
{
AlterTableStmt *stmtCopy = copyObject(alterTableStatement);
AlterTableStmtObjType_compat(stmtCopy) = OBJECT_SEQUENCE;
return PreprocessAlterSequenceOwnerStmt((Node *) stmtCopy, alterTableCommand,
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
@ -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 */
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = leftRelationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, leftRelationId);
const char *sqlForTaskList = alterTableCommand;
if (deparseAT)
@ -1758,18 +1775,31 @@ PreprocessAlterTableSchemaStmt(Node *node, const char *queryString,
{
return NIL;
}
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
stmt->missing_ok);
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);
stmtCopy->objectType = OBJECT_SEQUENCE;
return PreprocessAlterSequenceSchemaStmt((Node *) stmtCopy, queryString,
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 */
if (!OidIsValid(relationId) || !IsCitusTable(relationId))
@ -1779,7 +1809,7 @@ PreprocessAlterTableSchemaStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
QualifyTreeNode((Node *) stmt);
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = DeparseTreeNode((Node *) stmt);
ddlJob->taskList = DDLTaskList(relationId, ddlJob->metadataSyncCommand);
return list_make1(ddlJob);
@ -1939,12 +1969,19 @@ PostprocessAlterTableStmt(AlterTableStmt *alterTableStatement)
* since this is the only ALTER command of a sequence that
* 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;
PostprocessAlterSequenceOwnerStmt((Node *) alterTableStatement, NULL);
return;
}
else if (relKind == RELKIND_VIEW)
{
AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_VIEW;
PostprocessAlterViewStmt((Node *) alterTableStatement, NULL);
return;
}
/*
* Before ensuring each dependency exist, update dependent sequences

View File

@ -42,8 +42,6 @@
#include "distributed/worker_create_or_replace.h"
static List * GetDistributedTextSearchConfigurationNames(DropStmt *stmt);
static List * GetDistributedTextSearchDictionaryNames(DropStmt *stmt);
static DefineStmt * GetTextSearchConfigDefineStmt(Oid tsconfigOid);
static DefineStmt * GetTextSearchDictionaryDefineStmt(Oid tsdictOid);
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 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 *
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
* based on the configuration as defined in the catalog identified by tsconfigOid.

View File

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

View File

@ -40,15 +40,10 @@
#include "utils/rel.h"
#define LOCK_RELATION_IF_EXISTS "SELECT lock_relation_if_exists(%s, '%s');"
/* Local functions forward declarations for unsupported command checks */
static void ErrorIfUnsupportedTruncateStmt(TruncateStmt *truncateStatement);
static void ExecuteTruncateStmtSequentialIfNecessary(TruncateStmt *command);
static void EnsurePartitionTableNotReplicatedForTruncate(TruncateStmt *truncateStatement);
static void LockTruncatedRelationMetadataInWorkers(TruncateStmt *truncateStatement);
static void AcquireDistributedLockOnRelations(List *relationIdList, LOCKMODE lockMode);
static List * TruncateTaskList(Oid relationId);
@ -248,7 +243,13 @@ PreprocessTruncateStatement(TruncateStmt *truncateStatement)
ErrorIfUnsupportedTruncateStmt(truncateStatement);
EnsurePartitionTableNotReplicatedForTruncate(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;
/* 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 Oid GetTypeOwner(Oid typeOid);
static Oid LookupNonAssociatedArrayTypeNameOid(ParseState *pstate,
@ -104,365 +102,6 @@ static List * CompositeTypeColumnDefList(Oid typeOid);
static CreateEnumStmt * RecreateEnumStmt(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
* 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
* 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 */
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
@ -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
*
@ -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
* 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/tablecmds.h"
#include "distributed/adaptive_executor.h"
#include "distributed/backend_data.h"
#include "distributed/colocation_utils.h"
#include "distributed/commands.h"
#include "distributed/commands/multi_copy.h"
@ -76,6 +77,7 @@
#include "nodes/makefuncs.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@ -88,6 +90,7 @@ static int activeDropSchemaOrDBs = 0;
static bool ConstraintDropped = false;
ProcessUtility_hook_type PrevProcessUtility = NULL;
int UtilityHookLevel = 0;
@ -166,7 +169,6 @@ multi_ProcessUtility(PlannedStmt *pstmt,
parsetree = pstmt->utilityStmt;
if (IsA(parsetree, TransactionStmt) ||
IsA(parsetree, LockStmt) ||
IsA(parsetree, ListenStmt) ||
IsA(parsetree, NotifyStmt) ||
IsA(parsetree, ExecuteStmt) ||
@ -183,8 +185,8 @@ multi_ProcessUtility(PlannedStmt *pstmt,
* that state. Since we never need to intercept transaction statements,
* skip our checks and immediately fall into standard_ProcessUtility.
*/
standard_ProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag);
PrevProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag);
return;
}
@ -203,9 +205,22 @@ multi_ProcessUtility(PlannedStmt *pstmt,
parsetree);
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
* EXTENSION is invoked.
*/
standard_ProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag);
PrevProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag);
return;
}
@ -248,8 +263,8 @@ multi_ProcessUtility(PlannedStmt *pstmt,
PG_TRY();
{
standard_ProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag);
PrevProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag);
StoredProcedureLevel -= 1;
@ -282,8 +297,8 @@ multi_ProcessUtility(PlannedStmt *pstmt,
PG_TRY();
{
standard_ProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag);
PrevProcessUtility_compat(pstmt, queryString, false, context,
params, queryEnv, dest, completionTag);
DoBlockLevel -= 1;
}
@ -485,6 +500,18 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
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
* and distribute the partition if necessary.
@ -505,6 +532,20 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
parsetree = pstmt->utilityStmt;
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)
{
ddlJobs = ops->preprocess(parsetree, queryString, context);
@ -610,30 +651,81 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
/*upgrade citus */
DefElem *newVersionValue = GetExtensionOption(
((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);
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 */
if (strcmp(newVersion, "11.1-1") == 0 && citusColumnarOid == InvalidOid)
/*alter extension citus update to version >= 11.1-1, and no citus_columnar installed */
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");
}
else if (strcmp(curExtensionVersion, "11.1-1") == 0 && citusColumnarOid !=
InvalidOid)
else if (newVersionNumber * 100 < 1110 && citusColumnarOid != InvalidOid)
{
/*downgrade citus_columnar to Y */
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,
params, queryEnv, dest, completionTag);
PrevProcessUtility_compat(pstmt, queryString, false, context,
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)
{
@ -1115,16 +1207,20 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob)
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
* its metadata and verify some properties around the table.
* Only for ddlJobs that are targetting an object we want to sync
* its metadata.
*/
shouldSyncMetadata = ShouldSyncTableMetadata(targetRelationId);
EnsurePartitionTableNotReplicated(targetRelationId);
shouldSyncMetadata = ShouldSyncUserCommandForObject(targetObjectAddress);
if (targetObjectAddress.classId == RelationRelationId)
{
EnsurePartitionTableNotReplicated(targetObjectAddress.objectId);
}
}
bool localExecutionSupported = true;
@ -1375,7 +1471,7 @@ CreateCustomDDLTaskList(Oid relationId, TableDDLCommand *command)
}
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = GetTableDDLCommand(command);
ddlJob->taskList = taskList;
@ -1626,7 +1722,7 @@ NodeDDLTaskList(TargetWorkerSet targets, List *commands)
}
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = InvalidOid;
ddlJob->targetObjectAddress = InvalidObjectAddress;
ddlJob->metadataSyncCommand = NULL;
ddlJob->taskList = list_make1(task);
@ -1654,26 +1750,3 @@ DropSchemaOrDBInProgress(void)
{
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, " (");
Value *columnName = NULL;
String *columnName = NULL;
foreach_ptr(columnName, columnNameList)
{
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
* resetting its states.

View File

@ -1115,3 +1115,92 @@ SendCancelationRequest(MultiConnection *connection)
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,
List *optionList);
static void simple_quote_literal(StringInfo buf, const char *val);
static char * flatten_reloptions(Oid relid);
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
* src/backend/utils/adt/ruleutils.c
*/
static char *
char *
flatten_reloptions(Oid relid)
{
char *result = NULL;

View File

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

View File

@ -223,7 +223,7 @@ AppendDropForeignServerStmt(StringInfo buf, DropStmt *stmt)
static void
AppendServerNames(StringInfo buf, DropStmt *stmt)
{
Value *serverValue = NULL;
String *serverValue = NULL;
foreach_ptr(serverValue, stmt->objects)
{
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));
}
Value value = varArgConst->val;
switch (value.type)
Node *value = (Node *) &varArgConst->val;
switch (value->type)
{
case T_Integer:
{
appendStringInfo(buf, " %d", intVal(&value));
appendStringInfo(buf, " %d", intVal(value));
break;
}
case T_Float:
{
appendStringInfo(buf, " %s", strVal(&value));
appendStringInfo(buf, " %s", strVal(value));
break;
}
@ -428,7 +428,7 @@ AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
Datum interval =
DirectFunctionCall3(interval_in,
CStringGetDatum(strVal(&value)),
CStringGetDatum(strVal(value)),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(typmod));
@ -440,7 +440,7 @@ AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
else
{
appendStringInfo(buf, " %s", quote_literal_cstr(strVal(
&value)));
value)));
}
break;
}

View File

@ -126,7 +126,7 @@ AppendDropSchemaStmt(StringInfo buf, DropStmt *stmt)
appendStringInfoString(buf, "IF EXISTS ");
}
Value *schemaValue = NULL;
String *schemaValue = NULL;
foreach_ptr(schemaValue, stmt->objects)
{
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));
if (seq->schemaname == NULL)
{
Oid schemaOid = RangeVarGetCreationNamespace(seq);
seq->schemaname = get_namespace_name(schemaOid);
}
char *qualifiedSequenceName = quote_qualified_identifier(seq->schemaname,
seq->relname);
appendStringInfoString(buf, qualifiedSequenceName);

View File

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

View File

@ -464,7 +464,7 @@ DeparseTextSearchDictionaryCommentStmt(Node *node)
static void
AppendStringInfoTokentypeList(StringInfo buf, List *tokentypes)
{
Value *tokentype = NULL;
String *tokentype = NULL;
bool first = true;
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 "distributed/commands.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/version_compat.h"
#include "parser/parse_func.h"
#include "utils/lsyscache.h"
@ -38,8 +40,13 @@ QualifyAlterSequenceOwnerStmt(Node *node)
if (seq->schemaname == NULL)
{
Oid schemaOid = RangeVarGetCreationNamespace(seq);
seq->schemaname = get_namespace_name(schemaOid);
Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
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)
{
Oid schemaOid = RangeVarGetCreationNamespace(seq);
seq->schemaname = get_namespace_name(schemaOid);
Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
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)
{
Oid schemaOid = RangeVarGetCreationNamespace(seq);
seq->schemaname = get_namespace_name(schemaOid);
Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
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 "catalog/namespace.h"
#include "catalog/pg_statistic_ext.h"
#include "distributed/commands.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "nodes/parsenodes.h"
#include "nodes/value.h"
#include "utils/syscache.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/relcache.h"
static Oid GetStatsNamespaceOid(Oid statsOid);
void
QualifyCreateStatisticsStmt(Node *node)
{
@ -68,8 +72,14 @@ QualifyDropStatisticsStmt(Node *node)
if (stat->schemaname == NULL)
{
Oid schemaOid = RangeVarGetCreationNamespace(stat);
stat->schemaname = get_namespace_name(schemaOid);
Oid statsOid = get_statistics_object_oid(objectNameList,
dropStatisticsStmt->missing_ok);
if (OidIsValid(statsOid))
{
Oid schemaOid = GetStatsNamespaceOid(statsOid);
stat->schemaname = get_namespace_name(schemaOid);
}
}
objectNameListWithSchema = lappend(objectNameListWithSchema,
@ -94,7 +104,14 @@ QualifyAlterStatisticsRenameStmt(Node *node)
if (list_length(nameList) == 1)
{
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);
renameStmt->object = (Node *) MakeNameListFromRangeVar(stat);
}
@ -115,7 +132,14 @@ QualifyAlterStatisticsSchemaStmt(Node *node)
if (list_length(nameList) == 1)
{
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);
stmt->object = (Node *) MakeNameListFromRangeVar(stat);
}
@ -136,7 +160,14 @@ QualifyAlterStatisticsStmt(Node *node)
if (list_length(stmt->defnames) == 1)
{
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);
stmt->defnames = MakeNameListFromRangeVar(stat);
}
@ -159,8 +190,40 @@ QualifyAlterStatisticsOwnerStmt(Node *node)
if (list_length(nameList) == 1)
{
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);
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_physical_planner.h"
#include "distributed/multi_server_executor.h"
#include "distributed/param_utils.h"
#include "distributed/placement_access.h"
#include "distributed/placement_connection.h"
#include "distributed/relation_access_tracking.h"
@ -171,7 +172,6 @@
#include "storage/fd.h"
#include "storage/latch.h"
#include "utils/builtins.h"
#include "utils/int8.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
@ -831,6 +831,19 @@ AdaptiveExecutor(CitusScanState *scanState)
distributedPlan->modLevel, taskList, excludeFromXact);
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(
distributedPlan->modLevel,
taskList,
@ -1321,7 +1334,8 @@ StartDistributedExecution(DistributedExecution *execution)
/* make sure we are not doing remote execution from within a task */
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 (storeRows && *currentAffectedTupleString != '\0')
{
scanint8(currentAffectedTupleString, false, &currentAffectedTupleCount);
currentAffectedTupleCount = pg_strtoint64(currentAffectedTupleString);
Assert(currentAffectedTupleCount >= 0);
execution->rowsProcessed += currentAffectedTupleCount;
}

View File

@ -108,26 +108,26 @@
bool EnableLocalExecution = true;
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 uint64 ExecuteLocalTaskListInternal(List *taskList,
ParamListInfo paramListInfo,
DistributedPlan *distributedPlan,
TupleDestination *defaultTupleDest,
bool isUtilityCommand);
static void SplitLocalAndRemotePlacements(List *taskPlacementList,
List **localTaskPlacementList,
List **remoteTaskPlacementList);
static uint64 ExecuteLocalTaskPlan(PlannedStmt *taskPlan, char *queryString,
TupleDestination *tupleDest, Task *task,
ParamListInfo paramListInfo);
static uint64 LocallyExecuteTaskPlan(PlannedStmt *taskPlan, char *queryString,
TupleDestination *tupleDest, Task *task,
ParamListInfo paramListInfo);
static uint64 ExecuteTaskPlan(PlannedStmt *taskPlan, char *queryString,
TupleDestination *tupleDest, Task *task,
ParamListInfo paramListInfo);
static void RecordNonDistTableAccessesForTask(Task *task);
static void LogLocalCommand(Task *task);
static uint64 LocallyPlanAndExecuteMultipleQueries(List *queryStrings,
TupleDestination *tupleDest,
Task *task);
static void LocallyExecuteUtilityTask(Task *task);
static void ExecuteUdfTaskQuery(Query *localUdfCommandQuery);
static void EnsureTransitionPossible(LocalExecutionStatus from,
LocalExecutionStatus to);
@ -204,50 +204,7 @@ ExecuteLocalTaskListExtended(List *taskList,
TupleDestination *defaultTupleDest,
bool isUtilityCommand)
{
uint64 totalRowsProcessed = 0;
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;
int numParams = 0;
Oid *parameterTypes = NULL;
@ -263,6 +220,12 @@ ExecuteLocalTaskListInternal(List *taskList,
numParams = paramListInfo->numParams;
}
if (taskList != NIL)
{
bool isRemote = false;
EnsureTaskExecutionAllowed(isRemote);
}
/*
* Use a new memory context that gets reset after every task to free
* the deparsed query string and query plan.
@ -304,7 +267,7 @@ ExecuteLocalTaskListInternal(List *taskList,
if (isUtilityCommand)
{
ExecuteUtilityCommand(TaskQueryString(task));
LocallyExecuteUtilityTask(task);
MemoryContextSwitchTo(oldContext);
MemoryContextReset(loopContext);
@ -391,8 +354,8 @@ ExecuteLocalTaskListInternal(List *taskList,
}
totalRowsProcessed +=
ExecuteLocalTaskPlan(localPlan, shardQueryString,
tupleDest, task, paramListInfo);
LocallyExecuteTaskPlan(localPlan, shardQueryString,
tupleDest, task, paramListInfo);
MemoryContextSwitchTo(oldContext);
MemoryContextReset(loopContext);
@ -421,9 +384,9 @@ LocallyPlanAndExecuteMultipleQueries(List *queryStrings, TupleDestination *tuple
ParamListInfo paramListInfo = NULL;
PlannedStmt *localPlan = planner_compat(shardQuery, cursorOptions,
paramListInfo);
totalProcessedRows += ExecuteLocalTaskPlan(localPlan, queryString,
tupleDest, task,
paramListInfo);
totalProcessedRows += LocallyExecuteTaskPlan(localPlan, queryString,
tupleDest, task,
paramListInfo);
}
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
* 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
* local and remote nodes.
*/
task->partiallyLocalOrRemote = true;
Task *localTask = copyObject(task);
localTask->partiallyLocalOrRemote = true;
localTask->taskPlacementList = localTaskPlacementList;
*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 */
Assert(remoteTaskPlacementList != NIL);
Task *remoteTask = copyObject(task);
remoteTask->partiallyLocalOrRemote = true;
remoteTask->taskPlacementList = remoteTaskPlacementList;
*remoteTaskList = lappend(*remoteTaskList, remoteTask);
@ -630,9 +626,50 @@ SplitLocalAndRemotePlacements(List *taskPlacementList, List **localTaskPlacement
* case of DML.
*/
static uint64
ExecuteLocalTaskPlan(PlannedStmt *taskPlan, char *queryString,
TupleDestination *tupleDest, Task *task,
ParamListInfo paramListInfo)
LocallyExecuteTaskPlan(PlannedStmt *taskPlan, char *queryString,
TupleDestination *tupleDest, Task *task,
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;
QueryEnvironment *queryEnv = create_queryEnv();
@ -642,7 +679,7 @@ ExecuteLocalTaskPlan(PlannedStmt *taskPlan, char *queryString,
RecordNonDistTableAccessesForTask(task);
MemoryContext localContext = AllocSetContextCreate(CurrentMemoryContext,
"ExecuteLocalTaskPlan",
"ExecuteTaskPlan",
ALLOCSET_DEFAULT_SIZES);
MemoryContext oldContext = MemoryContextSwitchTo(localContext);

View File

@ -18,6 +18,7 @@
#include "catalog/dependency.h"
#include "catalog/pg_class.h"
#include "catalog/namespace.h"
#include "distributed/backend_data.h"
#include "distributed/citus_custom_scan.h"
#include "distributed/commands/multi_copy.h"
#include "distributed/commands/utility_hook.h"
@ -50,6 +51,7 @@
#include "tcop/dest.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "utils/fmgrprotos.h"
#include "utils/snapmgr.h"
#include "utils/memutils.h"
@ -62,6 +64,12 @@ int MultiShardConnectionType = PARALLEL_CONNECTION;
bool WritableStandbyCoordinator = false;
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.
* 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 bool CitusCustomScanStateWalker(PlanState *planState,
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
@ -617,7 +630,8 @@ RewriteRawQueryStmt(RawStmt *rawStmt, const char *queryString, Oid *paramOids, i
numParams)
{
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)
{
@ -803,6 +817,11 @@ GetObjectTypeString(ObjectType objType)
return "type";
}
case OBJECT_VIEW:
{
return "view";
}
default:
{
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
* a function in a query that gets pushed down to the worker, and the
* function performs a query on a distributed table.
*/
void
EnsureRemoteTaskExecutionAllowed(void)
EnsureTaskExecutionAllowed(bool isRemote)
{
if (!InTaskExecution())
if (IsTaskExecutionAllowed(isRemote))
{
/* we are not within a task, distributed execution is allowed */
return;
}
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
InTaskExecution(void)
static bool
IsTaskExecutionAllowed(bool isRemote)
{
if (LocalExecutorLevel > 0)
if (AllowNestedDistributedExecution)
{
/* in a local task */
/* user explicitly allows nested execution */
return true;
}
/*
* Normally, any query execution within a citus-initiated backend
* is considered a task execution, but an exception is when we
* are in a delegated function/procedure call.
*/
return IsCitusInternalBackend() &&
!InTopLevelDelegatedFunctionCall &&
!InDelegatedProcedureCall;
if (!isRemote)
{
if (AllowedDistributionColumnValue.isActive)
{
/*
* When we are in a forced delegated function call, we explicitly check
* whether local tasks use the same distribution column value in
* 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);
static void ApplyAddToDependencyList(ObjectAddressCollector *collector,
DependencyDefinition *definition);
static List * GetViewRuleReferenceDependencyList(Oid relationId);
static List * ExpandCitusSupportedTypes(ObjectAddressCollector *collector,
ObjectAddress target);
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.
*/
static List *
@ -747,7 +748,8 @@ SupportedDependencyByCitus(const ObjectAddress *address)
relKind == RELKIND_FOREIGN_TABLE ||
relKind == RELKIND_SEQUENCE ||
relKind == RELKIND_INDEX ||
relKind == RELKIND_PARTITIONED_INDEX)
relKind == RELKIND_PARTITIONED_INDEX ||
relKind == RELKIND_VIEW)
{
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
* object has any undistributable dependency.
@ -801,8 +855,11 @@ DeferErrorIfHasUnsupportedDependency(const ObjectAddress *objectAddress)
* Otherwise, callers are expected to throw the error returned from this
* function as a hard one by ignoring the detail part.
*/
appendStringInfo(detailInfo, "\"%s\" will be created only locally",
objectDescription);
if (!IsObjectDistributed(objectAddress))
{
appendStringInfo(detailInfo, "\"%s\" will be created only locally",
objectDescription);
}
if (SupportedDependencyByCitus(undistributableDependency))
{
@ -813,9 +870,19 @@ DeferErrorIfHasUnsupportedDependency(const ObjectAddress *objectAddress)
objectDescription,
dependencyDescription);
appendStringInfo(hintInfo, "Distribute \"%s\" first to distribute \"%s\"",
dependencyDescription,
objectDescription);
if (IsObjectDistributed(objectAddress))
{
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,
errorInfo->data, detailInfo->data, hintInfo->data);
@ -893,7 +960,9 @@ GetUndistributableDependency(const ObjectAddress *objectAddress)
{
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 */
continue;
@ -1307,9 +1376,26 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
* create all objects required by the indices before we create the table
* including indices.
*/
List *indexDependencyList = GetRelationIndicesDependencyList(relationId);
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:
@ -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
* list for the given relation.

View File

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

View File

@ -7,7 +7,9 @@
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "distributed/pg_version_constants.h"
#include "pg_version_compat.h"
#include "stdint.h"
#include "postgres.h"
@ -150,6 +152,7 @@ typedef struct MetadataCacheData
Oid distShardShardidIndexId;
Oid distPlacementShardidIndexId;
Oid distPlacementPlacementidIndexId;
Oid distColocationidIndexId;
Oid distPlacementGroupidIndexId;
Oid distTransactionRelationId;
Oid distTransactionGroupIndexId;
@ -183,6 +186,9 @@ bool EnableVersionChecks = true; /* version checks are enabled */
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 */
static HTAB *DistTableCacheHash = NULL;
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
*
@ -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
* 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 */
Oid
DistTransactionRelationId(void)
@ -2864,8 +2920,8 @@ CurrentUserName(void)
Oid
LookupTypeOid(char *schemaNameSting, char *typeNameString)
{
Value *schemaName = makeString(schemaNameSting);
Value *typeName = makeString(typeNameString);
String *schemaName = makeString(schemaNameSting);
String *typeName = makeString(typeNameString);
List *qualifiedName = list_make2(schemaName, typeName);
TypeName *enumTypeName = makeTypeNameFromNameList(qualifiedName);

View File

@ -97,6 +97,7 @@ static char * SchemaOwnerName(Oid objectId);
static bool HasMetadataWorkers(void);
static void CreateShellTableOnWorkers(Oid relationId);
static void CreateTableMetadataOnWorkers(Oid relationId);
static NodeMetadataSyncResult SyncNodeMetadataToNodesOptional(void);
static bool ShouldSyncTableMetadataInternal(bool hashDistributed,
bool citusTableWithNoDistKey);
static bool SyncNodeMetadataSnapshotToNode(WorkerNode *workerNode, bool raiseOnError);
@ -138,6 +139,7 @@ static char * RemoteTypeIdExpression(Oid typeId);
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(stop_metadata_sync_to_node);
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
* 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
* 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)
{
SendMetadataCommandListToWorkerInCoordinatedTransaction(workerNode->workerName,
workerNode->workerPort,
currentUser,
recreateMetadataSnapshotCommandList);
SendMetadataCommandListToWorkerListInCoordinatedTransaction(list_make1(
workerNode),
currentUser,
recreateMetadataSnapshotCommandList);
return true;
}
else
@ -2219,16 +2266,16 @@ DetachPartitionCommandList(void)
/*
* SyncNodeMetadataToNodes tries recreating the metadata snapshot in the
* metadata workers that are out of sync. Returns the result of
* synchronization.
* SyncNodeMetadataToNodesOptional tries recreating the metadata
* snapshot in the metadata workers that are out of sync.
* Returns the result of synchronization.
*
* This function must be called within coordinated transaction
* since updates on the pg_dist_node metadata must be rollbacked if anything
* goes wrong.
*/
static NodeMetadataSyncResult
SyncNodeMetadataToNodes(void)
SyncNodeMetadataToNodesOptional(void)
{
NodeMetadataSyncResult result = NODE_METADATA_SYNC_SUCCESS;
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
* MX nodes. It retries until success and then exits.
@ -2334,7 +2421,7 @@ SyncNodeMetadataToNodesMain(Datum main_arg)
{
UseCoordinatedTransaction();
NodeMetadataSyncResult result = SyncNodeMetadataToNodes();
NodeMetadataSyncResult result = SyncNodeMetadataToNodesOptional();
syncedAllNodes = (result == NODE_METADATA_SYNC_SUCCESS);
/* we use LISTEN/NOTIFY to wait for metadata syncing in tests */
@ -3393,12 +3480,19 @@ ColocationGroupCreateCommandList(void)
"distributioncolumncollationschema) AS (VALUES ");
Relation pgDistColocation = table_open(DistColocationRelationId(), AccessShareLock);
Relation colocationIdIndexRel = index_open(DistColocationIndexId(), AccessShareLock);
bool indexOK = false;
SysScanDesc scanDescriptor = systable_beginscan(pgDistColocation, InvalidOid, indexOK,
NULL, 0, NULL);
/*
* It is not strictly necessary to read the tuples in order.
* 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))
{
@ -3456,10 +3550,11 @@ ColocationGroupCreateCommandList(void)
"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);
if (!hasColocations)

View File

@ -106,17 +106,18 @@ static void InsertPlaceholderCoordinatorRecord(void);
static void InsertNodeRow(int nodeid, char *nodename, int32 nodeport, NodeMetadata
*nodeMetadata);
static void DeleteNodeRow(char *nodename, int32 nodeport);
static void SyncDistributedObjectsToNode(WorkerNode *workerNode);
static void SyncDistributedObjectsToNodeList(List *workerNodeList);
static void UpdateLocalGroupIdOnNode(WorkerNode *workerNode);
static void SyncPgDistTableMetadataToNode(WorkerNode *workerNode);
static void SyncPgDistTableMetadataToNodeList(List *nodeList);
static List * InterTableRelationshipCommandList();
static void BlockDistributedQueriesOnMetadataNodes(void);
static WorkerNode * TupleToWorkerNode(TupleDesc tupleDescriptor, HeapTuple heapTuple);
static List * PropagateNodeWideObjectsCommandList();
static WorkerNode * ModifiableWorkerNode(const char *nodeName, int32 nodePort);
static bool NodeIsLocal(WorkerNode *worker);
static void SetLockTimeoutLocally(int32 lock_cooldown);
static void UpdateNodeLocation(int32 nodeId, char *newNodeName, int32 newNodePort);
static bool UnsetMetadataSyncedForAll(void);
static bool UnsetMetadataSyncedForAllWorkers(void);
static char * GetMetadataSyncCommandToSetNodeColumn(WorkerNode *workerNode,
int columnIndex,
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_nodeport_for_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);
int32 nodePort = PG_GETARG_INT32(1);
bool forceDisableNode = PG_GETARG_BOOL(2);
bool synchronousDisableNode = PG_GETARG_BOOL(2);
char *nodeName = text_to_cstring(nodeNameText);
WorkerNode *workerNode = ModifiableWorkerNode(nodeName, nodePort);
@ -462,8 +464,10 @@ citus_disable_node(PG_FUNCTION_ARGS)
"isactive");
WorkerNode *firstWorkerNode = GetFirstPrimaryWorkerNode();
if (!forceDisableNode && firstWorkerNode &&
firstWorkerNode->nodeId == workerNode->nodeId)
bool disablingFirstNode =
(firstWorkerNode && firstWorkerNode->nodeId == workerNode->nodeId);
if (disablingFirstNode && !synchronousDisableNode)
{
/*
* 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.
*
* 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),
errmsg("disabling the first worker node in the "
"metadata is not allowed"),
errhint("You can force disabling node, but this operation "
"might cause replicated shards to diverge: SELECT "
"citus_disable_node('%s', %d, force:=true);",
workerNode->workerName,
nodePort)));
errhint("You can force disabling node, SELECT "
"citus_disable_node('%s', %d, "
"synchronous:=true);", workerNode->workerName,
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.
*/
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;
/*
* 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())
if (synchronousDisableNode)
{
/*
* 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();
}
@ -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.
*/
@ -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 shell distributed table
* - Inter relation between those shell tables
@ -799,17 +852,29 @@ SyncDistributedObjectsCommandList(WorkerNode *workerNode)
* since all the dependencies should be present in the coordinator already.
*/
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 */
return;
if (NodeIsCoordinator(workerNode))
{
/* 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;
}
@ -821,9 +886,8 @@ SyncDistributedObjectsToNode(WorkerNode *workerNode)
/* send commands to new workers, the current user should be a superuser */
Assert(superuser());
SendMetadataCommandListToWorkerInCoordinatedTransaction(
workerNode->workerName,
workerNode->workerPort,
SendMetadataCommandListToWorkerListInCoordinatedTransaction(
workerNodesToSync,
CurrentUserName(),
commandList);
}
@ -841,9 +905,8 @@ UpdateLocalGroupIdOnNode(WorkerNode *workerNode)
/* send commands to new workers, the current user should be a superuser */
Assert(superuser());
SendMetadataCommandListToWorkerInCoordinatedTransaction(
workerNode->workerName,
workerNode->workerPort,
SendMetadataCommandListToWorkerListInCoordinatedTransaction(
list_make1(workerNode),
CurrentUserName(),
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.
*
*/
static void
SyncPgDistTableMetadataToNode(WorkerNode *workerNode)
SyncPgDistTableMetadataToNodeList(List *nodeList)
{
if (NodeIsPrimary(workerNode) && !NodeIsCoordinator(workerNode))
{
List *syncPgDistMetadataCommandList = PgDistTableMetadataSyncCommandList();
/* send commands to new workers, the current user should be a superuser */
Assert(superuser());
/* send commands to new workers, the current user should be a superuser */
Assert(superuser());
SendMetadataCommandListToWorkerInCoordinatedTransaction(
workerNode->workerName,
workerNode->workerPort,
CurrentUserName(),
syncPgDistMetadataCommandList);
List *syncPgDistMetadataCommandList = PgDistTableMetadataSyncCommandList();
List *nodesWithMetadata = NIL;
WorkerNode *workerNode = NULL;
foreach_ptr(workerNode, nodeList)
{
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
* includes only replicating the reference tables and setting isactive column of the
* given node.
* ActivateNodeList iterates over the nodeList and activates the nodes.
* Some part of the node activation is done parallel across the nodes,
* such as syncing the metadata. However, reference table replication is
* done one by one across nodes.
*/
int
ActivateNode(char *nodeName, int nodePort)
void
ActivateNodeList(List *nodeList)
{
bool isActive = true;
/*
* We currently require the object propagation to happen via superuser,
* 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 */
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)));
}
/*
* Delete existing reference and replicated table placements on the
* given groupId if the group has been disabled earlier (e.g., isActive
* 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)
List *nodeToSyncMetadata = NIL;
WorkerNode *node = NULL;
foreach_ptr(node, nodeList)
{
/*
* We are going to sync the metadata anyway in this transaction, so do
* not fail just because the current metadata is not synced.
* First, locally mark the node is active, if everything goes well,
* we are going to sync this information to all the metadata nodes.
*/
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);
/*
* 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)
WorkerNode *workerNode =
FindWorkerNodeAnyCluster(node->workerName, node->workerPort);
if (workerNode == NULL)
{
ReplicateAllReferenceTablesToNode(workerNode);
ereport(ERROR, (errmsg("node at \"%s:%u\" does not exist", node->workerName,
node->workerPort)));
}
/*
* 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.
*/
SyncNodeMetadataToNode(nodeName, nodePort);
/* both nodes should be the same */
Assert(workerNode->nodeId == node->nodeId);
/*
* 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.
* Delete existing reference and replicated table placements on the
* given groupId if the group has been disabled earlier (e.g., isActive
* 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 */
WorkerNode *newWorkerNode = SetNodeState(nodeName, nodePort, isActive);
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
* 5 second intervals until sync is complete.
*/
if (UnsetMetadataSyncedForAll())
if (UnsetMetadataSyncedForAllWorkers())
{
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
* 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
* nodes to false. It returns true if it updated at least a node.
* UnsetMetadataSyncedForAllWorkers sets the metadatasynced column of all metadata
* worker nodes to false. It returns true if it updated at least a node.
*/
static bool
UnsetMetadataSyncedForAll(void)
UnsetMetadataSyncedForAllWorkers(void)
{
bool updatedAtLeastOne = false;
ScanKeyData scanKey[2];
int scanKeyCount = 2;
ScanKeyData scanKey[3];
int scanKeyCount = 3;
bool indexOK = false;
/*
@ -2669,6 +2806,11 @@ UnsetMetadataSyncedForAll(void)
ScanKeyInit(&scanKey[1], Anum_pg_dist_node_metadatasynced,
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);
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.
* Since we need to use intermediate data types Relation and Node from
@ -40,11 +40,6 @@ static void ErrorIfCurrentUserCanNotDistributeObject(ObjectType type,
Relation *relation);
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
* 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_FOREIGN_SERVER:
case OBJECT_LANGUAGE:
#if PG_VERSION_NUM >= PG_VERSION_15
case OBJECT_PARAMETER_ACL:
#endif
case OBJECT_PUBLICATION:
case OBJECT_ROLE:
case OBJECT_SCHEMA:
@ -320,6 +318,9 @@ PgGetObjectAddress(char *ttype, ArrayType *namearr, ArrayType *argsarr)
break;
}
#if PG_VERSION_NUM >= PG_VERSION_15
case OBJECT_PUBLICATION_NAMESPACE:
#endif
case OBJECT_USER_MAPPING:
{
objnode = (Node *) list_make2(linitial(name), linitial(args));
@ -419,6 +420,7 @@ ErrorIfCurrentUserCanNotDistributeObject(ObjectType type, ObjectAddress *addr,
case OBJECT_TABLE:
case OBJECT_EXTENSION:
case OBJECT_COLLATION:
case OBJECT_VIEW:
{
check_object_ownership(userId, type, *addr, node, *relation);
break;

View File

@ -31,6 +31,9 @@
#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);
static int ParseCommandParameters(FunctionCallInfo fcinfo, StringInfo **nodeNameArray,
@ -44,15 +47,15 @@ static void ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray,
int commandCount);
static bool GetConnectionStatusAndResult(MultiConnection *connection, bool *resultStatus,
StringInfo queryResultString);
static bool EvaluateQueryResult(MultiConnection *connection, PGresult *queryResult,
StringInfo queryResultString);
static void StoreErrorMessage(MultiConnection *connection, StringInfo queryResultString);
static void ExecuteCommandsAndStoreResults(StringInfo *nodeNameArray,
int *nodePortArray,
StringInfo *commandStringArray,
bool *statusArray,
StringInfo *resultStringArray,
int commandCount);
static bool ExecuteOptionalSingleResultCommand(MultiConnection *connection,
char *queryString, StringInfo
queryResultString);
static Tuplestorestate * CreateTupleStore(TupleDesc tupleDescriptor,
StringInfo *nodeNameArray, int *nodePortArray,
bool *statusArray,
@ -241,18 +244,66 @@ ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray, int *nodePor
FinishConnectionEstablishment(connection);
/* check whether connection attempt was successful */
if (PQstatus(connection->pgConn) != CONNECTION_OK)
{
appendStringInfo(queryResultString, "failed to connect to %s:%d", nodeName,
(int) nodePort);
nodePort);
statusArray[commandIndex] = false;
CloseConnection(connection);
connectionArray[commandIndex] = NULL;
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 */
@ -359,7 +410,7 @@ GetConnectionStatusAndResult(MultiConnection *connection, bool *resultStatus,
/* query result is available at this point */
PGresult *queryResult = PQgetResult(connection->pgConn);
bool success = EvaluateQueryResult(connection, queryResult, queryResultString);
bool success = EvaluateSingleQueryResult(connection, queryResult, queryResultString);
PQclear(queryResult);
*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
* nodeNameArray and nodePortArray, and executes command in commandStringArray
@ -471,63 +433,76 @@ ExecuteCommandsAndStoreResults(StringInfo *nodeNameArray, int *nodePortArray,
{
for (int commandIndex = 0; commandIndex < commandCount; commandIndex++)
{
CHECK_FOR_INTERRUPTS();
char *nodeName = nodeNameArray[commandIndex]->data;
int32 nodePort = nodePortArray[commandIndex];
char *queryString = commandStringArray[commandIndex]->data;
StringInfo queryResultString = resultStringArray[commandIndex];
bool reportResultError = false;
bool success = ExecuteRemoteQueryOrCommand(nodeName, nodePort, queryString,
queryResultString, reportResultError);
int connectionFlags = FORCE_NEW_CONNECTION;
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;
CHECK_FOR_INTERRUPTS();
CloseConnection(connection);
}
}
/*
* 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
* (success/failure), and query result. The query is expected to return a single
* target containing zero or one rows.
*/
bool
ExecuteRemoteQueryOrCommand(char *nodeName, uint32 nodePort, char *queryString,
StringInfo queryResultString, bool reportResultError)
static bool
ExecuteOptionalSingleResultCommand(MultiConnection *connection, char *queryString,
StringInfo queryResultString)
{
int connectionFlags = FORCE_NEW_CONNECTION;
MultiConnection *connection =
GetNodeConnection(connectionFlags, nodeName, nodePort);
bool raiseInterrupts = true;
if (PQstatus(connection->pgConn) != CONNECTION_OK)
{
appendStringInfo(queryResultString, "failed to connect to %s:%d", nodeName,
(int) nodePort);
appendStringInfo(queryResultString, "failed to connect to %s:%d",
connection->hostname, connection->port);
return false;
}
if (!SendRemoteCommand(connection, queryString))
{
appendStringInfo(queryResultString, "failed to send query to %s:%d", nodeName,
(int) nodePort);
appendStringInfo(queryResultString, "failed to send query to %s:%d",
connection->hostname, connection->port);
return false;
}
bool raiseInterrupts = true;
PGresult *queryResult = GetRemoteCommandResult(connection, raiseInterrupts);
bool success = EvaluateQueryResult(connection, queryResult, queryResultString);
if (!success && reportResultError)
{
ReportResultError(connection, queryResult, ERROR);
}
/* write the result value or error message to queryResultString */
bool success = EvaluateSingleQueryResult(connection, queryResult, queryResultString);
/* clear result and close the connection */
PQclear(queryResult);
/* close the connection */
CloseConnection(connection);
bool raiseErrors = false;
ClearResults(connection, raiseErrors);
return success;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "pg_version_compat.h"
#include "distributed/pg_version_constants.h"
#include "distributed/cte_inline.h"
@ -309,7 +310,7 @@ inline_cte_walker(Node *node, inline_cte_walker_context *context)
*/
if (columnAliasCount >= columnIndex)
{
Value *columnAlias = (Value *) list_nth(columnAliasList, columnIndex - 1);
String *columnAlias = (String *) list_nth(columnAliasList, columnIndex - 1);
Assert(IsA(columnAlias, String));
TargetEntry *targetEntry =
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
* one.
*
@ -1353,7 +1353,7 @@ FinalizeRouterPlan(PlannedStmt *localPlan, CustomScan *customScan)
TargetEntry *targetEntry = NULL;
foreach_ptr(targetEntry, customScan->scan.plan.targetlist)
{
Value *columnName = makeString(targetEntry->resname);
String *columnName = makeString(targetEntry->resname);
columnNameList = lappend(columnNameList, columnName);
}

View File

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

View File

@ -17,6 +17,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "distributed/backend_data.h"
#include "distributed/metadata_utility.h"
#include "distributed/citus_ruleutils.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 */
Query *analyzedQuery = parse_analyze_varparams(parseTree, queryString,
&paramTypes, &numParams);
Query *analyzedQuery = parse_analyze_varparams_compat(parseTree, queryString,
&paramTypes, &numParams, NULL);
#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, "%u", columnIndex);
Value *columnValue = makeString(columnName->data);
String *columnValue = makeString(columnName->data);
columnNameList = lappend(columnNameList, columnValue);
}

View File

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

View File

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

View File

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

View File

@ -24,8 +24,11 @@
#include "safe_lib.h"
#include "catalog/pg_authid.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_extension.h"
#include "citus_version.h"
#include "commands/explain.h"
#include "commands/extension.h"
#include "common/string.h"
#include "executor/executor.h"
#include "distributed/backend_data.h"
@ -74,7 +77,7 @@
#include "distributed/shared_library_init.h"
#include "distributed/statistics_collection.h"
#include "distributed/subplan_execution.h"
#include "distributed/resource_lock.h"
#include "distributed/transaction_management.h"
#include "distributed/transaction_recovery.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 */
static GucStringAssignHook OldApplicationNameAssignHook = NULL;
static object_access_hook_type PrevObjectAccessHook = NULL;
void _PG_init(void);
static void CitusObjectAccessHook(ObjectAccessType access, Oid classId, Oid objectId, int
subId, void *arg);
static void DoInitialCleanup(void);
static void ResizeStackToMaximumDepth(void);
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 WarnIfReplicationModelIsSet(int *newval, void **extra, GucSource source);
static bool NoticeIfSubqueryPushdownEnabled(bool *newval, void **extra, GucSource source);
static bool HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra,
GucSource source);
static void HideShardsFromAppNamePrefixesAssignHook(const char *newval, void *extra);
static bool ShowShardsForAppNamePrefixesCheckHook(char **newval, void **extra,
GucSource source);
static void ShowShardsForAppNamePrefixesAssignHook(const char *newval, void *extra);
static void ApplicationNameAssignHook(const char *newval, void *extra);
static bool NodeConninfoGucCheckHook(char **newval, void **extra, GucSource source);
static void NodeConninfoGucAssignHook(const char *newval, void *extra);
@ -334,9 +340,6 @@ _PG_init(void)
/* intercept planner */
planner_hook = distributed_planner;
/* register utility hook */
ProcessUtility_hook = multi_ProcessUtility;
/* register for planner hook */
set_rel_pathlist_hook = multi_relation_restriction_hook;
set_join_pathlist_hook = multi_join_restriction_hook;
@ -384,23 +387,22 @@ _PG_init(void)
DoInitialCleanup();
}
PrevObjectAccessHook = object_access_hook;
object_access_hook = CitusObjectAccessHook;
/* ensure columnar module is loaded at the right time */
load_file(COLUMNAR_MODULE_NAME, false);
/*
* Now, acquire symbols from columnar module. First, acquire
* the address of the set options hook, and set it so that we
* can propagate options changes.
* Register utility hook. This must be done after loading columnar, so
* that the citus hook is called first, followed by the columnar hook,
* 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 =
(ColumnarTableSetOptions_hook_type **) find_rendezvous_variable(
COLUMNAR_SETOPTIONS_HOOK_SYM);
/* rendezvous variable registered during columnar initialization */
Assert(ColumnarTableSetOptions_hook_ptr != NULL);
Assert(*ColumnarTableSetOptions_hook_ptr != NULL);
**ColumnarTableSetOptions_hook_ptr = ColumnarTableSetOptionsHook;
PrevProcessUtility = (ProcessUtility_hook != NULL) ?
ProcessUtility_hook : standard_ProcessUtility;
ProcessUtility_hook = multi_ProcessUtility;
/*
* Acquire symbols for columnar functions that citus calls.
@ -670,6 +672,43 @@ RegisterCitusConfigVariables(void)
GUC_NO_SHOW_ALL,
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(
"citus.check_available_space_before_move",
gettext_noop("When enabled will check free disk space before a shard move"),
@ -1174,24 +1213,6 @@ RegisterCitusConfigVariables(void)
GUC_NO_SHOW_ALL,
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(
"citus.isolation_test_session_process_id",
NULL,
@ -1716,6 +1737,25 @@ RegisterCitusConfigVariables(void)
GUC_STANDARD,
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(
"citus.sort_returning",
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
* citus.hide_shards_from_app_name_prefixes holds a valid list of application_name
* ShowShardsForAppNamePrefixesCheckHook ensures that the
* citus.show_shards_for_app_name_prefixes holds a valid list of application_name
* values.
*/
static bool
HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, GucSource source)
ShowShardsForAppNamePrefixesCheckHook(char **newval, void **extra, GucSource source)
{
List *prefixList = NIL;
@ -2020,7 +2060,7 @@ HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, GucSource so
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);
return false;
}
@ -2031,12 +2071,12 @@ HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, GucSource so
/*
* HideShardsFromAppNamePrefixesAssignHook ensures changes to
* citus.hide_shards_from_app_name_prefixes are reflected in the decision
* ShowShardsForAppNamePrefixesAssignHook ensures changes to
* citus.show_shards_for_app_name_prefixes are reflected in the decision
* whether or not to show shards.
*/
static void
HideShardsFromAppNamePrefixesAssignHook(const char *newval, void *extra)
ShowShardsForAppNamePrefixesAssignHook(const char *newval, void *extra)
{
ResetHideShardsDecision();
}
@ -2050,6 +2090,7 @@ static void
ApplicationNameAssignHook(const char *newval, void *extra)
{
ResetHideShardsDecision();
ResetCitusBackendType();
OldApplicationNameAssignHook(newval, extra);
}
@ -2295,3 +2336,29 @@ IsSuperuser(char *roleName)
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
--#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$
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"
END IF;
END;
$check_columnar$;
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.
-- this is to reliably propagate some of the alter database commands that might be
-- supported.
INSERT INTO citus.pg_dist_object SELECT
'pg_catalog.pg_database'::regclass::oid AS oid,
(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"
DO $check_columnar$
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"
END IF;
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 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"

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"
DO $check_columnar$
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"
END IF;
END;
@ -29,6 +33,8 @@ $check_columnar$;
#include "udfs/get_missing_time_partition_ranges/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);
CREATE FUNCTION pg_catalog.citus_drop_all_shards(logicalrelid regclass,
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_prepare_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
--#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$
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"
END IF;
END;
$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"
DO $check_columnar$
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"
END IF;
END;

View File

@ -3,11 +3,16 @@
-- bump version to 10.2-4
--#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$
BEGIN
IF NOT EXISTS (select 1 from pg_extension where extname='citus_columnar') THEN
#include "../../columnar/sql/columnar--10.2-3--10.2-4.sql"
END IF;
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-3--10.2-4.sql"
END IF;
END;
$check_columnar$;
@ -15,3 +20,4 @@ $check_columnar$;
#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/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_cleanup_job_schema_cache();
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_repartition_cleanup(bigint);
-- bump version to 11.1-1 as version 'Z'
-- drop columnar objects if they exists in citus extension
-- If upgrading citus, the columnar objects are already being a part of the
-- citus extension, and must be detached so that they can be attached
-- to the citus_columnar extension.
DO $check_citus$
BEGIN
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_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus' and p.proname = 'columnar_handler'
) THEN
ALTER EXTENSION citus DROP SCHEMA columnar;
ALTER EXTENSION citus DROP SEQUENCE columnar.storageid_seq;
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_proc AS p ON (p.oid = d.objid)
WHERE e.extname='citus' and p.proname = 'columnar_handler'
) THEN
ALTER EXTENSION citus DROP SCHEMA columnar;
ALTER EXTENSION citus DROP SEQUENCE columnar.storageid_seq;
-- columnar tables
ALTER EXTENSION citus DROP TABLE columnar.options;
ALTER EXTENSION citus DROP TABLE columnar.stripe;
ALTER EXTENSION citus DROP TABLE columnar.chunk_group;
ALTER EXTENSION citus DROP TABLE columnar.chunk;
-- columnar tables
ALTER EXTENSION citus DROP TABLE columnar.options;
ALTER EXTENSION citus DROP TABLE columnar.stripe;
ALTER EXTENSION citus DROP TABLE columnar.chunk_group;
ALTER EXTENSION citus DROP TABLE columnar.chunk;
DO $proc$
BEGIN
-- columnar functions
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN
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$;
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;
-- functions under citus_internal for columnar
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.columnar_ensure_am_depends_catalog;
-- functions under citus_internal for columnar
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.columnar_ensure_am_depends_catalog;
END IF;
END IF;
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',
$$lock_relation_if_exists$$;
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;

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
-- 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 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"
DO $check_columnar$
BEGIN
IF NOT EXISTS (select 1 from pg_extension where extname='citus_columnar') THEN
#include "../../columnar/sql/columnar--9.5-1--10.0-1.sql"
END IF;
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--9.5-1--10.0-1.sql"
END IF;
END;
$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
--#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"
END IF;
END;
$check_columnar$;
-- 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;
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;
DO $proc$
BEGIN
-- columnar functions
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN
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 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;
ALTER EXTENSION citus ADD FUNCTION citus_internal.upgrade_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);
CREATE FUNCTION pg_catalog.citus_disable_node(nodename text, nodeport integer, force bool default false)
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, force bool)
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

@ -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