mirror of https://github.com/citusdata/citus.git
Compare commits
73 Commits
Author | SHA1 | Date |
---|---|---|
|
5ca792aef9 | |
|
096047bfbc | |
|
c51095c462 | |
|
857a770b86 | |
|
06e55df141 | |
|
06d6ffbb6e | |
|
2bb106508a | |
|
5f46f2e9f7 | |
|
ac7511de7d | |
|
0eee7fd9b8 | |
|
03a4305e06 | |
|
d397dd0dfe | |
|
9d05c30c13 | |
|
bd02bd2dda | |
|
b559ae5813 | |
|
a01e45f3df | |
|
0861c80c8b | |
|
de6373b842 | |
|
4345627480 | |
|
978d31f330 | |
|
4bcffce036 | |
|
7166901492 | |
|
8ef705012a | |
|
530aafd8ee | |
|
c440cbb643 | |
|
a64e135a36 | |
|
50e8638ede | |
|
b34b1ce06b | |
|
0d0dd0af1c | |
|
3227d6551e | |
|
d147d5d0c5 | |
|
4b5f749c23 | |
|
29c67c660d | |
|
6da2d41e00 | |
|
2d5560537b | |
|
8b0499c91a | |
|
513e073206 | |
|
4b5cb7e2b9 | |
|
97b453e679 | |
|
8c5035c0a5 | |
|
7c6784b1f4 | |
|
556f43f24a | |
|
909b72b027 | |
|
3f282c660b | |
|
73fd4f7ded | |
|
8229d4b7ee | |
|
0cf769c43a | |
|
591f2565cc | |
|
ddfcbfdca1 | |
|
16071fac1d | |
|
9c4e3329f6 | |
|
36f641c586 | |
|
5fe384329e | |
|
c20732142e | |
|
082a14656d | |
|
33dede5b75 | |
|
5e4c0e4bea | |
|
c2d9e88bf5 | |
|
88369b6b23 | |
|
b7a39a232d | |
|
e8b41d1e5b | |
|
b4a65b9c45 | |
|
6ca3478c8d | |
|
86df61cae8 | |
|
e20a6dcd78 | |
|
6eed51b75c | |
|
675ba65f22 | |
|
d611a50a80 | |
|
c5797030de | |
|
a74d991445 | |
|
cb9e510e40 | |
|
e336b92552 | |
|
4784d5579b |
|
@ -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
|
||||
|
@ -520,12 +520,6 @@ workflows:
|
|||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
- check-merge-to-enterprise:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- /release-[0-9]+\.[0-9]+.*/ # match with releaseX.Y.*
|
||||
|
||||
- build:
|
||||
name: build-13
|
||||
pg_major: 13
|
||||
|
@ -604,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
|
||||
|
@ -612,6 +612,81 @@ workflows:
|
|||
make: check-failure
|
||||
requires: [build-13]
|
||||
|
||||
- test-citus:
|
||||
name: 'test-13_check-enterprise'
|
||||
pg_major: 13
|
||||
image_tag: '<< pipeline.parameters.pg13_version >>'
|
||||
make: check-enterprise
|
||||
requires: [build-13]
|
||||
- test-citus:
|
||||
name: 'test-13_check-enterprise-isolation'
|
||||
pg_major: 13
|
||||
image_tag: '<< pipeline.parameters.pg13_version >>'
|
||||
make: check-enterprise-isolation
|
||||
requires: [build-13]
|
||||
- test-citus:
|
||||
name: 'test-13_check-enterprise-isolation-logicalrep-1'
|
||||
pg_major: 13
|
||||
image_tag: '<< pipeline.parameters.pg13_version >>'
|
||||
make: check-enterprise-isolation-logicalrep-1
|
||||
requires: [build-13]
|
||||
- test-citus:
|
||||
name: 'test-13_check-enterprise-isolation-logicalrep-2'
|
||||
pg_major: 13
|
||||
image_tag: '<< pipeline.parameters.pg13_version >>'
|
||||
make: check-enterprise-isolation-logicalrep-2
|
||||
requires: [build-13]
|
||||
- test-citus:
|
||||
name: 'test-13_check-enterprise-isolation-logicalrep-3'
|
||||
pg_major: 13
|
||||
image_tag: '<< pipeline.parameters.pg13_version >>'
|
||||
make: check-enterprise-isolation-logicalrep-3
|
||||
requires: [build-13]
|
||||
- test-citus:
|
||||
name: 'test-13_check-enterprise-failure'
|
||||
pg_major: 13
|
||||
image: citus/failtester
|
||||
image_tag: '<< pipeline.parameters.pg13_version >>'
|
||||
make: check-enterprise-failure
|
||||
requires: [build-13]
|
||||
|
||||
- test-citus:
|
||||
name: 'test-14_check-enterprise'
|
||||
pg_major: 14
|
||||
image_tag: '<< pipeline.parameters.pg14_version >>'
|
||||
make: check-enterprise
|
||||
requires: [build-14]
|
||||
- test-citus:
|
||||
name: 'test-14_check-enterprise-isolation'
|
||||
pg_major: 14
|
||||
image_tag: '<< pipeline.parameters.pg14_version >>'
|
||||
make: check-enterprise-isolation
|
||||
requires: [build-14]
|
||||
- test-citus:
|
||||
name: 'test-14_check-enterprise-isolation-logicalrep-1'
|
||||
pg_major: 14
|
||||
image_tag: '<< pipeline.parameters.pg14_version >>'
|
||||
make: check-enterprise-isolation-logicalrep-1
|
||||
requires: [build-14]
|
||||
- test-citus:
|
||||
name: 'test-14_check-enterprise-isolation-logicalrep-2'
|
||||
pg_major: 14
|
||||
image_tag: '<< pipeline.parameters.pg14_version >>'
|
||||
make: check-enterprise-isolation-logicalrep-2
|
||||
requires: [build-14]
|
||||
- test-citus:
|
||||
name: 'test-14_check-enterprise-isolation-logicalrep-3'
|
||||
pg_major: 14
|
||||
image_tag: '<< pipeline.parameters.pg14_version >>'
|
||||
make: check-enterprise-isolation-logicalrep-3
|
||||
requires: [build-14]
|
||||
- test-citus:
|
||||
name: 'test-14_check-enterprise-failure'
|
||||
pg_major: 14
|
||||
image: citus/failtester
|
||||
image_tag: '<< pipeline.parameters.pg14_version >>'
|
||||
make: check-enterprise-failure
|
||||
requires: [build-14]
|
||||
- test-citus:
|
||||
name: 'test-14_check-multi'
|
||||
pg_major: 14
|
||||
|
@ -678,6 +753,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
|
||||
|
|
121
CHANGELOG.md
121
CHANGELOG.md
|
@ -1,11 +1,58 @@
|
|||
### citus v11.0.0_beta (March 22, 2022) ###
|
||||
### citus v11.0.4 (July 13, 2022) ###
|
||||
|
||||
* Fixes a bug that prevents promoting read-replicas as primaries
|
||||
|
||||
### citus v11.0.3 (July 5, 2022) ###
|
||||
|
||||
* Fixes a bug that prevents adding local tables with materialized views to
|
||||
Citus metadata
|
||||
|
||||
* Fixes a bug that prevents using `COMPRESSION` and `CONSTRAINT` on a column
|
||||
|
||||
* Fixes upgrades to Citus 11 when there are no nodes in the metadata
|
||||
|
||||
### citus v11.0.2 (June 15, 2022) ###
|
||||
|
||||
* Drops support for PostgreSQL 12
|
||||
|
||||
* Open sources enterprise features, see the rest of changelog items
|
||||
|
||||
* Turns metadata syncing on by default
|
||||
|
||||
* Adds `citus_finalize_upgrade_to_citus11()` which is necessary to upgrade to
|
||||
Citus 11+ from earlier versions
|
||||
* Introduces `citus_finish_citus_upgrade()` procedure which is necessary to
|
||||
upgrade from earlier versions
|
||||
|
||||
* Open sources non-blocking shard moves/shard rebalancer
|
||||
(`citus.logical_replication_timeout`)
|
||||
|
||||
* Open sources propagation of `CREATE/DROP/ALTER ROLE` statements
|
||||
|
||||
* Open sources propagation of `GRANT` statements
|
||||
|
||||
* Open sources propagation of `CLUSTER` statements
|
||||
|
||||
* Open sources propagation of `ALTER DATABASE ... OWNER TO ...`
|
||||
|
||||
* Open sources optimization for `COPY` when loading `JSON` to avoid double
|
||||
parsing of the `JSON` object (`citus.skip_jsonb_validation_in_copy`)
|
||||
|
||||
* Open sources support for row level security
|
||||
|
||||
* Open sources support for `pg_dist_authinfo`, which allows storing different
|
||||
authentication options for different users, e.g. you can store
|
||||
passwords or certificates here.
|
||||
|
||||
* Open sources support for `pg_dist_poolinfo`, which allows using connection
|
||||
poolers in between coordinator and workers
|
||||
|
||||
* Open sources tracking distributed query execution times using
|
||||
citus_stat_statements (`citus.stat_statements_max`,
|
||||
`citus.stat_statements_purge_interval`,
|
||||
`citus.stat_statements_track`). This is disabled by default.
|
||||
|
||||
* Open sources tenant_isolation
|
||||
|
||||
* Open sources support for `sslkey` and `sslcert` in `citus.node_conninfo`
|
||||
|
||||
* Adds `citus.max_client_connections` GUC to limit non-Citus connections
|
||||
|
||||
|
@ -33,6 +80,8 @@
|
|||
|
||||
* Adds propagation for foreign server commands
|
||||
|
||||
* Adds propagation of `DOMAIN` objects
|
||||
|
||||
* Adds propagation of `TEXT SEARCH CONFIGURATION` objects
|
||||
|
||||
* Adds propagation of `TEXT SEARCH DICTIONARY` objects
|
||||
|
@ -41,6 +90,8 @@
|
|||
|
||||
* Adds support for `CREATE SCHEMA AUTHORIZATION` statements without schema name
|
||||
|
||||
* Adds support for `CREATE/DROP/ALTER VIEW` commands
|
||||
|
||||
* Adds support for `TRUNCATE` for foreign tables
|
||||
|
||||
* Adds support for adding local tables to metadata using
|
||||
|
@ -60,6 +111,12 @@
|
|||
|
||||
* Adds support for shard replication > 1 hash distributed tables on Citus MX
|
||||
|
||||
* Adds support for `LOCK` commands on distributed tables from worker nodes
|
||||
|
||||
* Adds support for `TABLESAMPLE`
|
||||
|
||||
* Adds support for propagating views when syncing Citus table metadata
|
||||
|
||||
* Improves handling of `IN`, `OUT` and `INOUT` parameters for functions
|
||||
|
||||
* Introduces `citus_backend_gpid()` UDF to get global pid of the current backend
|
||||
|
@ -79,12 +136,23 @@
|
|||
|
||||
* Introduces a new flag `force_delegation` in `create_distributed_function()`
|
||||
|
||||
* Introduces `run_command_on_coordinator` UDF
|
||||
|
||||
* Introduces `synchronous` option to `citus_disable_node()` UDF
|
||||
|
||||
* Introduces `citus_is_coordinator` UDF to check whether a node is the
|
||||
coordinator
|
||||
|
||||
* Allows adding a unique constraint with an index
|
||||
|
||||
* Allows `create_distributed_function()` on a function owned by an extension
|
||||
|
||||
* Allows creating distributed tables in sequential mode
|
||||
|
||||
* Allows disabling nodes when multiple failures happen
|
||||
|
||||
* Allows `lock_table_if_exits` to be called outside of a transaction blocks
|
||||
|
||||
* Adds support for pushing procedures with `OUT` arguments down to the worker
|
||||
nodes
|
||||
|
||||
|
@ -108,6 +176,8 @@
|
|||
|
||||
* `citus_shards_on_worker` shows all local shards regardless of `search_path`
|
||||
|
||||
* Enables distributed execution from `run_command_on_*` functions
|
||||
|
||||
* Deprecates inactive shard state, never marks any placement inactive
|
||||
|
||||
* Disables distributed & reference foreign tables
|
||||
|
@ -130,6 +200,20 @@
|
|||
* Avoids unnecessary errors for `ALTER STATISTICS IF EXISTS` when the statistics
|
||||
does not exist
|
||||
|
||||
* Fixes a bug that prevents dropping/altering indexes
|
||||
|
||||
* Fixes a bug that prevents non-client backends from accessing shards
|
||||
|
||||
* Fixes columnar freezing/wraparound bug
|
||||
|
||||
* Fixes `invalid read of size 1` memory error with `citus_add_node`
|
||||
|
||||
* Fixes schema name qualification for `ALTER/DROP SEQUENCE`
|
||||
|
||||
* Fixes schema name qualification for `ALTER/DROP STATISTICS`
|
||||
|
||||
* Fixes schema name qualification for `CREATE STATISTICS`
|
||||
|
||||
* Fixes a bug that causes columnar storage pages to have zero LSN
|
||||
|
||||
* Fixes a bug that causes issues while create dependencies from multiple
|
||||
|
@ -157,6 +241,26 @@
|
|||
|
||||
* Fixes a bug that could cause re-partition joins involving local shards to fail
|
||||
|
||||
* Fixes a bug that could cause false positive distributed deadlocks due to local
|
||||
execution
|
||||
|
||||
* Fixes a bug that could cause leaking files when materialized views are
|
||||
refreshed
|
||||
|
||||
* Fixes a bug that could cause unqualified `DROP DOMAIN IF EXISTS` to fail
|
||||
|
||||
* Fixes a bug that could cause wrong schema and ownership after
|
||||
`alter_distributed_table`
|
||||
|
||||
* 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 limits usage of sequences in non-int columns
|
||||
|
||||
* Fixes a bug that prevents `DROP SCHEMA CASCADE`
|
||||
|
@ -188,17 +292,28 @@
|
|||
|
||||
* Fixes naming issues of newly created partitioned indexes
|
||||
|
||||
* Honors `enable_metadata_sync` in node operations
|
||||
|
||||
* Improves nested execution checks and adds GUC to control
|
||||
(`citus.allow_nested_distributed_execution`)
|
||||
|
||||
* Improves self-deadlock prevention for `CREATE INDEX / REINDEX CONCURRENTLY`
|
||||
commands for builds using PG14 or higher
|
||||
|
||||
* Moves `pg_dist_object` to `pg_catalog` schema
|
||||
|
||||
* Parallelizes metadata syncing on node activation
|
||||
|
||||
* Partitions shards to be co-located with the parent shards
|
||||
|
||||
* Prevents Citus table functions from being called on shards
|
||||
|
||||
* Prevents creating distributed functions when there are out of sync nodes
|
||||
|
||||
* Prevents alter table functions from dropping extensions
|
||||
|
||||
* Refrains reading the metadata cache for all tables during upgrade
|
||||
|
||||
* Provides notice message for idempotent `create_distributed_function` calls
|
||||
|
||||
* Reinstates optimisation for uniform shard interval ranges
|
||||
|
|
20
Makefile
20
Makefile
|
@ -11,7 +11,7 @@ endif
|
|||
|
||||
include Makefile.global
|
||||
|
||||
all: extension
|
||||
all: extension pg_send_cancellation
|
||||
|
||||
# build extension
|
||||
extension: $(citus_top_builddir)/src/include/citus_version.h
|
||||
|
@ -30,14 +30,24 @@ clean-extension:
|
|||
clean-full:
|
||||
$(MAKE) -C src/backend/distributed/ clean-full
|
||||
.PHONY: extension install-extension clean-extension clean-full
|
||||
# Add to generic targets
|
||||
install: install-extension install-headers
|
||||
|
||||
install-downgrades:
|
||||
$(MAKE) -C src/backend/distributed/ install-downgrades
|
||||
install-all: install-headers
|
||||
install-all: install-headers install-pg_send_cancellation
|
||||
$(MAKE) -C src/backend/distributed/ install-all
|
||||
|
||||
clean: clean-extension
|
||||
# build citus_send_cancellation binary
|
||||
pg_send_cancellation:
|
||||
$(MAKE) -C src/bin/pg_send_cancellation/ all
|
||||
install-pg_send_cancellation: pg_send_cancellation
|
||||
$(MAKE) -C src/bin/pg_send_cancellation/ install
|
||||
clean-pg_send_cancellation:
|
||||
$(MAKE) -C src/bin/pg_send_cancellation/ clean
|
||||
.PHONY: pg_send_cancellation install-pg_send_cancellation clean-pg_send_cancellation
|
||||
|
||||
# Add to generic targets
|
||||
install: install-extension install-headers install-pg_send_cancellation
|
||||
clean: clean-extension clean-pg_send_cancellation
|
||||
|
||||
# apply or check style
|
||||
reindent:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for Citus 11.0devel.
|
||||
# Generated by GNU Autoconf 2.69 for Citus 11.0.4.
|
||||
#
|
||||
#
|
||||
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
|
||||
|
@ -579,8 +579,8 @@ MAKEFLAGS=
|
|||
# Identity of this package.
|
||||
PACKAGE_NAME='Citus'
|
||||
PACKAGE_TARNAME='citus'
|
||||
PACKAGE_VERSION='11.0devel'
|
||||
PACKAGE_STRING='Citus 11.0devel'
|
||||
PACKAGE_VERSION='11.0.4'
|
||||
PACKAGE_STRING='Citus 11.0.4'
|
||||
PACKAGE_BUGREPORT=''
|
||||
PACKAGE_URL=''
|
||||
|
||||
|
@ -1260,7 +1260,7 @@ if test "$ac_init_help" = "long"; then
|
|||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures Citus 11.0devel to adapt to many kinds of systems.
|
||||
\`configure' configures Citus 11.0.4 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
|
@ -1322,7 +1322,7 @@ fi
|
|||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of Citus 11.0devel:";;
|
||||
short | recursive ) echo "Configuration of Citus 11.0.4:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
|
@ -1425,7 +1425,7 @@ fi
|
|||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
Citus configure 11.0devel
|
||||
Citus configure 11.0.4
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
|
@ -1908,7 +1908,7 @@ cat >config.log <<_ACEOF
|
|||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by Citus $as_me 11.0devel, which was
|
||||
It was created by Citus $as_me 11.0.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
|
@ -5360,7 +5360,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
|||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by Citus $as_me 11.0devel, which was
|
||||
This file was extended by Citus $as_me 11.0.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
|
@ -5422,7 +5422,7 @@ _ACEOF
|
|||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
Citus config.status 11.0devel
|
||||
Citus config.status 11.0.4
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# everyone needing autoconf installed, the resulting files are checked
|
||||
# into the SCM.
|
||||
|
||||
AC_INIT([Citus], [11.0devel])
|
||||
AC_INIT([Citus], [11.0.4])
|
||||
AC_COPYRIGHT([Copyright (c) Citus Data, Inc.])
|
||||
|
||||
# we'll need sed and awk for some of the version commands
|
||||
|
|
|
@ -1033,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.
|
||||
*/
|
||||
|
@ -1049,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
|
||||
|
@ -1072,6 +1096,79 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
|
|||
{
|
||||
TruncateColumnar(rel, elevel);
|
||||
}
|
||||
|
||||
RelationOpenSmgr(rel);
|
||||
BlockNumber new_rel_pages = smgrnblocks(rel->rd_smgr, 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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
-- no changes needed
|
|
@ -0,0 +1 @@
|
|||
-- no changes needed
|
|
@ -22,7 +22,7 @@ SUBDIRS = . commands connection ddl deparser executor metadata operations planne
|
|||
# columnar modules
|
||||
SUBDIRS += ../columnar
|
||||
# enterprise modules
|
||||
SUBDIRS +=
|
||||
SUBDIRS += replication
|
||||
|
||||
# Symlinks are not copied over to the build directory if a separete build
|
||||
# directory is used during configure (such as on CI)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Citus extension
|
||||
comment = 'Citus distributed database'
|
||||
default_version = '11.0-1'
|
||||
default_version = '11.0-3'
|
||||
module_pathname = '$libdir/citus'
|
||||
relocatable = false
|
||||
schema = pg_catalog
|
||||
|
|
|
@ -12,8 +12,8 @@ they are often moved to files that are named after the command.
|
|||
| `create_distributed_table.c` | Implementation of UDF's for creating distributed tables |
|
||||
| `drop_distributed_table.c` | Implementation for dropping metadata for partitions of distributed tables |
|
||||
| `extension.c` | Implementation of `CREATE EXTENSION` commands for citus specific checks |
|
||||
| `foreign_constraint.c` | Implementation of helper functions for foreign key constraints |
|
||||
| `grant.c` | Placeholder for code granting users access to relations, implemented as enterprise feature |
|
||||
| `foreign_constraint.c` | Implementation of and helper functions for foreign key constraints |
|
||||
| `grant.c` | Implementation of `GRANT` commands for roles/users on relations |
|
||||
| `index.c` | Implementation of commands specific to indices on distributed tables |
|
||||
| `multi_copy.c` | Implementation of `COPY` command. There are multiple different copy modes which are described in detail below |
|
||||
| `policy.c` | Implementation of `CREATE\ALTER POLICY` commands. |
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -32,6 +32,8 @@
|
|||
#include "access/xact.h"
|
||||
#include "catalog/dependency.h"
|
||||
#include "catalog/pg_am.h"
|
||||
#include "catalog/pg_depend.h"
|
||||
#include "catalog/pg_rewrite_d.h"
|
||||
#include "columnar/columnar.h"
|
||||
#include "columnar/columnar_tableam.h"
|
||||
#include "commands/defrem.h"
|
||||
|
@ -193,7 +195,6 @@ static void EnsureTableNotPartition(Oid relationId);
|
|||
static TableConversionState * CreateTableConversion(TableConversionParameters *params);
|
||||
static void CreateDistributedTableLike(TableConversionState *con);
|
||||
static void CreateCitusTableLike(TableConversionState *con);
|
||||
static List * GetViewCreationCommandsOfTable(Oid relationId);
|
||||
static void ReplaceTable(Oid sourceId, Oid targetId, List *justBeforeDropCommands,
|
||||
bool suppressNoticeMessages);
|
||||
static bool HasAnyGeneratedStoredColumns(Oid relationId);
|
||||
|
@ -205,16 +206,25 @@ static char * CreateWorkerChangeSequenceDependencyCommand(char *sequenceSchemaNa
|
|||
char *sourceName,
|
||||
char *targetSchemaName,
|
||||
char *targetName);
|
||||
static void ErrorIfMatViewSizeExceedsTheLimit(Oid matViewOid);
|
||||
static char * CreateMaterializedViewDDLCommand(Oid matViewOid);
|
||||
static char * GetAccessMethodForMatViewIfExists(Oid viewOid);
|
||||
static bool WillRecreateForeignKeyToReferenceTable(Oid relationId,
|
||||
CascadeToColocatedOption cascadeOption);
|
||||
static void WarningsForDroppingForeignKeysWithDistributedTables(Oid relationId);
|
||||
static void ErrorIfUnsupportedCascadeObjects(Oid relationId);
|
||||
static bool DoesCascadeDropUnsupportedObject(Oid classId, Oid id, HTAB *nodeMap);
|
||||
|
||||
PG_FUNCTION_INFO_V1(undistribute_table);
|
||||
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;
|
||||
|
||||
/* controlled by GUC, in MB */
|
||||
int MaxMatViewSizeToAutoRecreate = 1024;
|
||||
|
||||
/*
|
||||
* undistribute_table gets a distributed table name and
|
||||
|
@ -385,6 +395,8 @@ UndistributeTable(TableConversionParameters *params)
|
|||
ErrorIfAnyPartitionRelationInvolvedInNonInheritedFKey(partitionList);
|
||||
}
|
||||
|
||||
ErrorIfUnsupportedCascadeObjects(params->relationId);
|
||||
|
||||
params->conversionType = UNDISTRIBUTE_TABLE;
|
||||
params->shardCountIsNull = true;
|
||||
TableConversionState *con = CreateTableConversion(params);
|
||||
|
@ -416,6 +428,8 @@ AlterDistributedTable(TableConversionParameters *params)
|
|||
EnsureTableNotPartition(params->relationId);
|
||||
EnsureHashDistributedTable(params->relationId);
|
||||
|
||||
ErrorIfUnsupportedCascadeObjects(params->relationId);
|
||||
|
||||
params->conversionType = ALTER_DISTRIBUTED_TABLE;
|
||||
TableConversionState *con = CreateTableConversion(params);
|
||||
CheckAlterDistributedTableConversionParameters(con);
|
||||
|
@ -472,6 +486,8 @@ AlterTableSetAccessMethod(TableConversionParameters *params)
|
|||
}
|
||||
}
|
||||
|
||||
ErrorIfUnsupportedCascadeObjects(params->relationId);
|
||||
|
||||
params->conversionType = ALTER_TABLE_SET_ACCESS_METHOD;
|
||||
params->shardCountIsNull = true;
|
||||
TableConversionState *con = CreateTableConversion(params);
|
||||
|
@ -503,10 +519,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.
|
||||
|
@ -535,6 +557,7 @@ ConvertTable(TableConversionState *con)
|
|||
* subgraph including itself, so return here.
|
||||
*/
|
||||
SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys);
|
||||
InTableTypeConversionFunctionCall = false;
|
||||
return NULL;
|
||||
}
|
||||
char *newAccessMethod = con->accessMethod ? con->accessMethod :
|
||||
|
@ -562,8 +585,9 @@ ConvertTable(TableConversionState *con)
|
|||
List *justBeforeDropCommands = NIL;
|
||||
List *attachPartitionCommands = NIL;
|
||||
|
||||
postLoadCommands = list_concat(postLoadCommands,
|
||||
GetViewCreationCommandsOfTable(con->relationId));
|
||||
postLoadCommands =
|
||||
list_concat(postLoadCommands,
|
||||
GetViewCreationTableDDLCommandsOfTable(con->relationId));
|
||||
|
||||
List *foreignKeyCommands = NIL;
|
||||
if (con->conversionType == ALTER_DISTRIBUTED_TABLE)
|
||||
|
@ -819,6 +843,7 @@ ConvertTable(TableConversionState *con)
|
|||
|
||||
SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys);
|
||||
|
||||
InTableTypeConversionFunctionCall = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1237,6 +1262,94 @@ CreateCitusTableLike(TableConversionState *con)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* ErrorIfUnsupportedCascadeObjects gets oid of a relation, finds the objects
|
||||
* that dropping this relation cascades into and errors if there are any extensions
|
||||
* that would be dropped.
|
||||
*/
|
||||
static void
|
||||
ErrorIfUnsupportedCascadeObjects(Oid relationId)
|
||||
{
|
||||
HASHCTL info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.keysize = sizeof(Oid);
|
||||
info.entrysize = sizeof(Oid);
|
||||
info.hash = oid_hash;
|
||||
uint32 hashFlags = (HASH_ELEM | HASH_FUNCTION);
|
||||
HTAB *nodeMap = hash_create("object dependency map (oid)", 64, &info, hashFlags);
|
||||
|
||||
bool unsupportedObjectInDepGraph =
|
||||
DoesCascadeDropUnsupportedObject(RelationRelationId, relationId, nodeMap);
|
||||
|
||||
if (unsupportedObjectInDepGraph)
|
||||
{
|
||||
ereport(ERROR, (errmsg("cannot alter table because an extension depends on it")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DoesCascadeDropUnsupportedObject walks through the objects that depend on the
|
||||
* object with object id and returns true if it finds any unsupported objects.
|
||||
*
|
||||
* This function only checks extensions as unsupported objects.
|
||||
*
|
||||
* Extension dependency is different than the rest. If an object depends on an extension
|
||||
* dropping the object would drop the extension too.
|
||||
* So we check with IsObjectAddressOwnedByExtension function.
|
||||
*/
|
||||
static bool
|
||||
DoesCascadeDropUnsupportedObject(Oid classId, Oid objectId, HTAB *nodeMap)
|
||||
{
|
||||
bool found = false;
|
||||
hash_search(nodeMap, &objectId, HASH_ENTER, &found);
|
||||
|
||||
if (found)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectAddress objectAddress = { 0 };
|
||||
ObjectAddressSet(objectAddress, classId, objectId);
|
||||
|
||||
if (IsObjectAddressOwnedByExtension(&objectAddress, NULL))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Oid targetObjectClassId = classId;
|
||||
Oid targetObjectId = objectId;
|
||||
List *dependencyTupleList = GetPgDependTuplesForDependingObjects(targetObjectClassId,
|
||||
targetObjectId);
|
||||
|
||||
HeapTuple depTup = NULL;
|
||||
foreach_ptr(depTup, dependencyTupleList)
|
||||
{
|
||||
Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
|
||||
|
||||
Oid dependingOid = InvalidOid;
|
||||
Oid dependingClassId = InvalidOid;
|
||||
|
||||
if (pg_depend->classid == RewriteRelationId)
|
||||
{
|
||||
dependingOid = GetDependingView(pg_depend);
|
||||
dependingClassId = RelationRelationId;
|
||||
}
|
||||
else
|
||||
{
|
||||
dependingOid = pg_depend->objid;
|
||||
dependingClassId = pg_depend->classid;
|
||||
}
|
||||
|
||||
if (DoesCascadeDropUnsupportedObject(dependingClassId, dependingOid, nodeMap))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetViewCreationCommandsOfTable takes a table oid generates the CREATE VIEW
|
||||
* commands for views that depend to the given table. This includes the views
|
||||
|
@ -1246,46 +1359,152 @@ List *
|
|||
GetViewCreationCommandsOfTable(Oid relationId)
|
||||
{
|
||||
List *views = GetDependingViews(relationId);
|
||||
|
||||
List *commands = NIL;
|
||||
|
||||
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 ");
|
||||
ErrorIfMatViewSizeExceedsTheLimit(viewOid);
|
||||
|
||||
char *matViewCreateCommands = CreateMaterializedViewDDLCommand(viewOid);
|
||||
appendStringInfoString(query, matViewCreateCommands);
|
||||
}
|
||||
else
|
||||
{
|
||||
char *viewCreateCommand = CreateViewDDLCommand(viewOid);
|
||||
appendStringInfoString(query, viewCreateCommand);
|
||||
}
|
||||
|
||||
appendStringInfo(query, "VIEW %s ", qualifiedViewName);
|
||||
char *alterViewCommmand = AlterViewOwnerCommand(viewOid);
|
||||
appendStringInfoString(query, alterViewCommmand);
|
||||
|
||||
if (accessMethodName)
|
||||
{
|
||||
appendStringInfo(query, "USING %s ", accessMethodName);
|
||||
}
|
||||
|
||||
appendStringInfo(query, "AS %s", viewDefinition);
|
||||
|
||||
commands = lappend(commands, makeTableDDLCommandString(query->data));
|
||||
commands = lappend(commands, query->data);
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetViewCreationTableDDLCommandsOfTable is the same as GetViewCreationCommandsOfTable,
|
||||
* but the returned list includes objects of TableDDLCommand's, not strings.
|
||||
*/
|
||||
List *
|
||||
GetViewCreationTableDDLCommandsOfTable(Oid relationId)
|
||||
{
|
||||
List *commands = GetViewCreationCommandsOfTable(relationId);
|
||||
List *tableDDLCommands = NIL;
|
||||
|
||||
char *command = NULL;
|
||||
foreach_ptr(command, commands)
|
||||
{
|
||||
tableDDLCommands = lappend(tableDDLCommands, makeTableDDLCommandString(command));
|
||||
}
|
||||
|
||||
return tableDDLCommands;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ErrorIfMatViewSizeExceedsTheLimit takes the oid of a materialized view and errors
|
||||
* out if the size of the matview exceeds the limit set by the GUC
|
||||
* citus.max_matview_size_to_auto_recreate.
|
||||
*/
|
||||
static void
|
||||
ErrorIfMatViewSizeExceedsTheLimit(Oid matViewOid)
|
||||
{
|
||||
if (MaxMatViewSizeToAutoRecreate >= 0)
|
||||
{
|
||||
/* if it's below 0, it means the user has removed the limit */
|
||||
Datum relSizeDatum = DirectFunctionCall1(pg_total_relation_size,
|
||||
ObjectIdGetDatum(matViewOid));
|
||||
uint64 matViewSize = DatumGetInt64(relSizeDatum);
|
||||
|
||||
/* convert from MB to bytes */
|
||||
uint64 limitSizeInBytes = MaxMatViewSizeToAutoRecreate * 1024L * 1024L;
|
||||
|
||||
if (matViewSize > limitSizeInBytes)
|
||||
{
|
||||
ereport(ERROR, (errmsg("size of the materialized view %s exceeds "
|
||||
"citus.max_matview_size_to_auto_recreate "
|
||||
"(currently %d MB)", get_rel_name(matViewOid),
|
||||
MaxMatViewSizeToAutoRecreate),
|
||||
errdetail("Citus restricts automatically recreating "
|
||||
"materialized views that are larger than the "
|
||||
"limit, because it could take too long."),
|
||||
errhint(
|
||||
"Consider increasing the size limit by setting "
|
||||
"citus.max_matview_size_to_auto_recreate; "
|
||||
"or you can remove the limit by setting it to -1")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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.
|
||||
|
@ -1789,6 +2008,19 @@ ExecuteQueryViaSPI(char *query, int SPIOK)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecuteAndLogQueryViaSPI is a wrapper around ExecuteQueryViaSPI, that logs
|
||||
* the query to be executed, with the given log level.
|
||||
*/
|
||||
void
|
||||
ExecuteAndLogQueryViaSPI(char *query, int SPIOK, int logLevel)
|
||||
{
|
||||
ereport(logLevel, (errmsg("executing \"%s\"", query)));
|
||||
|
||||
ExecuteQueryViaSPI(query, SPIOK);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SwitchToSequentialAndLocalExecutionIfRelationNameTooLong generates the longest shard name
|
||||
* on the shards of a distributed table, and if exceeds the limit switches to sequential and
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "distributed/reference_table_utils.h"
|
||||
#include "distributed/relation_access_tracking.h"
|
||||
#include "distributed/worker_protocol.h"
|
||||
#include "executor/spi.h"
|
||||
#include "miscadmin.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/lsyscache.h"
|
||||
|
@ -512,6 +513,51 @@ ExecuteCascadeOperationForRelationIdList(List *relationIdList,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecuteAndLogUtilityCommandListInTableTypeConversionViaSPI is a wrapper function
|
||||
* around ExecuteAndLogQueryViaSPI, that executes view creation commands
|
||||
* with the flag InTableTypeConversionFunctionCall set to true.
|
||||
*/
|
||||
void
|
||||
ExecuteAndLogUtilityCommandListInTableTypeConversionViaSPI(List *utilityCommandList)
|
||||
{
|
||||
bool oldValue = InTableTypeConversionFunctionCall;
|
||||
InTableTypeConversionFunctionCall = true;
|
||||
|
||||
MemoryContext savedMemoryContext = CurrentMemoryContext;
|
||||
PG_TRY();
|
||||
{
|
||||
char *utilityCommand = NULL;
|
||||
foreach_ptr(utilityCommand, utilityCommandList)
|
||||
{
|
||||
/*
|
||||
* CREATE MATERIALIZED VIEW commands need to be parsed/transformed,
|
||||
* which SPI does for us.
|
||||
*/
|
||||
ExecuteAndLogQueryViaSPI(utilityCommand, SPI_OK_UTILITY, DEBUG1);
|
||||
}
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
InTableTypeConversionFunctionCall = oldValue;
|
||||
MemoryContextSwitchTo(savedMemoryContext);
|
||||
|
||||
ErrorData *errorData = CopyErrorData();
|
||||
FlushErrorState();
|
||||
|
||||
if (errorData->elevel != ERROR)
|
||||
{
|
||||
PG_RE_THROW();
|
||||
}
|
||||
|
||||
ThrowErrorData(errorData);
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
InTableTypeConversionFunctionCall = oldValue;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecuteAndLogUtilityCommandList takes a list of utility commands and calls
|
||||
* ExecuteAndLogUtilityCommand function for each of them.
|
||||
|
|
|
@ -81,7 +81,9 @@ static char * GetRenameShardTriggerCommand(Oid shardRelationId, char *triggerNam
|
|||
uint64 shardId);
|
||||
static void DropRelationTruncateTriggers(Oid relationId);
|
||||
static char * GetDropTriggerCommand(Oid relationId, char *triggerName);
|
||||
static void DropViewsOnTable(Oid relationId);
|
||||
static List * GetRenameStatsCommandList(List *statsOidList, uint64 shardId);
|
||||
static List * ReversedOidList(List *oidList);
|
||||
static void AppendExplicitIndexIdsToList(Form_pg_index indexForm,
|
||||
List **explicitIndexIdList,
|
||||
int flags);
|
||||
|
@ -328,6 +330,7 @@ CreateCitusLocalTable(Oid relationId, bool cascadeViaForeignKeys, bool autoConve
|
|||
EnsureReferenceTablesExistOnAllNodes();
|
||||
|
||||
List *shellTableDDLEvents = GetShellTableDDLEventsForCitusLocalTable(relationId);
|
||||
List *tableViewCreationCommands = GetViewCreationCommandsOfTable(relationId);
|
||||
|
||||
char *relationName = get_rel_name(relationId);
|
||||
Oid relationSchemaId = get_rel_namespace(relationId);
|
||||
|
@ -342,6 +345,12 @@ CreateCitusLocalTable(Oid relationId, bool cascadeViaForeignKeys, bool autoConve
|
|||
*/
|
||||
ExecuteAndLogUtilityCommandList(shellTableDDLEvents);
|
||||
|
||||
/*
|
||||
* Execute the view creation commands with the shell table.
|
||||
* Views will be distributed via FinalizeCitusLocalTableCreation below.
|
||||
*/
|
||||
ExecuteAndLogUtilityCommandListInTableTypeConversionViaSPI(tableViewCreationCommands);
|
||||
|
||||
/*
|
||||
* Set shellRelationId as the relation with relationId now points
|
||||
* to the shard relation.
|
||||
|
@ -699,6 +708,9 @@ ConvertLocalTableToShard(Oid relationId)
|
|||
*/
|
||||
DropRelationTruncateTriggers(relationId);
|
||||
|
||||
/* drop views that depend on the shard table */
|
||||
DropViewsOnTable(relationId);
|
||||
|
||||
/*
|
||||
* We create INSERT|DELETE|UPDATE triggers on shard relation too.
|
||||
* This is because citus prevents postgres executor to fire those
|
||||
|
@ -1019,6 +1031,55 @@ GetDropTriggerCommand(Oid relationId, char *triggerName)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* DropViewsOnTable drops the views that depend on the given relation.
|
||||
*/
|
||||
static void
|
||||
DropViewsOnTable(Oid relationId)
|
||||
{
|
||||
List *views = GetDependingViews(relationId);
|
||||
|
||||
/*
|
||||
* GetDependingViews returns views in the dependency order. We should drop views
|
||||
* in the reversed order since dropping views can cascade to other views below.
|
||||
*/
|
||||
List *reverseOrderedViews = ReversedOidList(views);
|
||||
|
||||
Oid viewId = InvalidOid;
|
||||
foreach_oid(viewId, reverseOrderedViews)
|
||||
{
|
||||
char *viewName = get_rel_name(viewId);
|
||||
char *schemaName = get_namespace_name(get_rel_namespace(viewId));
|
||||
char *qualifiedViewName = quote_qualified_identifier(schemaName, viewName);
|
||||
|
||||
StringInfo dropCommand = makeStringInfo();
|
||||
appendStringInfo(dropCommand, "DROP %sVIEW IF EXISTS %s",
|
||||
get_rel_relkind(viewId) == RELKIND_MATVIEW ? "MATERIALIZED " :
|
||||
"",
|
||||
qualifiedViewName);
|
||||
|
||||
ExecuteAndLogUtilityCommand(dropCommand->data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ReversedOidList takes a list of oids and returns the reverse ordered version of it.
|
||||
*/
|
||||
static List *
|
||||
ReversedOidList(List *oidList)
|
||||
{
|
||||
List *reversed = NIL;
|
||||
Oid oid = InvalidOid;
|
||||
foreach_oid(oid, oidList)
|
||||
{
|
||||
reversed = lcons_oid(oid, reversed);
|
||||
}
|
||||
|
||||
return reversed;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetExplicitIndexOidList returns a list of index oids defined "explicitly"
|
||||
* on the relation with relationId by the "CREATE INDEX" commands. That means,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -10,41 +10,105 @@
|
|||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "distributed/pg_version_constants.h"
|
||||
|
||||
#include "commands/defrem.h"
|
||||
|
||||
#include "catalog/namespace.h"
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/metadata_cache.h"
|
||||
|
||||
|
||||
/* placeholder for PreprocessClusterStmt */
|
||||
static bool IsClusterStmtVerbose_compat(ClusterStmt *clusterStmt);
|
||||
|
||||
/*
|
||||
* PreprocessClusterStmt first determines whether a given cluster statement involves
|
||||
* a distributed table. If so (and if it is supported, i.e. no verbose), it
|
||||
* creates a DDLJob to encapsulate information needed during the worker node
|
||||
* portion of DDL execution before returning that DDLJob in a List. If no
|
||||
* distributed table is involved, this function returns NIL.
|
||||
*/
|
||||
List *
|
||||
PreprocessClusterStmt(Node *node, const char *clusterCommand,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
ClusterStmt *clusterStmt = castNode(ClusterStmt, node);
|
||||
bool showPropagationWarning = false;
|
||||
bool missingOK = false;
|
||||
DDLJob *ddlJob = NULL;
|
||||
|
||||
/* CLUSTER all */
|
||||
if (clusterStmt->relation == NULL)
|
||||
{
|
||||
showPropagationWarning = true;
|
||||
ereport(WARNING, (errmsg("not propagating CLUSTER command to worker nodes"),
|
||||
errhint("Provide a specific table in order to CLUSTER "
|
||||
"distributed tables.")));
|
||||
|
||||
return NIL;
|
||||
}
|
||||
else
|
||||
|
||||
/* PostgreSQL uses access exclusive lock for CLUSTER command */
|
||||
Oid relationId = RangeVarGetRelid(clusterStmt->relation, AccessExclusiveLock,
|
||||
missingOK);
|
||||
|
||||
/*
|
||||
* If the table does not exist, don't do anything here to allow PostgreSQL
|
||||
* to throw the appropriate error or notice message later.
|
||||
*/
|
||||
if (!OidIsValid(relationId))
|
||||
{
|
||||
bool missingOK = false;
|
||||
return NIL;
|
||||
}
|
||||
|
||||
Oid relationId = RangeVarGetRelid(clusterStmt->relation, AccessShareLock,
|
||||
missingOK);
|
||||
/* we have no planning to do unless the table is distributed */
|
||||
bool isCitusRelation = IsCitusTable(relationId);
|
||||
if (!isCitusRelation)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
if (OidIsValid(relationId))
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
if (IsClusterStmtVerbose_compat(clusterStmt))
|
||||
#else
|
||||
if (clusterStmt->verbose)
|
||||
#endif
|
||||
{
|
||||
ereport(ERROR, (errmsg("cannot run CLUSTER command"),
|
||||
errdetail("VERBOSE option is currently unsupported "
|
||||
"for distributed tables.")));
|
||||
}
|
||||
|
||||
ddlJob = palloc0(sizeof(DDLJob));
|
||||
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
|
||||
ddlJob->metadataSyncCommand = clusterCommand;
|
||||
ddlJob->taskList = DDLTaskList(relationId, clusterCommand);
|
||||
|
||||
return list_make1(ddlJob);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IsClusterStmtVerbose_compat returns true if the given statement
|
||||
* is a cluster statement with verbose option.
|
||||
*/
|
||||
static bool
|
||||
IsClusterStmtVerbose_compat(ClusterStmt *clusterStmt)
|
||||
{
|
||||
#if PG_VERSION_NUM < PG_VERSION_14
|
||||
if (clusterStmt->options & CLUOPT_VERBOSE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
DefElem *opt = NULL;
|
||||
foreach_ptr(opt, clusterStmt->params)
|
||||
{
|
||||
if (strcmp(opt->defname, "verbose") == 0)
|
||||
{
|
||||
showPropagationWarning = IsCitusTable(relationId);
|
||||
return defGetBoolean(opt);
|
||||
}
|
||||
}
|
||||
|
||||
if (showPropagationWarning)
|
||||
{
|
||||
ereport(WARNING, (errmsg("not propagating CLUSTER command to worker nodes")));
|
||||
}
|
||||
|
||||
return NIL;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -36,9 +36,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
|
||||
|
@ -162,267 +159,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.
|
||||
|
@ -544,89 +280,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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -33,76 +33,7 @@ static AlterOwnerStmt * RecreateAlterDatabaseOwnerStmt(Oid databaseOid);
|
|||
static Oid get_database_owner(Oid db_oid);
|
||||
|
||||
/* controlled via GUC */
|
||||
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;
|
||||
}
|
||||
bool EnableAlterDatabaseOwner = true;
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -358,6 +387,15 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
|
|||
return CreateCollationDDLsIdempotent(dependency->objectId);
|
||||
}
|
||||
|
||||
case OCLASS_CONSTRAINT:
|
||||
{
|
||||
/*
|
||||
* Constraints can only be reached by domains, they resolve functions.
|
||||
* Constraints themself are recreated by the domain recreation.
|
||||
*/
|
||||
return NIL;
|
||||
}
|
||||
|
||||
case OCLASS_DATABASE:
|
||||
{
|
||||
List *databaseDDLCommands = NIL;
|
||||
|
@ -374,7 +412,10 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
|
|||
|
||||
case OCLASS_PROC:
|
||||
{
|
||||
return CreateFunctionDDLCommandsIdempotent(dependency);
|
||||
List *DDLCommands = CreateFunctionDDLCommandsIdempotent(dependency);
|
||||
List *grantDDLCommands = GrantOnFunctionDDLCommands(dependency->objectId);
|
||||
DDLCommands = list_concat(DDLCommands, grantDDLCommands);
|
||||
return DDLCommands;
|
||||
}
|
||||
|
||||
case OCLASS_ROLE:
|
||||
|
@ -417,7 +458,13 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
|
|||
|
||||
case OCLASS_FOREIGN_SERVER:
|
||||
{
|
||||
return GetForeignServerCreateDDLCommand(dependency->objectId);
|
||||
Oid serverId = dependency->objectId;
|
||||
|
||||
List *DDLCommands = GetForeignServerCreateDDLCommand(serverId);
|
||||
List *grantDDLCommands = GrantOnForeignServerDDLCommands(serverId);
|
||||
DDLCommands = list_concat(DDLCommands, grantDDLCommands);
|
||||
|
||||
return DDLCommands;
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
|
@ -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,16 +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 = 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,
|
||||
};
|
||||
|
@ -177,10 +197,34 @@ 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,
|
||||
.preprocess = PreprocessCreatePolicyStmt,
|
||||
.preprocess = NULL,
|
||||
.postprocess = PostprocessCreatePolicyStmt,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Any_CreateRole = {
|
||||
.deparse = DeparseCreateRoleStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessCreateRoleStmt,
|
||||
.postprocess = NULL,
|
||||
.address = CreateRoleStmtObjectAddress,
|
||||
.markDistributed = true,
|
||||
};
|
||||
static DistributeObjectOps Any_DropRole = {
|
||||
.deparse = DeparseDropRoleStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessDropRoleStmt,
|
||||
.postprocess = NULL,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
|
@ -188,8 +232,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,
|
||||
};
|
||||
|
@ -225,6 +270,14 @@ static DistributeObjectOps Any_Grant = {
|
|||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Any_GrantRole = {
|
||||
.deparse = DeparseGrantRoleStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessGrantRoleStmt,
|
||||
.postprocess = PostprocessGrantRoleStmt,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Any_Index = {
|
||||
.deparse = NULL,
|
||||
.qualify = NULL,
|
||||
|
@ -260,31 +313,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,
|
||||
|
@ -292,19 +348,76 @@ 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 = PreprocessAlterDistributedObjectStmt,
|
||||
.postprocess = PostprocessAlterDistributedObjectStmt,
|
||||
.objectType = OBJECT_DOMAIN,
|
||||
.address = AlterDomainStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Domain_AlterObjectSchema = {
|
||||
.deparse = DeparseAlterDomainSchemaStmt,
|
||||
.qualify = QualifyAlterDomainSchemaStmt,
|
||||
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||
.postprocess = PostprocessAlterDistributedObjectStmt,
|
||||
.objectType = OBJECT_DOMAIN,
|
||||
.address = AlterTypeSchemaStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Domain_AlterOwner = {
|
||||
.deparse = DeparseAlterDomainOwnerStmt,
|
||||
.qualify = QualifyAlterDomainOwnerStmt,
|
||||
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||
.postprocess = PostprocessAlterDistributedObjectStmt,
|
||||
.objectType = OBJECT_DOMAIN,
|
||||
.address = AlterDomainOwnerStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Domain_Drop = {
|
||||
.deparse = DeparseDropDomainStmt,
|
||||
.qualify = QualifyDropDomainStmt,
|
||||
.preprocess = PreprocessDropDistributedObjectStmt,
|
||||
.postprocess = NULL,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Domain_Rename = {
|
||||
.deparse = DeparseRenameDomainStmt,
|
||||
.qualify = QualifyRenameDomainStmt,
|
||||
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||
.postprocess = NULL,
|
||||
.objectType = OBJECT_DOMAIN,
|
||||
.address = RenameDomainStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
|
||||
static DistributeObjectOps Domain_RenameConstraint = {
|
||||
.deparse = DeparseDomainRenameConstraintStmt,
|
||||
.qualify = QualifyDomainRenameConstraintStmt,
|
||||
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||
.postprocess = NULL,
|
||||
.objectType = OBJECT_DOMAIN,
|
||||
.address = DomainRenameConstraintStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Extension_AlterObjectSchema = {
|
||||
.deparse = DeparseAlterExtensionSchemaStmt,
|
||||
.qualify = NULL,
|
||||
|
@ -321,10 +434,26 @@ static DistributeObjectOps Extension_Drop = {
|
|||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps FDW_Grant = {
|
||||
.deparse = DeparseGrantOnFDWStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessGrantOnFDWStmt,
|
||||
.postprocess = NULL,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps ForeignServer_Drop = {
|
||||
.deparse = DeparseDropForeignServerStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessDropForeignServerStmt,
|
||||
.preprocess = PreprocessDropDistributedObjectStmt,
|
||||
.postprocess = NULL,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps ForeignServer_Grant = {
|
||||
.deparse = DeparseGrantOnForeignServerStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessGrantOnForeignServerStmt,
|
||||
.postprocess = NULL,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
|
@ -332,16 +461,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,
|
||||
};
|
||||
|
@ -364,23 +495,41 @@ 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 Function_Grant = {
|
||||
.deparse = DeparseGrantOnFunctionStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessGrantOnFunctionStmt,
|
||||
.postprocess = PostprocessGrantOnFunctionStmt,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps View_Drop = {
|
||||
.deparse = DeparseDropViewStmt,
|
||||
.qualify = QualifyDropViewStmt,
|
||||
.preprocess = PreprocessDropViewStmt,
|
||||
.postprocess = NULL,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
|
@ -388,8 +537,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,
|
||||
};
|
||||
|
@ -428,32 +578,43 @@ 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,
|
||||
};
|
||||
static DistributeObjectOps Procedure_Grant = {
|
||||
.deparse = DeparseGrantOnFunctionStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessGrantOnFunctionStmt,
|
||||
.postprocess = PostprocessGrantOnFunctionStmt,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Procedure_Rename = {
|
||||
.deparse = DeparseRenameFunctionStmt,
|
||||
.qualify = QualifyRenameFunctionStmt,
|
||||
.preprocess = PreprocessRenameFunctionStmt,
|
||||
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||
.postprocess = NULL,
|
||||
.objectType = OBJECT_FUNCTION,
|
||||
.address = RenameFunctionStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
|
@ -491,12 +652,20 @@ static DistributeObjectOps Sequence_AlterOwner = {
|
|||
};
|
||||
static DistributeObjectOps Sequence_Drop = {
|
||||
.deparse = DeparseDropSequenceStmt,
|
||||
.qualify = NULL,
|
||||
.qualify = QualifyDropSequenceStmt,
|
||||
.preprocess = PreprocessDropSequenceStmt,
|
||||
.postprocess = NULL,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Sequence_Grant = {
|
||||
.deparse = DeparseGrantOnSequenceStmt,
|
||||
.qualify = QualifyGrantOnSequenceStmt,
|
||||
.preprocess = PreprocessGrantOnSequenceStmt,
|
||||
.postprocess = PostprocessGrantOnSequenceStmt,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Sequence_Rename = {
|
||||
.deparse = DeparseRenameSequenceStmt,
|
||||
.qualify = QualifyRenameSequenceStmt,
|
||||
|
@ -508,32 +677,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,
|
||||
};
|
||||
|
@ -541,14 +714,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,
|
||||
|
@ -556,40 +730,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,
|
||||
};
|
||||
|
@ -597,14 +776,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,
|
||||
|
@ -612,8 +792,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,
|
||||
};
|
||||
|
@ -628,32 +809,43 @@ 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,
|
||||
};
|
||||
static DistributeObjectOps Routine_Grant = {
|
||||
.deparse = DeparseGrantOnFunctionStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessGrantOnFunctionStmt,
|
||||
.postprocess = PostprocessGrantOnFunctionStmt,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Routine_Rename = {
|
||||
.deparse = DeparseRenameFunctionStmt,
|
||||
.qualify = QualifyRenameFunctionStmt,
|
||||
.preprocess = PreprocessRenameFunctionStmt,
|
||||
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||
.postprocess = NULL,
|
||||
.objectType = OBJECT_FUNCTION,
|
||||
.address = RenameFunctionStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
|
@ -676,8 +868,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,
|
||||
};
|
||||
|
@ -750,31 +943,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,
|
||||
|
@ -790,11 +1018,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,
|
||||
|
@ -815,6 +1059,11 @@ GetDistributeObjectOps(Node *node)
|
|||
{
|
||||
switch (nodeTag(node))
|
||||
{
|
||||
case T_AlterDomainStmt:
|
||||
{
|
||||
return &Domain_Alter;
|
||||
}
|
||||
|
||||
case T_AlterEnumStmt:
|
||||
{
|
||||
return &Any_AlterEnum;
|
||||
|
@ -887,6 +1136,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Collation_AlterObjectSchema;
|
||||
}
|
||||
|
||||
case OBJECT_DOMAIN:
|
||||
{
|
||||
return &Domain_AlterObjectSchema;
|
||||
}
|
||||
|
||||
case OBJECT_EXTENSION:
|
||||
{
|
||||
return &Extension_AlterObjectSchema;
|
||||
|
@ -938,6 +1192,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Type_AlterObjectSchema;
|
||||
}
|
||||
|
||||
case OBJECT_VIEW:
|
||||
{
|
||||
return &View_AlterObjectSchema;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return &NoDistributeOps;
|
||||
|
@ -965,6 +1224,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Database_AlterOwner;
|
||||
}
|
||||
|
||||
case OBJECT_DOMAIN:
|
||||
{
|
||||
return &Domain_AlterOwner;
|
||||
}
|
||||
|
||||
case OBJECT_FOREIGN_SERVER:
|
||||
{
|
||||
return &ForeignServer_AlterOwner;
|
||||
|
@ -1069,6 +1333,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Sequence_AlterOwner;
|
||||
}
|
||||
|
||||
case OBJECT_VIEW:
|
||||
{
|
||||
return &View_AlterView;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return &NoDistributeOps;
|
||||
|
@ -1123,6 +1392,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Any_CompositeType;
|
||||
}
|
||||
|
||||
case T_CreateDomainStmt:
|
||||
{
|
||||
return &Any_CreateDomain;
|
||||
}
|
||||
|
||||
case T_CreateEnumStmt:
|
||||
{
|
||||
return &Any_CreateEnum;
|
||||
|
@ -1148,6 +1422,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Any_CreatePolicy;
|
||||
}
|
||||
|
||||
case T_CreateRoleStmt:
|
||||
{
|
||||
return &Any_CreateRole;
|
||||
}
|
||||
|
||||
case T_CreateSchemaStmt:
|
||||
{
|
||||
return &Any_CreateSchema;
|
||||
|
@ -1195,6 +1474,11 @@ GetDistributeObjectOps(Node *node)
|
|||
}
|
||||
}
|
||||
|
||||
case T_DropRoleStmt:
|
||||
{
|
||||
return &Any_DropRole;
|
||||
}
|
||||
|
||||
case T_DropStmt:
|
||||
{
|
||||
DropStmt *stmt = castNode(DropStmt, node);
|
||||
|
@ -1210,6 +1494,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Collation_Drop;
|
||||
}
|
||||
|
||||
case OBJECT_DOMAIN:
|
||||
{
|
||||
return &Domain_Drop;
|
||||
}
|
||||
|
||||
case OBJECT_EXTENSION:
|
||||
{
|
||||
return &Extension_Drop;
|
||||
|
@ -1285,6 +1574,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Trigger_Drop;
|
||||
}
|
||||
|
||||
case OBJECT_VIEW:
|
||||
{
|
||||
return &View_Drop;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return &NoDistributeOps;
|
||||
|
@ -1292,6 +1586,11 @@ GetDistributeObjectOps(Node *node)
|
|||
}
|
||||
}
|
||||
|
||||
case T_GrantRoleStmt:
|
||||
{
|
||||
return &Any_GrantRole;
|
||||
}
|
||||
|
||||
case T_GrantStmt:
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
|
@ -1302,6 +1601,36 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Schema_Grant;
|
||||
}
|
||||
|
||||
case OBJECT_SEQUENCE:
|
||||
{
|
||||
return &Sequence_Grant;
|
||||
}
|
||||
|
||||
case OBJECT_FDW:
|
||||
{
|
||||
return &FDW_Grant;
|
||||
}
|
||||
|
||||
case OBJECT_FOREIGN_SERVER:
|
||||
{
|
||||
return &ForeignServer_Grant;
|
||||
}
|
||||
|
||||
case OBJECT_FUNCTION:
|
||||
{
|
||||
return &Function_Grant;
|
||||
}
|
||||
|
||||
case OBJECT_PROCEDURE:
|
||||
{
|
||||
return &Procedure_Grant;
|
||||
}
|
||||
|
||||
case OBJECT_ROUTINE:
|
||||
{
|
||||
return &Routine_Grant;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return &Any_Grant;
|
||||
|
@ -1314,6 +1643,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Any_Index;
|
||||
}
|
||||
|
||||
case T_ViewStmt:
|
||||
{
|
||||
return &Any_View;
|
||||
}
|
||||
|
||||
case T_ReindexStmt:
|
||||
{
|
||||
return &Any_Reindex;
|
||||
|
@ -1339,6 +1673,16 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Collation_Rename;
|
||||
}
|
||||
|
||||
case OBJECT_DOMAIN:
|
||||
{
|
||||
return &Domain_Rename;
|
||||
}
|
||||
|
||||
case OBJECT_DOMCONSTRAINT:
|
||||
{
|
||||
return &Domain_RenameConstraint;
|
||||
}
|
||||
|
||||
case OBJECT_FOREIGN_SERVER:
|
||||
{
|
||||
return &ForeignServer_Rename;
|
||||
|
@ -1394,6 +1738,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;
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* domain.c
|
||||
* Hooks to handle the creation, altering and removal of domains.
|
||||
* These hooks are responsible for duplicating the changes to the
|
||||
* workers nodes.
|
||||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/genam.h"
|
||||
#include "catalog/pg_collation.h"
|
||||
#include "catalog/pg_constraint.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "tcop/utility.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/regproc.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/metadata_sync.h"
|
||||
#include "distributed/metadata_utility.h"
|
||||
#include "distributed/multi_executor.h"
|
||||
#include "distributed/worker_create_or_replace.h"
|
||||
#include "distributed/worker_transaction.h"
|
||||
|
||||
|
||||
static CollateClause * MakeCollateClauseFromOid(Oid collationOid);
|
||||
static ObjectAddress GetDomainAddressByName(TypeName *domainName, bool missing_ok);
|
||||
|
||||
/*
|
||||
* GetDomainAddressByName returns the ObjectAddress of the domain identified by
|
||||
* domainName. When missing_ok is true the object id part of the ObjectAddress can be
|
||||
* InvalidOid. When missing_ok is false this function will raise an error instead when the
|
||||
* domain can't be found.
|
||||
*/
|
||||
static ObjectAddress
|
||||
GetDomainAddressByName(TypeName *domainName, bool missing_ok)
|
||||
{
|
||||
ObjectAddress address = { 0 };
|
||||
Oid domainOid = LookupTypeNameOid(NULL, domainName, missing_ok);
|
||||
ObjectAddressSet(address, TypeRelationId, domainOid);
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RecreateDomainStmt returns a CreateDomainStmt pointer where the statement represents
|
||||
* the creation of the domain to recreate the domain on a different postgres node based on
|
||||
* the current representation in the local catalog.
|
||||
*/
|
||||
CreateDomainStmt *
|
||||
RecreateDomainStmt(Oid domainOid)
|
||||
{
|
||||
CreateDomainStmt *stmt = makeNode(CreateDomainStmt);
|
||||
stmt->domainname = stringToQualifiedNameList(format_type_be_qualified(domainOid));
|
||||
|
||||
HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domainOid));
|
||||
if (!HeapTupleIsValid(tup))
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for type %u", domainOid);
|
||||
}
|
||||
|
||||
Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
|
||||
if (typTup->typtype != TYPTYPE_DOMAIN)
|
||||
{
|
||||
elog(ERROR, "type is not a domain type");
|
||||
}
|
||||
|
||||
stmt->typeName = makeTypeNameFromOid(typTup->typbasetype, typTup->typtypmod);
|
||||
|
||||
if (OidIsValid(typTup->typcollation))
|
||||
{
|
||||
stmt->collClause = MakeCollateClauseFromOid(typTup->typcollation);
|
||||
}
|
||||
|
||||
/*
|
||||
* typdefault and typdefaultbin are potentially null, so don't try to
|
||||
* access 'em as struct fields. Must do it the hard way with
|
||||
* SysCacheGetAttr.
|
||||
*/
|
||||
bool isNull = false;
|
||||
Datum typeDefaultDatum = SysCacheGetAttr(TYPEOID,
|
||||
tup,
|
||||
Anum_pg_type_typdefaultbin,
|
||||
&isNull);
|
||||
if (!isNull)
|
||||
{
|
||||
/* when not null there is default value which we should add as a constraint */
|
||||
Constraint *constraint = makeNode(Constraint);
|
||||
constraint->contype = CONSTR_DEFAULT;
|
||||
constraint->cooked_expr = TextDatumGetCString(typeDefaultDatum);
|
||||
|
||||
stmt->constraints = lappend(stmt->constraints, constraint);
|
||||
}
|
||||
|
||||
/* NOT NULL constraints are non-named on the actual type */
|
||||
if (typTup->typnotnull)
|
||||
{
|
||||
Constraint *constraint = makeNode(Constraint);
|
||||
constraint->contype = CONSTR_NOTNULL;
|
||||
|
||||
stmt->constraints = lappend(stmt->constraints, constraint);
|
||||
}
|
||||
|
||||
/* lookup and look all constraints to add them to the CreateDomainStmt */
|
||||
Relation conRel = table_open(ConstraintRelationId, AccessShareLock);
|
||||
|
||||
/* Look for CHECK Constraints on this domain */
|
||||
ScanKeyData key[1];
|
||||
ScanKeyInit(&key[0],
|
||||
Anum_pg_constraint_contypid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(domainOid));
|
||||
|
||||
SysScanDesc scan = systable_beginscan(conRel, ConstraintTypidIndexId, true, NULL, 1,
|
||||
key);
|
||||
|
||||
HeapTuple conTup = NULL;
|
||||
while (HeapTupleIsValid(conTup = systable_getnext(scan)))
|
||||
{
|
||||
Form_pg_constraint c = (Form_pg_constraint) GETSTRUCT(conTup);
|
||||
|
||||
if (c->contype != CONSTRAINT_CHECK)
|
||||
{
|
||||
/* Ignore non-CHECK constraints, shouldn't be any */
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* We create a constraint, completely ignoring c->convalidated because we can't
|
||||
* create a domain with an invalidated constraint. Once a constraint is added to
|
||||
* a domain -even non valid-, all new data is validated. Meaning, creating a
|
||||
* domain with a non-valid constraint doesn't make any sense.
|
||||
*
|
||||
* Given it will be too hard to defer the creation of a constraint till we
|
||||
* validate the constraint on the coordinator we will simply create the
|
||||
* non-validated constraint to ad hear to validating all new data.
|
||||
*
|
||||
* An edgecase here would be when moving existing data, that hasn't been validated
|
||||
* before to an other node. This behaviour is consistent with sending it to an
|
||||
* already existing node (that has the constraint created but not validated) and a
|
||||
* new node.
|
||||
*/
|
||||
|
||||
Constraint *constraint = makeNode(Constraint);
|
||||
|
||||
constraint->conname = pstrdup(NameStr(c->conname));
|
||||
constraint->contype = CONSTR_CHECK; /* we only come here with check constraints */
|
||||
|
||||
/* Not expecting conbin to be NULL, but we'll test for it anyway */
|
||||
Datum conbin = heap_getattr(conTup, Anum_pg_constraint_conbin, conRel->rd_att,
|
||||
&isNull);
|
||||
if (isNull)
|
||||
{
|
||||
elog(ERROR, "domain \"%s\" constraint \"%s\" has NULL conbin",
|
||||
NameStr(typTup->typname), NameStr(c->conname));
|
||||
}
|
||||
|
||||
/*
|
||||
* The conbin containes the cooked expression from when the constraint was
|
||||
* inserted into the catalog. We store it here for the deparser to distinguish
|
||||
* between cooked expressions and raw expressions.
|
||||
*
|
||||
* There is no supported way to go from a cooked expression to a raw expression.
|
||||
*/
|
||||
constraint->cooked_expr = TextDatumGetCString(conbin);
|
||||
|
||||
stmt->constraints = lappend(stmt->constraints, constraint);
|
||||
}
|
||||
|
||||
systable_endscan(scan);
|
||||
table_close(conRel, NoLock);
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
|
||||
QualifyTreeNode((Node *) stmt);
|
||||
|
||||
return stmt;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* MakeCollateClauseFromOid returns a CollateClause describing the COLLATE segment of a
|
||||
* CREATE DOMAIN statement based on the Oid of the collation used for the domain.
|
||||
*/
|
||||
static CollateClause *
|
||||
MakeCollateClauseFromOid(Oid collationOid)
|
||||
{
|
||||
CollateClause *collateClause = makeNode(CollateClause);
|
||||
|
||||
ObjectAddress collateAddress = { 0 };
|
||||
ObjectAddressSet(collateAddress, CollationRelationId, collationOid);
|
||||
|
||||
List *objName = NIL;
|
||||
List *objArgs = NIL;
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_14
|
||||
getObjectIdentityParts(&collateAddress, &objName, &objArgs, false);
|
||||
#else
|
||||
getObjectIdentityParts(&collateAddress, &objName, &objArgs);
|
||||
#endif
|
||||
|
||||
char *name = NULL;
|
||||
foreach_ptr(name, objName)
|
||||
{
|
||||
collateClause->collname = lappend(collateClause->collname, makeString(name));
|
||||
}
|
||||
|
||||
collateClause->location = -1;
|
||||
|
||||
return collateClause;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateDomainStmtObjectAddress returns the ObjectAddress of the domain that would be
|
||||
* created by the statement. When missing_ok is false the function will raise an error if
|
||||
* the domain cannot be found in the local catalog.
|
||||
*/
|
||||
ObjectAddress
|
||||
CreateDomainStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
CreateDomainStmt *stmt = castNode(CreateDomainStmt, node);
|
||||
|
||||
TypeName *typeName = makeTypeNameFromNameList(stmt->domainname);
|
||||
Oid typeOid = LookupTypeNameOid(NULL, typeName, missing_ok);
|
||||
ObjectAddress address = { 0 };
|
||||
ObjectAddressSet(address, TypeRelationId, typeOid);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AlterDomainStmtObjectAddress returns the ObjectAddress of the domain being altered.
|
||||
* When missing_ok is false this function will raise an error when the domain is not
|
||||
* found.
|
||||
*/
|
||||
ObjectAddress
|
||||
AlterDomainStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
AlterDomainStmt *stmt = castNode(AlterDomainStmt, node);
|
||||
|
||||
TypeName *domainName = makeTypeNameFromNameList(stmt->typeName);
|
||||
return GetDomainAddressByName(domainName, missing_ok);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DomainRenameConstraintStmtObjectAddress returns the ObjectAddress of the domain for
|
||||
* which the constraint is being renamed. When missing_ok this function will raise an
|
||||
* error if the domain cannot be found.
|
||||
*/
|
||||
ObjectAddress
|
||||
DomainRenameConstraintStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
|
||||
TypeName *domainName = makeTypeNameFromNameList(castNode(List, stmt->object));
|
||||
return GetDomainAddressByName(domainName, missing_ok);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AlterDomainOwnerStmtObjectAddress returns the ObjectAddress for which the owner is
|
||||
* being changed. When missing_ok is false this function will raise an error if the domain
|
||||
* cannot be found.
|
||||
*/
|
||||
ObjectAddress
|
||||
AlterDomainOwnerStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_DOMAIN);
|
||||
|
||||
TypeName *domainName = makeTypeNameFromNameList(castNode(List, stmt->object));
|
||||
return GetDomainAddressByName(domainName, missing_ok);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RenameDomainStmtObjectAddress returns the ObjectAddress of the domain being renamed.
|
||||
* When missing_ok is false this function will raise an error when the domain cannot be
|
||||
* found.
|
||||
*/
|
||||
ObjectAddress
|
||||
RenameDomainStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
Assert(stmt->renameType == OBJECT_DOMAIN);
|
||||
|
||||
TypeName *domainName = makeTypeNameFromNameList(castNode(List, stmt->object));
|
||||
return GetDomainAddressByName(domainName, missing_ok);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_constraint_typid returns the contypid of a constraint. This field is only set for
|
||||
* constraints on domain types. Returns InvalidOid if conoid is an invalid constraint, as
|
||||
* well as for constraints that are not on domain types.
|
||||
*/
|
||||
Oid
|
||||
get_constraint_typid(Oid conoid)
|
||||
{
|
||||
HeapTuple tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(conoid));
|
||||
if (HeapTupleIsValid(tp))
|
||||
{
|
||||
Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
|
||||
Oid result = contup->contypid;
|
||||
ReleaseSysCache(tp);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return InvalidOid;
|
||||
}
|
||||
}
|
|
@ -10,8 +10,12 @@
|
|||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/genam.h"
|
||||
#include "citus_version.h"
|
||||
#include "catalog/dependency.h"
|
||||
#include "catalog/pg_depend.h"
|
||||
#include "catalog/pg_extension_d.h"
|
||||
#include "catalog/pg_foreign_data_wrapper.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "commands/extension.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
|
@ -26,9 +30,12 @@
|
|||
#include "distributed/multi_executor.h"
|
||||
#include "distributed/relation_access_tracking.h"
|
||||
#include "distributed/transaction_management.h"
|
||||
#include "foreign/foreign.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
/* Local functions forward declarations for helper functions */
|
||||
|
@ -37,9 +44,11 @@ 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 ShouldPropagateExtensionCommand(Node *parseTree);
|
||||
static bool IsAlterExtensionSetSchemaCitus(Node *parseTree);
|
||||
static Node * RecreateExtensionStmt(Oid extensionOid);
|
||||
static List * GenerateGrantCommandsOnExtesionDependentFDWs(Oid extensionId);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -510,26 +519,78 @@ MarkExistingObjectDependenciesDistributedIfSupported()
|
|||
Oid citusTableId = InvalidOid;
|
||||
foreach_oid(citusTableId, citusTableIdList)
|
||||
{
|
||||
ObjectAddress tableAddress = { 0 };
|
||||
ObjectAddressSet(tableAddress, RelationRelationId, citusTableId);
|
||||
|
||||
if (ShouldSyncTableMetadata(citusTableId))
|
||||
if (!ShouldMarkRelationDistributed(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 (!ShouldMarkRelationDistributed(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 +626,40 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessAlterExtensionContentsStmt issues a notice. It does not propagate.
|
||||
*/
|
||||
|
@ -732,6 +827,12 @@ CreateExtensionDDLCommand(const ObjectAddress *extensionAddress)
|
|||
|
||||
List *ddlCommands = list_make1((void *) ddlCommand);
|
||||
|
||||
/* any privilege granted on FDWs that belong to the extension should be included */
|
||||
List *FDWGrants =
|
||||
GenerateGrantCommandsOnExtesionDependentFDWs(extensionAddress->objectId);
|
||||
|
||||
ddlCommands = list_concat(ddlCommands, FDWGrants);
|
||||
|
||||
return ddlCommands;
|
||||
}
|
||||
|
||||
|
@ -790,6 +891,88 @@ RecreateExtensionStmt(Oid extensionOid)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateGrantCommandsOnExtesionDependentFDWs returns a list of commands that GRANTs
|
||||
* the privileges on FDWs that are depending on the given extension.
|
||||
*/
|
||||
static List *
|
||||
GenerateGrantCommandsOnExtesionDependentFDWs(Oid extensionId)
|
||||
{
|
||||
List *commands = NIL;
|
||||
List *FDWOids = GetDependentFDWsToExtension(extensionId);
|
||||
|
||||
Oid FDWOid = InvalidOid;
|
||||
foreach_oid(FDWOid, FDWOids)
|
||||
{
|
||||
Acl *aclEntry = GetPrivilegesForFDW(FDWOid);
|
||||
|
||||
if (aclEntry == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AclItem *privileges = ACL_DAT(aclEntry);
|
||||
int numberOfPrivsGranted = ACL_NUM(aclEntry);
|
||||
|
||||
for (int i = 0; i < numberOfPrivsGranted; i++)
|
||||
{
|
||||
commands = list_concat(commands,
|
||||
GenerateGrantOnFDWQueriesFromAclItem(FDWOid,
|
||||
&privileges[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetDependentFDWsToExtension gets an extension oid and returns the list of oids of FDWs
|
||||
* that are depending on the given extension.
|
||||
*/
|
||||
List *
|
||||
GetDependentFDWsToExtension(Oid extensionId)
|
||||
{
|
||||
List *extensionFDWs = NIL;
|
||||
ScanKeyData key[3];
|
||||
int scanKeyCount = 3;
|
||||
HeapTuple tup;
|
||||
|
||||
Relation pgDepend = table_open(DependRelationId, AccessShareLock);
|
||||
|
||||
ScanKeyInit(&key[0],
|
||||
Anum_pg_depend_refclassid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(ExtensionRelationId));
|
||||
ScanKeyInit(&key[1],
|
||||
Anum_pg_depend_refobjid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(extensionId));
|
||||
ScanKeyInit(&key[2],
|
||||
Anum_pg_depend_classid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(ForeignDataWrapperRelationId));
|
||||
|
||||
SysScanDesc scan = systable_beginscan(pgDepend, InvalidOid, false,
|
||||
NULL, scanKeyCount, key);
|
||||
|
||||
while (HeapTupleIsValid(tup = systable_getnext(scan)))
|
||||
{
|
||||
Form_pg_depend pgDependEntry = (Form_pg_depend) GETSTRUCT(tup);
|
||||
|
||||
if (pgDependEntry->deptype == DEPENDENCY_EXTENSION)
|
||||
{
|
||||
extensionFDWs = lappend_oid(extensionFDWs, pgDependEntry->objid);
|
||||
}
|
||||
}
|
||||
|
||||
systable_endscan(scan);
|
||||
table_close(pgDepend, AccessShareLock);
|
||||
|
||||
return extensionFDWs;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AlterExtensionSchemaStmtObjectAddress returns the ObjectAddress of the extension that is
|
||||
* the subject of the AlterObjectSchemaStmt. Errors if missing_ok is false.
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* foreign_data_wrapper.c
|
||||
* Commands for FOREIGN DATA WRAPPER statements.
|
||||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "catalog/pg_foreign_data_wrapper.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/metadata_sync.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "foreign/foreign.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
static bool NameListHasFDWOwnedByDistributedExtension(List *FDWNames);
|
||||
static ObjectAddress GetObjectAddressByFDWName(char *FDWName, bool missing_ok);
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessGrantOnFDWStmt 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 to grant
|
||||
* on foreign data wrappers.
|
||||
*/
|
||||
List *
|
||||
PreprocessGrantOnFDWStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_FDW);
|
||||
|
||||
if (!NameListHasFDWOwnedByDistributedExtension(stmt->objects))
|
||||
{
|
||||
/*
|
||||
* We propagate granted privileges on a FDW only if it belongs to a distributed
|
||||
* extension. For now, we skip for custom FDWs, as most of the users prefer
|
||||
* extension FDWs.
|
||||
*/
|
||||
return NIL;
|
||||
}
|
||||
|
||||
if (list_length(stmt->objects) > 1)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot grant on FDW with other FDWs"),
|
||||
errhint("Try granting on each object in separate commands")));
|
||||
}
|
||||
|
||||
if (!ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
Assert(list_length(stmt->objects) == 1);
|
||||
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* NameListHasFDWOwnedByDistributedExtension takes a namelist of FDWs and returns true
|
||||
* if at least one of them depends on a distributed extension. Returns false otherwise.
|
||||
*/
|
||||
static bool
|
||||
NameListHasFDWOwnedByDistributedExtension(List *FDWNames)
|
||||
{
|
||||
Value *FDWValue = NULL;
|
||||
foreach_ptr(FDWValue, FDWNames)
|
||||
{
|
||||
/* captures the extension address during lookup */
|
||||
ObjectAddress extensionAddress = { 0 };
|
||||
ObjectAddress FDWAddress = GetObjectAddressByFDWName(strVal(FDWValue), false);
|
||||
|
||||
if (IsObjectAddressOwnedByExtension(&FDWAddress, &extensionAddress))
|
||||
{
|
||||
if (IsObjectDistributed(&extensionAddress))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetObjectAddressByFDWName takes a FDW name and returns the object address.
|
||||
*/
|
||||
static ObjectAddress
|
||||
GetObjectAddressByFDWName(char *FDWName, bool missing_ok)
|
||||
{
|
||||
ForeignDataWrapper *FDW = GetForeignDataWrapperByName(FDWName, missing_ok);
|
||||
Oid FDWId = FDW->fdwid;
|
||||
ObjectAddress address = { 0 };
|
||||
ObjectAddressSet(address, ForeignDataWrapperRelationId, FDWId);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetPrivilegesForFDW takes a FDW object id and returns the privileges granted
|
||||
* on that FDW as a Acl object. Returns NULL if there is no privilege granted.
|
||||
*/
|
||||
Acl *
|
||||
GetPrivilegesForFDW(Oid FDWOid)
|
||||
{
|
||||
HeapTuple fdwtup = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(FDWOid));
|
||||
|
||||
bool isNull = true;
|
||||
Datum aclDatum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, fdwtup,
|
||||
Anum_pg_foreign_data_wrapper_fdwacl, &isNull);
|
||||
if (isNull)
|
||||
{
|
||||
ReleaseSysCache(fdwtup);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Acl *aclEntry = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
ReleaseSysCache(fdwtup);
|
||||
|
||||
return aclEntry;
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
#include "miscadmin.h"
|
||||
|
||||
#include "catalog/pg_foreign_server.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
|
@ -23,240 +24,14 @@
|
|||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "nodes/primnodes.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
static char * GetForeignServerAlterOwnerCommand(Oid serverId);
|
||||
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 +49,88 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessGrantOnForeignServerStmt 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 to grant
|
||||
* on servers.
|
||||
*/
|
||||
List *
|
||||
PreprocessGrantOnForeignServerStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == 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 grant on distributed server with other servers"),
|
||||
errhint("Try granting on each object in separate commands")));
|
||||
}
|
||||
|
||||
if (!ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
Assert(list_length(stmt->objects) == 1);
|
||||
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
@ -303,14 +160,37 @@ GetForeignServerCreateDDLCommand(Oid serverId)
|
|||
Node *stmt = RecreateForeignServerStmt(serverId);
|
||||
|
||||
/* capture ddl command for the create statement */
|
||||
const char *ddlCommand = DeparseTreeNode(stmt);
|
||||
const char *createCommand = DeparseTreeNode(stmt);
|
||||
const char *alterOwnerCommand = GetForeignServerAlterOwnerCommand(serverId);
|
||||
|
||||
List *ddlCommands = list_make1((void *) ddlCommand);
|
||||
List *ddlCommands = list_make2((void *) createCommand,
|
||||
(void *) alterOwnerCommand);
|
||||
|
||||
return ddlCommands;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetForeignServerAlterOwnerCommand returns "ALTER SERVER .. OWNER TO .." statement
|
||||
* for the specified foreign server.
|
||||
*/
|
||||
static char *
|
||||
GetForeignServerAlterOwnerCommand(Oid serverId)
|
||||
{
|
||||
ForeignServer *server = GetForeignServer(serverId);
|
||||
Oid ownerId = server->owner;
|
||||
char *ownerName = GetUserNameFromId(ownerId, false);
|
||||
|
||||
StringInfo alterCommand = makeStringInfo();
|
||||
|
||||
appendStringInfo(alterCommand, "ALTER SERVER %s OWNER TO %s;",
|
||||
quote_identifier(server->servername),
|
||||
quote_identifier(ownerName));
|
||||
|
||||
return alterCommand->data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RecreateForeignServerStmt returns a parsetree for a CREATE SERVER statement
|
||||
* that would recreate the given server on a new node.
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
* We currently support replicating function definitions on the
|
||||
* coordinator in all the worker nodes in the form of
|
||||
*
|
||||
* CREATE OR REPLACE FUNCTION ... queries.
|
||||
* CREATE OR REPLACE FUNCTION ... queries and
|
||||
* GRANT ... ON FUNCTION queries
|
||||
*
|
||||
*
|
||||
* ALTER or DROP operations are not yet propagated.
|
||||
*
|
||||
|
@ -104,6 +106,7 @@ static void DistributeFunctionColocatedWithDistributedTable(RegProcedure funcOid
|
|||
functionAddress);
|
||||
static void DistributeFunctionColocatedWithReferenceTable(const
|
||||
ObjectAddress *functionAddress);
|
||||
static List * FilterDistributedFunctions(GrantStmt *grantStmt);
|
||||
|
||||
static void EnsureExtensionFunctionCanBeDistributed(const ObjectAddress functionAddress,
|
||||
const ObjectAddress extensionAddress,
|
||||
|
@ -239,8 +242,17 @@ create_distributed_function(PG_FUNCTION_ARGS)
|
|||
const char *createFunctionSQL = GetFunctionDDLCommand(funcOid, true);
|
||||
const char *alterFunctionOwnerSQL = GetFunctionAlterOwnerCommand(funcOid);
|
||||
initStringInfo(&ddlCommand);
|
||||
appendStringInfo(&ddlCommand, "%s;%s;%s;%s", DISABLE_METADATA_SYNC,
|
||||
createFunctionSQL, alterFunctionOwnerSQL, ENABLE_METADATA_SYNC);
|
||||
appendStringInfo(&ddlCommand, "%s;%s;%s", DISABLE_METADATA_SYNC,
|
||||
createFunctionSQL, alterFunctionOwnerSQL);
|
||||
List *grantDDLCommands = GrantOnFunctionDDLCommands(funcOid);
|
||||
char *grantOnFunctionSQL = NULL;
|
||||
foreach_ptr(grantOnFunctionSQL, grantDDLCommands)
|
||||
{
|
||||
appendStringInfo(&ddlCommand, ";%s", grantOnFunctionSQL);
|
||||
}
|
||||
|
||||
appendStringInfo(&ddlCommand, ";%s", ENABLE_METADATA_SYNC);
|
||||
|
||||
SendCommandToWorkersAsUser(NON_COORDINATOR_NODES, CurrentUserName(),
|
||||
ddlCommand.data);
|
||||
}
|
||||
|
@ -1263,12 +1275,7 @@ ShouldPropagateCreateFunction(CreateFunctionStmt *stmt)
|
|||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the create command is a part of a multi-statement transaction that is not in
|
||||
* sequential mode, don't propagate.
|
||||
*/
|
||||
if (IsMultiStatementTransaction() &&
|
||||
MultiShardConnectionType != SEQUENTIAL_CONNECTION)
|
||||
if (!ShouldPropagateCreateInCoordinatedTransction())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -1507,234 +1514,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
|
||||
|
@ -1808,30 +1587,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
|
||||
|
@ -2177,3 +1932,162 @@ EnsureExtensionFunctionCanBeDistributed(const ObjectAddress functionAddress,
|
|||
|
||||
EnsureDependenciesExistOnAllNodes(&functionAddress);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessGrantOnFunctionStmt 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 to grant
|
||||
* on distributed functions, procedures, routines.
|
||||
*/
|
||||
List *
|
||||
PreprocessGrantOnFunctionStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(isFunction(stmt->objtype));
|
||||
|
||||
List *distributedFunctions = FilterDistributedFunctions(stmt);
|
||||
|
||||
if (list_length(distributedFunctions) == 0 || !ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
List *grantFunctionList = NIL;
|
||||
ObjectAddress *functionAddress = NULL;
|
||||
foreach_ptr(functionAddress, distributedFunctions)
|
||||
{
|
||||
ObjectWithArgs *distFunction = ObjectWithArgsFromOid(
|
||||
functionAddress->objectId);
|
||||
grantFunctionList = lappend(grantFunctionList, distFunction);
|
||||
}
|
||||
|
||||
List *originalObjects = stmt->objects;
|
||||
GrantTargetType originalTargtype = stmt->targtype;
|
||||
|
||||
stmt->objects = grantFunctionList;
|
||||
stmt->targtype = ACL_TARGET_OBJECT;
|
||||
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
stmt->objects = originalObjects;
|
||||
stmt->targtype = originalTargtype;
|
||||
|
||||
List *commandList = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commandList);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PostprocessGrantOnFunctionStmt makes sure dependencies of each
|
||||
* distributed function in the statement exist on all nodes
|
||||
*/
|
||||
List *
|
||||
PostprocessGrantOnFunctionStmt(Node *node, const char *queryString)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
|
||||
List *distributedFunctions = FilterDistributedFunctions(stmt);
|
||||
|
||||
if (list_length(distributedFunctions) == 0)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
ObjectAddress *functionAddress = NULL;
|
||||
foreach_ptr(functionAddress, distributedFunctions)
|
||||
{
|
||||
EnsureDependenciesExistOnAllNodes(functionAddress);
|
||||
}
|
||||
return NIL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* FilterDistributedFunctions determines and returns a list of distributed functions
|
||||
* ObjectAddress-es from given grant statement.
|
||||
*/
|
||||
static List *
|
||||
FilterDistributedFunctions(GrantStmt *grantStmt)
|
||||
{
|
||||
List *grantFunctionList = NIL;
|
||||
|
||||
bool grantOnFunctionCommand = (grantStmt->targtype == ACL_TARGET_OBJECT &&
|
||||
isFunction(grantStmt->objtype));
|
||||
bool grantAllFunctionsOnSchemaCommand = (grantStmt->targtype ==
|
||||
ACL_TARGET_ALL_IN_SCHEMA &&
|
||||
isFunction(grantStmt->objtype));
|
||||
|
||||
/* we are only interested in function/procedure/routine level grants */
|
||||
if (!grantOnFunctionCommand && !grantAllFunctionsOnSchemaCommand)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
if (grantAllFunctionsOnSchemaCommand)
|
||||
{
|
||||
List *distributedFunctionList = DistributedFunctionList();
|
||||
ObjectAddress *distributedFunction = NULL;
|
||||
List *namespaceOidList = NIL;
|
||||
|
||||
/* iterate over all namespace names provided to get their oid's */
|
||||
Value *namespaceValue = NULL;
|
||||
foreach_ptr(namespaceValue, grantStmt->objects)
|
||||
{
|
||||
char *nspname = strVal(namespaceValue);
|
||||
bool missing_ok = false;
|
||||
Oid namespaceOid = get_namespace_oid(nspname, missing_ok);
|
||||
namespaceOidList = list_append_unique_oid(namespaceOidList, namespaceOid);
|
||||
}
|
||||
|
||||
/*
|
||||
* iterate over all distributed functions to filter the ones
|
||||
* that belong to one of the namespaces from above
|
||||
*/
|
||||
foreach_ptr(distributedFunction, distributedFunctionList)
|
||||
{
|
||||
Oid namespaceOid = get_func_namespace(distributedFunction->objectId);
|
||||
|
||||
/*
|
||||
* if this distributed function's schema is one of the schemas
|
||||
* specified in the GRANT .. ALL FUNCTIONS IN SCHEMA ..
|
||||
* add it to the list
|
||||
*/
|
||||
if (list_member_oid(namespaceOidList, namespaceOid))
|
||||
{
|
||||
grantFunctionList = lappend(grantFunctionList, distributedFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool missingOk = false;
|
||||
ObjectWithArgs *objectWithArgs = NULL;
|
||||
foreach_ptr(objectWithArgs, grantStmt->objects)
|
||||
{
|
||||
ObjectAddress *functionAddress = palloc0(sizeof(ObjectAddress));
|
||||
functionAddress->classId = ProcedureRelationId;
|
||||
functionAddress->objectId = LookupFuncWithArgs(grantStmt->objtype,
|
||||
objectWithArgs,
|
||||
missingOk);
|
||||
functionAddress->objectSubId = 0;
|
||||
|
||||
/*
|
||||
* if this function from GRANT .. ON FUNCTION .. is a distributed
|
||||
* function, add it to the list
|
||||
*/
|
||||
if (IsObjectDistributed(functionAddress))
|
||||
{
|
||||
grantFunctionList = lappend(grantFunctionList, functionAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
return grantFunctionList;
|
||||
}
|
||||
|
|
|
@ -8,13 +8,244 @@
|
|||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/metadata_cache.h"
|
||||
#include "distributed/version_compat.h"
|
||||
#include "lib/stringinfo.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "utils/lsyscache.h"
|
||||
|
||||
|
||||
/* placeholder for PreprocessGrantStmt */
|
||||
/* Local functions forward declarations for helper functions */
|
||||
static List * CollectGrantTableIdList(GrantStmt *grantStmt);
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessGrantStmt determines whether a given GRANT/REVOKE statement involves
|
||||
* a distributed table. If so, it creates DDLJobs to encapsulate information
|
||||
* needed during the worker node portion of DDL execution before returning the
|
||||
* DDLJobs in a List. If no distributed table is involved, this returns NIL.
|
||||
*
|
||||
* NB: So far column level privileges are not supported.
|
||||
*/
|
||||
List *
|
||||
PreprocessGrantStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
return NIL;
|
||||
GrantStmt *grantStmt = castNode(GrantStmt, node);
|
||||
StringInfoData privsString;
|
||||
StringInfoData granteesString;
|
||||
StringInfoData targetString;
|
||||
StringInfoData ddlString;
|
||||
ListCell *granteeCell = NULL;
|
||||
ListCell *tableListCell = NULL;
|
||||
bool isFirst = true;
|
||||
List *ddlJobs = NIL;
|
||||
|
||||
initStringInfo(&privsString);
|
||||
initStringInfo(&granteesString);
|
||||
initStringInfo(&targetString);
|
||||
initStringInfo(&ddlString);
|
||||
|
||||
/*
|
||||
* So far only table level grants are supported. Most other types of
|
||||
* grants aren't interesting anyway.
|
||||
*/
|
||||
if (grantStmt->objtype != OBJECT_TABLE)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
List *tableIdList = CollectGrantTableIdList(grantStmt);
|
||||
|
||||
/* nothing to do if there is no distributed table in the grant list */
|
||||
if (tableIdList == NIL)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
/* deparse the privileges */
|
||||
if (grantStmt->privileges == NIL)
|
||||
{
|
||||
appendStringInfo(&privsString, "ALL");
|
||||
}
|
||||
else
|
||||
{
|
||||
ListCell *privilegeCell = NULL;
|
||||
|
||||
isFirst = true;
|
||||
foreach(privilegeCell, grantStmt->privileges)
|
||||
{
|
||||
AccessPriv *priv = lfirst(privilegeCell);
|
||||
|
||||
if (!isFirst)
|
||||
{
|
||||
appendStringInfoString(&privsString, ", ");
|
||||
}
|
||||
isFirst = false;
|
||||
|
||||
if (priv->cols != NIL)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("grant/revoke on column list is currently "
|
||||
"unsupported")));
|
||||
}
|
||||
|
||||
Assert(priv->priv_name != NULL);
|
||||
|
||||
appendStringInfo(&privsString, "%s", priv->priv_name);
|
||||
}
|
||||
}
|
||||
|
||||
/* deparse the grantees */
|
||||
isFirst = true;
|
||||
foreach(granteeCell, grantStmt->grantees)
|
||||
{
|
||||
RoleSpec *spec = lfirst(granteeCell);
|
||||
|
||||
if (!isFirst)
|
||||
{
|
||||
appendStringInfoString(&granteesString, ", ");
|
||||
}
|
||||
isFirst = false;
|
||||
|
||||
appendStringInfoString(&granteesString, RoleSpecString(spec, true));
|
||||
}
|
||||
|
||||
/*
|
||||
* Deparse the target objects, and issue the deparsed statements to
|
||||
* workers, if applicable. That's so we easily can replicate statements
|
||||
* only to distributed relations.
|
||||
*/
|
||||
isFirst = true;
|
||||
foreach(tableListCell, tableIdList)
|
||||
{
|
||||
Oid relationId = lfirst_oid(tableListCell);
|
||||
const char *grantOption = "";
|
||||
|
||||
resetStringInfo(&targetString);
|
||||
appendStringInfo(&targetString, "%s", generate_relation_name(relationId, NIL));
|
||||
|
||||
if (grantStmt->is_grant)
|
||||
{
|
||||
if (grantStmt->grant_option)
|
||||
{
|
||||
grantOption = " WITH GRANT OPTION";
|
||||
}
|
||||
|
||||
appendStringInfo(&ddlString, "GRANT %s ON %s TO %s%s",
|
||||
privsString.data, targetString.data, granteesString.data,
|
||||
grantOption);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (grantStmt->grant_option)
|
||||
{
|
||||
grantOption = "GRANT OPTION FOR ";
|
||||
}
|
||||
|
||||
appendStringInfo(&ddlString, "REVOKE %s%s ON %s FROM %s",
|
||||
grantOption, privsString.data, targetString.data,
|
||||
granteesString.data);
|
||||
}
|
||||
|
||||
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
|
||||
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
|
||||
ddlJob->metadataSyncCommand = pstrdup(ddlString.data);
|
||||
ddlJob->taskList = NIL;
|
||||
if (IsCitusTable(relationId))
|
||||
{
|
||||
ddlJob->taskList = DDLTaskList(relationId, ddlString.data);
|
||||
}
|
||||
ddlJobs = lappend(ddlJobs, ddlJob);
|
||||
|
||||
resetStringInfo(&ddlString);
|
||||
}
|
||||
|
||||
return ddlJobs;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CollectGrantTableIdList determines and returns a list of distributed table
|
||||
* Oids from grant statement.
|
||||
* Grant statement may appear in two forms
|
||||
* 1 - grant on table:
|
||||
* each distributed table oid in grant object list is added to returned list.
|
||||
* 2 - grant all tables in schema:
|
||||
* Collect namespace oid list from grant statement
|
||||
* Add each distributed table oid in the target namespace list to the returned list.
|
||||
*/
|
||||
static List *
|
||||
CollectGrantTableIdList(GrantStmt *grantStmt)
|
||||
{
|
||||
List *grantTableList = NIL;
|
||||
|
||||
bool grantOnTableCommand = (grantStmt->targtype == ACL_TARGET_OBJECT &&
|
||||
grantStmt->objtype == OBJECT_TABLE);
|
||||
bool grantAllTablesOnSchemaCommand = (grantStmt->targtype ==
|
||||
ACL_TARGET_ALL_IN_SCHEMA &&
|
||||
grantStmt->objtype == OBJECT_TABLE);
|
||||
|
||||
/* we are only interested in table level grants */
|
||||
if (!grantOnTableCommand && !grantAllTablesOnSchemaCommand)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
if (grantAllTablesOnSchemaCommand)
|
||||
{
|
||||
List *citusTableIdList = CitusTableTypeIdList(ANY_CITUS_TABLE_TYPE);
|
||||
ListCell *citusTableIdCell = NULL;
|
||||
List *namespaceOidList = NIL;
|
||||
|
||||
ListCell *objectCell = NULL;
|
||||
foreach(objectCell, grantStmt->objects)
|
||||
{
|
||||
char *nspname = strVal(lfirst(objectCell));
|
||||
bool missing_ok = false;
|
||||
Oid namespaceOid = get_namespace_oid(nspname, missing_ok);
|
||||
Assert(namespaceOid != InvalidOid);
|
||||
namespaceOidList = list_append_unique_oid(namespaceOidList, namespaceOid);
|
||||
}
|
||||
|
||||
foreach(citusTableIdCell, citusTableIdList)
|
||||
{
|
||||
Oid relationId = lfirst_oid(citusTableIdCell);
|
||||
Oid namespaceOid = get_rel_namespace(relationId);
|
||||
if (list_member_oid(namespaceOidList, namespaceOid))
|
||||
{
|
||||
grantTableList = lappend_oid(grantTableList, relationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ListCell *objectCell = NULL;
|
||||
foreach(objectCell, grantStmt->objects)
|
||||
{
|
||||
RangeVar *relvar = (RangeVar *) lfirst(objectCell);
|
||||
Oid relationId = RangeVarGetRelid(relvar, NoLock, false);
|
||||
if (IsCitusTable(relationId))
|
||||
{
|
||||
grantTableList = lappend_oid(grantTableList, relationId);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check for distributed sequences included in GRANT ON TABLE statement */
|
||||
ObjectAddress sequenceAddress = { 0 };
|
||||
ObjectAddressSet(sequenceAddress, RelationRelationId, relationId);
|
||||
if (IsObjectDistributed(&sequenceAddress))
|
||||
{
|
||||
grantTableList = lappend_oid(grantTableList, relationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return grantTableList;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -73,10 +73,12 @@
|
|||
#include "distributed/commands/multi_copy.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/intermediate_results.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/local_executor.h"
|
||||
#include "distributed/log_utils.h"
|
||||
#include "distributed/coordinator_protocol.h"
|
||||
#include "distributed/metadata_cache.h"
|
||||
#include "distributed/multi_executor.h"
|
||||
#include "distributed/multi_partitioning_utils.h"
|
||||
#include "distributed/multi_physical_planner.h"
|
||||
#include "distributed/multi_router_planner.h"
|
||||
|
@ -102,6 +104,7 @@
|
|||
#include "libpq/pqformat.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "parser/parse_func.h"
|
||||
#include "parser/parse_type.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_13
|
||||
#include "tcop/cmdtag.h"
|
||||
|
@ -117,6 +120,9 @@
|
|||
/* constant used in binary protocol */
|
||||
static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
|
||||
|
||||
/* if true, skip validation of JSONB columns during COPY */
|
||||
bool SkipJsonbValidationInCopy = true;
|
||||
|
||||
/* custom Citus option for appending to a shard */
|
||||
#define APPEND_TO_SHARD_OPTION "append_to_shard"
|
||||
|
||||
|
@ -242,6 +248,9 @@ typedef enum LocalCopyStatus
|
|||
/* Local functions forward declarations */
|
||||
static void CopyToExistingShards(CopyStmt *copyStatement,
|
||||
QueryCompletionCompat *completionTag);
|
||||
static bool IsCopyInBinaryFormat(CopyStmt *copyStatement);
|
||||
static List * FindJsonbInputColumns(TupleDesc tupleDescriptor,
|
||||
List *inputColumnNameList);
|
||||
static List * RemoveOptionFromList(List *optionList, char *optionName);
|
||||
static bool BinaryOutputFunctionDefined(Oid typeId);
|
||||
static bool BinaryInputFunctionDefined(Oid typeId);
|
||||
|
@ -452,6 +461,7 @@ CopyToExistingShards(CopyStmt *copyStatement, QueryCompletionCompat *completionT
|
|||
List *columnNameList = NIL;
|
||||
int partitionColumnIndex = INVALID_PARTITION_COLUMN_INDEX;
|
||||
|
||||
bool isInputFormatBinary = IsCopyInBinaryFormat(copyStatement);
|
||||
uint64 processedRowCount = 0;
|
||||
|
||||
ErrorContextCallback errorCallback;
|
||||
|
@ -543,6 +553,72 @@ CopyToExistingShards(CopyStmt *copyStatement, QueryCompletionCompat *completionT
|
|||
copiedDistributedRelationTuple->relkind = RELKIND_RELATION;
|
||||
}
|
||||
|
||||
/*
|
||||
* We make an optimisation to skip JSON parsing for JSONB columns, because many
|
||||
* Citus users have large objects in this column and parsing it on the coordinator
|
||||
* causes significant CPU overhead. We do this by forcing BeginCopyFrom and
|
||||
* NextCopyFrom to parse the column as text and then encoding it as JSON again
|
||||
* by using citus_text_send_as_jsonb as the binary output function.
|
||||
*
|
||||
* The main downside of enabling this optimisation is that it defers validation
|
||||
* until the object is parsed by the worker, which is unable to give an accurate
|
||||
* line number.
|
||||
*/
|
||||
if (SkipJsonbValidationInCopy && !isInputFormatBinary)
|
||||
{
|
||||
CopyOutState copyOutState = copyDest->copyOutState;
|
||||
ListCell *jsonbColumnIndexCell = NULL;
|
||||
|
||||
/* get the column indices for all JSONB columns that appear in the input */
|
||||
List *jsonbColumnIndexList = FindJsonbInputColumns(
|
||||
copiedDistributedRelation->rd_att,
|
||||
copyStatement->attlist);
|
||||
|
||||
foreach(jsonbColumnIndexCell, jsonbColumnIndexList)
|
||||
{
|
||||
int jsonbColumnIndex = lfirst_int(jsonbColumnIndexCell);
|
||||
Form_pg_attribute currentColumn =
|
||||
TupleDescAttr(copiedDistributedRelation->rd_att, jsonbColumnIndex);
|
||||
|
||||
if (jsonbColumnIndex == partitionColumnIndex)
|
||||
{
|
||||
/*
|
||||
* In the curious case of using a JSONB column as partition column,
|
||||
* we leave it as is because we want to make sure the hashing works
|
||||
* correctly.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
ereport(DEBUG1, (errmsg("parsing JSONB column %s as text",
|
||||
NameStr(currentColumn->attname))));
|
||||
|
||||
/* parse the column as text instead of JSONB */
|
||||
currentColumn->atttypid = TEXTOID;
|
||||
|
||||
if (copyOutState->binary)
|
||||
{
|
||||
Oid textSendAsJsonbFunctionId = CitusTextSendAsJsonbFunctionId();
|
||||
|
||||
/*
|
||||
* If we're using binary encoding between coordinator and workers
|
||||
* then we should honour the format expected by jsonb_recv, which
|
||||
* is a version number followed by text. We therefore use an output
|
||||
* function which sends the text as if it were jsonb, namely by
|
||||
* prepending a version number.
|
||||
*/
|
||||
fmgr_info(textSendAsJsonbFunctionId,
|
||||
©Dest->columnOutputFunctions[jsonbColumnIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Oid textoutFunctionId = TextOutFunctionId();
|
||||
fmgr_info(textoutFunctionId,
|
||||
©Dest->columnOutputFunctions[jsonbColumnIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize copy state to read from COPY data source */
|
||||
CopyFromState copyState = BeginCopyFrom_compat(NULL,
|
||||
copiedDistributedRelation,
|
||||
|
@ -610,6 +686,82 @@ CopyToExistingShards(CopyStmt *copyStatement, QueryCompletionCompat *completionT
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* IsCopyInBinaryFormat determines whether the given COPY statement has the
|
||||
* WITH (format binary) option.
|
||||
*/
|
||||
static bool
|
||||
IsCopyInBinaryFormat(CopyStmt *copyStatement)
|
||||
{
|
||||
ListCell *optionCell = NULL;
|
||||
|
||||
foreach(optionCell, copyStatement->options)
|
||||
{
|
||||
DefElem *defel = lfirst_node(DefElem, optionCell);
|
||||
if (strcmp(defel->defname, "format") == 0 &&
|
||||
strcmp(defGetString(defel), "binary") == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* FindJsonbInputColumns finds columns in the tuple descriptor that have
|
||||
* the JSONB type and appear in inputColumnNameList. If the list is empty then
|
||||
* all JSONB columns are returned.
|
||||
*/
|
||||
static List *
|
||||
FindJsonbInputColumns(TupleDesc tupleDescriptor, List *inputColumnNameList)
|
||||
{
|
||||
List *jsonbColumnIndexList = NIL;
|
||||
int columnCount = tupleDescriptor->natts;
|
||||
|
||||
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
|
||||
{
|
||||
Form_pg_attribute currentColumn = TupleDescAttr(tupleDescriptor, columnIndex);
|
||||
if (currentColumn->attisdropped)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentColumn->atttypid != JSONBOID)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inputColumnNameList != NIL)
|
||||
{
|
||||
ListCell *inputColumnCell = NULL;
|
||||
bool isInputColumn = false;
|
||||
|
||||
foreach(inputColumnCell, inputColumnNameList)
|
||||
{
|
||||
char *inputColumnName = strVal(lfirst(inputColumnCell));
|
||||
|
||||
if (namestrcmp(¤tColumn->attname, inputColumnName) == 0)
|
||||
{
|
||||
isInputColumn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isInputColumn)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
jsonbColumnIndexList = lappend_int(jsonbColumnIndexList, columnIndex);
|
||||
}
|
||||
|
||||
return jsonbColumnIndexList;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
CompleteCopyQueryTagCompat(QueryCompletionCompat *completionTag, uint64 processedRowCount)
|
||||
{
|
||||
|
@ -3430,10 +3582,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
|
||||
|
|
|
@ -12,112 +12,650 @@
|
|||
|
||||
#include "catalog/namespace.h"
|
||||
#include "commands/policy.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/coordinator_protocol.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/metadata_cache.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "parser/parse_clause.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "rewrite/rewriteManip.h"
|
||||
#include "rewrite/rowsecurity.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/ruleutils.h"
|
||||
|
||||
|
||||
/* placeholder for CreatePolicyCommands */
|
||||
static const char * unparse_policy_command(const char aclchar);
|
||||
static void AddRangeTableEntryToQueryCompat(ParseState *parseState, Relation relation);
|
||||
static RowSecurityPolicy * GetPolicyByName(Oid relationId, const char *policyName);
|
||||
static List * GetPolicyListForRelation(Oid relationId);
|
||||
static char * CreatePolicyCommandForPolicy(Oid relationId, RowSecurityPolicy *policy);
|
||||
|
||||
|
||||
/*
|
||||
* CreatePolicyCommands takes in a relationId, and returns the list of create policy
|
||||
* commands needed to reconstruct the policies of that table.
|
||||
*/
|
||||
List *
|
||||
CreatePolicyCommands(Oid relationId)
|
||||
{
|
||||
/* placeholder for future implementation */
|
||||
return NIL;
|
||||
}
|
||||
List *commands = NIL;
|
||||
|
||||
List *policyList = GetPolicyListForRelation(relationId);
|
||||
|
||||
/* placeholder for PreprocessCreatePolicyStmt */
|
||||
List *
|
||||
PreprocessCreatePolicyStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
CreatePolicyStmt *stmt = castNode(CreatePolicyStmt, node);
|
||||
Oid relationId = RangeVarGetRelid(stmt->table,
|
||||
AccessExclusiveLock,
|
||||
false);
|
||||
if (IsCitusTable(relationId))
|
||||
RowSecurityPolicy *policy;
|
||||
foreach_ptr(policy, policyList)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("policies on distributed tables are only supported in "
|
||||
"Citus Enterprise")));
|
||||
char *createPolicyCommand = CreatePolicyCommandForPolicy(relationId, policy);
|
||||
commands = lappend(commands, makeTableDDLCommandString(createPolicyCommand));
|
||||
}
|
||||
|
||||
/* placeholder for future implementation */
|
||||
return NIL;
|
||||
return commands;
|
||||
}
|
||||
|
||||
|
||||
/* placeholder for PreprocessAlterPolicyStmt */
|
||||
/*
|
||||
* GetPolicyListForRelation returns a list of RowSecurityPolicy objects identifying
|
||||
* the policies on the relation with relationId. Note that this function acquires
|
||||
* AccessShareLock on relation and does not release it in the end to make sure that
|
||||
* caller will process valid policies through the transaction.
|
||||
*/
|
||||
static List *
|
||||
GetPolicyListForRelation(Oid relationId)
|
||||
{
|
||||
Relation relation = table_open(relationId, AccessShareLock);
|
||||
|
||||
if (!relation_has_policies(relation))
|
||||
{
|
||||
table_close(relation, NoLock);
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
if (relation->rd_rsdesc == NULL)
|
||||
{
|
||||
/*
|
||||
* there are policies, but since RLS is not enabled they are not loaded into
|
||||
* cache, we will do so here for us to access
|
||||
*/
|
||||
RelationBuildRowSecurity(relation);
|
||||
}
|
||||
|
||||
List *policyList = NIL;
|
||||
|
||||
RowSecurityPolicy *policy;
|
||||
foreach_ptr(policy, relation->rd_rsdesc->policies)
|
||||
{
|
||||
policyList = lappend(policyList, policy);
|
||||
}
|
||||
|
||||
table_close(relation, NoLock);
|
||||
|
||||
return policyList;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreatePolicyCommandForPolicy takes a relationId and a policy, returns
|
||||
* the CREATE POLICY command needed to reconstruct the policy identified
|
||||
* by the "policy" object on the relation with relationId.
|
||||
*/
|
||||
static char *
|
||||
CreatePolicyCommandForPolicy(Oid relationId, RowSecurityPolicy *policy)
|
||||
{
|
||||
char *relationName = generate_qualified_relation_name(relationId);
|
||||
List *relationContext = deparse_context_for(relationName, relationId);
|
||||
|
||||
StringInfo createPolicyCommand = makeStringInfo();
|
||||
|
||||
appendStringInfo(createPolicyCommand, "CREATE POLICY %s ON %s FOR %s",
|
||||
quote_identifier(policy->policy_name),
|
||||
relationName,
|
||||
unparse_policy_command(policy->polcmd));
|
||||
|
||||
|
||||
appendStringInfoString(createPolicyCommand, " TO ");
|
||||
|
||||
/*
|
||||
* iterate over all roles and append them to the ddl command with commas
|
||||
* separating the role names
|
||||
*/
|
||||
Oid *roles = (Oid *) ARR_DATA_PTR(policy->roles);
|
||||
for (int roleIndex = 0; roleIndex < ARR_DIMS(policy->roles)[0]; roleIndex++)
|
||||
{
|
||||
const char *roleName;
|
||||
|
||||
if (roleIndex > 0)
|
||||
{
|
||||
appendStringInfoString(createPolicyCommand, ", ");
|
||||
}
|
||||
|
||||
if (roles[roleIndex] == ACL_ID_PUBLIC)
|
||||
{
|
||||
roleName = "PUBLIC";
|
||||
}
|
||||
else
|
||||
{
|
||||
roleName = quote_identifier(GetUserNameFromId(roles[roleIndex], false));
|
||||
}
|
||||
|
||||
appendStringInfoString(createPolicyCommand, roleName);
|
||||
}
|
||||
|
||||
if (policy->qual)
|
||||
{
|
||||
char *qualString = deparse_expression((Node *) (policy->qual),
|
||||
relationContext, false, false);
|
||||
appendStringInfo(createPolicyCommand, " USING (%s)", qualString);
|
||||
}
|
||||
|
||||
if (policy->with_check_qual)
|
||||
{
|
||||
char *withCheckQualString = deparse_expression(
|
||||
(Node *) (policy->with_check_qual), relationContext, false, false);
|
||||
appendStringInfo(createPolicyCommand, " WITH CHECK (%s)",
|
||||
withCheckQualString);
|
||||
}
|
||||
|
||||
return createPolicyCommand->data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* unparse_policy_command takes the type of a policy command and converts it to its full
|
||||
* command string. This function is the exact inverse of parse_policy_command that is in
|
||||
* postgres.
|
||||
*/
|
||||
static const char *
|
||||
unparse_policy_command(const char aclchar)
|
||||
{
|
||||
switch (aclchar)
|
||||
{
|
||||
case '*':
|
||||
{
|
||||
return "ALL";
|
||||
}
|
||||
|
||||
case ACL_SELECT_CHR:
|
||||
{
|
||||
return "SELECT";
|
||||
}
|
||||
|
||||
case ACL_INSERT_CHR:
|
||||
{
|
||||
return "INSERT";
|
||||
}
|
||||
|
||||
case ACL_UPDATE_CHR:
|
||||
{
|
||||
return "UPDATE";
|
||||
}
|
||||
|
||||
case ACL_DELETE_CHR:
|
||||
{
|
||||
return "DELETE";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
elog(ERROR, "unrecognized aclchar: %d", aclchar);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PostprocessCreatePolicyStmt determines when a CREATE POLICY statement involves
|
||||
* a distributed table. If so, it creates DDLJobs to encapsulate information
|
||||
* needed during the worker node portion of DDL execution before returning the
|
||||
* DDLJobs in a List. If no distributed table is involved, this returns NIL.
|
||||
*/
|
||||
List *
|
||||
PostprocessCreatePolicyStmt(Node *node, const char *queryString)
|
||||
{
|
||||
CreatePolicyStmt *stmt = castNode(CreatePolicyStmt, node);
|
||||
|
||||
/* load relation information */
|
||||
RangeVar *relvar = stmt->table;
|
||||
Oid relationId = RangeVarGetRelid(relvar, NoLock, false);
|
||||
if (!IsCitusTable(relationId))
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
Relation relation = table_open(relationId, AccessShareLock);
|
||||
|
||||
|
||||
ParseState *qual_pstate = make_parsestate(NULL);
|
||||
AddRangeTableEntryToQueryCompat(qual_pstate, relation);
|
||||
Node *qual = transformWhereClause(qual_pstate,
|
||||
copyObject(stmt->qual),
|
||||
EXPR_KIND_POLICY,
|
||||
"POLICY");
|
||||
if (qual)
|
||||
{
|
||||
ErrorIfUnsupportedPolicyExpr(qual);
|
||||
}
|
||||
|
||||
ParseState *with_check_pstate = make_parsestate(NULL);
|
||||
AddRangeTableEntryToQueryCompat(with_check_pstate, relation);
|
||||
Node *with_check_qual = transformWhereClause(with_check_pstate,
|
||||
copyObject(stmt->with_check),
|
||||
EXPR_KIND_POLICY,
|
||||
"POLICY");
|
||||
if (with_check_qual)
|
||||
{
|
||||
ErrorIfUnsupportedPolicyExpr(with_check_qual);
|
||||
}
|
||||
|
||||
RowSecurityPolicy *policy = GetPolicyByName(relationId, stmt->policy_name);
|
||||
|
||||
if (policy == NULL)
|
||||
{
|
||||
/*
|
||||
* As this function is executed after standard process utility created the
|
||||
* policy, we should be able to find & deparse the policy with policy_name.
|
||||
* But to be more safe, error out here.
|
||||
*/
|
||||
ereport(ERROR, (errmsg("cannot create policy, policy does not exist.")));
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
char *ddlCommand = CreatePolicyCommandForPolicy(relationId, policy);
|
||||
|
||||
/*
|
||||
* create the DDLJob that needs to be executed both on the local relation and all its
|
||||
* placements.
|
||||
*/
|
||||
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
|
||||
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
|
||||
ddlJob->metadataSyncCommand = pstrdup(ddlCommand);
|
||||
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
|
||||
|
||||
relation_close(relation, NoLock);
|
||||
|
||||
return list_make1(ddlJob);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AddRangeTableEntryToQueryCompat adds the given relation to query.
|
||||
* This method is a compatibility wrapper.
|
||||
*/
|
||||
static void
|
||||
AddRangeTableEntryToQueryCompat(ParseState *parseState, Relation relation)
|
||||
{
|
||||
#if PG_VERSION_NUM >= PG_VERSION_13
|
||||
ParseNamespaceItem *rte = NULL;
|
||||
#else
|
||||
RangeTblEntry *rte = NULL;
|
||||
#endif
|
||||
|
||||
rte = addRangeTableEntryForRelation(parseState, relation,
|
||||
#if PG_VERSION_NUM >= PG_VERSION_12
|
||||
AccessShareLock,
|
||||
#endif
|
||||
NULL, false, false);
|
||||
#if PG_VERSION_NUM >= PG_VERSION_13
|
||||
addNSItemToQuery(parseState, rte, false, true, true);
|
||||
#else
|
||||
addRTEtoQuery(parseState, rte, false, true, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetPolicyByName takes a relationId and a policyName, returns RowSecurityPolicy
|
||||
* object which identifies the policy with name "policyName" on the relation
|
||||
* with relationId. If there does not exist such a policy, then this function
|
||||
* returns NULL.
|
||||
*/
|
||||
static RowSecurityPolicy *
|
||||
GetPolicyByName(Oid relationId, const char *policyName)
|
||||
{
|
||||
List *policyList = GetPolicyListForRelation(relationId);
|
||||
|
||||
RowSecurityPolicy *policy = NULL;
|
||||
foreach_ptr(policy, policyList)
|
||||
{
|
||||
if (strncmp(policy->policy_name, policyName, NAMEDATALEN) == 0)
|
||||
{
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessAlterPolicyStmt determines whether a given ALTER POLICY statement involves a
|
||||
* distributed table. If so, it creates DDLJobs to encapsulate information needed during
|
||||
* the worker node portion of DDL execution before returning the DDLJobs in a list. If no
|
||||
* distributed table is involved this returns NIL.
|
||||
*/
|
||||
List *
|
||||
PreprocessAlterPolicyStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
/* placeholder for future implementation */
|
||||
return NIL;
|
||||
AlterPolicyStmt *stmt = castNode(AlterPolicyStmt, node);
|
||||
StringInfoData ddlString;
|
||||
ListCell *roleCell = NULL;
|
||||
|
||||
/* load relation information */
|
||||
RangeVar *relvar = stmt->table;
|
||||
Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
|
||||
if (!IsCitusTable(relOid))
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
initStringInfo(&ddlString);
|
||||
|
||||
Relation relation = relation_open(relOid, AccessShareLock);
|
||||
char *relationName = generate_relation_name(relOid, NIL);
|
||||
|
||||
appendStringInfo(&ddlString, "ALTER POLICY %s ON %s",
|
||||
quote_identifier(stmt->policy_name),
|
||||
relationName
|
||||
);
|
||||
|
||||
if (stmt->roles)
|
||||
{
|
||||
appendStringInfoString(&ddlString, " TO ");
|
||||
foreach(roleCell, stmt->roles)
|
||||
{
|
||||
RoleSpec *roleSpec = (RoleSpec *) lfirst(roleCell);
|
||||
|
||||
appendStringInfoString(&ddlString, RoleSpecString(roleSpec, true));
|
||||
|
||||
if (lnext_compat(stmt->roles, roleCell) != NULL)
|
||||
{
|
||||
appendStringInfoString(&ddlString, ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List *relationContext = deparse_context_for(relationName, relOid);
|
||||
|
||||
ParseState *qual_pstate = make_parsestate(NULL);
|
||||
AddRangeTableEntryToQueryCompat(qual_pstate, relation);
|
||||
Node *qual = transformWhereClause(qual_pstate,
|
||||
copyObject(stmt->qual),
|
||||
EXPR_KIND_POLICY,
|
||||
"POLICY");
|
||||
if (qual)
|
||||
{
|
||||
ErrorIfUnsupportedPolicyExpr(qual);
|
||||
|
||||
char *qualString = deparse_expression(qual, relationContext, false, false);
|
||||
appendStringInfo(&ddlString, " USING (%s)", qualString);
|
||||
}
|
||||
|
||||
ParseState *with_check_pstate = make_parsestate(NULL);
|
||||
AddRangeTableEntryToQueryCompat(with_check_pstate, relation);
|
||||
Node *with_check_qual = transformWhereClause(with_check_pstate,
|
||||
copyObject(stmt->with_check),
|
||||
EXPR_KIND_POLICY,
|
||||
"POLICY");
|
||||
if (with_check_qual)
|
||||
{
|
||||
ErrorIfUnsupportedPolicyExpr(with_check_qual);
|
||||
|
||||
char *withCheckString = deparse_expression(with_check_qual, relationContext,
|
||||
false,
|
||||
false);
|
||||
appendStringInfo(&ddlString, " WITH CHECK (%s)", withCheckString);
|
||||
}
|
||||
|
||||
/*
|
||||
* create the DDLJob that needs to be executed both on the local relation and all its
|
||||
* placements.
|
||||
*/
|
||||
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
|
||||
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relOid);
|
||||
ddlJob->metadataSyncCommand = pstrdup(ddlString.data);
|
||||
ddlJob->taskList = DDLTaskList(relOid, ddlString.data);
|
||||
|
||||
relation_close(relation, NoLock);
|
||||
|
||||
return list_make1(ddlJob);
|
||||
}
|
||||
|
||||
|
||||
/* placeholder for ErrorIfUnsupportedPolicy */
|
||||
/*
|
||||
* ErrorIfUnsupportedPolicy runs checks related to a Relation their Policies and errors
|
||||
* out if it is not possible to create one of the policies in a distributed environment.
|
||||
*
|
||||
* To support policies we require that:
|
||||
* - Policy expressions do not contain subqueries.
|
||||
*/
|
||||
void
|
||||
ErrorIfUnsupportedPolicy(Relation relation)
|
||||
{
|
||||
if (relation_has_policies(relation))
|
||||
ListCell *policyCell = NULL;
|
||||
|
||||
if (!relation_has_policies(relation))
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("policies on distributed tables are only supported in "
|
||||
"Citus Enterprise"),
|
||||
errhint("Remove any policies on a table before distributing")));
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* even if a relation has policies they might not be loaded on the Relation yet. This
|
||||
* happens if policies are on a Relation without Row Level Security enabled. We need
|
||||
* to make sure the policies installed are valid for distribution if RLS gets enabled
|
||||
* after the table has been distributed. Therefore we force a build of the policies on
|
||||
* the cached Relation
|
||||
*/
|
||||
if (relation->rd_rsdesc == NULL)
|
||||
{
|
||||
RelationBuildRowSecurity(relation);
|
||||
}
|
||||
|
||||
foreach(policyCell, relation->rd_rsdesc->policies)
|
||||
{
|
||||
RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(policyCell);
|
||||
|
||||
ErrorIfUnsupportedPolicyExpr((Node *) policy->qual);
|
||||
ErrorIfUnsupportedPolicyExpr((Node *) policy->with_check_qual);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* placeholder for PreprocessDropPolicyStmt */
|
||||
/*
|
||||
* ErrorIfUnsupportedPolicyExpr tests if the provided expression for a policy is
|
||||
* supported on a distributed table.
|
||||
*/
|
||||
void
|
||||
ErrorIfUnsupportedPolicyExpr(Node *expr)
|
||||
{
|
||||
/*
|
||||
* We do not allow any sublink to prevent expressions with subqueries to be used as an
|
||||
* expression in policies on distributed tables.
|
||||
*/
|
||||
if (checkExprHasSubLink(expr))
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot create policy"),
|
||||
errdetail("Subqueries are not supported in policies on distributed "
|
||||
"tables")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessDropPolicyStmt determines whether a given DROP POLICY statement involves a
|
||||
* distributed table. If so it creates DDLJobs to encapsulate information needed during
|
||||
* the worker node portion of DDL execution before returning the DDLJobs in a List. If no
|
||||
* distributed table is involved this returns NIL.
|
||||
*/
|
||||
List *
|
||||
PreprocessDropPolicyStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
/* placeholder for future implementation */
|
||||
return NIL;
|
||||
DropStmt *stmt = castNode(DropStmt, node);
|
||||
List *ddlJobs = NIL;
|
||||
ListCell *dropObjectCell = NULL;
|
||||
|
||||
Assert(stmt->removeType == OBJECT_POLICY);
|
||||
|
||||
foreach(dropObjectCell, stmt->objects)
|
||||
{
|
||||
List *names = (List *) lfirst(dropObjectCell);
|
||||
|
||||
/*
|
||||
* the last element in the list of names is the name of the policy. The ones
|
||||
* before are describing the relation. By removing the last item from the list we
|
||||
* can use makeRangeVarFromNameList to get to the relation. As list_truncate
|
||||
* changes the list in place we make a copy before.
|
||||
*/
|
||||
names = list_copy(names);
|
||||
names = list_truncate(names, list_length(names) - 1);
|
||||
RangeVar *relation = makeRangeVarFromNameList(names);
|
||||
|
||||
Oid relOid = RangeVarGetRelid(relation, NoLock, false);
|
||||
if (!IsCitusTable(relOid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
|
||||
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relOid);
|
||||
ddlJob->metadataSyncCommand = queryString;
|
||||
ddlJob->taskList = DDLTaskList(relOid, queryString);
|
||||
|
||||
ddlJobs = lappend(ddlJobs, ddlJob);
|
||||
}
|
||||
|
||||
return ddlJobs;
|
||||
}
|
||||
|
||||
|
||||
/* placeholder for IsPolicyRenameStmt */
|
||||
/*
|
||||
* IsPolicyRenameStmt returns wherher the passed-in RenameStmt is one of the following
|
||||
* forms:
|
||||
*
|
||||
* - ALTER POLICY ... ON ... RENAME TO ...
|
||||
*/
|
||||
bool
|
||||
IsPolicyRenameStmt(RenameStmt *stmt)
|
||||
{
|
||||
/* placeholder for future implementation */
|
||||
return false;
|
||||
return stmt->renameType == OBJECT_POLICY;
|
||||
}
|
||||
|
||||
|
||||
/* placeholder for CreatePolicyEventExtendNames */
|
||||
/*
|
||||
* CreatePolicyEventExtendNames extends relation names in the given CreatePolicyStmt tree.
|
||||
* This function has side effects on the tree as the names are replaced inplace.
|
||||
*/
|
||||
void
|
||||
CreatePolicyEventExtendNames(CreatePolicyStmt *stmt, const char *schemaName, uint64
|
||||
shardId)
|
||||
{
|
||||
/* placeholder for future implementation */
|
||||
RangeVar *relation = stmt->table;
|
||||
char **relationName = &(relation->relname);
|
||||
char **relationSchemaName = &(relation->schemaname);
|
||||
|
||||
/* prefix with schema name if it is not added already */
|
||||
SetSchemaNameIfNotExist(relationSchemaName, schemaName);
|
||||
|
||||
AppendShardIdToName(relationName, shardId);
|
||||
}
|
||||
|
||||
|
||||
/* placeholder for AlterPolicyEventExtendNames */
|
||||
/*
|
||||
* AlterPolicyEventExtendNames extends relation names in the given AlterPolicyStatement
|
||||
* tree. This function has side effects on the tree as the names are replaced inplace.
|
||||
*/
|
||||
void
|
||||
AlterPolicyEventExtendNames(AlterPolicyStmt *stmt, const char *schemaName, uint64 shardId)
|
||||
{
|
||||
/* placeholder for future implementation */
|
||||
RangeVar *relation = stmt->table;
|
||||
char **relationName = &(relation->relname);
|
||||
char **relationSchemaName = &(relation->schemaname);
|
||||
|
||||
/* prefix with schema name if it is not added already */
|
||||
SetSchemaNameIfNotExist(relationSchemaName, schemaName);
|
||||
|
||||
AppendShardIdToName(relationName, shardId);
|
||||
}
|
||||
|
||||
|
||||
/* placeholder for RenamePolicyEventExtendNames */
|
||||
/*
|
||||
* RenamePolicyEventExtendNames extends relation names in the given RenameStmt tree. This
|
||||
* function has side effects on the tree as the names are replaced inline.
|
||||
*/
|
||||
void
|
||||
RenamePolicyEventExtendNames(RenameStmt *stmt, const char *schemaName, uint64 shardId)
|
||||
{
|
||||
/* placeholder for future implementation */
|
||||
char **relationName = &(stmt->relation->relname);
|
||||
char **objectSchemaName = &(stmt->relation->schemaname);
|
||||
|
||||
/* prefix with schema name if it is not added already */
|
||||
SetSchemaNameIfNotExist(objectSchemaName, schemaName);
|
||||
|
||||
AppendShardIdToName(relationName, shardId);
|
||||
}
|
||||
|
||||
|
||||
/* placeholder for DropPolicyEventExtendNames */
|
||||
/*
|
||||
* DropPolicyEventExtendNames extends relation names in the given DropStmt tree specific
|
||||
* to policies. This function has side effects on the tree as the names are replaced
|
||||
* inplace.
|
||||
*/
|
||||
void
|
||||
DropPolicyEventExtendNames(DropStmt *dropStmt, const char *schemaName, uint64 shardId)
|
||||
{
|
||||
/* placeholder for future implementation */
|
||||
Value *relationSchemaNameValue = NULL;
|
||||
Value *relationNameValue = NULL;
|
||||
|
||||
uint32 dropCount = list_length(dropStmt->objects);
|
||||
if (dropCount > 1)
|
||||
{
|
||||
ereport(ERROR, (errmsg("cannot extend name for multiple drop objects")));
|
||||
}
|
||||
|
||||
List *relationNameList = (List *) linitial(dropStmt->objects);
|
||||
int relationNameListLength = list_length(relationNameList);
|
||||
|
||||
switch (relationNameListLength)
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
relationNameValue = linitial(relationNameList);
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
relationSchemaNameValue = linitial(relationNameList);
|
||||
relationNameValue = lsecond(relationNameList);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("improper policy name: \"%s\"",
|
||||
NameListToString(relationNameList))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* prefix with schema name if it is not added already */
|
||||
if (relationSchemaNameValue == NULL)
|
||||
{
|
||||
Value *schemaNameValue = makeString(pstrdup(schemaName));
|
||||
relationNameList = lcons(schemaNameValue, relationNameList);
|
||||
}
|
||||
|
||||
char **relationName = &(relationNameValue->val.str);
|
||||
AppendShardIdToName(relationName, shardId);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
|
||||
#include "access/heapam.h"
|
||||
#include "access/htup_details.h"
|
||||
#include "access/genam.h"
|
||||
#include "access/table.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/catalog.h"
|
||||
#include "catalog/pg_auth_members.h"
|
||||
#include "catalog/pg_authid.h"
|
||||
|
@ -31,6 +33,9 @@
|
|||
#include "distributed/coordinator_protocol.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/metadata_sync.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/multi_executor.h"
|
||||
#include "distributed/relation_access_tracking.h"
|
||||
#include "distributed/version_compat.h"
|
||||
#include "distributed/worker_transaction.h"
|
||||
#include "miscadmin.h"
|
||||
|
@ -40,6 +45,7 @@
|
|||
#include "parser/scansup.h"
|
||||
#include "utils/acl.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/guc_tables.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/rel.h"
|
||||
|
@ -54,6 +60,9 @@ static char * CreateCreateOrAlterRoleCommand(const char *roleName,
|
|||
AlterRoleStmt *alterRoleStmt);
|
||||
static DefElem * makeDefElemInt(char *name, int value);
|
||||
static List * GenerateRoleOptionsList(HeapTuple tuple);
|
||||
static List * GenerateGrantRoleStmtsFromOptions(RoleSpec *roleSpec, List *options);
|
||||
static List * GenerateGrantRoleStmtsOfRole(Oid roleid);
|
||||
static void EnsureSequentialModeForRoleDDL(void);
|
||||
|
||||
static char * GetRoleNameFromDbRoleSetting(HeapTuple tuple,
|
||||
TupleDesc DbRoleSettingDescription);
|
||||
|
@ -68,6 +77,7 @@ static int ConfigGenericNameCompare(const void *lhs, const void *rhs);
|
|||
static ObjectAddress RoleSpecToObjectAddress(RoleSpec *role, bool missing_ok);
|
||||
|
||||
/* controlled via GUC */
|
||||
bool EnableCreateRolePropagation = true;
|
||||
bool EnableAlterRolePropagation = true;
|
||||
bool EnableAlterRoleSetPropagation = true;
|
||||
|
||||
|
@ -133,11 +143,13 @@ PostprocessAlterRoleStmt(Node *node, const char *queryString)
|
|||
return NIL;
|
||||
}
|
||||
|
||||
if (!EnableAlterRolePropagation || !IsCoordinator())
|
||||
if (!EnableAlterRolePropagation)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
AlterRoleStmt *stmt = castNode(AlterRoleStmt, node);
|
||||
|
||||
DefElem *option = NULL;
|
||||
|
@ -161,7 +173,9 @@ PostprocessAlterRoleStmt(Node *node, const char *queryString)
|
|||
break;
|
||||
}
|
||||
}
|
||||
List *commands = list_make1((void *) CreateAlterRoleIfExistsCommand(stmt));
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) CreateAlterRoleIfExistsCommand(stmt),
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
@ -206,14 +220,7 @@ PreprocessAlterRoleSetStmt(Node *node, const char *queryString,
|
|||
return NIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since roles need to be handled manually on community, we need to support such queries
|
||||
* by handling them locally on worker nodes
|
||||
*/
|
||||
if (!IsCoordinator())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
EnsureCoordinator();
|
||||
|
||||
QualifyTreeNode((Node *) stmt);
|
||||
const char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
@ -337,6 +344,7 @@ ExtractEncryptedPassword(Oid roleOid)
|
|||
|
||||
Datum passwordDatum = heap_getattr(tuple, Anum_pg_authid_rolpassword,
|
||||
pgAuthIdDescription, &isNull);
|
||||
char *passwordCstring = TextDatumGetCString(passwordDatum);
|
||||
|
||||
table_close(pgAuthId, AccessShareLock);
|
||||
ReleaseSysCache(tuple);
|
||||
|
@ -346,7 +354,7 @@ ExtractEncryptedPassword(Oid roleOid)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
return pstrdup(TextDatumGetCString(passwordDatum));
|
||||
return pstrdup(passwordCstring);
|
||||
}
|
||||
|
||||
|
||||
|
@ -492,6 +500,14 @@ GenerateCreateOrAlterRoleCommand(Oid roleOid)
|
|||
Form_pg_authid role = ((Form_pg_authid) GETSTRUCT(roleTuple));
|
||||
|
||||
CreateRoleStmt *createRoleStmt = NULL;
|
||||
if (EnableCreateRolePropagation)
|
||||
{
|
||||
createRoleStmt = makeNode(CreateRoleStmt);
|
||||
createRoleStmt->stmt_type = ROLESTMT_ROLE;
|
||||
createRoleStmt->role = pstrdup(NameStr(role->rolname));
|
||||
createRoleStmt->options = GenerateRoleOptionsList(roleTuple);
|
||||
}
|
||||
|
||||
AlterRoleStmt *alterRoleStmt = NULL;
|
||||
if (EnableAlterRolePropagation)
|
||||
{
|
||||
|
@ -525,6 +541,16 @@ GenerateCreateOrAlterRoleCommand(Oid roleOid)
|
|||
completeRoleList = list_concat(completeRoleList, alterRoleSetCommands);
|
||||
}
|
||||
|
||||
if (EnableCreateRolePropagation)
|
||||
{
|
||||
List *grantRoleStmts = GenerateGrantRoleStmtsOfRole(roleOid);
|
||||
Node *stmt = NULL;
|
||||
foreach_ptr(stmt, grantRoleStmts)
|
||||
{
|
||||
completeRoleList = lappend(completeRoleList, DeparseTreeNode(stmt));
|
||||
}
|
||||
}
|
||||
|
||||
return completeRoleList;
|
||||
}
|
||||
|
||||
|
@ -731,6 +757,157 @@ MakeSetStatementArguments(char *configurationName, char *configurationValue)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateGrantRoleStmtsFromOptions gets a RoleSpec of a role that is being
|
||||
* created and a list of options of CreateRoleStmt to generate GrantRoleStmts
|
||||
* for the role's memberships.
|
||||
*/
|
||||
static List *
|
||||
GenerateGrantRoleStmtsFromOptions(RoleSpec *roleSpec, List *options)
|
||||
{
|
||||
List *stmts = NIL;
|
||||
|
||||
DefElem *option = NULL;
|
||||
foreach_ptr(option, options)
|
||||
{
|
||||
if (strcmp(option->defname, "adminmembers") != 0 &&
|
||||
strcmp(option->defname, "rolemembers") != 0 &&
|
||||
strcmp(option->defname, "addroleto") != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GrantRoleStmt *grantRoleStmt = makeNode(GrantRoleStmt);
|
||||
grantRoleStmt->is_grant = true;
|
||||
|
||||
if (strcmp(option->defname, "adminmembers") == 0 || strcmp(option->defname,
|
||||
"rolemembers") == 0)
|
||||
{
|
||||
grantRoleStmt->granted_roles = list_make1(roleSpec);
|
||||
grantRoleStmt->grantee_roles = (List *) option->arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
grantRoleStmt->granted_roles = (List *) option->arg;
|
||||
grantRoleStmt->grantee_roles = list_make1(roleSpec);
|
||||
}
|
||||
|
||||
if (strcmp(option->defname, "adminmembers") == 0)
|
||||
{
|
||||
grantRoleStmt->admin_opt = true;
|
||||
}
|
||||
|
||||
stmts = lappend(stmts, grantRoleStmt);
|
||||
}
|
||||
return stmts;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateGrantRoleStmtsOfRole generates the GrantRoleStmts for the memberships
|
||||
* of the role whose oid is roleid.
|
||||
*/
|
||||
static List *
|
||||
GenerateGrantRoleStmtsOfRole(Oid roleid)
|
||||
{
|
||||
Relation pgAuthMembers = table_open(AuthMemRelationId, AccessShareLock);
|
||||
HeapTuple tuple = NULL;
|
||||
List *stmts = NIL;
|
||||
|
||||
ScanKeyData skey[1];
|
||||
|
||||
ScanKeyInit(&skey[0], Anum_pg_auth_members_member, BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(roleid));
|
||||
SysScanDesc scan = systable_beginscan(pgAuthMembers, AuthMemMemRoleIndexId, true,
|
||||
NULL, 1, &skey[0]);
|
||||
|
||||
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
|
||||
{
|
||||
Form_pg_auth_members membership = (Form_pg_auth_members) GETSTRUCT(tuple);
|
||||
|
||||
GrantRoleStmt *grantRoleStmt = makeNode(GrantRoleStmt);
|
||||
grantRoleStmt->is_grant = true;
|
||||
|
||||
RoleSpec *grantedRole = makeNode(RoleSpec);
|
||||
grantedRole->roletype = ROLESPEC_CSTRING;
|
||||
grantedRole->location = -1;
|
||||
grantedRole->rolename = GetUserNameFromId(membership->roleid, true);
|
||||
grantRoleStmt->granted_roles = list_make1(grantedRole);
|
||||
|
||||
RoleSpec *granteeRole = makeNode(RoleSpec);
|
||||
granteeRole->roletype = ROLESPEC_CSTRING;
|
||||
granteeRole->location = -1;
|
||||
granteeRole->rolename = GetUserNameFromId(membership->member, true);
|
||||
grantRoleStmt->grantee_roles = list_make1(granteeRole);
|
||||
|
||||
grantRoleStmt->grantor = NULL;
|
||||
|
||||
grantRoleStmt->admin_opt = membership->admin_option;
|
||||
|
||||
stmts = lappend(stmts, grantRoleStmt);
|
||||
}
|
||||
|
||||
systable_endscan(scan);
|
||||
table_close(pgAuthMembers, AccessShareLock);
|
||||
|
||||
return stmts;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessCreateRoleStmt creates a worker_create_or_alter_role query for the
|
||||
* role that is being created. With that query we can create the role in the
|
||||
* workers or if they exist we alter them to the way they are being created
|
||||
* right now.
|
||||
*/
|
||||
List *
|
||||
PreprocessCreateRoleStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
if (!EnableCreateRolePropagation || !ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
EnsureSequentialModeForRoleDDL();
|
||||
|
||||
LockRelationOid(DistNodeRelationId(), RowShareLock);
|
||||
|
||||
CreateRoleStmt *createRoleStmt = castNode(CreateRoleStmt, node);
|
||||
|
||||
AlterRoleStmt *alterRoleStmt = makeNode(AlterRoleStmt);
|
||||
alterRoleStmt->role = makeNode(RoleSpec);
|
||||
alterRoleStmt->role->roletype = ROLESPEC_CSTRING;
|
||||
alterRoleStmt->role->location = -1;
|
||||
alterRoleStmt->role->rolename = pstrdup(createRoleStmt->role);
|
||||
alterRoleStmt->action = 1;
|
||||
alterRoleStmt->options = createRoleStmt->options;
|
||||
|
||||
List *grantRoleStmts = GenerateGrantRoleStmtsFromOptions(alterRoleStmt->role,
|
||||
createRoleStmt->options);
|
||||
|
||||
char *createOrAlterRoleQuery = CreateCreateOrAlterRoleCommand(createRoleStmt->role,
|
||||
createRoleStmt,
|
||||
alterRoleStmt);
|
||||
|
||||
List *commands = NIL;
|
||||
commands = lappend(commands, DISABLE_DDL_PROPAGATION);
|
||||
commands = lappend(commands, createOrAlterRoleQuery);
|
||||
|
||||
/* deparse all grant statements and add them to the to commands list */
|
||||
Node *stmt = NULL;
|
||||
foreach_ptr(stmt, grantRoleStmts)
|
||||
{
|
||||
commands = lappend(commands, DeparseTreeNode(stmt));
|
||||
}
|
||||
|
||||
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* makeStringConst creates a Const Node that stores a given string
|
||||
*
|
||||
|
@ -785,6 +962,178 @@ makeFloatConst(char *str, int location)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessDropRoleStmt finds the distributed role out of the ones
|
||||
* being dropped and unmarks them distributed and creates the drop statements
|
||||
* for the workers.
|
||||
*/
|
||||
List *
|
||||
PreprocessDropRoleStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
DropRoleStmt *stmt = castNode(DropRoleStmt, node);
|
||||
List *allDropRoles = stmt->roles;
|
||||
|
||||
List *distributedDropRoles = FilterDistributedRoles(allDropRoles);
|
||||
if (list_length(distributedDropRoles) <= 0)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
if (!EnableCreateRolePropagation || !ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
EnsureSequentialModeForRoleDDL();
|
||||
|
||||
|
||||
stmt->roles = distributedDropRoles;
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
stmt->roles = allDropRoles;
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* UnmarkRolesDistributed unmarks the roles in the RoleSpec list distributed.
|
||||
*/
|
||||
void
|
||||
UnmarkRolesDistributed(List *roles)
|
||||
{
|
||||
Node *roleNode = NULL;
|
||||
foreach_ptr(roleNode, roles)
|
||||
{
|
||||
RoleSpec *role = castNode(RoleSpec, roleNode);
|
||||
ObjectAddress roleAddress = { 0 };
|
||||
Oid roleOid = get_rolespec_oid(role, true);
|
||||
|
||||
if (roleOid == InvalidOid)
|
||||
{
|
||||
/*
|
||||
* If the role is dropped (concurrently), we might get an inactive oid for the
|
||||
* role. If it is invalid oid, skip.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectAddressSet(roleAddress, AuthIdRelationId, roleOid);
|
||||
UnmarkObjectDistributed(&roleAddress);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* FilterDistributedRoles filters the list of RoleSpecs and returns the ones
|
||||
* that are distributed.
|
||||
*/
|
||||
List *
|
||||
FilterDistributedRoles(List *roles)
|
||||
{
|
||||
List *distributedRoles = NIL;
|
||||
Node *roleNode = NULL;
|
||||
foreach_ptr(roleNode, roles)
|
||||
{
|
||||
RoleSpec *role = castNode(RoleSpec, roleNode);
|
||||
ObjectAddress roleAddress = { 0 };
|
||||
Oid roleOid = get_rolespec_oid(role, true);
|
||||
if (roleOid == InvalidOid)
|
||||
{
|
||||
/*
|
||||
* Non-existing roles are ignored silently here. Postgres will
|
||||
* handle to give an error or not for these roles.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
ObjectAddressSet(roleAddress, AuthIdRelationId, roleOid);
|
||||
if (IsObjectDistributed(&roleAddress))
|
||||
{
|
||||
distributedRoles = lappend(distributedRoles, role);
|
||||
}
|
||||
}
|
||||
return distributedRoles;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessGrantRoleStmt finds the distributed grantee roles and creates the
|
||||
* query to run on the workers.
|
||||
*/
|
||||
List *
|
||||
PreprocessGrantRoleStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
if (!EnableCreateRolePropagation || !ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
|
||||
List *allGranteeRoles = stmt->grantee_roles;
|
||||
RoleSpec *grantor = stmt->grantor;
|
||||
|
||||
List *distributedGranteeRoles = FilterDistributedRoles(allGranteeRoles);
|
||||
if (list_length(distributedGranteeRoles) <= 0)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Postgres don't seem to use the grantor. Even dropping the grantor doesn't
|
||||
* seem to affect the membership. If this changes, we might need to add grantors
|
||||
* to the dependency resolution too. For now we just don't propagate it.
|
||||
*/
|
||||
stmt->grantor = NULL;
|
||||
stmt->grantee_roles = distributedGranteeRoles;
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
stmt->grantee_roles = allGranteeRoles;
|
||||
stmt->grantor = grantor;
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PostprocessGrantRoleStmt actually creates the plan we need to execute for grant
|
||||
* role statement.
|
||||
*/
|
||||
List *
|
||||
PostprocessGrantRoleStmt(Node *node, const char *queryString)
|
||||
{
|
||||
if (!EnableCreateRolePropagation || !IsCoordinator() || !ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
|
||||
|
||||
RoleSpec *role = NULL;
|
||||
foreach_ptr(role, stmt->grantee_roles)
|
||||
{
|
||||
ObjectAddress roleAddress = { 0 };
|
||||
Oid roleOid = get_rolespec_oid(role, false);
|
||||
ObjectAddressSet(roleAddress, AuthIdRelationId, roleOid);
|
||||
if (IsObjectDistributed(&roleAddress))
|
||||
{
|
||||
EnsureDependenciesExistOnAllNodes(&roleAddress);
|
||||
}
|
||||
}
|
||||
return NIL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ConfigGenericNameCompare compares two config_generic structs based on their
|
||||
* name fields. If the name fields contain the same strings two structs are
|
||||
|
@ -805,3 +1154,64 @@ ConfigGenericNameCompare(const void *a, const void *b)
|
|||
*/
|
||||
return pg_strcasecmp(confa->name, confb->name);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateRoleStmtObjectAddress finds the ObjectAddress for the role described
|
||||
* by the CreateRoleStmt. If missing_ok is false this function throws an error if the
|
||||
* role does not exist.
|
||||
*
|
||||
* Never returns NULL, but the objid in the address could be invalid if missing_ok was set
|
||||
* to true.
|
||||
*/
|
||||
ObjectAddress
|
||||
CreateRoleStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
CreateRoleStmt *stmt = castNode(CreateRoleStmt, node);
|
||||
Oid roleOid = get_role_oid(stmt->role, missing_ok);
|
||||
ObjectAddress roleAddress = { 0 };
|
||||
ObjectAddressSet(roleAddress, AuthIdRelationId, roleOid);
|
||||
|
||||
return roleAddress;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* EnsureSequentialModeForRoleDDL makes sure that the current transaction is already in
|
||||
* sequential mode, or can still safely be put in sequential mode, it errors if that is
|
||||
* not possible. The error contains information for the user to retry the transaction with
|
||||
* sequential mode set from the begining.
|
||||
*
|
||||
* As roles are node scoped objects there exists only 1 instance of the role used by
|
||||
* potentially multiple shards. To make sure all shards in the transaction can interact
|
||||
* with the role the role needs to be visible on all connections used by the transaction,
|
||||
* meaning we can only use 1 connection per node.
|
||||
*/
|
||||
static void
|
||||
EnsureSequentialModeForRoleDDL(void)
|
||||
{
|
||||
if (!IsTransactionBlock())
|
||||
{
|
||||
/* we do not need to switch to sequential mode if we are not in a transaction */
|
||||
return;
|
||||
}
|
||||
|
||||
if (ParallelQueryExecutedInTransaction())
|
||||
{
|
||||
ereport(ERROR, (errmsg("cannot create or modify role because there was a "
|
||||
"parallel operation on a distributed table in the "
|
||||
"transaction"),
|
||||
errdetail("When creating or altering a role, Citus needs to "
|
||||
"perform all operations over a single connection per "
|
||||
"node to ensure consistency."),
|
||||
errhint("Try re-running the transaction with "
|
||||
"\"SET LOCAL citus.multi_shard_modify_mode TO "
|
||||
"\'sequential\';\"")));
|
||||
}
|
||||
|
||||
ereport(DEBUG1, (errmsg("switching to sequential query execution mode"),
|
||||
errdetail("Role is created or altered. To make sure subsequent "
|
||||
"commands see the role correctly we need to make sure to "
|
||||
"use only one connection for all future commands")));
|
||||
SetLocalMultiShardModifyModeToSequential();
|
||||
}
|
||||
|
|
|
@ -161,14 +161,7 @@ PreprocessGrantOnSchemaStmt(Node *node, const char *queryString,
|
|||
return NIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since access control needs to be handled manually on community, we need to support
|
||||
* such queries by handling them locally on worker nodes.
|
||||
*/
|
||||
if (!IsCoordinator())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
EnsureCoordinator();
|
||||
|
||||
List *originalObjects = stmt->objects;
|
||||
|
||||
|
@ -178,41 +171,8 @@ PreprocessGrantOnSchemaStmt(Node *node, const char *queryString,
|
|||
|
||||
stmt->objects = originalObjects;
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, list_make1(sql));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/metadata_cache.h"
|
||||
#include "distributed/metadata_sync.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "distributed/worker_create_or_replace.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "utils/builtins.h"
|
||||
|
@ -32,6 +33,7 @@
|
|||
/* Local functions forward declarations for helper functions */
|
||||
static bool OptionsSpecifyOwnedBy(List *optionList, Oid *ownedByTableId);
|
||||
static Oid SequenceUsedInDistributedTable(const ObjectAddress *sequenceAddress);
|
||||
static List * FilterDistributedSequences(GrantStmt *stmt);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -226,7 +228,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 +260,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)
|
||||
{
|
||||
|
@ -660,6 +662,97 @@ PostprocessAlterSequenceOwnerStmt(Node *node, const char *queryString)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessGrantOnSequenceStmt 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 to grant
|
||||
* on distributed sequences.
|
||||
*/
|
||||
List *
|
||||
PreprocessGrantOnSequenceStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_SEQUENCE);
|
||||
|
||||
if (creating_extension)
|
||||
{
|
||||
/*
|
||||
* extensions should be created separately on the workers, sequences 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;
|
||||
}
|
||||
|
||||
List *distributedSequences = FilterDistributedSequences(stmt);
|
||||
|
||||
if (list_length(distributedSequences) == 0)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
GrantStmt *stmtCopy = copyObject(stmt);
|
||||
stmtCopy->objects = distributedSequences;
|
||||
|
||||
/*
|
||||
* if the original command was targeting schemas, we have expanded to the distributed
|
||||
* sequences in these schemas through FilterDistributedSequences.
|
||||
*/
|
||||
stmtCopy->targtype = ACL_TARGET_OBJECT;
|
||||
|
||||
QualifyTreeNode((Node *) stmtCopy);
|
||||
|
||||
char *sql = DeparseTreeNode((Node *) stmtCopy);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION, (void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_METADATA_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PostprocessGrantOnSequenceStmt makes sure dependencies of each
|
||||
* distributed sequence in the statement exist on all nodes
|
||||
*/
|
||||
List *
|
||||
PostprocessGrantOnSequenceStmt(Node *node, const char *queryString)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_SEQUENCE);
|
||||
|
||||
List *distributedSequences = FilterDistributedSequences(stmt);
|
||||
|
||||
if (list_length(distributedSequences) == 0)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
RangeVar *sequence = NULL;
|
||||
foreach_ptr(sequence, distributedSequences)
|
||||
{
|
||||
ObjectAddress sequenceAddress = { 0 };
|
||||
Oid sequenceOid = RangeVarGetRelid(sequence, NoLock, false);
|
||||
ObjectAddressSet(sequenceAddress, RelationRelationId, sequenceOid);
|
||||
EnsureDependenciesExistOnAllNodes(&sequenceAddress);
|
||||
}
|
||||
return NIL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateBackupNameForSequenceCollision generates a new sequence name for an existing
|
||||
* sequence. The name is generated in such a way that the new name doesn't overlap with
|
||||
|
@ -702,6 +795,96 @@ GenerateBackupNameForSequenceCollision(const ObjectAddress *address)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* FilterDistributedSequences determines and returns a list of distributed sequences
|
||||
* RangeVar-s from given grant statement.
|
||||
* - If the stmt's targtype is ACL_TARGET_OBJECT, i.e. of the form GRANT ON SEQUENCE ...
|
||||
* it returns the distributed sequences in the list of sequences in the statement
|
||||
* - If targtype is ACL_TARGET_ALL_IN_SCHEMA, i.e. GRANT ON ALL SEQUENCES IN SCHEMA ...
|
||||
* it expands the ALL IN SCHEMA to the actual sequences, and returns the distributed
|
||||
* sequences from those.
|
||||
*/
|
||||
static List *
|
||||
FilterDistributedSequences(GrantStmt *stmt)
|
||||
{
|
||||
bool grantOnSequenceCommand = (stmt->targtype == ACL_TARGET_OBJECT &&
|
||||
stmt->objtype == OBJECT_SEQUENCE);
|
||||
bool grantOnAllSequencesInSchemaCommand = (stmt->targtype ==
|
||||
ACL_TARGET_ALL_IN_SCHEMA &&
|
||||
stmt->objtype == OBJECT_SEQUENCE);
|
||||
|
||||
/* we are only interested in sequence level grants */
|
||||
if (!grantOnSequenceCommand && !grantOnAllSequencesInSchemaCommand)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
List *grantSequenceList = NIL;
|
||||
|
||||
if (grantOnAllSequencesInSchemaCommand)
|
||||
{
|
||||
/* iterate over all namespace names provided to get their oid's */
|
||||
List *namespaceOidList = NIL;
|
||||
Value *namespaceValue = NULL;
|
||||
foreach_ptr(namespaceValue, stmt->objects)
|
||||
{
|
||||
char *nspname = strVal(namespaceValue);
|
||||
bool missing_ok = false;
|
||||
Oid namespaceOid = get_namespace_oid(nspname, missing_ok);
|
||||
namespaceOidList = list_append_unique_oid(namespaceOidList, namespaceOid);
|
||||
}
|
||||
|
||||
/*
|
||||
* iterate over all distributed sequences to filter the ones
|
||||
* that belong to one of the namespaces from above
|
||||
*/
|
||||
List *distributedSequenceList = DistributedSequenceList();
|
||||
ObjectAddress *sequenceAddress = NULL;
|
||||
foreach_ptr(sequenceAddress, distributedSequenceList)
|
||||
{
|
||||
Oid namespaceOid = get_rel_namespace(sequenceAddress->objectId);
|
||||
|
||||
/*
|
||||
* if this distributed sequence's schema is one of the schemas
|
||||
* specified in the GRANT .. ALL SEQUENCES IN SCHEMA ..
|
||||
* add it to the list
|
||||
*/
|
||||
if (list_member_oid(namespaceOidList, namespaceOid))
|
||||
{
|
||||
RangeVar *distributedSequence = makeRangeVar(get_namespace_name(
|
||||
namespaceOid),
|
||||
get_rel_name(
|
||||
sequenceAddress->objectId),
|
||||
-1);
|
||||
grantSequenceList = lappend(grantSequenceList, distributedSequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool missing_ok = false;
|
||||
RangeVar *sequenceRangeVar = NULL;
|
||||
foreach_ptr(sequenceRangeVar, stmt->objects)
|
||||
{
|
||||
ObjectAddress sequenceAddress = { 0 };
|
||||
Oid sequenceOid = RangeVarGetRelid(sequenceRangeVar, NoLock, missing_ok);
|
||||
ObjectAddressSet(sequenceAddress, RelationRelationId, sequenceOid);
|
||||
|
||||
/*
|
||||
* if this sequence from GRANT .. ON SEQUENCE .. is a distributed
|
||||
* sequence, add it to the list
|
||||
*/
|
||||
if (IsObjectDistributed(&sequenceAddress))
|
||||
{
|
||||
grantSequenceList = lappend(grantSequenceList, sequenceRangeVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return grantSequenceList;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RenameExistingSequenceWithDifferentTypeIfExists renames the sequence's type if
|
||||
* that sequence exists and the desired sequence type is different than it's type.
|
||||
|
|
|
@ -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);
|
||||
|
@ -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);
|
||||
|
|
|
@ -10,13 +10,129 @@
|
|||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "safe_lib.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "commands/defrem.h"
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/connection_management.h"
|
||||
#include "distributed/pg_version_constants.h"
|
||||
#include "distributed/version_compat.h"
|
||||
#include "libpq-fe.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
|
||||
/* placeholder for ProcessCreateSubscriptionStmt */
|
||||
static char * GenerateConninfoWithAuth(char *conninfo);
|
||||
|
||||
/*
|
||||
* ProcessCreateSubscriptionStmt looks for a special citus_use_authinfo option.
|
||||
* If it is set to true, then we'll expand the node's authinfo into the create
|
||||
* statement (see GenerateConninfoWithAuth).
|
||||
*/
|
||||
Node *
|
||||
ProcessCreateSubscriptionStmt(CreateSubscriptionStmt *createSubStmt)
|
||||
{
|
||||
ListCell *currCell = NULL;
|
||||
#if PG_VERSION_NUM < PG_VERSION_13
|
||||
ListCell *prevCell = NULL;
|
||||
#endif
|
||||
bool useAuthinfo = false;
|
||||
|
||||
foreach(currCell, createSubStmt->options)
|
||||
{
|
||||
DefElem *defElem = (DefElem *) lfirst(currCell);
|
||||
|
||||
if (strcmp(defElem->defname, "citus_use_authinfo") == 0)
|
||||
{
|
||||
useAuthinfo = defGetBoolean(defElem);
|
||||
|
||||
createSubStmt->options = list_delete_cell_compat(createSubStmt->options,
|
||||
currCell,
|
||||
prevCell);
|
||||
|
||||
break;
|
||||
}
|
||||
#if PG_VERSION_NUM < PG_VERSION_13
|
||||
prevCell = currCell;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (useAuthinfo)
|
||||
{
|
||||
createSubStmt->conninfo = GenerateConninfoWithAuth(createSubStmt->conninfo);
|
||||
}
|
||||
|
||||
return (Node *) createSubStmt;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateConninfoWithAuth extracts the host and port from the provided libpq
|
||||
* conninfo string, using them to find an appropriate authinfo for the target
|
||||
* host. If such an authinfo is found, it is added to the (repalloc'd) string,
|
||||
* which is then returned.
|
||||
*/
|
||||
static char *
|
||||
GenerateConninfoWithAuth(char *conninfo)
|
||||
{
|
||||
StringInfo connInfoWithAuth = makeStringInfo();
|
||||
char *host = NULL, *user = NULL;
|
||||
int32 port = -1;
|
||||
PQconninfoOption *option = NULL, *optionArray = NULL;
|
||||
|
||||
optionArray = PQconninfoParse(conninfo, NULL);
|
||||
if (optionArray == NULL)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("not a valid libpq connection info string: %s",
|
||||
conninfo)));
|
||||
}
|
||||
|
||||
for (option = optionArray; option->keyword != NULL; option++)
|
||||
{
|
||||
if (option->val == NULL || option->val[0] == '\0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(option->keyword, "host") == 0)
|
||||
{
|
||||
host = option->val;
|
||||
}
|
||||
else if (strcmp(option->keyword, "port") == 0)
|
||||
{
|
||||
port = pg_atoi(option->val, 4, 0);
|
||||
}
|
||||
else if (strcmp(option->keyword, "user") == 0)
|
||||
{
|
||||
user = option->val;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* In case of repetition of parameters in connection strings, last value
|
||||
* wins. So first add the provided connection string, then global
|
||||
* connection parameters, then node specific ones.
|
||||
*
|
||||
* Note that currently lists of parameters in pg_dist_authnode and
|
||||
* citus.node_conninfo do not overlap.
|
||||
*
|
||||
* The only overlapping parameter between these three lists is
|
||||
* connect_timeout, which is assigned in conninfo (generated
|
||||
* by CreateShardMoveSubscription) and is also allowed in
|
||||
* citus.node_conninfo. Prioritizing the value in citus.node_conninfo
|
||||
* over conninfo gives user the power to control this value.
|
||||
*/
|
||||
appendStringInfo(connInfoWithAuth, "%s %s", conninfo, NodeConninfo);
|
||||
if (host != NULL && port > 0 && user != NULL)
|
||||
{
|
||||
char *nodeAuthInfo = GetAuthinfo(host, port, user);
|
||||
appendStringInfo(connInfoWithAuth, " %s", nodeAuthInfo);
|
||||
}
|
||||
|
||||
PQconninfoFree(optionArray);
|
||||
|
||||
return connInfoWithAuth->data;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -2563,6 +2600,7 @@ ErrorIfUnsupportedConstraint(Relation relation, char distributionMethod,
|
|||
* ALTER TABLE ADD|DROP CONSTRAINT
|
||||
* ALTER TABLE REPLICA IDENTITY
|
||||
* ALTER TABLE SET ()
|
||||
* ALTER TABLE ENABLE|DISABLE|NO FORCE|FORCE ROW LEVEL SECURITY
|
||||
* ALTER TABLE RESET ()
|
||||
* ALTER TABLE ENABLE/DISABLE TRIGGER (if enable_unsafe_triggers is not set, we only support triggers for citus local tables)
|
||||
*/
|
||||
|
@ -2906,6 +2944,10 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement)
|
|||
case AT_SetNotNull:
|
||||
case AT_ReplicaIdentity:
|
||||
case AT_ChangeOwner:
|
||||
case AT_EnableRowSecurity:
|
||||
case AT_DisableRowSecurity:
|
||||
case AT_ForceRowSecurity:
|
||||
case AT_NoForceRowSecurity:
|
||||
case AT_ValidateConstraint:
|
||||
case AT_DropConstraint: /* we do the check for invalidation in AlterTableDropsForeignKey */
|
||||
#if PG_VERSION_NUM >= PG_VERSION_14
|
||||
|
@ -2950,6 +2992,7 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement)
|
|||
errdetail("Only ADD|DROP COLUMN, SET|DROP NOT NULL, "
|
||||
"SET|DROP DEFAULT, ADD|DROP|VALIDATE CONSTRAINT, "
|
||||
"SET (), RESET (), "
|
||||
"ENABLE|DISABLE|NO FORCE|FORCE ROW LEVEL SECURITY, "
|
||||
"ATTACH|DETACH PARTITION and TYPE subcommands "
|
||||
"are supported.")));
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -612,6 +159,11 @@ CreateTypeStmtByObjectAddress(const ObjectAddress *address)
|
|||
return (Node *) RecreateCompositeTypeStmt(address->objectId);
|
||||
}
|
||||
|
||||
case TYPTYPE_DOMAIN:
|
||||
{
|
||||
return (Node *) RecreateDomainStmt(address->objectId);
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
ereport(ERROR, (errmsg("unsupported type to generate create statement for"),
|
||||
|
@ -854,7 +406,7 @@ ObjectAddress
|
|||
AlterTypeSchemaStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TYPE);
|
||||
Assert(stmt->objectType == OBJECT_TYPE || stmt->objectType == OBJECT_DOMAIN);
|
||||
|
||||
List *names = (List *) stmt->object;
|
||||
|
||||
|
@ -1046,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
|
||||
*
|
||||
|
@ -1140,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.
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "commands/defrem.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"
|
||||
|
@ -53,6 +54,7 @@
|
|||
#include "distributed/listutils.h"
|
||||
#include "distributed/local_executor.h"
|
||||
#include "distributed/maintenanced.h"
|
||||
#include "distributed/multi_logical_replication.h"
|
||||
#include "distributed/multi_partitioning_utils.h"
|
||||
#if PG_VERSION_NUM < 140000
|
||||
#include "distributed/metadata_cache.h"
|
||||
|
@ -64,6 +66,7 @@
|
|||
#include "distributed/multi_physical_planner.h"
|
||||
#include "distributed/reference_table_utils.h"
|
||||
#include "distributed/resource_lock.h"
|
||||
#include "distributed/string_utils.h"
|
||||
#include "distributed/transmit.h"
|
||||
#include "distributed/version_compat.h"
|
||||
#include "distributed/worker_shard_visibility.h"
|
||||
|
@ -77,6 +80,7 @@
|
|||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
bool EnableDDLPropagation = true; /* ddl propagation is enabled */
|
||||
int CreateObjectPropagationMode = CREATE_OBJECT_PROPAGATION_IMMEDIATE;
|
||||
PropSetCmdBehavior PropagateSetCommands = PROPSETCMD_NONE; /* SET prop off */
|
||||
|
@ -164,7 +168,6 @@ multi_ProcessUtility(PlannedStmt *pstmt,
|
|||
parsetree = pstmt->utilityStmt;
|
||||
|
||||
if (IsA(parsetree, TransactionStmt) ||
|
||||
IsA(parsetree, LockStmt) ||
|
||||
IsA(parsetree, ListenStmt) ||
|
||||
IsA(parsetree, NotifyStmt) ||
|
||||
IsA(parsetree, ExecuteStmt) ||
|
||||
|
@ -409,6 +412,31 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
|
|||
parsetree = ProcessCreateSubscriptionStmt(createSubStmt);
|
||||
}
|
||||
|
||||
if (IsA(parsetree, AlterSubscriptionStmt))
|
||||
{
|
||||
AlterSubscriptionStmt *alterSubStmt = (AlterSubscriptionStmt *) parsetree;
|
||||
if (!superuser() &&
|
||||
StringStartsWith(alterSubStmt->subname,
|
||||
SHARD_MOVE_SUBSCRIPTION_PREFIX))
|
||||
{
|
||||
ereport(ERROR, (
|
||||
errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||
errmsg("Only superusers can alter shard move subscriptions")));
|
||||
}
|
||||
}
|
||||
|
||||
if (IsA(parsetree, DropSubscriptionStmt))
|
||||
{
|
||||
DropSubscriptionStmt *dropSubStmt = (DropSubscriptionStmt *) parsetree;
|
||||
if (!superuser() &&
|
||||
StringStartsWith(dropSubStmt->subname, SHARD_MOVE_SUBSCRIPTION_PREFIX))
|
||||
{
|
||||
ereport(ERROR, (
|
||||
errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||
errmsg("Only superusers can drop shard move subscriptions")));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Process SET LOCAL and SET TRANSACTION statements in multi-statement
|
||||
* transactions.
|
||||
|
@ -505,6 +533,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.
|
||||
|
@ -525,6 +565,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);
|
||||
|
@ -575,7 +629,7 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
|
|||
errhint("You can manually create a database and its "
|
||||
"extensions on workers.")));
|
||||
}
|
||||
else if (IsA(parsetree, CreateRoleStmt))
|
||||
else if (IsA(parsetree, CreateRoleStmt) && !EnableCreateRolePropagation)
|
||||
{
|
||||
ereport(NOTICE, (errmsg("not propagating CREATE ROLE/USER commands to worker"
|
||||
" nodes"),
|
||||
|
@ -605,6 +659,24 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
|
|||
StopMaintenanceDaemon(MyDatabaseId);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure that dropping the role deletes the pg_dist_object entries. There is a
|
||||
* separate logic for roles, since roles are not included as dropped objects in the
|
||||
* drop event trigger. To handle it both on worker and coordinator nodes, it is not
|
||||
* implemented as a part of process functions but here.
|
||||
*/
|
||||
if (IsA(parsetree, DropRoleStmt))
|
||||
{
|
||||
DropRoleStmt *stmt = castNode(DropRoleStmt, parsetree);
|
||||
List *allDropRoles = stmt->roles;
|
||||
|
||||
List *distributedDropRoles = FilterDistributedRoles(allDropRoles);
|
||||
if (list_length(distributedDropRoles) > 0)
|
||||
{
|
||||
UnmarkRolesDistributed(distributedDropRoles);
|
||||
}
|
||||
}
|
||||
|
||||
pstmt->utilityStmt = parsetree;
|
||||
|
||||
PG_TRY();
|
||||
|
@ -714,6 +786,21 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
|
|||
{
|
||||
PostprocessAlterTableStmt(castNode(AlterTableStmt, parsetree));
|
||||
}
|
||||
if (IsA(parsetree, GrantStmt))
|
||||
{
|
||||
GrantStmt *grantStmt = (GrantStmt *) parsetree;
|
||||
if (grantStmt->targtype == ACL_TARGET_ALL_IN_SCHEMA)
|
||||
{
|
||||
/*
|
||||
* Grant .. IN SCHEMA causes a deadlock if we don't use local execution
|
||||
* because standard process utility processes the shard placements as well
|
||||
* and the row-level locks in pg_class will not be released until the current
|
||||
* transaction commits. We could skip the local shard placements after standard
|
||||
* process utility, but for simplicity we just prefer using local execution.
|
||||
*/
|
||||
SetLocalExecutionStatus(LOCAL_EXECUTION_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
DDLJob *ddlJob = NULL;
|
||||
foreach_ptr(ddlJob, ddlJobs)
|
||||
|
@ -1081,16 +1168,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;
|
||||
|
@ -1341,7 +1432,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;
|
||||
|
||||
|
@ -1592,10 +1683,9 @@ NodeDDLTaskList(TargetWorkerSet targets, List *commands)
|
|||
}
|
||||
|
||||
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
|
||||
ddlJob->targetRelationId = InvalidOid;
|
||||
ddlJob->targetObjectAddress = InvalidObjectAddress;
|
||||
ddlJob->metadataSyncCommand = NULL;
|
||||
ddlJob->taskList = list_make1(task);
|
||||
|
||||
return list_make1(ddlJob);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,706 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* 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 ||
|
||||
get_rel_relkind(viewOid) == RELKIND_MATVIEW);
|
||||
|
||||
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;
|
||||
}
|
|
@ -10,9 +10,12 @@
|
|||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/transam.h"
|
||||
#include "access/xact.h"
|
||||
#include "distributed/backend_data.h"
|
||||
#include "distributed/citus_safe_lib.h"
|
||||
#include "distributed/connection_management.h"
|
||||
#include "distributed/intermediate_result_pruning.h"
|
||||
#include "distributed/metadata_cache.h"
|
||||
#include "distributed/worker_manager.h"
|
||||
|
||||
|
@ -40,6 +43,7 @@ typedef struct ConnParamsInfo
|
|||
static ConnParamsInfo ConnParams;
|
||||
|
||||
/* helper functions for processing connection info */
|
||||
static ConnectionHashKey * GetEffectiveConnKey(ConnectionHashKey *key);
|
||||
static Size CalculateMaxSize(void);
|
||||
static int uri_prefix_length(const char *connstr);
|
||||
|
||||
|
@ -232,6 +236,7 @@ GetConnParams(ConnectionHashKey *key, char ***keywords, char ***values,
|
|||
* already we can add a pointer to the runtimeValues.
|
||||
*/
|
||||
char nodePortString[12] = "";
|
||||
ConnectionHashKey *effectiveKey = GetEffectiveConnKey(key);
|
||||
|
||||
StringInfo applicationName = makeStringInfo();
|
||||
appendStringInfo(applicationName, "%s%ld", CITUS_APPLICATION_NAME_PREFIX,
|
||||
|
@ -260,10 +265,10 @@ GetConnParams(ConnectionHashKey *key, char ***keywords, char ***values,
|
|||
"application_name"
|
||||
};
|
||||
const char *runtimeValues[] = {
|
||||
key->hostname,
|
||||
effectiveKey->hostname,
|
||||
nodePortString,
|
||||
key->database,
|
||||
key->user,
|
||||
effectiveKey->database,
|
||||
effectiveKey->user,
|
||||
GetDatabaseEncodingName(),
|
||||
applicationName->data
|
||||
};
|
||||
|
@ -300,7 +305,7 @@ GetConnParams(ConnectionHashKey *key, char ***keywords, char ***values,
|
|||
errmsg("too many connParams entries")));
|
||||
}
|
||||
|
||||
pg_ltoa(key->port, nodePortString); /* populate node port string with port */
|
||||
pg_ltoa(effectiveKey->port, nodePortString); /* populate node port string with port */
|
||||
|
||||
/* first step: copy global parameters to beginning of array */
|
||||
for (Size paramIndex = 0; paramIndex < ConnParams.size; paramIndex++)
|
||||
|
@ -322,6 +327,58 @@ GetConnParams(ConnectionHashKey *key, char ***keywords, char ***values,
|
|||
MemoryContextStrdup(context, runtimeValues[runtimeParamIndex]);
|
||||
}
|
||||
|
||||
/* we look up authinfo by original key, not effective one */
|
||||
char *authinfo = GetAuthinfo(key->hostname, key->port, key->user);
|
||||
char *pqerr = NULL;
|
||||
PQconninfoOption *optionArray = PQconninfoParse(authinfo, &pqerr);
|
||||
if (optionArray == NULL)
|
||||
{
|
||||
/* PQconninfoParse failed, it's unsafe to continue as this has caused segfaults in production */
|
||||
if (pqerr == NULL)
|
||||
{
|
||||
/* parse failed without an error message, treat as OOM error */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OUT_OF_MEMORY),
|
||||
errmsg("out of memory"),
|
||||
errdetail("Failed to parse authentication information via libpq")));
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Parse error, should not be possible as the validity is checked upon insert into pg_dist_authinfo,
|
||||
* however, better safe than sorry
|
||||
*/
|
||||
|
||||
/*
|
||||
* errmsg is populated by PQconninfoParse which requires us to free the message. Since we want to
|
||||
* incorporate the parse error into the detail of our message we need to copy the error message before
|
||||
* freeing it. Not freeing the message will leak memory.
|
||||
*/
|
||||
char *pqerrcopy = pstrdup(pqerr);
|
||||
PQfreemem(pqerr);
|
||||
|
||||
ereport(ERROR, (errmsg(
|
||||
"failed to parse node authentication information for %s@%s:%d",
|
||||
key->user, key->hostname, key->port),
|
||||
errdetail("%s", pqerrcopy)));
|
||||
}
|
||||
}
|
||||
|
||||
for (PQconninfoOption *option = optionArray; option->keyword != NULL; option++)
|
||||
{
|
||||
if (option->val == NULL || option->val[0] == '\0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
connKeywords[authParamsIdx] = MemoryContextStrdup(context, option->keyword);
|
||||
connValues[authParamsIdx] = MemoryContextStrdup(context, option->val);
|
||||
|
||||
authParamsIdx++;
|
||||
}
|
||||
|
||||
PQconninfoFree(optionArray);
|
||||
|
||||
/* final step: add terminal NULL, required by libpq */
|
||||
connKeywords[authParamsIdx] = connValues[authParamsIdx] = NULL;
|
||||
}
|
||||
|
@ -346,6 +403,116 @@ GetConnParam(const char *keyword)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetEffectiveConnKey checks whether there is any pooler configuration for the
|
||||
* provided key (host/port combination). The one case where this logic is not
|
||||
* applied is for loopback connections originating within the task tracker. If
|
||||
* a corresponding row is found in the poolinfo table, a modified (effective)
|
||||
* key is returned with the node, port, and dbname overridden, as applicable,
|
||||
* otherwise, the original key is returned unmodified.
|
||||
*/
|
||||
ConnectionHashKey *
|
||||
GetEffectiveConnKey(ConnectionHashKey *key)
|
||||
{
|
||||
PQconninfoOption *option = NULL, *optionArray = NULL;
|
||||
|
||||
if (!IsTransactionState())
|
||||
{
|
||||
/* we're in the task tracker, so should only see loopback */
|
||||
Assert(strncmp(LOCAL_HOST_NAME, key->hostname, MAX_NODE_LENGTH) == 0 &&
|
||||
PostPortNumber == key->port);
|
||||
return key;
|
||||
}
|
||||
|
||||
WorkerNode *worker = FindWorkerNode(key->hostname, key->port);
|
||||
if (worker == NULL)
|
||||
{
|
||||
/* this can be hit when the key references an unknown node */
|
||||
return key;
|
||||
}
|
||||
|
||||
char *poolinfo = GetPoolinfoViaCatalog(worker->nodeId);
|
||||
if (poolinfo == NULL)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
/* copy the key to provide defaults for all fields */
|
||||
ConnectionHashKey *effectiveKey = palloc(sizeof(ConnectionHashKey));
|
||||
*effectiveKey = *key;
|
||||
|
||||
optionArray = PQconninfoParse(poolinfo, NULL);
|
||||
for (option = optionArray; option->keyword != NULL; option++)
|
||||
{
|
||||
if (option->val == NULL || option->val[0] == '\0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(option->keyword, "host") == 0)
|
||||
{
|
||||
strlcpy(effectiveKey->hostname, option->val, MAX_NODE_LENGTH);
|
||||
}
|
||||
else if (strcmp(option->keyword, "port") == 0)
|
||||
{
|
||||
effectiveKey->port = pg_atoi(option->val, 4, 0);
|
||||
}
|
||||
else if (strcmp(option->keyword, "dbname") == 0)
|
||||
{
|
||||
/* permit dbname for poolers which can key pools based on dbname */
|
||||
strlcpy(effectiveKey->database, option->val, NAMEDATALEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(FATAL, (errmsg("unrecognized poolinfo keyword")));
|
||||
}
|
||||
}
|
||||
|
||||
PQconninfoFree(optionArray);
|
||||
|
||||
return effectiveKey;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetAuthinfo simply returns the string representation of authentication info
|
||||
* for a specified hostname/port/user combination. If the current transaction
|
||||
* is valid, then we use the catalog, otherwise a shared memory hash is used,
|
||||
* a mode that is currently only useful for getting authentication information
|
||||
* to the Task Tracker, which lacks a database connection and transaction.
|
||||
*/
|
||||
char *
|
||||
GetAuthinfo(char *hostname, int32 port, char *user)
|
||||
{
|
||||
char *authinfo = NULL;
|
||||
bool isLoopback = (strncmp(LOCAL_HOST_NAME, hostname, MAX_NODE_LENGTH) == 0 &&
|
||||
PostPortNumber == port);
|
||||
|
||||
if (IsTransactionState())
|
||||
{
|
||||
int64 nodeId = WILDCARD_NODE_ID;
|
||||
|
||||
/* -1 is a special value for loopback connections (task tracker) */
|
||||
if (isLoopback)
|
||||
{
|
||||
nodeId = LOCALHOST_NODE_ID;
|
||||
}
|
||||
else
|
||||
{
|
||||
WorkerNode *worker = FindWorkerNode(hostname, port);
|
||||
if (worker != NULL)
|
||||
{
|
||||
nodeId = worker->nodeId;
|
||||
}
|
||||
}
|
||||
|
||||
authinfo = GetAuthinfoViaCatalog(user, nodeId);
|
||||
}
|
||||
|
||||
return (authinfo != NULL) ? authinfo : "";
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CalculateMaxSize simply counts the number of elements returned by
|
||||
* PQconnDefaults, including the final NULL. This helps us know how space would
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "distributed/listutils.h"
|
||||
#include "distributed/log_utils.h"
|
||||
#include "distributed/remote_commands.h"
|
||||
#include "distributed/errormessage.h"
|
||||
#include "distributed/cancel_utils.h"
|
||||
#include "lib/stringinfo.h"
|
||||
#include "miscadmin.h"
|
||||
|
@ -1115,3 +1116,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);
|
||||
}
|
||||
|
|
|
@ -79,8 +79,8 @@ static void deparse_index_columns(StringInfo buffer, List *indexParameterList,
|
|||
List *deparseContext);
|
||||
static void AppendStorageParametersToString(StringInfo stringBuffer,
|
||||
List *optionList);
|
||||
static const char * convert_aclright_to_string(int aclright);
|
||||
static void simple_quote_literal(StringInfo buf, const char *val);
|
||||
static char * flatten_reloptions(Oid relid);
|
||||
static void AddVacuumParams(ReindexStmt *reindexStmt, StringInfo buffer);
|
||||
|
||||
|
||||
|
@ -377,6 +377,14 @@ pg_get_tableschemadef_string(Oid tableRelationId, IncludeSequenceDefaults
|
|||
atttypmod);
|
||||
appendStringInfoString(&buffer, attributeTypeName);
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_14
|
||||
if (CompressionMethodIsValid(attributeForm->attcompression))
|
||||
{
|
||||
appendStringInfo(&buffer, " COMPRESSION %s",
|
||||
GetCompressionMethodName(attributeForm->attcompression));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* if this column has a default value, append the default value */
|
||||
if (attributeForm->atthasdef)
|
||||
{
|
||||
|
@ -448,14 +456,6 @@ pg_get_tableschemadef_string(Oid tableRelationId, IncludeSequenceDefaults
|
|||
appendStringInfoString(&buffer, " NOT NULL");
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_14
|
||||
if (CompressionMethodIsValid(attributeForm->attcompression))
|
||||
{
|
||||
appendStringInfo(&buffer, " COMPRESSION %s",
|
||||
GetCompressionMethodName(attributeForm->attcompression));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (attributeForm->attcollation != InvalidOid &&
|
||||
attributeForm->attcollation != DEFAULT_COLLATION_OID)
|
||||
{
|
||||
|
@ -1063,6 +1063,138 @@ pg_get_indexclusterdef_string(Oid indexRelationId)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* pg_get_table_grants returns a list of sql statements which recreate the
|
||||
* permissions for a specific table.
|
||||
*
|
||||
* This function is modeled after aclexplode(), don't change too heavily.
|
||||
*/
|
||||
List *
|
||||
pg_get_table_grants(Oid relationId)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
StringInfoData buffer;
|
||||
List *defs = NIL;
|
||||
bool isNull = false;
|
||||
|
||||
Relation relation = relation_open(relationId, AccessShareLock);
|
||||
char *relationName = generate_relation_name(relationId, NIL);
|
||||
|
||||
initStringInfo(&buffer);
|
||||
|
||||
/* lookup all table level grants */
|
||||
HeapTuple classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId));
|
||||
if (!HeapTupleIsValid(classTuple))
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_TABLE),
|
||||
errmsg("relation with OID %u does not exist",
|
||||
relationId)));
|
||||
}
|
||||
|
||||
Datum aclDatum = SysCacheGetAttr(RELOID, classTuple, Anum_pg_class_relacl,
|
||||
&isNull);
|
||||
|
||||
ReleaseSysCache(classTuple);
|
||||
|
||||
if (!isNull)
|
||||
{
|
||||
|
||||
/*
|
||||
* First revoke all default permissions, so we can start adding the
|
||||
* exact permissions from the master. Note that we only do so if there
|
||||
* are any actual grants; an empty grant set signals default
|
||||
* permissions.
|
||||
*
|
||||
* Note: This doesn't work correctly if default permissions have been
|
||||
* changed with ALTER DEFAULT PRIVILEGES - but that's hard to fix
|
||||
* properly currently.
|
||||
*/
|
||||
appendStringInfo(&buffer, "REVOKE ALL ON %s FROM PUBLIC",
|
||||
relationName);
|
||||
defs = lappend(defs, pstrdup(buffer.data));
|
||||
resetStringInfo(&buffer);
|
||||
|
||||
/* iterate through the acl datastructure, emit GRANTs */
|
||||
|
||||
Acl *acl = DatumGetAclP(aclDatum);
|
||||
AclItem *aidat = ACL_DAT(acl);
|
||||
|
||||
int offtype = -1;
|
||||
int i = 0;
|
||||
while (i < ACL_NUM(acl))
|
||||
{
|
||||
AclItem *aidata = NULL;
|
||||
AclMode priv_bit = 0;
|
||||
|
||||
offtype++;
|
||||
|
||||
if (offtype == N_ACL_RIGHTS)
|
||||
{
|
||||
offtype = 0;
|
||||
i++;
|
||||
if (i >= ACL_NUM(acl)) /* done */
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
aidata = &aidat[i];
|
||||
priv_bit = 1 << offtype;
|
||||
|
||||
if (ACLITEM_GET_PRIVS(*aidata) & priv_bit)
|
||||
{
|
||||
const char *roleName = NULL;
|
||||
const char *withGrant = "";
|
||||
|
||||
if (aidata->ai_grantee != 0)
|
||||
{
|
||||
|
||||
HeapTuple htup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(aidata->ai_grantee));
|
||||
if (HeapTupleIsValid(htup))
|
||||
{
|
||||
Form_pg_authid authForm = ((Form_pg_authid) GETSTRUCT(htup));
|
||||
|
||||
roleName = quote_identifier(NameStr(authForm->rolname));
|
||||
|
||||
ReleaseSysCache(htup);
|
||||
}
|
||||
else
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for role %u", aidata->ai_grantee);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
roleName = "PUBLIC";
|
||||
}
|
||||
|
||||
if ((ACLITEM_GET_GOPTIONS(*aidata) & priv_bit) != 0)
|
||||
{
|
||||
withGrant = " WITH GRANT OPTION";
|
||||
}
|
||||
|
||||
appendStringInfo(&buffer, "GRANT %s ON %s TO %s%s",
|
||||
convert_aclright_to_string(priv_bit),
|
||||
relationName,
|
||||
roleName,
|
||||
withGrant);
|
||||
|
||||
defs = lappend(defs, pstrdup(buffer.data));
|
||||
|
||||
resetStringInfo(&buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resetStringInfo(&buffer);
|
||||
|
||||
relation_close(relation, NoLock);
|
||||
return defs;
|
||||
/* *INDENT-ON* */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* generate_qualified_relation_name computes the schema-qualified name to display for a
|
||||
* relation specified by OID.
|
||||
|
@ -1157,6 +1289,45 @@ AppendStorageParametersToString(StringInfo stringBuffer, List *optionList)
|
|||
}
|
||||
|
||||
|
||||
/* copy of postgresql's function, which is static as well */
|
||||
static const char *
|
||||
convert_aclright_to_string(int aclright)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
switch (aclright)
|
||||
{
|
||||
case ACL_INSERT:
|
||||
return "INSERT";
|
||||
case ACL_SELECT:
|
||||
return "SELECT";
|
||||
case ACL_UPDATE:
|
||||
return "UPDATE";
|
||||
case ACL_DELETE:
|
||||
return "DELETE";
|
||||
case ACL_TRUNCATE:
|
||||
return "TRUNCATE";
|
||||
case ACL_REFERENCES:
|
||||
return "REFERENCES";
|
||||
case ACL_TRIGGER:
|
||||
return "TRIGGER";
|
||||
case ACL_EXECUTE:
|
||||
return "EXECUTE";
|
||||
case ACL_USAGE:
|
||||
return "USAGE";
|
||||
case ACL_CREATE:
|
||||
return "CREATE";
|
||||
case ACL_CREATE_TEMP:
|
||||
return "TEMPORARY";
|
||||
case ACL_CONNECT:
|
||||
return "CONNECT";
|
||||
default:
|
||||
elog(ERROR, "unrecognized aclright: %d", aclright);
|
||||
return NULL;
|
||||
}
|
||||
/* *INDENT-ON* */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* contain_nextval_expression_walker walks over expression tree and returns
|
||||
* true if it contains call to 'nextval' function.
|
||||
|
@ -1225,13 +1396,53 @@ pg_get_replica_identity_command(Oid tableRelationId)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* pg_get_row_level_security_commands function returns the required ALTER .. TABLE
|
||||
* commands to define the row level security settings for a relation.
|
||||
*/
|
||||
List *
|
||||
pg_get_row_level_security_commands(Oid relationId)
|
||||
{
|
||||
StringInfoData buffer;
|
||||
List *commands = NIL;
|
||||
|
||||
initStringInfo(&buffer);
|
||||
|
||||
Relation relation = table_open(relationId, AccessShareLock);
|
||||
|
||||
if (relation->rd_rel->relrowsecurity)
|
||||
{
|
||||
char *relationName = generate_qualified_relation_name(relationId);
|
||||
|
||||
appendStringInfo(&buffer, "ALTER TABLE %s ENABLE ROW LEVEL SECURITY",
|
||||
relationName);
|
||||
commands = lappend(commands, pstrdup(buffer.data));
|
||||
resetStringInfo(&buffer);
|
||||
}
|
||||
|
||||
if (relation->rd_rel->relforcerowsecurity)
|
||||
{
|
||||
char *relationName = generate_qualified_relation_name(relationId);
|
||||
|
||||
appendStringInfo(&buffer, "ALTER TABLE %s FORCE ROW LEVEL SECURITY",
|
||||
relationName);
|
||||
commands = lappend(commands, pstrdup(buffer.data));
|
||||
resetStringInfo(&buffer);
|
||||
}
|
||||
|
||||
table_close(relation, AccessShareLock);
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate a C string representing a relation's reloptions, or NULL if none.
|
||||
*
|
||||
* This function comes from PostgreSQL source code in
|
||||
* src/backend/utils/adt/ruleutils.c
|
||||
*/
|
||||
static char *
|
||||
char *
|
||||
flatten_reloptions(Oid relid)
|
||||
{
|
||||
char *result = NULL;
|
||||
|
|
|
@ -0,0 +1,626 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* deparse_domain_stmts.c
|
||||
* Functions to turn all Statement structures related to domains back
|
||||
* into sql.
|
||||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/htup_details.h"
|
||||
#include "catalog/heap.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
#include "parser/parse_collate.h"
|
||||
#include "parser/parse_expr.h"
|
||||
#include "parser/parse_node.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/ruleutils.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/namespace_utils.h"
|
||||
|
||||
static void AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName,
|
||||
TypeName *typeName);
|
||||
static Node * replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref);
|
||||
static Node * TransformDefaultExpr(Node *expr, List *domainName, TypeName *typeName);
|
||||
static Node * TransformConstraintExpr(Node *expr, TypeName *typeName);
|
||||
static CoerceToDomainValue * GetCoerceDomainValue(TypeName *typeName);
|
||||
static char * TypeNameAsIdentifier(TypeName *typeName);
|
||||
|
||||
static Oid DomainGetBaseTypeOid(List *names, int32 *baseTypeMod);
|
||||
|
||||
static void AppendAlterDomainStmtSetDefault(StringInfo buf, AlterDomainStmt *stmt);
|
||||
static void AppendAlterDomainStmtAddConstraint(StringInfo buf, AlterDomainStmt *stmt);
|
||||
static void AppendAlterDomainStmtDropConstraint(StringInfo buf, AlterDomainStmt *stmt);
|
||||
|
||||
|
||||
/*
|
||||
* DeparseCreateDomainStmt returns the sql representation for the CREATE DOMAIN statement.
|
||||
*/
|
||||
char *
|
||||
DeparseCreateDomainStmt(Node *node)
|
||||
{
|
||||
CreateDomainStmt *stmt = castNode(CreateDomainStmt, node);
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
const char *domainIdentifier = NameListToQuotedString(stmt->domainname);
|
||||
const char *typeIdentifier = TypeNameAsIdentifier(stmt->typeName);
|
||||
appendStringInfo(&buf, "CREATE DOMAIN %s AS %s", domainIdentifier, typeIdentifier);
|
||||
|
||||
if (stmt->collClause)
|
||||
{
|
||||
const char *collateIdentifier =
|
||||
NameListToQuotedString(stmt->collClause->collname);
|
||||
appendStringInfo(&buf, " COLLATE %s", collateIdentifier);
|
||||
}
|
||||
|
||||
Constraint *constraint = NULL;
|
||||
foreach_ptr(constraint, stmt->constraints)
|
||||
{
|
||||
AppendConstraint(&buf, constraint, stmt->domainname, stmt->typeName);
|
||||
}
|
||||
|
||||
appendStringInfoString(&buf, ";");
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TypeNameAsIdentifier returns the sql identifier of a TypeName. This is more complex
|
||||
* than concatenating the schema name and typename since certain types contain modifiers
|
||||
* that need to be correctly represented.
|
||||
*/
|
||||
static char *
|
||||
TypeNameAsIdentifier(TypeName *typeName)
|
||||
{
|
||||
int32 typmod = 0;
|
||||
Oid typeOid = InvalidOid;
|
||||
bits16 formatFlags = FORMAT_TYPE_TYPEMOD_GIVEN | FORMAT_TYPE_FORCE_QUALIFY;
|
||||
|
||||
typenameTypeIdAndMod(NULL, typeName, &typeOid, &typmod);
|
||||
|
||||
return format_type_extended(typeOid, typmod, formatFlags);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseDropDomainStmt returns the sql for teh DROP DOMAIN statement.
|
||||
*/
|
||||
char *
|
||||
DeparseDropDomainStmt(Node *node)
|
||||
{
|
||||
DropStmt *stmt = castNode(DropStmt, node);
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
appendStringInfoString(&buf, "DROP DOMAIN ");
|
||||
if (stmt->missing_ok)
|
||||
{
|
||||
appendStringInfoString(&buf, "IF EXISTS ");
|
||||
}
|
||||
|
||||
TypeName *domainName = NULL;
|
||||
bool first = true;
|
||||
foreach_ptr(domainName, stmt->objects)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
appendStringInfoString(&buf, ", ");
|
||||
}
|
||||
first = false;
|
||||
|
||||
const char *identifier = NameListToQuotedString(domainName->names);
|
||||
appendStringInfoString(&buf, identifier);
|
||||
}
|
||||
|
||||
if (stmt->behavior == DROP_CASCADE)
|
||||
{
|
||||
appendStringInfoString(&buf, " CASCADE");
|
||||
}
|
||||
|
||||
appendStringInfoString(&buf, ";");
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseAlterDomainStmt returns the sql representation of the DOMAIN specific ALTER
|
||||
* statements.
|
||||
*/
|
||||
char *
|
||||
DeparseAlterDomainStmt(Node *node)
|
||||
{
|
||||
AlterDomainStmt *stmt = castNode(AlterDomainStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
appendStringInfo(&buf, "ALTER DOMAIN %s ", NameListToQuotedString(stmt->typeName));
|
||||
switch (stmt->subtype)
|
||||
{
|
||||
case 'T': /* SET DEFAULT */
|
||||
{
|
||||
AppendAlterDomainStmtSetDefault(&buf, stmt);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'N': /* DROP NOT NULL */
|
||||
{
|
||||
appendStringInfoString(&buf, "DROP NOT NULL");
|
||||
break;
|
||||
}
|
||||
|
||||
case 'O': /* SET NOT NULL */
|
||||
{
|
||||
appendStringInfoString(&buf, "SET NOT NULL");
|
||||
break;
|
||||
}
|
||||
|
||||
case 'C': /* ADD [CONSTRAINT name] */
|
||||
{
|
||||
AppendAlterDomainStmtAddConstraint(&buf, stmt);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'X': /* DROP CONSTRAINT */
|
||||
{
|
||||
AppendAlterDomainStmtDropConstraint(&buf, stmt);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'V': /* VALIDATE CONSTRAINT */
|
||||
{
|
||||
appendStringInfo(&buf, "VALIDATE CONSTRAINT %s",
|
||||
quote_identifier(stmt->name));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
elog(ERROR, "unsupported alter domain statement for distribution");
|
||||
}
|
||||
}
|
||||
|
||||
appendStringInfoChar(&buf, ';');
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseDomainRenameConstraintStmt returns the sql representation of the domain
|
||||
* constraint renaming.
|
||||
*/
|
||||
char *
|
||||
DeparseDomainRenameConstraintStmt(Node *node)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
char *domainIdentifier = NameListToQuotedString(castNode(List, stmt->object));
|
||||
appendStringInfo(&buf, "ALTER DOMAIN %s RENAME CONSTRAINT %s TO %s;",
|
||||
domainIdentifier,
|
||||
quote_identifier(stmt->subname),
|
||||
quote_identifier(stmt->newname));
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseAlterDomainOwnerStmt returns the sql representation of the ALTER DOMAIN OWNER
|
||||
* statement.
|
||||
*/
|
||||
char *
|
||||
DeparseAlterDomainOwnerStmt(Node *node)
|
||||
{
|
||||
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
List *domainName = castNode(List, stmt->object);
|
||||
char *domainIdentifier = NameListToQuotedString(domainName);
|
||||
appendStringInfo(&buf, "ALTER DOMAIN %s OWNER TO %s;",
|
||||
domainIdentifier,
|
||||
RoleSpecString(stmt->newowner, true));
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseRenameDomainStmt returns the sql representation of the ALTER DOMAIN RENAME
|
||||
* statement.
|
||||
*/
|
||||
char *
|
||||
DeparseRenameDomainStmt(Node *node)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
List *domainName = castNode(List, stmt->object);
|
||||
char *domainIdentifier = NameListToQuotedString(domainName);
|
||||
appendStringInfo(&buf, "ALTER DOMAIN %s RENAME TO %s;",
|
||||
domainIdentifier,
|
||||
quote_identifier(stmt->newname));
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseAlterDomainSchemaStmt returns the sql representation of the ALTER DOMAIN SET
|
||||
* SCHEMA statement.
|
||||
*/
|
||||
char *
|
||||
DeparseAlterDomainSchemaStmt(Node *node)
|
||||
{
|
||||
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
List *domainName = castNode(List, stmt->object);
|
||||
char *domainIdentifier = NameListToQuotedString(domainName);
|
||||
appendStringInfo(&buf, "ALTER DOMAIN %s SET SCHEMA %s;",
|
||||
domainIdentifier,
|
||||
quote_identifier(stmt->newschema));
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DomainGetBaseTypeOid returns the type Oid and the type modifiers of the type underlying
|
||||
* a domain addresses by the namelist provided as the names argument. The type modifier is
|
||||
* only provided if the baseTypeMod pointer is a valid pointer on where to write the
|
||||
* modifier (not a NULL pointer).
|
||||
*
|
||||
* If the type cannot be found this function will raise a non-userfacing error. Care needs
|
||||
* to be taken by the caller that the domain is actually existing.
|
||||
*/
|
||||
static Oid
|
||||
DomainGetBaseTypeOid(List *names, int32 *baseTypeMod)
|
||||
{
|
||||
TypeName *domainName = makeTypeNameFromNameList(names);
|
||||
Oid domainoid = typenameTypeId(NULL, domainName);
|
||||
HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domainoid));
|
||||
if (!HeapTupleIsValid(tup))
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for type %u", domainoid);
|
||||
}
|
||||
Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
|
||||
Oid baseTypeOid = typTup->typbasetype;
|
||||
if (baseTypeMod)
|
||||
{
|
||||
*baseTypeMod = typTup->typtypmod;
|
||||
}
|
||||
ReleaseSysCache(tup);
|
||||
return baseTypeOid;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendAlterDomainStmtSetDefault is a helper function that appends the default value
|
||||
* portion of an ALTER DOMAIN statement that is changing the default value of the domain.
|
||||
*/
|
||||
static void
|
||||
AppendAlterDomainStmtSetDefault(StringInfo buf, AlterDomainStmt *stmt)
|
||||
{
|
||||
if (stmt->def == NULL)
|
||||
{
|
||||
/* no default expression is a DROP DEFAULT statment */
|
||||
appendStringInfoString(buf, "DROP DEFAULT");
|
||||
return;
|
||||
}
|
||||
|
||||
int32 baseTypMod = 0;
|
||||
Oid baseOid = DomainGetBaseTypeOid(stmt->typeName, &baseTypMod);
|
||||
TypeName *baseTypeName = makeTypeNameFromOid(baseOid, baseTypMod);
|
||||
|
||||
/* cook the default expression, without cooking we can't deparse */
|
||||
Node *expr = stmt->def;
|
||||
expr = TransformDefaultExpr(expr, stmt->typeName, baseTypeName);
|
||||
|
||||
/* deparse while the searchpath is cleared to force qualification of identifiers */
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
char *exprSql = deparse_expression(expr, NIL, true, true);
|
||||
PopOverrideSearchPath();
|
||||
|
||||
appendStringInfo(buf, "SET DEFAULT %s", exprSql);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendAlterDomainStmtAddConstraint is a helper function that appends the constraint
|
||||
* specification for an ALTER DOMAIN statement that adds a constraint to the domain.
|
||||
*/
|
||||
static void
|
||||
AppendAlterDomainStmtAddConstraint(StringInfo buf, AlterDomainStmt *stmt)
|
||||
{
|
||||
if (stmt->def == NULL || !IsA(stmt->def, Constraint))
|
||||
{
|
||||
ereport(ERROR, (errmsg("unable to deparse ALTER DOMAIN statement due to "
|
||||
"unexpected contents")));
|
||||
}
|
||||
|
||||
Constraint *constraint = castNode(Constraint, stmt->def);
|
||||
appendStringInfoString(buf, "ADD");
|
||||
|
||||
int32 baseTypMod = 0;
|
||||
Oid baseOid = DomainGetBaseTypeOid(stmt->typeName, &baseTypMod);
|
||||
TypeName *baseTypeName = makeTypeNameFromOid(baseOid, baseTypMod);
|
||||
|
||||
AppendConstraint(buf, constraint, stmt->typeName, baseTypeName);
|
||||
|
||||
if (!constraint->initially_valid)
|
||||
{
|
||||
appendStringInfoString(buf, " NOT VALID");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendAlterDomainStmtDropConstraint is a helper function that appends the DROP
|
||||
* CONSTRAINT part of an ALTER DOMAIN statement for an alter statement that drops a
|
||||
* constraint.
|
||||
*/
|
||||
static void
|
||||
AppendAlterDomainStmtDropConstraint(StringInfo buf, AlterDomainStmt *stmt)
|
||||
{
|
||||
appendStringInfoString(buf, "DROP CONSTRAINT ");
|
||||
|
||||
if (stmt->missing_ok)
|
||||
{
|
||||
appendStringInfoString(buf, "IF EXISTS ");
|
||||
}
|
||||
|
||||
appendStringInfoString(buf, quote_identifier(stmt->name));
|
||||
|
||||
if (stmt->behavior == DROP_CASCADE)
|
||||
{
|
||||
appendStringInfoString(buf, " CASCADE");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendConstraint is a helper function that appends a constraint specification to a sql
|
||||
* string that is adding a constraint.
|
||||
*
|
||||
* There are multiple places where a constraint specification is appended to sql strings.
|
||||
*
|
||||
* Given the complexities of serializing a constraint they all use this routine.
|
||||
*/
|
||||
static void
|
||||
AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName,
|
||||
TypeName *typeName)
|
||||
{
|
||||
if (constraint->conname)
|
||||
{
|
||||
appendStringInfo(buf, " CONSTRAINT %s", quote_identifier(constraint->conname));
|
||||
}
|
||||
|
||||
switch (constraint->contype)
|
||||
{
|
||||
case CONSTR_CHECK:
|
||||
{
|
||||
Node *expr = NULL;
|
||||
if (constraint->raw_expr)
|
||||
{
|
||||
/* the expression was parsed from sql, still needs to transform */
|
||||
expr = TransformConstraintExpr(constraint->raw_expr, typeName);
|
||||
}
|
||||
else if (constraint->cooked_expr)
|
||||
{
|
||||
/* expression was read from the catalog, no cooking required just parse */
|
||||
expr = stringToNode(constraint->cooked_expr);
|
||||
}
|
||||
else
|
||||
{
|
||||
elog(ERROR, "missing expression for domain constraint");
|
||||
}
|
||||
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
char *exprSql = deparse_expression(expr, NIL, true, true);
|
||||
PopOverrideSearchPath();
|
||||
|
||||
appendStringInfo(buf, " CHECK (%s)", exprSql);
|
||||
return;
|
||||
}
|
||||
|
||||
case CONSTR_DEFAULT:
|
||||
{
|
||||
Node *expr = NULL;
|
||||
if (constraint->raw_expr)
|
||||
{
|
||||
/* the expression was parsed from sql, still needs to transform */
|
||||
expr = TransformDefaultExpr(constraint->raw_expr, domainName, typeName);
|
||||
}
|
||||
else if (constraint->cooked_expr)
|
||||
{
|
||||
/* expression was read from the catalog, no cooking required just parse */
|
||||
expr = stringToNode(constraint->cooked_expr);
|
||||
}
|
||||
else
|
||||
{
|
||||
elog(ERROR, "missing expression for domain default");
|
||||
}
|
||||
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
char *exprSql = deparse_expression(expr, NIL, true, true);
|
||||
PopOverrideSearchPath();
|
||||
|
||||
appendStringInfo(buf, " DEFAULT %s", exprSql);
|
||||
return;
|
||||
}
|
||||
|
||||
case CONSTR_NOTNULL:
|
||||
{
|
||||
appendStringInfoString(buf, " NOT NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
case CONSTR_NULL:
|
||||
{
|
||||
appendStringInfoString(buf, " NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
ereport(ERROR, (errmsg("unsupported constraint for distributed domain")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TransformDefaultExpr transforms a default expression from the expression passed on the
|
||||
* AST to a cooked version that postgres uses internally.
|
||||
*
|
||||
* Only the cooked version can be easily turned back into a sql string, hence its use in
|
||||
* the deparser. This is only called for default expressions that don't have a cooked
|
||||
* variant stored.
|
||||
*/
|
||||
static Node *
|
||||
TransformDefaultExpr(Node *expr, List *domainName, TypeName *typeName)
|
||||
{
|
||||
const char *domainNameStr = NameListToQuotedString(domainName);
|
||||
int32 basetypeMod = 0; /* capture typeMod during lookup */
|
||||
Type tup = typenameType(NULL, typeName, &basetypeMod);
|
||||
Oid basetypeoid = typeTypeId(tup);
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
|
||||
ParseState *pstate = make_parsestate(NULL);
|
||||
Node *defaultExpr = cookDefault(pstate, expr,
|
||||
basetypeoid,
|
||||
basetypeMod,
|
||||
domainNameStr,
|
||||
0);
|
||||
|
||||
return defaultExpr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TransformConstraintExpr transforms a constraint expression from the expression passed
|
||||
* on the AST to a cooked version that postgres uses internally.
|
||||
*
|
||||
* Only the cooked version can be easily turned back into a sql string, hence its use in
|
||||
* the deparser. This is only called for default expressions that don't have a cooked
|
||||
* variant stored.
|
||||
*/
|
||||
static Node *
|
||||
TransformConstraintExpr(Node *expr, TypeName *typeName)
|
||||
{
|
||||
/*
|
||||
* Convert the A_EXPR in raw_expr into an EXPR
|
||||
*/
|
||||
ParseState *pstate = make_parsestate(NULL);
|
||||
|
||||
/*
|
||||
* Set up a CoerceToDomainValue to represent the occurrence of VALUE in
|
||||
* the expression. Note that it will appear to have the type of the base
|
||||
* type, not the domain. This seems correct since within the check
|
||||
* expression, we should not assume the input value can be considered a
|
||||
* member of the domain.
|
||||
*/
|
||||
|
||||
CoerceToDomainValue *domVal = GetCoerceDomainValue(typeName);
|
||||
|
||||
pstate->p_pre_columnref_hook = replace_domain_constraint_value;
|
||||
pstate->p_ref_hook_state = (void *) domVal;
|
||||
|
||||
expr = transformExpr(pstate, expr, EXPR_KIND_DOMAIN_CHECK);
|
||||
|
||||
/*
|
||||
* Make sure it yields a boolean result.
|
||||
*/
|
||||
expr = coerce_to_boolean(pstate, expr, "CHECK");
|
||||
|
||||
/*
|
||||
* Fix up collation information.
|
||||
*/
|
||||
assign_expr_collations(pstate, expr);
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetCoerceDomainValue creates a stub CoerceToDomainValue struct representing the type
|
||||
* referenced by the typeName.
|
||||
*/
|
||||
static CoerceToDomainValue *
|
||||
GetCoerceDomainValue(TypeName *typeName)
|
||||
{
|
||||
int32 typMod = 0; /* capture typeMod during lookup */
|
||||
Type tup = LookupTypeName(NULL, typeName, &typMod, false);
|
||||
if (tup == NULL)
|
||||
{
|
||||
elog(ERROR, "unable to lookup type information for %s",
|
||||
NameListToQuotedString(typeName->names));
|
||||
}
|
||||
|
||||
CoerceToDomainValue *domVal = makeNode(CoerceToDomainValue);
|
||||
domVal->typeId = typeTypeId(tup);
|
||||
domVal->typeMod = typMod;
|
||||
domVal->collation = typeTypeCollation(tup);
|
||||
domVal->location = -1;
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
return domVal;
|
||||
}
|
||||
|
||||
|
||||
/* Parser pre_columnref_hook for domain CHECK constraint parsing */
|
||||
static Node *
|
||||
replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref)
|
||||
{
|
||||
/*
|
||||
* Check for a reference to "value", and if that's what it is, replace
|
||||
* with a CoerceToDomainValue as prepared for us by domainAddConstraint.
|
||||
* (We handle VALUE as a name, not a keyword, to avoid breaking a lot of
|
||||
* applications that have used VALUE as a column name in the past.)
|
||||
*/
|
||||
if (list_length(cref->fields) == 1)
|
||||
{
|
||||
Node *field1 = (Node *) linitial(cref->fields);
|
||||
Assert(IsA(field1, String));
|
||||
char *colname = strVal(field1);
|
||||
|
||||
if (strcmp(colname, "value") == 0)
|
||||
{
|
||||
CoerceToDomainValue *domVal = copyObject(pstate->p_ref_hook_state);
|
||||
|
||||
/* Propagate location knowledge, if any */
|
||||
domVal->location = cref->location;
|
||||
return (Node *) domVal;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* deparse_foreign_data_wrapper_stmts.c
|
||||
* All routines to deparse foreign data wrapper statements.
|
||||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "commands/defrem.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/relay_utility.h"
|
||||
#include "lib/stringinfo.h"
|
||||
#include "nodes/nodes.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
static void AppendGrantOnFDWStmt(StringInfo buf, GrantStmt *stmt);
|
||||
static void AppendGrantOnFDWNames(StringInfo buf, GrantStmt *stmt);
|
||||
|
||||
|
||||
char *
|
||||
DeparseGrantOnFDWStmt(Node *node)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_FDW);
|
||||
|
||||
StringInfoData str = { 0 };
|
||||
initStringInfo(&str);
|
||||
|
||||
AppendGrantOnFDWStmt(&str, stmt);
|
||||
|
||||
return str.data;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendGrantOnFDWStmt(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
Assert(stmt->objtype == OBJECT_FDW);
|
||||
|
||||
appendStringInfo(buf, "%s ", stmt->is_grant ? "GRANT" : "REVOKE");
|
||||
|
||||
if (!stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfo(buf, "GRANT OPTION FOR ");
|
||||
}
|
||||
|
||||
AppendGrantPrivileges(buf, stmt);
|
||||
|
||||
AppendGrantOnFDWNames(buf, stmt);
|
||||
|
||||
AppendGrantGrantees(buf, stmt);
|
||||
|
||||
if (stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfo(buf, " WITH GRANT OPTION");
|
||||
}
|
||||
if (!stmt->is_grant)
|
||||
{
|
||||
if (stmt->behavior == DROP_RESTRICT)
|
||||
{
|
||||
appendStringInfo(buf, " RESTRICT");
|
||||
}
|
||||
else if (stmt->behavior == DROP_CASCADE)
|
||||
{
|
||||
appendStringInfo(buf, " CASCADE");
|
||||
}
|
||||
}
|
||||
appendStringInfo(buf, ";");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendGrantOnFDWNames(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
ListCell *cell = NULL;
|
||||
appendStringInfo(buf, " ON FOREIGN DATA WRAPPER ");
|
||||
|
||||
foreach(cell, stmt->objects)
|
||||
{
|
||||
char *fdwname = strVal(lfirst(cell));
|
||||
|
||||
appendStringInfoString(buf, quote_identifier(fdwname));
|
||||
if (cell != list_tail(stmt->objects))
|
||||
{
|
||||
appendStringInfo(buf, ", ");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,6 +27,8 @@ static void AppendDropForeignServerStmt(StringInfo buf, DropStmt *stmt);
|
|||
static void AppendServerNames(StringInfo buf, DropStmt *stmt);
|
||||
static void AppendBehavior(StringInfo buf, DropStmt *stmt);
|
||||
static char * GetDefElemActionString(DefElemAction action);
|
||||
static void AppendGrantOnForeignServerStmt(StringInfo buf, GrantStmt *stmt);
|
||||
static void AppendGrantOnForeignServerServers(StringInfo buf, GrantStmt *stmt);
|
||||
|
||||
char *
|
||||
DeparseCreateForeignServerStmt(Node *node)
|
||||
|
@ -104,6 +106,21 @@ DeparseDropForeignServerStmt(Node *node)
|
|||
}
|
||||
|
||||
|
||||
char *
|
||||
DeparseGrantOnForeignServerStmt(Node *node)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_FOREIGN_SERVER);
|
||||
|
||||
StringInfoData str = { 0 };
|
||||
initStringInfo(&str);
|
||||
|
||||
AppendGrantOnForeignServerStmt(&str, stmt);
|
||||
|
||||
return str.data;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendCreateForeignServerStmt(StringInfo buf, CreateForeignServerStmt *stmt)
|
||||
{
|
||||
|
@ -275,3 +292,58 @@ GetDefElemActionString(DefElemAction action)
|
|||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendGrantOnForeignServerStmt(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
Assert(stmt->objtype == OBJECT_FOREIGN_SERVER);
|
||||
|
||||
appendStringInfo(buf, "%s ", stmt->is_grant ? "GRANT" : "REVOKE");
|
||||
|
||||
if (!stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfo(buf, "GRANT OPTION FOR ");
|
||||
}
|
||||
|
||||
AppendGrantPrivileges(buf, stmt);
|
||||
|
||||
AppendGrantOnForeignServerServers(buf, stmt);
|
||||
|
||||
AppendGrantGrantees(buf, stmt);
|
||||
|
||||
if (stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfo(buf, " WITH GRANT OPTION");
|
||||
}
|
||||
if (!stmt->is_grant)
|
||||
{
|
||||
if (stmt->behavior == DROP_RESTRICT)
|
||||
{
|
||||
appendStringInfo(buf, " RESTRICT");
|
||||
}
|
||||
else if (stmt->behavior == DROP_CASCADE)
|
||||
{
|
||||
appendStringInfo(buf, " CASCADE");
|
||||
}
|
||||
}
|
||||
appendStringInfo(buf, ";");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendGrantOnForeignServerServers(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
ListCell *cell = NULL;
|
||||
appendStringInfo(buf, " ON FOREIGN SERVER ");
|
||||
|
||||
foreach(cell, stmt->objects)
|
||||
{
|
||||
char *servername = strVal(lfirst(cell));
|
||||
appendStringInfoString(buf, quote_identifier(servername));
|
||||
if (cell != list_tail(stmt->objects))
|
||||
{
|
||||
appendStringInfo(buf, ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,9 @@ static void AppendAlterFunctionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt
|
|||
static void AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
|
||||
static void AppendAlterFunctionDependsStmt(StringInfo buf, AlterObjectDependsStmt *stmt);
|
||||
|
||||
static void AppendGrantOnFunctionStmt(StringInfo buf, GrantStmt *stmt);
|
||||
static void AppendGrantOnFunctionFunctions(StringInfo buf, GrantStmt *stmt);
|
||||
|
||||
static char * CopyAndConvertToUpperCase(const char *str);
|
||||
|
||||
/*
|
||||
|
@ -711,3 +714,113 @@ CopyAndConvertToUpperCase(const char *str)
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseGrantOnFunctionStmt builds and returns a string representing the GrantOnFunctionStmt
|
||||
*/
|
||||
char *
|
||||
DeparseGrantOnFunctionStmt(Node *node)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(isFunction(stmt->objtype));
|
||||
|
||||
StringInfoData str = { 0 };
|
||||
initStringInfo(&str);
|
||||
|
||||
AppendGrantOnFunctionStmt(&str, stmt);
|
||||
|
||||
return str.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendGrantOnFunctionStmt builds and returns an SQL command representing a
|
||||
* GRANT .. ON FUNCTION command from given GrantStmt object.
|
||||
*/
|
||||
static void
|
||||
AppendGrantOnFunctionStmt(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
Assert(isFunction(stmt->objtype));
|
||||
|
||||
if (stmt->targtype == ACL_TARGET_ALL_IN_SCHEMA)
|
||||
{
|
||||
elog(ERROR,
|
||||
"GRANT .. ALL FUNCTIONS/PROCEDURES IN SCHEMA is not supported for formatting.");
|
||||
}
|
||||
|
||||
appendStringInfoString(buf, stmt->is_grant ? "GRANT " : "REVOKE ");
|
||||
|
||||
if (!stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfoString(buf, "GRANT OPTION FOR ");
|
||||
}
|
||||
|
||||
AppendGrantPrivileges(buf, stmt);
|
||||
|
||||
AppendGrantOnFunctionFunctions(buf, stmt);
|
||||
|
||||
AppendGrantGrantees(buf, stmt);
|
||||
|
||||
if (stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfoString(buf, " WITH GRANT OPTION");
|
||||
}
|
||||
if (!stmt->is_grant)
|
||||
{
|
||||
if (stmt->behavior == DROP_RESTRICT)
|
||||
{
|
||||
appendStringInfoString(buf, " RESTRICT");
|
||||
}
|
||||
else if (stmt->behavior == DROP_CASCADE)
|
||||
{
|
||||
appendStringInfoString(buf, " CASCADE");
|
||||
}
|
||||
}
|
||||
appendStringInfoString(buf, ";");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendGrantOnFunctionFunctions appends the function names along with their arguments
|
||||
* to the given StringInfo from the given GrantStmt
|
||||
*/
|
||||
static void
|
||||
AppendGrantOnFunctionFunctions(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
ListCell *cell = NULL;
|
||||
appendStringInfo(buf, " ON %s ", ObjectTypeToKeyword(stmt->objtype));
|
||||
|
||||
foreach(cell, stmt->objects)
|
||||
{
|
||||
/*
|
||||
* GrantOnFunction statement keeps its objects (functions) as
|
||||
* a list of ObjectWithArgs
|
||||
*/
|
||||
ObjectWithArgs *function = (ObjectWithArgs *) lfirst(cell);
|
||||
|
||||
appendStringInfoString(buf, NameListToString(function->objname));
|
||||
if (!function->args_unspecified)
|
||||
{
|
||||
/* if args are specified, we should append "(arg1, arg2, ...)" to the function name */
|
||||
const char *args = TypeNameListToString(function->objargs);
|
||||
appendStringInfo(buf, "(%s)", args);
|
||||
}
|
||||
if (cell != list_tail(stmt->objects))
|
||||
{
|
||||
appendStringInfoString(buf, ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* isFunction returns true if the given ObjectType is a function, a procedure or a routine
|
||||
* otherwise returns false
|
||||
*/
|
||||
bool
|
||||
isFunction(ObjectType objectType)
|
||||
{
|
||||
return (objectType == OBJECT_FUNCTION || objectType == OBJECT_PROCEDURE ||
|
||||
objectType == OBJECT_ROUTINE);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,11 @@
|
|||
|
||||
static void AppendAlterRoleStmt(StringInfo buf, AlterRoleStmt *stmt);
|
||||
static void AppendAlterRoleSetStmt(StringInfo buf, AlterRoleSetStmt *stmt);
|
||||
static void AppendCreateRoleStmt(StringInfo buf, CreateRoleStmt *stmt);
|
||||
static void AppendRoleOption(StringInfo buf, ListCell *optionCell);
|
||||
static void AppendRoleList(StringInfo buf, List *roleList);
|
||||
static void AppendDropRoleStmt(StringInfo buf, DropRoleStmt *stmt);
|
||||
static void AppendGrantRoleStmt(StringInfo buf, GrantRoleStmt *stmt);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -173,6 +177,213 @@ AppendRoleOption(StringInfo buf, ListCell *optionCell)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseCreateRoleStmt builds and returns a string representing of the
|
||||
* CreateRoleStmt for application on a remote server.
|
||||
*/
|
||||
char *
|
||||
DeparseCreateRoleStmt(Node *node)
|
||||
{
|
||||
CreateRoleStmt *stmt = castNode(CreateRoleStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
AppendCreateRoleStmt(&buf, stmt);
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendCreateRoleStmt generates the string representation of the
|
||||
* CreateRoleStmt and appends it to the buffer.
|
||||
*/
|
||||
static void
|
||||
AppendCreateRoleStmt(StringInfo buf, CreateRoleStmt *stmt)
|
||||
{
|
||||
ListCell *optionCell = NULL;
|
||||
|
||||
appendStringInfo(buf, "CREATE ");
|
||||
|
||||
switch (stmt->stmt_type)
|
||||
{
|
||||
case ROLESTMT_ROLE:
|
||||
{
|
||||
appendStringInfo(buf, "ROLE ");
|
||||
break;
|
||||
}
|
||||
|
||||
case ROLESTMT_USER:
|
||||
{
|
||||
appendStringInfo(buf, "USER ");
|
||||
break;
|
||||
}
|
||||
|
||||
case ROLESTMT_GROUP:
|
||||
{
|
||||
appendStringInfo(buf, "GROUP ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
appendStringInfo(buf, "%s", quote_identifier(stmt->role));
|
||||
|
||||
foreach(optionCell, stmt->options)
|
||||
{
|
||||
AppendRoleOption(buf, optionCell);
|
||||
|
||||
DefElem *option = (DefElem *) lfirst(optionCell);
|
||||
|
||||
if (strcmp(option->defname, "sysid") == 0)
|
||||
{
|
||||
appendStringInfo(buf, " SYSID %s", quote_literal_cstr(strVal(option->arg)));
|
||||
}
|
||||
else if (strcmp(option->defname, "adminmembers") == 0)
|
||||
{
|
||||
appendStringInfo(buf, " ADMIN ");
|
||||
AppendRoleList(buf, (List *) option->arg);
|
||||
}
|
||||
else if (strcmp(option->defname, "rolemembers") == 0)
|
||||
{
|
||||
appendStringInfo(buf, " ROLE ");
|
||||
AppendRoleList(buf, (List *) option->arg);
|
||||
}
|
||||
else if (strcmp(option->defname, "addroleto") == 0)
|
||||
{
|
||||
appendStringInfo(buf, " IN ROLE ");
|
||||
AppendRoleList(buf, (List *) option->arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseDropRoleStmt builds and returns a string representing of the
|
||||
* DropRoleStmt for application on a remote server.
|
||||
*/
|
||||
char *
|
||||
DeparseDropRoleStmt(Node *node)
|
||||
{
|
||||
DropRoleStmt *stmt = castNode(DropRoleStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
AppendDropRoleStmt(&buf, stmt);
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendDropRoleStmt generates the string representation of the
|
||||
* DropRoleStmt and appends it to the buffer.
|
||||
*/
|
||||
static void
|
||||
AppendDropRoleStmt(StringInfo buf, DropRoleStmt *stmt)
|
||||
{
|
||||
appendStringInfo(buf, "DROP ROLE ");
|
||||
|
||||
if (stmt->missing_ok)
|
||||
{
|
||||
appendStringInfo(buf, "IF EXISTS ");
|
||||
}
|
||||
|
||||
AppendRoleList(buf, stmt->roles);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendRoleList(StringInfo buf, List *roleList)
|
||||
{
|
||||
ListCell *cell = NULL;
|
||||
foreach(cell, roleList)
|
||||
{
|
||||
Node *roleNode = (Node *) lfirst(cell);
|
||||
Assert(IsA(roleNode, RoleSpec) || IsA(roleNode, AccessPriv));
|
||||
char const *rolename = NULL;
|
||||
if (IsA(roleNode, RoleSpec))
|
||||
{
|
||||
rolename = RoleSpecString((RoleSpec *) roleNode, true);
|
||||
}
|
||||
if (IsA(roleNode, AccessPriv))
|
||||
{
|
||||
rolename = quote_identifier(((AccessPriv *) roleNode)->priv_name);
|
||||
}
|
||||
appendStringInfoString(buf, rolename);
|
||||
if (cell != list_tail(roleList))
|
||||
{
|
||||
appendStringInfo(buf, ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseGrantRoleStmt builds and returns a string representing of the
|
||||
* GrantRoleStmt for application on a remote server.
|
||||
*/
|
||||
char *
|
||||
DeparseGrantRoleStmt(Node *node)
|
||||
{
|
||||
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
AppendGrantRoleStmt(&buf, stmt);
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendGrantRoleStmt generates the string representation of the
|
||||
* GrantRoleStmt and appends it to the buffer.
|
||||
*/
|
||||
static void
|
||||
AppendGrantRoleStmt(StringInfo buf, GrantRoleStmt *stmt)
|
||||
{
|
||||
appendStringInfo(buf, "%s ", stmt->is_grant ? "GRANT" : "REVOKE");
|
||||
|
||||
if (!stmt->is_grant && stmt->admin_opt)
|
||||
{
|
||||
appendStringInfo(buf, "ADMIN OPTION FOR ");
|
||||
}
|
||||
|
||||
AppendRoleList(buf, stmt->granted_roles);
|
||||
|
||||
appendStringInfo(buf, "%s ", stmt->is_grant ? " TO " : " FROM ");
|
||||
|
||||
AppendRoleList(buf, stmt->grantee_roles);
|
||||
|
||||
if (stmt->is_grant)
|
||||
{
|
||||
if (stmt->admin_opt)
|
||||
{
|
||||
appendStringInfo(buf, " WITH ADMIN OPTION");
|
||||
}
|
||||
|
||||
if (stmt->grantor)
|
||||
{
|
||||
appendStringInfo(buf, " GRANTED BY %s", RoleSpecString(stmt->grantor, true));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (stmt->behavior == DROP_RESTRICT)
|
||||
{
|
||||
appendStringInfo(buf, " RESTRICT");
|
||||
}
|
||||
else if (stmt->behavior == DROP_CASCADE)
|
||||
{
|
||||
appendStringInfo(buf, " CASCADE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendAlterRoleSetStmt generates the string representation of the
|
||||
* AlterRoleSetStmt and appends it to the buffer.
|
||||
|
|
|
@ -22,9 +22,7 @@
|
|||
static void AppendCreateSchemaStmt(StringInfo buf, CreateSchemaStmt *stmt);
|
||||
static void AppendDropSchemaStmt(StringInfo buf, DropStmt *stmt);
|
||||
static void AppendGrantOnSchemaStmt(StringInfo buf, GrantStmt *stmt);
|
||||
static void AppendGrantOnSchemaPrivileges(StringInfo buf, GrantStmt *stmt);
|
||||
static void AppendGrantOnSchemaSchemas(StringInfo buf, GrantStmt *stmt);
|
||||
static void AppendGrantOnSchemaGrantees(StringInfo buf, GrantStmt *stmt);
|
||||
static void AppendAlterSchemaRenameStmt(StringInfo buf, RenameStmt *stmt);
|
||||
|
||||
char *
|
||||
|
@ -161,11 +159,11 @@ AppendGrantOnSchemaStmt(StringInfo buf, GrantStmt *stmt)
|
|||
appendStringInfo(buf, "GRANT OPTION FOR ");
|
||||
}
|
||||
|
||||
AppendGrantOnSchemaPrivileges(buf, stmt);
|
||||
AppendGrantPrivileges(buf, stmt);
|
||||
|
||||
AppendGrantOnSchemaSchemas(buf, stmt);
|
||||
|
||||
AppendGrantOnSchemaGrantees(buf, stmt);
|
||||
AppendGrantGrantees(buf, stmt);
|
||||
|
||||
if (stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
|
@ -186,8 +184,8 @@ AppendGrantOnSchemaStmt(StringInfo buf, GrantStmt *stmt)
|
|||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendGrantOnSchemaPrivileges(StringInfo buf, GrantStmt *stmt)
|
||||
void
|
||||
AppendGrantPrivileges(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
if (list_length(stmt->privileges) == 0)
|
||||
{
|
||||
|
@ -227,8 +225,8 @@ AppendGrantOnSchemaSchemas(StringInfo buf, GrantStmt *stmt)
|
|||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendGrantOnSchemaGrantees(StringInfo buf, GrantStmt *stmt)
|
||||
void
|
||||
AppendGrantGrantees(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
ListCell *cell = NULL;
|
||||
appendStringInfo(buf, " %s ", stmt->is_grant ? "TO" : "FROM");
|
||||
|
|
|
@ -27,6 +27,8 @@ static void AppendSequenceNameList(StringInfo buf, List *objects, ObjectType obj
|
|||
static void AppendRenameSequenceStmt(StringInfo buf, RenameStmt *stmt);
|
||||
static void AppendAlterSequenceSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt);
|
||||
static void AppendAlterSequenceOwnerStmt(StringInfo buf, AlterTableStmt *stmt);
|
||||
static void AppendGrantOnSequenceStmt(StringInfo buf, GrantStmt *stmt);
|
||||
static void AppendGrantOnSequenceSequences(StringInfo buf, GrantStmt *stmt);
|
||||
|
||||
/*
|
||||
* DeparseDropSequenceStmt builds and returns a string representing the DropStmt
|
||||
|
@ -86,12 +88,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);
|
||||
|
@ -260,3 +256,107 @@ AppendAlterSequenceOwnerStmt(StringInfo buf, AlterTableStmt *stmt)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseGrantOnSequenceStmt builds and returns a string representing the GrantOnSequenceStmt
|
||||
*/
|
||||
char *
|
||||
DeparseGrantOnSequenceStmt(Node *node)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_SEQUENCE);
|
||||
|
||||
StringInfoData str = { 0 };
|
||||
initStringInfo(&str);
|
||||
|
||||
AppendGrantOnSequenceStmt(&str, stmt);
|
||||
|
||||
return str.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendGrantOnSequenceStmt builds and returns an SQL command representing a
|
||||
* GRANT .. ON SEQUENCE command from given GrantStmt object.
|
||||
*/
|
||||
static void
|
||||
AppendGrantOnSequenceStmt(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
Assert(stmt->objtype == OBJECT_SEQUENCE);
|
||||
|
||||
if (stmt->targtype == ACL_TARGET_ALL_IN_SCHEMA)
|
||||
{
|
||||
/*
|
||||
* Normally we shouldn't reach this
|
||||
* We deparse a GrantStmt with OBJECT_SEQUENCE after setting targtype
|
||||
* to ACL_TARGET_OBJECT
|
||||
*/
|
||||
elog(ERROR,
|
||||
"GRANT .. ALL SEQUENCES IN SCHEMA is not supported for formatting.");
|
||||
}
|
||||
|
||||
appendStringInfoString(buf, stmt->is_grant ? "GRANT " : "REVOKE ");
|
||||
|
||||
if (!stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfoString(buf, "GRANT OPTION FOR ");
|
||||
}
|
||||
|
||||
AppendGrantPrivileges(buf, stmt);
|
||||
|
||||
AppendGrantOnSequenceSequences(buf, stmt);
|
||||
|
||||
AppendGrantGrantees(buf, stmt);
|
||||
|
||||
if (stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfoString(buf, " WITH GRANT OPTION");
|
||||
}
|
||||
if (!stmt->is_grant)
|
||||
{
|
||||
if (stmt->behavior == DROP_RESTRICT)
|
||||
{
|
||||
appendStringInfoString(buf, " RESTRICT");
|
||||
}
|
||||
else if (stmt->behavior == DROP_CASCADE)
|
||||
{
|
||||
appendStringInfoString(buf, " CASCADE");
|
||||
}
|
||||
}
|
||||
appendStringInfoString(buf, ";");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendGrantOnSequenceSequences appends the sequence names along with their arguments
|
||||
* to the given StringInfo from the given GrantStmt
|
||||
*/
|
||||
static void
|
||||
AppendGrantOnSequenceSequences(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
Assert(stmt->objtype == OBJECT_SEQUENCE);
|
||||
|
||||
appendStringInfoString(buf, " ON SEQUENCE ");
|
||||
|
||||
ListCell *cell = NULL;
|
||||
foreach(cell, stmt->objects)
|
||||
{
|
||||
/*
|
||||
* GrantOnSequence statement keeps its objects (sequences) as
|
||||
* a list of RangeVar-s
|
||||
*/
|
||||
RangeVar *sequence = (RangeVar *) lfirst(cell);
|
||||
|
||||
/*
|
||||
* We have qualified the statement beforehand
|
||||
*/
|
||||
appendStringInfoString(buf, quote_qualified_identifier(sequence->schemaname,
|
||||
sequence->relname));
|
||||
|
||||
if (cell != list_tail(stmt->objects))
|
||||
{
|
||||
appendStringInfoString(buf, ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* qualify_domain.c
|
||||
* Functions to fully qualify, make the statements independent of
|
||||
* search_path settings, for all domain related statements. This
|
||||
* mostly consists of adding the schema name to all the domain
|
||||
* names referencing domains.
|
||||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/pg_collation.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
|
||||
|
||||
static void QualifyTypeName(TypeName *typeName, bool missing_ok);
|
||||
static void QualifyCollate(CollateClause *collClause, bool missing_ok);
|
||||
|
||||
|
||||
/*
|
||||
* QualifyCreateDomainStmt modifies the CreateDomainStmt passed to become search_path
|
||||
* independent.
|
||||
*/
|
||||
void
|
||||
QualifyCreateDomainStmt(Node *node)
|
||||
{
|
||||
CreateDomainStmt *stmt = castNode(CreateDomainStmt, node);
|
||||
|
||||
char *schemaName = NULL;
|
||||
char *domainName = NULL;
|
||||
|
||||
/* fully qualify domain name */
|
||||
DeconstructQualifiedName(stmt->domainname, &schemaName, &domainName);
|
||||
if (!schemaName)
|
||||
{
|
||||
RangeVar *var = makeRangeVarFromNameList(stmt->domainname);
|
||||
Oid creationSchema = RangeVarGetCreationNamespace(var);
|
||||
schemaName = get_namespace_name(creationSchema);
|
||||
|
||||
stmt->domainname = list_make2(makeString(schemaName), makeString(domainName));
|
||||
}
|
||||
|
||||
/* referenced types should be fully qualified */
|
||||
QualifyTypeName(stmt->typeName, false);
|
||||
QualifyCollate(stmt->collClause, false);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyDropDomainStmt modifies the DropStmt for DOMAIN's to be search_path independent.
|
||||
*/
|
||||
void
|
||||
QualifyDropDomainStmt(Node *node)
|
||||
{
|
||||
DropStmt *stmt = castNode(DropStmt, node);
|
||||
|
||||
TypeName *domainName = NULL;
|
||||
foreach_ptr(domainName, stmt->objects)
|
||||
{
|
||||
QualifyTypeName(domainName, stmt->missing_ok);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyAlterDomainStmt modifies the AlterDomainStmt to be search_path independent.
|
||||
*/
|
||||
void
|
||||
QualifyAlterDomainStmt(Node *node)
|
||||
{
|
||||
AlterDomainStmt *stmt = castNode(AlterDomainStmt, node);
|
||||
|
||||
if (list_length(stmt->typeName) == 1)
|
||||
{
|
||||
TypeName *typeName = makeTypeNameFromNameList(stmt->typeName);
|
||||
QualifyTypeName(typeName, false);
|
||||
stmt->typeName = typeName->names;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyDomainRenameConstraintStmt modifies the RenameStmt for domain constraints to be
|
||||
* search_path independent.
|
||||
*/
|
||||
void
|
||||
QualifyDomainRenameConstraintStmt(Node *node)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
Assert(stmt->renameType == OBJECT_DOMCONSTRAINT);
|
||||
|
||||
List *domainName = castNode(List, stmt->object);
|
||||
if (list_length(domainName) == 1)
|
||||
{
|
||||
TypeName *typeName = makeTypeNameFromNameList(domainName);
|
||||
QualifyTypeName(typeName, false);
|
||||
stmt->object = (Node *) typeName->names;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyAlterDomainOwnerStmt modifies the AlterOwnerStmt for DOMAIN's to be search_oath
|
||||
* independent.
|
||||
*/
|
||||
void
|
||||
QualifyAlterDomainOwnerStmt(Node *node)
|
||||
{
|
||||
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_DOMAIN);
|
||||
|
||||
List *domainName = castNode(List, stmt->object);
|
||||
if (list_length(domainName) == 1)
|
||||
{
|
||||
TypeName *typeName = makeTypeNameFromNameList(domainName);
|
||||
QualifyTypeName(typeName, false);
|
||||
stmt->object = (Node *) typeName->names;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyRenameDomainStmt modifies the RenameStmt for the Domain to be search_path
|
||||
* independent.
|
||||
*/
|
||||
void
|
||||
QualifyRenameDomainStmt(Node *node)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
Assert(stmt->renameType == OBJECT_DOMAIN);
|
||||
|
||||
List *domainName = castNode(List, stmt->object);
|
||||
if (list_length(domainName) == 1)
|
||||
{
|
||||
TypeName *typeName = makeTypeNameFromNameList(domainName);
|
||||
QualifyTypeName(typeName, false);
|
||||
stmt->object = (Node *) typeName->names;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyAlterDomainSchemaStmt modifies the AlterObjectSchemaStmt to be search_path
|
||||
* independent.
|
||||
*/
|
||||
void
|
||||
QualifyAlterDomainSchemaStmt(Node *node)
|
||||
{
|
||||
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_DOMAIN);
|
||||
|
||||
List *domainName = castNode(List, stmt->object);
|
||||
if (list_length(domainName) == 1)
|
||||
{
|
||||
TypeName *typeName = makeTypeNameFromNameList(domainName);
|
||||
QualifyTypeName(typeName, false);
|
||||
stmt->object = (Node *) typeName->names;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyTypeName qualifies a TypeName object in place. When missing_ok is false it might
|
||||
* throw an error if the type can't be found based on its name. When an oid is provided
|
||||
* missing_ok is ignored and treated as false. Meaning, even if missing_ok is true the
|
||||
* function might raise an error for non-existing types if the oid can't be found.
|
||||
*/
|
||||
static void
|
||||
QualifyTypeName(TypeName *typeName, bool missing_ok)
|
||||
{
|
||||
if (OidIsValid(typeName->typeOid))
|
||||
{
|
||||
/*
|
||||
* When the typeName is provided as oid, fill in the names.
|
||||
* missing_ok is ignored for oid's
|
||||
*/
|
||||
Type typeTup = typeidType(typeName->typeOid);
|
||||
|
||||
char *name = typeTypeName(typeTup);
|
||||
|
||||
Oid namespaceOid = TypeOidGetNamespaceOid(typeName->typeOid);
|
||||
char *schemaName = get_namespace_name(namespaceOid);
|
||||
|
||||
typeName->names = list_make2(makeString(schemaName), makeString(name));
|
||||
|
||||
ReleaseSysCache(typeTup);
|
||||
}
|
||||
else
|
||||
{
|
||||
char *name = NULL;
|
||||
char *schemaName = NULL;
|
||||
DeconstructQualifiedName(typeName->names, &schemaName, &name);
|
||||
if (!schemaName)
|
||||
{
|
||||
Oid typeOid = LookupTypeNameOid(NULL, typeName, missing_ok);
|
||||
if (OidIsValid(typeOid))
|
||||
{
|
||||
Oid namespaceOid = TypeOidGetNamespaceOid(typeOid);
|
||||
schemaName = get_namespace_name(namespaceOid);
|
||||
|
||||
typeName->names = list_make2(makeString(schemaName), makeString(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyCollate qualifies any given CollateClause by adding any missing schema name to
|
||||
* the collation being identified.
|
||||
*
|
||||
* If collClause is a NULL pointer this function is a no-nop.
|
||||
*/
|
||||
static void
|
||||
QualifyCollate(CollateClause *collClause, bool missing_ok)
|
||||
{
|
||||
if (collClause == NULL)
|
||||
{
|
||||
/* no collate clause, nothing to qualify*/
|
||||
return;
|
||||
}
|
||||
|
||||
if (list_length(collClause->collname) != 1)
|
||||
{
|
||||
/* already qualified */
|
||||
return;
|
||||
}
|
||||
|
||||
Oid collOid = get_collation_oid(collClause->collname, missing_ok);
|
||||
ObjectAddress collationAddress = { 0 };
|
||||
ObjectAddressSet(collationAddress, CollationRelationId, collOid);
|
||||
|
||||
List *objName = NIL;
|
||||
List *objArgs = NIL;
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_14
|
||||
getObjectIdentityParts(&collationAddress, &objName, &objArgs, false);
|
||||
#else
|
||||
getObjectIdentityParts(&collationAddress, &objName, &objArgs);
|
||||
#endif
|
||||
|
||||
collClause->collname = NIL;
|
||||
char *name = NULL;
|
||||
foreach_ptr(name, objName)
|
||||
{
|
||||
collClause->collname = lappend(collClause->collname, makeString(name));
|
||||
}
|
||||
}
|
|
@ -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,12 +66,53 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyRenameSequenceStmt transforms a
|
||||
* ALTER SEQUENCE .. RENAME TO ..
|
||||
|
@ -84,3 +132,41 @@ QualifyRenameSequenceStmt(Node *node)
|
|||
seq->schemaname = get_namespace_name(schemaOid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyGrantOnSequenceStmt transforms a
|
||||
* GRANT ON SEQUENCE ...
|
||||
* statement in place and makes the sequence names fully qualified.
|
||||
*/
|
||||
void
|
||||
QualifyGrantOnSequenceStmt(Node *node)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_SEQUENCE);
|
||||
|
||||
/*
|
||||
* The other option would be GRANT ALL SEQUENCES ON SCHEMA ...
|
||||
* For that we don't need to qualify
|
||||
*/
|
||||
if (stmt->targtype != ACL_TARGET_OBJECT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List *qualifiedSequenceRangeVars = NIL;
|
||||
RangeVar *sequenceRangeVar = NULL;
|
||||
foreach_ptr(sequenceRangeVar, stmt->objects)
|
||||
{
|
||||
if (sequenceRangeVar->schemaname == NULL)
|
||||
{
|
||||
Oid seqOid = RangeVarGetRelid(sequenceRangeVar, NoLock, false);
|
||||
Oid schemaOid = get_rel_namespace(seqOid);
|
||||
sequenceRangeVar->schemaname = get_namespace_name(schemaOid);
|
||||
}
|
||||
|
||||
qualifiedSequenceRangeVars = lappend(qualifiedSequenceRangeVars,
|
||||
sequenceRangeVar);
|
||||
}
|
||||
|
||||
stmt->objects = qualifiedSequenceRangeVars;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -38,6 +42,12 @@ QualifyCreateStatisticsStmt(Node *node)
|
|||
relation->schemaname = get_namespace_name(schemaOid);
|
||||
}
|
||||
|
||||
if (list_length(stmt->defnames) < 1)
|
||||
{
|
||||
/* no name to qualify */
|
||||
return;
|
||||
}
|
||||
|
||||
RangeVar *stat = makeRangeVarFromNameList(stmt->defnames);
|
||||
|
||||
if (stat->schemaname == NULL)
|
||||
|
@ -68,8 +78,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 +110,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 +138,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 +166,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 +196,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;
|
||||
}
|
||||
|
|
|
@ -31,13 +31,10 @@
|
|||
#include "utils/syscache.h"
|
||||
#include "utils/lsyscache.h"
|
||||
|
||||
static char * GetTypeNamespaceNameByNameList(List *names);
|
||||
static Oid TypeOidGetNamespaceOid(Oid typeOid);
|
||||
|
||||
/*
|
||||
* GetTypeNamespaceNameByNameList resolved the schema name of a type by its namelist.
|
||||
*/
|
||||
static char *
|
||||
char *
|
||||
GetTypeNamespaceNameByNameList(List *names)
|
||||
{
|
||||
TypeName *typeName = makeTypeNameFromNameList(names);
|
||||
|
@ -51,7 +48,7 @@ GetTypeNamespaceNameByNameList(List *names)
|
|||
/*
|
||||
* TypeOidGetNamespaceOid resolves the namespace oid for a type identified by its type oid
|
||||
*/
|
||||
static Oid
|
||||
Oid
|
||||
TypeOidGetNamespaceOid(Oid typeOid)
|
||||
{
|
||||
HeapTuple typeTuple = SearchSysCache1(TYPEOID, typeOid);
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* 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;
|
||||
List *viewNameToAdd = possiblyQualifiedViewName;
|
||||
DeconstructQualifiedName(possiblyQualifiedViewName, &schemaName, &viewName);
|
||||
|
||||
if (schemaName == NULL)
|
||||
{
|
||||
RangeVar *viewRangeVar = makeRangeVarFromNameList(possiblyQualifiedViewName);
|
||||
Oid viewOid = RangeVarGetRelid(viewRangeVar, AccessExclusiveLock,
|
||||
stmt->missing_ok);
|
||||
|
||||
/*
|
||||
* If DROP VIEW IF EXISTS called and the view doesn't exist, oid can be invalid.
|
||||
* Do not try to qualify it.
|
||||
*/
|
||||
if (OidIsValid(viewOid))
|
||||
{
|
||||
Oid schemaOid = get_rel_namespace(viewOid);
|
||||
schemaName = get_namespace_name(schemaOid);
|
||||
|
||||
List *qualifiedViewName = list_make2(makeString(schemaName),
|
||||
makeString(viewName));
|
||||
viewNameToAdd = qualifiedViewName;
|
||||
}
|
||||
}
|
||||
|
||||
qualifiedViewNames = lappend(qualifiedViewNames, viewNameToAdd);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1321,7 +1321,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "nodes/makefuncs.h"
|
||||
#include "optimizer/optimizer.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/datum.h"
|
||||
|
@ -674,7 +675,10 @@ CitusEndScan(CustomScanState *node)
|
|||
partitionKeyConst = workerJob->partitionKeyValue;
|
||||
}
|
||||
|
||||
/* queryId is not set if pg_stat_statements is not installed */
|
||||
/*
|
||||
* queryId is not set if pg_stat_statements is not installed,
|
||||
* it can be set with as of pg14: set compute_query_id to on;
|
||||
*/
|
||||
if (queryId != 0)
|
||||
{
|
||||
if (partitionKeyConst != NULL && executorType == MULTI_EXECUTOR_ADAPTIVE)
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
static bool CreatedResultsDirectory = false;
|
||||
static List *CreatedResultsDirectories = NIL;
|
||||
|
||||
|
||||
/* CopyDestReceiver can be used to stream results into a distributed table */
|
||||
|
@ -594,26 +594,28 @@ CreateIntermediateResultsDirectory(void)
|
|||
{
|
||||
char *resultDirectory = IntermediateResultsDirectory();
|
||||
|
||||
if (!CreatedResultsDirectory)
|
||||
int makeOK = mkdir(resultDirectory, S_IRWXU);
|
||||
if (makeOK != 0)
|
||||
{
|
||||
int makeOK = mkdir(resultDirectory, S_IRWXU);
|
||||
if (makeOK != 0)
|
||||
if (errno == EEXIST)
|
||||
{
|
||||
if (errno == EEXIST)
|
||||
{
|
||||
/* someone else beat us to it, that's ok */
|
||||
return resultDirectory;
|
||||
}
|
||||
|
||||
ereport(ERROR, (errcode_for_file_access(),
|
||||
errmsg("could not create intermediate results directory "
|
||||
"\"%s\": %m",
|
||||
resultDirectory)));
|
||||
/* someone else beat us to it, that's ok */
|
||||
return resultDirectory;
|
||||
}
|
||||
|
||||
CreatedResultsDirectory = true;
|
||||
ereport(ERROR, (errcode_for_file_access(),
|
||||
errmsg("could not create intermediate results directory "
|
||||
"\"%s\": %m",
|
||||
resultDirectory)));
|
||||
}
|
||||
|
||||
MemoryContext oldContext = MemoryContextSwitchTo(TopTransactionContext);
|
||||
|
||||
CreatedResultsDirectories =
|
||||
lappend(CreatedResultsDirectories, pstrdup(resultDirectory));
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
return resultDirectory;
|
||||
}
|
||||
|
||||
|
@ -693,13 +695,14 @@ IntermediateResultsDirectory(void)
|
|||
|
||||
|
||||
/*
|
||||
* RemoveIntermediateResultsDirectory removes the intermediate result directory
|
||||
* RemoveIntermediateResultsDirectories removes the intermediate result directory
|
||||
* for the current distributed transaction, if any was created.
|
||||
*/
|
||||
void
|
||||
RemoveIntermediateResultsDirectory(void)
|
||||
RemoveIntermediateResultsDirectories(void)
|
||||
{
|
||||
if (CreatedResultsDirectory)
|
||||
char *directoryElement = NULL;
|
||||
foreach_ptr(directoryElement, CreatedResultsDirectories)
|
||||
{
|
||||
/*
|
||||
* The shared directory is renamed before deleting it. Otherwise it
|
||||
|
@ -708,7 +711,7 @@ RemoveIntermediateResultsDirectory(void)
|
|||
* that's not possible. The current PID is included in the new
|
||||
* filename, so there can be no collisions with other backends.
|
||||
*/
|
||||
char *sharedName = IntermediateResultsDirectory();
|
||||
char *sharedName = directoryElement;
|
||||
StringInfo privateName = makeStringInfo();
|
||||
appendStringInfo(privateName, "%s.removed-by-%d", sharedName, MyProcPid);
|
||||
if (rename(sharedName, privateName->data))
|
||||
|
@ -728,9 +731,12 @@ RemoveIntermediateResultsDirectory(void)
|
|||
{
|
||||
PathNameDeleteTemporaryDir(privateName->data);
|
||||
}
|
||||
|
||||
CreatedResultsDirectory = false;
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
list_free_deep(CreatedResultsDirectories);
|
||||
|
||||
CreatedResultsDirectories = NIL;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
@ -763,6 +776,11 @@ GetObjectTypeString(ObjectType objType)
|
|||
return "database";
|
||||
}
|
||||
|
||||
case OBJECT_DOMAIN:
|
||||
{
|
||||
return "domain";
|
||||
}
|
||||
|
||||
case OBJECT_EXTENSION:
|
||||
{
|
||||
return "extension";
|
||||
|
@ -798,6 +816,11 @@ GetObjectTypeString(ObjectType objType)
|
|||
return "type";
|
||||
}
|
||||
|
||||
case OBJECT_VIEW:
|
||||
{
|
||||
return "view";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
ereport(DEBUG1, (errmsg("unsupported object type"),
|
||||
|
@ -860,43 +883,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;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/pg_version_constants.h"
|
||||
|
||||
#include "access/genam.h"
|
||||
|
@ -20,8 +21,13 @@
|
|||
#include "catalog/catalog.h"
|
||||
#include "catalog/dependency.h"
|
||||
#include "catalog/indexing.h"
|
||||
#include "catalog/pg_auth_members.h"
|
||||
#include "catalog/pg_authid_d.h"
|
||||
#include "catalog/pg_class.h"
|
||||
#include "catalog/pg_constraint.h"
|
||||
#include "catalog/pg_depend.h"
|
||||
#include "catalog/pg_extension_d.h"
|
||||
#include "catalog/pg_foreign_data_wrapper_d.h"
|
||||
#include "catalog/pg_namespace.h"
|
||||
#include "catalog/pg_proc_d.h"
|
||||
#include "catalog/pg_rewrite.h"
|
||||
|
@ -43,6 +49,7 @@
|
|||
#include "utils/fmgroids.h"
|
||||
#include "utils/hsearch.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
/*
|
||||
* ObjectAddressCollector keeps track of collected ObjectAddresses. This can be used
|
||||
|
@ -127,6 +134,7 @@ static List * GetRelationTriggerFunctionDependencyList(Oid relationId);
|
|||
static List * GetRelationStatsSchemaDependencyList(Oid relationId);
|
||||
static List * GetRelationIndicesDependencyList(Oid relationId);
|
||||
static DependencyDefinition * CreateObjectAddressDependencyDef(Oid classId, Oid objectId);
|
||||
static List * GetTypeConstraintDependencyDefinition(Oid typeId);
|
||||
static List * CreateObjectAddressDependencyDefList(Oid classId, List *objectIdList);
|
||||
static ObjectAddress DependencyDefinitionObjectAddress(DependencyDefinition *definition);
|
||||
|
||||
|
@ -162,10 +170,12 @@ 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 List * GetDependentRoleIdsFDW(Oid FDWOid);
|
||||
static List * ExpandRolesToGroups(Oid roleid);
|
||||
static ViewDependencyNode * BuildViewDependencyGraph(Oid relationId, HTAB *nodeMap);
|
||||
static Oid GetDependingView(Form_pg_depend pg_depend);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -422,7 +432,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 *
|
||||
|
@ -630,6 +640,15 @@ SupportedDependencyByCitus(const ObjectAddress *address)
|
|||
return IsObjectAddressOwnedByExtension(address, NULL);
|
||||
}
|
||||
|
||||
case OCLASS_CONSTRAINT:
|
||||
{
|
||||
/*
|
||||
* Constraints are only supported when on domain types. Other constraints have
|
||||
* their typid set to InvalidOid.
|
||||
*/
|
||||
return OidIsValid(get_constraint_typid(address->objectId));
|
||||
}
|
||||
|
||||
case OCLASS_COLLATION:
|
||||
{
|
||||
return true;
|
||||
|
@ -658,16 +677,13 @@ SupportedDependencyByCitus(const ObjectAddress *address)
|
|||
|
||||
case OCLASS_ROLE:
|
||||
{
|
||||
/*
|
||||
* Community only supports the extension owner as a distributed object to
|
||||
* propagate alter statements for this user
|
||||
*/
|
||||
if (address->objectId == CitusExtensionOwner())
|
||||
/* if it is a reserved role do not propagate */
|
||||
if (IsReservedName(GetUserNameFromId(address->objectId, false)))
|
||||
{
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
case OCLASS_EXTENSION:
|
||||
|
@ -691,6 +707,7 @@ SupportedDependencyByCitus(const ObjectAddress *address)
|
|||
{
|
||||
case TYPTYPE_ENUM:
|
||||
case TYPTYPE_COMPOSITE:
|
||||
case TYPTYPE_DOMAIN:
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -734,7 +751,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;
|
||||
}
|
||||
|
@ -751,6 +769,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.
|
||||
|
@ -788,8 +858,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))
|
||||
{
|
||||
|
@ -800,9 +873,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);
|
||||
|
@ -880,7 +963,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;
|
||||
|
@ -1194,19 +1279,74 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
|
|||
|
||||
switch (target.classId)
|
||||
{
|
||||
case TypeRelationId:
|
||||
case AuthIdRelationId:
|
||||
{
|
||||
/*
|
||||
* types depending on other types are not captured in pg_depend, instead they
|
||||
* are described with their dependencies by the relation that describes the
|
||||
* composite type.
|
||||
* Roles are members of other roles. These relations are not recorded directly
|
||||
* but can be deduced from pg_auth_members
|
||||
*/
|
||||
if (get_typtype(target.objectId) == TYPTYPE_COMPOSITE)
|
||||
return ExpandRolesToGroups(target.objectId);
|
||||
}
|
||||
|
||||
case ExtensionRelationId:
|
||||
{
|
||||
/*
|
||||
* FDWs get propagated along with the extensions they belong to.
|
||||
* In case there are GRANTed privileges on FDWs to roles, those
|
||||
* GRANT statements will be propagated to. In order to make sure
|
||||
* that those GRANT statements work, the privileged roles should
|
||||
* exist on the worker nodes. Hence, here we find these dependent
|
||||
* roles and add them as dependencies.
|
||||
*/
|
||||
|
||||
Oid extensionId = target.objectId;
|
||||
List *FDWOids = GetDependentFDWsToExtension(extensionId);
|
||||
|
||||
Oid FDWOid = InvalidOid;
|
||||
foreach_oid(FDWOid, FDWOids)
|
||||
{
|
||||
Oid typeRelationId = get_typ_typrelid(target.objectId);
|
||||
DependencyDefinition *dependency =
|
||||
CreateObjectAddressDependencyDef(RelationRelationId, typeRelationId);
|
||||
result = lappend(result, dependency);
|
||||
List *dependentRoleIds = GetDependentRoleIdsFDW(FDWOid);
|
||||
List *dependencies =
|
||||
CreateObjectAddressDependencyDefList(AuthIdRelationId,
|
||||
dependentRoleIds);
|
||||
result = list_concat(result, dependencies);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case TypeRelationId:
|
||||
{
|
||||
switch (get_typtype(target.objectId))
|
||||
{
|
||||
/*
|
||||
* types depending on other types are not captured in pg_depend, instead
|
||||
* they are described with their dependencies by the relation that
|
||||
* describes the composite type.
|
||||
*/
|
||||
case TYPTYPE_COMPOSITE:
|
||||
{
|
||||
Oid typeRelationId = get_typ_typrelid(target.objectId);
|
||||
DependencyDefinition *dependency =
|
||||
CreateObjectAddressDependencyDef(RelationRelationId,
|
||||
typeRelationId);
|
||||
result = lappend(result, dependency);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Domains can have constraints associated with them. Constraints themself
|
||||
* can depend on things like functions. To support the propagation of
|
||||
* these functions we will add the constraints to the list of objects to
|
||||
* be created.
|
||||
*/
|
||||
case TYPTYPE_DOMAIN:
|
||||
{
|
||||
List *dependencies =
|
||||
GetTypeConstraintDependencyDefinition(target.objectId);
|
||||
result = list_concat(result, dependencies);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1275,9 +1415,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:
|
||||
|
@ -1290,6 +1447,131 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetDependentRoleIdsFDW returns a list of role oids that has privileges on the
|
||||
* FDW with the given object id.
|
||||
*/
|
||||
static List *
|
||||
GetDependentRoleIdsFDW(Oid FDWOid)
|
||||
{
|
||||
List *roleIds = NIL;
|
||||
|
||||
Acl *aclEntry = GetPrivilegesForFDW(FDWOid);
|
||||
|
||||
if (aclEntry == NULL)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
AclItem *privileges = ACL_DAT(aclEntry);
|
||||
int numberOfPrivsGranted = ACL_NUM(aclEntry);
|
||||
|
||||
for (int i = 0; i < numberOfPrivsGranted; i++)
|
||||
{
|
||||
roleIds = lappend_oid(roleIds, privileges[i].ai_grantee);
|
||||
}
|
||||
|
||||
return roleIds;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExpandRolesToGroups returns a list of object addresses pointing to roles that roleid
|
||||
* depends on.
|
||||
*/
|
||||
static List *
|
||||
ExpandRolesToGroups(Oid roleid)
|
||||
{
|
||||
Relation pgAuthMembers = table_open(AuthMemRelationId, AccessShareLock);
|
||||
HeapTuple tuple = NULL;
|
||||
|
||||
ScanKeyData scanKey[1];
|
||||
const int scanKeyCount = 1;
|
||||
|
||||
/* scan pg_auth_members for member = $1 via index pg_auth_members_member_role_index */
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_auth_members_member, BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(roleid));
|
||||
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgAuthMembers, AuthMemMemRoleIndexId,
|
||||
true, NULL, scanKeyCount, scanKey);
|
||||
|
||||
List *roles = NIL;
|
||||
while ((tuple = systable_getnext(scanDescriptor)) != NULL)
|
||||
{
|
||||
Form_pg_auth_members membership = (Form_pg_auth_members) GETSTRUCT(tuple);
|
||||
|
||||
DependencyDefinition *definition = palloc0(sizeof(DependencyDefinition));
|
||||
definition->mode = DependencyObjectAddress;
|
||||
ObjectAddressSet(definition->data.address, AuthIdRelationId, membership->roleid);
|
||||
|
||||
roles = lappend(roles, definition);
|
||||
}
|
||||
|
||||
systable_endscan(scanDescriptor);
|
||||
table_close(pgAuthMembers, AccessShareLock);
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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.
|
||||
|
@ -1381,6 +1663,49 @@ GetRelationTriggerFunctionDependencyList(Oid relationId)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetTypeConstraintDependencyDefinition creates a list of constraint dependency
|
||||
* definitions for a given type
|
||||
*/
|
||||
static List *
|
||||
GetTypeConstraintDependencyDefinition(Oid typeId)
|
||||
{
|
||||
/* lookup and look all constraints to add them to the CreateDomainStmt */
|
||||
Relation conRel = table_open(ConstraintRelationId, AccessShareLock);
|
||||
|
||||
/* Look for CHECK Constraints on this domain */
|
||||
ScanKeyData key[1];
|
||||
ScanKeyInit(&key[0],
|
||||
Anum_pg_constraint_contypid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(typeId));
|
||||
|
||||
SysScanDesc scan = systable_beginscan(conRel, ConstraintTypidIndexId, true, NULL, 1,
|
||||
key);
|
||||
|
||||
List *dependencies = NIL;
|
||||
HeapTuple conTup = NULL;
|
||||
while (HeapTupleIsValid(conTup = systable_getnext(scan)))
|
||||
{
|
||||
Form_pg_constraint c = (Form_pg_constraint) GETSTRUCT(conTup);
|
||||
|
||||
if (c->contype != CONSTRAINT_CHECK)
|
||||
{
|
||||
/* Ignore non-CHECK constraints, shouldn't be any */
|
||||
continue;
|
||||
}
|
||||
|
||||
dependencies = lappend(dependencies, CreateObjectAddressDependencyDef(
|
||||
ConstraintRelationId, c->oid));
|
||||
}
|
||||
|
||||
systable_endscan(scan);
|
||||
table_close(conRel, NoLock);
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateObjectAddressDependencyDef returns DependencyDefinition object that
|
||||
* stores the ObjectAddress for the database object identified by classId and
|
||||
|
|
|
@ -28,8 +28,11 @@
|
|||
#include "catalog/pg_type.h"
|
||||
#include "citus_version.h"
|
||||
#include "commands/extension.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/colocation_utils.h"
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/metadata/dependency.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/metadata/pg_dist_object.h"
|
||||
#include "distributed/metadata_cache.h"
|
||||
|
@ -42,11 +45,11 @@
|
|||
#include "parser/parse_type.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/regproc.h"
|
||||
#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 +198,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;
|
||||
|
@ -221,6 +224,52 @@ MarkObjectDistributedLocally(const ObjectAddress *distAddress)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* ShouldMarkRelationDistributed is a helper function that
|
||||
* decides whether the input relation should be marked as distributed.
|
||||
*/
|
||||
bool
|
||||
ShouldMarkRelationDistributed(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 isObjectSupported = SupportedDependencyByCitus(&relationAddress);
|
||||
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
|
||||
* isObjectSupported: Citus does not support propagation of some objects
|
||||
* 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 || !isObjectSupported || ownedByExtension || alreadyDistributed ||
|
||||
hasUnsupportedDependency || hasCircularDependency)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreatePgDistObjectEntryCommand creates command to insert pg_dist_object tuple
|
||||
* for the given object address.
|
||||
|
@ -472,3 +521,82 @@ UpdateDistributedObjectColocationId(uint32 oldColocationId,
|
|||
table_close(pgDistObjectRel, NoLock);
|
||||
CommandCounterIncrement();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DistributedFunctionList returns the list of ObjectAddress-es of all the
|
||||
* distributed functions found in pg_dist_object
|
||||
*/
|
||||
List *
|
||||
DistributedFunctionList(void)
|
||||
{
|
||||
List *distributedFunctionList = NIL;
|
||||
|
||||
ScanKeyData key[1];
|
||||
Relation pgDistObjectRel = table_open(DistObjectRelationId(), AccessShareLock);
|
||||
|
||||
/* scan pg_dist_object for classid = ProcedureRelationId via index */
|
||||
ScanKeyInit(&key[0], Anum_pg_dist_object_classid, BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(ProcedureRelationId));
|
||||
SysScanDesc pgDistObjectScan = systable_beginscan(pgDistObjectRel,
|
||||
DistObjectPrimaryKeyIndexId(),
|
||||
true, NULL, 1, key);
|
||||
|
||||
HeapTuple pgDistObjectTup = NULL;
|
||||
while (HeapTupleIsValid(pgDistObjectTup = systable_getnext(pgDistObjectScan)))
|
||||
{
|
||||
Form_pg_dist_object pg_dist_object =
|
||||
(Form_pg_dist_object) GETSTRUCT(pgDistObjectTup);
|
||||
|
||||
ObjectAddress *functionAddress = palloc0(sizeof(ObjectAddress));
|
||||
functionAddress->classId = ProcedureRelationId;
|
||||
functionAddress->objectId = pg_dist_object->objid;
|
||||
functionAddress->objectSubId = pg_dist_object->objsubid;
|
||||
distributedFunctionList = lappend(distributedFunctionList, functionAddress);
|
||||
}
|
||||
|
||||
systable_endscan(pgDistObjectScan);
|
||||
relation_close(pgDistObjectRel, AccessShareLock);
|
||||
return distributedFunctionList;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DistributedSequenceList returns the list of ObjectAddress-es of all the
|
||||
* distributed sequences found in pg_dist_object
|
||||
*/
|
||||
List *
|
||||
DistributedSequenceList(void)
|
||||
{
|
||||
List *distributedSequenceList = NIL;
|
||||
|
||||
ScanKeyData key[1];
|
||||
Relation pgDistObjectRel = table_open(DistObjectRelationId(), AccessShareLock);
|
||||
|
||||
/* scan pg_dist_object for classid = RelationRelationId via index */
|
||||
ScanKeyInit(&key[0], Anum_pg_dist_object_classid, BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(RelationRelationId));
|
||||
SysScanDesc pgDistObjectScan = systable_beginscan(pgDistObjectRel,
|
||||
DistObjectPrimaryKeyIndexId(),
|
||||
true, NULL, 1, key);
|
||||
|
||||
HeapTuple pgDistObjectTup = NULL;
|
||||
while (HeapTupleIsValid(pgDistObjectTup = systable_getnext(pgDistObjectScan)))
|
||||
{
|
||||
Form_pg_dist_object pg_dist_object =
|
||||
(Form_pg_dist_object) GETSTRUCT(pgDistObjectTup);
|
||||
|
||||
if (get_rel_relkind(pg_dist_object->objid) == RELKIND_SEQUENCE)
|
||||
{
|
||||
ObjectAddress *sequenceAddress = palloc0(sizeof(ObjectAddress));
|
||||
sequenceAddress->classId = RelationRelationId;
|
||||
sequenceAddress->objectId = pg_dist_object->objid;
|
||||
sequenceAddress->objectSubId = pg_dist_object->objsubid;
|
||||
distributedSequenceList = lappend(distributedSequenceList, sequenceAddress);
|
||||
}
|
||||
}
|
||||
|
||||
systable_endscan(pgDistObjectScan);
|
||||
relation_close(pgDistObjectRel, AccessShareLock);
|
||||
return distributedSequenceList;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "access/nbtree.h"
|
||||
#include "access/xact.h"
|
||||
#include "access/sysattr.h"
|
||||
#include "catalog/index.h"
|
||||
#include "catalog/indexing.h"
|
||||
#include "catalog/pg_am.h"
|
||||
#include "catalog/pg_collation.h"
|
||||
|
@ -37,6 +38,7 @@
|
|||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/multi_executor.h"
|
||||
#include "distributed/function_utils.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/foreign_key_relationship.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/metadata_utility.h"
|
||||
|
@ -62,11 +64,13 @@
|
|||
#include "parser/parse_func.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/array.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/catcache.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/elog.h"
|
||||
#include "utils/hsearch.h"
|
||||
#include "utils/jsonb.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_13
|
||||
#include "common/hashfn.h"
|
||||
#endif
|
||||
|
@ -149,6 +153,7 @@ typedef struct MetadataCacheData
|
|||
Oid distShardShardidIndexId;
|
||||
Oid distPlacementShardidIndexId;
|
||||
Oid distPlacementPlacementidIndexId;
|
||||
Oid distColocationidIndexId;
|
||||
Oid distPlacementGroupidIndexId;
|
||||
Oid distTransactionRelationId;
|
||||
Oid distTransactionGroupIndexId;
|
||||
|
@ -160,6 +165,7 @@ typedef struct MetadataCacheData
|
|||
Oid workerHashFunctionId;
|
||||
Oid anyValueFunctionId;
|
||||
Oid textSendAsJsonbFunctionId;
|
||||
Oid textoutFunctionId;
|
||||
Oid extensionOwner;
|
||||
Oid binaryCopyFormatId;
|
||||
Oid textCopyFormatId;
|
||||
|
@ -167,6 +173,10 @@ typedef struct MetadataCacheData
|
|||
Oid secondaryNodeRoleId;
|
||||
Oid pgTableIsVisibleFuncId;
|
||||
Oid citusTableIsVisibleFuncId;
|
||||
Oid distAuthinfoRelationId;
|
||||
Oid distAuthinfoIndexId;
|
||||
Oid distPoolinfoRelationId;
|
||||
Oid distPoolinfoIndexId;
|
||||
Oid relationIsAKnownShardFuncId;
|
||||
Oid jsonbExtractPathFuncId;
|
||||
Oid jsonbExtractPathTextFuncId;
|
||||
|
@ -229,6 +239,7 @@ static void InitializeWorkerNodeCache(void);
|
|||
static void RegisterForeignKeyGraphCacheCallbacks(void);
|
||||
static void RegisterWorkerNodeCacheCallbacks(void);
|
||||
static void RegisterLocalGroupIdCacheCallbacks(void);
|
||||
static void RegisterAuthinfoCacheCallbacks(void);
|
||||
static void RegisterCitusTableCacheEntryReleaseCallbacks(void);
|
||||
static uint32 WorkerNodeHashCode(const void *key, Size keySize);
|
||||
static void ResetCitusTableCacheEntry(CitusTableCacheEntry *cacheEntry);
|
||||
|
@ -240,6 +251,7 @@ static void InvalidateForeignRelationGraphCacheCallback(Datum argument, Oid rela
|
|||
static void InvalidateDistRelationCacheCallback(Datum argument, Oid relationId);
|
||||
static void InvalidateNodeRelationCacheCallback(Datum argument, Oid relationId);
|
||||
static void InvalidateLocalGroupIdRelationCacheCallback(Datum argument, Oid relationId);
|
||||
static void InvalidateConnParamsCacheCallback(Datum argument, Oid relationId);
|
||||
static void CitusTableCacheEntryReleaseCallback(ResourceReleasePhase phase, bool isCommit,
|
||||
bool isTopLevel, void *arg);
|
||||
static HeapTuple LookupDistPartitionTuple(Relation pgDistPartition, Oid relationId);
|
||||
|
@ -267,6 +279,10 @@ static bool IsCitusTableTypeInternal(char partitionMethod, char replicationModel
|
|||
CitusTableType tableType);
|
||||
static bool RefreshTableCacheEntryIfInvalid(ShardIdCacheEntry *shardEntry);
|
||||
|
||||
static Oid DistAuthinfoRelationId(void);
|
||||
static Oid DistAuthinfoIndexId(void);
|
||||
static Oid DistPoolinfoRelationId(void);
|
||||
static Oid DistPoolinfoIndexId(void);
|
||||
|
||||
/* exports for SQL callable functions */
|
||||
PG_FUNCTION_INFO_V1(citus_dist_partition_cache_invalidate);
|
||||
|
@ -719,6 +735,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
|
||||
*
|
||||
|
@ -2503,6 +2537,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)
|
||||
|
@ -2536,6 +2581,50 @@ DistPlacementGroupidIndexId(void)
|
|||
}
|
||||
|
||||
|
||||
/* return oid of pg_dist_authinfo relation */
|
||||
static Oid
|
||||
DistAuthinfoRelationId(void)
|
||||
{
|
||||
CachedRelationLookup("pg_dist_authinfo",
|
||||
&MetadataCache.distAuthinfoRelationId);
|
||||
|
||||
return MetadataCache.distAuthinfoRelationId;
|
||||
}
|
||||
|
||||
|
||||
/* return oid of pg_dist_authinfo identification index */
|
||||
static Oid
|
||||
DistAuthinfoIndexId(void)
|
||||
{
|
||||
CachedRelationLookup("pg_dist_authinfo_identification_index",
|
||||
&MetadataCache.distAuthinfoIndexId);
|
||||
|
||||
return MetadataCache.distAuthinfoIndexId;
|
||||
}
|
||||
|
||||
|
||||
/* return oid of pg_dist_poolinfo relation */
|
||||
static Oid
|
||||
DistPoolinfoRelationId(void)
|
||||
{
|
||||
CachedRelationLookup("pg_dist_poolinfo",
|
||||
&MetadataCache.distPoolinfoRelationId);
|
||||
|
||||
return MetadataCache.distPoolinfoRelationId;
|
||||
}
|
||||
|
||||
|
||||
/* return oid of pg_dist_poolinfo primary key index */
|
||||
static Oid
|
||||
DistPoolinfoIndexId(void)
|
||||
{
|
||||
CachedRelationLookup("pg_dist_poolinfo_pkey",
|
||||
&MetadataCache.distPoolinfoIndexId);
|
||||
|
||||
return MetadataCache.distPoolinfoIndexId;
|
||||
}
|
||||
|
||||
|
||||
/* return oid of the read_intermediate_result(text,citus_copy_format) function */
|
||||
Oid
|
||||
CitusReadIntermediateResultFuncId(void)
|
||||
|
@ -2655,6 +2744,42 @@ CitusAnyValueFunctionId(void)
|
|||
}
|
||||
|
||||
|
||||
/* return oid of the citus_text_send_as_jsonb(text) function */
|
||||
Oid
|
||||
CitusTextSendAsJsonbFunctionId(void)
|
||||
{
|
||||
if (MetadataCache.textSendAsJsonbFunctionId == InvalidOid)
|
||||
{
|
||||
List *nameList = list_make2(makeString("pg_catalog"),
|
||||
makeString("citus_text_send_as_jsonb"));
|
||||
Oid paramOids[1] = { TEXTOID };
|
||||
|
||||
MetadataCache.textSendAsJsonbFunctionId =
|
||||
LookupFuncName(nameList, 1, paramOids, false);
|
||||
}
|
||||
|
||||
return MetadataCache.textSendAsJsonbFunctionId;
|
||||
}
|
||||
|
||||
|
||||
/* return oid of the textout(text) function */
|
||||
Oid
|
||||
TextOutFunctionId(void)
|
||||
{
|
||||
if (MetadataCache.textoutFunctionId == InvalidOid)
|
||||
{
|
||||
List *nameList = list_make2(makeString("pg_catalog"),
|
||||
makeString("textout"));
|
||||
Oid paramOids[1] = { TEXTOID };
|
||||
|
||||
MetadataCache.textoutFunctionId =
|
||||
LookupFuncName(nameList, 1, paramOids, false);
|
||||
}
|
||||
|
||||
return MetadataCache.textoutFunctionId;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PgTableVisibleFuncId returns oid of the pg_table_is_visible function.
|
||||
*/
|
||||
|
@ -3243,7 +3368,7 @@ citus_conninfo_cache_invalidate(PG_FUNCTION_ARGS)
|
|||
errmsg("must be called as trigger")));
|
||||
}
|
||||
|
||||
/* no-op in community edition */
|
||||
CitusInvalidateRelcacheByRelid(DistAuthinfoRelationId());
|
||||
|
||||
PG_RETURN_DATUM(PointerGetDatum(NULL));
|
||||
}
|
||||
|
@ -3371,6 +3496,7 @@ InitializeCaches(void)
|
|||
RegisterForeignKeyGraphCacheCallbacks();
|
||||
RegisterWorkerNodeCacheCallbacks();
|
||||
RegisterLocalGroupIdCacheCallbacks();
|
||||
RegisterAuthinfoCacheCallbacks();
|
||||
RegisterCitusTableCacheEntryReleaseCallbacks();
|
||||
}
|
||||
PG_CATCH();
|
||||
|
@ -3776,6 +3902,18 @@ RegisterLocalGroupIdCacheCallbacks(void)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* RegisterAuthinfoCacheCallbacks registers the callbacks required to
|
||||
* maintain cached connection parameters at fresh values.
|
||||
*/
|
||||
static void
|
||||
RegisterAuthinfoCacheCallbacks(void)
|
||||
{
|
||||
/* Watch for invalidation events. */
|
||||
CacheRegisterRelcacheCallback(InvalidateConnParamsCacheCallback, (Datum) 0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* WorkerNodeHashCode computes the hash code for a worker node from the node's
|
||||
* host name and port number. Nodes that only differ by their rack locations
|
||||
|
@ -4274,6 +4412,30 @@ InvalidateLocalGroupIdRelationCacheCallback(Datum argument, Oid relationId)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* InvalidateConnParamsCacheCallback sets isValid flag to false for all entries
|
||||
* in ConnParamsHash, a cache used during connection establishment.
|
||||
*/
|
||||
static void
|
||||
InvalidateConnParamsCacheCallback(Datum argument, Oid relationId)
|
||||
{
|
||||
if (relationId == MetadataCache.distAuthinfoRelationId ||
|
||||
relationId == MetadataCache.distPoolinfoRelationId ||
|
||||
relationId == InvalidOid)
|
||||
{
|
||||
ConnParamsHashEntry *entry = NULL;
|
||||
HASH_SEQ_STATUS status;
|
||||
|
||||
hash_seq_init(&status, ConnParamsHash);
|
||||
|
||||
while ((entry = (ConnParamsHashEntry *) hash_seq_search(&status)) != NULL)
|
||||
{
|
||||
entry->isValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CitusTableCacheFlushInvalidatedEntries frees invalidated cache entries.
|
||||
* Invalidated entries aren't freed immediately as callers expect their lifetime
|
||||
|
@ -4853,6 +5015,12 @@ DistNodeMetadata(void)
|
|||
"could not find any entries in pg_dist_metadata")));
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy the jsonb result before closing the table
|
||||
* since that memory can be freed.
|
||||
*/
|
||||
metadata = JsonbPGetDatum(DatumGetJsonbPCopy(metadata));
|
||||
|
||||
systable_endscan(scanDescriptor);
|
||||
table_close(pgDistNodeMetadata, AccessShareLock);
|
||||
|
||||
|
@ -4875,37 +5043,164 @@ role_exists(PG_FUNCTION_ARGS)
|
|||
|
||||
|
||||
/*
|
||||
* authinfo_valid is a check constraint which errors on all rows, intended for
|
||||
* use in prohibiting writes to pg_dist_authinfo in Citus Community.
|
||||
* GetPoolinfoViaCatalog searches the pg_dist_poolinfo table for a row matching
|
||||
* the provided nodeId and returns the poolinfo field of this row if found.
|
||||
* Otherwise, this function returns NULL.
|
||||
*/
|
||||
Datum
|
||||
authinfo_valid(PG_FUNCTION_ARGS)
|
||||
char *
|
||||
GetPoolinfoViaCatalog(int64 nodeId)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot write to pg_dist_authinfo"),
|
||||
errdetail(
|
||||
"Citus Community Edition does not support the use of "
|
||||
"custom authentication options."),
|
||||
errhint(
|
||||
"To learn more about using advanced authentication schemes "
|
||||
"with Citus, please contact us at "
|
||||
"https://citusdata.com/about/contact_us")));
|
||||
ScanKeyData scanKey[1];
|
||||
const int scanKeyCount = 1;
|
||||
const AttrNumber nodeIdIdx = 1, poolinfoIdx = 2;
|
||||
Relation pgDistPoolinfo = table_open(DistPoolinfoRelationId(), AccessShareLock);
|
||||
bool indexOK = true;
|
||||
char *poolinfo = NULL;
|
||||
|
||||
/* set scan arguments */
|
||||
ScanKeyInit(&scanKey[0], nodeIdIdx, BTEqualStrategyNumber, F_INT4EQ,
|
||||
Int32GetDatum(nodeId));
|
||||
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgDistPoolinfo, DistPoolinfoIndexId(),
|
||||
indexOK,
|
||||
NULL, scanKeyCount, scanKey);
|
||||
|
||||
HeapTuple heapTuple = systable_getnext(scanDescriptor);
|
||||
if (HeapTupleIsValid(heapTuple))
|
||||
{
|
||||
TupleDesc tupleDescriptor = RelationGetDescr(pgDistPoolinfo);
|
||||
bool isNull = false;
|
||||
|
||||
Datum poolinfoDatum = heap_getattr(heapTuple, poolinfoIdx, tupleDescriptor,
|
||||
&isNull);
|
||||
|
||||
Assert(!isNull);
|
||||
|
||||
poolinfo = TextDatumGetCString(poolinfoDatum);
|
||||
}
|
||||
|
||||
systable_endscan(scanDescriptor);
|
||||
table_close(pgDistPoolinfo, AccessShareLock);
|
||||
|
||||
return poolinfo;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* poolinfo_valid is a check constraint which errors on all rows, intended for
|
||||
* use in prohibiting writes to pg_dist_poolinfo in Citus Community.
|
||||
* GetAuthinfoViaCatalog searches pg_dist_authinfo for a row matching a pro-
|
||||
* vided role and node id. Three types of rules are currently permitted: those
|
||||
* matching a specific node (non-zero nodeid), those matching all nodes (a
|
||||
* nodeid of zero), and those denoting a loopback connection (nodeid of -1).
|
||||
* Rolename must always be specified. If both types of rules exist for a given
|
||||
* user/host, the more specific (host-specific) rule wins. This means that when
|
||||
* both a zero and non-zero row exist for a given rolename, the non-zero row
|
||||
* has precedence.
|
||||
*
|
||||
* In short, this function will return a rule matching nodeId, or if that's
|
||||
* absent the rule for 0, or if that's absent, an empty string. Callers can
|
||||
* just use the returned authinfo and know the precedence has been honored.
|
||||
*/
|
||||
char *
|
||||
GetAuthinfoViaCatalog(const char *roleName, int64 nodeId)
|
||||
{
|
||||
char *authinfo = "";
|
||||
Datum nodeIdDatumArray[2] = {
|
||||
Int32GetDatum(nodeId),
|
||||
Int32GetDatum(WILDCARD_NODE_ID)
|
||||
};
|
||||
ArrayType *nodeIdArrayType = DatumArrayToArrayType(nodeIdDatumArray,
|
||||
lengthof(nodeIdDatumArray),
|
||||
INT4OID);
|
||||
ScanKeyData scanKey[2];
|
||||
const AttrNumber nodeIdIdx = 1, roleIdx = 2, authinfoIdx = 3;
|
||||
|
||||
/*
|
||||
* Our index's definition ensures correct precedence for positive nodeIds,
|
||||
* but when handling a negative value we need to traverse backwards to keep
|
||||
* the invariant that the zero rule has lowest precedence.
|
||||
*/
|
||||
ScanDirection direction = (nodeId < 0) ? BackwardScanDirection : ForwardScanDirection;
|
||||
|
||||
if (ReindexIsProcessingIndex(DistAuthinfoIndexId()))
|
||||
{
|
||||
ereport(ERROR, (errmsg("authinfo is being reindexed; try again")));
|
||||
}
|
||||
|
||||
memset(&scanKey, 0, sizeof(scanKey));
|
||||
|
||||
/* first column in index is rolename, need exact match there ... */
|
||||
ScanKeyInit(&scanKey[0], roleIdx, BTEqualStrategyNumber,
|
||||
F_NAMEEQ, CStringGetDatum(roleName));
|
||||
|
||||
/* second column is nodeId, match against array of nodeid and zero (any node) ... */
|
||||
ScanKeyInit(&scanKey[1], nodeIdIdx, BTEqualStrategyNumber,
|
||||
F_INT4EQ, PointerGetDatum(nodeIdArrayType));
|
||||
scanKey[1].sk_flags |= SK_SEARCHARRAY;
|
||||
|
||||
/*
|
||||
* It's important that we traverse the index in order: we need to ensure
|
||||
* that rules with nodeid 0 are encountered last. We'll use the first tuple
|
||||
* we find. This ordering defines the precedence order of authinfo rules.
|
||||
*/
|
||||
Relation pgDistAuthinfo = table_open(DistAuthinfoRelationId(), AccessShareLock);
|
||||
Relation pgDistAuthinfoIdx = index_open(DistAuthinfoIndexId(), AccessShareLock);
|
||||
SysScanDesc scanDescriptor = systable_beginscan_ordered(pgDistAuthinfo,
|
||||
pgDistAuthinfoIdx,
|
||||
NULL, lengthof(scanKey),
|
||||
scanKey);
|
||||
|
||||
/* first tuple represents highest-precedence rule for this node */
|
||||
HeapTuple authinfoTuple = systable_getnext_ordered(scanDescriptor, direction);
|
||||
if (HeapTupleIsValid(authinfoTuple))
|
||||
{
|
||||
TupleDesc tupleDescriptor = RelationGetDescr(pgDistAuthinfo);
|
||||
bool isNull = false;
|
||||
|
||||
Datum authinfoDatum = heap_getattr(authinfoTuple, authinfoIdx,
|
||||
tupleDescriptor, &isNull);
|
||||
|
||||
Assert(!isNull);
|
||||
|
||||
authinfo = TextDatumGetCString(authinfoDatum);
|
||||
}
|
||||
|
||||
systable_endscan_ordered(scanDescriptor);
|
||||
index_close(pgDistAuthinfoIdx, AccessShareLock);
|
||||
table_close(pgDistAuthinfo, AccessShareLock);
|
||||
|
||||
return authinfo;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* authinfo_valid is a check constraint to verify that an inserted authinfo row
|
||||
* uses only permitted libpq parameters.
|
||||
*/
|
||||
Datum
|
||||
authinfo_valid(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *authinfo = TextDatumGetCString(PG_GETARG_DATUM(0));
|
||||
|
||||
/* this array _must_ be kept in an order usable by bsearch */
|
||||
const char *allowList[] = { "password", "sslcert", "sslkey" };
|
||||
bool authinfoValid = CheckConninfo(authinfo, allowList, lengthof(allowList), NULL);
|
||||
|
||||
PG_RETURN_BOOL(authinfoValid);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* poolinfo_valid is a check constraint to verify that an inserted poolinfo row
|
||||
* uses only permitted libpq parameters.
|
||||
*/
|
||||
Datum
|
||||
poolinfo_valid(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot write to pg_dist_poolinfo"),
|
||||
errdetail(
|
||||
"Citus Community Edition does not support the use of "
|
||||
"pooler options."),
|
||||
errhint("To learn more about using advanced pooling schemes "
|
||||
"with Citus, please contact us at "
|
||||
"https://citusdata.com/about/contact_us")));
|
||||
char *poolinfo = TextDatumGetCString(PG_GETARG_DATUM(0));
|
||||
|
||||
/* this array _must_ be kept in an order usable by bsearch */
|
||||
const char *allowList[] = { "dbname", "host", "port" };
|
||||
bool poolinfoValid = CheckConninfo(poolinfo, allowList, lengthof(allowList), NULL);
|
||||
|
||||
PG_RETURN_BOOL(poolinfoValid);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
#include "distributed/metadata_cache.h"
|
||||
#include "distributed/metadata_sync.h"
|
||||
#include "distributed/metadata_utility.h"
|
||||
#include "distributed/metadata/dependency.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/metadata/pg_dist_object.h"
|
||||
#include "distributed/multi_executor.h"
|
||||
|
@ -70,6 +71,7 @@
|
|||
#include "executor/spi.h"
|
||||
#include "foreign/foreign.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/pg_list.h"
|
||||
#include "pgstat.h"
|
||||
#include "postmaster/bgworker.h"
|
||||
|
@ -95,6 +97,8 @@ static char * SchemaOwnerName(Oid objectId);
|
|||
static bool HasMetadataWorkers(void);
|
||||
static void CreateShellTableOnWorkers(Oid relationId);
|
||||
static void CreateTableMetadataOnWorkers(Oid relationId);
|
||||
static void CreateDependingViewsOnWorkers(Oid relationId);
|
||||
static NodeMetadataSyncResult SyncNodeMetadataToNodesOptional(void);
|
||||
static bool ShouldSyncTableMetadataInternal(bool hashDistributed,
|
||||
bool citusTableWithNoDistKey);
|
||||
static bool SyncNodeMetadataSnapshotToNode(WorkerNode *workerNode, bool raiseOnError);
|
||||
|
@ -110,6 +114,11 @@ static List * GetObjectsForGrantStmt(ObjectType objectType, Oid objectId);
|
|||
static AccessPriv * GetAccessPrivObjectForGrantStmt(char *permission);
|
||||
static List * GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid,
|
||||
AclItem *aclItem);
|
||||
static List * GenerateGrantOnFunctionQueriesFromAclItem(Oid schemaOid,
|
||||
AclItem *aclItem);
|
||||
static List * GrantOnSequenceDDLCommands(Oid sequenceOid);
|
||||
static List * GenerateGrantOnSequenceQueriesFromAclItem(Oid sequenceOid,
|
||||
AclItem *aclItem);
|
||||
static void SetLocalReplicateReferenceTablesOnActivate(bool state);
|
||||
static char * GenerateSetRoleQuery(Oid roleOid);
|
||||
static void MetadataSyncSigTermHandler(SIGNAL_ARGS);
|
||||
|
@ -136,6 +145,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);
|
||||
|
@ -192,6 +202,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().
|
||||
|
@ -271,7 +308,8 @@ SyncNodeMetadataToNode(const char *nodeNameString, int32 nodePort)
|
|||
* SyncCitusTableMetadata syncs citus table metadata to worker nodes with metadata.
|
||||
* Our definition of metadata includes the shell table and its inter relations with
|
||||
* other shell tables, corresponding pg_dist_object, pg_dist_partiton, pg_dist_shard
|
||||
* and pg_dist_shard placement entries.
|
||||
* and pg_dist_shard placement entries. This function also propagates the views that
|
||||
* depend on the given relation, to the metadata workers.
|
||||
*/
|
||||
void
|
||||
SyncCitusTableMetadata(Oid relationId)
|
||||
|
@ -286,6 +324,51 @@ SyncCitusTableMetadata(Oid relationId)
|
|||
ObjectAddressSet(relationAddress, RelationRelationId, relationId);
|
||||
MarkObjectDistributed(&relationAddress);
|
||||
}
|
||||
|
||||
CreateDependingViewsOnWorkers(relationId);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateDependingViewsOnWorkers takes a relationId and creates the views that depend on
|
||||
* that relation on workers with metadata. Propagated views are marked as distributed.
|
||||
*/
|
||||
static void
|
||||
CreateDependingViewsOnWorkers(Oid relationId)
|
||||
{
|
||||
List *views = GetDependingViews(relationId);
|
||||
|
||||
if (list_length(views) < 1)
|
||||
{
|
||||
/* no view to propagate */
|
||||
return;
|
||||
}
|
||||
|
||||
SendCommandToWorkersWithMetadata(DISABLE_DDL_PROPAGATION);
|
||||
|
||||
Oid viewOid = InvalidOid;
|
||||
foreach_oid(viewOid, views)
|
||||
{
|
||||
if (!ShouldMarkRelationDistributed(viewOid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectAddress viewAddress = { 0 };
|
||||
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
|
||||
|
||||
EnsureDependenciesExistOnAllNodes(&viewAddress);
|
||||
|
||||
char *createViewCommand = CreateViewDDLCommand(viewOid);
|
||||
char *alterViewOwnerCommand = AlterViewOwnerCommand(viewOid);
|
||||
|
||||
SendCommandToWorkersWithMetadata(createViewCommand);
|
||||
SendCommandToWorkersWithMetadata(alterViewOwnerCommand);
|
||||
|
||||
MarkObjectDistributed(&viewAddress);
|
||||
}
|
||||
|
||||
SendCommandToWorkersWithMetadata(ENABLE_DDL_PROPAGATION);
|
||||
}
|
||||
|
||||
|
||||
|
@ -423,6 +506,25 @@ 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) ||
|
||||
ShouldSyncSequenceMetadata(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
|
||||
|
@ -488,6 +590,26 @@ ShouldSyncTableMetadataInternal(bool hashDistributed, bool citusTableWithNoDistK
|
|||
|
||||
|
||||
/*
|
||||
* ShouldSyncSequenceMetadata checks if the metadata of a sequence should be
|
||||
* propagated to metadata workers, i.e. the sequence is marked as distributed
|
||||
*/
|
||||
bool
|
||||
ShouldSyncSequenceMetadata(Oid relationId)
|
||||
{
|
||||
if (!OidIsValid(relationId) || !(get_rel_relkind(relationId) == RELKIND_SEQUENCE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectAddress sequenceAddress = { 0 };
|
||||
ObjectAddressSet(sequenceAddress, RelationRelationId, relationId);
|
||||
|
||||
return IsObjectDistributed(&sequenceAddress);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SyncMetadataSnapshotToNode does the following:
|
||||
* SyncNodeMetadataSnapshotToNode does the following:
|
||||
* 1. Sets the localGroupId on the worker so the worker knows which tuple in
|
||||
* pg_dist_node represents itself.
|
||||
|
@ -522,10 +644,10 @@ SyncNodeMetadataSnapshotToNode(WorkerNode *workerNode, bool raiseOnError)
|
|||
*/
|
||||
if (raiseOnError)
|
||||
{
|
||||
SendMetadataCommandListToWorkerInCoordinatedTransaction(workerNode->workerName,
|
||||
workerNode->workerPort,
|
||||
currentUser,
|
||||
recreateMetadataSnapshotCommandList);
|
||||
SendMetadataCommandListToWorkerListInCoordinatedTransaction(list_make1(
|
||||
workerNode),
|
||||
currentUser,
|
||||
recreateMetadataSnapshotCommandList);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -1249,6 +1371,23 @@ ShardListInsertCommand(List *shardIntervalList)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* ShardListDeleteCommand generates a command list that can be executed to delete
|
||||
* shard and shard placement metadata for the given shard.
|
||||
*/
|
||||
List *
|
||||
ShardDeleteCommandList(ShardInterval *shardInterval)
|
||||
{
|
||||
uint64 shardId = shardInterval->shardId;
|
||||
|
||||
StringInfo deleteShardCommand = makeStringInfo();
|
||||
appendStringInfo(deleteShardCommand,
|
||||
"SELECT citus_internal_delete_shard_metadata(%ld);", shardId);
|
||||
|
||||
return list_make1(deleteShardCommand->data);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* NodeDeleteCommand generate a command that can be
|
||||
* executed to delete the metadata for a worker node.
|
||||
|
@ -1383,6 +1522,8 @@ DDLCommandsForSequence(Oid sequenceOid, char *ownerName)
|
|||
|
||||
sequenceDDLList = lappend(sequenceDDLList, wrappedSequenceDef->data);
|
||||
sequenceDDLList = lappend(sequenceDDLList, sequenceGrantStmt->data);
|
||||
sequenceDDLList = list_concat(sequenceDDLList, GrantOnSequenceDDLCommands(
|
||||
sequenceOid));
|
||||
|
||||
return sequenceDDLList;
|
||||
}
|
||||
|
@ -1842,7 +1983,7 @@ GrantOnSchemaDDLCommands(Oid schemaOid)
|
|||
|
||||
|
||||
/*
|
||||
* GenerateGrantOnSchemaQueryFromACL generates a query string for replicating a users permissions
|
||||
* GenerateGrantOnSchemaQueryFromACLItem generates a query string for replicating a users permissions
|
||||
* on a schema.
|
||||
*/
|
||||
List *
|
||||
|
@ -1926,6 +2067,34 @@ GetObjectsForGrantStmt(ObjectType objectType, Oid objectId)
|
|||
return list_make1(makeString(get_namespace_name(objectId)));
|
||||
}
|
||||
|
||||
/* enterprise supported object types */
|
||||
case OBJECT_FUNCTION:
|
||||
case OBJECT_PROCEDURE:
|
||||
{
|
||||
ObjectWithArgs *owa = ObjectWithArgsFromOid(objectId);
|
||||
return list_make1(owa);
|
||||
}
|
||||
|
||||
case OBJECT_FDW:
|
||||
{
|
||||
ForeignDataWrapper *fdw = GetForeignDataWrapper(objectId);
|
||||
return list_make1(makeString(fdw->fdwname));
|
||||
}
|
||||
|
||||
case OBJECT_FOREIGN_SERVER:
|
||||
{
|
||||
ForeignServer *server = GetForeignServer(objectId);
|
||||
return list_make1(makeString(server->servername));
|
||||
}
|
||||
|
||||
case OBJECT_SEQUENCE:
|
||||
{
|
||||
Oid namespaceOid = get_rel_namespace(objectId);
|
||||
RangeVar *sequence = makeRangeVar(get_namespace_name(namespaceOid),
|
||||
get_rel_name(objectId), -1);
|
||||
return list_make1(sequence);
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
elog(ERROR, "unsupported object type for GRANT");
|
||||
|
@ -1936,6 +2105,211 @@ GetObjectsForGrantStmt(ObjectType objectType, Oid objectId)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GrantOnFunctionDDLCommands creates a list of ddl command for replicating the permissions
|
||||
* of roles on distributed functions.
|
||||
*/
|
||||
List *
|
||||
GrantOnFunctionDDLCommands(Oid functionOid)
|
||||
{
|
||||
HeapTuple proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid));
|
||||
|
||||
bool isNull = true;
|
||||
Datum aclDatum = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proacl,
|
||||
&isNull);
|
||||
if (isNull)
|
||||
{
|
||||
ReleaseSysCache(proctup);
|
||||
return NIL;
|
||||
}
|
||||
|
||||
Acl *acl = DatumGetAclPCopy(aclDatum);
|
||||
AclItem *aclDat = ACL_DAT(acl);
|
||||
int aclNum = ACL_NUM(acl);
|
||||
List *commands = NIL;
|
||||
|
||||
ReleaseSysCache(proctup);
|
||||
|
||||
for (int i = 0; i < aclNum; i++)
|
||||
{
|
||||
commands = list_concat(commands,
|
||||
GenerateGrantOnFunctionQueriesFromAclItem(
|
||||
functionOid,
|
||||
&aclDat[i]));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GrantOnForeignServerDDLCommands creates a list of ddl command for replicating the
|
||||
* permissions of roles on distributed foreign servers.
|
||||
*/
|
||||
List *
|
||||
GrantOnForeignServerDDLCommands(Oid serverId)
|
||||
{
|
||||
HeapTuple servertup = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverId));
|
||||
|
||||
bool isNull = true;
|
||||
Datum aclDatum = SysCacheGetAttr(FOREIGNSERVEROID, servertup,
|
||||
Anum_pg_foreign_server_srvacl, &isNull);
|
||||
if (isNull)
|
||||
{
|
||||
ReleaseSysCache(servertup);
|
||||
return NIL;
|
||||
}
|
||||
|
||||
Acl *aclEntry = DatumGetAclPCopy(aclDatum);
|
||||
AclItem *privileges = ACL_DAT(aclEntry);
|
||||
int numberOfPrivsGranted = ACL_NUM(aclEntry);
|
||||
List *commands = NIL;
|
||||
|
||||
ReleaseSysCache(servertup);
|
||||
|
||||
for (int i = 0; i < numberOfPrivsGranted; i++)
|
||||
{
|
||||
commands = list_concat(commands,
|
||||
GenerateGrantOnForeignServerQueriesFromAclItem(
|
||||
serverId,
|
||||
&privileges[i]));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateGrantOnForeignServerQueriesFromAclItem generates a query string for
|
||||
* replicating a users permissions on a foreign server.
|
||||
*/
|
||||
List *
|
||||
GenerateGrantOnForeignServerQueriesFromAclItem(Oid serverId, AclItem *aclItem)
|
||||
{
|
||||
/* privileges to be granted */
|
||||
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_FOREIGN_SERVER;
|
||||
|
||||
/* WITH GRANT OPTION clause */
|
||||
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_FOREIGN_SERVER;
|
||||
|
||||
/*
|
||||
* seems unlikely but we check if there is a grant option in the list without the actual permission
|
||||
*/
|
||||
Assert(!(grants & ACL_USAGE) || (permissions & ACL_USAGE));
|
||||
|
||||
Oid granteeOid = aclItem->ai_grantee;
|
||||
List *queries = NIL;
|
||||
|
||||
/* switch to the role which had granted acl */
|
||||
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
|
||||
|
||||
/* generate the GRANT stmt that will be executed by the grantor role */
|
||||
if (permissions & ACL_USAGE)
|
||||
{
|
||||
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||
OBJECT_FOREIGN_SERVER, granteeOid, serverId,
|
||||
"USAGE", grants & ACL_USAGE));
|
||||
queries = lappend(queries, query);
|
||||
}
|
||||
|
||||
/* reset the role back */
|
||||
queries = lappend(queries, "RESET ROLE");
|
||||
|
||||
return queries;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateGrantOnFunctionQueryFromACLItem generates a query string for replicating a users permissions
|
||||
* on a distributed function.
|
||||
*/
|
||||
List *
|
||||
GenerateGrantOnFunctionQueriesFromAclItem(Oid functionOid, AclItem *aclItem)
|
||||
{
|
||||
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_FUNCTION;
|
||||
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_FUNCTION;
|
||||
|
||||
/*
|
||||
* seems unlikely but we check if there is a grant option in the list without the actual permission
|
||||
*/
|
||||
Assert(!(grants & ACL_EXECUTE) || (permissions & ACL_EXECUTE));
|
||||
Oid granteeOid = aclItem->ai_grantee;
|
||||
List *queries = NIL;
|
||||
|
||||
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
|
||||
|
||||
if (permissions & ACL_EXECUTE)
|
||||
{
|
||||
char prokind = get_func_prokind(functionOid);
|
||||
ObjectType objectType;
|
||||
|
||||
if (prokind == PROKIND_FUNCTION)
|
||||
{
|
||||
objectType = OBJECT_FUNCTION;
|
||||
}
|
||||
else if (prokind == PROKIND_PROCEDURE)
|
||||
{
|
||||
objectType = OBJECT_PROCEDURE;
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(ERROR, (errmsg("unsupported prokind"),
|
||||
errdetail("GRANT commands on procedures are propagated only "
|
||||
"for procedures and functions.")));
|
||||
}
|
||||
|
||||
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||
objectType, granteeOid, functionOid, "EXECUTE",
|
||||
grants & ACL_EXECUTE));
|
||||
queries = lappend(queries, query);
|
||||
}
|
||||
|
||||
queries = lappend(queries, "RESET ROLE");
|
||||
|
||||
return queries;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateGrantOnFDWQueriesFromAclItem generates a query string for
|
||||
* replicating a users permissions on a foreign data wrapper.
|
||||
*/
|
||||
List *
|
||||
GenerateGrantOnFDWQueriesFromAclItem(Oid FDWId, AclItem *aclItem)
|
||||
{
|
||||
/* privileges to be granted */
|
||||
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_FDW;
|
||||
|
||||
/* WITH GRANT OPTION clause */
|
||||
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_FDW;
|
||||
|
||||
/*
|
||||
* seems unlikely but we check if there is a grant option in the list without the actual permission
|
||||
*/
|
||||
Assert(!(grants & ACL_USAGE) || (permissions & ACL_USAGE));
|
||||
|
||||
Oid granteeOid = aclItem->ai_grantee;
|
||||
List *queries = NIL;
|
||||
|
||||
/* switch to the role which had granted acl */
|
||||
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
|
||||
|
||||
/* generate the GRANT stmt that will be executed by the grantor role */
|
||||
if (permissions & ACL_USAGE)
|
||||
{
|
||||
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||
OBJECT_FDW, granteeOid, FDWId, "USAGE",
|
||||
grants & ACL_USAGE));
|
||||
queries = lappend(queries, query);
|
||||
}
|
||||
|
||||
/* reset the role back */
|
||||
queries = lappend(queries, "RESET ROLE");
|
||||
|
||||
return queries;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetAccessPrivObjectForGrantStmt creates an AccessPriv object for the given permission.
|
||||
* It will be used when creating GrantStmt objects.
|
||||
|
@ -1951,6 +2325,93 @@ GetAccessPrivObjectForGrantStmt(char *permission)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GrantOnSequenceDDLCommands creates a list of ddl command for replicating the permissions
|
||||
* of roles on distributed sequences.
|
||||
*/
|
||||
static List *
|
||||
GrantOnSequenceDDLCommands(Oid sequenceOid)
|
||||
{
|
||||
HeapTuple seqtup = SearchSysCache1(RELOID, ObjectIdGetDatum(sequenceOid));
|
||||
bool isNull = false;
|
||||
Datum aclDatum = SysCacheGetAttr(RELOID, seqtup, Anum_pg_class_relacl,
|
||||
&isNull);
|
||||
if (isNull)
|
||||
{
|
||||
ReleaseSysCache(seqtup);
|
||||
return NIL;
|
||||
}
|
||||
|
||||
Acl *acl = DatumGetAclPCopy(aclDatum);
|
||||
AclItem *aclDat = ACL_DAT(acl);
|
||||
int aclNum = ACL_NUM(acl);
|
||||
List *commands = NIL;
|
||||
|
||||
ReleaseSysCache(seqtup);
|
||||
|
||||
for (int i = 0; i < aclNum; i++)
|
||||
{
|
||||
commands = list_concat(commands,
|
||||
GenerateGrantOnSequenceQueriesFromAclItem(
|
||||
sequenceOid,
|
||||
&aclDat[i]));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateGrantOnSequenceQueriesFromAclItem generates a query string for replicating a users permissions
|
||||
* on a distributed sequence.
|
||||
*/
|
||||
static List *
|
||||
GenerateGrantOnSequenceQueriesFromAclItem(Oid sequenceOid, AclItem *aclItem)
|
||||
{
|
||||
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_SEQUENCE;
|
||||
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_SEQUENCE;
|
||||
|
||||
/*
|
||||
* seems unlikely but we check if there is a grant option in the list without the actual permission
|
||||
*/
|
||||
Assert(!(grants & ACL_USAGE) || (permissions & ACL_USAGE));
|
||||
Assert(!(grants & ACL_SELECT) || (permissions & ACL_SELECT));
|
||||
Assert(!(grants & ACL_UPDATE) || (permissions & ACL_UPDATE));
|
||||
|
||||
Oid granteeOid = aclItem->ai_grantee;
|
||||
List *queries = NIL;
|
||||
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
|
||||
|
||||
if (permissions & ACL_USAGE)
|
||||
{
|
||||
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||
OBJECT_SEQUENCE, granteeOid, sequenceOid,
|
||||
"USAGE", grants & ACL_USAGE));
|
||||
queries = lappend(queries, query);
|
||||
}
|
||||
|
||||
if (permissions & ACL_SELECT)
|
||||
{
|
||||
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||
OBJECT_SEQUENCE, granteeOid, sequenceOid,
|
||||
"SELECT", grants & ACL_SELECT));
|
||||
queries = lappend(queries, query);
|
||||
}
|
||||
|
||||
if (permissions & ACL_UPDATE)
|
||||
{
|
||||
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||
OBJECT_SEQUENCE, granteeOid, sequenceOid,
|
||||
"UPDATE", grants & ACL_UPDATE));
|
||||
queries = lappend(queries, query);
|
||||
}
|
||||
|
||||
queries = lappend(queries, "RESET ROLE");
|
||||
|
||||
return queries;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SetLocalEnableMetadataSync sets the enable_metadata_sync locally
|
||||
*/
|
||||
|
@ -2042,7 +2503,7 @@ SchemaOwnerName(Oid objectId)
|
|||
static bool
|
||||
HasMetadataWorkers(void)
|
||||
{
|
||||
List *workerNodeList = ActivePrimaryNonCoordinatorNodeList(NoLock);
|
||||
List *workerNodeList = ActiveReadableNonCoordinatorNodeList();
|
||||
|
||||
WorkerNode *workerNode = NULL;
|
||||
foreach_ptr(workerNode, workerNodeList)
|
||||
|
@ -2217,16 +2678,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())
|
||||
|
@ -2286,6 +2747,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.
|
||||
|
@ -2332,7 +2833,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 */
|
||||
|
@ -3391,12 +3892,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))
|
||||
{
|
||||
|
@ -3454,10 +3962,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)
|
||||
|
|
|
@ -66,6 +66,9 @@
|
|||
#include "utils/lsyscache.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/syscache.h"
|
||||
#if PG_VERSION_NUM < 120000
|
||||
#include "utils/tqual.h"
|
||||
#endif
|
||||
|
||||
#define DISK_SPACE_FIELDS 2
|
||||
|
||||
|
@ -2175,11 +2178,8 @@ EnsureSuperUser(void)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return a table's owner as a string.
|
||||
*/
|
||||
char *
|
||||
TableOwner(Oid relationId)
|
||||
Oid
|
||||
TableOwnerOid(Oid relationId)
|
||||
{
|
||||
HeapTuple tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
|
@ -2191,8 +2191,17 @@ TableOwner(Oid relationId)
|
|||
Oid userId = ((Form_pg_class) GETSTRUCT(tuple))->relowner;
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
return userId;
|
||||
}
|
||||
|
||||
return GetUserNameFromId(userId, false);
|
||||
|
||||
/*
|
||||
* Return a table's owner as a string.
|
||||
*/
|
||||
char *
|
||||
TableOwner(Oid relationId)
|
||||
{
|
||||
return GetUserNameFromId(TableOwnerOid(relationId), false);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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,45 +514,89 @@ 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 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)
|
||||
{
|
||||
TriggerMetadataSyncOnCommit();
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
@ -693,8 +746,6 @@ PgDistTableMetadataSyncCommandList(void)
|
|||
metadataSnapshotCommandList = list_concat(metadataSnapshotCommandList,
|
||||
colocationGroupSyncCommandList);
|
||||
|
||||
/* As the last step, propagate the pg_dist_object entities */
|
||||
Assert(ShouldPropagate());
|
||||
List *distributedObjectSyncCommandList = DistributedObjectMetadataSyncCommandList();
|
||||
metadataSnapshotCommandList = list_concat(metadataSnapshotCommandList,
|
||||
distributedObjectSyncCommandList);
|
||||
|
@ -790,7 +841,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 +850,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 +884,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 +903,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 +912,36 @@ 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 *nodesWithMetadata = NIL;
|
||||
WorkerNode *workerNode = NULL;
|
||||
foreach_ptr(workerNode, nodeList)
|
||||
{
|
||||
if (NodeIsPrimary(workerNode) && !NodeIsCoordinator(workerNode))
|
||||
{
|
||||
nodesWithMetadata = lappend(nodesWithMetadata, workerNode);
|
||||
}
|
||||
}
|
||||
|
||||
if (nodesWithMetadata == NIL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List *syncPgDistMetadataCommandList = PgDistTableMetadataSyncCommandList();
|
||||
SendMetadataCommandListToWorkerListInCoordinatedTransaction(
|
||||
nodesWithMetadata,
|
||||
CurrentUserName(),
|
||||
syncPgDistMetadataCommandList);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1065,15 +1137,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 +1161,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,9 +1434,9 @@ 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())
|
||||
{
|
||||
TriggerMetadataSyncOnCommit();
|
||||
TriggerNodeMetadataSyncOnCommit();
|
||||
}
|
||||
|
||||
if (handle != NULL)
|
||||
|
@ -1558,6 +1673,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 &&
|
||||
ActiveReadableNodeCount() > 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.
|
||||
|
@ -1761,12 +1899,15 @@ RemoveNodeFromCluster(char *nodeName, int32 nodePort)
|
|||
|
||||
RemoveOldShardPlacementForNodeGroup(workerNode->groupId);
|
||||
|
||||
char *nodeDeleteCommand = NodeDeleteCommand(workerNode->nodeId);
|
||||
|
||||
/* make sure we don't have any lingering session lifespan connections */
|
||||
CloseNodeConnectionsAfterTransaction(workerNode->workerName, nodePort);
|
||||
|
||||
SendCommandToWorkersWithMetadata(nodeDeleteCommand);
|
||||
if (EnableMetadataSync)
|
||||
{
|
||||
char *nodeDeleteCommand = NodeDeleteCommand(workerNode->nodeId);
|
||||
|
||||
SendCommandToWorkersWithMetadata(nodeDeleteCommand);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -2017,18 +2158,21 @@ AddNodeMetadata(char *nodeName, int32 nodePort,
|
|||
|
||||
workerNode = FindWorkerNodeAnyCluster(nodeName, nodePort);
|
||||
|
||||
/* send the delete command to all primary nodes with metadata */
|
||||
char *nodeDeleteCommand = NodeDeleteCommand(workerNode->nodeId);
|
||||
SendCommandToWorkersWithMetadata(nodeDeleteCommand);
|
||||
|
||||
/* finally prepare the insert command and send it to all primary nodes */
|
||||
uint32 primariesWithMetadata = CountPrimariesWithMetadata();
|
||||
if (primariesWithMetadata != 0)
|
||||
if (EnableMetadataSync)
|
||||
{
|
||||
List *workerNodeList = list_make1(workerNode);
|
||||
char *nodeInsertCommand = NodeListInsertCommand(workerNodeList);
|
||||
/* send the delete command to all primary nodes with metadata */
|
||||
char *nodeDeleteCommand = NodeDeleteCommand(workerNode->nodeId);
|
||||
SendCommandToWorkersWithMetadata(nodeDeleteCommand);
|
||||
|
||||
SendCommandToWorkersWithMetadata(nodeInsertCommand);
|
||||
/* finally prepare the insert command and send it to all primary nodes */
|
||||
uint32 primariesWithMetadata = CountPrimariesWithMetadata();
|
||||
if (primariesWithMetadata != 0)
|
||||
{
|
||||
List *workerNodeList = list_make1(workerNode);
|
||||
char *nodeInsertCommand = NodeListInsertCommand(workerNodeList);
|
||||
|
||||
SendCommandToWorkersWithMetadata(nodeInsertCommand);
|
||||
}
|
||||
}
|
||||
|
||||
return workerNode->nodeId;
|
||||
|
@ -2047,11 +2191,13 @@ SetWorkerColumn(WorkerNode *workerNode, int columnIndex, Datum value)
|
|||
{
|
||||
workerNode = SetWorkerColumnLocalOnly(workerNode, columnIndex, value);
|
||||
|
||||
char *metadataSyncCommand = GetMetadataSyncCommandToSetNodeColumn(workerNode,
|
||||
columnIndex,
|
||||
value);
|
||||
if (EnableMetadataSync)
|
||||
{
|
||||
char *metadataSyncCommand =
|
||||
GetMetadataSyncCommandToSetNodeColumn(workerNode, columnIndex, value);
|
||||
|
||||
SendCommandToWorkersWithMetadata(metadataSyncCommand);
|
||||
SendCommandToWorkersWithMetadata(metadataSyncCommand);
|
||||
}
|
||||
|
||||
return workerNode;
|
||||
}
|
||||
|
@ -2646,15 +2792,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 +2815,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,
|
||||
|
|
|
@ -419,20 +419,10 @@ ErrorIfCurrentUserCanNotDistributeObject(ObjectType type, ObjectAddress *addr,
|
|||
case OBJECT_TABLE:
|
||||
case OBJECT_EXTENSION:
|
||||
case OBJECT_COLLATION:
|
||||
{
|
||||
check_object_ownership(userId, type, *addr, node, *relation);
|
||||
break;
|
||||
}
|
||||
|
||||
case OBJECT_VIEW:
|
||||
case OBJECT_ROLE:
|
||||
{
|
||||
/* Support only extension owner role with community */
|
||||
if (addr->objectId != CitusExtensionOwner())
|
||||
{
|
||||
ereport(ERROR, (errmsg("Current user does not have required "
|
||||
"access privileges on role %d with type %d",
|
||||
addr->objectId, type)));
|
||||
}
|
||||
check_object_ownership(userId, type, *addr, node, *relation);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,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,
|
||||
|
@ -42,15 +45,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,
|
||||
|
@ -239,18 +242,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 */
|
||||
|
@ -357,7 +408,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;
|
||||
|
@ -366,95 +417,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
|
||||
|
@ -469,63 +431,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;
|
||||
}
|
||||
|
|
|
@ -622,6 +622,17 @@ GetPreLoadTableCreationCommands(Oid relationId,
|
|||
}
|
||||
}
|
||||
|
||||
List *tableACLList = pg_get_table_grants(relationId);
|
||||
if (tableACLList != NIL)
|
||||
{
|
||||
char *tableACLCommand = NULL;
|
||||
foreach_ptr(tableACLCommand, tableACLList)
|
||||
{
|
||||
tableDDLEventList = lappend(tableDDLEventList,
|
||||
makeTableDDLCommandString(tableACLCommand));
|
||||
}
|
||||
}
|
||||
|
||||
char *tableOwnerDef = TableOwnerResetCommand(relationId);
|
||||
if (tableOwnerDef != NULL)
|
||||
{
|
||||
|
@ -629,6 +640,9 @@ GetPreLoadTableCreationCommands(Oid relationId,
|
|||
tableOwnerDef));
|
||||
}
|
||||
|
||||
List *tableRowLevelSecurityCommands = GetTableRowLevelSecurityCommands(relationId);
|
||||
tableDDLEventList = list_concat(tableDDLEventList, tableRowLevelSecurityCommands);
|
||||
|
||||
List *policyCommands = CreatePolicyCommands(relationId);
|
||||
tableDDLEventList = list_concat(tableDDLEventList, policyCommands);
|
||||
|
||||
|
@ -777,6 +791,29 @@ GatherIndexAndConstraintDefinitionList(Form_pg_index indexForm, List **indexDDLE
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetTableRowLevelSecurityCommands takes in a relationId, and returns the list of
|
||||
* commands needed to reconstruct the row level security policy.
|
||||
*/
|
||||
List *
|
||||
GetTableRowLevelSecurityCommands(Oid relationId)
|
||||
{
|
||||
List *rowLevelSecurityCommandList = NIL;
|
||||
|
||||
List *rowLevelSecurityEnableCommands = pg_get_row_level_security_commands(relationId);
|
||||
|
||||
char *rowLevelSecurityCommand = NULL;
|
||||
foreach_ptr(rowLevelSecurityCommand, rowLevelSecurityEnableCommands)
|
||||
{
|
||||
rowLevelSecurityCommandList = lappend(
|
||||
rowLevelSecurityCommandList,
|
||||
makeTableDDLCommandString(rowLevelSecurityCommand));
|
||||
}
|
||||
|
||||
return rowLevelSecurityCommandList;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IndexImpliedByAConstraint is a helper function to be used while scanning
|
||||
* pg_index. It returns true if the index identified by the given indexForm is
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "distributed/metadata_cache.h"
|
||||
#include "distributed/metadata_sync.h"
|
||||
#include "distributed/multi_join_order.h"
|
||||
#include "distributed/multi_logical_replication.h"
|
||||
#include "distributed/multi_partitioning_utils.h"
|
||||
#include "distributed/reference_table_utils.h"
|
||||
#include "distributed/remote_commands.h"
|
||||
|
@ -53,6 +54,9 @@
|
|||
#include "utils/syscache.h"
|
||||
|
||||
/* local function forward declarations */
|
||||
static void VerifyTablesHaveReplicaIdentity(List *colocatedTableList);
|
||||
static bool RelationCanPublishAllModifications(Oid relationId);
|
||||
static bool CanUseLogicalReplication(Oid relationId, char shardReplicationMode);
|
||||
static void ErrorIfTableCannotBeReplicated(Oid relationId);
|
||||
static void RepairShardPlacement(int64 shardId, const char *sourceNodeName,
|
||||
int32 sourceNodePort, const char *targetNodeName,
|
||||
|
@ -64,6 +68,12 @@ static void ReplicateColocatedShardPlacement(int64 shardId, char *sourceNodeName
|
|||
static void CopyShardTables(List *shardIntervalList, char *sourceNodeName,
|
||||
int32 sourceNodePort, char *targetNodeName,
|
||||
int32 targetNodePort, bool useLogicalReplication);
|
||||
static void CopyShardTablesViaLogicalReplication(List *shardIntervalList,
|
||||
char *sourceNodeName,
|
||||
int32 sourceNodePort,
|
||||
char *targetNodeName,
|
||||
int32 targetNodePort);
|
||||
|
||||
static void CopyShardTablesViaBlockWrites(List *shardIntervalList, char *sourceNodeName,
|
||||
int32 sourceNodePort,
|
||||
char *targetNodeName, int32 targetNodePort);
|
||||
|
@ -146,11 +156,10 @@ citus_copy_shard_placement(PG_FUNCTION_ARGS)
|
|||
char *targetNodeName = text_to_cstring(targetNodeNameText);
|
||||
|
||||
char shardReplicationMode = LookupShardTransferMode(shardReplicationModeOid);
|
||||
if (shardReplicationMode == TRANSFER_MODE_FORCE_LOGICAL)
|
||||
if (doRepair && shardReplicationMode == TRANSFER_MODE_FORCE_LOGICAL)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("the force_logical transfer mode is currently "
|
||||
"unsupported")));
|
||||
errmsg("logical replication cannot be used for repairs")));
|
||||
}
|
||||
|
||||
ShardInterval *shardInterval = LoadShardInterval(shardId);
|
||||
|
@ -282,8 +291,7 @@ CheckSpaceConstraints(MultiConnection *connection, uint64 colocationSizeInBytes)
|
|||
* After that, there are two different paths. First one is blocking shard move in the
|
||||
* sense that during shard move all modifications are paused to the shard. The second
|
||||
* one relies on logical replication meaning that the writes blocked only for a very
|
||||
* short duration almost only when the metadata is actually being updated. This option
|
||||
* is currently only available in Citus Enterprise.
|
||||
* short duration almost only when the metadata is actually being updated.
|
||||
*
|
||||
* After successful move operation, shards in the source node gets deleted. If the move
|
||||
* fails at any point, this function throws an error, leaving the cluster without doing
|
||||
|
@ -354,23 +362,52 @@ citus_move_shard_placement(PG_FUNCTION_ARGS)
|
|||
}
|
||||
|
||||
char shardReplicationMode = LookupShardTransferMode(shardReplicationModeOid);
|
||||
if (shardReplicationMode == TRANSFER_MODE_FORCE_LOGICAL)
|
||||
if (shardReplicationMode == TRANSFER_MODE_AUTOMATIC)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("the force_logical transfer mode is currently "
|
||||
"unsupported")));
|
||||
VerifyTablesHaveReplicaIdentity(colocatedTableList);
|
||||
}
|
||||
|
||||
EnsureEnoughDiskSpaceForShardMove(colocatedShardList, sourceNodeName, sourceNodePort,
|
||||
targetNodeName, targetNodePort);
|
||||
|
||||
BlockWritesToShardList(colocatedShardList);
|
||||
/*
|
||||
* At this point of the shard moves, we don't need to block the writes to
|
||||
* shards when logical replication is used.
|
||||
*/
|
||||
bool useLogicalReplication = CanUseLogicalReplication(distributedTableId,
|
||||
shardReplicationMode);
|
||||
if (!useLogicalReplication)
|
||||
{
|
||||
BlockWritesToShardList(colocatedShardList);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* We prevent multiple shard moves in a transaction that use logical
|
||||
* replication. That's because the first call opens a transaction block
|
||||
* on the worker to drop the old shard placement and replication slot
|
||||
* creation waits for pending transactions to finish, which will not
|
||||
* happen ever. In other words, we prevent a self-deadlock if both
|
||||
* source shard placements are on the same node.
|
||||
*/
|
||||
if (PlacementMovedUsingLogicalReplicationInTX)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("moving multiple shard placements via logical "
|
||||
"replication in the same transaction is currently "
|
||||
"not supported"),
|
||||
errhint("If you wish to move multiple shard placements "
|
||||
"in a single transaction set the shard_transfer_mode "
|
||||
"to 'block_writes'.")));
|
||||
}
|
||||
|
||||
PlacementMovedUsingLogicalReplicationInTX = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* CopyColocatedShardPlacement function copies given shard with its co-located
|
||||
* shards.
|
||||
*/
|
||||
bool useLogicalReplication = false;
|
||||
CopyShardTables(colocatedShardList, sourceNodeName, sourceNodePort, targetNodeName,
|
||||
targetNodePort, useLogicalReplication);
|
||||
|
||||
|
@ -522,6 +559,74 @@ ErrorIfMoveUnsupportedTableType(Oid relationId)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* VerifyTablesHaveReplicaIdentity throws an error if any of the tables
|
||||
* do not have a replica identity, which is required for logical replication
|
||||
* to replicate UPDATE and DELETE commands.
|
||||
*/
|
||||
static void
|
||||
VerifyTablesHaveReplicaIdentity(List *colocatedTableList)
|
||||
{
|
||||
ListCell *colocatedTableCell = NULL;
|
||||
|
||||
foreach(colocatedTableCell, colocatedTableList)
|
||||
{
|
||||
Oid colocatedTableId = lfirst_oid(colocatedTableCell);
|
||||
|
||||
if (!RelationCanPublishAllModifications(colocatedTableId))
|
||||
{
|
||||
char *colocatedRelationName = get_rel_name(colocatedTableId);
|
||||
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot use logical replication to transfer shards of "
|
||||
"the relation %s since it doesn't have a REPLICA "
|
||||
"IDENTITY or PRIMARY KEY", colocatedRelationName),
|
||||
errdetail("UPDATE and DELETE commands on the shard will "
|
||||
"error out during logical replication unless "
|
||||
"there is a REPLICA IDENTITY or PRIMARY KEY."),
|
||||
errhint("If you wish to continue without a replica "
|
||||
"identity set the shard_transfer_mode to "
|
||||
"'force_logical' or 'block_writes'.")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RelationCanPublishAllModifications returns true if the relation is safe to publish
|
||||
* all modification while being replicated via logical replication.
|
||||
*/
|
||||
static bool
|
||||
RelationCanPublishAllModifications(Oid relationId)
|
||||
{
|
||||
Relation relation = RelationIdGetRelation(relationId);
|
||||
bool canPublish = false;
|
||||
|
||||
if (relation == NULL)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("could not open relation with OID %u", relationId)));
|
||||
}
|
||||
|
||||
/* if relation has replica identity we are always good */
|
||||
if (relation->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
|
||||
OidIsValid(RelationGetReplicaIndex(relation)))
|
||||
{
|
||||
canPublish = true;
|
||||
}
|
||||
|
||||
/* partitioned tables do not contain any data themselves, can always replicate */
|
||||
if (PartitionedTable(relationId))
|
||||
{
|
||||
canPublish = true;
|
||||
}
|
||||
|
||||
RelationClose(relation);
|
||||
|
||||
return canPublish;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* BlockWritesToShardList blocks writes to all shards in the given shard
|
||||
* list. The function assumes that all the shards in the list are colocated.
|
||||
|
@ -567,6 +672,49 @@ BlockWritesToShardList(List *shardList)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* CanUseLogicalReplication returns true if the given table can be logically replicated.
|
||||
*/
|
||||
static bool
|
||||
CanUseLogicalReplication(Oid relationId, char shardReplicationMode)
|
||||
{
|
||||
if (shardReplicationMode == TRANSFER_MODE_BLOCK_WRITES)
|
||||
{
|
||||
/* user explicitly chose not to use logical replication */
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Logical replication doesn't support replicating foreign tables and views.
|
||||
*/
|
||||
if (!RegularTable(relationId))
|
||||
{
|
||||
ereport(LOG, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("Cannot use logical replication for "
|
||||
"shard move since the relation %s is not "
|
||||
"a regular relation",
|
||||
get_rel_name(relationId))));
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Logical replication doesn't support inherited tables */
|
||||
if (IsParentTable(relationId))
|
||||
{
|
||||
ereport(LOG, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("Cannot use logical replication for "
|
||||
"shard move since the relation %s is an "
|
||||
"inherited relation",
|
||||
get_rel_name(relationId))));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ErrorIfTableCannotBeReplicated function errors out if the given table is not suitable
|
||||
* for its shard being replicated. There are 2 cases in which shard replication is not
|
||||
|
@ -790,7 +938,16 @@ ReplicateColocatedShardPlacement(int64 shardId, char *sourceNodeName,
|
|||
*/
|
||||
colocatedShardList = SortList(colocatedShardList, CompareShardIntervalsById);
|
||||
|
||||
BlockWritesToShardList(colocatedShardList);
|
||||
/*
|
||||
* At this point of the shard replication, we don't need to block the writes to
|
||||
* shards when logical replication is used.
|
||||
*/
|
||||
bool useLogicalReplication = CanUseLogicalReplication(distributedTableId,
|
||||
shardReplicationMode);
|
||||
if (!useLogicalReplication)
|
||||
{
|
||||
BlockWritesToShardList(colocatedShardList);
|
||||
}
|
||||
|
||||
ShardInterval *colocatedShard = NULL;
|
||||
foreach_ptr(colocatedShard, colocatedShardList)
|
||||
|
@ -805,6 +962,11 @@ ReplicateColocatedShardPlacement(int64 shardId, char *sourceNodeName,
|
|||
targetNodeName, targetNodePort);
|
||||
}
|
||||
|
||||
if (shardReplicationMode == TRANSFER_MODE_AUTOMATIC)
|
||||
{
|
||||
VerifyTablesHaveReplicaIdentity(colocatedTableList);
|
||||
}
|
||||
|
||||
if (!IsCitusTableType(distributedTableId, REFERENCE_TABLE))
|
||||
{
|
||||
/*
|
||||
|
@ -818,7 +980,6 @@ ReplicateColocatedShardPlacement(int64 shardId, char *sourceNodeName,
|
|||
EnsureReferenceTablesExistOnAllNodesExtended(shardReplicationMode);
|
||||
}
|
||||
|
||||
bool useLogicalReplication = false;
|
||||
CopyShardTables(colocatedShardList, sourceNodeName, sourceNodePort,
|
||||
targetNodeName, targetNodePort, useLogicalReplication);
|
||||
|
||||
|
@ -912,7 +1073,9 @@ CopyShardTables(List *shardIntervalList, char *sourceNodeName, int32 sourceNodeP
|
|||
|
||||
if (useLogicalReplication)
|
||||
{
|
||||
/* only supported in Citus enterprise */
|
||||
CopyShardTablesViaLogicalReplication(shardIntervalList, sourceNodeName,
|
||||
sourceNodePort, targetNodeName,
|
||||
targetNodePort);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -922,6 +1085,50 @@ CopyShardTables(List *shardIntervalList, char *sourceNodeName, int32 sourceNodeP
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* CopyShardTablesViaLogicalReplication copies a shard along with its co-located shards
|
||||
* from a source node to target node via logical replication.
|
||||
*/
|
||||
static void
|
||||
CopyShardTablesViaLogicalReplication(List *shardIntervalList, char *sourceNodeName,
|
||||
int32 sourceNodePort, char *targetNodeName,
|
||||
int32 targetNodePort)
|
||||
{
|
||||
MemoryContext localContext = AllocSetContextCreate(CurrentMemoryContext,
|
||||
"CopyShardTablesViaLogicalReplication",
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
MemoryContext oldContext = MemoryContextSwitchTo(localContext);
|
||||
|
||||
/*
|
||||
* Iterate through the colocated shards and create them on the
|
||||
* target node. We do not create the indexes yet.
|
||||
*/
|
||||
ShardInterval *shardInterval = NULL;
|
||||
foreach_ptr(shardInterval, shardIntervalList)
|
||||
{
|
||||
Oid relationId = shardInterval->relationId;
|
||||
uint64 shardId = shardInterval->shardId;
|
||||
List *tableRecreationCommandList = RecreateTableDDLCommandList(relationId);
|
||||
tableRecreationCommandList =
|
||||
WorkerApplyShardDDLCommandList(tableRecreationCommandList, shardId);
|
||||
|
||||
char *tableOwner = TableOwner(shardInterval->relationId);
|
||||
|
||||
SendCommandListToWorkerOutsideTransaction(targetNodeName, targetNodePort,
|
||||
tableOwner,
|
||||
tableRecreationCommandList);
|
||||
|
||||
MemoryContextReset(localContext);
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
/* data copy is done seperately when logical replication is used */
|
||||
LogicallyReplicateShards(shardIntervalList, sourceNodeName,
|
||||
sourceNodePort, targetNodeName, targetNodePort);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CopyShardTablesViaBlockWrites copies a shard along with its co-located shards
|
||||
* from a source node to target node via COPY command. While the command is in
|
||||
|
@ -989,12 +1196,12 @@ CopyShardTablesViaBlockWrites(List *shardIntervalList, char *sourceNodeName,
|
|||
{
|
||||
List *shardForeignConstraintCommandList = NIL;
|
||||
List *referenceTableForeignConstraintList = NIL;
|
||||
List *commandList = NIL;
|
||||
|
||||
CopyShardForeignConstraintCommandListGrouped(shardInterval,
|
||||
&shardForeignConstraintCommandList,
|
||||
&referenceTableForeignConstraintList);
|
||||
|
||||
List *commandList = NIL;
|
||||
commandList = list_concat(commandList, shardForeignConstraintCommandList);
|
||||
commandList = list_concat(commandList, referenceTableForeignConstraintList);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* left around. Shards that are left around are marked as state 4
|
||||
* (SHARD_STATE_TO_DELETE) in pg_dist_placement.
|
||||
*
|
||||
* Copyright (c), Citus Data, Inc.
|
||||
* Copyright (c) 2018, Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
* $Id$
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
@ -317,7 +319,7 @@ CheckRebalanceStateInvariants(const RebalanceState *state)
|
|||
|
||||
/* Check that utilization field is up to date. */
|
||||
Assert(fillState->utilization == CalculateUtilization(fillState->totalCost,
|
||||
fillState->capacity));
|
||||
fillState->capacity)); /* lgtm[cpp/equality-on-floats] */
|
||||
|
||||
/*
|
||||
* Check that fillState->totalCost is within 0.1% difference of
|
||||
|
@ -698,14 +700,6 @@ ExecutePlacementUpdates(List *placementUpdateList, Oid shardReplicationModeOid,
|
|||
|
||||
ListCell *placementUpdateCell = NULL;
|
||||
|
||||
char shardReplicationMode = LookupShardTransferMode(shardReplicationModeOid);
|
||||
if (shardReplicationMode == TRANSFER_MODE_FORCE_LOGICAL)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("the force_logical transfer mode is currently "
|
||||
"unsupported")));
|
||||
}
|
||||
|
||||
DropOrphanedShardsInSeparateTransaction();
|
||||
|
||||
foreach(placementUpdateCell, placementUpdateList)
|
||||
|
@ -2341,7 +2335,7 @@ FindAndMoveShardCost(float4 utilizationLowerBound,
|
|||
}
|
||||
if (newTargetUtilization == sourceFillState->utilization &&
|
||||
newSourceUtilization <= targetFillState->utilization
|
||||
)
|
||||
) /* lgtm[cpp/equality-on-floats] */
|
||||
{
|
||||
/*
|
||||
* this can trigger when capacity of the nodes is not the
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "postgres.h"
|
||||
#include "c.h"
|
||||
#include "fmgr.h"
|
||||
#include "libpq-fe.h"
|
||||
|
||||
#include "catalog/pg_class.h"
|
||||
#include "distributed/colocation_utils.h"
|
||||
|
@ -20,14 +21,17 @@
|
|||
#include "distributed/metadata_cache.h"
|
||||
#include "distributed/metadata_sync.h"
|
||||
#include "distributed/multi_join_order.h"
|
||||
#include "distributed/multi_partitioning_utils.h"
|
||||
#include "distributed/multi_router_planner.h"
|
||||
#include "distributed/pg_dist_partition.h"
|
||||
#include "distributed/pg_dist_shard.h"
|
||||
#include "distributed/remote_commands.h"
|
||||
#include "distributed/reference_table_utils.h"
|
||||
#include "distributed/resource_lock.h"
|
||||
#include "distributed/worker_manager.h"
|
||||
#include "distributed/worker_protocol.h"
|
||||
#include "distributed/worker_transaction.h"
|
||||
#include "distributed/version_compat.h"
|
||||
#include "nodes/pg_list.h"
|
||||
#include "storage/lock.h"
|
||||
#include "utils/builtins.h"
|
||||
|
@ -42,6 +46,25 @@ PG_FUNCTION_INFO_V1(isolate_tenant_to_new_shard);
|
|||
PG_FUNCTION_INFO_V1(worker_hash);
|
||||
|
||||
|
||||
/* local function forward declarations */
|
||||
static uint64 SplitShardByValue(ShardInterval *sourceShard, Datum distributionValueDatum);
|
||||
static void ErrorIfCannotSplitShard(ShardInterval *sourceShard);
|
||||
static void CreateSplitOffShards(ShardInterval *sourceShard, int hashedValue,
|
||||
List **splitOffShardList, int *isolatedShardId);
|
||||
static List * ShardTemplateList(ShardInterval *sourceShard, int hashedValue,
|
||||
int *isolatedShardIndex);
|
||||
static ShardInterval * CreateSplitOffShardFromTemplate(ShardInterval *shardTemplate,
|
||||
Oid relationId);
|
||||
static List * SplitOffCommandList(ShardInterval *sourceShard,
|
||||
ShardInterval *splitOffShard);
|
||||
static void ExecuteCommandListOnPlacements(List *commandList, List *placementList);
|
||||
static void InsertSplitOffShardMetadata(List *splitOffShardList,
|
||||
List *sourcePlacementList);
|
||||
static void CreateForeignConstraints(List *splitOffShardList, List *sourcePlacementList);
|
||||
static void ExecuteCommandListOnWorker(char *nodeName, int nodePort, List *commandList);
|
||||
static void DropShardList(List *shardIntervalList);
|
||||
|
||||
|
||||
/*
|
||||
* isolate_tenant_to_new_shard isolates a tenant to its own shard by spliting
|
||||
* the current matching shard.
|
||||
|
@ -49,9 +72,100 @@ PG_FUNCTION_INFO_V1(worker_hash);
|
|||
Datum
|
||||
isolate_tenant_to_new_shard(PG_FUNCTION_ARGS)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("isolate_tenant_to_new_shard() is only supported on "
|
||||
"Citus Enterprise")));
|
||||
CheckCitusVersion(ERROR);
|
||||
EnsureCoordinator();
|
||||
|
||||
Oid relationId = PG_GETARG_OID(0);
|
||||
Datum inputDatum = PG_GETARG_DATUM(1);
|
||||
text *cascadeOptionText = PG_GETARG_TEXT_P(2);
|
||||
ListCell *colocatedTableCell = NULL;
|
||||
|
||||
EnsureTableOwner(relationId);
|
||||
|
||||
CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId);
|
||||
|
||||
char partitionMethod = cacheEntry->partitionMethod;
|
||||
if (partitionMethod != DISTRIBUTE_BY_HASH)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot isolate tenant because tenant isolation "
|
||||
"is only support for hash distributed tables")));
|
||||
}
|
||||
|
||||
if (PartitionedTable(relationId))
|
||||
{
|
||||
char *sourceRelationName = get_rel_name(relationId);
|
||||
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot isolate shard placement of '%s', because it "
|
||||
"is a partitioned table", sourceRelationName),
|
||||
errdetail("Citus does not support isolating placements of "
|
||||
"partitioned tables.")));
|
||||
}
|
||||
|
||||
List *colocatedTableList = ColocatedTableList(relationId);
|
||||
int colocatedTableCount = list_length(colocatedTableList);
|
||||
|
||||
foreach(colocatedTableCell, colocatedTableList)
|
||||
{
|
||||
Oid colocatedTableId = lfirst_oid(colocatedTableCell);
|
||||
|
||||
/*
|
||||
* At the moment, Citus does not support copying a shard if that shard's
|
||||
* relation is in a colocation group with a partitioned table or partition.
|
||||
*/
|
||||
if (colocatedTableId != relationId &&
|
||||
PartitionedTable(colocatedTableId))
|
||||
{
|
||||
char *sourceRelationName = get_rel_name(relationId);
|
||||
char *colocatedRelationName = get_rel_name(colocatedTableId);
|
||||
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot isolate shard placement of '%s', because it "
|
||||
"is a partitioned table", colocatedRelationName),
|
||||
errdetail("In colocation group of '%s', a partitioned "
|
||||
"relation exists: '%s'. Citus does not support "
|
||||
"isolating placements of partitioned tables.",
|
||||
sourceRelationName, colocatedRelationName)));
|
||||
}
|
||||
}
|
||||
|
||||
Oid inputDataType = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
||||
char *tenantIdString = DatumToString(inputDatum, inputDataType);
|
||||
|
||||
char *cascadeOptionString = text_to_cstring(cascadeOptionText);
|
||||
if (pg_strncasecmp(cascadeOptionString, "CASCADE", NAMEDATALEN) != 0 &&
|
||||
colocatedTableCount > 1)
|
||||
{
|
||||
char *relationName = get_rel_name(relationId);
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot isolate tenant because \"%s\" has colocated "
|
||||
"tables", relationName),
|
||||
errhint("Use CASCADE option to isolate tenants for the "
|
||||
"colocated tables too. Example usage: "
|
||||
"isolate_tenant_to_new_shard('%s', '%s', 'CASCADE')",
|
||||
relationName, tenantIdString)));
|
||||
}
|
||||
|
||||
EnsureReferenceTablesExistOnAllNodes();
|
||||
|
||||
Var *distributionColumn = DistPartitionKey(relationId);
|
||||
|
||||
/* earlier we checked that the table was hash partitioned, so there should be a distribution column */
|
||||
Assert(distributionColumn != NULL);
|
||||
|
||||
Oid distributionColumnType = distributionColumn->vartype;
|
||||
|
||||
Datum tenantIdDatum = StringToDatum(tenantIdString, distributionColumnType);
|
||||
ShardInterval *sourceShard = FindShardInterval(tenantIdDatum, cacheEntry);
|
||||
if (sourceShard == NULL)
|
||||
{
|
||||
ereport(ERROR, (errmsg("tenant does not have a shard")));
|
||||
}
|
||||
|
||||
uint64 isolatedShardId = SplitShardByValue(sourceShard, tenantIdDatum);
|
||||
|
||||
PG_RETURN_INT64(isolatedShardId);
|
||||
}
|
||||
|
||||
|
||||
|
@ -86,3 +200,623 @@ worker_hash(PG_FUNCTION_ARGS)
|
|||
|
||||
PG_RETURN_INT32(hashedValueDatum);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SplitShardByValue gets a shard and a value which is in the range of
|
||||
* distribution column of this shard. Then, it splits this shard and all its
|
||||
* colocated shards into three; the lower range, the given value itself, and
|
||||
* the upper range. Finally, it returns the id of the shard which is created
|
||||
* for the given value.
|
||||
*/
|
||||
static uint64
|
||||
SplitShardByValue(ShardInterval *sourceShard, Datum distributionValueDatum)
|
||||
{
|
||||
Oid relationId = sourceShard->relationId;
|
||||
int isolatedShardId = 0;
|
||||
List *splitOffShardList = NIL;
|
||||
|
||||
if (XactModificationLevel > XACT_MODIFICATION_NONE)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
||||
errmsg("cannot isolate a tenant after other modifications "
|
||||
"in the same transaction")));
|
||||
}
|
||||
|
||||
/* sort the tables to avoid deadlocks */
|
||||
List *colocatedTableList = ColocatedTableList(relationId);
|
||||
colocatedTableList = SortList(colocatedTableList, CompareOids);
|
||||
|
||||
Oid colocatedTableId = InvalidOid;
|
||||
foreach_oid(colocatedTableId, colocatedTableList)
|
||||
{
|
||||
/*
|
||||
* Block concurrent DDL / TRUNCATE commands on the relation. Similarly,
|
||||
* block concurrent citus_move_shard_placement()/isolate_tenant_to_new_shard()
|
||||
* on any shard of the same relation. This is OK for now since
|
||||
* we're executing shard moves/splits sequentially anyway.
|
||||
*/
|
||||
LockRelationOid(colocatedTableId, ShareUpdateExclusiveLock);
|
||||
}
|
||||
|
||||
/* get colocated shard list */
|
||||
List *colocatedShardList = ColocatedShardIntervalList(sourceShard);
|
||||
|
||||
/* get locks */
|
||||
BlockWritesToShardList(colocatedShardList);
|
||||
|
||||
ErrorIfCannotSplitShard(sourceShard);
|
||||
|
||||
/* get hash function name */
|
||||
CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId);
|
||||
FmgrInfo *hashFunction = cacheEntry->hashFunction;
|
||||
|
||||
/* get hashed value of the distribution value */
|
||||
Datum hashedValueDatum = FunctionCall1(hashFunction, distributionValueDatum);
|
||||
int hashedValue = DatumGetInt32(hashedValueDatum);
|
||||
|
||||
/* create a list of nodes with source shard placements */
|
||||
List *sourcePlacementList = ActiveShardPlacementList(sourceShard->shardId);
|
||||
|
||||
/* create new shards in a separate transaction and commit them */
|
||||
CreateSplitOffShards(sourceShard, hashedValue, &splitOffShardList, &isolatedShardId);
|
||||
|
||||
/*
|
||||
* Drop old shards and delete related metadata. Have to do that before
|
||||
* creating the new shard metadata, because there's cross-checks
|
||||
* preventing inconsistent metadata (like overlapping shards).
|
||||
*/
|
||||
DropShardList(colocatedShardList);
|
||||
|
||||
/* insert new metadata */
|
||||
InsertSplitOffShardMetadata(splitOffShardList, sourcePlacementList);
|
||||
|
||||
/*
|
||||
* Create foreign keys if exists after the metadata changes happening in
|
||||
* DropShardList() and InsertSplitOffShardMetadata() because the foreign
|
||||
* key creation depends on the new metadata.
|
||||
*/
|
||||
CreateForeignConstraints(splitOffShardList, sourcePlacementList);
|
||||
|
||||
CitusInvalidateRelcacheByRelid(DistShardRelationId());
|
||||
|
||||
return isolatedShardId;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateForeignConstraints creates the foreign constraints on the newly
|
||||
* created shards via the tenant isolation.
|
||||
*
|
||||
* The function treats foreign keys to reference tables and foreign keys to
|
||||
* co-located distributed tables differently. The former one needs to be
|
||||
* executed over a single connection to prevent self-deadlocks. The latter
|
||||
* one can be executed in parallel if there are multiple replicas.
|
||||
*/
|
||||
static void
|
||||
CreateForeignConstraints(List *splitOffShardList, List *sourcePlacementList)
|
||||
{
|
||||
ListCell *splitOffShardCell = NULL;
|
||||
|
||||
List *colocatedShardForeignConstraintCommandList = NIL;
|
||||
List *referenceTableForeignConstraintList = NIL;
|
||||
|
||||
foreach(splitOffShardCell, splitOffShardList)
|
||||
{
|
||||
ShardInterval *splitOffShard = (ShardInterval *) lfirst(splitOffShardCell);
|
||||
|
||||
List *currentColocatedForeignKeyList = NIL;
|
||||
List *currentReferenceForeignKeyList = NIL;
|
||||
|
||||
CopyShardForeignConstraintCommandListGrouped(splitOffShard,
|
||||
¤tColocatedForeignKeyList,
|
||||
¤tReferenceForeignKeyList);
|
||||
|
||||
colocatedShardForeignConstraintCommandList =
|
||||
list_concat(colocatedShardForeignConstraintCommandList,
|
||||
currentColocatedForeignKeyList);
|
||||
referenceTableForeignConstraintList =
|
||||
list_concat(referenceTableForeignConstraintList,
|
||||
currentReferenceForeignKeyList);
|
||||
}
|
||||
|
||||
/*
|
||||
* We can use parallel connections to while creating co-located foreign keys
|
||||
* if the source placement .
|
||||
* However, foreign keys to reference tables need to be created using a single
|
||||
* connection per worker to prevent self-deadlocks.
|
||||
*/
|
||||
if (colocatedShardForeignConstraintCommandList != NIL)
|
||||
{
|
||||
ExecuteCommandListOnPlacements(colocatedShardForeignConstraintCommandList,
|
||||
sourcePlacementList);
|
||||
}
|
||||
|
||||
if (referenceTableForeignConstraintList != NIL)
|
||||
{
|
||||
ListCell *shardPlacementCell = NULL;
|
||||
foreach(shardPlacementCell, sourcePlacementList)
|
||||
{
|
||||
ShardPlacement *shardPlacement =
|
||||
(ShardPlacement *) lfirst(shardPlacementCell);
|
||||
|
||||
char *nodeName = shardPlacement->nodeName;
|
||||
int32 nodePort = shardPlacement->nodePort;
|
||||
|
||||
/*
|
||||
* We're using the connections that we've used for dropping the
|
||||
* source placements within the same coordinated transaction.
|
||||
*/
|
||||
ExecuteCommandListOnWorker(nodeName, nodePort,
|
||||
referenceTableForeignConstraintList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecuteCommandListOnWorker executes the command on the given node within
|
||||
* the coordinated 2PC.
|
||||
*/
|
||||
static void
|
||||
ExecuteCommandListOnWorker(char *nodeName, int nodePort, List *commandList)
|
||||
{
|
||||
ListCell *commandCell = NULL;
|
||||
|
||||
foreach(commandCell, commandList)
|
||||
{
|
||||
char *command = (char *) lfirst(commandCell);
|
||||
|
||||
SendCommandToWorker(nodeName, nodePort, command);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ErrorIfCannotSplitShard checks relation kind and invalid shards. It errors
|
||||
* out if we are not able to split the given shard.
|
||||
*/
|
||||
static void
|
||||
ErrorIfCannotSplitShard(ShardInterval *sourceShard)
|
||||
{
|
||||
Oid relationId = sourceShard->relationId;
|
||||
ListCell *colocatedTableCell = NULL;
|
||||
ListCell *colocatedShardCell = NULL;
|
||||
|
||||
/* checks for table ownership and foreign tables */
|
||||
List *colocatedTableList = ColocatedTableList(relationId);
|
||||
foreach(colocatedTableCell, colocatedTableList)
|
||||
{
|
||||
Oid colocatedTableId = lfirst_oid(colocatedTableCell);
|
||||
|
||||
/* check that user has owner rights in all co-located tables */
|
||||
EnsureTableOwner(colocatedTableId);
|
||||
|
||||
char relationKind = get_rel_relkind(colocatedTableId);
|
||||
if (relationKind == RELKIND_FOREIGN_TABLE)
|
||||
{
|
||||
char *relationName = get_rel_name(colocatedTableId);
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot isolate tenant because \"%s\" is a "
|
||||
"foreign table", relationName),
|
||||
errdetail("Isolating shards backed by foreign tables "
|
||||
"is not supported.")));
|
||||
}
|
||||
}
|
||||
|
||||
/* check shards with inactive placements */
|
||||
List *colocatedShardList = ColocatedShardIntervalList(sourceShard);
|
||||
foreach(colocatedShardCell, colocatedShardList)
|
||||
{
|
||||
ShardInterval *shardInterval = (ShardInterval *) lfirst(colocatedShardCell);
|
||||
uint64 shardId = shardInterval->shardId;
|
||||
ListCell *shardPlacementCell = NULL;
|
||||
|
||||
List *shardPlacementList = ShardPlacementListWithoutOrphanedPlacements(shardId);
|
||||
foreach(shardPlacementCell, shardPlacementList)
|
||||
{
|
||||
ShardPlacement *placement = (ShardPlacement *) lfirst(shardPlacementCell);
|
||||
if (placement->shardState != SHARD_STATE_ACTIVE)
|
||||
{
|
||||
char *relationName = get_rel_name(shardInterval->relationId);
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot isolate tenant because relation "
|
||||
"\"%s\" has an inactive shard placement "
|
||||
"for the shard %lu", relationName, shardId),
|
||||
errhint("Use master_copy_shard_placement UDF to "
|
||||
"repair the inactive shard placement.")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateSplitOffShards gets a shard and a hashed value to pick the split point.
|
||||
* First, it creates templates to create new shards. Then, for every colocated
|
||||
* shard, it creates new split shards data and physically creates them on the
|
||||
* worker nodes. This function returns newly created split off shards and the
|
||||
* matching shard id for the source shard and hashed value via passed parameters.
|
||||
*/
|
||||
static void
|
||||
CreateSplitOffShards(ShardInterval *sourceShard, int hashedValue,
|
||||
List **splitOffShardList, int *isolatedShardId)
|
||||
{
|
||||
List *nodeCommandList = NIL;
|
||||
ListCell *sourceColocatedShardCell = NULL;
|
||||
int isolatedShardIndex = 0;
|
||||
|
||||
List *sourceColocatedShardList = ColocatedShardIntervalList(sourceShard);
|
||||
List *shardTemplateList = ShardTemplateList(sourceShard, hashedValue,
|
||||
&isolatedShardIndex);
|
||||
|
||||
foreach(sourceColocatedShardCell, sourceColocatedShardList)
|
||||
{
|
||||
ShardInterval *sourceColocatedShard =
|
||||
(ShardInterval *) lfirst(sourceColocatedShardCell);
|
||||
Oid relationId = sourceColocatedShard->relationId;
|
||||
ListCell *templateShardCell = NULL;
|
||||
int currentShardIndex = 0;
|
||||
|
||||
foreach(templateShardCell, shardTemplateList)
|
||||
{
|
||||
ShardInterval *templateShard = (ShardInterval *) lfirst(templateShardCell);
|
||||
|
||||
ShardInterval *splitOffShard = CreateSplitOffShardFromTemplate(templateShard,
|
||||
relationId);
|
||||
List *splitOffCommandList = SplitOffCommandList(sourceColocatedShard,
|
||||
splitOffShard);
|
||||
nodeCommandList = list_concat(nodeCommandList, splitOffCommandList);
|
||||
|
||||
/* check if this is the isolated shard for the given table */
|
||||
if (splitOffShard->relationId == sourceShard->relationId &&
|
||||
currentShardIndex == isolatedShardIndex)
|
||||
{
|
||||
(*isolatedShardId) = splitOffShard->shardId;
|
||||
}
|
||||
|
||||
/* add newly created split off shards to list */
|
||||
(*splitOffShardList) = lappend(*splitOffShardList, splitOffShard);
|
||||
|
||||
currentShardIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
List *sourcePlacementList = ActiveShardPlacementList(sourceShard->shardId);
|
||||
ExecuteCommandListOnPlacements(nodeCommandList, sourcePlacementList);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ShardTemplateList creates shard templates with new min and max values from
|
||||
* the given shard and the split point which is the given hashed value.
|
||||
* It returns the list of shard templates, and passes the isolated shard index
|
||||
* via isolatedShardIndex parameter.
|
||||
*/
|
||||
static List *
|
||||
ShardTemplateList(ShardInterval *sourceShard, int hashedValue, int *isolatedShardIndex)
|
||||
{
|
||||
List *shardTemplateList = NIL;
|
||||
|
||||
/* get min and max values of the source shard */
|
||||
int32 shardMinValue = DatumGetInt32(sourceShard->minValue);
|
||||
int32 shardMaxValue = DatumGetInt32(sourceShard->maxValue);
|
||||
|
||||
(*isolatedShardIndex) = 0;
|
||||
|
||||
/* add a shard template for lower range if exists */
|
||||
if (shardMinValue < hashedValue)
|
||||
{
|
||||
ShardInterval *lowerRangeShard = CopyShardInterval(sourceShard);
|
||||
|
||||
lowerRangeShard->minValue = Int32GetDatum(shardMinValue);
|
||||
lowerRangeShard->maxValue = Int32GetDatum(hashedValue - 1);
|
||||
|
||||
shardTemplateList = lappend(shardTemplateList, lowerRangeShard);
|
||||
(*isolatedShardIndex) = 1;
|
||||
}
|
||||
|
||||
/* add shard template for the isolated value */
|
||||
ShardInterval *isolatedShard = CopyShardInterval(sourceShard);
|
||||
|
||||
isolatedShard->minValue = Int32GetDatum(hashedValue);
|
||||
isolatedShard->maxValue = Int32GetDatum(hashedValue);
|
||||
|
||||
shardTemplateList = lappend(shardTemplateList, isolatedShard);
|
||||
|
||||
/* add a shard template for upper range if exists */
|
||||
if (shardMaxValue > hashedValue)
|
||||
{
|
||||
ShardInterval *upperRangeShard = CopyShardInterval(sourceShard);
|
||||
|
||||
upperRangeShard->minValue = Int32GetDatum(hashedValue + 1);
|
||||
upperRangeShard->maxValue = Int32GetDatum(shardMaxValue);
|
||||
|
||||
shardTemplateList = lappend(shardTemplateList, upperRangeShard);
|
||||
}
|
||||
|
||||
if (list_length(shardTemplateList) == 1)
|
||||
{
|
||||
char *tableName = get_rel_name(sourceShard->relationId);
|
||||
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("table \"%s\" has already been isolated for the "
|
||||
"given value", tableName)));
|
||||
}
|
||||
|
||||
return shardTemplateList;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateSplitOffShardFromTemplate creates a new split off shard from the given
|
||||
* shard template by creating a new shard id and setting the relation id.
|
||||
*/
|
||||
static ShardInterval *
|
||||
CreateSplitOffShardFromTemplate(ShardInterval *shardTemplate, Oid relationId)
|
||||
{
|
||||
ShardInterval *splitOffShard = CopyShardInterval(shardTemplate);
|
||||
|
||||
/* set new shard id and the relation id */
|
||||
splitOffShard->shardId = GetNextShardId();
|
||||
splitOffShard->relationId = relationId;
|
||||
|
||||
return splitOffShard;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SplitOffCommandList creates a command list to run on worker nodes to create
|
||||
* new split off shard from the source shard.
|
||||
*/
|
||||
static List *
|
||||
SplitOffCommandList(ShardInterval *sourceShard, ShardInterval *splitOffShard)
|
||||
{
|
||||
List *splitOffCommandList = NIL;
|
||||
bool includeSequenceDefaults = false;
|
||||
|
||||
Oid relationId = sourceShard->relationId;
|
||||
Var *partitionKey = DistPartitionKey(relationId);
|
||||
Assert(partitionKey != NULL);
|
||||
const char *partitionColumnName = get_attname(relationId,
|
||||
partitionKey->varattno, false);
|
||||
const char *quotedPartitionColumnName = quote_identifier(partitionColumnName);
|
||||
|
||||
char *splitOffShardName = ConstructQualifiedShardName(splitOffShard);
|
||||
char *sourceShardName = ConstructQualifiedShardName(sourceShard);
|
||||
|
||||
int32 shardMinValue = DatumGetInt32(splitOffShard->minValue);
|
||||
int32 shardMaxValue = DatumGetInt32(splitOffShard->maxValue);
|
||||
|
||||
List *tableCreationCommandList =
|
||||
GetPreLoadTableCreationCommands(relationId, includeSequenceDefaults, NULL);
|
||||
tableCreationCommandList = WorkerApplyShardDDLCommandList(tableCreationCommandList,
|
||||
splitOffShard->shardId);
|
||||
|
||||
splitOffCommandList = list_concat(splitOffCommandList, tableCreationCommandList);
|
||||
|
||||
StringInfo splitOffShardCommand = makeStringInfo();
|
||||
appendStringInfo(splitOffShardCommand,
|
||||
"INSERT INTO %s SELECT * FROM %s WHERE "
|
||||
"worker_hash(%s) >= %d AND worker_hash(%s) <= %d",
|
||||
splitOffShardName, sourceShardName, quotedPartitionColumnName,
|
||||
shardMinValue, quotedPartitionColumnName, shardMaxValue);
|
||||
|
||||
splitOffCommandList = lappend(splitOffCommandList, splitOffShardCommand->data);
|
||||
|
||||
List *indexCommandList = GetPostLoadTableCreationCommands(relationId, true, true);
|
||||
indexCommandList = WorkerApplyShardDDLCommandList(indexCommandList,
|
||||
splitOffShard->shardId);
|
||||
|
||||
splitOffCommandList = list_concat(splitOffCommandList, indexCommandList);
|
||||
|
||||
return splitOffCommandList;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecuteCommandListOnPlacements runs the given command list on the nodes of
|
||||
* the given shard placement list. First, it creates connections. Then it sends
|
||||
* commands one by one. For every command, first it send the command to all
|
||||
* connections and then checks the results. This helps to run long running
|
||||
* commands in parallel. Finally, it sends commit messages to all connections
|
||||
* and close them.
|
||||
*/
|
||||
static void
|
||||
ExecuteCommandListOnPlacements(List *commandList, List *placementList)
|
||||
{
|
||||
List *workerConnectionList = NIL;
|
||||
ListCell *workerConnectionCell = NULL;
|
||||
ListCell *shardPlacementCell = NULL;
|
||||
ListCell *commandCell = NULL;
|
||||
|
||||
/* create connections and start transactions */
|
||||
foreach(shardPlacementCell, placementList)
|
||||
{
|
||||
ShardPlacement *shardPlacement = (ShardPlacement *) lfirst(shardPlacementCell);
|
||||
char *nodeName = shardPlacement->nodeName;
|
||||
int32 nodePort = shardPlacement->nodePort;
|
||||
|
||||
int connectionFlags = FORCE_NEW_CONNECTION;
|
||||
char *currentUser = CurrentUserName();
|
||||
|
||||
/* create a new connection */
|
||||
MultiConnection *workerConnection = GetNodeUserDatabaseConnection(connectionFlags,
|
||||
nodeName,
|
||||
nodePort,
|
||||
currentUser,
|
||||
NULL);
|
||||
|
||||
/* mark connection as critical ans start transaction */
|
||||
MarkRemoteTransactionCritical(workerConnection);
|
||||
RemoteTransactionBegin(workerConnection);
|
||||
|
||||
/* add connection to the list */
|
||||
workerConnectionList = lappend(workerConnectionList, workerConnection);
|
||||
}
|
||||
|
||||
/* send and check results for every command one by one */
|
||||
foreach(commandCell, commandList)
|
||||
{
|
||||
char *command = lfirst(commandCell);
|
||||
|
||||
/* first only send the command */
|
||||
foreach(workerConnectionCell, workerConnectionList)
|
||||
{
|
||||
MultiConnection *workerConnection =
|
||||
(MultiConnection *) lfirst(workerConnectionCell);
|
||||
|
||||
int querySent = SendRemoteCommand(workerConnection, command);
|
||||
if (querySent == 0)
|
||||
{
|
||||
ReportConnectionError(workerConnection, ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/* then check the result separately to run long running commands in parallel */
|
||||
foreach(workerConnectionCell, workerConnectionList)
|
||||
{
|
||||
MultiConnection *workerConnection =
|
||||
(MultiConnection *) lfirst(workerConnectionCell);
|
||||
bool raiseInterrupts = true;
|
||||
|
||||
PGresult *result = GetRemoteCommandResult(workerConnection, raiseInterrupts);
|
||||
if (!IsResponseOK(result))
|
||||
{
|
||||
ReportResultError(workerConnection, result, ERROR);
|
||||
}
|
||||
|
||||
PQclear(result);
|
||||
ForgetResults(workerConnection);
|
||||
}
|
||||
}
|
||||
|
||||
/* finally commit each transaction and close connections */
|
||||
foreach(workerConnectionCell, workerConnectionList)
|
||||
{
|
||||
MultiConnection *workerConnection =
|
||||
(MultiConnection *) lfirst(workerConnectionCell);
|
||||
|
||||
RemoteTransactionCommit(workerConnection);
|
||||
CloseConnection(workerConnection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* InsertSplitOffShardMetadata inserts new shard and shard placement data into
|
||||
* catolog tables both the coordinator and mx nodes.
|
||||
*/
|
||||
static void
|
||||
InsertSplitOffShardMetadata(List *splitOffShardList, List *sourcePlacementList)
|
||||
{
|
||||
List *syncedShardList = NIL;
|
||||
ListCell *shardCell = NULL;
|
||||
ListCell *commandCell = NULL;
|
||||
|
||||
/* add new metadata */
|
||||
foreach(shardCell, splitOffShardList)
|
||||
{
|
||||
ShardInterval *splitOffShard = (ShardInterval *) lfirst(shardCell);
|
||||
Oid relationId = splitOffShard->relationId;
|
||||
uint64 shardId = splitOffShard->shardId;
|
||||
char storageType = splitOffShard->storageType;
|
||||
ListCell *shardPlacementCell = NULL;
|
||||
|
||||
int32 shardMinValue = DatumGetInt32(splitOffShard->minValue);
|
||||
int32 shardMaxValue = DatumGetInt32(splitOffShard->maxValue);
|
||||
text *shardMinValueText = IntegerToText(shardMinValue);
|
||||
text *shardMaxValueText = IntegerToText(shardMaxValue);
|
||||
|
||||
InsertShardRow(relationId, shardId, storageType, shardMinValueText,
|
||||
shardMaxValueText);
|
||||
|
||||
/* split off shard placement metadata */
|
||||
foreach(shardPlacementCell, sourcePlacementList)
|
||||
{
|
||||
ShardPlacement *placement = (ShardPlacement *) lfirst(shardPlacementCell);
|
||||
uint64 shardSize = 0;
|
||||
|
||||
InsertShardPlacementRow(shardId, INVALID_PLACEMENT_ID, SHARD_STATE_ACTIVE,
|
||||
shardSize, placement->groupId);
|
||||
}
|
||||
|
||||
if (ShouldSyncTableMetadata(relationId))
|
||||
{
|
||||
syncedShardList = lappend(syncedShardList, splitOffShard);
|
||||
}
|
||||
}
|
||||
|
||||
/* send commands to synced nodes one by one */
|
||||
List *splitOffShardMetadataCommandList = ShardListInsertCommand(syncedShardList);
|
||||
foreach(commandCell, splitOffShardMetadataCommandList)
|
||||
{
|
||||
char *command = (char *) lfirst(commandCell);
|
||||
SendCommandToWorkersWithMetadata(command);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DropShardList drops shards and their metadata from both the coordinator and
|
||||
* mx nodes.
|
||||
*/
|
||||
static void
|
||||
DropShardList(List *shardIntervalList)
|
||||
{
|
||||
ListCell *shardIntervalCell = NULL;
|
||||
|
||||
foreach(shardIntervalCell, shardIntervalList)
|
||||
{
|
||||
ShardInterval *shardInterval = (ShardInterval *) lfirst(shardIntervalCell);
|
||||
ListCell *shardPlacementCell = NULL;
|
||||
Oid relationId = shardInterval->relationId;
|
||||
uint64 oldShardId = shardInterval->shardId;
|
||||
|
||||
/* delete metadata from synced nodes */
|
||||
if (ShouldSyncTableMetadata(relationId))
|
||||
{
|
||||
ListCell *commandCell = NULL;
|
||||
|
||||
/* send the commands one by one */
|
||||
List *shardMetadataDeleteCommandList = ShardDeleteCommandList(shardInterval);
|
||||
foreach(commandCell, shardMetadataDeleteCommandList)
|
||||
{
|
||||
char *command = (char *) lfirst(commandCell);
|
||||
SendCommandToWorkersWithMetadata(command);
|
||||
}
|
||||
}
|
||||
|
||||
/* delete shard placements and drop shards */
|
||||
List *shardPlacementList = ActiveShardPlacementList(oldShardId);
|
||||
foreach(shardPlacementCell, shardPlacementList)
|
||||
{
|
||||
ShardPlacement *placement = (ShardPlacement *) lfirst(shardPlacementCell);
|
||||
char *workerName = placement->nodeName;
|
||||
uint32 workerPort = placement->nodePort;
|
||||
StringInfo dropQuery = makeStringInfo();
|
||||
|
||||
DeleteShardPlacementRow(placement->placementId);
|
||||
|
||||
/* get shard name */
|
||||
char *qualifiedShardName = ConstructQualifiedShardName(shardInterval);
|
||||
|
||||
char storageType = shardInterval->storageType;
|
||||
if (storageType == SHARD_STORAGE_TABLE)
|
||||
{
|
||||
appendStringInfo(dropQuery, DROP_REGULAR_TABLE_COMMAND,
|
||||
qualifiedShardName);
|
||||
}
|
||||
else if (storageType == SHARD_STORAGE_FOREIGN)
|
||||
{
|
||||
appendStringInfo(dropQuery, DROP_FOREIGN_TABLE_COMMAND,
|
||||
qualifiedShardName);
|
||||
}
|
||||
|
||||
/* drop old shard */
|
||||
SendCommandToWorker(workerName, workerPort, dropQuery->data);
|
||||
}
|
||||
|
||||
/* delete shard row */
|
||||
DeleteShardRow(oldShardId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,12 +94,12 @@ ActivePrimaryNonCoordinatorNodeCount(void)
|
|||
|
||||
|
||||
/*
|
||||
* ActivePrimaryNodeCount returns the number of groups with a primary in the cluster.
|
||||
* ActiveReadableNodeCount returns the number of nodes in the cluster.
|
||||
*/
|
||||
uint32
|
||||
ActivePrimaryNodeCount(void)
|
||||
ActiveReadableNodeCount(void)
|
||||
{
|
||||
List *nodeList = ActivePrimaryNodeList(NoLock);
|
||||
List *nodeList = ActiveReadableNodeList();
|
||||
return list_length(nodeList);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -57,7 +57,9 @@
|
|||
#include "nodes/print.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/planner.h"
|
||||
#include "parser/analyze.h"
|
||||
#include "portability/instr_time.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "tcop/dest.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
#include "tcop/utility.h"
|
||||
|
@ -152,7 +154,11 @@ static void ExplainAnalyzeDestPutTuple(TupleDestination *self, Task *task,
|
|||
HeapTuple heapTuple, uint64 tupleLibpqSize);
|
||||
static TupleDesc ExplainAnalyzeDestTupleDescForQuery(TupleDestination *self, int
|
||||
queryNumber);
|
||||
static char * WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc);
|
||||
static char * WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc,
|
||||
ParamListInfo params);
|
||||
static char * FetchPlanQueryForExplainAnalyze(const char *queryString,
|
||||
ParamListInfo params);
|
||||
static char * ParameterResolutionSubquery(ParamListInfo params);
|
||||
static List * SplitString(const char *str, char delimiter, int maxLength);
|
||||
|
||||
/* Static Explain functions copied from explain.c */
|
||||
|
@ -310,6 +316,8 @@ ExplainSubPlans(DistributedPlan *distributedPlan, ExplainState *es)
|
|||
es->indent += 3;
|
||||
}
|
||||
|
||||
ExplainOpenGroup("Subplan", NULL, true, es);
|
||||
|
||||
if (es->analyze)
|
||||
{
|
||||
if (es->timing)
|
||||
|
@ -352,9 +360,14 @@ ExplainSubPlans(DistributedPlan *distributedPlan, ExplainState *es)
|
|||
}
|
||||
#endif
|
||||
|
||||
ExplainOpenGroup("PlannedStmt", "PlannedStmt", false, es);
|
||||
|
||||
ExplainOnePlanCompat(plan, into, es, queryString, params, NULL, &planduration,
|
||||
(es->buffers ? &bufusage : NULL));
|
||||
|
||||
ExplainCloseGroup("PlannedStmt", "PlannedStmt", false, es);
|
||||
ExplainCloseGroup("Subplan", NULL, true, es);
|
||||
|
||||
if (es->format == EXPLAIN_FORMAT_TEXT)
|
||||
{
|
||||
es->indent -= 3;
|
||||
|
@ -373,7 +386,7 @@ static void
|
|||
ExplainPropertyBytes(const char *qlabel, int64 bytes, ExplainState *es)
|
||||
{
|
||||
Datum textDatum = DirectFunctionCall1(pg_size_pretty, Int64GetDatum(bytes));
|
||||
ExplainPropertyText(qlabel, text_to_cstring(DatumGetTextP(textDatum)), es);
|
||||
ExplainPropertyText(qlabel, TextDatumGetCString(textDatum), es);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1042,14 +1055,25 @@ worker_save_query_explain_analyze(PG_FUNCTION_ARGS)
|
|||
int numParams = boundParams ? boundParams->numParams : 0;
|
||||
Oid *paramTypes = NULL;
|
||||
const char **paramValues = NULL;
|
||||
|
||||
if (boundParams != NULL)
|
||||
{
|
||||
ExtractParametersFromParamList(boundParams, ¶mTypes, ¶mValues, false);
|
||||
}
|
||||
|
||||
List *queryList = pg_analyze_and_rewrite(parseTree, queryString, paramTypes,
|
||||
numParams, NULL);
|
||||
/* resolve OIDs of unknown (user-defined) types */
|
||||
Query *analyzedQuery = parse_analyze_varparams(parseTree, queryString,
|
||||
¶mTypes, &numParams);
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_14
|
||||
|
||||
/* pg_rewrite_query is a wrapper around QueryRewrite with some debugging logic */
|
||||
List *queryList = pg_rewrite_query(analyzedQuery);
|
||||
#else
|
||||
|
||||
/* pg_rewrite_query is not yet public in PostgreSQL 13 */
|
||||
List *queryList = QueryRewrite(analyzedQuery);
|
||||
#endif
|
||||
if (list_length(queryList) != 1)
|
||||
{
|
||||
ereport(ERROR, (errmsg("cannot EXPLAIN ANALYZE a query rewritten "
|
||||
|
@ -1377,9 +1401,21 @@ ExplainAnalyzeTaskList(List *originalTaskList,
|
|||
|
||||
Task *explainAnalyzeTask = copyObject(originalTask);
|
||||
const char *queryString = TaskQueryString(explainAnalyzeTask);
|
||||
char *wrappedQuery = WrapQueryForExplainAnalyze(queryString, tupleDesc);
|
||||
char *fetchQuery =
|
||||
"SELECT explain_analyze_output, execution_duration FROM worker_last_saved_explain_analyze()";
|
||||
ParamListInfo taskParams = params;
|
||||
|
||||
/*
|
||||
* We will not send parameters if they have already been resolved in the query
|
||||
* string.
|
||||
*/
|
||||
if (explainAnalyzeTask->parametersInQueryStringResolved)
|
||||
{
|
||||
taskParams = NULL;
|
||||
}
|
||||
|
||||
char *wrappedQuery = WrapQueryForExplainAnalyze(queryString, tupleDesc,
|
||||
taskParams);
|
||||
char *fetchQuery = FetchPlanQueryForExplainAnalyze(queryString, taskParams);
|
||||
|
||||
SetTaskQueryStringList(explainAnalyzeTask, list_make2(wrappedQuery, fetchQuery));
|
||||
|
||||
TupleDestination *originalTaskDest = originalTask->tupleDest ?
|
||||
|
@ -1401,7 +1437,8 @@ ExplainAnalyzeTaskList(List *originalTaskList,
|
|||
* call so we can fetch its explain analyze after its execution.
|
||||
*/
|
||||
static char *
|
||||
WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc)
|
||||
WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc,
|
||||
ParamListInfo params)
|
||||
{
|
||||
StringInfo columnDef = makeStringInfo();
|
||||
for (int columnIndex = 0; columnIndex < tupleDesc->natts; columnIndex++)
|
||||
|
@ -1453,6 +1490,17 @@ WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc)
|
|||
* number of columns returned by worker_save_query_explain_analyze.
|
||||
*/
|
||||
char *workerSaveQueryFetchCols = (tupleDesc->natts == 0) ? "" : "*";
|
||||
|
||||
if (params != NULL)
|
||||
{
|
||||
/*
|
||||
* Add a dummy CTE to ensure all parameters are referenced, such that their
|
||||
* types can be resolved.
|
||||
*/
|
||||
appendStringInfo(wrappedQuery, "WITH unused AS (%s) ",
|
||||
ParameterResolutionSubquery(params));
|
||||
}
|
||||
|
||||
appendStringInfo(wrappedQuery,
|
||||
"SELECT %s FROM worker_save_query_explain_analyze(%s, %s) AS (%s)",
|
||||
workerSaveQueryFetchCols,
|
||||
|
@ -1464,6 +1512,60 @@ WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* FetchPlanQueryForExplainAnalyze generates a query to fetch the plan saved
|
||||
* by worker_save_query_explain_analyze from the worker.
|
||||
*/
|
||||
static char *
|
||||
FetchPlanQueryForExplainAnalyze(const char *queryString, ParamListInfo params)
|
||||
{
|
||||
StringInfo fetchQuery = makeStringInfo();
|
||||
|
||||
if (params != NULL)
|
||||
{
|
||||
/*
|
||||
* Add a dummy CTE to ensure all parameters are referenced, such that their
|
||||
* types can be resolved.
|
||||
*/
|
||||
appendStringInfo(fetchQuery, "WITH unused AS (%s) ",
|
||||
ParameterResolutionSubquery(params));
|
||||
}
|
||||
|
||||
appendStringInfoString(fetchQuery,
|
||||
"SELECT explain_analyze_output, execution_duration "
|
||||
"FROM worker_last_saved_explain_analyze()");
|
||||
|
||||
return fetchQuery->data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ParameterResolutionSubquery generates a subquery that returns all parameters
|
||||
* in params with explicit casts to their type names. This can be used in cases
|
||||
* where we use custom type parameters that are not directly referenced.
|
||||
*/
|
||||
static char *
|
||||
ParameterResolutionSubquery(ParamListInfo params)
|
||||
{
|
||||
StringInfo paramsQuery = makeStringInfo();
|
||||
|
||||
appendStringInfo(paramsQuery, "SELECT");
|
||||
|
||||
for (int paramIndex = 0; paramIndex < params->numParams; paramIndex++)
|
||||
{
|
||||
ParamExternData *param = ¶ms->params[paramIndex];
|
||||
char *typeName = format_type_extended(param->ptype, -1,
|
||||
FORMAT_TYPE_FORCE_QUALIFY);
|
||||
|
||||
appendStringInfo(paramsQuery, "%s $%d::%s",
|
||||
paramIndex > 0 ? "," : "",
|
||||
paramIndex + 1, typeName);
|
||||
}
|
||||
|
||||
return paramsQuery->data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SplitString splits the given string by the given delimiter.
|
||||
*
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "distributed/multi_physical_planner.h"
|
||||
#include "distributed/pg_dist_partition.h"
|
||||
#include "distributed/query_pushdown_planning.h"
|
||||
#include "distributed/string_utils.h"
|
||||
#include "distributed/tdigest_extension.h"
|
||||
#include "distributed/worker_protocol.h"
|
||||
#include "distributed/version_compat.h"
|
||||
|
@ -58,9 +59,6 @@
|
|||
#include "utils/rel.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
#define StartsWith(msg, prefix) \
|
||||
(strncmp(msg, prefix, strlen(prefix)) == 0)
|
||||
|
||||
/* Config variable managed via guc.c */
|
||||
int LimitClauseRowFetchCount = -1; /* number of rows to fetch from each task */
|
||||
double CountDistinctErrorRate = 0.0; /* precision of count(distinct) approximate */
|
||||
|
@ -3418,7 +3416,7 @@ GetAggregateType(Aggref *aggregateExpression)
|
|||
* perform these checks if there is some chance it will actually result in a positive
|
||||
* hit.
|
||||
*/
|
||||
if (StartsWith(aggregateProcName, "tdigest"))
|
||||
if (StringStartsWith(aggregateProcName, "tdigest"))
|
||||
{
|
||||
if (aggFunctionId == TDigestExtensionAggTDigest1())
|
||||
{
|
||||
|
|
|
@ -75,7 +75,6 @@ static Oid NodeTryGetRteRelid(Node *node);
|
|||
static bool FullCompositeFieldList(List *compositeFieldList);
|
||||
static bool HasUnsupportedJoinWalker(Node *node, void *context);
|
||||
static bool ErrorHintRequired(const char *errorHint, Query *queryTree);
|
||||
static bool HasTablesample(Query *queryTree);
|
||||
static bool HasComplexRangeTableType(Query *queryTree);
|
||||
static bool IsReadIntermediateResultFunction(Node *node);
|
||||
static bool IsReadIntermediateResultArrayFunction(Node *node);
|
||||
|
@ -899,14 +898,6 @@ DeferErrorIfQueryNotSupported(Query *queryTree)
|
|||
errorHint = filterHint;
|
||||
}
|
||||
|
||||
bool hasTablesample = HasTablesample(queryTree);
|
||||
if (hasTablesample)
|
||||
{
|
||||
preconditionsSatisfied = false;
|
||||
errorMessage = "could not run distributed query which use TABLESAMPLE";
|
||||
errorHint = filterHint;
|
||||
}
|
||||
|
||||
bool hasUnsupportedJoin = HasUnsupportedJoinWalker((Node *) queryTree->jointree,
|
||||
NULL);
|
||||
if (hasUnsupportedJoin)
|
||||
|
@ -960,28 +951,6 @@ DeferErrorIfQueryNotSupported(Query *queryTree)
|
|||
}
|
||||
|
||||
|
||||
/* HasTablesample returns tree if the query contains tablesample */
|
||||
static bool
|
||||
HasTablesample(Query *queryTree)
|
||||
{
|
||||
List *rangeTableList = queryTree->rtable;
|
||||
ListCell *rangeTableEntryCell = NULL;
|
||||
bool hasTablesample = false;
|
||||
|
||||
foreach(rangeTableEntryCell, rangeTableList)
|
||||
{
|
||||
RangeTblEntry *rangeTableEntry = lfirst(rangeTableEntryCell);
|
||||
if (rangeTableEntry->tablesample)
|
||||
{
|
||||
hasTablesample = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hasTablesample;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* HasUnsupportedJoinWalker returns tree if the query contains an unsupported
|
||||
* join type. We currently support inner, left, right, full and anti joins.
|
||||
|
@ -1541,6 +1510,7 @@ MultiTableNodeList(List *tableEntryList, List *rangeTableList)
|
|||
tableNode->alias = rangeTableEntry->alias;
|
||||
tableNode->referenceNames = rangeTableEntry->eref;
|
||||
tableNode->includePartitions = GetOriginalInh(rangeTableEntry);
|
||||
tableNode->tablesample = rangeTableEntry->tablesample;
|
||||
|
||||
tableNodeList = lappend(tableNodeList, tableNode);
|
||||
}
|
||||
|
|
|
@ -735,6 +735,7 @@ BaseRangeTableList(MultiNode *multiNode)
|
|||
rangeTableEntry->alias = multiTable->alias;
|
||||
rangeTableEntry->relid = multiTable->relationId;
|
||||
rangeTableEntry->inh = multiTable->includePartitions;
|
||||
rangeTableEntry->tablesample = multiTable->tablesample;
|
||||
|
||||
SetRangeTblExtraData(rangeTableEntry, CITUS_RTE_RELATION, NULL, NULL,
|
||||
list_make1_int(multiTable->rangeTableId),
|
||||
|
|
|
@ -158,7 +158,7 @@ RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId)
|
|||
AccessShareLock,
|
||||
missingOk);
|
||||
|
||||
if (constraint->contype == CONSTR_PRIMARY && constraint->indexname)
|
||||
if (constraint->indexname)
|
||||
{
|
||||
char **indexName = &(constraint->indexname);
|
||||
AppendShardIdToName(indexName, shardId);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -37,6 +37,7 @@
|
|||
#include "distributed/connection_management.h"
|
||||
#include "distributed/cte_inline.h"
|
||||
#include "distributed/distributed_deadlock_detection.h"
|
||||
#include "distributed/errormessage.h"
|
||||
#include "distributed/insert_select_executor.h"
|
||||
#include "distributed/intermediate_result_pruning.h"
|
||||
#include "distributed/local_multi_copy.h"
|
||||
|
@ -44,6 +45,7 @@
|
|||
#include "distributed/local_distributed_join_planner.h"
|
||||
#include "distributed/locally_reserved_shared_connections.h"
|
||||
#include "distributed/maintenanced.h"
|
||||
#include "distributed/shard_cleaner.h"
|
||||
#include "distributed/metadata_utility.h"
|
||||
#include "distributed/coordinator_protocol.h"
|
||||
#include "distributed/metadata_cache.h"
|
||||
|
@ -52,6 +54,7 @@
|
|||
#include "distributed/multi_executor.h"
|
||||
#include "distributed/multi_explain.h"
|
||||
#include "distributed/multi_join_order.h"
|
||||
#include "distributed/multi_logical_replication.h"
|
||||
#include "distributed/multi_logical_optimizer.h"
|
||||
#include "distributed/distributed_planner.h"
|
||||
#include "distributed/combine_query_planner.h"
|
||||
|
@ -59,6 +62,7 @@
|
|||
#include "distributed/multi_server_executor.h"
|
||||
#include "distributed/pg_dist_partition.h"
|
||||
#include "distributed/placement_connection.h"
|
||||
#include "distributed/query_stats.h"
|
||||
#include "distributed/recursive_planning.h"
|
||||
#include "distributed/reference_table_utils.h"
|
||||
#include "distributed/relation_access_tracking.h"
|
||||
|
@ -74,7 +78,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/worker_log_messages.h"
|
||||
|
@ -127,9 +131,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);
|
||||
|
@ -152,6 +156,12 @@ static const struct config_enum_entry propagate_set_commands_options[] = {
|
|||
};
|
||||
|
||||
|
||||
static const struct config_enum_entry stat_statements_track_options[] = {
|
||||
{ "none", STAT_STATEMENTS_TRACK_NONE, false },
|
||||
{ "all", STAT_STATEMENTS_TRACK_ALL, false },
|
||||
{ NULL, 0, false }
|
||||
};
|
||||
|
||||
static const struct config_enum_entry task_assignment_policy_options[] = {
|
||||
{ "greedy", TASK_ASSIGNMENT_GREEDY, false },
|
||||
{ "first-replica", TASK_ASSIGNMENT_FIRST_REPLICA, false },
|
||||
|
@ -601,6 +611,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.binary_worker_copy_format",
|
||||
gettext_noop("Use the binary worker copy format."),
|
||||
|
@ -765,7 +812,7 @@ RegisterCitusConfigVariables(void)
|
|||
"workers"),
|
||||
NULL,
|
||||
&EnableAlterDatabaseOwner,
|
||||
false,
|
||||
true,
|
||||
PGC_USERSET,
|
||||
GUC_NO_SHOW_ALL,
|
||||
NULL, NULL, NULL);
|
||||
|
@ -818,6 +865,17 @@ RegisterCitusConfigVariables(void)
|
|||
GUC_NO_SHOW_ALL,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomBoolVariable(
|
||||
"citus.enable_create_role_propagation",
|
||||
gettext_noop("Enables propagating CREATE ROLE "
|
||||
"and DROP ROLE statements to workers"),
|
||||
NULL,
|
||||
&EnableCreateRolePropagation,
|
||||
true,
|
||||
PGC_USERSET,
|
||||
GUC_STANDARD,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomBoolVariable(
|
||||
"citus.enable_create_type_propagation",
|
||||
gettext_noop("Enables propagating of CREATE TYPE statements to workers"),
|
||||
|
@ -1121,24 +1179,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,
|
||||
|
@ -1283,6 +1323,18 @@ RegisterCitusConfigVariables(void)
|
|||
GUC_STANDARD,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomIntVariable(
|
||||
"citus.logical_replication_timeout",
|
||||
gettext_noop("Sets the timeout to error out when logical replication is used"),
|
||||
gettext_noop("Citus uses logical replication when it moves/replicates shards. "
|
||||
"This setting determines when Citus gives up waiting for progress "
|
||||
"during logical replication and errors out."),
|
||||
&LogicalReplicationTimeout,
|
||||
2 * 60 * 60 * 1000, 0, 7 * 24 * 3600 * 1000,
|
||||
PGC_SIGHUP,
|
||||
GUC_NO_SHOW_ALL | GUC_UNIT_MS,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomIntVariable(
|
||||
"citus.max_adaptive_executor_pool_size",
|
||||
gettext_noop("Sets the maximum number of connections per worker node used by "
|
||||
|
@ -1353,6 +1405,17 @@ RegisterCitusConfigVariables(void)
|
|||
GUC_UNIT_KB | GUC_STANDARD,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomIntVariable(
|
||||
"citus.max_matview_size_to_auto_recreate",
|
||||
gettext_noop("Sets the maximum size of materialized views in MB to "
|
||||
"automatically distribute them."),
|
||||
NULL,
|
||||
&MaxMatViewSizeToAutoRecreate,
|
||||
1024, -1, INT_MAX,
|
||||
PGC_USERSET,
|
||||
GUC_UNIT_MB | GUC_STANDARD,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomIntVariable(
|
||||
"citus.max_rebalancer_logged_ignored_moves",
|
||||
gettext_noop("Sets the maximum number of ignored moves the rebalance logs"),
|
||||
|
@ -1677,6 +1740,41 @@ 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.skip_jsonb_validation_in_copy",
|
||||
gettext_noop("Skip validation of JSONB columns on the coordinator during COPY "
|
||||
"into a distributed table"),
|
||||
gettext_noop("Parsing large JSON objects may incur significant CPU overhead, "
|
||||
"which can lower COPY throughput. If this GUC is set (the default), "
|
||||
"JSON parsing is skipped on the coordinator, which means you cannot "
|
||||
"see the line number in case of malformed JSON, but throughput will "
|
||||
"be higher. This setting does not apply if the input format is "
|
||||
"binary."),
|
||||
&SkipJsonbValidationInCopy,
|
||||
true,
|
||||
PGC_USERSET,
|
||||
0,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomBoolVariable(
|
||||
"citus.sort_returning",
|
||||
gettext_noop("Sorts the RETURNING clause to get consistent test output"),
|
||||
|
@ -1691,6 +1789,47 @@ RegisterCitusConfigVariables(void)
|
|||
GUC_NO_SHOW_ALL,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
/*
|
||||
* It takes about 140 bytes of shared memory to store one row, therefore
|
||||
* this setting should be used responsibly. setting it to 10M will require
|
||||
* 1.4GB of shared memory.
|
||||
*/
|
||||
DefineCustomIntVariable(
|
||||
"citus.stat_statements_max",
|
||||
gettext_noop("Determines maximum number of statements tracked by "
|
||||
"citus_stat_statements."),
|
||||
NULL,
|
||||
&StatStatementsMax,
|
||||
50000, 1000, 10000000,
|
||||
PGC_POSTMASTER,
|
||||
GUC_NO_SHOW_ALL,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomIntVariable(
|
||||
"citus.stat_statements_purge_interval",
|
||||
gettext_noop("Determines time interval in seconds for "
|
||||
"citus_stat_statements to purge expired entries."),
|
||||
NULL,
|
||||
&StatStatementsPurgeInterval,
|
||||
10, -1, INT_MAX,
|
||||
PGC_SIGHUP,
|
||||
GUC_UNIT_MS | GUC_NO_SHOW_ALL,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomEnumVariable(
|
||||
"citus.stat_statements_track",
|
||||
gettext_noop(
|
||||
"Enables/Disables the stats collection for citus_stat_statements."),
|
||||
gettext_noop("Enables the stats collection when set to 'all'. "
|
||||
"Disables when set to 'none'. Disabling can be useful for "
|
||||
"avoiding extra CPU cycles needed for the calculations."),
|
||||
&StatStatementsTrack,
|
||||
STAT_STATEMENTS_TRACK_NONE,
|
||||
stat_statements_track_options,
|
||||
PGC_SUSET,
|
||||
GUC_STANDARD,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomBoolVariable(
|
||||
"citus.subquery_pushdown",
|
||||
gettext_noop("Usage of this GUC is highly discouraged, please read the long "
|
||||
|
@ -1946,12 +2085,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;
|
||||
|
||||
|
@ -1981,7 +2120,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;
|
||||
}
|
||||
|
@ -1992,12 +2131,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();
|
||||
}
|
||||
|
@ -2011,6 +2150,7 @@ static void
|
|||
ApplicationNameAssignHook(const char *newval, void *extra)
|
||||
{
|
||||
ResetHideShardsDecision();
|
||||
ResetCitusBackendType();
|
||||
OldApplicationNameAssignHook(newval, extra);
|
||||
}
|
||||
|
||||
|
@ -2036,8 +2176,10 @@ NodeConninfoGucCheckHook(char **newval, void **extra, GucSource source)
|
|||
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
|
||||
"krbsrvname",
|
||||
#endif
|
||||
"sslcert",
|
||||
"sslcompression",
|
||||
"sslcrl",
|
||||
"sslkey",
|
||||
"sslmode",
|
||||
"sslrootcert",
|
||||
"tcp_user_timeout",
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#include "udfs/citus_shards_on_worker/11.0-2.sql"
|
||||
#include "udfs/citus_shard_indexes_on_worker/11.0-2.sql"
|
||||
#include "udfs/citus_disable_node/11.0-2.sql"
|
||||
#include "udfs/citus_is_coordinator/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"
|
||||
#include "udfs/citus_finish_citus_upgrade/11.0-2.sql"
|
|
@ -0,0 +1 @@
|
|||
#include "udfs/citus_finalize_upgrade_to_citus11/11.0-3.sql"
|
|
@ -1461,8 +1461,17 @@ CREATE FUNCTION pg_catalog.citus_server_id()
|
|||
COMMENT ON FUNCTION citus_server_id()
|
||||
IS 'generates a random UUID to be used as server identifier';
|
||||
|
||||
-- Insert the latest extension version into pg_dist_node_metadata
|
||||
-- for new installations.
|
||||
--
|
||||
-- While users could technically upgrade to an intermediate version
|
||||
-- everything in Citus fails until it is upgraded to the latest version,
|
||||
-- so it seems safe to use the latest.
|
||||
INSERT INTO pg_dist_node_metadata
|
||||
VALUES (jsonb_build_object('server_id', citus_server_id()::text));
|
||||
SELECT jsonb_build_object('server_id', citus_server_id()::text,
|
||||
'last_upgrade_version', default_version)
|
||||
FROM pg_available_extensions
|
||||
WHERE name = 'citus';
|
||||
|
||||
-- rebalancer functions
|
||||
CREATE TYPE citus.shard_transfer_mode AS ENUM (
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
#include "../udfs/citus_shards_on_worker/11.0-1.sql"
|
||||
#include "../udfs/citus_shard_indexes_on_worker/11.0-1.sql"
|
||||
#include "../udfs/citus_finalize_upgrade_to_citus11/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 PROCEDURE pg_catalog.citus_finish_citus_upgrade();
|
|
@ -0,0 +1 @@
|
|||
#include "../udfs/citus_finalize_upgrade_to_citus11/11.0-2.sql"
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
221
src/backend/distributed/sql/udfs/citus_finalize_upgrade_to_citus11/11.0-2.sql
generated
Normal file
221
src/backend/distributed/sql/udfs/citus_finalize_upgrade_to_citus11/11.0-2.sql
generated
Normal 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
Loading…
Reference in New Issue